You are here

securesite.inc in Secure Site 7.2

Same filename and directory in other branches
  1. 5 securesite.inc
  2. 6.2 securesite.inc
  3. 6 securesite.inc

Secure Site log-in functions.

File

securesite.inc
View source
<?php

/**
 * @file
 * Secure Site log-in functions.
 */

/**
 * Boot with selected authentication mechanism.
 */
function _securesite_boot($type) {
  global $user;
  switch ($type) {
    case SECURESITE_DIGEST:
      $edit = _securesite_parse_directives($_SERVER['PHP_AUTH_DIGEST']);
      $edit['name'] = $edit['username'];
      $edit['pass'] = NULL;
      $function = '_securesite_digest_auth';
      break;
    case SECURESITE_BASIC:
      $edit['name'] = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '';
      $edit['pass'] = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
      $function = '_securesite_plain_auth';
      break;
    case SECURESITE_FORM:

      // @ignore security_17:4
      if (!empty($_POST['openid_identifier'])) {
        openid_begin($_POST['openid_identifier'], $_POST['openid.return_to']);
      }
      $edit = array(
        'name' => $_POST['name'],
        'pass' => $_POST['pass'],
      );
      $function = '_securesite_plain_auth';
      break;
  }

  // Are credentials different from current user?
  if ((!isset($user->name) || $edit['name'] !== $user->name) && (!isset($_SESSION['securesite_guest']) || $edit['name'] !== $_SESSION['securesite_guest'])) {
    $function($edit);
  }
}

/**
 * Parse digest header into an array of directives.
 */
function _securesite_parse_directives($field_value) {
  $directives = array();
  foreach (explode(',', trim($field_value)) as $directive) {
    list($directive, $value) = explode('=', trim($directive), 2);
    $directives[$directive] = trim($value, '"');
  }
  return $directives;
}

/**
 * Menu callback; handle restricted pages.
 */
function _securesite_403() {
  global $user;
  if (in_array('user/logout', drupal_get_destination())) {
    module_load_include('pages.inc', 'user');
    user_logout();
  }
  if (empty($user->uid) && !isset($_SESSION['securesite_guest'])) {
    _securesite_dialog(securesite_type_get());
  }
  else {
    $path = drupal_get_normal_path(variable_get('securesite_403', ''));
    menu_set_active_item($path);
    return menu_execute_active_handler($path);
  }
}

/**
 * Perform digest authentication.
 */
function _securesite_digest_auth($edit) {
  global $user;
  $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));
  $header = _securesite_digest_validate($status, array(
    'data' => $_SERVER['PHP_AUTH_DIGEST'],
    'method' => $_SERVER['REQUEST_METHOD'],
    'uri' => request_uri(),
    'realm' => $realm,
  ));
  $users = user_load_multiple(array(), array(
    'name' => $edit['name'],
    'status' => 1,
  ));
  $account = reset($users);
  if (empty($account->uid)) {

    // Not a registered user. See if we have guest user credentials.
    switch ($status) {
      case 1:
        drupal_add_http_header('Status', '400 Bad Request');
        _securesite_dialog(securesite_type_get());
        break;
      case 0:

        // Password is correct. Log user in.
        drupal_add_http_header($header['name'], $header['value']);
        $edit['pass'] = variable_get('securesite_guest_pass', '');
      default:
        _securesite_guest_login($edit);
        break;
    }
  }
  else {
    switch ($status) {
      case 0:

        // Password is correct. Log user in.
        drupal_add_http_header($header['name'], $header['value']);
        _securesite_user_login($edit, $account);
        break;
      case 2:

        // Password not stored. Request credentials using next most secure authentication method.
        $mechanism = _securesite_mechanism();
        $types = variable_get('securesite_type', array(
          SECURESITE_BASIC,
        ));
        rsort($types);
        foreach ($types as $type) {
          if ($type < $mechanism) {
            break;
          }
        }
        watchdog('user', 'Secure log-in failed for %user.', array(
          '%user' => $edit['name'],
        ));
        drupal_set_message(t('Secure log-in failed. Please try again.'), 'error');
        _securesite_dialog($type);
        break;
      case 1:
        drupal_add_http_header('Status', '400 Bad Request');
      default:

        // Authentication failed. Request credentials using most secure authentication method.
        watchdog('user', 'Log-in attempt failed for %user.', array(
          '%user' => $edit['name'],
        ));
        drupal_set_message(t('Unrecognized user name and/or password.'), 'error');
        _securesite_dialog(securesite_type_get());
        break;
    }
  }
}

