You are here

auto_login_url.module in Auto Login URL 7

Same filename and directory in other branches
  1. 8 auto_login_url.module
  2. 2.x auto_login_url.module

Main file for auto_login_url module.

File

auto_login_url.module
View source
<?php

/**
 * @file
 * Main file for auto_login_url module.
 */

/**
 * Implements hook_menu().
 */
function auto_login_url_menu() {
  $items = array();

  // Callback of autologin process.
  $items[variable_get('auto_login_url_short_url', 'autologinurl') . '/%/%'] = array(
    'title' => 'Auto Login',
    'page callback' => '_auto_login_url_page',
    'page arguments' => array(
      1,
      2,
    ),
    'access callback' => '_auto_login_url_access',
    'access arguments' => array(
      1,
      2,
    ),
    'type' => MENU_CALLBACK,
  );

  // Root URL of settings page.
  $items['admin/config/people/autologinurl'] = array(
    'title' => 'Auto Login URL settings',
    'description' => 'Settings page for Auto Login URL',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      '_auto_login_url_settings',
    ),
    'access arguments' => array(
      'administer auto login url',
    ),
  );

  // Default tab.
  $items['admin/config/people/autologinurl/settings'] = array(
    'title' => 'Auto Login URL settings',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  return $items;
}

/**
 * Auto login the user.
 *
 * @param integer $uid
 *   User id.
 * @param string $hash
 *   Code that passes through URL.
 */
