Creating Drupal External Authentication

30 November -0001

Recently I recommended that my employer, the University of Pennsylvania School of Arts and Sciences, begin pushing Drupal as a CMS solution for departmental websites. There were a lot of factors to consider when evaluating the various CMS solutions available, especially for an institution of higher education. We took a look at a number of CMS solutions and based our evaluation on a wide breadth of criteria. Ultimately we scored each of the CMS solutions based on a common set of benchmarks. Typo3 was actually our first choice for deployment. Typo3 has a strongly tiered, but central deployment that allows you to set up test, staging and production servers but also maintain a single central deployment from which a multitude of sites can be run. However, Typo3 ha(s|d) a number of problems with MySQL 5 and therefore we decided to go with our close second choice - Drupal.

Here's a rundown of how Drupal fared in our evaluation (PennKey is our centrally managed authorization system):

Official Homepage

Product Feature List

External Authentication Capability

  • Modularity of authentication system?
    • Somewhat, it looks as though a custom module or alteration of an existing module would be required to enable PennKey authentication in Drupal.
  • Estimated difficulty of inserting PennKey authentication
    • medium to high
  • Relative importance of this Criteria:
    • High

Plugins

  • Desc: How modular is the system?
    • The site is completely modular, even the core contains modules.
  • How are plugins managed (is there a central body that vets new modules/plugins or are they available ad-hoc)?
    • Modules are managed in an ad-hoc fashion. Core modules are contributed by the dev team, but virtually any developer can create other modules.
  • Is it easy to develop new modules/plugins?
    • Module development is well documented, with several tutorials and core documentation available.
  • How easy is it to add/remove plugins?
    • Modules are added via the web based management interface.
  • Can new modules/plugins be deployed universally or do they have to be installed/maintained case by case?
    • Case by case
  • Relative Importance of this Criteria:
    • Medium

Content Workflow / Authorization Granularity

  • Desc: How many levels of authorization exist for the creation/alteration and publishing of content?
  • Does the system have stock roles (admin, editor, author) or can custom roles be implemented?
    • Drupal allows customizing permissions to perform system and CMS tasks based on groups. System has two stock groups at first - authenticated and unauthenticated users. New groups can be added and their permissions defined. Each user is assigned to a group and their group membership determines their permissions.
  • Does the system allow implementation of business rules to publishing (i.e. a document requires an editor to give the OK before the document goes "live")?
    • This functionality requires the workflow module to be installed [3]
  • Can content be published/revoked based on dates?
    • This functionality requires and external module
  • Relative Importance of this Criteria:
    • Medium

Ease of Patching / Maintaining Base System

  • Desc: Is this a single shared install which serves multiple sites, or do we have separate instances for each site?
    • The core code can power multiple sites, but themes, plugins and other extras have to be installed on a per-site basis. [4]
  • What form are patches distributed in (zip files that can be uploaded via a web interface, shell scripts, code snippets that have to be cut and pasted into existing code base, etc.)?
    • Module patches are distributed as new modules that are downloaded from the main site. Patches to the core code are distributed as new versions of the code. Upgrades must be downloaded and unzipped to overwrite the existing code. An install script run via web browser updates the database.
  • How is patch notification handled (mailing list, web site posting, etc.)?
    • There are multiple mailing lists, web sites and support forums [5]
  • How compact is the system? Does maintenance just involve a core code set or are external extensions required?
    • Drupal is primarily composed of the PHP code and related database. No external extensions should be required.
  • Relative Importance of this Criteria:
    • Mediaum

Performance / Optimization / Scalability

  • Desc: Can this system handle all SAS traffic? Will we need to tweak for performance?
    • Like many LAMP systems, Drupal performance depends more on the stack below it than the code base per se [6]
  • Relative Importance of this Criteria: ?

Administrative Control / Auth Granularity

  • Desc: What kinds of control can we give to individual groups or consultants?
    • Consultants should develop modules and themes that can be integrated with a standard core.
  • Do consultants have to have access to Penn systems or can they develop on their own environments and deliver customized packages to be plugged into the Penn deployment?
    • Consultants should be able to develop modules and themes independent of the Penn installations.
  • Relative Importance of this Criteria:
    • Medium

