Commit dbaddb73 authored by Dries's avatar Dries
Browse files

- Patch #148419 by Moshe, bjaspan et al: refactor distributed auth out of user.module.

parent 5874ed10
......@@ -40,9 +40,11 @@ function drupal_help($section) {
return $output;
case 'admin/settings/distributed-authentication':
return '<p>'. t('Using this your site can "call home" to another Drupal server. By calling home to drupal.org and sending a list of your installed modules and themes, you help rank projects on drupal.org and so assist all Drupal administrators to find the best components for meeting their needs. If you want to register with a different server, you can change the Drupal XML-RPC server setting -- but the server has to be able to handle Drupal XML. Some XML-RPC servers may present directories of all registered sites. To get all your site information listed, go to the <a href="@site-settings">site information settings page</a> and set the site name, the e-mail address, the slogan, and the mission statement.', array('@site-settings' => url('admin/settings/site-information'))) .'</p>';
case 'user/help#drupal':
return variable_get('drupal_authentication_service', 0) ? t('<p><a href="@Drupal">Drupal</a> is the name of the software that powers %this-site. There are Drupal websites all over the world, and many of them share their registration databases so that users may freely log in to any Drupal site using a single <strong>Drupal ID</strong>.</p>
<p>So please feel free to log in to your account here at %this-site with a username from another Drupal site. The format of a Drupal ID is similar to an e-mail address: <strong>username</strong>@<em>server</em>. An example of a valid Drupal ID is <strong>mwlily</strong>@<em>drupal.org</em>.</p>', array('@Drupal' => 'http://drupal.org', '%this-site' => variable_get('site_name', 'Drupal'))) : '';
case 'user/register':
if (!user_access('administer users')) {
return t('Note: if you have an account with another Drupal site, you may be able to <a href="!login">log in</a> with its username and password instead of registering.', array('!login' => url('user/login')));
}
break;
}
}
......@@ -147,6 +149,66 @@ function drupal_distributed_authentication_settings() {
return system_settings_form($form);
}
function drupal_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'user_login_block' || $form_id == 'user_login') {
// Splice in our validate handler for authentication if user is performing a distributed login.
// Remove the local authentication handler added by user.module
if (!empty($form_state['post']['name']) && drupal_is_distributed_login($form_state['post']['name'])) {
$key = array_search('user_login_authenticate_validate', $form['#validate']);
$form['#validate']['key'] = 'drupal_distributed_validate';
}
}
}
/**
* When login form is shown on full page, let users know that Drupal IDs are accepted.
*
* @return void
**/
function drupal_form_user_login_alter(&$form, $form_state) {
if (variable_get('drupal_authentication_service', FALSE)) {
$form['name']['#description'] = t('Enter your @s username, or a Drupal ID from another web site.', array('@s' => variable_get('site_name', 'Drupal')));
}
}
/**
* Given a username, determine if user is attempting a distributed login.
*
* @return boolean
**/
function drupal_is_distributed_login($name) {
return variable_get('drupal_authentication_service', FALSE) && (strpos($name, '@') || variable_get('drupal_default_da_server', ''));
}
/**
* A custom validate handler on the login form. Checks supplied username/password against a remote Drupal site.
*
* @return boolean
**/
function drupal_distributed_validate($form, &$form_state) {
global $user;
if ($user->uid) {
return;
}
$name = $form_state['values']['name'];
$pass = trim($form_state['values']['pass']);
// Strip name and server from ID:
if ($server = strrchr($name, '@')) {
$name = substr($name, 0, strlen($name) - strlen($server));
$server = substr($server, 1);
}
if (drupal_auth($name, $pass, $server)) {
// We have a successful authentication. Login or register the user.
if ($server) {
$name .= '@'. $server;
}
user_external_login_register($name, 'drupal');
}
}
/**
* Implementation of hook_cron(); handles pings to and from the site.
*/
......@@ -305,23 +367,10 @@ function drupal_notify($server) {
}
/**
* Implementation of hook_info().
*/
function drupal_info($field = 0) {
$info['name'] = 'Drupal';
$info['protocol'] = 'XML-RPC';
if ($field) {
return $info[$field];
}
else {
return $info;
}
}
/**
* Implementation of hook_auth().
*/
* Attempt to authenticate using the presented credentials and Drupal site.
*
* @return boolean
**/
function drupal_auth($username, $password, $server = FALSE) {
if (variable_get('drupal_authentication_service', 0)) {
if (!$server) {
......@@ -363,21 +412,21 @@ function drupal_menu() {
'access arguments' => array('administer site configuration'),
);
if (variable_get('drupal_authentication_service', 0)) {
$items['drupal'] = array(
'title' => 'Drupal',
'page callback' => 'drupal_page_help',
'access callback' => TRUE,
'type' => MENU_SUGGESTED_ITEM,
$items['drupal/help'] = array(
'title' => t('External login tips'),
'page callback' => 'drupal_page_help',
'type' => MENU_CALLBACK,
);
}
return $items;
}
/**
* Menu callback; print Drupal-authentication-specific information from user/help.
* Menu callback; print Drupal-authentication-specific information.
*/
function drupal_page_help() {
return drupal_help('user/help#drupal');
return t('<p><a href="@Drupal">Drupal</a> is the name of the software that powers %this-site. There are Drupal websites all over the world, and many of them share their registration databases so that users may freely log in to any Drupal site using a single <strong>Drupal ID</strong>.</p>
<p>So please feel free to log in to your account here at %this-site with a username from another Drupal site. The format of a Drupal ID is similar to an e-mail address: <strong>username</strong>@<em>server</em>. An example of a valid Drupal ID is <strong>mwlily</strong>@<em>drupal.org</em>.</p>', array('@Drupal' => 'http://drupal.org', '%this-site' => variable_get('site_name', 'Drupal')));
}
/**
......@@ -387,11 +436,6 @@ function drupal_page_help() {
*/
function drupal_login($username, $password) {
if (variable_get('drupal_authentication_service', 0)) {
if ($user = user_load(array('name' => $username, 'pass' => $password, 'status' => 1))) {
return $user->uid;
}
else {
return 0;
}
return user_authenticate($username, $password);
}
}
......@@ -70,6 +70,33 @@ function user_external_load($authname) {
}
}
/**
* Perform standard Drupal login operations for a user object. The
* user object must already be authenticated. This function verifies
* that the user account is not blocked/denied and then performs the login,
* updates the login timestamp in the database, invokes hook_user('login'),
* regenerates the session, etc.
*
* @param $account
* An authenticated user object to be set as the currently logged
* in user.
* @return boolean
* TRUE if the login succeeds, FALSE otherwise.
*/
function user_external_login($account) {
$form = drupal_get_form('user_login');
$state = array();
user_login_name_validate($form, $state, (array)$account);
if (form_get_errors()) {
return FALSE;
}
global $user;
$user = $account;
user_login_submit($form, $state, (array)$account);
return TRUE;
}
/**
* Fetch a user object.
*
......@@ -210,7 +237,7 @@ function user_save($account, $array = array(), $category = 'account') {
user_module_invoke('after_update', $array, $user, $category);
}
else {
if (!isset($array['created'])) { // Allow 'created' to be set by hook_auth
if (!isset($array['created'])) { // Allow 'created' to be set by the caller
$array['created'] = time();
}
......@@ -258,7 +285,7 @@ function user_save($account, $array = array(), $category = 'account') {
db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);
// Save user roles (delete just to be safe).
if (is_array($array['roles'])) {
if (isset($roles) && is_array($array['roles'])) {
db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
foreach (array_keys($array['roles']) as $rid) {
if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
......@@ -516,7 +543,7 @@ function user_login_block() {
$form = array(
'#action' => url($_GET['q'], array('query' => drupal_get_destination())),
'#id' => 'user-login-form',
'#validate' => array('user_login_validate'),
'#validate' => user_login_default_validators(),
'#submit' => array('user_login_submit'),
);
$form['name'] = array('#type' => 'textfield',
......@@ -802,11 +829,6 @@ function user_menu() {
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['user/help'] = array(
'title' => 'Help',
'page callback' => 'user_help_page',
'type' => MENU_CALLBACK,
);
// Admin user pages
$items['admin/user'] = array(
......@@ -1006,20 +1028,6 @@ function user_set_authmaps($account, $authmaps) {
}
}
function user_auth_help_links() {
$links = array();
foreach (module_list() as $module) {
if (module_hook($module, 'auth')) {
$links[] = l(module_invoke($module, 'info', 'name'), 'user/help', array('fragment' => $module));
}
}
return $links;
}
/*** User features *********************************************************/
function user_login(&$form_state, $msg = '') {
global $user;
......@@ -1039,25 +1047,47 @@ function user_login(&$form_state, $msg = '') {
'#required' => TRUE,
'#attributes' => array('tabindex' => '1'),
);
if (variable_get('drupal_authentication_service', FALSE) && count(user_auth_help_links()) > 0) {
$form['name']['#description'] = t('Enter your @s username, or an ID from one of our affiliates: !a.', array('@s' => variable_get('site_name', 'Drupal'), '!a' => implode(', ', user_auth_help_links())));
}
else {
$form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
}
$form['name']['#description'] = t('Enter your @s username.', array('@s' => variable_get('site_name', 'Drupal')));
$form['pass'] = array('#type' => 'password',
'#title' => t('Password'),
'#description' => t('Enter the password that accompanies your username.'),
'#required' => TRUE,
'#attributes' => array('tabindex' => '2'),
);
$form['#validate'] = user_login_default_validators();
$form['submit'] = array('#type' => 'submit', '#value' => t('Log in'), '#weight' => 2, '#attributes' => array('tabindex' => '3'));
return $form;
}
function user_login_validate($form, &$form_state) {
if ($form_state['values']['name']) {
/**
* Setup a series for validators which check for blocked/denied users,
* then authenticate against local database, then return an error if
* authentication fails. Distributed authentication modules (e.g.
* drupal.module) are welcome to use hook_form_alter() to change this
* series in order to authenticate against their user database instead
* of local users table.
*
* We use three validators instead of one since external authentication
* modules usually only need to alter the second validator. See
* drupal_form_alter() in drupal.module for an example of altering
* this series of validators.
*
* @return array
* A simple list of validate functions.
**/
function user_login_default_validators() {
return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
}
/**
* A FAPI validate handler. Sets an error is supplied username has been blocked or denied access.
*
* @return void
**/
function user_login_name_validate($form, &$form_state) {
if (isset($form_state['values']['name'])) {
if (user_is_blocked($form_state['values']['name'])) {
// blocked in user administration
form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
......@@ -1066,17 +1096,56 @@ function user_login_validate($form, &$form_state) {
// denied by access controls
form_set_error('name', t('The name %name is a reserved username.', array('%name' => $form_state['values']['name'])));
}
else if ($form_state['values']['pass']) {
$user = user_authenticate($form_state['values']['name'], trim($form_state['values']['pass']));
}
}
if (!$user->uid) {
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']));
}
}
/**
* A validate handler on the login form. Check supplied username/password against local users table.
* If successful, sets the global $user object.
* @return void
**/
function user_login_authenticate_validate($form, &$form_state) {
global $user;
if ($account = user_authenticate($form_state['values']['name'], trim($form_state['values']['pass']))) {
$user = $account;
}
}
/**
* A validate handler on the login form. Should be the last validator. Sets an error if
* user has not been authenticated yet.
*
* @return void
**/
function user_login_final_validate($form, &$form_state) {
global $user;
if (!$user->uid) {
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']));
}
}
/**
* Try to log in the user locally.
*
* @return
* A $user object, if successful.
**/
function user_authenticate($name, $pass) {
global $user;
if ($account = user_load(array('name' => $name, 'pass' => $pass, 'status' => 1))) {
$user = $account;
return $user;
}
}
/**
* A validate handler on the login form. Update user's login timestamp, fire hook_user('login), and generate new session ID.
*
* @return void
**/
function user_login_submit($form, &$form_state) {
global $user;
if ($user->uid) {
......@@ -1093,52 +1162,22 @@ function user_login_submit($form, &$form_state) {
}
}
function user_authenticate($name, $pass) {
/**
* Helper function for authentication modules. Either login in or registers the current user, based on username.
* Either way, the global $user object is populated based on $name.
*
* @return void
**/
function user_external_login_register($name, $module) {
global $user;
// Try to log in the user locally. Don't set $user unless successful.
if ($account = user_load(array('name' => $name, 'pass' => $pass, 'status' => 1))) {
$user = $account;
return $user;
}
// Strip name and server from ID:
if ($server = strrchr($name, '@')) {
$name = substr($name, 0, strlen($name) - strlen($server));
$server = substr($server, 1);
}
// When possible, determine corresponding external auth source. Invoke
// source, and log in user if successful:
if ($server && ($result = user_get_authmaps("$name@$server"))) {
if (module_invoke(key($result), 'auth', $name, $pass, $server)) {
$user = user_external_load("$name@$server");
watchdog('user', 'External load by %user using module %module.', array('%user' => $name .'@'. $server, '%module' => key($result)));
}
}
// Try each external authentication source in series. Register user if
// successful.
else {
foreach (module_implements('auth') as $module) {
if (module_invoke($module, 'auth', $name, $pass, $server)) {
if ($server) {
$name .= '@'. $server;
}
$user = user_load(array('name' => $name));
if (!$user->uid) { // Register this new user.
$userinfo = array('name' => $name, 'pass' => user_password(), 'init' => $name, 'status' => 1);
if ($server) {
$userinfo["authname_$module"] = $name;
}
$user = user_save('', $userinfo);
watchdog('user', 'New external user: %user using module %module.', array('%user' => $name, '%module' => $module), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
break;
}
}
}
$user = user_load(array('name' => $name));
if (!isset($user->uid)) {
// Register this new user.
$userinfo = array('name' => $name, 'pass' => user_password(), 'init' => $name, 'status' => 1, "authname_$module" => $name);
$user = user_save('', $userinfo);
watchdog('user', 'New external user: %name using module %module.', array('%name' => $name, '%module' => $module), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
}
return $user;
}
/**
......@@ -1287,11 +1326,7 @@ function user_register() {
if (!$admin) {
$form['user_registration_help'] = array('#value' => filter_xss_admin(variable_get('user_registration_help', '')));
}
$affiliates = user_auth_help_links();
if (!$admin && count($affiliates) > 0) {
$affiliates = implode(', ', $affiliates);
$form['affiliates'] = array('#value' => '<p>'. t('Note: if you have an account with one of our affiliates (!s), you may <a href="@login_uri">login now</a> instead of registering.', array('!s' => $affiliates, '@login_uri' => url('user'))) .'</p>');
}
// Merge in the default user edit fields.
$form = array_merge($form, user_edit_form($form_state, NULL, NULL, TRUE));
if ($admin) {
......@@ -2746,39 +2781,7 @@ function user_help($section) {
</ul>', array('@permissions' => url('admin/user/access')));
case 'admin/user/search':
return '<p>'. t('Enter a simple pattern ("*" may be used as a wildcard match) to search for a username. For example, one may search for "br" and Drupal might return "brian", "brad", and "brenda".') .'</p>';
case 'user/help#user':
$site = variable_get('site_name', 'Drupal');
$affiliates = user_auth_help_links();
if (count($affiliates)) {
$affiliate_info = implode(', ', user_auth_help_links());
}
else {
$affiliate_info = t('one of our affiliates');
}
$output = t('
<h3>Distributed authentication<a id="da"></a></h3>
<p>One of the more tedious moments in visiting a new website is filling out the registration form. Here at @site, you do not have to fill out a registration form if you are already a member of !affiliate-info. This capability is called <em>distributed authentication</em>, and <a href="@drupal">Drupal</a>, the software which powers @site, fully supports it.</p>
<p>Distributed authentication enables a new user to input a username and password into the login box, and immediately be recognized, even if that user never registered at @site. This works because Drupal knows how to communicate with external registration databases. For example, lets say that new user \'Joe\' is already a registered member of <a href="@delphi-forums">Delphi Forums</a>. Drupal informs Joe on registration and login screens that he may login with his Delphi ID instead of registering with @site. Joe likes that idea, and logs in with a username of joe@remote.delphiforums.com and his usual Delphi password. Drupal then contacts the <em>remote.delphiforums.com</em> server behind the scenes (usually using <a href="@xml">XML-RPC</a>, <a href="@http-post">HTTP POST</a>, or <a href="@soap">SOAP</a>) and asks: "Is the password for user Joe correct?". If Delphi replies yes, then we create a new @site account for Joe and log him into it. Joe may keep on logging into @site in the same manner, and he will always be logged into the same account.</p>', array('!affiliate-info' => $affiliate_info, '@site' => $site, '@drupal' => 'http://drupal.org', '@delphi-forums' => 'http://www.delphiforums.com', '@xml' => 'http://www.xmlrpc.com', '@http-post' => 'http://www.w3.org/Protocols/', '@soap' => 'http://www.soapware.org'));
foreach (module_list() as $module) {
if (module_hook($module, 'auth')) {
$output .= "<h4><a id=\"$module\"></a>". module_invoke($module, 'info', 'name') .'</h4>';
$output .= module_invoke($module, 'help', "user/help#$module");
}
}
return $output;
}
}
/**
* Menu callback; Prints user-specific help information.
*/
function user_help_page() {
return user_help('user/help#user');
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment