I think having a drupal authentication base class (for "drupal" authentication") that could be extended by external authentication modules would save a lot of code and make implementing ldap, cas, etc. easier. If done right the user interface for admins could be more consistent and the login sequence more predictable.
Hooks could be used throughout the authentication process so non authentication modules could react to the process; but authentication modules would be expected to implement certain properties and methods. Simpletests would become easier to share for external auth modules also.
It might end up we need two base classes; one for drupal form based authentication and one for tools such as openid, cert based, and cas where you enter your credentials elsewhere.
something like:
Authentication Class Properties:
An instance of this class represents an authentication means such as an ldap server, cas, or drupal.
- boolean enabled
- string moduleName
- string instanceID (instance identifier. for ldap it might be server id. needed for cases where a single module provides more than one authentication means)
- boolean provisionDrupalAccounts signifies if successful authentication can lead to drupal user account creation.
- boolean allowPasswordResetinDrupal signifies if password stored in external structure (ldap, shib, etc.) can be reset via drupal reset pwd interface.
- string passwordResetURL
- string accountHelpURL
Authentication Class Methods:
- __construct($username)
- authenticationDerivedUserAttributes($username, $uid = NULL); returns array of attributes such as mail, first name, last name, etc. that could be mapped to drupal profile, account, etc.
- drupalAccountExists()
- userCredentialTest($pwd)
Then instead of every external author module figuring out how to fit itself into the the authentication process, user_login_authenticate_validate() could look something like:
function user_login_authenticate_validate($form, &$form_state) {
$name = $form_state['values']['name'];
$password = trim($form_state['values']['pass']);
$drupal_accnt = drupal_account_from_name($name);
check flood issues given name
$authn_providers = module_invoke_all('authentication_providers');
$results = array();
if ($drupal_acct->uid == 1) {
// for user 1, only allow authentication on user module.
$authn_providers = array('user__drupal' => $authn_providers['user__drupal']);
}
foreach ($authn_providers as $instance_id => $instance_data {
require_once($instance_data['class_path']);
$authn = new {$instance_data['classname']}($instance_data, $name);
$results[$instance_id]['authn_object'] = $authn;
$result = $authn->userCredentialTest($password);
if (!$result) {
$results[$instance_id]['result'] = USER_AUTHN_CREDENTIAL_FAILED;
continue;
}
if (!$drupal_accnt) {
if (!$authn->provisionDrupalAccounts) {
$results[$instance_id]]['result'] = USER_AUTHN_ACCT_MISSING;
continue;
}
list($result, $user_data) = $authn->provisionAccount($name);
if (!$result) {
$results[$instance_id]['result'] = USER_AUTHN_FAILED_TO_CREATE_ACCT;
continue;
}
else {
user_external_login_register($name, $user_data, $authn);
}
}
$results[$instance_id]['result'] = USER_AUTHN_SUCCESS;
$user->authnObject = $authn;
exit;
}
if ($results[$instance_id]['result'] == USER_AUTHN_SUCCESS)) {
user_login_finalize();
user_module_invoke('login', $user_data, $user);
}
else { // no authentications succeeded.
if (count($authn_providers) == 1) { // if only one authentication provider, allow it to generate error msg
list($field, $message) = $authn->loginFormSetError();
form_set_error($field, $message);
}
else {
form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password'))));
watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
}
}
}