1<?php 2 3use Elgg\SystemMessagesService; 4use Elgg\Di\ServiceProvider; 5 6/** 7 * Elgg session management 8 * Functions to manage logins 9 */ 10 11/** 12 * Gets Elgg's session object 13 * 14 * @return \ElggSession 15 * @since 1.9 16 */ 17function elgg_get_session() { 18 return elgg()->session; 19} 20 21/** 22 * Return the current logged in user, or null if no user is logged in. 23 * 24 * @return \ElggUser|null 25 */ 26function elgg_get_logged_in_user_entity() { 27 return elgg()->session->getLoggedInUser(); 28} 29 30/** 31 * Return the current logged in user by guid. 32 * 33 * @see elgg_get_logged_in_user_entity() 34 * @return int 35 */ 36function elgg_get_logged_in_user_guid() { 37 return elgg()->session->getLoggedInUserGuid(); 38} 39 40/** 41 * Returns whether or not the user is currently logged in 42 * 43 * @return bool 44 */ 45function elgg_is_logged_in() { 46 return elgg()->session->isLoggedIn(); 47} 48 49/** 50 * Returns whether or not the viewer is currently logged in and an admin user. 51 * 52 * @return bool 53 */ 54function elgg_is_admin_logged_in() { 55 return elgg()->session->isAdminLoggedIn(); 56} 57 58/** 59 * Perform user authentication with a given username and password. 60 * 61 * @warning This returns an error message on failure. Use the identical operator to check 62 * for access: if (true === elgg_authenticate()) { ... }. 63 * 64 * 65 * @see login() 66 * 67 * @param string $username The username 68 * @param string $password The password 69 * 70 * @return true|string True or an error message on failure 71 * @internal 72 */ 73function elgg_authenticate($username, $password) { 74 $pam = new \ElggPAM('user'); 75 $credentials = ['username' => $username, 'password' => $password]; 76 $result = $pam->authenticate($credentials); 77 if (!$result) { 78 return $pam->getFailureMessage(); 79 } 80 return true; 81} 82 83/** 84 * Hook into the PAM system which accepts a username and password and attempts to authenticate 85 * it against a known user. 86 * 87 * @param array $credentials Associated array of credentials passed to 88 * Elgg's PAM system. This function expects 89 * 'username' and 'password' (cleartext). 90 * 91 * @return bool 92 * @throws LoginException 93 * @internal 94 */ 95function pam_auth_userpass(array $credentials = []) { 96 97 if (!isset($credentials['username']) || !isset($credentials['password'])) { 98 return false; 99 } 100 101 return elgg_call(ELGG_SHOW_DISABLED_ENTITIES, function() use ($credentials) { 102 $user = get_user_by_username($credentials['username']); 103 if (!$user) { 104 throw new \LoginException(_elgg_services()->translator->translate('LoginException:UsernameFailure')); 105 } 106 107 $password_svc = _elgg_services()->passwords; 108 $password = $credentials['password']; 109 $hash = $user->password_hash; 110 111 if (check_rate_limit_exceeded($user->guid)) { 112 throw new \LoginException(_elgg_services()->translator->translate('LoginException:AccountLocked')); 113 } 114 115 if (!$password_svc->verify($password, $hash)) { 116 log_login_failure($user->guid); 117 throw new \LoginException(_elgg_services()->translator->translate('LoginException:PasswordFailure')); 118 } 119 120 if ($password_svc->needsRehash($hash)) { 121 $password_svc->forcePasswordReset($user, $password); 122 } 123 124 return true; 125 }); 126} 127 128/** 129 * Log a failed login for $user_guid 130 * 131 * @param int $user_guid User GUID 132 * 133 * @return bool 134 */ 135function log_login_failure($user_guid) { 136 return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($user_guid) { 137 $user_guid = (int) $user_guid; 138 $user = get_entity($user_guid); 139 140 if (($user_guid) && ($user) && ($user instanceof \ElggUser)) { 141 $fails = (int) $user->getPrivateSetting("login_failures"); 142 $fails++; 143 144 $user->setPrivateSetting("login_failures", $fails); 145 $user->setPrivateSetting("login_failure_$fails", time()); 146 147 return true; 148 } 149 150 return false; 151 }); 152} 153 154/** 155 * Resets the fail login count for $user_guid 156 * 157 * @param int $user_guid User GUID 158 * 159 * @return bool true on success (success = user has no logged failed attempts) 160 */ 161function reset_login_failure_count($user_guid) { 162 return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($user_guid) { 163 $user_guid = (int) $user_guid; 164 165 $user = get_entity($user_guid); 166 167 if (($user_guid) && ($user) && ($user instanceof \ElggUser)) { 168 $fails = (int) $user->getPrivateSetting("login_failures"); 169 170 if ($fails) { 171 for ($n = 1; $n <= $fails; $n++) { 172 $user->removePrivateSetting("login_failure_$n"); 173 } 174 175 $user->removePrivateSetting("login_failures"); 176 177 return true; 178 } 179 180 // nothing to reset 181 return true; 182 } 183 184 return false; 185 }); 186} 187 188/** 189 * Checks if the rate limit of failed logins has been exceeded for $user_guid. 190 * 191 * @param int $user_guid User GUID 192 * 193 * @return bool on exceeded limit. 194 */ 195function check_rate_limit_exceeded($user_guid) { 196 return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($user_guid) { 197 // 5 failures in 5 minutes causes temporary block on logins 198 $limit = 5; 199 $user_guid = (int) $user_guid; 200 $user = get_entity($user_guid); 201 202 if (($user_guid) && ($user) && ($user instanceof \ElggUser)) { 203 $fails = (int) $user->getPrivateSetting("login_failures"); 204 if ($fails >= $limit) { 205 $cnt = 0; 206 $time = time(); 207 for ($n = $fails; $n > 0; $n--) { 208 $f = $user->getPrivateSetting("login_failure_$n"); 209 if ($f > $time - (60 * 5)) { 210 $cnt++; 211 } 212 213 if ($cnt == $limit) { 214 // Limit reached 215 return true; 216 } 217 } 218 } 219 } 220 221 return false; 222 }); 223} 224 225/** 226 * Set a cookie, but allow plugins to customize it first. 227 * 228 * To customize all cookies, register for the 'init:cookie', 'all' event. 229 * 230 * @param \ElggCookie $cookie The cookie that is being set 231 * @return bool 232 * @since 1.9 233 */ 234function elgg_set_cookie(\ElggCookie $cookie) { 235 return _elgg_services()->responseFactory->setCookie($cookie); 236} 237 238/** 239 * Logs in a specified \ElggUser. For standard registration, use in conjunction 240 * with elgg_authenticate. 241 * 242 * @see elgg_authenticate() 243 * 244 * @param \ElggUser $user A valid Elgg user object 245 * @param boolean $persistent Should this be a persistent login? 246 * 247 * @return true or throws exception 248 * @throws LoginException 249 */ 250function login(\ElggUser $user, $persistent = false) { 251 if ($user->isBanned()) { 252 throw new \LoginException(elgg_echo('LoginException:BannedUser')); 253 } 254 255 // give plugins a chance to reject the login of this user (no user in session!) 256 if (!elgg_trigger_before_event('login', 'user', $user)) { 257 throw new \LoginException(elgg_echo('LoginException:Unknown')); 258 } 259 260 if (!$user->isEnabled()) { 261 throw new \LoginException(elgg_echo('LoginException:DisabledUser')); 262 } 263 264 // #5933: set logged in user early so code in login event will be able to 265 // use elgg_get_logged_in_user_entity(). 266 $session = elgg()->session; 267 $session->setLoggedInUser($user); 268 269 // re-register at least the core language file for users with language other than site default 270 elgg()->translator->registerTranslations(\Elgg\Project\Paths::elgg() . 'languages/'); 271 272 // if remember me checked, set cookie with token and store hash(token) for user 273 if ($persistent) { 274 _elgg_services()->persistentLogin->makeLoginPersistent($user); 275 } 276 277 // User's privilege has been elevated, so change the session id (prevents session fixation) 278 $session->migrate(); 279 280 $user->setLastLogin(); 281 reset_login_failure_count($user->guid); 282 283 elgg_trigger_after_event('login', 'user', $user); 284 285 return true; 286} 287 288/** 289 * Log the current user out 290 * 291 * @return bool 292 */ 293function logout() { 294 $session = elgg()->session; 295 $user = $session->getLoggedInUser(); 296 if (!$user) { 297 return false; 298 } 299 300 if (!elgg_trigger_before_event('logout', 'user', $user)) { 301 return false; 302 } 303 304 _elgg_services()->persistentLogin->removePersistentLogin(); 305 306 // pass along any messages into new session 307 $old_msg = $session->get(SystemMessagesService::SESSION_KEY, []); 308 $session->invalidate(); 309 $session->set(SystemMessagesService::SESSION_KEY, $old_msg); 310 311 elgg_trigger_after_event('logout', 'user', $user); 312 313 return true; 314} 315 316/** 317 * Determine which URL the user should be forwarded to upon successful login 318 * 319 * @param \Elgg\Request $request Request object 320 * @param \ElggUser $user Logged in user 321 * @return string 322 * 323 * @internal 324 */ 325function _elgg_get_login_forward_url(\Elgg\Request $request, \ElggUser $user) { 326 327 $session = elgg_get_session(); 328 if ($session->has('last_forward_from')) { 329 $forward_url = $session->get('last_forward_from'); 330 $session->remove('last_forward_from'); 331 $forward_source = 'last_forward_from'; 332 } elseif ($request->getParam('returntoreferer')) { 333 $forward_url = REFERER; 334 $forward_source = 'return_to_referer'; 335 } else { 336 // forward to main index page 337 $forward_url = ''; 338 $forward_source = null; 339 } 340 341 $params = [ 342 'request' => $request, 343 'user' => $user, 344 'source' => $forward_source, 345 ]; 346 347 return elgg_trigger_plugin_hook('login:forward', 'user', $params, $forward_url); 348 349} 350 351/** 352 * Cleanup expired persistent login tokens from the database 353 * 354 * @param \Elgg\Hook $hook 'cron', 'daily' 355 * 356 * @return void 357 * @since 3.0 358 * @internal 359 */ 360function _elgg_session_cleanup_persistent_login(\Elgg\Hook $hook) { 361 362 $time = (int) $hook->getParam('time', time()); 363 _elgg_services()->persistentLogin->removeExpiredTokens($time); 364} 365 366/** 367 * Initializes the session and checks for the remember me cookie 368 * 369 * @param ServiceProvider $services Services 370 * @return bool 371 * @throws SecurityException 372 * @internal 373 */ 374function _elgg_session_boot(ServiceProvider $services) { 375 $services->timer->begin([__FUNCTION__]); 376 377 $session = $services->session; 378 $session->start(); 379 380 // test whether we have a user session 381 if ($session->has('guid')) { 382 /** @var ElggUser $user */ 383 $user = $services->entityTable->get($session->get('guid'), 'user'); 384 if (!$user) { 385 // OMG user has been deleted. 386 $session->invalidate(); 387 forward(''); 388 } 389 } else { 390 $user = $services->persistentLogin->bootSession(); 391 if ($user) { 392 $services->persistentLogin->updateTokenUsage($user); 393 } 394 } 395 396 if ($user) { 397 $session->setLoggedInUser($user); 398 $user->setLastAction(); 399 400 // logout a user with open session who has been banned 401 if ($user->isBanned()) { 402 logout(); 403 return false; 404 } 405 } 406 407 $services->timer->end([__FUNCTION__]); 408 return true; 409} 410 411/** 412 * @see \Elgg\Application::loadCore Do not do work here. Just register for events. 413 */ 414return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) { 415 register_pam_handler('pam_auth_userpass'); 416 417 $hooks->registerHandler('cron', 'daily', '_elgg_session_cleanup_persistent_login'); 418}; 419