/**
 * Get the result of digest validation.
 *
 * @param $status
 *   Will be set to the return status of the validation script
 * @param $edit
 *   An array of parameters to pass to the validation script
 *
 * @return
 *   An HTTP header string.
 */
function _securesite_digest_validate(&$status, $edit = array()) {
  static $header;
  if (!empty($edit)) {
    $args = array();
    foreach ($edit as $key => $value) {
      $args[] = "{$key}=" . escapeshellarg($value);
    }
    $script = variable_get('securesite_digest_script', drupal_get_path('module', 'securesite') . '/digest_md5/digest_md5.php');
    $response = exec($script . ' ' . implode(' ', $args), $output, $status);

    // drupal_set_header() is now drupal_add_http_header() and requires headers passed as name, value in an array.
    // The script returns a string, so we shall break it up as best we can. The existing code doesn't seem
    // to worry about correct data to append to 'WWW-Authenticate: ' etc so I won't add any for the D7 conversion.
    $response = explode('=', $response);
    $name = array_shift($response);
    $value = implode('=', $response);
    if (isset($edit['data']) && empty($status)) {
      $header = array(
        'name' => "Authentication-Info: " . $name,
        'value' => $value,
      );
    }
    else {
      $header = array(
        'name' => "WWW-Authenticate: Digest " . $name,
        'value' => $value,
      );
    }
  }
  return $header;
}

/**
 * Perform plain authentication.
 */
function _securesite_plain_auth($edit) {

  // We cant set username to be a required field so we check here if it is empty
  if (empty($edit['name'])) {
    drupal_set_message(t('Unrecognized user name and/or password.'), 'error');
    _securesite_dialog(securesite_type_get());
  }
  $users = user_load_multiple(array(), array(
    'name' => $edit['name'],
    'status' => 1,
  ));
  $account = reset($users);
  if (empty($account->uid)) {

    // Not a registered user.
    // If we have correct LDAP credentials, register this new user.
    if (module_exists('ldapauth') && _ldapauth_auth($edit['name'], $edit['pass'], TRUE) !== FALSE) {
      $users = user_load_multiple(array(), array(
        'name' => $edit['name'],
        'status' => 1,
      ));
      $account = reset($users);

      // System should be setup correctly now, perform log-in.
      _securesite_user_login($edit, $account);
    }
    else {

      // See if we have guest user credentials.
      _securesite_guest_login($edit);
    }
  }
  else {
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
    if (user_check_password($edit['pass'], $account) || module_exists('ldapauth') && _ldapauth_auth($edit['name'], $edit['pass']) !== FALSE) {

      // Password is correct. Perform log-in.
      _securesite_user_login($edit, $account);
    }
    else {

      // Request credentials using most secure authentication method.
      watchdog('user', 'Log-in attempt failed for %user.', array(
        '%user' => $edit['name'],
      ));
      drupal_set_message(t('Unrecognized user name and/or password.'), 'error');
      _securesite_dialog(securesite_type_get());
    }
  }
}

/**
 * Log in authenticated user.
 */
function _securesite_user_login($edit, $account) {
  if (user_access('access secured pages', $account)) {
    global $user;
    $user = $account;
    user_login_finalize($edit);

    // Reset menu to prevent access denied error.
    $router_items =& drupal_static('menu_get_item');
    unset($router_items[current_path()], $GLOBALS['theme']);
    menu_set_custom_theme();
    drupal_theme_initialize();

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

    // Unset the session variable set by securesite_denied().
    unset($_SESSION['securesite_denied']);

    // Unset messages from previous log-in attempts.
    unset($_SESSION['messages']);

    // Clear the guest session.
    unset($_SESSION['securesite_guest']);
  }
  else {
    _securesite_denied(t('You have not been authorized to log in to secured pages.'));
  }
}

