The Login Toboggan module improves the Drupal login system by offering the following features:
  1. Allow users to login using either their username OR their e-mail address.
  2. Allow users to define their own password.
  3. Allow users to login immediately.
  4. Provide a login form on Access Denied pages for non-logged-in (anonymous) users.
  5. The module provides two login block options: One uses JavaScript to display the form within the block immediately upon clicking "log in". The other brings the user to a separate page, but returns the user to their original page upon login.
  6. Customize the registration form with two e-mail fields to ensure accuracy.
These features may be turned on or off in the Login Toboggan settings.

Feel funny about people logging in at "http://yoursite.com/toboggan/login"? (Yes, we know it\'s a silly name.) You can use the path.module\'s "url aliases" to redefine Login Toboggan\'s paths as something else (perhaps: "usr/login" or just "login").

Because this module completely reorients the Drupal login process you will probably want to edit the welcome e-mail on the user settings page. For instance if you have enabled "Set passwords during registration" you probably should not send the user\'s password out in the welcome e-mail. Also when either "Set passwords during registration" or "Immediate login" are enabled, the %login_url becomes a verification url that the user MUST visit in order to enable authenticated status. The following is an example welcome e-mail:

', array('%url' => url('admin/settings/logintoboggan'))); $example = t(' %username, Thank you for registering at %site. IMPORTANT: For full site access, you will need to click on this link or copy and paste it in your browser: %login_url This will verify your account and log you into the site. In the future you will be able to log in using the username and password that you created during registration. Your new %site membership also enables to you to login to other Drupal powered websites (e.g. http://www.drupal.org/) without registering. Just use the following Drupal ID along with the password you\'ve chosen: Drupal ID: %username@%uri_brief -- %site team'); $output .= form(form_textarea('', 'foo', $example, 60, 15)); $output .= t('

Note that if you have set the "Visitors can create accounts but administrator approval is required" option for account approval, and are also allowing users to set their own password, the user will immediately receive the permissions of the pre-authorized user role--you may wish to set the pre-authorized role to "anonymous user" if you wish the newly created user to only have anonymous permissions.

When a site administrator unblocks a user who is awaiting administrator approval, they must also manually remove the user from the site\'s pre-authorized role and add the "authenticated user" role to the user (if you are using the "Visitors can create accounts and no administrator approval is required" option, this will happen automatically).

'); return $output; case 'admin/modules#description': return t("Improves Drupal's login system."); case 'admin/settings/logintoboggan': return '

'. t('Customize your login and registration system. More help can be found here', array('%url' => url('admin/help/logintoboggan'))). '

'; } } /** * Implementation of hook_menu() * * @ingroup logintoboggan_core */ function logintoboggan_menu($may_cache) { global $user; $items = array(); if ($may_cache) { //local tab for login form $items[] = array('path' => 'toboggan/login', 'title' => t('login'), 'callback' => 'logintoboggan_login', 'access' => TRUE, 'type' => MENU_LOCAL_TASK, 'weight' => 1); //local tab for registration form $items[] = array('path' => 'toboggan/register', 'title' => t('register'), 'callback' => '_logintoboggan_register', 'access' => TRUE, 'type' => MENU_LOCAL_TASK, 'weight' => 2); //local tab for password request form $items[] = array('path' => 'toboggan/password', 'title' => t('request new password'), 'callback' => 'logintoboggan_pass', 'access' => $user->uid == 0, 'type' => MENU_LOCAL_TASK, 'weight' => 3); //local tab for user validate routine $items[] = array('path' => 'user/validate', 'title' => t('validate e-mail address'), 'callback' => 'logintoboggan_validate_email', 'access' => TRUE, 'type' => MENU_CALLBACK); //callback for handling access denied redirection $items[] = array('path' => 'toboggan/denied', 'title' => t('access denied'), 'callback' => 'logintoboggan_denied', 'access' => TRUE, 'type' => MENU_CALLBACK); $items[] = array('path' => 'admin/settings/logintoboggan/settings', 'access' => user_access('access administration pages'), 'title' => t('general'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items[] = array('path' => 'admin/settings/logintoboggan/remember', 'access' => user_access('access administration pages'), 'callback' => 'logintoboggan_rememberme_admin', 'title' => t('remember me'), 'type' => MENU_LOCAL_TASK, ); } else { //determine if we need to hijack the menu path--we do it if it's an anon user trying //to get to any of the user login/register/password pages. if (variable_get('toboggan_hijack', 1) && $user->uid == 0) { $arg1 = arg(1); if (arg(0) == 'user' && (!$arg1 || $arg1 == 'register' || $arg1 == 'password')) { //since we're hijacking here, we need to perform some magic on any $_GET query strings //so they're not lost. if (is_array($_GET) && count($_GET)) { $query = array(); foreach ($_GET as $key => $value) { if ($key != 'q') { $query_pieces[] = "$key=$value"; } } if (count($query_pieces)) { $query = implode('&', $query_pieces); } else { $query = NULL; } } else { $query = NULL; } //unsetting destination here, so drupal_goto will work properly--otherwise it might create an infinite loop. unset($_REQUEST['destination']); unset($_REQUEST['edit']['destination']); //redirect to the toboggan counterpart. switch ($arg1) { case 'register': drupal_goto('toboggan/register', $query); case 'password': drupal_goto('toboggan/password', $query); default: drupal_goto('toboggan/login', $query); } } } //callback for re-sending validation e-mail $items[] = array('path' => 'toboggan/revalidate', 'title' => t('re-send validation e-mail'), 'callback' => 'logintoboggan_resend_validation', 'callback arguments' => array(arg(2)), 'access' => $user->uid == arg(2), 'type' => MENU_CALLBACK); } return $items; } /** * Rewrite of user_login() * * Changes here should be better documented... * * @param string $msg * @param string $title * @return page output */ function logintoboggan_login($msg = '', $title = NULL) { global $user, $base_url; $edit = $_POST['edit']; // If we are already logged on, go to the user page instead. if ($user->uid) { drupal_goto('user'); } if (isset($edit['name'])) { if (user_deny('user', $edit['name'])) { // blocked in user administration $error = t('The username %name has been blocked.', array('%name' => theme('placeholder', $edit['name']))); } else if ($edit['pass']) { if (!$user->uid) { $user = logintoboggan_authenticate($edit['name'], trim($edit['pass'])); } if ($user->uid) { watchdog('user', t('Session opened for %name.', array('%name' => theme('placeholder', $user->name)))); // Update the user table timestamp noting user has logged in. db_query("UPDATE {users} SET changed = '%d' WHERE uid = '%s'", time(), $user->uid); user_module_invoke('login', $edit, $user); // Redirect the user to the page he logged on from. drupal_goto(); } else { if (!$error) { $error = t('Sorry. Unrecognized username or password.') .' '. l(t('Have you forgotten your password?'), 'toboggan/password'); } watchdog('user', t('Login attempt failed for %user: %error.', array('%user' => theme('placeholder', $edit['name']), '%error' => theme('placeholder', $error)))); } } } // Display error message (if any): if ($error) { drupal_set_message($error, 'error'); } // Display login form: if ($msg) { $output .= "

$msg

"; } if (variable_get('rememberme_username_status', 0) > 0) { // pre-fill username for login PAGE if this is something we're remembering if ($_COOKIE['LTRM_sessid']) { $account = db_fetch_object(db_query('SELECT name FROM {logintoboggan_rememberme} ltrm WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid'])); $edit['name'] = $account->name; } } if (count(user_auth_help_links()) > 0) { if (variable_get('login_with_mail', 0)) { $output .= form_textfield(t('Username or e-mail address'), 'name', $edit['name'], 30, 64, t('Enter your %s registered username or password, or an ID from one of our affiliates: %a.', array('%s' => variable_get('site_name', 'local'), '%a' => implode(', ', user_auth_help_links())))); } else { $output .= form_textfield(t('Username'), 'name', $edit['name'], 30, 64, t('Enter your %s username, or an ID from one of our affiliates: %a.', array('%s' => theme('placeholder', variable_get('site_name', 'local')), '%a' => implode(', ', user_auth_help_links())))); } } else { if (variable_get('login_with_mail', 0)) { $output .= form_textfield(t('Username or e-mail address'), 'name', $edit['name'], 30, 64, t('Enter your %s username or e-mail address.', array('%s' => variable_get('site_name', 'local')))); } else { $output .= form_textfield(t('Username'), 'name', $edit['name'], 30, 64, t('Enter your %s username.', array('%s' => variable_get('site_name', 'local')))); } } $output .= form_password(t('Password'), 'pass', $pass, 30, 64, t('Enter the password that accompanies your username.')); if (variable_get('rememberme_login_status', 0) == 1 || variable_get('rememberme_username_status', 0) == 1) { // add a rememberme checkbox $output .= form_checkbox(t('remember me'), 'rememberme', 1, TRUE, NULL, NULL, FALSE); } $output .= form_submit(t('Log in')); drupal_set_title($title ? $title : t('user account')); $output = form($output, 'post', url('toboggan/login', logintoboggan_destination())); print theme('page', $output); } //custom registration form function _logintoboggan_register() { global $user, $base_url, $base_path; $edit = $_POST['edit']; // If we are already logged on, go to the user page instead. if ($user->uid) { drupal_goto('user/'. $user->uid); } if ($edit) { if (variable_get('login_with_mail', 0)) { // check that it's not an e-mail if (valid_email_address($edit['name'])) { form_set_error('name', t('You may not use an e-mail address as your username.')); } } //Check to see whether our e-mail address matches the confirm address if enabled. if (variable_get('email_reg_confirm', 0)) { if ($edit['mail'] != $edit['conf_mail']) { form_set_error('conf_mail', t('Your e-mail address and confirmed e-mail address must match.')); } } //Do some password validation if password selection is enabled. if (variable_get('reg_passwd_set', 0)) { if ($edit['pass'] != $edit['conf_pass']) { form_set_error('conf_pass', t('Your password and confirmed password must match.')); } $pass_err = logintoboggan_validate_pass($edit['pass']); if ($pass_err) { form_set_error('conf_pass', $pass_err); } } //We're hacking q here to get around the hardcoded check for user/register in profile module's validation code $q = $_GET['q']; $_GET['q'] = 'user/register'; user_module_invoke('validate', $edit, $edit, 'account'); if (!form_get_errors()) { //unset the confirmation variables because we don't care anymore and we don't want them stored if (variable_get('reg_passwd_set', 0)) { unset($edit['conf_pass']); } if (variable_get('email_reg_confirm', 0)) { unset($edit['conf_mail']); } $from = variable_get('site_mail', ini_get('sendmail_from')); //If we are allowing user selected passwords then skip the auto-generate function if (variable_get('reg_passwd_set', 0)) { $pass = $edit['pass']; } else { $pass = user_password(); } // TODO: Is this necessary? Won't session_write() replicate this? unset($edit['session']); if (array_intersect(array_keys($edit), array('uid', 'roles', 'init', 'session', 'status'))) { watchdog('security', t('Detected malicious attempt to alter protected user fields.'), WATCHDOG_WARNING); drupal_goto('toboggan/register'); } $password_immediate = (variable_get('reg_passwd_set', 0) || variable_get('toboggan_immed_login', 0)); //If password selection is enabled put the user in the 'validating' role initially until they confirm their e-mail address, //create a %validate_url variable for use in the e-mail, and set the user message for the type of account creation. if ($password_immediate) { $account = user_save('', array_merge($edit, array('pass' => $pass, 'init' => $edit['mail'], 'roles' => array(logintoboggan_validating_id()), 'status' => (variable_get('user_register', 1) == 1 ? 1 : 0)))); $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => $base_path, '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => logintoboggan_eml_validate_url($account)); $message = t('A validation e-mail has been sent to your e-mail address. You will need to follow the instructions in that message in order to gain full access to the site.'); } else { $account = user_save('', array_merge($edit, array('pass' => $pass, 'init' => $edit['mail'], 'roles' => array(_user_authenticated_id()), 'status' => (variable_get('user_register', 1) == 1 ? 1 : 0)))); $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => $base_path, '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => url('user', NULL, NULL, TRUE)); $message = t('Your password and further instructions have been sent to your e-mail address.'); } watchdog('user', t('New user: %name %email.', array('%name' => theme('placeholder', $edit['name']), '%email' => theme('placeholder', '<'. $edit['mail'] .'>'))), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit')); if ($account->status) { // Create new user account, no administrator approval required. $subject = _user_mail_text('welcome_subject', $variables); $body = _user_mail_text('welcome_body', $variables); user_mail($edit['mail'], $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from"); if (variable_get('toboggan_immed_login', 0)){ drupal_set_message($message); logintoboggan_process_login($account); } } else { // Create new user account, administrator approval required. $subject = _user_mail_text('approval_subject', $variables); $body = _user_mail_text('approval_body', $variables); $message = t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.') .'
'. $message; user_mail($edit['mail'], $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from"); user_mail(variable_get('site_mail', ini_get('sendmail_from')), $subject, t("%u has applied for an account.\n\n%uri", array('%u' => $account->name, '%uri' => url("user/$account->uid/edit", NULL, NULL, TRUE))), "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from"); } drupal_set_message($message); $_GET['q'] = $q; // Restore original q. drupal_goto(); } } // Display the registration form. $output .= variable_get('user_registration_help', ''); $affiliates = user_auth_help_links(); if (count($affiliates) > 0) { $affiliates = implode(', ', $affiliates); $output .= '

'. t('Note: if you have an account with one of our affiliates (%s), you may login now instead of registering.', array('%s' => $affiliates, '%login_uri' => url('user'))) .'

'; } $default = form_textfield(t('Username'), 'name', $edit['name'], 50, 64, t('Your full name or your preferred username; only letters, numbers and spaces are allowed.'), NULL, TRUE); $default .= form_textfield(t('E-mail address'), 'mail', $edit['mail'], 50, 64, t('A password and instructions will be sent to this e-mail address from %sitemail, please check your SPAM filters.', array('%sitemail' => variable_get('site_mail', ini_get('sendmail_from')))), NULL, TRUE); //Display a confirm e-mail address box if option is enabled. if (variable_get('email_reg_confirm', 0)) { $default .= form_textfield(t('Confirm e-mail address'), 'conf_mail', $edit['conf_mail'], 50, 64, t('Please re-type your e-mail address to confirm it is accurate.'), NULL, TRUE); } //Display a password and password confirm box if users can select their own passwords. if (variable_get('reg_passwd_set', 0)) { $default .= form_password(t('Password'), 'pass', $edit['pass'], 50, 30, t('Please choose a password for your account; it must be between 6 and 30 characters and spaces are not allowed.'), NULL, TRUE); $default .= form_password(t('Confirm Password'), 'conf_pass', $edit['conf_pass'], 50, 30, t('Please re-type your password to confirm it is accurate.'), NULL, TRUE); } // Profile.module checks the path, so we have to fake it out $oldget = $_GET['q']; $_GET['q'] = 'user/register'; $extra = _user_forms($edit, $account, $category, 'register'); $_GET['q'] = $oldget; // Only display form_group around default fields if there are other groups. if ($extra) { $output .= form_group(t('Account information'), $default); $output .= $extra; } else { $output .= $default; } $output .= form_submit(t('Create new account')); drupal_set_title(t('new user registration')); $form = form($output, 'post', url('toboggan/register', 'destination='.$_GET['destination'])); print theme('page', $form); } //support function which returns the custom login link function _logintoboggan_link() { global $user; //not logged in, then display the login/register link if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1))) && !(arg(0) == 'toboggan')) { return l(t('login/register'), 'toboggan/login'); //otherwise display the username you are logged in as. } elseif ($user->uid) { return theme_lt_loggedinblock(); } } /** * @defgroup logintoboggan_block Functions for LoginToboggan blocks. */ /** * Implementation of hook_block */ function logintoboggan_block($op = 'list', $delta = 0, $edit = array()) { global $user; switch ($op) { case 'list' : $blocks[0]['info'] = t('LoginToboggan custom login'); return $blocks; break; case 'configure': if ($delta == 0){ $output .= form_checkbox(t('Display the \'logged in\' block'), 'toboggan_display_logged_in', 1, variable_get('toboggan_display_logged_in', 1)); $output .= form_radios(t('Block type'), 'toboggan_block_type', variable_get('toboggan_block_type', 1), array(t('Link'), t('Collapsible Form'))); $output .= form_textarea(t('Set a custom message to appear at the top of your login block'), 'toboggan_block_msg', variable_get('toboggan_block_msg', ''), 200, 5); return $output; } break; case 'save' : if ($delta == 0){ variable_set('toboggan_block_type', $edit['toboggan_block_type']); variable_set('toboggan_display_logged_in', $edit['toboggan_display_logged_in']); variable_set('toboggan_block_msg', $edit['toboggan_block_msg']); } break; case 'view' : if (user_access('access content')) { switch ($delta) { case 0: // For usability's sake, avoid showing two login forms on one page. if (!$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1))) && !(arg(0) == 'toboggan')) { if (variable_get('toboggan_block_type', 1) == 1) { $edit = $_POST['edit']; $block['content'] = _logintoboggan_toggleboggan($edit); } else { $block['content'] = l(t('Login/Register'), 'user/login', array(), drupal_get_destination()); } } elseif ($user->uid && variable_get('toboggan_display_logged_in', 1)) { $block['content'] = theme('lt_loggedinblock'); } } return $block; } break; } } /** * Custom theme function for defining what gets displayed for logged in users. */ function theme_lt_loggedinblock(){ global $user; return $user->name .' | ' . l(t('log out'), 'logout'); } /** * User login block with JavaScript to expand * * this should really be themed * * @param array $edit * @return string * the rendered html */ function _logintoboggan_toggleboggan ($edit = array()){ $output = '
'; $output .= l(t('Login/Register'), 'toboggan/login', array('onclick' => "toggleboggan('toboggan-login');this.blur();return false;")); //Grab the message from settings if there is one to display at the top of the login block. if ($login_msg = variable_get('toboggan_block_msg', '')) { $output .= '
'. $login_msg .'
'; } //the block that will be toggled $output .= '
'; $nametitle = (variable_get('login_with_mail', 0)) ? t('Username or e-mail') : t('Username'); $form = form_textfield($nametitle, 'name', $edit['name'], 15, 64); $form .= form_password(t('Password'), 'pass', $pass, 15, 64); $form .= form_submit(t('Log in')); $output .= form($form, 'post', url('toboggan/login', drupal_get_destination())); if (variable_get('user_register', 1)) { $items[] = l(t('Register a new account'), 'toboggan/register', array('title' => t('Create a new user account.'))); } $items[] = l(t('Request new password'), 'toboggan/password', array('title' => t('Request new password via e-mail.'))); $output .= theme('item_list', $items); $output .= '
'; //javascript toggle function $output .= ''; return $output; } function logintoboggan_settings() { $version = str_replace(array('$Re'.'vision:', '$Da'.'te:', '$'), array('', '', ''), '

Login Toboggan version: $Revision: 1.7.2.46 $, $Date: 2006/09/08 16:08:58 $

'); $_disabled = t('disabled'); $_enabled = t('enabled'); $group = form_radios(t('Allow users to login using their e-mail address'), 'login_with_mail', variable_get('login_with_mail', 0), array($_disabled, $_enabled), t('Users will be able to enter EITHER their username OR their e-mail address to log in. NOTE: This will disallow users from registering using an e-mail address as their username.')); $output = $version . form_group(t('Login'), $group); $group = form_radios(t('Use two e-mail fields on registration form'), 'email_reg_confirm', variable_get('email_reg_confirm', 0), array($_disabled, $_enabled), t('User will have to type the same e-mail address into both fields. This helps to confirm that they\'ve typed the correct address.')); $group .= form_radios(t('Set password during registration'), 'reg_passwd_set', variable_get('reg_passwd_set', 0), array($_disabled, $_enabled), t('This will allow users to choose their initial password when registering.')); $group .= form_radios(t('Immediate login'), 'toboggan_immed_login', variable_get('toboggan_immed_login', 0), array($_disabled, $_enabled), t('When enabled, users will be assigned to the role below and logged in immediately upon registration. They will not be assigned to the "authenticated user" role until they confirm their e-mail address by following the link in their registration e-mail. It is HIGHLY recommended that you set up a "pre-authorized" role with limited permissions for this purpose.
NOTE: If you enable either of these features, you should edit the user e-mail welcome message--more help in writing the e-mail message can be found at logintoboggan help.', array('%settings' => url('admin/user/configure'), '%help' => url('admin/help/logintoboggan')))); $group .= form_select(t('Non-authenticated role'), 'toboggan_role', variable_get('toboggan_role', 1), user_roles(), t('If either "Set password during registration" or "Immediate login" is selected, users will be able to login before their e-mail address has been authenticated. Therefore, you must choose a role for new non-authenticated users. Users will be removed from this role and assigned to the "authenticated user" once they follow the link in their welcome e-mail. Add new roles.', array('%url' => url('admin/access/roles')))); $output .= form_group(t('Registration'), $group); $site403 = variable_get('site_403', 0); $disabled = ($site403 == 'toboggan/denied') ? '' : $site403; $options = array($disabled => $_disabled, 'toboggan/denied' => $_enabled); $group = form_radios(t('Present login form on access denied (403)'), 'site_403', $site403, $options, t('Anonymous users will be presented with a login form along with an access denied message.')); $group .= form_radios(t("Redirect Drupal's login paths"), 'toboggan_hijack', variable_get('toboggan_hijack', 1), array($_disabled, $_enabled), t('If enabled, anonymous users who are sent to "user/login", "user/register", or "user/password" will be redirected to the LoginToboggan counterparts. (Enabled by default)')); $output .= form_group(t('Other'), $group); return $output; } /** * Modified version of user_authenticate * - allows users to login using their e-mail address */ function logintoboggan_authenticate($name, $pass) { global $user; // Try to log in the user locally: $user = user_load(array('name' => $name, 'pass' => $pass, 'status' => 1)); // Didn't work? See if it's an e-mail address. <-- LoginToboggan if (!$user->uid && variable_get('login_with_mail', 0)) { $user = user_load(array('mail' => $name, 'pass' => $pass, 'status' => 1)); } // 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 (!$user->uid && $server && $result = user_get_authmaps("$name@$server")) { if (module_invoke(key($result), 'auth', $name, $pass, $server)) { $user = user_external_load("$name@$server"); watchdog('user', t('External load by %user using module %module.', array('%user' => theme('placeholder', $name .'@'. $server), '%module' => theme('placeholder', key($result))))); } else { $error = t('Invalid password for %s.', array('%s' => theme('placeholder', $name .'@'. $server))); } } // Try each external authentication source in series. Register user if // successful. else if (!$user->uid && $server) { foreach (module_list() as $module) { if (module_hook($module, 'auth')) { if (module_invoke($module, 'auth', $name, $pass, $server)) { if (variable_get('user_register', 1) == 1) { $account = user_load(array('name' => "$name@$server")); if (!$account->uid) { // Register this new user. $user = user_save('', array('name' => "$name@$server", 'pass' => user_password(), 'init' => "$name@$server", 'status' => 1, "authname_$module" => "$name@$server", 'roles' => array(_user_authenticated_id()))); watchdog('user', t('New external user: %user using module %module.', array('%user' => theme('placeholder', $name .'@'. $server), '%module' => theme('placeholder', $module))), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit')); break; } } } } } } return $user; } function logintoboggan_denied() { global $user; if ($user->uid == 0) { $msg = t('Access Denied. You may need to log in to access this page.'); $title = t('Access Denied / User Login'); // set up the tabs $item[] = array('path' => 'toboggan/login', 'title' => t('Access Denied')); menu_set_location($item); $return = logintoboggan_login($msg, $title); } else { drupal_set_title(t('Access Denied')); $return = theme('page', theme('lt_access_denied')); } print $return; } // Themeable function so that the access denied message can be customized function theme_lt_access_denied() { return t('You are not authorized to access this page.'); } // slight rewrite of drupal_get_destination() // with custom 403, drupal_get_destination() would return toboggan/denied // which would show 'Access Denied' after login... what good is that!? function logintoboggan_destination() { global $base_url, $base_path; $parts = parse_url($base_url); $base_path = (isset($parts['path']) ? $parts['path'] . '/' : '/'); // Drupal has reset $_GET[q], so we need a workaround. Start with home page, // then try to determine the correct destination. $destination = 'destination='. variable_get('site_frontpage', 'node'); if ($internal_path = substr(request_uri(), strlen($base_path))) { $uriarray = explode('/', $internal_path); if (!variable_get('clean_url', 0)) { $uriarray[0] = str_replace('?q=', '', $uriarray[0]); } $destination = 'destination='. implode('/', $uriarray); } return $destination; } /** * Modified version of user_validate_name * - validates user submitted passwords have a certain length and only contain letters or numbers */ function logintoboggan_validate_pass($pass) { if (!strlen($pass)) return t('You must enter a password.'); if (ereg(' ', $pass)) return t('The password cannot contain spaces.'); if (ereg("[^\x80-\xF7 [:alnum:]@_.-]", $pass)) return t('The password contains an illegal character.'); if (preg_match('/[\x{80}-\x{A0}'. // Non-printable ISO-8859-1 + NBSP '\x{AD}'. // Soft-hyphen '\x{2000}-\x{200F}'. // Various space characters '\x{2028}-\x{202F}'. // Bidirectional text overrides '\x{205F}-\x{206F}'. // Various text hinting characters '\x{FEFF}'. // Byte order mark '\x{FF01}-\x{FF60}'. // Full-width latin '\x{FFF9}-\x{FFFD}]/u', // Replacement characters $pass)) { return t('The password contains an illegal character.'); } if (strlen($pass) > 30) return t('The password is too long: it must be less than 30 characters.'); if (strlen($pass) < 6) return t('The password is too short: it must be greater than 6 characters.'); } /** * Modified version of _user_authenticated_id * - gets the role id for the "validating" user role. */ function logintoboggan_validating_id() { return variable_get('toboggan_role', 1); } /** * Menu callback; process validate the e-mail address as a one time URL, * and redirects to the user page on success. */ function logintoboggan_validate_email($uid, $timestamp, $hashed_pass) { global $user; $current = time(); // Some redundant checks for extra security if ($timestamp < $current && is_numeric($uid) && $account = user_load(array('uid' => $uid)) ) { // No time out for first time login. if ($account->uid && !empty($account) && $timestamp < $current && $hashed_pass == logintoboggan_eml_rehash($account->pass, $timestamp, $account->mail)) { watchdog('user', t('E-mail validation URL used for %name with timestamp %timestamp.', array('%name' => theme('placeholder', $account->name), '%timestamp' => check_plain($timestamp)))); // Update the user table noting user has logged in. // And this also makes this hashed password a one-time-only login. db_query("UPDATE {users} SET changed = '%d' WHERE uid = '%s'", time(), $user->uid); // Move the user into the auth role, unless they haven't been approved yet. if ($account->status) { db_query("UPDATE {users_roles} SET rid ='%d' WHERE uid = %d AND rid = %d", _user_authenticated_id(), $account->uid, logintoboggan_validating_id()); } // Now we can set the new user. $user = $account; // If neccessary, alert the user that they still don't have full perms because they are awaiting approval. $awaiting_approval = $account->status ? '' : t(' You will receive full site permissions after your account has been approved by a site administrator.'); // And proceed with normal login, going to user page. drupal_set_message(t('You have successfully validated your e-mail address.') . $awaiting_approval); logintoboggan_process_login($user); } } // Deny access, no more clues. // Everything will be in the watchdog's URL for the administrator to check. drupal_access_denied(); } /** * Actually log the user on * * @param object $account */ function logintoboggan_process_login($account){ global $user; $user = $account; watchdog('user', t('Session opened for %name.', array('%name' => theme('placeholder', $user->name)))); // Update the user table timestamp noting user has logged in. db_query("UPDATE {users} SET changed = '%d' WHERE uid = '%s'", time(), $user->uid); // user has new permissions, so we clear their menu cache cache_clear_all('menu:'. $user->uid, TRUE); $edit = array(); user_module_invoke('login', $edit, $user); drupal_goto('user/'. $user->uid); } function logintoboggan_eml_validate_url($account){ $timestamp = time(); return url("user/validate/$account->uid/$timestamp/".logintoboggan_eml_rehash($account->pass, $timestamp, $account->mail), NULL, NULL, TRUE); } function logintoboggan_eml_rehash($password, $timestamp, $mail){ return md5($timestamp . $password . $mail); } /** * Simple callback function to set the proper page title before forwarding to * request password screen. */ function logintoboggan_pass() { drupal_set_title(t('request new password')); user_pass(); } /** * Implementation of hook_user(). */ function logintoboggan_user($op, &$edit, &$user_edit, $category = NULL) { global $user; if ($op == 'form' && $category == 'account' && $user->uid == arg(1)) { // User is editing their own account settings if ((variable_get('reg_passwd_set', 0) || variable_get('toboggan_immed_login', 0)) && array_key_exists(logintoboggan_validating_id(), $user_edit->roles)) { // User has not yet validated their account; display link to re-send e-mail. return array(array('title' => t('Account validation'), 'data' => l(t('re-send validation e-mail'), 'toboggan/revalidate/'. $user_edit->uid), 'weight' => -10)); } }elseif ($op == 'login' && variable_get('login_successful', 0)) { drupal_set_message(t('Login successful.')); } if ($op == 'login') { // compressed all the logic for what and if to set into it's own function _logintoboggan_rememberme_login($edit['rememberme']); } elseif ($op == 'logout') { if ($_COOKIE['LTRM_sessid']) { // set uid = 0 so login won't happen again, but keep the username to prefill db_query('UPDATE {logintoboggan_rememberme} SET uid = 0 WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid']); } } } /** * Re-sends validation e-mail to user specified by $uid. */ function logintoboggan_resend_validation($uid) { global $base_url, $base_path; $account = user_load(array('uid' => $uid)); // Variables to replace in e-mail $pass = t('If required, you may reset your password from: %url', array('%url' => url('user/password', NULL, NULL, TRUE))); $variables = array('%username' => $account->name, '%site' => variable_get('site_name', 'drupal'), '%password' => $pass, '%uri' => $base_url, '%uri_brief' => $base_path, '%mailto' => $account->mail, '%date' => format_date(time()), '%login_uri' => url('user', NULL, NULL, TRUE), '%edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE), '%login_url' => logintoboggan_eml_validate_url($account)); // Prepare and send e-mail. $from = variable_get('site_mail', ini_get('sendmail_from')); $subject = _user_mail_text('welcome_subject', $variables); $body = _user_mail_text('welcome_body', $variables); user_mail($account->mail, $subject, $body, "From: $from\nReply-to: $from\nX-Mailer: Drupal\nReturn-path: $from\nErrors-to: $from"); // Notify user that e-mail was sent and return to user edit form. drupal_set_message(t('A validation e-mail has been sent to your e-mail address. You will need to follow the instructions in that message in order to gain full access to the site.')); drupal_goto('user/'. $account->uid .'/edit'); } function logintoboggan_init() { global $user; // rememberme: first see if a login should be activated if ($user->uid == 0 && variable_get('rememberme_login_status', 0) > 0 && $_COOKIE['LTRM_sessid']) { // got an anonymous user, a rememberme cookie, and we're go for remembering logins... $rememberme = db_fetch_object(db_query('SELECT uid, timestamp FROM {logintoboggan_rememberme} WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid'])); if ($rememberme->uid > 0 && $rememberme->timestamp > (time() - variable_get('rememberme_username_life', 0))) { // uid is still present (didn't logout) and timestamp is within login_life // so that means we're a go to restart the session $user = user_load(array('uid' => $rememberme->uid)); // refresh timestamp db_query('UPDATE {logintoboggan_rememberme} SET timestamp = %d WHERE sid = \'%s\' AND uid = %d', time(), $_COOKIE['LTRM_sessid'], $user->uid); } } elseif ($user->uid > 0 && variable_get('rememberme_login_status', 0) > 0 && $_COOKIE['LTRM_sessid']) { // if the system is active and there's a login and cookie, refresh cookie _logintoboggan_rememberme_setcookie($_COOKIE['LTRM_sessid']); db_query('UPDATE {logintoboggan_rememberme} SET timestamp = %d WHERE sid = \'%s\' AND uid = %d', time(), $_COOKIE['LTRM_sessid'], $user->uid); } // If it's not an anonymous user, and the user has the pre-auth role, and the pre-auth role // isn't also the auth role, then unset the auth role for this user--they haven't validated yet. unset($user->roles[2]); if (variable_get('rememberme_username_status', 0) > 0) { // must prevent caching of /user page in order to remember usernames global $base_url; db_query('DELETE FROM {cache} WHERE cid = \'%s\'', $base_url.'/user'); } } function logintoboggan_rememberme_admin() { if($_POST['submit'] == 'submit'){ variable_set('rememberme_login_status', $_POST['edit']['login_status']); variable_set('rememberme_login_life', $_POST['edit']['login_life'] * 24 * 3600); variable_set('rememberme_username_status', $_POST['edit']['username_status']); variable_set('rememberme_username_life', $_POST['edit']['username_life'] * 24 * 3600); drupal_set_message(t('configuration options saved')); } drupal_set_title(t('logintoboggan: %name', array('%name' => t('remember me')))); $time = (variable_get('rememberme_login_status', 0) > 0) ? variable_get('rememberme_login_life', 0) : ini_get('session.gc_maxlifetime'); $output = '

'.t('Use the %name feature to extend this for users on your site, and/or to pre-fill their login name the next time they come to the site. Your current user login sessions will last %time DAYS.', array('%time' => round($time / 24 / 3600, 1), '%name' => t('remember me'))).'

'; if (variable_get('rememberme_login_status', 0) == 1) { $output .= '

'.t('Sessions for users who do not use the %name checkbox will last %time DAYS', array('%name' => t('remember me'), '%time' => round(ini_get('session.gc_maxlifetime') / 24 / 3600, 1))).'

'; } $login_group .= form_radios( t('status for logins', array('%name' => t('remember me'))), 'login_status', variable_get('rememberme_login_status', 1), array('Off', 'On w/Checkbox', 'On Always'), t('Choose whether or not to enable the %name feature to log users in automatically, either through a checkbox on user login forms, or else as an always-on feature.', array('%name' => t('remember me'))), FALSE, NULL); $login_group .= form_textfield( t('session lifetime'), 'login_life', round(variable_get('rememberme_login_life', $time) / 24 / 3600, 1), 10, 10, t('Time in DAYS that you wish the auto-login feature to remain active. In other words: how long can a user be away from the site, and still find themselves logged when they return using the same computer.'), NULL, FALSE); $output .= form_group( t('logins'), $login_group, t('These settings control the %name function for automatically logging users in. Note that this presents a security vulnerability in that the browser the user used to access your site will be able to auto-log-in for however long you choose here!', array('%name' => t('remember me')))); $username_group .= form_radios( t('status for usernames', array('%name' => t('remember me'))), 'username_status', variable_get('rememberme_username_status', 2), array('Off', 'On w/Checkbox', 'On Always'), t('Choose whether or not to enable the %name feature to remember usernames automatically, either with a checkbox on user login forms, or else as an always-on feature.', array('%name' => t('remember me'))), FALSE, NULL); $username_group .= form_textfield( t('username remember lifetime'), 'username_life', round(variable_get('rememberme_username_life', $time * 2) / 24 / 3600, 1), 10, 10, t('Time in DAYS that you wish to remember usernames. If you are also using the login setting, this should probably be longer.'), NULL, FALSE); $output .= form_group( t('username'), $username_group, t('These settings control the %name function for remebering usernames and pre-filling the login form with that value.', array('%name' => t('remember me'))) ); $output .= form_submit('submit', 'submit', NULL); //$output .= drupal_get_form('logintoboggan_rememberme', $output); print theme('page',form($output)); } /** * Shorthand for the logic stuff for logins. * * This compares the status of the settings with the presence of the "remember * me" checkbox on the login form and tells us whether or not to remember what. */ function _logintoboggan_rememberme_login($checkbox = FALSE) { global $user; if ($_COOKIE['LTRM_sessid']) { // out with the old, just in case db_query('DELETE FROM {logintoboggan_rememberme} WHERE sid = \'%s\'', $_COOKIE['LTRM_sessid']); } if (variable_get('rememberme_login_status', 0) > 0 || variable_get('rememberme_username_status', 0) > 0) { // system is active, continue if ((variable_get('rememberme_login_status', 0) == 1 && $checkbox) || variable_get('rememberme_login_status', 0) == 2) { // we're supposed to remember the uid (auto re-login) $uid = $user->uid; } else { // store 0 for the uid, but we may stlll remember the login name for pre-fill $uid = 0; } if ((variable_get('rememberme_username_status', 0) == 1 && $checkbox) || variable_get('rememberme_username_status', 0) == 2) { // we're supposed to remember the username (prefill login form) // if we're allowing email addys, store that instead for prefill $name = variable_get('login_with_mail', 0) ? $user->mail : $user->name; } else { // store nothing for the username $name = ''; } $token = md5(time()/$user->created); // should be pretty safe _logintoboggan_rememberme_setcookie($token); db_query('INSERT INTO {logintoboggan_rememberme} (uid, name, sid, timestamp) VALUES(%d, \'%s\', \'%s\', %d)', $uid, $name, $token, time()); watchdog('user', t('Session remembered for %name.', array('%name' => theme('placeholder', $user->name)))); } } /** * Set our Logintoboggan Rememberme Session cookie (LTRM_sessid) */ function _logintoboggan_rememberme_setcookie($token) { global $base_url; $url = str_replace('http://', '', $base_url); $url = explode('/', $url); $domain = array_shift($url); $path = '/'.implode('/', $url); // get the longer lifetime for cookie life; this should generally be username, but just in case... $lifetime = ((variable_get('rememberme_username_life', 0) > (variable_get('rememberme_login_life', 0))) ? variable_get('rememberme_username_life', 0) : variable_get('rememberme_login_life', 0)); setcookie('LTRM_sessid', $token, time() + $lifetime, $path, $domain); } function logintoboggan_cron() { db_query('DELETE FROM {logintoboggan_rememberme} WHERE timestamp < %d', time() - variable_get('rememberme_username_life', 1*365*24*60*60) ); }