So, ever since I made a post about what I had done with the OpenID drupal module, I have been receiving great feedback on how I can make it better. Much of the feedback were actual feature requests for the next version of the module. The main request was to allow every user in the Drupal db to enter an OpenID, allowing all users to authenticate with it. One person told me that if I did this, they would eliminate the default login block all together(a great idea!). The other big request was to pull default account information from the OpenID's persona.
Naturally, I thought these were both great ideas. Being the OpenID geek I am, I would love to do anything to help spread the 'Identity 2.0' love. I waited a couple nights to get my midterms out of the way, and then started to dive into the code. Man o man, I thought I had a good understanding on how Drupal modules worked before. At the end of a 12 hour stint(8pm to 8am), I came out on top with the latest rendition(minus a few bug fixes), which you can find at it's git repository. That link will actually always point to the latest version of the code, which at this point is probably the best idea.
The first thing I decided to tackle was pulling the information from the persona. After talking with Greg during work one day, he had mentioned that he was working on this in his own module for Drupal 4.6. He pointed me towards his blog, where he actually had posted the code to pull information using sreg. This was perfect! After incorporating his little tidbits into the code, I was able to pull information from the persona perfectly. For Greg's module, he chose to pull the full name, and use that in the user's profile. I decided that I wanted to set a users username to their nickname stored in the persona, as oppose to their actual OpenID. This way it would feel more like the rest of the drupal site, and long URLs wouldn't break my theme. I took care of the username and email validation with the following:
- if (user_validate_name($query['nickname']) != NULL){
- drupal_set_message('Username invalid.', 'error');
- }
- if (!valid_email_address($query['email'])) {
- drupal_set_message('Email address invalid.', 'error');
- }
- drupal_set_message('Username exists. Please try again', 'error');
- }
- drupal_set_message('This email account already exists. Please log in using your existing password and add in your OpenID URL through the My Account option.', 'error');
- }
With this code I am essentially trying to load a user from the DB with the given username or email address. If I can, that mean it exists, and we shouldn't try to create a new one(for obvious reasons). I also pulled a little code from Greg's blog again here to validate usernames and email addresses to make sure they are syntactically correct(alpha-numeric, non-empty essentially).
The next step was the biggie, editing a user's profile to allow them to store an OpenID, allowing users to add/change their OpenID on a whim. I started hunting around on the Drupal 5 module API page, looking for a way to edit the user's profile. Unfortunately, I was unable to find any good documentation(examples) on how to do this. Eventually, I stumbled across some source somewhere that showed me how to do this. Personally, I thought this would be a more common use, but it seems that most modules just create new pages or nodes, as oppose to just using the 'My Account' page. I was able to use the following to get it working for me:
- switch($op){
- case 'form':
- if($category == 'account'){
- return openid_user_form($edit, $user, $category);
- }
- break;
I put this start of the switch statement in the hook_user function. Also in this function, I am able to tell when the user updates their profile, deletes their account, or their account is being viewed. Using the switch appropriately, I am able to get every state that a user profile can be in. Perfect. The function that is being called in the above snippet looks like this:
- function openid_user_form($edit, $user, $category){
- $row = db_fetch_object(db_query("SELECT openid FROM {openid} WHERE uid = '%d'", $user->uid));
- '#type' => 'fieldset',
- '#collapsible' => FALSE,
- '#collapsed' => FALSE,
- '#weight' => 1,
- );
- '#type' => 'textfield',
- '#title' => t('OpenID'),
- '#maxlength' => 64,
- '#default_value' => $row->openid,
- '#description' => 'If you have one, enter your OpenID here.',
- );
- return $form;
- }
All this does is create the new form item on profile page, and assign the different values. The fieldset type in the first item gives the text field its own little section on the page, and fits in well with the theme.
After getting all of the interface parts to work, I then had to start work on how to save user OpenIDs. My original thoughts(it being 6am by this time), were to just add a field to the existing user table in the database. This is an obvious bad choice, and quickly corrected myself as I was eating a refreshing piece of fruit. I decided that it would be best to just create a new table, with a key relating back to the user id. I was easily able to do this with a install script that Drupal will invoke when the module is installed(uninstall things go here as well). This was nice and simple, and looked like this:
- $query1 = db_query("CREATE TABLE {openid} (
- uid INT NOT NULL,
- openid VARCHAR( 64 ) NOT NULL,
- PRIMARY KEY (uid)
- )");
With some simple SQL-fu on the hooks that I mentioned before, I was able to save, delete, and view the new field that I saved in the database. Woot!
The last and final part that I needed to add, was instead of using an authorization map to log OpenID users in, I wanted to allow any user to login with the OpenID specified. The big difference with the OpenID method of logging in, is that a user may never specify a password to the drupal site if they use OpenID from the get go. I needed to figure out a way to allow manually log users in, if it was returned to me from the OpenID provider that they had authenticated. Apparently they thought about this, and had a great function for it. Perfect! It looked a little like this:
- $row = db_fetch_object(db_query("SELECT * FROM {openid} WHERE openid = '%s'", $openid_url));
- if($row->uid){
This code trys and grab a user from the table that has the OpenID that just authenticated. If it finds it, it trys and loads/logs in the user. Voila! We now have a Drupal authentication module. Woot!
Hopefully this module will help some other people out there. Above all, I really do hope that having a CMS as big as Drupal support OpenID now, it will help push the use of the protocol that much more.
Cheers!
Update:
I just finished adding MyOpenID affiliate support. You will now see a link in the login block allowing users to create an OpenID through MyOpenID. If you go into the admin settings, you can now set an affiliate ID, which will automatically change the create an OpenID link in the login block to be your affiliate page. After users create an OpenID, it will automatically start and log them in to the Drupal site(which will invoke the drupal account creation page naturally). Enjoy!
Update 2:
I just made the git archive public, so it will be a bit easier for others to update their code to my fixes and such. You can clone the archive from http://jirwin.net/git/openid.git. Enjoy!
