Search

What the Quote?

"So it's a little like 'E.T.', if E.T. was eating people."

Chris Toohey

"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning."

Rich Cook

"We thought we'd be brilliant and just copy your code, and your code said, 'Yeah, you're funny'."

Monica Ferrante

08/26/2010

reflections on IamLUG and MWLUG

Category iamlug mwlug

Now that my busiest month since Lotusphere is drawing to a close, I wanted to share just a few of my thoughts about two recent conferences I had the pleasure to attend: IamLUG and MWLUG.


Both of these conferences were extremely well organized.
Although each was only two days (not counting peripheral elements, such as the welcome reception at MWLUG and the follow-on day at IamLUG), these weren't user group meetings... they were definitely conferences. As I mentioned in person to some of the organizers of each, what amazes me most is that, while IBM had some involvement in the form of sponsorship, keynotes and the like, the actual organization of the events - choosing and scheduling sessions, communicating with speakers and attendees, coordinating with venue personnel, and all of the many details I can't even think of that are crucial to making events like these useful and enjoyable - all of these tasks were performed solely by members of the community who are passionate enough to sacrifice whatever time, sanity, and other personal comforts they had to in order to not only pull it off, but make it look easy.


No hotel's ISP can survive a horde of Yellowbleeders.
At both events, the wireless held up pretty well the first day, then died spectacularly that night. Then came back up. Then crashed again. Repeatedly. Eventually, whoever was trying to resuscitate it went to sleep, and the external connection was down the rest of the night, revived just barely before sessions resumed in the morning. It was eerie how similarly this played out at both events. Seriously, if someone were to show me crash logs from the two routers that showed identical timeframes between, and duration of, the outages, I wouldn't be surprised. By the time I left Cleveland on Saturday, I was even half tempted to cut Disney some slack for their perennial failure to accommodate our hunger for bandwidth... but only half tempted. If I can get 20 Mb/s down, 4 Mb/s up for $50 a month - as an individual consumer with no bargaining clout - surely a Hilton Garden Inn can afford more than a 384 Kb/s DSL line... and "the award-winning Walt Disney World Swan and Dolphin" has absolutely no excuse: they charge about 4 times that much each night for a single room during Lotusphere, and most of us that stay onsite stay for at least 4 nights. It baffles me that Internet is still such a problem for almost every hotel, yet Chick-fil-A can figure it out... and offer it for free.


I get by with a little help from my friends.
I always enjoy getting to hang out with my Lotus pals. It might surprise some that only know my blog persona, but I'm an introvert. It's often a struggle of Herculean proportions to force myself to interact with others, especially face-to-face. Not so when I'm with Yellowbleeders. Perhaps knowing that we have a common interest reassures me that this is one place where I don't have to fear awkward silence, because we're never going to run out of things to talk about. But I've also been fortunate to encounter at events like these numerous folks whom I would genuinely enjoy being around, even if we didn't share professional interests. So each time I get to attend a user group meeting, a mid-year conference, or Lotusphere, I leave socially rejuvenated as well as professionally inspired. For me, at least, that's the true value of these events: I come back to my day job, keenly aware of why I do what I do; in our industry niche, that awareness alone can be powerful.


A track-within-a-track can be very effective.
A couple of the MWLUG speakers noticed in advance that there seemed to be a natural progression inherent in the XPage-related sessions, and suggested to the organizers that the agenda be scheduled in a specific order to allow each session to segue into the next, creating a sort of "XPages boot camp". The organizers liked the idea, and, aside from a tiny bit of overlap (very difficult to avoid when you're squeezing 45 sessions across 3 tracks into a 2 day event), the result was perfect. In fact, the result was so effective that, in retrospect, I'm actually glad that my "grand finale" demo crashed on me. I know that sounds strange, but it allowed me to witness something I wouldn't otherwise have been able to see firsthand.


You see, the entire goal of my session ("XPages: the Evolution of Possible") was to challenge the attendees to discard all assumptions of limitation - to believe that we can now do anything with Domino, even if we don't yet know how. Some of the demonstrations illustrated functionality that was possible but difficult or hacktastic in prior versions, but the final demo would have shown something that I've never even seen attempted without XPages... something that is a fundamental departure from our very definition of the word "collaboration" in the context of the platform. Those of you who saw it in action at IamLUG know what I'm talking about. To be perfectly honest, if someone had told me that XPages make this not only possible but easy back when I was first learning XPages, I would have been extremely skeptical. Point of fact, I suspect I wouldn't have believed it. After all, at the time, I just thought the big win with XPages was that they let us do joins in views.


I really, really wanted to be able to show this demo, because I feel it perfectly illustrates why I get so excited about this stuff. When I realized it wasn't going to happen, I confessed my disappointment to the attendees, stating that a mere verbal description of the functionality just wouldn't do it justice (which is why I'm now being intentionally vague about what the demo was). But then something fascinating happened: as I proceeded to provide a mere verbal description of the functionality, I glanced around the room... undistracted by slides, browser windows, Designer, or anything else I'd been intending to call to their attention, I could simply look people in the eye and tell them an incredible story about what XPages had empowered me to create. And here's the punchline: having listened for two days to some amazing speakers (among others, Scott Good, Dave Leedy, Mike McGarel, and Roy Rumaner,) explaining what XPages are, what technologies are fundamental to working with them (including some we've long been familiar with, like Notes Formula and LotusScript), and what features we can add to our applications that we never could before... I could tell that they believed me. I didn't even have to show them; they didn't ask me to "prove" to them that this crazy thing we would previously never have dared attempt actually works. I simply looked them in the eye and told them, and they knew I was telling the truth. I think that's amazing, and I don't think that would have been possible had the sequence and quality of the presentations leading up to mine not prepared them to discard the very assumptions of limitation I'd been hoping to convince them to abandon. In short, while most (if not all) speakers want every presentation to go exactly as planned, this one unexpected failure turned out to be the best proof I could have hoped for that my session achieved - for at least a few folks - precisely what I'd hoped.


So I want to thank the organizers, fellow presenters, and attendees of both events for helping to make this one of the most fun and inspiring months I've had in a very, very long time. I hope to see many of you again soon...


08/24/2010

alternate URL for my blog: xmage.gbs.com

Category musings

Like Nathan, I am now rocking a shiny new dedicated GROUP Live instance, so going forward, you can get to this blog via xmage.gbs.com. The blog database on that server is just a replica of the one that was already on Frodo, so for the foreseeable future, you'll still be able to get here via TimTripcony.com - no need to update your feed readers, bookmarks, tattoos, etc., but the xmage server is generally going to be faster and comparatively impervious to Canton's frequent thunderstorms, so you might want to anyway.


07/21/2010

fantastic new single from Alexa Wilkinson

Category music

My favorite singer/songwriter, Alexa Wilkinson has just released a great new single: "Waiting". You should totally check it out. Also, one of my favorite songs of hers, "Grey Skies", is included on an incredible compilation album, "1% for the Planet - The Music, Vol. 1". 41 tracks for $9.99. I'm a little fuzzy on the math, but if I'm not mistaken, that's, like, 24 cents a song.

Oh, and she's touring with Lelia Broussard August 11 - 28. If they're coming to your town (or within driving distance), you should drop by.

P.S. I'm aware that mentioning three different product/service offerings in a single blog post constitutes "excessive commercial promotion" (albeit on someone else's behalf). Then again, my site, my prerogative.


07/07/2010

keeping things in perspective

Category eclipse

Because Domino Designer is now based on Eclipse, we suddenly have these wonderful things called "perspectives": basically, that term just means the sum total of all the interface components you see. In the case of Designer, that includes (among others) the applications navigator, control and data palettes, and panes for properties, events and problems. But (again, because Designer is now based on Eclipse) we can finally customize what Designer looks like: we can move stuff around, remove what we don't use, and add Eclipse components that don't necessarily have anything to do with Designer. To add any optional component, simply select Window > Show Eclipse Views > Other, and choose from the categorized list that displays.


Arguably the most useful component that is available, but not enabled by default, is the "Package Explorer" (available in the aforementioned dialog under the "Java" category): this allows you to navigate the structure of any application as though it were a series of folders and subfolders on a network drive. This also allows you to navigate to portions of the NSF structure that you can't see at all from the applications navigator. Not only does this make it more convenient to incorporate custom (or downloaded) Java into your XPage applications than switching entirely to the Java perspective, but it has other convenient benefits even if you're not directly using Java at all.


For example, suppose you want to use the Blueprint CSS framework in your application, and don't have the luxury of an administrator who is willing to put the framework files directly on the server. In the Package Explorer, between the folder for Views and the folder for XPages, you'll see another folder called WebContent. Any files placed in this folder are treated as though they exist at the "root" of the NSF; similarly, subfolders created within this folder are treated as subfolders of the NSF. As a result, you can simply drag the parent folder for the Blueprint framework directly from your hard drive to that folder, and it will store the entire framework inside the NSF, using the same subfolder hierarchy it uses on your hard drive. You can then reference those files in your XPages (or even in traditional Domino design elements) relative to the root path of the NSF. This isn't the ideal, of course, but again, some administrators simply won't budge on these sorts of things, even if it means we have to duplicate storage and prevent users from caching CSS/image resources across applications.


In addition to adding Eclipse "views" to a perspective, we can also modify which menu options show up in various contexts by selecting Window > Customize Perspective. For instance, if you will be adding Java to your XPage applications but don't want to spend all day in the full-blown Java perspective, on the Shortcuts tab of the Customize Perspective dialog, select the "New" submenu, then enable whatever you want to see in that menu when you right-click an item in the Package Explorer. In my case, I've enabled "Folder" (under "General") and "Class", "Enum", "Interface" and "Package" (under "Java"). This saves me the hassle of having to choose New > Other > Java > Class > Next every time I want to add a class to my project... instead, I can just right click the package it will be added to and select New > Class. Sure, it only saves me three clicks each time, but it just feels so much faster, annoys me less, and over the course of a complex project, I have no doubt the savings add up.


