Results matching “2006/08/09/implementing_single_sign_on_drupal_to_magnolia” from Contentment

Phew! This is getting nuts. I am more busy on web development at home than I am at work. Frankly, with this flurry of stuff going on at home, I'm starting to get a bit bored with work. Well, I do want to talk about something I did at work yesterday because it was pretty cool. I've put together a knowledge base and issue tracking system using Drupal and RT. It was pretty easy and I think others might benefit from at least some aspect of the solution.

We've been using Trac to keep track of our knowledge base, issues, milestones, and source browser surrounding the web development project. For the internal IT stuff, we've been using Microsoft SharePoint. Both have had limited success. Trac worked perfect until we needed a way for web site visitors to send in support requests via email. Trac's support for this is total crud. It's still being worked upon, but after having used RT in CIS, I'm pretty hard to please when it comes to support tracker email integration. Sharepoint has worked about as well as it ever does: good enough, but it's a bit tiresome to use. If we were using the beta of the new version, it would probably be fine, but that's not trivial upgrade to perform without causing massive alterations to lots of other services we run.

Okay, so I've started to put together something that could potentially replace both of these with flying colors. It's still a work in progress, but I think I've already got something better after only a couple days of working on the problem.

First, I installed RT 3.4. After installing it and getting Sendmail configured on our hosted server, I got RT configured. I leave learning how to do this as punishment for the reader.

Then, I went in and installed Drupal 4.7 and started configuring it. First thing to do is to install all the modules. I've installed the following:

  • Category. This is one of my new favorites. It is a drop in replacement for the Taxonomy module that makes every vocabulary and every term into a node. You can even use arbitrary content types as vocabularies and terms and can have vocabularies within vocabularies. It's very nice. Oh, and you can set the default item depth of a category so that it automatically aggregates children to a certain depth. Also very nice.
  • Interwiki. This is kind of the best available for the functionality I'm looking for, but it needs a bit of work to get it right. Basically, you can register prefixes with it that will then be translated into URLs using a wiki-like syntax. For example, it comes configured with the prefix "w" automatically linking to Wikipedia articles. Thus, you can insert Nichols Hall and it would link off to Nichols Hall at Wikipedia. You can add other prefixes to other sites or use no prefix, i.e., foo or //drupal.org/project/pathauto">Pathauto. This ought to be a standard Drupal module. It generates paths for a node according to settings you set. I use it heavily on this site now and it's what allows us to have nice wiki-like articles on the new knowledge base.
  • TinyMCE. This is a must have if you have non-technical or semi-technical staff helping you write articles. We have a content editor, Doug who understands but doesn't really converse in HTML on a regular basis. There's no need to make him when TinyMCE exists and is so easy to install and use.

That's it. I installed all of those and then went through the tedious process of setting up access controls, updating settings, etc. Now, I can create a page on the site titled "You are a foo foo" and then create another article and link to it by adding the text [:You are a foo foo
. I can use TinyMCE to format the text without worrying about learning the Wiki-syntax-flavor-of-the-month and so I won't be too afraid to show someone completely non-technical how to enter knowledge base information later on.

I added new custom interwiki prefixes for getting at RT and Trac tickets and the old Trac knowledge base articles as well for the transition period. Now, I can link to our old standards document via BoomerStandards
or to a ticket in the old system via 435
or to a ticket in the new system via 219
. I added a number of other shortcuts as well and this is really the only wiki-syntax anyone needs to know about and this is a pretty easy one to remember.

The knowledge base is now in place. Let's go back to RT. To RT, I've now added a callback to the system for the ShowMessageStanza ticket element in the RT system. That is, I created a file named "Callbacks/boomer/Ticket/Elements/ShowMessageStanza/Default" in the local hacks directory for RT. You may wish to see CustomizingWithCallbacks for more information on creating customizations using the RT callback system. Anyway, the contents of this callback is as follows:

<%perl>
$$content =~ s{(?<!&)\#(\d+)}
{<a href="${RT::WebURL}Ticket/Display.html?id=$1">#$1</a>}gx;

my %config = (
tracwiki => 'http://trac.example.com/trac/foo/wiki/$1',
trac => 'http://trac.example.com/trac/foo/ticket/$1',
rt => $RT::WebURL.'Ticket/Display.html?id=$1',
http => 'http:$1',
https => 'https:$1',
'' => 'http://supportkb.example.com/$1',
);

$$content =~
s{
(!|\\)? # stop now if there's a ! in front
(
\\
\|]*): # match a prefix like abc:, remember the prefix
(([^\
]+))? # match a pretty name like "Blah Blah is cool."
\] # match closing ]
)
}{
my ($escape, $match, $prefix, $link, $title)
= ($1, $2, $3, $4, $5);

if (defined $escape) {
qq($match);
}
elsif (defined $config{$prefix}) {
my $url = $config{$prefix};

my $term1 = $link;
$term1 =~ s/\ /_/gx;

my $term2 = $link;
$term2 =~ s/\ /\+/gx;

my $term3 = $link;
$term3 =~ s/\ /%20/gx;

my $term4 = $link;
$term4 =~ s/\ /-/gx;

$url =~ s/\$1/$term1/gx;
$url =~ s/\$2/$term2/gx;
$url =~ s/\$3/$term3/gx;
$url =~ s/\$4/$term4/gx;

$title = defined $title ? $title
: $link =~ /^ "$prefix:$link";

qq(<a href="$url">$title</a>);
}

else {
qq($match);
}
}gex;


</%perl>

<%args>
$content
</%args>

If you're unfamiliar with Perl and Mason, you may have a migraine by now. You might have one anyway, unless you're a JAPH like me.

