ip_login.module in IP Login 6.2
Same filename and directory in other branches
Allow user login by IP addresses, ranges or wildcards.
Originally by David W 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
File
ip_login.moduleView source
<?php
/**
* @file
* Allow user login by IP addresses, ranges or wildcards.
*
* Originally by David W 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
*/
/*
* @todo for security, IP addresses and ranges should really be checked for collisions between existing accounts users
* @todo Check usage of $GLOBALS['conf']['cache'] in ip_login_attempt_login())
*/
define('ATTEMPT_IP_LOGIN', 'user/login_by_ip');
// path for ip login
define('IP_LOGOUT', '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
/**
* 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) {
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_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_help().
*/
function ip_login_help($path, $arg) {
// @todo use t()
switch ($path) {
case 'admin/settings/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/user/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_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/settings/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,
);
$items['admin/user/ip_login'] = array(
'title' => 'Users with IP Login',
'description' => 'Lists the users who can auto-login by IP address',
'access callback' => 'user_access',
'access arguments' => array(
'administer ip login',
),
'page callback' => 'ip_login_user_list',
'file' => 'ip_login.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* 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 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() {
$GLOBALS['conf']['cache'] = FALSE;
// don't cache - not sure if this is correct usage
$matched_uid = ip_login_check(ip_address());
if ($matched_uid > 0) {
ip_login_login($matched_uid);
}
drupal_goto(variable_get('ip_login_destination', 'user'));
}
/**
* Implementation of hook_perm().
*/
function ip_login_perm() {
// @todo add perms checks to correct places in module
return array(
'administer ip login',
'can log in as another user',
);
}
/**
* 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_address());
if ($matched_uid > 0) {
$link_text = t(variable_get('ip_login_link_login_page', 'Log in automatically'));
if (drupal_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 = '<br/>';
$markup .= '<ul class="item-list">';
$markup .= '<li><strong>' . l($link_text, ATTEMPT_IP_LOGIN, array(
'query' => array(
'ip_login_override_pages' => 'yes',
),
)) . '</strong>';
if (drupal_strlen($link_help)) {
$markup .= '<br/><small>' . filter_xss_admin($link_help) . '</small>';
}
$markup .= '</li></ul>';
$form['ip_login'] = array(
'#value' => filter_xss($markup),
'#weight' => variable_get('ip_login_link_login_page_weight', -10),
);
}
}
break;
case 'user_login_block':
$matched_uid = ip_login_check(ip_address());
if ($matched_uid > 0) {
$link_text = t(variable_get('ip_login_link_login_block', 'Log in automatically'));
if (drupal_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(
'#value' => filter_xss($markup),
'#weight' => variable_get('ip_login_link_login_block_weight', -10),
);
}
}
break;
}
}
/**
* Implementation of hook_block().
*
* Makes simple a 'Log in by IP' link available for those not wanting to use the
* overridden 'User Login' block.
*/
function ip_login_block($op = 'list', $delta = 0, $edit = array()) {
if ($op == 'list') {
$blocks = array();
$blocks[0] = array(
'info' => t('Log in by IP link'),
'cache' => BLOCK_NO_CACHE,
);
return $blocks;
}
elseif ($op == 'view') {
// only show for anonymous users who can log in
global $user;
if ($user->uid > 0 || !ip_login_is_possible()) {
return;
}
if ($delta != '0') {
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_user().
*
* Allows user admin of IP addresses.
*
* Also allows users to log out without immediately logging them back in.
* NOTE: this module may stop other modules hook_user(logout) code firing as it
* destroys the session
*/
function ip_login_user($op, &$edit, &$account, $category = NULL) {
// if logging out and we're currently logged in by IP...
switch ($op) {
case 'logout':
// check if user can login as another, or just re-login
ip_login_user_logout();
break;
case 'form':
// add IP Login field to the User Account form.
case 'register':
// and admin add user page
if ($op == 'form' && $category == 'account' || $op == 'register') {
if (user_access('administer ip login', $user)) {
// wrap in a fieldset
$form['ip_login_matches'] = array(
'#type' => 'fieldset',
'#title' => t('IP Login'),
'#description' => ip_login_help_ranges(t('This user can be automatically logged in by IP address.')),
'#weight' => '-1',
);
$form['ip_login_matches']['ip_login_match'] = array(
'#type' => 'textfield',
'#title' => t('IP address matches'),
'#description' => t('IP matches based in format listed above. Leave blank to disable automatic login by IP for this user.'),
'#default_value' => _ip_login_get_user_range($account->uid),
'#maxlength' => 255,
);
return $form;
}
}
break;
case 'validate':
if ($op == 'form' && $category == 'account' || $op == 'register' && isset($edit['ip_login_match'])) {
//TODO: replace with regexp ideally
// validate: replace all non-numeric but legal IP range chars with '|'
$value = $edit['ip_login_match'];
$ip_login_addresses = strtr($value, ' ,.-*', '|||||');
foreach (explode('|', $ip_login_addresses) as $quad) {
if (!empty($quad) && !is_numeric($quad)) {
// bad entry, warn & bail
form_set_error('ip_login_match', t('Only numbers, spaces, commas, dots, asterisks and hyphens are allowed in IP ranges.'));
break;
}
}
}
break;
case 'update':
case 'insert':
if ($category == 'account' && isset($edit['ip_login_match'])) {
_ip_login_set_user_range($account->uid, trim(check_plain($edit['ip_login_match'])));
}
break;
case 'delete':
_ip_login_set_user_range($account->uid, NULL);
}
}
/**
* 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];
}
// break up IP for ip address
$addr = explode(".", check_plain($ip));
$matches = FALSE;
$uid_matched = 0;
// Find user ip matches on the first part of the user's IP ANYWHERE except the end.
// Not desperately efficient but works consistently with comma separated IP ranges and spaces,
// and these checks are only done once per session anyway.
$partial_matches = db_query("SELECT uid, ip_match\n FROM {ip_login_user}\n WHERE ip_match LIKE ('%s')\n ORDER by LENGTH(ip_match) ASC", '%' . $addr[0] . '.%');
while ($row = db_fetch_object($partial_matches)) {
// multiple values are separated with commas so try each in turn
$user_ip_ranges = explode(",", $row->ip_match);
foreach ($user_ip_ranges as $ip_range) {
// clear any whitespace, break into quads, then compare
$ip_range = explode('.', trim($ip_range));
foreach ($ip_range as $index => $quad) {
$matches = ip_login_match_quad($addr[$index], $quad);
if (!$matches) {
break;
}
// no match, escape this foreach and move on to next IP range
}
// if it matches, stop here and do login
if ($matches) {
$uid_matched = $row->uid;
break 2;
// escape the foreach (ranges) and while (db_result)
}
}
}
// 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 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);
}
/**
* 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(array(
'uid' => $uid,
'status' => 1,
));
if (!$account) {
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_query("UPDATE {users} SET login = %d WHERE uid = %d", $user->login, $user->uid);
// 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;
sess_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 (drupal_strlen($url) == 0) {
$url = str_replace(array(
"\n",
"\r",
), '', $_GET["q"]);
}
if ($url == 'logout') {
$url = '<front>';
}
$url = url($url, array(
'query' => 'ip_login_no_cache=' . md5(time()),
'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;
}
}
}
/**
* Logs the current user out.
*
* Called from hook_user on logout, most of the code taken from user_logout()
* and _drupal_bootstrap().
*/
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'));
}
}
/**
* Compares a single IP quadrant to a matching quadrant.
*
* The matching quad can contain wildcards (*), ranges (10-12) or exact numbers
* @param $find_value
* A string containing the quadrant value being looked for
* @param $in_range
* String with a quadrant value, range or wildcard to compare to
* @return TRUE
* If $find_value matches an IP address $in_range
*
*/
function ip_login_match_quad($find_value, $in_range) {
// if we've got a wildcard just return TRUE
if ($in_range == '*') {
return TRUE;
}
// check if this quad contains the range character '-'
$range = explode('-', $in_range);
if (isset($range[1])) {
// we've got a range, test upper and lower bounds
if ($find_value >= $range[0] && $find_value <= $range[1]) {
return TRUE;
}
}
else {
// no range, just do normal match
return $range[0] == $find_value;
}
return FALSE;
}
/*
* 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;
}
// people running from their own machines can
//if (ip_address() == '127.0.0.1') return TRUE;
// If the user doesn't have a matching IP, then we let them log in normally
if (!ip_login_check(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;
}
/**
* Gets a user's IP range match string by uid
* @param $uid
* The user ID
* @return
* The IP range string for the user if one is found, otherwise NULL.
*
*/
function _ip_login_get_user_range($uid) {
if (isset($uid) && is_numeric($uid)) {
if ($result = db_fetch_object(db_query("SELECT * FROM {ip_login_user} WHERE uid = %d", $uid))) {
return $result->ip_match;
}
}
return NULL;
}
/**
* Sets the IP range match string for a user.
*
* If $ip_range is NULL, any IP Login record for this user is removed. Otherwise a row is inserted or updated.
* @param $uid
* The user ID
* @param $ip_range
* IP range match string
* @return TRUE
* If an update occured sucessfully.
*/
function _ip_login_set_user_range($uid, $ip_range = NULL) {
if (isset($uid) && is_numeric($uid)) {
if (is_null($ip_range) || empty($ip_range)) {
// null ip range = delete row
return db_query("DELETE FROM {ip_login_user} WHERE uid = %d", $uid);
}
else {
if (is_null(_ip_login_get_user_range($uid))) {
// no range presently = insert row
return db_query("INSERT INTO {ip_login_user} (`uid`, `ip_match`) VALUES (%d, '%s')", $uid, $ip_range);
}
else {
return db_query("UPDATE {ip_login_user} SET `ip_match` = '%s' WHERE `uid` = %d", $ip_range, $uid);
}
}
}
return 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 | Implementation of hook_block(). |
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_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_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_quad | Compares a single IP quadrant to a matching quadrant. |
ip_login_menu | Implementation of hook_menu(). |
ip_login_perm | Implementation of hook_perm(). |
ip_login_user | Implementation of hook_user(). |
ip_login_user_logout | Logs the current user out. |
_ip_login_can_login_as_another_user | |
_ip_login_get_user_range | Gets a user's IP range match string by uid |
_ip_login_set_user_range | Sets the IP range match string for a user. |