function _auto_login_url_page($uid, $hash) {

  // Check for flood events.
  if (!flood_is_allowed('failed_login_attempt_ip', variable_get('user_failed_login_ip_limit', 50), variable_get('user_failed_login_ip_window', 3600))) {
    drupal_set_message(t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later.'), 'error');

    // Return access denied.
    drupal_access_denied();
    return;
  }

  // Get ALU secret.
  $auto_login_url_secret = _auto_login_url_get_secret();

  // Get user password.
  $password = db_query("SELECT pass FROM {users} WHERE uid = :uid", array(
    ':uid' => $uid,
  ))
    ->fetchField();

  // Create key.
  $key = drupal_get_hash_salt() . $auto_login_url_secret . $password;

  // Get if the hash is in the db.
  $result = db_select('auto_login_url', 'a')
    ->fields('a', array(
    'id',
    'uid',
    'destination',
  ))
    ->condition('hash', drupal_hmac_base64($hash, $key), '=')
    ->execute()
    ->fetchAssoc();
  if ($result !== FALSE && count($result) > 0) {
    $current_user = user_load($result['uid']);

    // Get destination URL.
    $destination = urldecode($result['destination']);

    // Account for anchor.
    if (strrpos($destination, '#') !== FALSE) {
      $fragment = substr($destination, strrpos($destination, '#') + 1);
      $url_array['fragment'] = $fragment;
      $url_array['alias'] = TRUE;
      $destination = substr($destination, 0, strrpos($destination, '#'));
    }

    // Account for GET[] arguments.
    if (strrpos($destination, '?') !== FALSE) {
      $arguments = substr($destination, strrpos($destination, '?') + 1);
      $arguments = explode('&', $arguments);
      $arguments_array = array();
      foreach ($arguments as $argument) {
        $temp_array = explode('=', $argument);
        $arguments_array[$temp_array[0]] = isset($temp_array[1]) ? $temp_array[1] : '';
      }
      $url_array['query'] = $arguments_array;
      $destination = substr($destination, 0, strrpos($destination, '?'));
    }

    // Create auto login url pre hook.
    module_invoke_all('pre_auto_login_url', $current_user, $destination);

    // Auto login the user.
    global $user;

    // Update the user table timestamp noting user has logged in.
    $current_user->login = REQUEST_TIME;
    db_update('users')
      ->fields(array(
      'login' => $current_user->login,
    ))
      ->condition('uid', $current_user->uid)
      ->execute();
    $user = $current_user;

    // Regenerate the session ID to prevent against session fixation attacks.
    // This is called before hook_user in case one of those functions fails
    // or incorrectly does a redirect which would leave the old session in place.
    drupal_session_regenerate();

    // Finalize the login process.
    user_login_finalize();

    // Delete auto login URL, if option checked.
    if (variable_get('auto_login_url_delete_on_use', FALSE)) {
      db_delete('auto_login_url')
        ->condition('id', array(
        $result['id'],
      ))
        ->execute();
    }

    // A generic array for arguments.
    $url_array = array();

    // Create auto login url post hook.
    module_invoke_all('post_auto_login_url', $current_user, $destination);
    if (count($url_array)) {
      drupal_goto($destination, $url_array);
    }
    else {
      drupal_goto($destination);
    }
  }
  else {

    // Register flood event for this IP.
    _auto_login_url_register_flood($hash);

    // Return access denied.
    return MENU_ACCESS_DENIED;
  }
}

/**
 * Access check for Auto login the user.
 *
 * @param integer $uid
 *   User id.
 * @param string $hash
 *   Code that passes through URL.
 */
function _auto_login_url_access($uid, $hash) {
  if (!empty($hash) && db_query("SELECT uid FROM {users} WHERE uid = :uid", array(
    ':uid' => $uid,
  ))
    ->fetchField()) {
    return TRUE;
  }
  else {

    // Register flood event for this IP.
    _auto_login_url_register_flood($hash);
    return FALSE;
  }
}

/**
 * Register flood event for this IP.
 *
 * @param string $hash
 *   Code that passes through URL.
 */
function _auto_login_url_register_flood($hash) {

  // Register flood event.
  flood_register_event('failed_login_attempt_ip', variable_get('user_failed_login_ip_window', 3600));

  // Log error.
  watchdog('auto_login_url', 'Failed Auto Login URL from ip: @ip and hash: @hash', array(
    '@ip' => ip_address(),
    '@hash' => $hash,
  ));
}

/**
 * Get secret key for ALU or create now.
 */
function _auto_login_url_get_secret() {

  // Check if it exists.
  $secret = variable_get('auto_login_url_secret', '');

  // Create if it does not exist.
  if ($secret == '') {
    $secret = drupal_random_key();
    variable_set('auto_login_url_secret', $secret);
  }
  return $secret;
}

/**
 * Settings form for Auto Login URL.
 *
 * @return object
 *   Form object.
 */
function _auto_login_url_settings() {
  $form = array();

  // Secret word.
  $form['auto_login_url_secret'] = array(
    '#type' => 'textfield',
    '#title' => t('Secret word'),
    '#required' => TRUE,
    '#default_value' => _auto_login_url_get_secret(),
    '#description' => t('Secret word to create hashes that are stored in DB.
      Every time this changes all previous URLs are invalidated.'),
  );

  // Expiration.
  $form['auto_login_url_expiration'] = array(
    '#type' => 'textfield',
    '#title' => t('Expiration'),
    '#required' => TRUE,
    '#default_value' => variable_get('auto_login_url_expiration', 2592000),
    '#description' => t('Expiration of URLs in seconds.'),
    '#element_validate' => array(
      'element_validate_integer_positive',
    ),
  );

  // Short URL.
  $form['auto_login_url_short_url'] = array(
    '#type' => 'textfield',
    '#title' => t('Short URL'),
    '#required' => TRUE,
    '#default_value' => variable_get('auto_login_url_short_url', 'autologinurl'),
    '#description' => t('Set URL prefix to use before random token.'),
  );

  // Token length.
  $form['auto_login_url_token_length'] = array(
    '#type' => 'textfield',
    '#title' => t('Token length'),
    '#required' => TRUE,
    '#default_value' => variable_get('auto_login_url_token_length', 64),
    '#description' => t('Length of generated URL token.
      WARNING: Please understand the security implications of a short auto-login-url string before you change this value.
      It has to be between 8 and 64 digits.'),
    '#element_validate' => array(
      'element_validate_integer_positive',
    ),
  );

  // Delete URLs on use.
  $form['auto_login_url_delete_on_use'] = array(
    '#type' => 'checkbox',
    '#title' => t('Delete on use'),
    '#default_value' => variable_get('auto_login_url_delete_on_use', FALSE),
    '#description' => t('Auto delete URLs after use.'),
  );
  $form = system_settings_form($form);
  $form['#submit'][] = '_auto_login_url_settings_submit';
  return $form;
}

/**
 * Validate handler for the admin settings form.
 */
function _auto_login_url_settings_validate($form, &$form_state) {

  // Validate token length.
  if ($form_state['values']['auto_login_url_token_length'] < 8 || $form_state['values']['auto_login_url_token_length'] > 64) {
    form_set_error('auto_login_url_token_length', t('Token length has to be between 8 and 64 digits.'));
  }
}

/**
 * Submit handler for the admin settings form.
 */
function _auto_login_url_settings_submit($form, &$form_state) {

  // Reload hook_menu cache.
  menu_rebuild();
}

/**
 * Implements hook_permission().
 */
function auto_login_url_permission() {
  return array(
    'administer auto login url' => array(
      'title' => t('Administer Auto Login URL settings'),
      'description' => t('Perform administration tasks on Auto Login URL.'),
    ),
  );
}

/**
 * Implements hook_variable_info().
 */
function auto_login_url_variable_info() {
  $variables = array();

  // Secret word to create hashes.
  $variables['auto_login_url_secret'] = array(
    'title' => t('Auto Login URL secret word'),
    'default' => _auto_login_url_get_secret(),
    'group' => 'auto_login_url',
    'token' => FALSE,
  );

  // Default cron schedule to delete old auto logins.
  $variables['auto_login_url_expiration'] = array(
    'title' => t('Auto Login URL expiration'),
    'default' => 2592000,
    'group' => 'auto_login_url',
    'token' => FALSE,
  );
  $variables['auto_login_url_short_url'] = array(
    'title' => t('Short URL'),
    'default' => 'autologinurl',
    'group' => 'auto_login_url',
    'token' => FALSE,
  );
  return $variables;
}

/**
 * Create an auto login hash on demand.
 *
 * @param int $uid
 *   User id.
 * @param string $destination
 *   Destination URL.
 * @param bool $absolute
 *   Absolute or relative link.
 *
 * @return string
 *   Autologin URL.
 */
function auto_login_url_create($uid = NULL, $destination = '', $absolute = FALSE) {
  if ($uid != NULL && is_numeric($uid) && $uid != 0) {

    // Get ALU secret.
    $auto_login_url_secret = _auto_login_url_get_secret();

    // Get user password.
    $password = db_query("SELECT pass FROM {users} WHERE uid = :uid", array(
      ':uid' => $uid,
    ))
      ->fetchField();

    // Create key.
    $key = drupal_get_hash_salt() . $auto_login_url_secret . $password;

    // Repeat until the hash that is saved in DB is unique.
    $hash_helper = 0;
    do {
      $data = $uid . microtime(TRUE) . $destination . $hash_helper;

      // Generate hash.
      $hash = drupal_hmac_base64($data, $key);

      // Get substring.
      $hash = substr($hash, 0, variable_get('auto_login_url_token_length', 64));

      // Generate hash to save to DB.
      $hash_db = drupal_hmac_base64($hash, $key);

      // Check hash is unique.
      $result = db_select('auto_login_url', 'alu')
        ->fields('alu', array(
        'hash',
      ))
        ->condition('alu.hash', $hash_db)
        ->execute()
        ->rowCount();

      // Increment value in case there will be a next iteration.
      $hash_helper++;
    } while ($result != 0);

    // Insert a new hash.
    db_insert('auto_login_url')
      ->fields(array(
      'uid',
      'hash',
      'destination',
      'timestamp',
    ))
      ->values(array(
      'uid' => $uid,
      'hash' => $hash_db,
      'destination' => $destination,
      'timestamp' => time(),
    ))
      ->execute();

    // Check if link is absolute.
    $absolute_path = '';
    if ($absolute) {
      global $base_url;
      $absolute_path = $base_url . '/';
    }
    return $absolute_path . variable_get('auto_login_url_short_url', 'autologinurl') . '/' . $uid . '/' . $hash;
  }

  // Return empty string.
  return '';
}

/**
 * Convert a whole text(E.g. mail with autologin links).
 *
 * @param int $uid
 *   User id.
 * @param string $text
 *   Text to change links to.
 *
 * @return string
 *   The text with changed links.
 */
function auto_login_url_convert_text($uid, $text) {
  global $base_root;

  // A pattern to convert links, but not images.
  // I am not very sure about that.
  $pattern = '/' . str_replace('/', '\\/', $base_root) . '\\/[^\\s^"^\']*/';

  // Create a new object and pass the uid.
  $current_conversion = new AutoLoginUrlConvertTextClass($uid);

  // Replace text with regex/callback.
  $text = preg_replace_callback($pattern, array(
    &$current_conversion,
    'replace',
  ), $text);
  return $text;
}

/**
 * Class to use for callback of link replacement.
 *
 * @author Thanos
 */
class AutoLoginUrlConvertTextClass {

  /**
   * Constructor.
   *
   * @param int $uid
   *   User ID.
   */
  public function __construct($uid) {
    $this->uid = $uid;
  }

  /**
   * Replace each link in the text.
   *
   * @param array $matches
   *   Matches array.
   *
   * @return string
   *   Converted URL.
   */
  public function replace(array $matches) {

    // Make a new search to check that the link is not image.
    // I know, not very clean.
    $pattern = '/(\\.jpg|\\.gif|\\.png)/';
    preg_match($pattern, $matches[0], $new_matches);
    if (count($new_matches) > 0) {
      return $matches[0];
    }
    else {
      return auto_login_url_create($this->uid, $matches[0], TRUE);
    }
  }

}

/**
 * Implements hook_cron().
 */
function auto_login_url_cron() {

  // Delete over one month auto logins.
  db_delete('auto_login_url')
    ->condition('timestamp', time() - variable_get('auto_login_url_expiration', 2592000), '<=')
    ->execute();
}

/**
 * Implements hook_token_info().
 */
function auto_login_url_token_info() {

  // Add new tokens.
  $info = array();

  // Home token.
  $info['tokens']['user']['auto-login-url-token'] = array(
    'name' => t('Auto Login URL'),
    'description' => t('This an auto login token for the user.'),
  );

  // Link that goes to user edit page.
  $info['tokens']['user']['auto-login-url-account-edit-token'] = array(
    'name' => t('Auto Login URL account edit'),
    'description' => t('This an auto login for the user account page.'),
  );
  return $info;
}

/**
 * Implements hook_tokens().
 */
function auto_login_url_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $replacements = array();
  if ($type == 'user' && isset($data['user'])) {
    $user = $data['user'];
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'auto-login-url-token':
          $replacements[$original] = auto_login_url_create($user->uid, '<front>', TRUE);
          break;
        case 'auto-login-url-account-edit-token':
          $replacements[$original] = auto_login_url_create($user->uid, 'user/' . $user->uid . '/edit', TRUE);
          break;
      }
    }
  }
  return $replacements;
}

Functions

Namesort descending Description
auto_login_url_convert_text Convert a whole text(E.g. mail with autologin links).
auto_login_url_create Create an auto login hash on demand.
auto_login_url_cron Implements hook_cron().
auto_login_url_menu Implements hook_menu().
auto_login_url_permission Implements hook_permission().
auto_login_url_tokens Implements hook_tokens().
auto_login_url_token_info Implements hook_token_info().
auto_login_url_variable_info Implements hook_variable_info().
_auto_login_url_access Access check for Auto login the user.
_auto_login_url_get_secret Get secret key for ALU or create now.
_auto_login_url_page Auto login the user.
_auto_login_url_register_flood Register flood event for this IP.
_auto_login_url_settings Settings form for Auto Login URL.
_auto_login_url_settings_submit Submit handler for the admin settings form.
_auto_login_url_settings_validate Validate handler for the admin settings form.

Classes

Namesort descending Description
AutoLoginUrlConvertTextClass Class to use for callback of link replacement.