Infrastructure Match

  • Desc: How well does this product match with SASC's existing infrastructure and staff expertise in terms of:
  • Database connectivity:
    • MySQL is the primary database, PostgreSQL is also supported. Currently MS SQL and Oracle are not supported.
  • Web Server:
    • Apache
  • Scripting Language:
    • PHP
  • OS:
    • Any, Drupal is only dependent on the Apache/PHP/MySQL configuration
  • Relative Importance of this Criteria:
    • High

Health of Community / Future Outlook of Product / Developer Community

  • Desc: What does the environment around the development of this product look like? Are there 2 main developers who are currently feuding? Is there a large community writing modules for this app? Is this product backed by a commercial entity?
  • Project lifespan
    • Project was started in 2000
  • Organized events for developers (sprints, conferences, etc.)
    • There are many Drupal conferences, events, and user groups worldwide [7]
  • Active mailing list or forums
    • Drupal maintains several mailing lists [8]
    • Drupal has an active support forum [9]
  • Relative Importance of this Criteria: ?

Import / Export of Data

  • Desc: How hard is it to get data in or out of this system?
  • Do data migration tools exist?
    • Drupal includes an Import/Export API [10]
  • Is data stored in local systems similar to existing ones (i.e. Oracle, MySQL, MS SQL)?
    • MySQL
  • Can data be exported in XML format?
    • yes
  • Relative Importance of this Criteria: ?

Ease of Separating Development, Staging, and Production Environments

  • Desc: How does this product handle development, staging, and production environments?
    • Drupal requires separate environments but the PHP/MySQL could be replicated quite easily.
  • Relative Importance of this Criteria: ?

Security

  • Desc: What do we think of the security stance of this product, considering:
    • Drupal has a dedicated security team, a security website, email alerts and an RSS feed for updates.
  • Time to patch critical vulns
  • Patch release schedule
    • New versions of Drupal are released roughly every 6 months
  • Ease or difficulty of patching
  • Security designed into architecture at every level
  • Solidity of authentication and authorization systems
  • Character of historical vulns
  • Separation of core features from 3rd party modules
  • OS and Framework level module requirements
  • Relative Importance of this Criteria: ?

Accessibility

  • Desc: Is this product easily made Section 508 compliant?
  • Relative Importance of this Criteria: ?

Total Cost of Ownership

  • Desc: What costs should be associated with this product, considering:
  • Licensing:
    • free (GPL)
  • Maintenance Contract:
  • Training:
    • There are several books written about Drupal [12]
  • Existing Resource Reuse:

Ultimately the ability to tie in our central authentication scheme was of utmost importance since all our user accounts are maintained by the university's central system.

Ideally what I wanted was a system where I could flip a switch and utilize Penn's own authentication system, but none were to be found. Instead I started to look into customizing Drupal's own authentication scheme. When I first started looking at Drupal I was extremely tempted to hack the user module. This is highly discouraged, and it's a sloppy way to implement an external authentication. If I took this tack it would mean that any future upgrades would likely break the customization.

We have a custom PHP library that we use to handle the authentication. In a nutshell it examines session variables to determine if the user is logged in. If not the user is redirected to an external webpage that handles authentication. If the user authenticates there they are popped back to a specified location with a token. The custom PHP library checks the validity of the token and if it is valid the user is flagged as good.

I struggled for a long time to try and figure out how to hook this scheme into Drupal's user authentication module. Essentially this module expects that certain form data from the user module will be passed back to Drupal to be checked. The core routine in this operation is the user_authenticate() function, which expects a username and a password that can be looked up in Drupal's local tables (or barring that external data sources).

Due to our unique configuration I would not have access to passwords. I would be able to get a distinct username and a token, but that was all.

