ip_login.module in IP Login 7.3
Same filename and directory in other branches
Allow user login by IP addresses, ranges or wildcards.
Originally by David W H Thomas Major enhancements and 2.x branch by Jim Kirkpatrick Other big contributions from:
- JohnV: Code tidy/refactor & D7 version - http://drupal.org/node/1151458
- PeterX: Many bug fixes and tweaks
Picked up again by David Thomas for 7.3.x
File
ip_login.moduleView source
<?php
/**
* @file
* Allow user login by IP addresses, ranges or wildcards.
*
* Originally by David W H Thomas
* Major enhancements and 2.x branch by Jim Kirkpatrick
* Other big contributions from:
* - JohnV: Code tidy/refactor & D7 version - http://drupal.org/node/1151458
* - PeterX: Many bug fixes and tweaks
* Picked up again by David Thomas for 7.3.x
*/
/*
* @todo for security, IP addresses and ranges should really be checked for collisions between existing accounts users
*/
define('ATTEMPT_IP_LOGIN', 'user/login_by_ip');
// path for ip login
define('IP_LOGOUT', 'user/logout');
// path for user logout (different between D6/D7)
define('IP_CHECKED', 'ip_login_checked');
// TRUE when IP check has happened
define('IP_UID_MATCH', 'ip_login_uid');
// TRUE when a user account matches the request IP
define('LOGIN_AS_DIFFERENT_USER', 'ip_login_as_different_user');
// TRUE when user wants alternate account
define('CACHE_DISABLED', 0);
// D7 TODO: this is removed from bootstrap.inc, and now in?? --> avoid double definition
/**
* Implementation of hook_menu().
*/
function ip_login_menu() {
$items[ATTEMPT_IP_LOGIN] = array(
'title' => 'Automatically log me in by IP',
'access callback' => 'ip_login_is_possible',
'page callback' => 'ip_login_attempt_login',
'type' => MENU_CALLBACK,
);
$items['admin/config/people/ip_login'] = array(
'title' => 'IP Login',
'description' => 'Configure IP Login settings',
'access callback' => 'user_access',
'access arguments' => array(
'administer ip login',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'ip_login_admin_settings',
),
'file' => 'ip_login.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implementation of hook_help().
*/
function ip_login_help($path, $arg) {
switch ($path) {
case 'admin/config/people/ip_login':
$help = '<p>';
$help .= t("This module allows this site to automatically authenticate and login users arriving with a chosen IP address - optionally at certain pages only.") . '</p> <p>';
$help .= t('It also allows users with the <code>administer ip login</code> and <code>can log in as another user</code> <a href="@permissions-link">permissions</a> to log out and log in as another user, with other users being forced to stay logged in.', array(
'@permissions-link' => '/admin/people/permissions#module-ip_login',
));
$help .= '</p>';
$help .= ip_login_help_ranges();
return $help;
}
}
/**
* Provides help about accepted values of IP ranges etc.
*/
function ip_login_help_ranges($intro = '') {
$help = '<p id="ip_login">' . $intro . ' ' . t('Accepted IP Login match values are:') . '</p>';
$help .= '<ul><li>';
$help .= t("Single IP matches like <code>123.123.123.123</code>");
$help .= '</li><li>';
$help .= t("Wildcards using an asterisk (<code>*</code>) in any quadrant except the first one, for example <code>123.123.123.*</code> or <code>100.*.*.*</code> etc.");
$help .= '</li><li>';
$help .= t("Ranges using a hyphen (<code>-</code>) in any quadrant except the first one, for example <code>123.123.123.100-200</code> etc.");
$help .= '</li><li>';
$help .= t("Any number of comma-separated IP addresses or ranges like <code>10.11.12.13, 123.123.123.100-200, 123.123.124-125.*</code> etc.");
$help .= '</li></ul>';
return $help;
}
/**
* Implementation of hook_permission().
*/
function ip_login_permission() {
// @todo add perms checks to correct places in module
return array(
'administer ip login' => array(
'title' => t('Administer IP Login module'),
'description' => t('Perform administration tasks for IP Login.'),
),
'view any field_ip_login' => array(
'title' => t('View any field_ip_login'),
'description' => t('View the display output of field_ip_login on any user account.'),
),
'view own field_ip_login' => array(
'title' => t('View own field_ip_login'),
'description' => t('View the display output of field_ip_login on own user account.'),
),
'edit any field_ip_login' => array(
'title' => t('Edit any field_ip_login'),
'description' => t('Edit the value of field_ip_login on any user account.'),
),
'edit own field_ip_login' => array(
'title' => t('Edit own field_ip_login'),
'description' => t('Edit the value of field_ip_login on own user account.'),
),
'can log in as another user' => array(
'title' => t('Can login as other user'),
'description' => t('Allow user to logout and login as another user.'),
),
);
}
/**
* Implements hook_field_access
* Restricts view and edit access to field_ip_login
*/
function ip_login_field_access($op, $field, $entity_type, $entity, $account) {
if ($field['field_name'] == 'field_ip_login') {
global $user;
$account = $account ? $account : $user;
// Use logged in user if account not passed
switch ($op) {
case 'view':
// User has admin access?
if (user_access('administer ip login', $account) || user_access('administer users', $account)) {
return TRUE;
// User has access to view any field_ip_login instance?
}
elseif (user_access('view any field_ip_login', $account)) {
return TRUE;
// User has access to view own field_ip_login instance
// and logged in user uid matches entity uid being checked
}
elseif (user_access('view own field_ip_login', $account) && isset($entity->uid) && $entity->uid == $user->uid) {
return TRUE;
}
break;
case 'edit':
// User has admin access?
if (user_access('administer ip login', $account) || user_access('administer users', $account)) {
return TRUE;
// User has access to edit any field_ip_login instance?
}
elseif (user_access('edit any field_ip_login', $account)) {
return TRUE;
// User has access to view own field_ip_login instance
// and logged in user uid matches entity uid being checked
}
elseif (user_access('edit own field_ip_login', $account) && isset($entity->uid) && $entity->uid == $user->uid) {
return TRUE;
}
break;
}
return FALSE;
// Default access deny
}
}
/**
* Implementation of hook_boot().
*
* see http://drupal.org/node/509028
*/
function ip_login_boot() {
// skip rest of this if user is logged in
global $user;
if ($user->uid != 0 || drupal_is_cli()) {
return;
}
// skip rest of this if the admin has disabled IP login
if (!variable_get('ip_login_enabled', 1)) {
return;
}
// Avoid settings cookies if not on an IP Login-enabled page to improve
// external caching support - http://drupal.org/node/1263234 thanks Vacilando
if (ip_login_check_path() === FALSE) {
return;
}
// check the user IP
$matched_uid = ip_login_check(ip_login_ip_address());
if ($matched_uid > 0) {
$can_login_as_another_user = isset($_COOKIE[LOGIN_AS_DIFFERENT_USER]) ? $_COOKIE[LOGIN_AS_DIFFERENT_USER] : NULL;
// for clarity about every scenario, use extensive logic
if (is_null($can_login_as_another_user)) {
// first time login for user, so log in automatically.
ip_login_login($matched_uid);
drupal_goto(ltrim(request_uri(), '/'));
}
elseif ($can_login_as_another_user == FALSE) {
// user logged out, but is not allowed to use another user, so log in again.
ip_login_login($matched_uid);
drupal_goto(ltrim(request_uri(), '/'));
}
elseif ($can_login_as_another_user == TRUE) {
// user logged out, and is allowed to login as another user,
// so do nothing, just stay on this page and wait for user action.
}
else {
// do automatic login.
ip_login_login($matched_uid);
drupal_goto(ltrim(request_uri(), '/'));
}
}
}
/**
* Implementation of hook_block_info
*
* Adds the simple 'Automatic login' link block
*/
function ip_login_block_info() {
$blocks = array();
$blocks['ip-login-link'] = array(
'info' => t('Log in by IP link'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
/**
* Implementation of hook_block_view
*
* Makes simple a 'Automatic login' link available for those not wanting to use the
* overridden 'User Login' block.
*/
function ip_login_block_view($delta = '') {
// only show for anonymous users who can log in
global $user;
if ($user->uid > 0 || !ip_login_is_possible()) {
return;
}
if ($delta != 'ip-login-link') {
return;
}
// build simple block
// @todo should be a hook_theme call
$link_text = t(variable_get('ip_login_link_login_block', 'Log in automatically'));
$markup = '<div class="ip-login-available"><span class="ip-login-link">';
$markup .= l($link_text, ATTEMPT_IP_LOGIN, array(
'query' => array(
'ip_login_override_pages' => 'yes',
),
));
$markup .= '</span></div>';
$block = array(
'subject' => t('Automatic login'),
'content' => $markup,
);
return $block;
}
/**
* Implementation of hook_form_alter().
*/
function ip_login_form_alter(&$form, &$form_state, $form_id) {
// @todo should call hook_theme ideally
switch ($form_id) {
case 'user_login':
$matched_uid = ip_login_check(ip_login_ip_address());
if ($matched_uid > 0) {
$link_text = t(variable_get('ip_login_link_login_page', 'Log in automatically'));
if (strlen($link_text)) {
// hide if no link text
$link_help = t(variable_get('ip_login_link_login_page_help', "Your computer's IP address has been matched and validated."));
$markup = '<ul class="item-list">';
$markup .= '<li><strong>' . l($link_text, ATTEMPT_IP_LOGIN, array(
'query' => array(
'ip_login_override_pages' => 'yes',
),
)) . '</strong>';
if (strlen($link_help)) {
$markup .= '<br/><small>' . filter_xss_admin($link_help) . '</small>';
}
$markup .= '</li></ul>';
$form['ip_login'] = array(
'#markup' => $markup,
'#weight' => variable_get('ip_login_link_login_page_weight', -10),
);
}
}
break;
case 'user_login_block':
$matched_uid = ip_login_check(ip_login_ip_address());
if ($matched_uid > 0) {
$link_text = t(variable_get('ip_login_link_login_block', 'Log in automatically'));
if (strlen($link_text)) {
// hide if no link text
$markup = '<ul class="item-list">';
$markup .= '<li><strong>' . l($link_text, ATTEMPT_IP_LOGIN, array(
'query' => array(
'ip_login_override_pages' => 'yes',
),
)) . '</strong>';
$markup .= '</li></ul>';
$form['ip_login'] = array(
'#markup' => $markup,
'#weight' => variable_get('ip_login_link_login_page_weight', -10),
);
}
}
break;
}
}
/**
* Helper function to return IP address
* for easy test/debug
*/
function ip_login_ip_address() {
return ip_address();
}
/**
* Callback function for hook_menu (menu access)
*
* @return boolean
* TRUE if login by IP can happen because a user match has happened
*/
function ip_login_is_possible() {
// Return TRUE if a matching uid is found.
return !empty($_SESSION[IP_UID_MATCH]);
}
/**
* Checks path of current page matches any set as options on the admin page to
* see if IP login should occur. Adapted from core Block module's block_list().
*
* @return $uid_matched
* The uid of the matching user account
*/
function ip_login_check_path() {
if (!isset($_GET['ip_login_override_pages'])) {
$pages = variable_get('ip_login_active_pages', '');
if ($pages) {
$page_match = FALSE;
// first char, if variable set, is 'check_mode' - remainder is paths to match or PHP
$check_mode = substr($pages, 0, 1);
$pages = substr($pages, 1);
if ($check_mode < 2) {
// Compare with the path with allowed pages.
// Since this happens in hook_boot, we cannot call drupal_get_path_alias
// so call our own path matcher code and avoid a DB/alias check
$path = isset($_GET['q']) ? $_GET['q'] : '';
$page_match = ip_login_match_path($path, $pages);
// When $check_mode has a value of 0, the IP check happens on
// all pages except those listed in $pages. When set to 1, IPs
// are checked only on those pages listed in $pages.
$page_match = !($check_mode xor $page_match);
}
else {
// evaluate PHP
$page_match = drupal_eval($pages);
}
// if we don't have a path/PHP match, don't log in
if (!$page_match) {
return FALSE;
}
}
}
// all is well, continue with login.
return TRUE;
}
/**
* Check if a path matches any pattern in a set of patterns. This is a clone of
* drupal_match_path() found in path.inc because the bootstrap hasn't occurred,
* so path.inc isn't available.
*
* See: http://api.drupal.org/api/drupal/includes--path.inc/function/drupal_match_path/6
*/
function ip_login_match_path($path, $patterns) {
static $regexps;
if (!isset($regexps[$patterns])) {
$regexps[$patterns] = '/^(' . preg_replace(array(
'/(\\r\\n?|\\n)/',
'/\\\\\\*/',
'/(^|\\|)\\\\<front\\\\>($|\\|)/',
), array(
'|',
'.*',
'\\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\\2',
), preg_quote($patterns, '/')) . ')$/';
}
return preg_match($regexps[$patterns], $path);
}
/**
* Checks the request IP and logs user in there's a match by calling
* ip_login_check then ip_login_attempt_login
*
* Callback function for hook_menu
*/
function ip_login_attempt_login() {
drupal_page_is_cacheable(FALSE);
$matched_uid = ip_login_check(ip_login_ip_address());
if ($matched_uid > 0) {
ip_login_login($matched_uid);
}
drupal_goto(variable_get('ip_login_destination', 'user'));
}
/**
* Compares the current request's IP address to the ip_login_user table
* and then does a proper match for each match on exact, ranges and wildcards
*
* @param $ip
* An ip address string, usually from the current user's request
* @return $uid_matched
* The uid of the matching user account
*/
function ip_login_check($ip, $diagnostics = FALSE) {
// have we checked user IP already this session?
if (!empty($_SESSION[IP_CHECKED])) {
return $_SESSION[IP_UID_MATCH];
}
$uid_matched = ip_login_match_user($ip, $diagnostics);
// if not diagnostic test, set processed session flag, store matching user (if there is one)
if (!$diagnostics) {
$_SESSION[IP_CHECKED] = TRUE;
$_SESSION[IP_UID_MATCH] = $uid_matched;
}
return $uid_matched;
}
/**
* Checks for a user with matching IP address
*/
function ip_login_match_user($ip, $diagnostics = FALSE) {
// Check for ip address in field_data_field_ip_login data table
// We can utilize a range search via
// field_ipaddress long numeric range query
// Currently IPv4 only
$matching_uid = db_select('field_data_field_ip_login', 'ip')
->fields('ip', array(
'entity_id',
))
->condition('entity_type', 'user')
->condition('field_ip_login_start', ip2long($ip), '<=')
->condition('field_ip_login_end', ip2long($ip), '>=')
->execute()
->fetchField();
return $matching_uid;
}
/**
* Performs a login for user with $uid and stores IP Login variables for later
*
* @param $uid
* The UID of the account to be logged in
*/
function ip_login_login($uid) {
if ($uid) {
// if a uid is passed in
// check this page's path is ok to login automatically from
if (ip_login_check_path() === FALSE) {
return;
}
// get user module and include some handy functions
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
// get account (reload from db) , bail if no loaded active user
$account = user_load($uid, TRUE);
if (!$account || $account->status != 1) {
return;
}
// login by assigning account to global $user object
global $user;
$user = $account;
if (!variable_get('ip_login_suppress_messages', 0)) {
// notify user - if messages not suppressed
$message = t('Welcome %name. You have been automatically logged into %sitename.', array(
'%name' => $user->name,
'%sitename' => variable_get('site_name', 'this website'),
));
drupal_set_message($message);
// add handy message for those who can log out and then back in as another user
if (_ip_login_can_login_as_another_user($user)) {
$message = t('You may also <a href="@other_user_link">log in as another user</a> if required.', array(
'@other_user_link' => url(IP_LOGOUT),
));
drupal_set_message($message);
}
}
// following borrowed from user_authenticate_finalize(), but with slightly different message
watchdog('user', 'Session opened for %name by IP Login.', array(
'%name' => $user->name,
));
// This is also used to invalidate one-time login links.
$user->login = time();
db_update('users')
->fields(array(
'login' => $user->login,
))
->condition('uid', $user->uid)
->execute();
// 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.
$edit = NULL;
drupal_session_regenerate();
user_module_invoke('login', $edit, $user);
// following borrowed from ipAuthenticator's login and avoids caching issues
if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && !isset($_GET['ip_login_no_cache'])) {
// make a url to reload page, remove newlines from the URL to avoid header injection attacks.
// use admin settings for destination if set.
$url = variable_get('ip_login_destination', '');
if (strlen($url) == 0) {
$url = str_replace(array(
"\n",
"\r",
), '', $_GET["q"]);
}
if ($url == 'logout') {
$url = '<front>';
}
$url = url($url, array(
'query' => array(
'ip_login_no_cache' => drupal_random_bytes(8),
),
'absolute' => TRUE,
));
// Before the redirect, allow modules to react to the end of the page request.
module_invoke_all('exit', $url);
// Even though session_write_close() is registered as a shutdown function, we
// need all session data written to the database before redirecting.
session_write_close();
header('Location: ' . $url, TRUE, 302);
exit;
}
}
}
/**
* Implementation of hook_user_logout
*
* Called from hook_user on logout, most of the code taken from user_logout()
* and _drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION).
* D7-changes: this is now an implementation of a hook, so:
* - Only do a logout, leave the automatic login to ip_login_boot;
* - prevent logging out if needed, just by calling drupal_goto()
* - N.B. check user_logout() in user.pages.inc; this is the calling function;
* - N.B. check devel_switch_user() in devel.module; here users are switched, too;
*/
function ip_login_user_logout() {
// prevent recursive call via user_module_invoke() / module_invoke_all() in user.pages.inc
if (!ip_login_is_possible()) {
return;
}
else {
$_SESSION[IP_CHECKED] = FALSE;
$_SESSION[IP_UID_MATCH] = 0;
}
global $user;
// store whether this user can log back in automatically
$can_login_as_another_user = _ip_login_can_login_as_another_user($user);
// sets indicator to behaviour in hook_boot().
$expire = 0;
// Cookie expires at the end of the session (when the browser closes).
setcookie(LOGIN_AS_DIFFERENT_USER, $can_login_as_another_user, $expire, '/');
if (!$can_login_as_another_user) {
// @todo: it is possible that some other hook_user_logout() has been called already
// does this generate an undetermined state?
$message = t(variable_get('ip_login_logged_back_in', 'This account does not have permission to log out once logged in automatically. You have been logged back in.'));
$message = token_replace($message, array(
'user' => $user,
), array(
'clear' => TRUE,
));
drupal_set_message($message, 'warning');
// show the login page
drupal_goto(variable_get('ip_login_destination', 'user'));
}
}
/*
* Returns TRUE if a user has permission to log out and back in as another user
*/
function _ip_login_can_login_as_another_user($user) {
// super user can log in as another user
if ($user->uid == 1) {
return TRUE;
}
// people who can administer this module can
if (user_access('administer ip login', $user)) {
return TRUE;
}
// If the user doesn't have a matching IP, then we let them log in normally
if (!ip_login_check(ip_login_ip_address())) {
return TRUE;
}
// all others check correct permission, making sure only TRUE, FALSE is returned
return user_access('can log in as another user', $user) ? TRUE : FALSE;
}
Functions
Name![]() |
Description |
---|---|
ip_login_attempt_login | Checks the request IP and logs user in there's a match by calling ip_login_check then ip_login_attempt_login |
ip_login_block_info | Implementation of hook_block_info |
ip_login_block_view | Implementation of hook_block_view |
ip_login_boot | Implementation of hook_boot(). |
ip_login_check | Compares the current request's IP address to the ip_login_user table and then does a proper match for each match on exact, ranges and wildcards |
ip_login_check_path | Checks path of current page matches any set as options on the admin page to see if IP login should occur. Adapted from core Block module's block_list(). |
ip_login_field_access | Implements hook_field_access Restricts view and edit access to field_ip_login |
ip_login_form_alter | Implementation of hook_form_alter(). |
ip_login_help | Implementation of hook_help(). |
ip_login_help_ranges | Provides help about accepted values of IP ranges etc. |
ip_login_ip_address | Helper function to return IP address for easy test/debug |
ip_login_is_possible | Callback function for hook_menu (menu access) |
ip_login_login | Performs a login for user with $uid and stores IP Login variables for later |
ip_login_match_path | Check if a path matches any pattern in a set of patterns. This is a clone of drupal_match_path() found in path.inc because the bootstrap hasn't occurred, so path.inc isn't available. |
ip_login_match_user | Checks for a user with matching IP address |
ip_login_menu | Implementation of hook_menu(). |
ip_login_permission | Implementation of hook_permission(). |
ip_login_user_logout | Implementation of hook_user_logout |
_ip_login_can_login_as_another_user |