Yesterday’s blog post on Java 5 and PeopleTools 8.49 (and my high hopes that it would have fixed something that been annoying me) didn’t have a happy ending. PeopleTools doesn’t (yet) take advantage of the some of the Java 5 features, so we have to stick with some workarounds for the time being.
PeopleTools 8.49 using Java 5 does provide me another good new thing to work on though. The
open source Asterisk-Java library requires Java 5 at a minimum, but with PeopleTools 8.49, that’s OK. What the library provides is a good Java level interface into the
open source Asterisk telephony platform.
There’s tons of material about Asterisk available, both online and printed books, so this blog post won’t go into too much background on it. If you want to follow along though, you can’t go wrong with the different downloadable Asterisk appliances out there (PBX in a Flash, AsteriskNOW,
etc.).
This blog post is actually sort of a lead-in for one of the sessions that I submitted for this year via Mix was called “Telephony Integration with PeopleSoft on the cheap”. I don’t know whether that session will actually make it in for OpenWorld (there are an astounding number of sessions that have been submitted via Mix), but it’s a good topic, so I thought I’d start blogging it.
For this blog post, what we want to do is be able to initiate a phone call to the end user from PeopleSoft, prompt them for a PIN code, and take action in PeopleSoft depending on whether they were successful or not. We may want to do this as part of the initial signon process for
two-factor authentication of your PeopleSoft users, or we might tie this in with some business logic (e.g. be really sure who is sending off a large wire transfer). For the business logic use case, we recommend that you utilize an application firewall to simplify the configuration and maintenance of it. Our own ERP Firewall for PeopleSoft product has a demonstration on our product page of doing 2-factor authentication using Instant Messaging technology (a similar technique to that used here).
For testing, I always like to whip up an App Engine test program for playing around. Let’s take a look at our first attempt at the code, then we’ll look at the Asterisk configuration.
REM Connectivity to Asterisk server. ;
REM Assumes that &user has been granted access to Asterisk Manager API;
Local string &host = "192.168.1.200";
Local string &user = "asterisk_user_id";
Local string &pswd = "asterisk_password";
REM Asterisk information for how we will be originating phone calls;
Local String &voip_provider_acct = "123456789</code><code>";
Local string &channel = "IAX2/" | </code><code>&voip_provider_acct</code><code>;
Local string &context = "challenge-psft-user";
Local string &exten = "7189";
REM The phone number that we will be calling;
REM This would normally come out of the user's profile;
Local string &phone = "15105551212";
REM The PeopleSoft userID of the person to call;
Local string &userID = "PTDMO";
REM This PIN could be autogenerated or stored in the DB;
Local string &pinCode = "4554";
REM ******** No more user variables below here *********** ;
Local string &base = "org.asteriskjava.manager.";
Local JavaObject &connectFactory = CreateJavaObject(&base | "ManagerConnectionFactory", &host, &user, &pswd);
Local JavaObject &connection = &connectFactory.createManagerConnection();
&connection.login();
Local JavaObject &originateAction = CreateJavaObject(&base | "action.OriginateAction");
Local JavaObject &vars = CreateJavaObject("java.util.HashMap");
&vars.put("PIN", &pinCode);
&vars.put("PSFTUSERID", &userID);
&originateAction.setChannel(&channel | "/" | &phone);
&originateAction.setContext(&context);
&originateAction.setVariables(&vars);
&originateAction.setExten(&exten);
&originateAction.setPriority(CreateJavaObject("java.lang.Integer", 1));
Local JavaObject &originateResponse = &connection.sendAction(&originateAction, 30000);
Warning (&originateResponse.getResponse());
After setting up a bunch of variables that we’ll need, the code begins by connecting to the Manager API of our Asterisk server. We’ll need a user/password here that has appropriate access to the Manager API; there probably won’t be any in your default install of Asterisk so you’ll need to add one. This is not an end-user account though; it’s a service account that should be treated with the appropriate security. The other configuration note here is that the Manager API lets you limit access with an IP address range for each user, so in addition to having a strong password, you should limit the user to only be able to connect from your PeopleSoft servers.
After that, the code creates a call origination object. The Asterisk Manager API allows us to originate a call between a channel and an extension that is the Asterisk dialplan. A channel can be a lot of things (different Voice over IP protocols, regular phone network, etc.). In this case, I’m using a IAX2 channel that I’ve previously defined in Asterisk to use a VoIP (Voice over Internet Protocol) connection to the outside world. IAX2 is an Asterisk specific protocol similar to the more commonly known standard
SIP (for example, both Oracle and BEA have SIP servers).
The nice thing is that you don’t need to know too much about the channel details for experimentation purposes though. For example, for this test, I use a pay-by-the-drip VoIP provider called
CallWithUs. There’s other providers out there (and we use some of those also), but CallWithUs have a nice web based provisioning system. Sign up through their webpage, send ’em some money, and you’re all set. You then supply the info that they give you to Asterisk, so when you ask Asterisk to make a call, it can send it along through CallWithUs, who then connect the call. If you’re using one of the Asterisk appliances, then you can probably get your first call happening in 30-60 minutes or so.
The nice thing about doing it this way is that this can all be running on standard computer equipment. Everything that we’re doing is software based, and CallWithUs deal with the part of actually connecting to “real” phone system.
As part of the channel definition for the Asterisk Manager API, we add the end-user’s phone numb
er to our “dial string”, which Asterisk sends off to CallWithUs, who make the phone call to the end user.
That’s one half of the call equation; my mobile phone is now ringing! Here’s what we see in the Asterisk console with the logging turned up.
== Parsing '/etc/asterisk/manager.conf': Found
== Manager 'asterisk_user_id' logged on from 192.168.1.11
-- Call accepted by 64.85.163.184 (format ulaw)
-- Format for call is ulaw
As soon as I answered the phone, then the Asterisk console shows
> Channel IAX2/123456789-2 was answered.
If you’ll remember our original requirements, we needed some programmatic validation of a PIN code through the phone. The cool thing about Asterisk is that there are tons of built-in options for how you handle a call coming in. Here’s a snippet from the Asterisk configuration. This defines what Asterisk calls a “context” and what to do with a call into that context for extension 7189. The name of the context and the extension don’t really matter; we just need for our PeopleCode to match up with Asterisk has.
[challenge-psft-user]
exten => 7189,1,Answer()
exten => 7189
,2,Playback(gs-demo-login)
exten =>
7189
,3,NoOp(Authenticating user ${PSFTUSERID})
exten =>
7189
,4,Authenticate(${PIN},j)
exten =>
7189
,5,NoOp(Successful login ${PSFTUSERID})
exten =>
7189
,6,Hangup()
exten =>
7189
,105,NoOp(Unsuccessful login ${PSFTUSERID})
exten =>
7189
,106,Hangup()
One thing that may be a little confusing here is that we’re using Asterisk from two sides. One is our code that is calling the Asterisk Manager API that is initiating the call between my mobile phone and this context/extension definition within Asterisk. However, the Manager API is just initiating the call and then it’s done. The context/extension definition within Asterisk then says what to do with the call to this context/extension.
Line 1 of the context/extension definition says to Answer the call. Line 2 plays a message that explains what is happening. On my mobile phone, I hear “This is the Grey Sparling demo login. “. Line 3 just logs what is happening on the Asterisk console. We use one of the variables that was set in the PeopleCode side so that we can match up calls with the PeopleSoft user accounts.
Line 4 is a builtin Asterisk command to challenge the user to type in a PIN code. Here we’re using the PIN code that was set from the PeopleCode side. There are a series of voice prompts already delivered in Asterisk that get played as part of this as well as Asterisk “listening” for the DTMF tones from the buttons on the phone being pushed. You can roll your own handling of this within Asterisk (speech recognition anyone?), but the Authenticate command has a lot of built-in functionality for free so we make use of that.
One strange thing worth mentioning here is the “j” parameter after the PIN code. That tells Asterisk to jump “+101” if the command fails (it continues on the next line if successful). It’s a bizarre form of
GOTO (note that there are other ways of adding logic to Asterisk though).
Then we log what happened on the console (depending on whether we get the right PIN code or not) and then hangup the phone. Here’s what the Asterisk console looks like with an invalid login.
-- Executing [7189@challenge-psft-user:1] Answer("IAX2/123456789-2", "") in new stack
-- Executing [7189@challenge-psft-user:2] Playback("IAX2/123456789-2", "gs-demo-login") in new stack
-- Playing 'gs-demo-login' (language 'en')
== Manager 'asterisk_user_id' logged off from 192.168.1.11
-- Executing [7189@challenge-psft-user:3] NoOp("IAX2/123456789-2", "Authenticating user PTDMO") in new stack
-- Executing [7189@challenge-psft-user:4] Authenticate("IAX2/123456789-2", "4554|j") in new stack
-- Playing 'agent-pass' (language 'en')
-- Playing 'auth-incorrect' (language 'en')
-- Playing 'auth-incorrect' (language 'en')
-- Executing [7189@challenge-psft-user:105] NoOp("IAX2/123456789-2", "Unsuccessful login PTDMO") in new stack
-- Executing [7189@challenge-psft-user:106] Hangup("IAX2/123456789-2", "") in new stack
== Spawn extension (challenge-psft-user, 7189, 106) exited non-zero on 'IAX2/123456789-2'
-- Hungup 'IAX2/123456789-2'
We can see that in this instance I didn’t type in the PIN code correctly, so I wouldn’t have been granted access. Here’s the relevant bits from the Asterisk console of a successful login.
-- Playing 'agent-pass' (language 'en')
-- Playing 'auth-thankyou' (language 'en')
-- Executing [7189@challenge-psft-user:5] NoOp("IAX2/123456789-3", "Successful login PTDMO") in new stack
-- Executing [7189@challenge-psft-user:6] Hangup("IAX2/123456789-3", "") in new stack
But how do we find that out within the PeopleCode side so that we can actually take action?
That will have to wait until part 2
One final note here. I mentioned that CallWithUs is pay by the drip. One reason that is important is because the pay by the drip VoIP providers are typically more open to initiating multiple calls at once (since you’re not paying a flat fee), which is something that you’d need for doing this for authenticating users.
Once you’re comfortable with doing this sort of thing, then you might want to get your own internal phone folks involved, but since this sort of thing is fairly uncommon at this point, you’ll probably be on your own for your initial experiments in this.
The nice thing is that it’s fairly cheap though. For each call to my mobile phone that was initiated while testing this I paid US$0.0125. So you get 80 login attempts for a buck 🙂