/** * login * * The script checks provided name and password against the local database. * * If no record matches, and if the provided name explicitly mentions some origin server * (e.g., '*****@*****.**'), then this server is asked to authenticate the user. * This is done by transmitting the user name and the password to the origin server, * through a XML-RPC call ([code]drupal.login[/code] at [script]services/xml_rpc.php[/script]). * On success the origin server will provide the original id for the user profile. * Else a null id will be returned. * * On successful remote authentication the surfer will be considered as logged, either * as an associate, a member (default case), or as a subscriber (for closed communities). * * On successful remote authentication a 'shadow' user profile will be created locally, * using another id, and a copy of the authentication url saved in the password field. * Also the user description explicitly references the original user profile. * This local record may be referenced in pages published locally. * * This means that on subsequent visits the 'shadow' profile will be retrieved, and the origin * server will be sollicitated again for credentials validation. * As a consequence the validity of login data is always checked by the server that actually * stores the original user profile. * If the user profile is modified or is deleted this change will be taken into account on next login. * * @link http://drupal.org/node/312 Using distributed authentication (drupal.org) * * This script also allows for a last resort password. * When a webmaster has lost his password, and if there is no other associate to help, * he can modify manually the file [code]parameters/control.include.php[/code] to add * a parameter [code]$context['last_resort_password'][/code], followed by a long passphrase * of at least seven characters. For example: * [php] * $context['last_resort_password'] = '******'; * [/php] * * Then he can authenticate normally, using this password, and any name. * * On successful login the returned array contains following named atributes: * - id - record in the table of users (can be a shadow record) * - nick_name - name to be displayed in user menu, and server messages * - email - user e-mail address, if any * - capability - either 'A'ssociate, 'M'ember, 'S'ubscriber or '?' * * @param string the nickname or the email address of the user * @param string the submitted password * @return the record of the authenticated surfer, or NULL * * @see users/login.php * @see services/blog.php */ public static function login($name, $password) { global $context; // using the last resort password if (isset($context['last_resort_password']) && strlen(trim($context['last_resort_password'])) >= 1 && $password == $context['last_resort_password']) { // this is an event to remember Logger::remember('users/users.php: ' . i18n::c('lrp has logged in'), i18n::c('Login using the last resort password')); // a fake associate $user = array(); $user['id'] = 1; $user['nick_name'] = 'lrp'; $user['email'] = ''; $user['capability'] = 'A'; return $user; } // user has not been authenticated yet $authenticated = FALSE; $item = NULL; // search a user profile locally $query = "SELECT * FROM " . SQL::table_name('users') . " AS users" . " WHERE users.email LIKE '" . SQL::escape($name) . "' OR users.nick_name LIKE '" . SQL::escape($name) . "' OR users.full_name LIKE '" . SQL::escape($name) . "'"; if (isset($context['users_connection']) && ($item = SQL::query_first($query, FALSE, $context['users_connection']))) { // the user has been explicitly locked if ($item['capability'] == '?') { return NULL; } elseif ($item['authenticate_failures'] >= 3 && $item['authenticate_date'] > gmstrftime('%Y-%m-%d %H:%M:%S', time() - 3600)) { Logger::error(i18n::s('Wait for one hour to recover from too many failed authentications.')); return NULL; // successful local check } elseif (md5($password) == $item['password']) { $authenticated = TRUE; } } // we have to authenticate externally, if this has been explicitly allowed if (!$authenticated && isset($context['users_authenticator']) && $context['users_authenticator']) { // load and configure an authenticator instance include_once $context['path_to_root'] . 'users/authenticator.php'; if (!($authenticator = Authenticator::bind($context['users_authenticator']))) { return NULL; } // submit full name to authenticator if (isset($item['full_name']) && trim($item['full_name']) && $authenticator->login($item['full_name'], $password)) { $authenticated = TRUE; } elseif ($authenticator->login($name, $password)) { $authenticated = TRUE; } } // we have to create a shadow record if ($authenticated && !isset($item['id'])) { // shadow record $fields = array(); $fields['nick_name'] = $name; $fields['description'] = i18n::s('Authenticated externally.'); $fields['password'] = '******'; $fields['with_newsletters'] = 'Y'; $fields['without_alerts'] = 'N'; $fields['without_confirmations'] = 'N'; $fields['authenticate_date'] = gmstrftime('%Y-%m-%d %H:%M:%S'); $fields['authenticate_failures'] = 0; // stop on error if (!($fields['id'] = Users::post($fields))) { return NULL; } // retrieve the shadow record $item = Users::get($fields['id']); } // bad credentials if (!$authenticated && isset($item['id'])) { // increment failing authentications during last hour if (isset($item['authenticate_date']) && $item['authenticate_date'] >= gmstrftime('%Y-%m-%d %H:%M:%S', time() - 3600)) { $query = "UPDATE " . SQL::table_name('users') . " SET authenticate_failures=authenticate_failures+1" . " WHERE id = " . $item['id']; if ($item['authenticate_failures'] >= 2) { Logger::error(i18n::s('Wait for one hour to recover from too many failed authentications.')); } elseif ($item['authenticate_failures'] == 1) { Logger::error(i18n::s('You have 1 grace authentication attempt.')); } // first failure in a row } else { $query = "UPDATE " . SQL::table_name('users') . " SET authenticate_date = '" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'" . ", authenticate_failures=1" . " WHERE id = " . $item['id']; Logger::error(i18n::s('You have 2 grace authentication attempts.')); } // update target record SQL::query($query, FALSE, $context['users_connection']); // no user record is returned return NULL; } // not authenticated, or no record if (!$authenticated || !isset($item['id'])) { return NULL; } // generate a random handle if necessary $handle = ''; if (!isset($item['handle']) || !$item['handle']) { $item['handle'] = md5(rand()); $handle = ", handle='" . $item['handle'] . "' "; } // remember silently the date of the last login, and reset authentication counter $query = "UPDATE " . SQL::table_name('users') . " SET login_date='" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'" . ", login_address='" . $_SERVER['REMOTE_ADDR'] . "'" . ", authenticate_date='" . gmstrftime('%Y-%m-%d %H:%M:%S') . "'" . ", authenticate_failures=0" . $handle . " WHERE id = " . $item['id']; SQL::query($query, FALSE, $context['users_connection']); // valid user - date of previous login is transmitted as well return $item; }