There are various other ways in which you can tweak your perspective settings. Just be sure that, once you've made any changes that you want to be permanent (or, at least, to survive a restart), select Window > Save Perspective As - you can either just overwrite the existing perspective (this is the default behavior) or you can enter a new name, which will create a new perspective based on the current settings. Conversely, if you've made some changes and decide you'd rather go back to what you had, select Window > Reset Perspective, which will revert the settings to the defaults.


07/01/2010

the power of the extensibility API

Category xpages
Disclaimer: this post discusses features planned for version 8.5.2 of Lotus Notes and Domino. Version 8.5.2 has not yet been officially released. Ergo, what I'm about to describe to you may well vanish when the real thing arrives... if it does, I will be seriously tempted to continue using the beta version indefinitely. That is how powerful I consider the focus of this post to be.

You might well be wondering what could possibly temper my tendency to upgrade to each bleeding edge version the very moment I can get my hands on it. Back in April, I referred to 8.5.2 as "primarily a nuance release". Multi-threaded replication, purty icons... even updates to the bundled version of Dojo and the XPiNC XUL engine... nifty stuff, but that's all just polish. Polish, of course, is a very good thing. But these all absolutely pale in the face of the biggest change slated for 8.5.2: the XPages Extensibility API.

What is the Extensibility API? Simply put, it allows you to write your own controls.

No, I'm not talking about Custom Controls. I agree, the terminology is confusing: surely, if you're writing your own controls, those are custom controls, right? Well, yes, in a purely etymological sense, that's true. But in a Domino Designer terminology sense, this has (almost) nothing to do with the Custom Control design element that was added to the NSF model in 8.5.0. Nope... this is way bigger.

A Custom Control design element merely defines a wrapper for zero or more Core Controls as provided by IBM - panels, repeats, edit boxes, buttons, and so on - and a list of zero or more properties that can be passed to any instance of this wrapper to instruct it how it should behave differently from any other instance of its design definition. Additionally, a Custom Control may contain other Custom Controls, which in turn may contain others. By decomposing any structure you want to build down to its "primitives", you can develop rather sophisticated features in ways that allow you to later reuse each underlying layer of complexity to develop other features. That's pretty darn cool, but still has one limitation: dig deep enough in any such hierarchy, and all you'll find are a pile of Core Controls that IBM provides for you out of the box.

Not any more.

I'll say it again: the Extensibility API allows you to write your own controls.

When you look at the control palette in Domino Designer, do you see anything glaringly missing? Well, "glaringly" is a surprisingly relative term: each of us builds different types of applications upon Domino for different reasons, based on requests from customers, clients, partners, colleagues in the community... so each of us has different priorities that determine what we miss most. Some may be baffled that there is a View Panel control, but no Calendar View Panel. Others may wonder why there's no Outline - or any type of Tree Control, for that matter. Still others may wish that there were more social controls native to the platform, such as a "Share This" control, or a "Like" button, or even just a rating mechanism that allows a user to click one of five stars to indicate how much or how little they approve of something.

Again, you could probably create each of the above examples as a Custom Control. And then all you have to do to make use of each feature is copy the Custom Control - and every Custom Control in its hierarchy - to every NSF where you want to use that feature.

When you use the Extensibility API to create a control, however, the process is slightly different:
  1. Write the code that defines the control.
  2. Package the control into a "library" - this creates an Eclipse plugin that defines what controls the library contains: you can bundle as many controls as you like into a single library, or just create the library to contain one control.
  3. Deploy the library to each Domino server that will host applications using controls in this library.
  4. Install the library into Domino Designer.

Once you've performed those steps, the icon for each control in the library just shows up in Designer, no matter which NSF you're in. You can use these controls in any NSF without having to add any design elements to it. Reusability goes through the proverbial roof.

Still not convinced? Then allow me to offer one additional selling point for the power of this feature. Every control in XPages is based upon (in Java-speak, "extends") a Java class called UIComponent. If it's an editable control - what most of us would call a "field" - it extends UIInput, which in turn extends UIComponent. As a basic example, the edit box control is defined by a class called XspInputText (hence the xp:inputText tag we see when viewing the XML source of an XPage that includes an edit box). This class extends UIInput, which is what makes it an editable control. Naturally, then, when writing what I've taken to calling "library controls" (to distinguish custom controls that are packaged in a library as described above from Custom Control design elements), you'll extend UIInput if you want your control to accept input from the user, and extend UIComponent if you simply want to display information to the user... or do anything else that requires no input from them. But... there's nothing stopping you from extending XspInputText.

If you have a pet peeve with the way that the Date Picker works, you can simply extend XspDateTimeHelper and change the one aspect of it that bugs you. If you think something's missing from the Link control (such as an option to display an icon if the link points to an external site)... extend XspOutputLink and add that one missing feature.

Back when Notes 8 was originally released, I joked with some colleagues that sometimes it felt like the only reason IBM ported the Notes client to Eclipse was to dodge subsequent enhancement requests: "Don't like how feature X works? Write a plugin." Obviously that wasn't the case. But with the Extensibility API, the implication for XPages is the same, even though (I'm assuming) that wasn't IBM's motivation for creating it: if we aren't satisfied with the capabilities of the framework, we are being given the power to change it. No whining, no excuses, just make it do whatever we want.

