diff options
Diffstat (limited to 'engine/lib/sessions.php')
| -rw-r--r-- | engine/lib/sessions.php | 505 |
1 files changed, 209 insertions, 296 deletions
diff --git a/engine/lib/sessions.php b/engine/lib/sessions.php index fdc6d1806..e3d5ce9cd 100644 --- a/engine/lib/sessions.php +++ b/engine/lib/sessions.php @@ -4,137 +4,43 @@ * Elgg session management * Functions to manage logins * - * @package Elgg - * @subpackage Core - * @author Curverider Ltd - * @link http://elgg.org/ + * @package Elgg.Core + * @subpackage Session */ /** Elgg magic session */ global $SESSION; /** - * Magic session class. - * This class is intended to extend the $_SESSION magic variable by providing an API hook - * to plug in other values. + * Return the current logged in user, or NULL if no user is logged in. * - * Primarily this is intended to provide a way of supplying "logged in user" details without touching the session - * (which can cause problems when accessed server side). + * If no user can be found in the current session, a plugin + * hook - 'session:get' 'user' to give plugin authors another + * way to provide user details to the ACL system without touching the session. * - * If a value is present in the session then that value is returned, otherwise a plugin hook 'session:get', '$var' is called, - * where $var is the variable being requested. - * - * Setting values will store variables in the session in the normal way. - * - * LIMITATIONS: You can not access multidimensional arrays - * - * This is EXPERIMENTAL. + * @return ElggUser */ -class ElggSession implements ArrayAccess { - /** Local cache of trigger retrieved variables */ - private static $__localcache; - - function __isset($key) { - return $this->offsetExists($key); - } - - /** Set a value, go straight to session. */ - function offsetSet($key, $value) { - $_SESSION[$key] = $value; - } - - /** - * Get a variable from either the session, or if its not in the session attempt to get it from - * an api call. - */ - function offsetGet($key) { - if (!ElggSession::$__localcache) { - ElggSession::$__localcache = array(); - } - - if (isset($_SESSION[$key])) { - return $_SESSION[$key]; - } - - if (isset(ElggSession::$__localcache[$key])) { - return ElggSession::$__localcache[$key]; - } - - $value = null; - $value = trigger_plugin_hook('session:get', $key, null, $value); - - ElggSession::$__localcache[$key] = $value; - - return ElggSession::$__localcache[$key]; - } - - /** - * Unset a value from the cache and the session. - */ - function offsetUnset($key) { - unset(ElggSession::$__localcache[$key]); - unset($_SESSION[$key]); - } - - /** - * Return whether the value is set in either the session or the cache. - */ - function offsetExists($offset) { - if (isset(ElggSession::$__localcache[$offset])) { - return true; - } - - if (isset($_SESSION[$offset])) { - return true; - } - - if ($this->offsetGet($offset)){ - return true; - } - } - - - // Alias functions - function get($key) { - return $this->offsetGet($key); - } - - function set($key, $value) { - return $this->offsetSet($key, $value); - } - - function del($key) { - return $this->offsetUnset($key); - } -} - - -/** - * Return the current logged in user, or null if no user is logged in. - * - * If no user can be found in the current session, a plugin hook - 'session:get' 'user' to give plugin - * authors another way to provide user details to the ACL system without touching the session. - */ -function get_loggedin_user() { +function elgg_get_logged_in_user_entity() { global $SESSION; if (isset($SESSION)) { return $SESSION['user']; } - return false; + return NULL; } /** * Return the current logged in user by id. * - * @see get_loggedin_user() + * @see elgg_get_logged_in_user_entity() * @return int */ -function get_loggedin_userid() { - $user = get_loggedin_user(); - if ($user) +function elgg_get_logged_in_user_guid() { + $user = elgg_get_logged_in_user_entity(); + if ($user) { return $user->guid; + } return 0; } @@ -142,14 +48,10 @@ function get_loggedin_userid() { /** * Returns whether or not the user is currently logged in * - * @return true|false + * @return bool */ -function isloggedin() { - if (!is_installed()) { - return false; - } - - $user = get_loggedin_user(); +function elgg_is_logged_in() { + $user = elgg_get_logged_in_user_entity(); if ((isset($user)) && ($user instanceof ElggUser) && ($user->guid > 0)) { return true; @@ -161,126 +63,136 @@ function isloggedin() { /** * Returns whether or not the user is currently logged in and that they are an admin user. * - * @uses isloggedin() - * @return true|false + * @return bool */ -function isadminloggedin() { - if (!is_installed()) { - return false; - } +function elgg_is_admin_logged_in() { + $user = elgg_get_logged_in_user_entity(); - $user = get_loggedin_user(); - - if ((isloggedin()) && (($user->admin || $user->siteadmin))) { - return true; + if ((elgg_is_logged_in()) && $user->isAdmin()) { + return TRUE; } - return false; + return FALSE; } /** * Check if the given user has full access. + * * @todo: Will always return full access if the user is an admin. * - * @param $user_guid + * @param int $user_guid The user to check + * * @return bool + * @since 1.7.1 */ function elgg_is_admin_user($user_guid) { global $CONFIG; - // cannot use metadata here because of recursion - - // caching is done at the db level so no need to here. - $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e, - {$CONFIG->dbprefix}metastrings as ms1, - {$CONFIG->dbprefix}metastrings as ms2, - {$CONFIG->dbprefix}metadata as md - WHERE ( - ( - (ms1.string = 'admin' AND ms2.string = 'yes') - OR (ms1.string = 'admin' AND ms2.string = '1') - ) - AND md.name_id = ms1.id AND md.value_id = ms2.id - AND e.guid = md.entity_guid - AND e.guid = {$user_guid} - AND e.banned = 'no' - )"; -// OR ( -// ms1.string = 'admin' AND ms2.string = '1' -// AND md.name_id = ms1.id AND md.value_id = ms2.id -// AND e.guid = md.entity_guid -// AND e.guid = {$user_guid} -// AND e.banned = 'no' -// )"; + $user_guid = (int)$user_guid; + // cannot use magic metadata here because of recursion + + // must support the old way of getting admin from metadata + // in order to run the upgrade to move it into the users table. + $version = (int) datalist_get('version'); + + if ($version < 2010040201) { + $admin = get_metastring_id('admin'); + $yes = get_metastring_id('yes'); + $one = get_metastring_id('1'); + + $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e, + {$CONFIG->dbprefix}metadata as md + WHERE ( + md.name_id = '$admin' + AND md.value_id IN ('$yes', '$one') + AND e.guid = md.entity_guid + AND e.guid = {$user_guid} + AND e.banned = 'no' + )"; + } else { + $query = "SELECT * FROM {$CONFIG->dbprefix}users_entity as e + WHERE ( + e.guid = {$user_guid} + AND e.admin = 'yes' + )"; + } // normalizing the results from get_data() // See #1242 $info = get_data($query); - if (!((is_array($info) && count($info) < 1) || $info === false)) { - return true; + if (!((is_array($info) && count($info) < 1) || $info === FALSE)) { + return TRUE; } - return false; + return FALSE; } /** - * Perform standard authentication with a given username and password. - * Returns an ElggUser object for use with login. + * Perform user authentication with a given username and password. + * + * @warning This returns an error message on failure. Use the identical operator to check + * for access: if (true === elgg_authenticate()) { ... }. + * * * @see login - * @param string $username The username, optionally (for standard logins) - * @param string $password The password, optionally (for standard logins) - * @return ElggUser|false The authenticated user object, or false on failure. + * + * @param string $username The username + * @param string $password The password + * + * @return true|string True or an error message on failure + * @access private */ - -function authenticate($username, $password) { - if (pam_authenticate(array('username' => $username, 'password' => $password))) { - return get_user_by_username($username); +function elgg_authenticate($username, $password) { + $pam = new ElggPAM('user'); + $credentials = array('username' => $username, 'password' => $password); + $result = $pam->authenticate($credentials); + if (!$result) { + return $pam->getFailureMessage(); } - - return false; + return true; } /** * Hook into the PAM system which accepts a username and password and attempts to authenticate * it against a known user. * - * @param array $credentials Associated array of credentials passed to pam_authenticate. This function expects - * 'username' and 'password' (cleartext). + * @param array $credentials Associated array of credentials passed to + * Elgg's PAM system. This function expects + * 'username' and 'password' (cleartext). + * + * @return bool + * @throws LoginException + * @access private */ -function pam_auth_userpass($credentials = NULL) { - - if (is_array($credentials) && ($credentials['username']) && ($credentials['password'])) { - if ($user = get_user_by_username($credentials['username'])) { +function pam_auth_userpass(array $credentials = array()) { - // Let admins log in without validating their email, but normal users must have validated their email or been admin created - if ((!$user->admin) && (!$user->validated) && (!$user->admin_created)) { - return false; - } + if (!isset($credentials['username']) || !isset($credentials['password'])) { + return false; + } - // User has been banned, so prevent from logging in - if ($user->isBanned()) { - return false; - } + $user = get_user_by_username($credentials['username']); + if (!$user) { + throw new LoginException(elgg_echo('LoginException:UsernameFailure')); + } - if ($user->password == generate_user_password($user, $credentials['password'])) { - return true; - } else { - // Password failed, log. - log_login_failure($user->guid); - } + if (check_rate_limit_exceeded($user->guid)) { + throw new LoginException(elgg_echo('LoginException:AccountLocked')); + } - } + if ($user->password !== generate_user_password($user, $credentials['password'])) { + log_login_failure($user->guid); + throw new LoginException(elgg_echo('LoginException:PasswordFailure')); } - return false; + return true; } /** * Log a failed login for $user_guid * - * @param $user_guid - * @return bool on success + * @param int $user_guid User GUID + * + * @return bool */ function log_login_failure($user_guid) { $user_guid = (int)$user_guid; @@ -301,8 +213,9 @@ function log_login_failure($user_guid) { /** * Resets the fail login count for $user_guid * - * @param $user_guid - * @return bool on success (success = user has no logged failed attempts) + * @param int $user_guid User GUID + * + * @return bool true on success (success = user has no logged failed attempts) */ function reset_login_failure_count($user_guid) { $user_guid = (int)$user_guid; @@ -312,7 +225,7 @@ function reset_login_failure_count($user_guid) { $fails = (int)$user->getPrivateSetting("login_failures"); if ($fails) { - for ($n=1; $n <= $fails; $n++) { + for ($n = 1; $n <= $fails; $n++) { $user->removePrivateSetting("login_failure_$n"); } @@ -331,11 +244,12 @@ function reset_login_failure_count($user_guid) { /** * Checks if the rate limit of failed logins has been exceeded for $user_guid. * - * @param $user_guid + * @param int $user_guid User GUID + * * @return bool on exceeded limit. */ function check_rate_limit_exceeded($user_guid) { - // 5 failures in 5 minutes causes temporary block on logins + // 5 failures in 5 minutes causes temporary block on logins $limit = 5; $user_guid = (int)$user_guid; $user = get_entity($user_guid); @@ -345,13 +259,13 @@ function check_rate_limit_exceeded($user_guid) { if ($fails >= $limit) { $cnt = 0; $time = time(); - for ($n=$fails; $n>0; $n--) { + for ($n = $fails; $n > 0; $n--) { $f = $user->getPrivateSetting("login_failure_$n"); - if ($f > $time - (60*5)) { + if ($f > $time - (60 * 5)) { $cnt++; } - if ($cnt==$limit) { + if ($cnt == $limit) { // Limit reached return true; } @@ -364,24 +278,20 @@ function check_rate_limit_exceeded($user_guid) { /** * Logs in a specified ElggUser. For standard registration, use in conjunction - * with authenticate. + * with elgg_authenticate. + * + * @see elgg_authenticate + * + * @param ElggUser $user A valid Elgg user object + * @param boolean $persistent Should this be a persistent login? * - * @see authenticate - * @param ElggUser $user A valid Elgg user object - * @param boolean $persistent Should this be a persistent login? - * @return true|false Whether login was successful + * @return true or throws exception + * @throws LoginException */ function login(ElggUser $user, $persistent = false) { - global $CONFIG; - // User is banned, return false. if ($user->isBanned()) { - return false; - } - - // Check rate limit - if (check_rate_limit_exceeded($user->guid)) { - return false; + throw new LoginException(elgg_echo('LoginException:BannedUser')); } $_SESSION['user'] = $user; @@ -395,18 +305,18 @@ function login(ElggUser $user, $persistent = false) { $code = (md5($user->name . $user->username . time() . rand())); $_SESSION['code'] = $code; $user->code = md5($code); - setcookie("elggperm", $code, (time()+(86400 * 30)),"/"); + setcookie("elggperm", $code, (time() + (86400 * 30)), "/"); } - if (!$user->save() || !trigger_elgg_event('login','user',$user)) { + if (!$user->save() || !elgg_trigger_event('login', 'user', $user)) { unset($_SESSION['username']); unset($_SESSION['name']); unset($_SESSION['code']); unset($_SESSION['guid']); unset($_SESSION['id']); unset($_SESSION['user']); - setcookie("elggperm", "", (time()-(86400 * 30)),"/"); - return false; + setcookie("elggperm", "", (time() - (86400 * 30)), "/"); + throw new LoginException(elgg_echo('LoginException:Unknown')); } // Users privilege has been elevated, so change the session id (prevents session fixation) @@ -416,26 +326,23 @@ function login(ElggUser $user, $persistent = false) { set_last_login($_SESSION['guid']); reset_login_failure_count($user->guid); // Reset any previous failed login attempts - // Set admin shortcut flag if this is an admin -// if (isadminloggedin()) { -// //@todo REMOVE THIS. -// global $is_admin; -// $is_admin = true; -// } - + // if memcache is enabled, invalidate the user in memcache @see https://github.com/Elgg/Elgg/issues/3143 + if (is_memcache_available()) { + // this needs to happen with a shutdown function because of the timing with set_last_login() + register_shutdown_function("_elgg_invalidate_memcache_for_entity", $_SESSION['guid']); + } + return true; } /** * Log the current user out * - * @return true|false + * @return bool */ function logout() { - global $CONFIG; - - if (isset($_SESSION['user'])) { - if (!trigger_elgg_event('logout','user',$_SESSION['user'])) { + if (isset($_SESSION['user'])) { + if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { return false; } $_SESSION['user']->code = ""; @@ -449,7 +356,7 @@ function logout() { unset($_SESSION['id']); unset($_SESSION['user']); - setcookie("elggperm", "", (time()-(86400 * 30)),"/"); + setcookie("elggperm", "", (time() - (86400 * 30)), "/"); // pass along any messages $old_msg = $_SESSION['msg']; @@ -457,71 +364,47 @@ function logout() { session_destroy(); // starting a default session to store any post-logout messages. - session_init(NULL, NULL, NULL); + _elgg_session_boot(NULL, NULL, NULL); $_SESSION['msg'] = $old_msg; return TRUE; } /** - * Returns a fingerprint for an elgg session. - * - * @return string - */ -function get_session_fingerprint() { - global $CONFIG; - - return md5($_SERVER['HTTP_USER_AGENT'] . get_site_secret()); -} - -/** * Initialises the system session and potentially logs the user in * * This function looks for: * * 1. $_SESSION['id'] - if not present, we're logged out, and this is set to 0 - * 2. The cookie 'elggperm' - if present, checks it for an authentication token, validates it, and potentially logs the user in + * 2. The cookie 'elggperm' - if present, checks it for an authentication + * token, validates it, and potentially logs the user in * * @uses $_SESSION - * @param unknown_type $event - * @param unknown_type $object_type - * @param unknown_type $object + * + * @return bool + * @access private */ -function session_init($event, $object_type, $object) { +function _elgg_session_boot() { global $DB_PREFIX, $CONFIG; - if (!is_db_installed()) { - return false; - } - // Use database for sessions // HACK to allow access to prefix after object destruction $DB_PREFIX = $CONFIG->dbprefix; if ((!isset($CONFIG->use_file_sessions))) { - session_set_save_handler("__elgg_session_open", - "__elgg_session_close", - "__elgg_session_read", - "__elgg_session_write", - "__elgg_session_destroy", - "__elgg_session_gc"); + session_set_save_handler("_elgg_session_open", + "_elgg_session_close", + "_elgg_session_read", + "_elgg_session_write", + "_elgg_session_destroy", + "_elgg_session_gc"); } session_name('Elgg'); session_start(); - // Do some sanity checking by generating a fingerprint (makes some XSS attacks harder) - if (isset($_SESSION['__elgg_fingerprint'])) { - if ($_SESSION['__elgg_fingerprint'] != get_session_fingerprint()) { - session_destroy(); - return false; - } - } else { - $_SESSION['__elgg_fingerprint'] = get_session_fingerprint(); - } - // Generate a simple token (private from potentially public session id) if (!isset($_SESSION['__elgg_session'])) { - $_SESSION['__elgg_session'] = md5(microtime().rand()); + $_SESSION['__elgg_session'] = md5(microtime() . rand()); } // test whether we have a user session @@ -532,7 +415,7 @@ function session_init($event, $object_type, $object) { unset($_SESSION['id']); unset($_SESSION['guid']); unset($_SESSION['code']); - + // is there a remember me cookie if (isset($_COOKIE['elggperm'])) { // we have a cookie, so try to log the user in @@ -545,7 +428,7 @@ function session_init($event, $object_type, $object) { $_SESSION['guid'] = $_SESSION['id']; $_SESSION['code'] = $_COOKIE['elggperm']; } - } + } } else { // we have a session and we have already checked the fingerprint // reload the user object from database in case it has changed during the session @@ -566,8 +449,8 @@ function session_init($event, $object_type, $object) { set_last_action($_SESSION['guid']); } - register_action("login",true); - register_action("logout"); + elgg_register_action('login', '', 'public'); + elgg_register_action('logout'); // Register a default PAM handler register_pam_handler('pam_auth_userpass'); @@ -582,42 +465,48 @@ function session_init($event, $object_type, $object) { return false; } - // Since we have loaded a new user, this user may have different language preferences - register_translations(dirname(dirname(dirname(__FILE__))) . "/languages/"); - return true; } /** * Used at the top of a page to mark it as logged in users only. * + * @return void */ function gatekeeper() { - if (!isloggedin()) { + if (!elgg_is_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('loggedinrequired')); - forward(); + forward('', 'login'); } } /** * Used at the top of a page to mark it as logged in admin or siteadmin only. * + * @return void */ function admin_gatekeeper() { gatekeeper(); - if (!isadminloggedin()) { + if (!elgg_is_admin_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('adminrequired')); - forward(); + forward('', 'admin'); } } /** - * DB Based session handling code. + * Handles opening a session in the DB + * + * @param string $save_path The path to save the sessions + * @param string $session_name The name of the session + * + * @return true + * @todo Document + * @access private */ -function __elgg_session_open($save_path, $session_name) { +function _elgg_session_open($save_path, $session_name) { global $sess_save_path; $sess_save_path = $save_path; @@ -625,16 +514,27 @@ function __elgg_session_open($save_path, $session_name) { } /** - * DB Based session handling code. + * Closes a session + * + * @todo implement + * @todo document + * + * @return true + * @access private */ -function __elgg_session_close() { +function _elgg_session_close() { return true; } /** - * DB Based session handling code. + * Read the session data from DB failing back to file. + * + * @param string $id The session ID + * + * @return string + * @access private */ -function __elgg_session_read($id) { +function _elgg_session_read($id) { global $DB_PREFIX; $id = sanitise_string($id); @@ -660,9 +560,15 @@ function __elgg_session_read($id) { } /** - * DB Based session handling code. + * Write session data to the DB falling back to file. + * + * @param string $id The session ID + * @param mixed $sess_data Session data + * + * @return bool + * @access private */ -function __elgg_session_write($id, $sess_data) { +function _elgg_session_write($id, $sess_data) { global $DB_PREFIX; $id = sanitise_string($id); @@ -675,7 +581,7 @@ function __elgg_session_write($id, $sess_data) { (session, ts, data) VALUES ('$id', '$time', '$sess_data_sanitised')"; - if (insert_data($q)!==false) { + if (insert_data($q) !== false) { return true; } } catch (DatabaseException $e) { @@ -695,9 +601,14 @@ function __elgg_session_write($id, $sess_data) { } /** - * DB Based session handling code. + * Destroy a DB session, falling back to file. + * + * @param string $id Session ID + * + * @return bool + * @access private */ -function __elgg_session_destroy($id) { +function _elgg_session_destroy($id) { global $DB_PREFIX; $id = sanitise_string($id); @@ -710,24 +621,28 @@ function __elgg_session_destroy($id) { global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; - return(@unlink($sess_file)); + return @unlink($sess_file); } - - return false; } /** - * DB Based session handling code. + * Perform garbage collection on session table / files + * + * @param int $maxlifetime Max age of a session + * + * @return bool + * @access private */ -function __elgg_session_gc($maxlifetime) { +function _elgg_session_gc($maxlifetime) { global $DB_PREFIX; - $life = time()-$maxlifetime; + $life = time() - $maxlifetime; try { return (bool)delete_data("DELETE from {$DB_PREFIX}users_sessions where ts<'$life'"); } catch (DatabaseException $e) { - // Fall back to file store in this case, since this likely means that the database hasn't been upgraded + // Fall back to file store in this case, since this likely means that the database + // hasn't been upgraded global $sess_save_path; foreach (glob("$sess_save_path/sess_*") as $filename) { @@ -739,5 +654,3 @@ function __elgg_session_gc($maxlifetime) { return true; } - -register_elgg_event_handler("boot","system","session_init",20); |