/**
 * Log in guest user.
 */
function _securesite_guest_login($edit) {
  $guest_name = variable_get('securesite_guest_name', '');
  $guest_pass = variable_get('securesite_guest_pass', '');

  // Check anonymous user permission and credentials.
  if (user_access('access secured pages') && (empty($guest_name) || $edit['name'] == $guest_name) && (empty($guest_pass) || $edit['pass'] == $guest_pass)) {

    // Unset the session variable set by securesite_denied().
    if (isset($_SESSION['securesite_denied'])) {
      unset($_SESSION['securesite_denied']);
    }

    // Mark this session to prevent re-login (note: guests can't log out).
    $_SESSION['securesite_guest'] = $edit['name'];
    $_SESSION['securesite_login'] = TRUE;

    // Prevent a 403 error by redirecting off the logout page.
    if ($_GET['q'] == 'user/logout') {
      drupal_goto();
    }
  }
  else {
    if (empty($edit['name'])) {
      watchdog('user', 'Log-in attempt failed for <em>anonymous</em> user.');
      _securesite_denied(t('Anonymous users are not allowed to log in to secured pages.'));
    }
    else {
      watchdog('user', 'Log-in attempt failed for %user.', array(
        '%user' => $edit['name'],
      ));
      drupal_set_message(t('Unrecognized user name and/or password.'), 'error');
      _securesite_dialog(securesite_type_get());
    }
  }
}

/**
 * Deny access to users who are not authorized to access secured pages.
 */
function _securesite_denied($message) {
  if (empty($_SESSION['securesite_denied'])) {

    // Unset messages from previous log-in attempts.
    if (isset($_SESSION['messages'])) {
      unset($_SESSION['messages']);
    }

    // Set a session variable so that the log-in dialog will be displayed when the page is reloaded.
    $_SESSION['securesite_denied'] = TRUE;
    drupal_add_http_header('Status', '403 Forbidden');
    drupal_set_title(t('Access denied'));
    drupal_set_message(filter_xss($message), 'error');

    // Theme and display output
    $content = _securesite_dialog_page();
    print theme('securesite_page', array(
      'content' => $content,
    ));

    // Exit
    module_invoke_all('exit');
    exit;
  }
  else {
    unset($_SESSION['securesite_denied']);

    // Safari will attempt to use old credentials before requesting new credentials
    // from the user. Logging out requires that the WWW-Authenticate header be sent
    // twice.
    $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? drupal_strtolower($_SERVER['HTTP_USER_AGENT']) : '';
    if ($user_agent != str_replace('safari', '', $user_agent)) {
      $_SESSION['securesite_repeat'] = TRUE;
    }
    $types = variable_get('securesite_type', array(
      SECURESITE_BASIC,
    ));
    if (in_array(SECURESITE_DIGEST, $types)) {

      // Reset the digest header.
      $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));
      _securesite_digest_validate($status, array(
        'realm' => $realm,
        'fakerealm' => _securesite_fake_realm(),
      ));
    }
    _securesite_dialog(securesite_type_get());
  }
}

/**
 * Determine if Secure Site authentication should be forced.
 */
function _securesite_forced() {
  global $base_path;

  // Do we require credentials to display this page?
  if (php_sapi_name() == 'cli' || $_GET['q'] == 'admin/reports/request-test') {
    return FALSE;
  }
  else {
    switch (variable_get('securesite_enabled', SECURESITE_DISABLED)) {
      case SECURESITE_ALWAYS:
        return TRUE;
      case SECURESITE_OFFLINE:
        return variable_get('site_offline', FALSE);
      default:
        return FALSE;
    }
  }
}

/**
 * Display authentication dialog and send password reset mails.
 *
 * @param $type
 *   The type of authentication dialog to display.
 */
