You are here

securesite.module in Secure Site 6

Secure Site contrib module

Secure Site allows the use of HTTP Auth to secure Drupal sites

File

securesite.module
View source
<?php

/**
 * @file
 * Secure Site contrib module
 *
 * Secure Site allows the use of HTTP Auth to secure Drupal sites
 */

/**
 * Secure Site status: Disabled
 */
define('SECURESITE_DISABLED', 0);

/**
 * Secure Site status: Enabled with Web browser HTTP Auth security
 */
define('SECURESITE_AUTH', 1);

/**
 * Secure Site status: Enabled with Web browser HTTP Auth security using the
 * alterative method (deprecated)
 */
define('SECURESITE_AUTH_ALT', 2);

/**
 * Secure Site status: Enabled with HTML login form
 */
define('SECURESITE_FORM', 3);

/**
 * Secure Site bypass: Only the listed pages don't require authentication
 *
 * @see _securesite_filter_check()
 */
define('SECURESITE_WHITELIST', 0);

/**
 * Secure Site bypass: Only the listed pages do require authentication
 *
 * @see _securesite_filter_check()
 */
define('SECURESITE_BLACKLIST', 1);

/**
 * Implementation of hook_perm()
 */
function securesite_perm() {
  return array(
    'access secured pages',
  );
}

/**
 * Implementation of hook_menu()
 */
function securesite_menu() {
  $items['admin/settings/securesite'] = array(
    'title' => 'Secure Site',
    'description' => 'Enables HTTP Auth security or an HTML form to restrict site access.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'securesite_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'securesite.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_boot()
 *
 * This is where Secure Site does most of its processing
 *
 * Note: When a user is logged in, but doesn't have the 'access secured pages'
 * permission, they get a normal Access Denied message
 */
function securesite_boot() {
  global $user, $base_path;
  $securesite_enabled = variable_get('securesite_enabled', SECURESITE_DISABLED);
  $guest_name = variable_get('securesite_guest_name', '');
  $guest_pass = variable_get('securesite_guest_pass', '');

  // Step #1: Process conditions that bypass Secure Site authentication
  if (!$securesite_enabled || $securesite_enabled == SECURESITE_AUTH_ALT || php_sapi_name() == 'cli' || request_uri() == $base_path . 'cron.php' || $user->uid == 1 || !empty($guest_name) && (isset($_SESSION['securesite_guest']) ? $_SESSION['securesite_guest'] : '')) {
    return;
  }

  // Do a full bootstrap since Secure Site uses many functions throughout Core,
  // such as path.inc and user.module functions, t(), and theme()
  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

  // This has to be after a full bootstrap
  if (_securesite_filter_check(isset($_GET['q']) ? $_GET['q'] : '') || $user->uid && user_access('access secured pages')) {

    // User is logged in and has privileges to access secured pages
    return;
  }

  // Prevent a login/logout loop by redirecting off the logout page
  if (strpos(request_uri(), $base_path . 'logout') === 0) {
    drupal_goto('<front>');
  }

  // 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,
    ))) {

      // Sanity-checking complete, now let user_pass_reset() and the menu
      // system handle it
      return;
    }
    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
  if ($securesite_enabled == SECURESITE_FORM && !empty($_POST['edit'])) {
    $edit = $_POST['edit'];
  }
  elseif ($securesite_enabled == SECURESITE_AUTH) {

    // PHP in CGI mode work-arounds.  Sometimes, "REDIRECT_" prefixes $_SERVER
    // variables.  See http://www.php.net/reserved.variables
    if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && empty($_SERVER['HTTP_AUTHORIZATION'])) {
      $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
    }

    // Auth variables set via Rewrite rules need to be decoded
    // See http://www.php.net/manual/en/features.http-auth.php#76708
    if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
      list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
    }

    // Process username and password normally.  The correct $_SERVER variables
    // are now set if PHP is run in CGI mode
    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['name']) || empty($edit['pass'])) && $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;
    $_SESSION['securesite_login'] = TRUE;

    // Redirect to prevent some caching problems
    drupal_goto($_GET['q']);
  }
  unset($_SESSION['securesite_guest']);

  // If not a guest, make sure to clear the 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 directly
  if (function_exists('_ldapauth_user_authenticate')) {
    $account = _ldapauth_user_authenticate($edit['name'], $edit['pass']);
  }
  else {
    $account = user_authenticate(array(
      'name' => $edit['name'],
      'pass' => $edit['pass'],
    ));
  }

  // Step #7: Process login attempt
  if ((isset($account->uid) ? $account->uid : FALSE) && user_access('access secured pages', $account)) {

    // Mark the session so Secure Site will be triggered on logout
    $_SESSION['securesite_login'] = TRUE;

    // Redirect to prevent some caching problems
    drupal_goto($_GET['q']);
  }
  else {

    // Login failed
    if (!empty($edit['name'])) {
      watchdog('user', 'Login attempt failed for %user.', array(
        '%user' => $edit['name'],
      ));
    }
    else {
      watchdog('user', 'Login attempt failed for <em>anonymous</em> user.', array(
        '%user' => $edit['name'],
      ));
    }
    _securesite_user_auth();
  }
}