And that brings me to the "bad news": as you've probably already guessed, to create a library control, you have to write Java. So this is the part of the post where I become an unsympathetic hypocrite: yes, it's hard. Do it anyway. Or others will. I know that's harsh, but it's the truth.

Over the last three years, IBM has demonstrated quite clearly to me that developers are not an afterthought. With the exception of providing thorough and accurate documentation, everything I asked for in that rant has since occurred. And, of course, I wholeheartedly agree with jonvon that they need to be marketing Domino as an application platform... I'd be ecstatic if they made messaging the afterthought: "What? Oh, messaging? Sure, Domino does messaging... anybody can do messaging. That's easy. Now can we get back to talking about what Domino does that nothing else does?" If you owned a nightclub and found out that you were losing business, would your first priority be to remodel the restroom? Sure, that room serves a necessary purpose, but is that really why your customers are there? And is that why others are going elsewhere? Of course, if that's all you're advertising, sadly, you might still be losing business to a nearby hotel bar just because the bathroom sinks there have gold-plated faucets.

But the point remains: they're about to give us one heck of a big gun for our arsenal. It'd be a shame if it sat on a shelf collecting dust simply because we couldn't find time to go buy our own ammo...

06/28/2010

characteristics of an App Store

Category musings
Prior to the original release of the iPhone, I never heard any suggestion that Notes should have an App Store. The merits of OpenNTF, solutions catalog, "Nifty 50", sure... but nobody was calling any of these (or other theoretical entities) an "App Store" until the Apple App Store made apps the reason people buy an iPhone - not the fact that it's a phone. So I'm assuming that the success of that App Store is what has inspired some to believe that having an App Store for Notes would be A Good Thing™.

Given that Android and Blackberry now have their own platform-specific equivalents of an App Store, but didn't until after Apple's experienced viral adoption, I'ma go out on a limb and make the bold assumption that the success of Apple's store might have played some role in inspiring the others to create their own marketplace... particularly the Blackberry, which, as a device, far predates the arrival of the iPhone, although their store arrived long after Apple's.

Hence, I thought it might be fun to compare various attributes of each and add a column for what a Notes App Store might someday become:

  Apple Android Blackberry Notes
vendor SDK availableyesyesmultiplemultiple
API exposes platform strengthsyesyesyesyes
seamless purchasing and installationyesyesyesTBD
native to the platformyesyesinstall availableTBD
who vets the applicationsAppledevelopersadminsTBD
attracts new developers to the platformyesyesyesTBD
attracts new users to the platformyesyesdebatableTBD
wildly successfulyesyesnoTBD
target audienceusersusersusersTBD


Most Blackberry users have a Blackberry because their employer decided for them that they will have a Blackberry. The users had no choice in the matter. But do they have any influence over that decision?

I believe that question can be answered with two words:

Lotus Traveler.

Why would Traveler even exist unless users are exerting an influence upon their employers concerning the device platform they insist upon using? And why would they do so, unless something about that platform creates a perception that their use thereof provides more value to them than they would receive from a competing device? Is it the incredible call quality they experience when using an iPhone that so noticeably exceeds that of a Blackberry? Or, perhaps, it's the unparalleled combination of network availability and customer service that AT&T provides to their customers?

Or... could it possibly be... the apps?

Most Notes users use Notes because their employer decided for them that they will use Notes. OK, all Notes users. Conversely, users who aren't using Notes aren't using it because their employer decided that they won't use Notes. The users have no choice in the matter. But do they have any influence over that decision? And... if so... what might possibly make them vocal enough to exert that influence?

Could it be the security built into the platform? Oh, wait, they don't even notice that because users don't notice that their data is secure and their computers are free from viruses, but they sure notice the opposite. Or, perhaps, could it be Domino's unparalleled reliability? Oops, users don't notice that either; they notice when a server isn't up. Could it be the flexibility that allows the organization to choose what hardware and operating system will host the server, instead of allowing the vendor to make that decision for them? Uh... yeah, users don't care. Or, maybe, when their admin finally gets around to upgrading them to version 8, they'll be so ecstatic that their mail and calendar are now visually indistinguishable from Outlook, that they'll riot if the CIO suggests they actually migrate to Outlook. Could that be what would make the users vocal?

Or... could it possibly be... the apps?

06/25/2010

Rolling your own authentication for REST API's - Epilogue

Category xpages

In summary, let's recap some of the most unconventional characteristics of this approach to authentication:


  • We're storing a hashed representation of the user's password directly within the application, but do not store their user ID within the same record.

  • Despite not storing the user ID in the record that stores their password, we can rapidly retrieve the password when we need to because the UNID for that record adheres to a format that is both predictable and inexpensive to calculate.

  • Once the user has been registered, their password never needs to be sent to our application again. As long as the consumer knows the user's ID and password, it can generate a temporary API key based on tokens it requests from the API.

  • Our token and key format causes tokens to expire after a period of time of our choosing, allowing each consumer to cache tokens temporarily but not indefinitely. This allows users to perform multiple operations during a single session without having to request a new token for each. This approach minimizes both the total traffic to our application and the response time for each operation, but also minimizes the period of time during which an intercepted key can be used maliciously.

  • We never store either the token that we send to the consumer or the API key we expect to receive in return, but because we know what that key must be, we assign the token record a predictable UNID, allowing both the validity and the identity of the user to be rapidly calculated.

  • The use of a predictable UNID for both account records and token records removes the need to create views that display either document type. This reduction of the indexing burden that the application places on the Domino server, in addition to the rapid retrieval of data when requested, provides superior scalability and performance compared to traditional Domino data structures.

  • Most importantly, by preventing all users – both anonymous and conventionally authenticated – from accessing any data or design elements other than the XPage design elements used to surface our API to consumer applications, we are able to keep our application's data entirely secure despite being unable to leverage the entire security model that is built into the Domino application model.


