James Gordon's blog

Having received quite a number of requests through my contact page for Drupal 7 External Authentication Sample Code, today I finally found the time to churn something out.

The Drupal 7 API has changed fairly significantly, with major changes to the database API, hook_user() API, etc. However, the documentation is awesome and the various threads and other contributions on drupal.org make it fairly easy to figure out the changes.

As with my Drupal 6 External Authentication Sample Code, I have once again kept things simple by implementing a scheme whereby any user identified by an email address is assumed to be subject to external authentication, whilst all other users will be subject to Drupal's internal (password) authentication.

To get this code working you will need to:

  1. Add a folder called "extauth" underneath your "sites/all/modules" folder
  2. Place a copy of the two files below in that folder
  3. Use the admin interface to create a new role called "external user"
  4. Go to the Modules administration area and activate the "extauth" module.

If you do all this on a clean installation of Drupal 7, the new role you created will end up with a role ID of 3. If you had other roles already in your database, take note of the role ID of the new role and change the definition of EXTERNAL_AUTH_RID in the code.

Actually, it just occurred to me that the creation of the "external user" role is superfluous in the code below. However, in a real application you will more than likely want to give externally authenticated users a different set of permissions than other users, so assigning them a specific role will be very helpful.

Anyway, enough of my babbling. Here's the code!

extauth.module

<?php
/**
* Implement hook_help() to display a small help message if somebody clicks the "Help" link on the modules list.
*/
function extauth_help( $path, $arg )
{
    switch (
$path )
    {
        case
'admin/help#extauth':
        {
            return(
'<p>' . t('This module allows users who login with e-mail addresses to authenticate off an external system.') . '</p>' );
        }
    }
}

/**
   * Implement hook_form_alter() to change the behaviour of the login form.
   *
   * Login validators are set in the user_login_default_validators() function in user.module.
   * They are normally set to array('user_login_name_validate',
   * 'user_login_authenticate_validate', 'user_login_final_validate').
   * We simply replace 'user_login_authenticate_validate' with 'extauth_login_validate'.
   */
function extauth_form_user_login_alter( &$form, $form_state )
{
    unset(
$form['links']);
   
$form['#validate'] = array( 'user_login_name_validate', 'extauth_login_validate', 'user_login_final_validate' );
}

function
extauth_form_user_login_block_alter( &$form, $form_state )
{
    return
extauth_form_user_login_alter( $form, $form_state );
}

/**
* Implement hook_user_profile_form_alter() to disable the ability to change email address and
* password for externally authenticated users.
*/
function extauth_form_user_profile_form_alter( &$form, $form_state )
{
    if (
strpos( $form['#user']->name, '@' ) !== false )
    {
       
$form['account']['name']['#disabled'] = TRUE;
       
$form['account']['name']['#description'] = t('The username for this account cannot be changed');
       
$form['account']['mail']['#disabled'] = TRUE;
       
$form['account']['mail']['#description'] = t('This e-mail address for this account cannot be changed.');
       
$form['account']['current_pass']['#disabled'] = TRUE;
       
$form['account']['current_pass']['#description'] = t('Neither the email address or password for this account can be changed.');
       
$form['account']['pass']['#disabled'] = TRUE;
       
$form['account']['pass']['#description'] = t('The password for this account cannot be changed.');
    }
}

/**
* The extauth_login_validate() function attempts to authenticate a user off the external system
* using their e-mail address.
*/
function extauth_login_validate( $form, &$form_state )
{
    global
$user;

   
$username = $form_state['values']['name'];

   
// In our case we're assuming that any username with an '@' sign is an e-mail address,
    // hence we're going to check the credentials against our external system.
   
if ( strpos( $username, '@' ) !== false )
    {
       
// Looks like we found them - now we need to check if the password is correct
       
if ( validateExternalUser( $username, $form_state['values']['pass'] ))
        {
           
user_external_login_register( $username, 'extauth' );
           
// I wish we didn't have to do this, but I couldn't find any other way to get the
            // uid at this point
           
$form_state['uid'] = $user->uid;
        }
// else drop through to the end and return nothing - Drupal will handle the rejection
   
}
    else
    {
       
// Username is not an e-mail address, so use standard Drupal authentication function
       
user_login_authenticate_validate( $form, $form_state );
    }
}

/**
* The extauth_user_insert() function gets called by Drupal AFTER a new user has been added.
* If the e-mail address has already been set then we don't want to overwrite it, as the user
* is probably being added manually. Thankfully the only time a user can be added without the
* e-mail being set is when an external user gets authenticated for the first time, at which
* point a user is inserted into the database without an e-mail address, which is the case we
* will deal with in this function.
*/
define( 'EXTERNAL_AUTH_RID', 3 );

function
extauth_user_insert( &$edit, &$account, $category = null )
{
   
// Remember: this function gets called whenever a new user is added, not just when a new
    // user is being added as a result of them being externally authenticated. So we need to
    // avoid running the following checks if the user is being added by some other means (eg.
    // manually by the administrator). In this simple example we're assuming that any user ID
    // that is an email address is externally authenticated. However, there are possibly
    // better ways to do this, such as look up the authmaps table and see if there is a row
    // for this user where module is 'extauth'.
   
if ( strpos( $account->name, '@' ) !== false )
    {
       
// This hook is called during the registration process, AFTER the new user has been
        // added to the users table but BEFORE the roles are written to the users_roles table
       
if ( empty( $account->mail ))
        {
           
db_update( 'users' )->fields( array( 'mail' => $account->name ))
                                ->
condition( 'uid', $account->uid, '=' )
                                ->
execute();
        }

       
// Note: you can do other stuff here, like set the password to be the md5 hash of the
        // remote password. This might be handy if you wanted to allow people to log on when
        // the external system is unavailable, but, of course, it introduces the hassle of
        // keeping the passwords in sync.

        // This is where we set that additional role to indicate that the user is authenticated
        // externally. Note that EXTERNAL_AUTH_RID is defined as 3 in this sample code but you
        // should set it to whatever Role ID is appropriate in your case, eg. create the new
        // role, do a query to find the RID for that role and set EXTERNAL_AUTH_RID to that RID.
       
$account->roles[EXTERNAL_AUTH_RID] = 'external user';
    }
}

/**
* This is the helper function that you will need to modify in order to invoke your external
* authentication mechanism.
*/
function validateExternalUser( $username, $password )
{
    return
true;
}
?>

extauth.info

name = External Authentication
description = Authenticate users against an external service.
package = Authentication
core = "7.x"
version = "7.x-1.0"

I get a TON if hits on this site from people looking for Drupal 7 external authentication code, so hopefully this article will address that need for more than a few people. If you spot any issues with the code, please feel free to drop me a line.

A few months ago a colleague mentioned Skybound's Stylizer for CSS development. It looked awesome in the video demo so I was quite eager to try it out. About one month ago I was tasked with re-theming my employer's web site (which is, of course, powered by Drupal), so figured that would be a great opportunity to test out Stylizer.

At first glance, Stylizer looks quite beautiful, especially with the dark theme and the fine graphics on the instrument panel. However, flicking constantly between Firefox and Eclipse (mostly white) and Stylizer (mostly black) almost made my retinas explode, so it was a good thing I found the option to switch Stylizer to a mostly white theme, which solved the problem.

My overall plan for the project was to use the Zen Starter Kit to create a new Zen sub-theme and build up from there. One of the things I discovered about Zen (and, indeed, many other Drupal themes) is that it takes "separation of concerns" to a whole new level, resulting in a very large number of CSS files (17 if you strip it back to the bare minimum). Drupal also likes to produce quite a few levels of nested divs. Both of these factors make learning Stylizer a bit more difficult, as it complicates the navigation of the CSS files, even with the "bullseye" functionality. As a result, I would definitely not recommend trying to learn Stylizer on a Drupal theme.

One of the great things I noticed about Stylizer when watching the demo was the code reformatting. Stylizer provides some great options, but unfortunately, not reformatting the code is not one of them. Although this isn't a huge deal, it does mean you'll somewhat lose the ability to make small mods to your sub-theme and then compare your updated files with the original version later to see what you changed. I suppose you could save all your files with Stylizer first, check those into version control and then work from there, but you will still lose the ability to easily compare your updated version against the original version of Zen (or any future versions).

The Stylizer positioning system looks excellent too, but it does rely on some "initialisation" code being included in one of your stylesheets. It also means that some effects that could be achieved in one line (eg. float: right) may be implemented with three lines instead. This bothered me a little, but not hugely.

The image replacement functionality also looked awesome, but does require a separate piece of JavaScript to be included on your page, which means integrating it into your theme. Also, I recall seeing CSS expressions being used as well, which are quite bad for performance, so I wasn't too thrilled about that either.

In terms of general usage, the only really annoying thing that I noticed was the need to separately refresh Stylizer's embedded browser (which is Chrome on OS X) whenever you wanted to fully reload the page. There is a Command-R shortcut, but this only re-loads the stylesheets. You have to click the browser window's Refresh icon if you want to actually re-render the page. This is hugely annoying, especially if you're a command-line junkie like me.

And that, honestly, is probably the key trait that will determine whether or not you fall in love with Stylizer. If you are a command-line junkie, by definition you prefer to type, not click, slide, wiggle and jiggle your way through an "instrument panel". You will probably also object to having three lines of CSS code generated where one line would suffice. And you'll definitely object to having to click an icon to re-render the page! If you're anything like me, you'll eventually start to wonder what value Stylizer is providing above and beyond just using Firebug in conjunction with your favourite IDE. And although some people swear by Stylizer for Drupal theme development, the huge number of CSS files in themes like Zen does, IMHO, make using a tool like Stylizer quite difficult. For the record, I ended up ditching Zen and modifying the Danland theme instead.

Having said all that, Stylizer is still a very solid product and I could definitely see it being of great use in certain situations eg. whipping up a static 10-page brochure site for a client with lots of photos, call-outs, testamonials, etc. on the different pages, requiring lots of different positioning, font sizes, etc. In a scenario like this, Stylizer would be extremely handy.

Ultimatey, my advice would be to download it and check it out for yourself. Whether or not it adds value to your workflow will probably depend on what your workflow currently looks like and what your overall development mindset is like.

Recently, whilst trying to make an old PHP application W3C compliant, I realised that none of the ampersands separating the GET parameters in the links that the application was generating were written as &amp; - they were all just plain ampersands.

This results in the following error from the validator:

Warning: unescaped & or unknown entity "&id"

The solution is pretty straightforward - find all instances in the code where URLs are being generated and replace each ampersand with the proper &amp; notation.

However, in this particular application, there were 502 instances! Although I would be happy with a manual search and replace solution, the problem with just searching for the ampersands is that there are obviously many others in the code that are unrelated to URL generation.

So after a short amount of Googling I came up with the following solution:

Search for: \&+([a-zA-Z]+)
Replace with: &amp;$1

This will find all strings that consist of an ampersand followed by some alpha characters and replace the ampersand with the proper code. The good thing is that this way of searching excludes logic operators, single ampersands in comments, etc. The bad thing is that it picks up ampersands at the front of things that are already HTML entities. If anybody knows of a way to exclude those using regular expressions, please feel free to drop a comment below.