Anyway, this replicates part of the functionality of interwiki within RT messages by converting the [rt:123
sequences into links. This also features the ability to create links between tickets using a #123 syntax as well, which I'd like to add to the knowledge base eventually as well because I find the Trac-style linking of #123 (tickets), @123 and r123 (revisions), and [123] (changesets) to be very easy to remember and very handy.

That gets me much of the way to an integrated Drupal/RT knoweldge base/issue tracker system. I still need more though, but I haven't gotten there yet. Stuff I'd like to add in:

  • Organic Groups. This would allow us to have a subsite for each knowledge base: one for internal IT, one for web development. Later, we could add one for sales information, one for content style guides, or whatever.
  • Fix/replace interwiki. The interwiki module is nice, but isn't quite nice enough. It would be nice to have additional formats available. With very little effort, I should be able to create a custom filter to either extend this functionality or outright replace it just for our needs.
  • Single Sign-on. I have some experience with this lately. If I can get it so that I only sign in once to Drupal and or RT and then I'm in both, that would be great. If I could implement it using an independent LDAP server, all the better.
  • Configuring RT. Configuring the interwiki-ish filter for RT is done in code. This isn't ideal. With whatever solution I work on for Drupal, it would be nice if the RT filter could load it's configuration from the same place, or at least get it from a config file and not in the code itself.

Will I get all this done? Probably not, but I'll get some of it done and if I think it's interesting, I'll try to share it with you.

Cheers.

As I said earlier this week, I want to write about how I implemented single sign-on for the new Boomer.com web site. I didn't come up with the design on my own, but the implementation is from scratch and unique--i.e., proprietary. In the future, I hope to replace the system with something else but since certain aspects of our system's integration or currently in limbo and the lack of prefab Magnolia-to-Drupal implementations, a quick and dirty implementation was certainly in order. I like the results, as much as I can for a quickie, which is why I share it here.

Design

As I just mentioned, I did not invent this design myself. I merely implemented the pattern described for Cosign, a project at the University of Michigan. If my deadline hadn't been looming so close, I might have considered implementing a Drupal plugin for Cosign. As it were, though, I created a fresh implementation that has no other relationship to Cosign than the fact that I have approximately the same structure of HTTP requests and responses that it has (and probably several other implementations).

For the following use-case, I will call the web site that holds authoritative information the Auth-Site and the other site requring authentication information against the Auth-Site, the Sub-Site.

Basically, the use-case works like this:

  1. The Client attempts to connect to a protected document on the Sub-Site.
  2. The Sub-Site server returns a Sub-Site session cookie to the Client and redirects the Client to the Login-Checker of the Auth-Site.
  3. The Login-Checker of the Auth-Site determines if the Client has logged in already to the Auth-Site. Assuming that the Client has not, the Login-Checker returns an Auth-Site session cookie to the Client and redirects the Client to the Login-Form.
  4. The Client then fills out the Login-Form and returns it to the Auth-Site, which validates the login using the Login-Form-Checker. Assuming success, the Login-Form-Checker notes that the original Auth-Site session cookie came from the Sub-Site. It then performs a redirect to the original protected document on the Sub-Site that includes in the URL a Login-Token.
  5. The Client then returns to request the original protected document from the Sub-Site, but with the Login-Token this time. The Sub-Site then performs an out-of-band (direct connection from Sub-Site to Auth-Site) to see if the Login-Token is valid. Assuming the Login-Token is valid, the Auth-Site returns the Client profile to the Sub-Site and the Sub-Site returns the protected document.

That probably sounds pretty complicated, but it basically amounts to the Sub-Site deferring login to the Auth-Site and asking the Auth-Site for a Login-Token to validate the user by. The major hiccup is the risk of session hijacking if an attacker happens to guess the token.

If that's still confusing, the Cosign site has a lovely sequence diagram of the interaction.

Implementation

So the actual implementation required two additional Java servlets to run with the Magnolia server (in addition to the login servlet already in place). One I called the CheckForLoginServlet. This servlet checks to see if the visiting user has already logged in. If the user has, the user is redirected from whence she came with the login token to verify her authenticity. If the user is new, the user is then redirected to the login form, which is notified of which URL the user needs to be returned to afterward.

The second servlet is the ValidateAuthTokenServlet. This servlet handles the out-of-band, server-to-server communication, which verfies the user and sends an XMLized version of the user's profile from Magnolia auth-site to the Drupal sub-site. The token itself is a UUID, which should be sufficiently difficult to guess, but might still be vulnerable to snooping attacks, since the communications aren't currently encrypted. Really, though, this is no worse than any other plain text login, which has the same vulnerabilities. We plan to move the authenticated parts of the site to SSL in the next few months to make the system stronger.

Another interesting piece to the puzzle is that I made the LoginToken objects semi-autonomous in that they erase the persistent data they store with the user objects when the session they belong to is invalidated. A user can login multiple times from differnet locations and each location will have a unique session token. When the user's session times out, the token is also invalidated.

On the Drupal side of things, I had to add a couple little hacks to get things to work just so. First, since the Drupal site is entirely contained within the Extranet, all documents are protected documents. No one should see anything on the site without logging in first. Thus, I overrode hook_init to perform a redirect to the CheckForLoginServlet unless the request came with a login token, came from a browser with an already validated session, or was coming for the cron script---which I didn't want to authenticate to execute.

If the request comes with a login token, the Drupal server performs the out-of-band check against ValidateAuthTokenServlet to see if that login token matches any current user session. If it doesn't, the user is kicked out to the CheckForLoginServlet to login again. If it does match, the user is, effectively, logged in by a special subroutine which loads their profile information from the XML passed back by the ValidateAuthTokenServlet. This also updates all the roles the user is in to make sure his permissions are correct and then drops them into the page.

All-in-all, the visitors should be relatively oblivious to the system. The only hiccup at this point that I need to take care of is to make sure that anytime the query string of a request to Drupal or to Magnolia contains a login token, that the client be immediately redirected again to remove the token from the location bar. Those tokens shouldn't accidentally end up in linked URLs if the user copies-and-pastes, or someone may find themselves logged in as the other user---i.e., bad stuff.

In the long-term, however, I will probably implement something like Cosign or CAS or SXIP, depending on how things shake out over the next few months. If we choose the right standard, we should be able to provide better integration with clients or third party apps to improve our SaaS opportunities in the future.

Find recent content on the main index or look in the archives to find all content.