I hope that you've found this article informative, and that this approach to authentication gives you yet another way to use Domino to provide amazing application experiences to your users.


FIN

Download this entire article as:

For additional convenience, here are links to each section of this article:

  1. Putting your data to REST
  2. Making a Token gesture
  3. Anonymity is a warm blanket
  4. Welcome to the club
  5. The Keymaster and the Gatekeeper
  6. The sum of the parts


06/24/2010

Rolling your own authentication for REST API's, part 6 - the sum of the parts

Category xpages

Thus far, we've only looked at the "back end" code that can be used within an XPage to register and authenticate users. So let's finish off by exploring how all the concepts this article has discussed can come together to create a fully functional application API.


The following is the entire source XML of registerUser.xsp:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.getFilePath());
 API.sendJson(API.registerUser(param.userId, param.userPassword, signerDatabase).toJson());
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


The first characteristic of this element that is atypical for an XPage is the rendered attribute of the UIViewRoot node: by setting it to false, we tell Domino not to send any markup of its own. This allows us to "print" to the browser only the specific data that we want to send. Since we are surfacing an API to consumer applications that will provide its own visual representation of the data, we can send just the data with no visual characteristics of our own. We do that using the sendJson method of our base API class:


  1. public static void sendJson(String json) {
  2.  XspHttpServletResponse response = (XspHttpServletResponse) FacesContext
  3.    .getCurrentInstance().getExternalContext().getResponse();
  4.  response.setContentType("application/json");
  5.  response.setHeader("Cache-Control", "no-cache");
  6.  try {
  7.   PrintWriter out = response.getWriter();
  8.   out.write(json);
  9.  } catch (IOException e) {
  10.   e.printStackTrace();
  11.  }
  12. }


This method obtains a handle on the servlet response, which allows us to not only write our desired content directly to the consumer, but also to set the content type of the response and set a header that prevents caching, just in case the consumer would otherwise choose to cache the response.


We use the afterPageLoad event of the XPage to call this method, but before doing so, we use the native importPackage function of SSJS to allow us to use an abbreviated syntax. Since all SSJS code is ultimately interpreted by Java at runtime, we would normally have to use the full Java namespace (com.puncrock.api.API.sendJson) to allow the runtime to load the correct method; importPackage provides the class loader a shortcut for finding the method, so as long as we haven't defined any conflicting variables, we can simply call API.sendJson() and the correct code will be executed.


The registerUser method returns an instance of a UserRegistration class. We've previously examined its register method; let's take a look now at its toJson method:


  1. public String toJson() {
  2.  JSONObject json = new JSONObject();
  3.  try {
  4.   json.put("success", isSuccess());
  5.   if (!(isSuccess())) {
  6.    json.put("error", getError());
  7.   }
  8.  } catch (JSONException e) {
  9.   e.printStackTrace();
  10.  }
  11.  return json.toString();
  12. }


This method uses the org.json package, which can be downloaded from (as you might expect) json.org. Although the XPage runtime automatically includes multiple packages for generating JSON, when generating – and parsing – basic JSON structures, the org.json package is far simpler to use, as the above example illustrates.


If the registration request succeeded, the consumer will receive the following response:


{"success":true}


If the request was unsuccessful, however, the response will be similar to the following:


{"success":false, "error":"User Id timtripcony already exists"}


Either response provides the consumer all of the information it needs to provide appropriate feedback to the user.


The source of getToken.xsp is quite similar:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.getFilePath());
 API.sendJson(API.getToken(param.userId, signerDatabase).toJson());
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


Once again, we block the default rendering of the page, and in its place, send back a JSON response – this time, by calling an almost identical toJson method of the TokenRequest class we examined earlier:


  1. public String toJson() {
  2.  JSONObject json = new JSONObject();
  3.  try {
  4.   String token = getToken();
  5.   if (!(token == null)) {
  6.    json.put("token", getToken());
  7.   } else {
  8.    json.put("error", getError());
  9.   }
  10.  } catch (JSONException e) {
  11.   e.printStackTrace();
  12.  }
  13.  return json.toString();
  14. }


This time, if a valid user ID was provided, the consumer should receive a response similar to the following:


{"token":"AEB5929ED34F602FD98DB7917098AC5B"}


Finally, let's allow a user to actually do something in our application. To be precise, let's allow a user to store their current GPS position in their account record. So we'll create setUserLocation.xsp:


<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" rendered="false">
 <xp:this.afterPageLoad><![CDATA[#{javascript:importPackage(com.puncrock.api);
try {
 var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.get FilePath());
 API.sendJson(API.setUserLocation(param.apiKey, Number(param.latitude), Number(param.longitude), signerDatabase));
} catch(e) {
 print(e);
}}]]></xp:this.afterPageLoad>
</xp:view>


This page calls the setUserLocation method of our base API class, and passes it several request parameters, including the API key that should identify a valid user:


  1. public static String setUserLocation(String apiKey, Double latitude,
  2.   Double longitude, Database database) {
  3.  JSONObject json = new JSONObject();
  4.  String userId = getUserId(apiKey, database);
  5.  if (userId != null) {
  6.   Document userRecord = new DominoUtil(database)
  7.     .getDocumentByPrimaryKey(userId, false);
  8.   try {
  9.    userRecord.replaceItemValue("latitude", latitude);
  10.    userRecord.replaceItemValue("longitude", longitude);
  11.    json.put("success", userRecord.save());
  12. &nbs p; } catch (NotesException ne) {
  13.    try {
  14.     json.put("success", false);
  15.     json.put("error", ne.toString());
  16.    } catch (JSONException je) {
  17.     je.printStackTrace();
  18.    }
  19.   } catch (JSONException je) {
  20.    je.printStackTrace();
  21.   }
  22.  } else {
  23.   try {
  24.    json.put("success", false);
  25.    json.put("error", "User not authenticated");
  26.   } catch (JSONException je) {
  27.    je.printStackTrace();
  28.   }
  29.  }
  30.  return json.toString();
  31. }


Because this is the longest single block of code we've yet seen, but also because it finally demonstrates a real use case for this authentication technique, let's examine it in excruciating detail.


  1. We create an empty JSON object that will be sent back to the consumer to notify the user of the result of the attempted operation.

  2. We call the getUserId method we examined earlier, passing it the API key received with the request. If the key is expired – or was never valid – we update the JSON object to convey that the operation was not successful because the user is not authenticated. This provides the consumer an opportunity to request a new token and try the same operation again.

  3. If, however, the key we received was valid, we now know the user ID associated with the token used to generate the key. We can, therefore, rapidly obtain a handle on their account record.

  4. Having now located their account record, we write the latitude and longitude data received with the API request and save the record.

  5. Finally, we update the JSON object to convey that the operation was successful.


Expanding on this basic premise would allow your service to do more interesting things: instead of just storing the user's current location by overwriting item values in their account record, you could create a separate "check-in" document each time, storing the user ID, location data, and current date-time. This might allow consumer applications to display a chronological map of a user's travels, perhaps even calculate patterns of changes in physical distance between members of a given social circle.


What is significant about this example is not what is done with the data that was submitted to the application; rather, the most pertinent facet of the above method is how the application is determining whether the data should be accepted in the first place – and if so, on whose behalf.


06/23/2010

Rolling your own authentication for REST API's, part 5 - the Keymaster and the Gatekeeper

Category xpages

Now that our application has users, we need to allow them to log in. As we discussed earlier, this process essentially just consists of two steps: generating temporary tokens, and matching tokens that come back in subsequent API requests to the corresponding user. The latter step also includes determining whether the token is even valid to begin with and, if so, whether it has expired. We'll now take a look at exactly how to do this.


Just as the data transport format you choose for your API is a decision you must make based on priorities you set for your application, you can choose any format you wish for constructing tokens. In order to provide one example of this technique, we will be modeling our authentication scheme based on some principles used by Toodledo, a cloud-based task management service. To be precise, we will construct a temporary token upon request to send to the consuming application. Instead of simply sending that token back, however, the consumer will be required to send back an API "key" based on that token accompanying any subsequent API request.


The format that Toodledo uses for an API key is as follows:


md5 ( md5 ( password ) + token + userId )


Let's express this format briefly in paragraph form. First, the user's password is hashed using the MD5 algorithm. Next we append the temporary token and the user ID to that result. Finally, we MD5 hash the entire combination one more time. The result is a 32-character mixed case key that is a representation not only of the user's credentials, but also of the period of time during which the key is valid.


In order to illustrate how we can generate the temporary token that will be used to construct the API key, let's take a look at the constructor of a TokenRequest class:


  1. public TokenRequest(String userId, Database database) {
  2.  setUserId(userId);
  3.  setDatabase(database);
  4.  DominoUtil util = new DominoUtil(database);
  5.  Document userRecord = util.getDocumentByPrimaryKey(userId, false);
  6.  try {
  7.   if (!(userRecord == null)) {
  8.    DateTime loginTime = util.getDateTime();
  9.    Document tokenRecord = database.createDocument();
  10.    setToken(tokenRecord.getUniversalID());
  11.    tokenRecord.replaceItemValue("Form", "token");
  12.    tokenRecord.replaceItemValue("userId", userId);
  13.    tokenRecord.replaceItemValue("tokenCreated", loginTime);
  14.    String key = getAuthKey(userId, userRecord
  15.      .getItemValueString("userPass"), getToken());
  16.    tokenRecord.setUniversalID(HashMaster.md2(key));
  17.    tokenRecord.save();
  18.   } else {
  19.    setError("User Id " + userId + " does not exist");
  20.   }
  21.  } catch (NotesException e) {
  22.   setError("An unexpected error occurred while attempting to request a token");
  23.   e.printStackTrace();
  24.  }
  25. }


