'admin/settings/securesite', 'title' => t('Secure Site'), 'callback' => 'drupal_get_form', 'callback arguments' => 'securesite_admin_settings', 'description' => t('Enables HTTP Auth security or an HTML form to restrict site access.'), 'access' => user_access('administer site configuration'), ); } return $items; } /** * Implementation of hook_settings() */ function securesite_admin_settings() { global $base_url; // Authentication settings $form['authentication'] = array( '#type' => 'fieldset', '#title' => t('Authentication'), ); $form['authentication']['securesite_enabled'] = array( '#type' => 'radios', '#title' => t('Forced authentication'), '#default_value' => variable_get('securesite_enabled', SECURESITE_DISABLED), '#options' => array( SECURESITE_DISABLED => t('Disabled'), SECURESITE_AUTH => t('Enabled with web browser HTTP Auth security'), SECURESITE_AUTH_ALT => t('Enabled with web browser HTTP Auth security with browser logout work-around'), SECURESITE_FORM => t('Enabled with HTML login form'), ), '#description' => t('HTTP Auth requires extra configuration if PHP is not configured as an Apache module. Since browsers manage the HTTP Auth variables differently, the work-around will append a random number to the end of the realm to force the user\'s browser to clear their username and password. Without the work-around users will not be properly logged out in some browsers.', array('!extra' => url('http://drupal.org/node/28408'))), '#suffix' => t('Note: Users must have the "access site" permission in order to access the site once the above setting is enabled.', array('!access' => url('admin/user/access', NULL, 'module-securesite'))), ); $form['authentication']['securesite_guest_name'] = array( '#type' => 'textfield', '#title' => t('Guest user'), '#default_value' => variable_get('securesite_guest_name', ''), '#length' => 30, '#maxlength' => 40, '#description' => t('Guests can access the secured site without an account. Leave empty to disable guest access'), ); $form['authentication']['securesite_guest_pass'] = array( '#type' => 'textfield', '#title' => t('Guest password'), '#default_value' => variable_get('securesite_guest_pass', ''), '#length' => 30, '#maxlength' => 40, '#description' => t('Leave empty to disable guest access'), ); $form['authentication']['securesite_realm'] = array( '#type' => 'textfield', '#title' => t('Authentication realm'), '#default_value' => variable_get('securesite_realm', preg_replace('`^https?://`i', '', $base_url)), '#length' => 30, '#maxlength' => 40, '#description' => t('Name to identify login area in HTTP Auth dialog'), ); // HTML login form settings $form['login_form'] = array( '#type' => 'fieldset', '#title' => t('Customize HTML forms'), ); $form['login_form']['securesite_login_form'] = array( '#type' => 'textarea', '#title' => t('Custom message for login form'), '#default_value' => variable_get('securesite_login_form', t('

Enter your %site username and password.

', array('%site' => variable_get('site_name', 'Drupal')))), '#length' => 60, '#height' => 3, ); $form['login_form']['securesite_request_form'] = array( '#type' => 'textarea', '#title' => t('Custom message for password reset form'), '#default_value' => variable_get('securesite_request_form', t('

Enter your username or e-mail address.

')), '#length' => 60, '#height' => 3, '#description' => t('Leave empty to disable Secure Site\'s password reset form.'), ); // Bypass login filter pages settings $form['filter_pages'] = array( '#type' => 'fieldset', '#title' => t('Bypass login'), ); $form['filter_pages']['securesite_filter_pages_type'] = array( '#type' => 'radios', '#title' => t('Force authentication'), '#default_value' => variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST), '#options' => array( SECURESITE_WHITELIST => t('On every page except the listed pages. Use this setting to restrict access of your entire site.'), SECURESITE_BLACKLIST => t('Only on the listed pages. Use this setting only if you want to restrict access to certain pages, not your entire site.'), ), ); $form['filter_pages']['securesite_filter_pages'] = array( '#type' => 'textarea', '#title' => t('Pages'), '#default_value' => variable_get('securesite_filter_pages', ''), '#length' => 60, '#height' => 3, '#description' => t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are 'blog' for the blog page and 'blog/*' for every personal blog. Select \"On every page except the listed pages\" and leave the list above empty to disable bypasses. The cron page is always accessible."), ); return system_settings_form($form); } /** * Implementation of hook_init() * * Implements the securesite authentication */ function securesite_init() { global $user, $base_path; $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED); // Step #1: Process conditions that skip Securesite authentication if (!$securesite_enabled // Securesite isn't enabled || (request_uri() == $base_path .'cron.php') // Accessing cron page || ($user->uid == 1) // Site administrator || ($user->uid && user_access('access site')) // User is logged in and has privileges to access the site || (isset($_SESSION['securesite_guest']) ? $_SESSION['securesite_guest'] : '') // Guest user that's already logged in || (!$user->uid && securesite_filter_check()) // User not logged in, but accessing a page in the bypass list ) { return; } // Step #2: Process password resets if (strpos(request_uri(), $base_path .'user/reset/') === 0) { $args = explode('/', $_GET['q']); // The password reset function doesn't work well if it doesn't have all the required parameters or if the UID parameter isn't valid if (count($args) >= 5 && user_load(array('uid' => $args[2], 'status' => 1))) { user_pass_reset($args[2], $args[3], $args[4], 'login'); } else { drupal_set_message(t('You have tried to use an invalid one-time login link. Please request a new one using the form below.'), 'error'); } } // Step #3: Set up variables $guest_name = variable_get('securesite_guest_name', ''); $guest_pass = variable_get('securesite_guest_pass', ''); if ($securesite_enabled == SECURESITE_FORM && !empty($_POST)) { $edit = $_POST['edit']; } elseif ($securesite_enabled == SECURESITE_AUTH || $securesite_enabled == SECURESITE_AUTH_ALT) { if (isset($_SERVER['PHP_AUTH_USER'])) { $edit['name'] = $_SERVER['PHP_AUTH_USER']; } if (isset($_SERVER['PHP_AUTH_PW'])) { $edit['pass'] = $_SERVER['PHP_AUTH_PW']; } } // Step #4: If needed, ask user for credentials if (empty($edit) && ($user->uid == 0)) { securesite_user_auth(); } // Step #5: Check if user is a guest and log them in if they are if (!empty($guest_name) && !empty($guest_pass) && $guest_name == $edit['name'] && $guest_pass == $edit['pass']) { // Mark this session to prevent re-login (note: guests can't logout) $_SESSION['securesite_guest'] = TRUE; if (arg(0) != 'logout') { // only redirect if on logout page return; } securesite_goto(); } unset($_SESSION['securesite_guest']); // If not a guest make sure to unset guest session // Step #6: Check user's credentials /** * The LDAP auth module can't use the regular external user login system, so we have to call its * login function separately */ if (function_exists('_ldapauth_user_authenticate')) { $account = _ldapauth_user_authenticate($edit['name'], $edit['pass']); } else { $account = user_authenticate($edit['name'], $edit['pass']); } // Step #7: Process login attempt if ($account->uid && user_access('access site', $account)) { // Login successful $user = $account; watchdog('user', t('Session opened for %name.', array('%name' => $user->name))); db_query("UPDATE {users} SET login = '%d' WHERE uid = '%s'", time(), $user->uid); user_module_invoke('login', $edit, $user); if (arg(0) != 'logout') { // only redirect if on logout page return; } securesite_goto(); } else { // Login failed watchdog('user', t('Login attempt failed for %user.', array('%user' => $edit['name']))); securesite_user_auth(); } } /** * Implementation of hook_user() */ function securesite_user($op, &$edit, &$user) { if ($op == 'logout') { module_invoke_all('exit', request_uri()); unset($GLOBALS['user']); $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED); if ($securesite_enabled == SECURESITE_AUTH || $securesite_enabled == SECURESITE_AUTH_ALT) { securesite_user_auth(); } else { // redirect first to browser prevent caching problems securesite_goto(); } } } /** * Securesite redirect */ function securesite_goto() { global $base_url; $url = (arg(0) == 'logout' ? $base_url : request_uri()); if (ini_get('session.use_trans_sid') && session_id() && !strstr($url, session_id())) { $url .= (strstr($url, '?') && !strstr($url, $sid) ? '&' : '?') . session_name() .'='. session_id(); } header('Location: '. $url); module_invoke_all('exit', request_uri()); exit; } /** * Display authentication dialog and send password requests */ function securesite_user_auth() { global $base_url; // Clear the cache if it's enabled. Work-around for http://drupal.org/node/217466 if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { cache_clear_all(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); } include_once('securesite.inc'); $edit = isset($_POST['edit']) ? $_POST['edit'] : ''; $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED); $content = ''; // Step #1: Check if the user attempted to submit the login form. If so, getting here means they didn't enter their // info correctly if ((isset($_POST['securesite_login_form']) ? $_POST['securesite_login_form'] : '')) { drupal_set_message(t('Unrecognized username and/or password.'), 'error'); } // Step #2: Check if the user attempted to submit the password request form. If so, check if we have information for // the name/mail they entered and send it if we do if ((isset($_POST['securesite_request_form']) ? $_POST['securesite_request_form'] : '')) { if (($edit['name'] || $edit['mail'])) { // Only look-up information if input was given $load['status'] = 1; if (!empty($edit['name'])) { $load['name'] = $edit['name']; } if (!empty($edit['mail'])) { $load['mail'] = $edit['mail']; } // Check account information $account = user_load($load); if ($account->uid) { // Valid account, e-mail the user a new password // Generate a new password for this user $account = user_save($account, array('pass' => user_password())); // Mail new password $variables = array( '!username' => $account->name, '!site' => variable_get('site_name', 'Drupal'), '!login_url' => user_pass_reset_url($account), '!uri' => $base_url, '!uri_brief' => preg_replace('`^https?://`i', '', $base_url), '!mailto' => $account->mail, '!date' => format_date(time()), '!login_uri' => url('user', NULL, NULL, TRUE), '!edit_uri' => url('user/'. $account->uid .'/edit', NULL, NULL, TRUE) ); $subject = _user_mail_text('pass_subject', $variables); $body = _user_mail_text('pass_body', $variables); $mail_success = drupal_mail('securesite-password', $account->mail, $subject, $body); if ($mail_success) { watchdog('user', t('Password mailed to %name at %email.', array('%name' => $account->name, '%email' => $account->mail))); // We exit here because presumably the user can't do anything more before visiting the password reset URL _securesite_dialog_page('

'. t('Your password and further instructions have been sent to your e-mail address.') ."

\n"); module_invoke_all('exit', request_uri()); exit; } else { // Note: At this point, the user's password has already been reset watchdog('user', t('Error mailing password to %name at %email.', array('%name' => $account->name, '%email' => $account->mail)), WATCHDOG_ERROR); drupal_set_message(t('Unable to send mail. Please contact the site admin.', 'error')); } } else { // Name or mail not valid or account disabled drupal_set_message(t('Unrecognized username or e-mail address.'), 'error'); } } else { // Nothing entered drupal_set_message(t('Unrecognized username or e-mail address.'), 'error'); } } // Get content for dialog if ($securesite_enabled == SECURESITE_FORM) { $content .= _securesite_login_form(); } $content .= _securesite_request_form(); // Step #3: If using HTTP Auth, send the appropriate headers, but only if the user isn't logged in and they haven't // just submitted the password reset or login forms if (($securesite_enabled == SECURESITE_AUTH || $securesite_enabled == SECURESITE_AUTH_ALT) && empty($_POST['securesite_request_form']) && empty($_POST['securesite_login_form'])) { $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal')); if ($securesite_enabled == SECURESITE_AUTH_ALT) { /********* * If not on the home page of the site, Opera will not show the auth dialog the first time after logout. * It will show the page displayed before logging out. Reloading will cause the dialog to display * Safari doesn't seem show the login/password request form when cancelling the auth dialog *********/ $browsers = array('msie', 'opera', 'safari'); $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']); foreach ($browsers as $browser) { if (strpos($user_agent, $browser) !== FALSE) { $realm .= ' - '. mt_rand(10, 999); break; } } } header('WWW-Authenticate: Basic realm="'. $realm .'"'); header('HTTP/1.0 401 Unauthorized'); } // Step #4: Show the login form or password request form _securesite_dialog_page($content); module_invoke_all('exit', request_uri()); exit; } /** * Check if pages should bypass securesite */ function securesite_filter_check() { // If cache is enabled we need to load the path system if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH); } // Fetch paths to ignore $pages = variable_get('securesite_filter_pages', ''); // Check if the current path matches the list defined by the admin $path = drupal_get_path_alias($_GET['q']); $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($pages, '/')) .')$/'; $page_match = preg_match($regexp, $path); // Check normal paths if the alias lookup fails to match if (!$page_match) { $path = drupal_get_normal_path($_GET['q']); $page_match = preg_match($regexp, $path); } // Whitelist or blacklist? if (variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST) == !$page_match) { return TRUE; } return FALSE; }