function _securesite_dialog($type) {
  global $user, $base_path, $language;

  // Has the password reset form been submitted?
  if (isset($_POST['form_id']) && $_POST['form_id'] == 'securesite_user_pass') {

    // Get form messages, but do not display form.
    drupal_get_form('securesite_user_pass');
    $content = '';
  }
  elseif (strpos($_GET['q'], 'user/reset/') === 0 || module_exists('locale') && $language->enabled && strpos($_GET['q'], $language->prefix . '/user/reset/') === 0) {
    $args = explode('/', $_GET['q']);
    if (module_exists('locale') && $language->enabled && $language->prefix != '') {

      // Remove the language argument.
      array_shift($args);
    }

    // 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 || count(user_load_multiple(array(), array(
      'uid' => $args[2],
      'status' => 1,
    ))) == 0) {
      $error = t('You have tried to use an invalid one-time log-in link.');
      $reset = variable_get('securesite_reset_form', t('Enter your user name or e-mail address.'));
      if (empty($reset)) {
        drupal_set_message($error, 'error');
        $content = '';
      }
      else {
        $error .= ' ' . t('Please request a new one using the form below.');
        drupal_set_message($error, 'error');
        $content = drupal_get_form('securesite_user_pass');
      }
    }
  }
  elseif (!module_exists('openid') || $_GET['q'] != 'openid/authenticate') {

    // Display log-in dialog.
    switch ($type) {
      case SECURESITE_DIGEST:
        $header = _securesite_digest_validate($status);
        if (empty($header)) {
          $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));
          $header = _securesite_digest_validate($status, array(
            'realm' => $realm,
            'fakerealm' => _securesite_fake_realm(),
          ));
        }
        if (strpos($header, 'WWW-Authenticate') === 0) {
          if (!empty($user->uid) || drupal_get_destination() != 'user/logout') {
            drupal_add_http_header('Status', '401 Unauthorized');
          }
        }
        else {
          drupal_add_http_header($header['name'], $header['value']);
        }
        break;
      case SECURESITE_BASIC:
        drupal_add_http_header('WWW-Authenticate', 'Basic realm="' . _securesite_fake_realm() . '"');
        if (!empty($user->uid) || !in_array('user/logout', drupal_get_destination())) {
          drupal_add_http_header('Status', '401 Unauthorized');
        }
        break;
      case SECURESITE_FORM:
        drupal_add_http_header('Status', '200 OK');
        break;
    }

    // Form authentication doesn't work for cron, so allow cron.php to run
    // without authenticating when no other authentication type is enabled.
    if (current_path() != 'user/logout' && (request_uri() != $base_path . 'cron.php' || variable_get('securesite_type', array(
      SECURESITE_BASIC,
    )) != array(
      SECURESITE_FORM,
    ))) {
      drupal_set_title(t('Authentication required'));
      $content = _securesite_dialog_page();
    }
  }
  if (isset($content) && !in_array('user/logout', drupal_get_destination())) {

    // Theme and display output
    print theme('securesite_page', array(
      'content' => $content,
    ));

    // Exit page
    module_invoke_all('exit');
    ob_flush();
    exit;
  }
}

/**
 * Opera and Internet Explorer save credentials indefinitely and will keep
 * attempting to use them even when they have failed multiple times. We add a
 * random string to the realm to allow users to log out.
 */
function _securesite_fake_realm() {
  $realm = variable_get('securesite_realm', variable_get('site_name', 'Drupal'));
  $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? drupal_strtolower($_SERVER['HTTP_USER_AGENT']) : '';
  if ($user_agent != str_replace(array(
    'msie',
    'opera',
  ), '', $user_agent)) {
    $realm .= ' - ' . mt_rand(10, 999);
  }
  return $realm;
}

/**
 * Display fall-back HTML for HTTP authentication dialogs. Safari will not load
 * this. Opera will not load this after log-out unless the page has been
 * reloaded and the authentication dialog has been displayed twice.
 */