This code attempts to locate the user's account record. Remember, we don't have to search a view to locate this document: because the UNID of an account record is predictable, we know how to locate it even though the document that we'll retrieve doesn't actually store the user ID. Hence, if the hashed user ID does not match the UNID of an existing document, the specified user does not exist. Otherwise, the handle we now have on the account record was retrieved rapidly, no matter how much data our application currently contains. This performance implication is fundamental to why this technique is so useful when designing an application to be as scalable as possible.


If the specified user ID is valid, we create a document that represents the temporary token that is being requested. As before, we set some convenience field values – this time, both the form associated with the document and the creation date of the token. Similar to our user registration process, however, we have no need to ever store the token. As you can see, we're overwriting the UNID of the token document with a value of our own choosing, but until we do, it has a default UNID automatically generated by Domino; for the sake of convenience, this will be the token that we send back to the consumer. We could instead generate our own random or sequential identifier, of course, but Domino has already generated a sequential identifier for us, which we are about to discard anyway, so we may as well put it to good use while we have it.


Having set our return value to be the temporary, automatically-generated UNID, we retrieve the user's password from their account record, and pass it along with the user ID and token to a method that will determine what the corresponding API key must be when it is sent back by the consumer. Because we're already storing the MD5 hash of the user's current password, we don't need to hash it again when calculating the API key (NOTE: this is a slight change from what was discussed in the previous section in response to a suggestion from Karsten; the PDF version of this article will reflect this change in section 4):


  1. public String getAuthKey(String userId, String password, String token) {
  2.  return HashMaster.md5(password + token + userId);
  3. }


We don't need to calculate this yet, of course: it's the consumer's responsibility to provide this key, not the token generation process. However... immediately before we save the token document, we set its new UNID to be an MD2 hash of the expected API key. As a result, when we do receive an API request that includes a key, determining whether that key is valid – and, if so, the identity of the corresponding user – is easy:


  1. public static String getUserId(String key, Database database) {
  2.  String result = null;
  3.  DominoUtil util = new DominoUtil(database);
  4.  Document tokenRecord = util.getDocumentByPrimaryKey(key, false);
  5.  if (!(tokenRecord == null)) {
  6.   Date now = new Date();
  7.   try {
  8.    Date then = tokenRecord.getFirstItem("userId")
  9.      .getLastModified().toJavaDate();
  10.    double timeDifferenceMinutes = (now.getTime() - then.getTime()) / 60000;
  11.    if (timeDifferenceMinutes < 240) {
  12.     result = tokenRecord.getItemValueString("userId");
  13.    }
  14.   } catch (NotesException e) {
  15.    e.printStackTrace();
  16.   }
  17.  }
  18.  return result;
  19. }


We simply hash the key, and try to find a document with a matching UNID. If none exists, that key was never valid. If a document does match, we check the last modified date of the userId item; if it's older than 4 hours (here, again, is an arbitrary portion of this approach that can be changed to suit your own priorities), then it's expired. But if the document exists, and the token it represents hasn't expired, then the userId item tells us on whose behalf the action specified in the API request should be performed. Because each requested token is valid for a specific period of time, we can inform all API consumers how long they can cache tokens within their own application before having to request a new one; they need not request a new token prior to every API request.


In the next section, we'll pull all of these concepts together to create a functional API.


06/23/2010

Rolling your own authentication for REST API's, part 4 - Welcome to the club

Category xpages

Before we can allow users to log in, we must first, of course, have users. Since we're using a custom authentication scheme, we obviously need a custom registration mechanism as well.


For the purposes of this authentication approach, a user consists only of a document within our application that stores a single item value: the user's password. This, again, may seem to be unconventional if not heretical. Remember, however, that no end user will have direct access to any data. As long as our API does not programmatically expose a user's password, our system will remain secure. But you may also have noticed that we're not storing the user's ID in the document that stores their password... there's no need, as you'll soon see.


Earlier we discussed an option built into Domino for obtaining an MD2 hash of any string value. Because this implementation is specific to Domino, it is actually a variation on the standard algorithm, and will not return the same encoded result that would be returned by other implementations. Furthermore, the token format used in the authentication technique this article proposes relies upon the use of MD5 hashes; not only is there no implementation of this algorithm built directly into Domino, but we need to be absolutely sure that the token value we receive from third-party applications is the precise value we have calculated in advance that we are expecting to receive. We need, therefore, a more generic method for hashing string values.


