guid; } return 0; } /** * Returns whether or not the user is currently logged in * * @return bool */ function elgg_is_logged_in() { $user = elgg_get_logged_in_user_entity(); if ((isset($user)) && ($user instanceof ElggUser) && ($user->guid > 0)) { return true; } return false; } /** * Returns whether or not the user is currently logged in and that they are an admin user. * * @return bool */ function elgg_is_admin_logged_in() { $user = elgg_get_logged_in_user_entity(); if ((elgg_is_logged_in()) && $user->isAdmin()) { return TRUE; } return FALSE; } /** * Check if the given user has full access. * * @todo: Will always return full access if the user is an admin. * * @param int $user_guid The user to check * * @return bool * @since 1.7.1 */ function elgg_is_admin_user($user_guid) { global $CONFIG; $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; } return FALSE; } /** * 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 * @param string $password The password * * @return true|string True or an error message on failure * @access private */ 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 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 * Elgg's PAM system. This function expects * 'username' and 'password' (cleartext). * * @return bool * @throws LoginException * @access private */ function pam_auth_userpass(array $credentials = array()) { if (!isset($credentials['username']) || !isset($credentials['password'])) { return false; } $user = get_user_by_username($credentials['username']); if (!$user) { throw new LoginException(elgg_echo('LoginException:UsernameFailure')); } 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 true; } /** * Log a failed login for $user_guid * * @param int $user_guid User GUID * * @return bool */ function log_login_failure($user_guid) { $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (($user_guid) && ($user) && ($user instanceof ElggUser)) { $fails = (int)$user->getPrivateSetting("login_failures"); $fails++; $user->setPrivateSetting("login_failures", $fails); $user->setPrivateSetting("login_failure_$fails", time()); return true; } return false; } /** * Resets the fail login count for $user_guid * * @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; $user = get_entity($user_guid); if (($user_guid) && ($user) && ($user instanceof ElggUser)) { $fails = (int)$user->getPrivateSetting("login_failures"); if ($fails) { for ($n = 1; $n <= $fails; $n++) { $user->removePrivateSetting("login_failure_$n"); } $user->removePrivateSetting("login_failures"); return true; } // nothing to reset return true; } return false; } /** * Checks if the rate limit of failed logins has been exceeded for $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 $limit = 5; $user_guid = (int)$user_guid; $user = get_entity($user_guid); if (($user_guid) && ($user) && ($user instanceof ElggUser)) { $fails = (int)$user->getPrivateSetting("login_failures"); if ($fails >= $limit) { $cnt = 0; $time = time(); for ($n = $fails; $n > 0; $n--) { $f = $user->getPrivateSetting("login_failure_$n"); if ($f > $time - (60 * 5)) { $cnt++; } if ($cnt == $limit) { // Limit reached return true; } } } } return false; } /** * Logs in a specified ElggUser. For standard registration, use in conjunction * with elgg_authenticate. * * @see elgg_authenticate * * @param ElggUser $user A valid Elgg user object * @param boolean $persistent Should this be a persistent login? * * @return true or throws exception * @throws LoginException */ function login(ElggUser $user, $persistent = false) { // User is banned, return false. if ($user->isBanned()) { throw new LoginException(elgg_echo('LoginException:BannedUser')); } $_SESSION['user'] = $user; $_SESSION['guid'] = $user->getGUID(); $_SESSION['id'] = $_SESSION['guid']; $_SESSION['username'] = $user->username; $_SESSION['name'] = $user->name; // if remember me checked, set cookie with token and store token on user if (($persistent)) { $code = (md5($user->name . $user->username . time() . rand())); $_SESSION['code'] = $code; $user->code = md5($code); setcookie("elggperm", $code, (time() + (86400 * 30)), "/"); } 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)), "/"); throw new LoginException(elgg_echo('LoginException:Unknown')); } // Users privilege has been elevated, so change the session id (prevents session fixation) session_regenerate_id(); // Update statistics set_last_login($_SESSION['guid']); reset_login_failure_count($user->guid); // Reset any previous failed login attempts // 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 bool */ function logout() { if (isset($_SESSION['user'])) { if (!elgg_trigger_event('logout', 'user', $_SESSION['user'])) { return false; } $_SESSION['user']->code = ""; $_SESSION['user']->save(); } unset($_SESSION['username']); unset($_SESSION['name']); unset($_SESSION['code']); unset($_SESSION['guid']); unset($_SESSION['id']); unset($_SESSION['user']); setcookie("elggperm", "", (time() - (86400 * 30)), "/"); // pass along any messages $old_msg = $_SESSION['msg']; session_destroy(); // starting a default session to store any post-logout messages. _elgg_session_boot(NULL, NULL, NULL); $_SESSION['msg'] = $old_msg; return TRUE; } /** * 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 * * @uses $_SESSION * * @return bool * @access private */ function _elgg_session_boot() { global $DB_PREFIX, $CONFIG; // 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_name('Elgg'); session_start(); // Generate a simple token (private from potentially public session id) if (!isset($_SESSION['__elgg_session'])) { $_SESSION['__elgg_session'] = md5(microtime() . rand()); } // test whether we have a user session if (empty($_SESSION['guid'])) { // clear session variables before checking cookie unset($_SESSION['user']); 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 $code = $_COOKIE['elggperm']; $code = md5($code); if ($user = get_user_by_code($code)) { // we have a user, log him in $_SESSION['user'] = $user; $_SESSION['id'] = $user->getGUID(); $_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 if ($user = get_user($_SESSION['guid'])) { $_SESSION['user'] = $user; $_SESSION['id'] = $user->getGUID(); $_SESSION['guid'] = $_SESSION['id']; } else { // user must have been deleted with a session active unset($_SESSION['user']); unset($_SESSION['id']); unset($_SESSION['guid']); unset($_SESSION['code']); } } if (isset($_SESSION['guid'])) { set_last_action($_SESSION['guid']); } elgg_register_action('login', '', 'public'); elgg_register_action('logout'); // Register a default PAM handler register_pam_handler('pam_auth_userpass'); // Initialise the magic session global $SESSION; $SESSION = new ElggSession(); // Finally we ensure that a user who has been banned with an open session is kicked. if ((isset($_SESSION['user'])) && ($_SESSION['user']->isBanned())) { session_destroy(); return false; } return true; } /** * Used at the top of a page to mark it as logged in users only. * * @return void */ function gatekeeper() { if (!elgg_is_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('loggedinrequired')); 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 (!elgg_is_admin_logged_in()) { $_SESSION['last_forward_from'] = current_page_url(); register_error(elgg_echo('adminrequired')); forward('', 'admin'); } } /** * 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) { global $sess_save_path; $sess_save_path = $save_path; return true; } /** * Closes a session * * @todo implement * @todo document * * @return true * @access private */ function _elgg_session_close() { return true; } /** * 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) { global $DB_PREFIX; $id = sanitise_string($id); try { $result = get_data_row("SELECT * from {$DB_PREFIX}users_sessions where session='$id'"); if ($result) { return (string)$result->data; } } catch (DatabaseException $e) { // Fall back to file store in this case, since this likely means // that the database hasn't been upgraded global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; return (string) @file_get_contents($sess_file); } return ''; } /** * 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) { global $DB_PREFIX; $id = sanitise_string($id); $time = time(); try { $sess_data_sanitised = sanitise_string($sess_data); $q = "REPLACE INTO {$DB_PREFIX}users_sessions (session, ts, data) VALUES ('$id', '$time', '$sess_data_sanitised')"; if (insert_data($q) !== false) { return true; } } catch (DatabaseException $e) { // Fall back to file store in this case, since this likely means // that the database hasn't been upgraded global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; if ($fp = @fopen($sess_file, "w")) { $return = fwrite($fp, $sess_data); fclose($fp); return $return; } } return false; } /** * Destroy a DB session, falling back to file. * * @param string $id Session ID * * @return bool * @access private */ function _elgg_session_destroy($id) { global $DB_PREFIX; $id = sanitise_string($id); try { return (bool)delete_data("DELETE from {$DB_PREFIX}users_sessions where session='$id'"); } catch (DatabaseException $e) { // Fall back to file store in this case, since this likely means that // the database hasn't been upgraded global $sess_save_path; $sess_file = "$sess_save_path/sess_$id"; return @unlink($sess_file); } } /** * Perform garbage collection on session table / files * * @param int $maxlifetime Max age of a session * * @return bool * @access private */ function _elgg_session_gc($maxlifetime) { global $DB_PREFIX; $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 global $sess_save_path; foreach (glob("$sess_save_path/sess_*") as $filename) { if (filemtime($filename) < $life) { @unlink($filename); } } } return true; }