At first I attempted to search through the Drupal code and identify places where the user would be denied access. All authentication failures (ultimately) call the function drupal_access_denied() in the includes/common.inc file. My first thought was to intercept calls to this function and kick off an external authentication routine, but by the time Drupal reaches this call the user has already failed authentication, and even if intercepted the call would have to include some sort of a page refresh to recheck authorization levels - not an ideal solution.

I also thought to insert the authorization in the Drupal index.php page in the switch code for the case MENU_ACCESS_DENIED. This solution was more compact and slightly cleaner, but again, I was intercepting the call after the access privilege had already failed a check so a refresh was required.

What I ultimately needed was a custom module that when called would allow the user to initiate an external authentication check that would return the user to the module's summary page - indicating whether or not the user had succeeded in authenticating. Doing this was actually a lot simpler than I thought. The module is quite small and consists of just two files. The module dovetails quite nicely with the hooks for the existing user module as well. The only trick is that the theme has to be customized to display the log in and log out options like so:

<div id="pennkey_loginout">
      		<span id="pennkey_link">
          <?php if ($logged_in) {?>
          	<a href="http://our.test.sitesas.upenn.edu/pennkey_logout">Log Out</a>
          <?php } else { ?>
          	<a href="http://our.test.site.sas.upenn.edu/pennkey">Log In</a>
          <?php } ?>
          </span>
</div> <!-- PennKey login and out -->

Other than this customization though the module is self sufficient. All that is required is for the user to successfully authenticate against Penn's external sources, and to have a user account on the Drupal system. The account retrieved on the basis of username only, but since Penn's external authentication forces unique user names, this was not a problem. This system cuts password management entirely out of Drupal and allows for a seamless external authentication. The module consists of just two files:

pennkey.info

; Id: pennkey.module,v 1.0 2007/12/11 11:36:00 jukeane@sas.upenn.edu Exp $
name = PennKey Authentication
description = "PennKey authentication module"
package = Other
version = VERSION

; Information added by drupal.org packaging script on 2007-01-15
version = "1.0"
project = "drupal"

and pennkey.module

<?php
// $Id: pennkey.module,v 1.0 2007/06/11 13:36:00 justin Exp $

/**
* Display help and module information
* @param section which section of the site we're displaying help
* @return help text for section
*/
function pennkey_help($section='') {

  $output = '';

  switch ($section) {
    case "admin/help#pennkey":
      $output = '<p>'.  t("Enables external PennKey authentication."). '</p>';
      break;
  }

  return $output;
} // function onthisdate_help

/**
* Valid permissions for this module
* @return array An array of valid permissions for the onthisdate module
*/

function pennkey_perm() {
  return array('access pennkey content');
} // function onthisdate_perm()



function pennkey_all() {
		global $user;
		$page_content = '';

	 	/**
		* The next four lines handle Penn's external
		* authentication process
		*/
		require_once('penn/websec.php');
		$session = new Websec($web_app);
		$session->validate();
		$penn_key = $session->pennkey;
		//load the user if they pass
		if ($penn_key) {
			if ($account = user_load(array('name' => $penn_key, 'status' => 1))) {
			    $user = $account;
			    $page_content = 'Thank you, you are now logged in.';
			}
			else {
				$page_content = 'We are sorry, although your account is 
				valid it is not authorized to access this site.';
			}
		}
		else {
			$page_content = 'We are sorry, a problem was encountered with your 
			PennKey authentication.';
		}
	return $page_content;
}

function pennkey_logout() {
	global $user;

	/**
	* Set up the websec properties
	*/
	require_once('penn/websec.php');
	$session = new Websec($web_app);
	$session->expire();
	user_logout();
}

function pennkey_menu($may_cache) {

	$items[] = array('path' => 'pennkey',
			'title' => t('PennKey Authentication'),
			'callback' => 'pennkey_all',
			'access' => user_access('access pennkey content'),
			'type' => MENU_CALLBACK);
	$items[] = array('path' => 'pennkey_logout',
			'title' => t('PennKey Log Out'),
			'callback' => 'pennkey_logout',
			'access' => user_access('access pennkey content'),
			'type' => MENU_CALLBACK);

	return $items;
}

?>