One of the key benefits of XPages is that it is now easier than ever to incorporate external code into our applications: specifically, Java code written by developers with no knowledge of Domino can be imported directly into, and leveraged within, Domino applications – nearly always without any additional modification needed. In many cases, features built into the base Java language can be very useful in our applications. As an initial example, let's take a look at a small custom utility class, called HashMaster:


  1. import java.math.BigInteger;
  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;
  4.  
  5. public class HashMaster {
  6.  
  7.     public static String md2(String unhashed) {
  8.         return encode(unhashed, "MD2");
  9.     }
  10.  
  11.     public static String md5(String unhashed) {
  12.         return encode(unhashed, "MD5");
  13.     }
  14.  
  15.     private static String encode(String unhashed, String algorithmId) {
  16.         String hashed = "";
  17.         byte[] defaultBytes = unhashed.getBytes();
  18.         try {
  19.             MessageDigest algorithm = MessageDigest.getInstance(algorithmId);
  20.             algorithm.reset();
  21.             algorithm.update(defaultBytes);
  22.             byte messageDig est[] = algorithm.digest();
  23.             hashed = new BigInteger(1, messageDigest).toString(16);
  24.         } catch (NoSuchAlgorithmException nsae) {
  25.             nsae.printStackTrace();
  26.         }
  27.         return hashed;
  28.     }
  29. }


This class not only allows us to generate an MD2 hash for reasons already mentioned, but the same class can also obtain an MD5 hash, which will be useful later, both in generating and in validating authentication tokens. In the meantime, let's take a look at a method from another class (DominoUtil), which will be used extensively in our API:


  1. public Document getDocumentByPrimaryKey(Database source, String key,
  2.     Boolean createOnFail) {
  3.   Document result = null;
  4.   String unid = "";
  5.   try {
  6.     unid = HashMaster.md2(key);
  7.     result = source.getDocumentByUNID(unid);
  8.   } catch (NotesException ne) {
  9.     if (ne.id == 4091 && createOnFail) {
  10.       try {
  11.         result = source.createDocument();
  12.         result.setUniversalID(unid);
  13.       } catch (Exception e) {
  14.         e.printStackTrace();
  15.       }
  16.     }
  17.   } catch (Exception e) {
  18.     e.printStackTrace();
  19.   }
  20.   return result;
  21. }


This method does the following:

  1. Obtains an MD2 hash of the "primary key" passed to the method

  2. Since the result of the hash is a syntactically valid UNID, it attempts to locate a document within the specified database that has the same UNID

  3. Although the hash result might be the UNID of an existing document but also might not be, there is the possibility that an exception will be thrown. In that scenario we may optionally create a new document and set its UNID to match the hash. Take note that, if a new document is created, it is not yet saved. In either scenario, we end up with a document whose UNID is an encoded version of the text used to request it... in other words, the UNID is meaningful: if I pass "Tim Tripcony" as the key, the resulting UNID is an encoded representation of my name.


Finally, let's examine how this concept is put to good use within the register() method of our UserRegistration class:


  1. public boolean register() {
  2.   try {
  3.     DominoUtil util = new DominoUtil(getDatabase());
  4.     Document userRecord = util.getDocumentByPrimaryKey(getUserId(),
  5.         true);
  6.     if (userRecord.isNewNote()) {
  7.       userRecord.replaceItemValue("userPass", getPassword());
  8.       userRecord.replaceItemValue("Form", "user");
  9.       setSuccess(userRecord.save());
  10.     } else {
  11.       setError("User Id " + getUserId() + " already exists");
  12.     }
  13.   } catch (NotesException e) {
  14.     setError("An unexpected error occurred while attempting to register user");
  15.     e.printStackTrace();
  16.   }
  17.   return isSuccess();
  18. }


As you can see, this method attempts to locate a document that has a UNID matching the MD2 hash of the user ID we are attempting to register. If the document it returns is not a new note, then the user has already been registered: the database already contains a document with a UNID representative of the specified user ID. If, however, the document is a new note, then we can safely create the new account.


As was previously mentioned, we're not storing the user ID in their account record – just their password. For the sake of convenience, we are writing a Form item, in case we want to create a view to display all user accounts, though it's not likely we ever would. Similarly, you may choose in your own application to store additional information about each user to allow users to search for other users and view information about them, such as a profile picture or contact information. For the purposes of authentication, however, we never need to search any index for a user's account record, because we can always obtain a hash of their ID and navigate straight to the account record via the resulting UNID.


One concept that should be noted is the format used for the user ID. This could be a canonical name (i.e. "CN=John Doe/OU=..."), a shortname (i.e. "jdoe"), an email address... when using this style of authentication, the name format is inconsequential as long as it's consistent. The key limitation is that – unlike traditional Domino authentication – this particular technique would not allow the same user to enter their canonical name, shortname or email address and still be authenticated as the same user... you have to choose one.


An additional implication of using a hash of the user ID as the UNID for the account record is that this makes the user ID case sensitive, which is atypical; most authentication systems expect the user's password to be entered using the same case as the stored value, but do not require the case to match for the user ID. Because an hash of a lowercase string will not return the same value as an MD2 hash of an upper- or mixed-case version of the same string, however, our implementation requires that the case of the ID match each time it is evaluated – at least, in the low-level code. You can certainly shield your users from this requirement by converting the case of the ID to ensure it is always the same prior to being hashed. If, however, you prefer that the ID be case-sensitive, hashing the ID as passed without case modification will automatically enforce that requirement for you.


Contact Me

Elsewhere

Assorted Linkage


Locations of visitors to this page