function _securesite_dialog_page() {
  $reset = variable_get('securesite_reset_form', t('Enter your user name or e-mail address.'));
  if (in_array(SECURESITE_FORM, variable_get('securesite_type', array(
    SECURESITE_BASIC,
  )))) {
    $user_login = drupal_get_form('securesite_user_login_form');
    $output = render($user_login);
    if (!empty($reset)) {
      $user_pass = drupal_get_form('securesite_user_pass');
      $output .= "<hr />\n" . render($user_pass);
    }
  }
  else {
    if (!empty($reset)) {
      $user_pass = drupal_get_form('securesite_user_pass');
      $output = render($user_pass);
    }
    else {
      $output = '<p>' . t('Reload the page to try logging in again.') . '</p>';
    }
  }
  return $output;
}

/**
 * Form constructor for user login form.
 *
 * We use our own version of the login form for theming. We do not use the
 * default validate and submit functions because we may allow anonymous users.
 *
 * @see user_login()
 *
 * @ingroup forms
 */
function securesite_user_login_form($form, &$form_state) {
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('User name'),
    '#maxlength' => USERNAME_MAX_LENGTH,
    '#size' => 15,
  );
  $form['pass'] = array(
    '#type' => 'password',
    '#title' => t('Password'),
    '#maxlength' => 60,
    '#size' => 15,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Log in'),
    '#weight' => 2,
  );
  if (module_exists('openid')) {
    global $base_path;
    $style = '<style type="text/css" media="all">' . "\n" . '#securesite-user-login li.openid-link {' . "\n" . '  background:transparent url(' . $base_path . drupal_get_path('module', 'openid') . '/login-bg.png) no-repeat scroll 1px 0.35em;' . "\n" . '}' . "\n" . '</style>';
    drupal_add_html_head($style, 'securesite_open_id');
  }

  // drupal_alter takes this variable by reference so can't be a literal.
  $form_id = 'user_login';
  drupal_alter('form', $form, $form_state, $form_id);
  return $form;
}

/**
 * Form constructor for password reset form.
 *
 * We use our own version of the password reset form for theming.
 *
 * @see user_pass_validate()
 * @see user_pass_submit()
 *
 * @ingroup forms
 */
function securesite_user_pass($form, &$form_state) {
  module_load_include('inc', 'user', 'user.pages');
  $form = user_pass();
  $form['name']['#title'] = t('User name or e-mail address');
  $form_id = 'user_pass';
  drupal_alter('form', $form, $form_state, $form_id);
  $form['#redirect'] = FALSE;
  $form['#validate'][] = 'user_pass_validate';
  $form['#submit'][] = 'user_pass_submit';
  return $form;
}

/**
 * Gets the preferred authentication method.
 *
 * @return int
 */
function securesite_type_get() {
  $securesite_type = variable_get('securesite_type', array(
    SECURESITE_BASIC,
  ));
  return array_pop($securesite_type);
}

Functions

Namesort descending Description
securesite_type_get Gets the preferred authentication method.
securesite_user_login_form Form constructor for user login form.
securesite_user_pass Form constructor for password reset form.
_securesite_403 Menu callback; handle restricted pages.
_securesite_boot Boot with selected authentication mechanism.
_securesite_denied Deny access to users who are not authorized to access secured pages.
_securesite_dialog Display authentication dialog and send password reset mails.
_securesite_dialog_page Display fall-back HTML for HTTP authentication dialogs. Safari will not load this. Opera will not load this after log-out unless the page has been reloaded and the authentication dialog has been displayed twice.
_securesite_digest_auth Perform digest authentication.
_securesite_digest_validate Get the result of digest validation.
_securesite_fake_realm Opera and Internet Explorer save credentials indefinitely and will keep attempting to use them even when they have failed multiple times. We add a random string to the realm to allow users to log out.
_securesite_forced Determine if Secure Site authentication should be forced.
_securesite_guest_login Log in guest user.
_securesite_parse_directives Parse digest header into an array of directives.
_securesite_plain_auth Perform plain authentication.
_securesite_user_login Log in authenticated user.