/**
 * Implementation of hook_user()
 *
 * When users logout, show the HTTP Auth dialog to make sure the HTTP Auth
 * credentials are cleared
 *
 * @see _securesite_user_auth()
 */
function securesite_user($op, &$edit, &$user) {
  if ($op == 'logout' && variable_get('securesite_enabled', SECURESITE_DISABLED) == SECURESITE_AUTH && (isset($_SESSION['securesite_login']) ? $_SESSION['securesite_login'] : FALSE)) {

    // Load the anonymous user
    $user = drupal_anonymous_user();

    // Clear stored credentials
    _securesite_user_auth();
  }
}

/**
 * Display authentication dialog and send password reset mails
 */
function _securesite_user_auth() {
  global $base_url;
  module_load_include('inc', 'securesite');
  $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'])) {
    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']) && isset($_POST['edit'])) {
    _securesite_password_reset($_POST['edit']);
  }

  // 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 && empty($_POST['securesite_request_form']) && empty($_POST['securesite_login_form'])) {
    $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));

    // 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 no matter what
    $browsers = array(
      'msie',
      'opera',
      'safari',
    );
    $user_agent = isset($_SERVER['HTTP_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 and/or password request form if user cancels
  // HTTP Auth dialog
  _securesite_dialog_page($content);
  module_invoke_all('exit', request_uri());
  session_write_close();
  exit;
}

/**
 * Check if pages should bypass Secure Site
 *
 * @param $path
 *   String containing the path to be filtered
 *
 * @return
 *   TRUE if Secure Site should be bypassed
 */
function _securesite_filter_check($path) {

  // Don't allow empty paths
  if (empty($path)) {
    return FALSE;
  }

  // Fetch paths to ignore
  $pages = variable_get('securesite_filter_pages', '');

  // Check if the current path matches the list defined by the admin
  $alias = drupal_get_path_alias($path);
  $regexp = '/^(' . preg_replace(array(
    '/(\\r\\n?|\\n)/',
    '/\\\\\\*/',
    '/(^|\\|)\\\\<front\\\\>($|\\|)/',
  ), array(
    '|',
    '.*',
    '\\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\\2',
  ), preg_quote($pages, '/')) . ')$/';
  $page_match = preg_match($regexp, $alias);

  // Check normal paths if the alias lookup fails to match
  if (!$page_match) {
    $alias = drupal_get_normal_path($path);
    $page_match = preg_match($regexp, $alias);
  }

  // Whitelist or blacklist?
  if (variable_get('securesite_filter_pages_type', SECURESITE_WHITELIST) == !$page_match) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implementation of hook_mail()
 */
function securesite_mail($key, &$message, $params) {

  // Ignoring $key for now, since there's only one type of mail sent by Secure Site
  $message['subject'] = $params['subject'];
  $message['body'] = $params['body'];
}

Functions

Namesort descending Description
securesite_boot Implementation of hook_boot()
securesite_mail Implementation of hook_mail()
securesite_menu Implementation of hook_menu()
securesite_perm Implementation of hook_perm()
securesite_user Implementation of hook_user()
_securesite_filter_check Check if pages should bypass Secure Site
_securesite_user_auth Display authentication dialog and send password reset mails

Constants

Namesort descending Description
SECURESITE_AUTH Secure Site status: Enabled with Web browser HTTP Auth security
SECURESITE_AUTH_ALT Secure Site status: Enabled with Web browser HTTP Auth security using the alterative method (deprecated)
SECURESITE_BLACKLIST Secure Site bypass: Only the listed pages do require authentication
SECURESITE_DISABLED Secure Site status: Disabled
SECURESITE_FORM Secure Site status: Enabled with HTML login form
SECURESITE_WHITELIST Secure Site bypass: Only the listed pages don't require authentication