Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/lib/sessions.php (4 issues)

1
<?php
2
3
use Elgg\SystemMessagesService;
4
use Elgg\Di\ServiceProvider;
5
6
/**
7
 * Elgg session management
8
 * Functions to manage logins
9
 *
10
 * @package    Elgg.Core
11
 * @subpackage Session
12
 */
13
14
/**
15
 * Gets Elgg's session object
16
 *
17
 * @return \ElggSession
18
 * @since 1.9
19
 */
20
function elgg_get_session() {
21 163
	return _elgg_services()->session;
22
}
23
24
/**
25
 * Return the current logged in user, or null if no user is logged in.
26
 *
27
 * @return \ElggUser|null
28
 */
29
function elgg_get_logged_in_user_entity() {
30 260
	return _elgg_services()->session->getLoggedInUser();
31
}
32
33
/**
34
 * Return the current logged in user by guid.
35
 *
36
 * @see elgg_get_logged_in_user_entity()
37
 * @return int
38
 */
39
function elgg_get_logged_in_user_guid() {
40 1255
	return _elgg_services()->session->getLoggedInUserGuid();
41
}
42
43
/**
44
 * Returns whether or not the user is currently logged in
45
 *
46
 * @return bool
47
 */
48
function elgg_is_logged_in() {
49 5389
	return _elgg_services()->session->isLoggedIn();
50
}
51
52
/**
53
 * Returns whether or not the viewer is currently logged in and an admin user.
54
 *
55
 * @return bool
56
 */
57
function elgg_is_admin_logged_in() {
58 39
	return _elgg_services()->session->isAdminLoggedIn();
59
}
60
61
/**
62
 * Check if the given user has full access.
63
 *
64
 * @todo: Will always return full access if the user is an admin.
65
 *
66
 * @param int $user_guid The user to check
67
 *
68
 * @return bool
69
 * @since 1.7.1
70
 */
71
function elgg_is_admin_user($user_guid) {
72
	
73
	$user_guid = (int) $user_guid;
74
75
	$entity = get_user($user_guid);
76
	if (!$entity) {
77
		return false;
78
	}
79
	
80
	return $entity->isAdmin();
81
}
82
83
/**
84
 * Perform user authentication with a given username and password.
85
 *
86
 * @warning This returns an error message on failure. Use the identical operator to check
87
 * for access: if (true === elgg_authenticate()) { ... }.
88
 *
89
 *
90
 * @see login
91
 *
92
 * @param string $username The username
93
 * @param string $password The password
94
 *
95
 * @return true|string True or an error message on failure
96
 * @access private
97
 */
98
function elgg_authenticate($username, $password) {
99 7
	$pam = new \ElggPAM('user');
100 7
	$credentials = ['username' => $username, 'password' => $password];
101 7
	$result = $pam->authenticate($credentials);
102 7
	if (!$result) {
103 2
		return $pam->getFailureMessage();
104
	}
105 5
	return true;
106
}
107
108
/**
109
 * Hook into the PAM system which accepts a username and password and attempts to authenticate
110
 * it against a known user.
111
 *
112
 * @param array $credentials Associated array of credentials passed to
113
 *                           Elgg's PAM system. This function expects
114
 *                           'username' and 'password' (cleartext).
115
 *
116
 * @return bool
117
 * @throws LoginException
118
 * @access private
119
 */
120
function pam_auth_userpass(array $credentials = []) {
121
122 9
	if (!isset($credentials['username']) || !isset($credentials['password'])) {
123 2
		return false;
124
	}
125
126 7
	$user = get_user_by_username($credentials['username']);
127 7
	if (!$user) {
128 1
		throw new \LoginException(_elgg_services()->translator->translate('LoginException:UsernameFailure'));
129
	}
130
131 6
	$password_svc = _elgg_services()->passwords;
132 6
	$password = $credentials['password'];
133 6
	$hash = $user->password_hash;
134
135 6
	if (check_rate_limit_exceeded($user->guid)) {
136
		throw new \LoginException(_elgg_services()->translator->translate('LoginException:AccountLocked'));
137
	}
138
139 6
	if (!$password_svc->verify($password, $hash)) {
140 1
		log_login_failure($user->guid);
141 1
		throw new \LoginException(_elgg_services()->translator->translate('LoginException:PasswordFailure'));
142
	}
143
144 5
	if ($password_svc->needsRehash($hash)) {
145
		$password_svc->forcePasswordReset($user, $password);
146
	}
147
148 5
	return true;
149
}
150
151
/**
152
 * Log a failed login for $user_guid
153
 *
154
 * @param int $user_guid User GUID
155
 *
156
 * @return bool
157
 */
158
function log_login_failure($user_guid) {
159 1
	$user_guid = (int) $user_guid;
160 1
	$user = get_entity($user_guid);
161
162 1
	if (($user_guid) && ($user) && ($user instanceof \ElggUser)) {
163 1
		$fails = (int) $user->getPrivateSetting("login_failures");
164 1
		$fails++;
165
166 1
		$user->setPrivateSetting("login_failures", $fails);
167 1
		$user->setPrivateSetting("login_failure_$fails", time());
168 1
		return true;
169
	}
170
171
	return false;
172
}
173
174
/**
175
 * Resets the fail login count for $user_guid
176
 *
177
 * @param int $user_guid User GUID
178
 *
179
 * @return bool true on success (success = user has no logged failed attempts)
180
 */
181
function reset_login_failure_count($user_guid) {
182 4
	$user_guid = (int) $user_guid;
183 4
	$user = get_entity($user_guid);
184
185 4
	if (($user_guid) && ($user) && ($user instanceof \ElggUser)) {
186 4
		$fails = (int) $user->getPrivateSetting("login_failures");
187
188 4
		if ($fails) {
189
			for ($n = 1; $n <= $fails; $n++) {
190
				$user->removePrivateSetting("login_failure_$n");
191
			}
192
193
			$user->removePrivateSetting("login_failures");
194
195
			return true;
196
		}
197
198
		// nothing to reset
199 4
		return true;
200
	}
201
202
	return false;
203
}
204
205
/**
206
 * Checks if the rate limit of failed logins has been exceeded for $user_guid.
207
 *
208
 * @param int $user_guid User GUID
209
 *
210
 * @return bool on exceeded limit.
211
 */
212
function check_rate_limit_exceeded($user_guid) {
213
	// 5 failures in 5 minutes causes temporary block on logins
214 6
	$limit = 5;
215 6
	$user_guid = (int) $user_guid;
216 6
	$user = get_entity($user_guid);
217
218 6
	if (($user_guid) && ($user) && ($user instanceof \ElggUser)) {
219 6
		$fails = (int) $user->getPrivateSetting("login_failures");
220 6
		if ($fails >= $limit) {
221
			$cnt = 0;
222
			$time = time();
223
			for ($n = $fails; $n > 0; $n--) {
224
				$f = $user->getPrivateSetting("login_failure_$n");
225
				if ($f > $time - (60 * 5)) {
226
					$cnt++;
227
				}
228
229
				if ($cnt == $limit) {
230
					// Limit reached
231
					return true;
232
				}
233
			}
234
		}
235
	}
236
237 6
	return false;
238
}
239
240
/**
241
 * Set a cookie, but allow plugins to customize it first.
242
 *
243
 * To customize all cookies, register for the 'init:cookie', 'all' event.
244
 *
245
 * @param \ElggCookie $cookie The cookie that is being set
246
 * @return bool
247
 * @since 1.9
248
 */
249
function elgg_set_cookie(\ElggCookie $cookie) {
250
	if (elgg_trigger_event('init:cookie', $cookie->name, $cookie)) {
251
		return setcookie($cookie->name, $cookie->value, $cookie->expire, $cookie->path,
252
						$cookie->domain, $cookie->secure, $cookie->httpOnly);
253
	}
254
	return false;
255
}
256
257
/**
258
 * Logs in a specified \ElggUser. For standard registration, use in conjunction
259
 * with elgg_authenticate.
260
 *
261
 * @see elgg_authenticate
262
 *
263
 * @param \ElggUser $user       A valid Elgg user object
264
 * @param boolean   $persistent Should this be a persistent login?
265
 *
266
 * @return true or throws exception
267
 * @throws LoginException
268
 */
269
function login(\ElggUser $user, $persistent = false) {
270 6
	if ($user->isBanned()) {
271 1
		throw new \LoginException(elgg_echo('LoginException:BannedUser'));
272
	}
273
274 5
	$session = _elgg_services()->session;
275
276
	// give plugins a chance to reject the login of this user (no user in session!)
277 5
	if (!elgg_trigger_before_event('login', 'user', $user)) {
278 1
		throw new \LoginException(elgg_echo('LoginException:Unknown'));
279
	}
280
281
	// #5933: set logged in user early so code in login event will be able to
282
	// use elgg_get_logged_in_user_entity().
283 4
	$session->setLoggedInUser($user);
284
285
	// if remember me checked, set cookie with token and store hash(token) for user
286 4
	if ($persistent) {
287
		_elgg_services()->persistentLogin->makeLoginPersistent($user);
288
	}
289
290
	// User's privilege has been elevated, so change the session id (prevents session fixation)
291 4
	$session->migrate();
292
293 4
	$user->setLastLogin();
294 4
	reset_login_failure_count($user->guid);
295
296 4
	elgg_trigger_after_event('login', 'user', $user);
0 ignored issues
show
$user of type ElggUser is incompatible with the type string expected by parameter $object of elgg_trigger_after_event(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

296
	elgg_trigger_after_event('login', 'user', /** @scrutinizer ignore-type */ $user);
Loading history...
297
298 4
	return true;
299
}
300
301
/**
302
 * Log the current user out
303
 *
304
 * @return bool
305
 */
306
function logout() {
307
	$session = _elgg_services()->session;
308
	$user = $session->getLoggedInUser();
309
	if (!$user) {
310
		return false;
311
	}
312
313
	if (!elgg_trigger_before_event('logout', 'user', $user)) {
314
		return false;
315
	}
316
317
	_elgg_services()->persistentLogin->removePersistentLogin();
318
319
	// pass along any messages into new session
320
	$old_msg = $session->get(SystemMessagesService::SESSION_KEY, []);
321
	$session->invalidate();
322
	$session->set(SystemMessagesService::SESSION_KEY, $old_msg);
323
324
	elgg_trigger_after_event('logout', 'user', $user);
0 ignored issues
show
$user of type ElggUser is incompatible with the type string expected by parameter $object of elgg_trigger_after_event(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
	elgg_trigger_after_event('logout', 'user', /** @scrutinizer ignore-type */ $user);
Loading history...
325
326
	return true;
327
}
328
329
/**
330
 * Initializes the session and checks for the remember me cookie
331
 *
332
 * @param ServiceProvider $services Services
333
 * @return bool
334
 * @throws SecurityException
335
 * @access private
336
 */
337
function _elgg_session_boot(ServiceProvider $services) {
338 4777
	$services->timer->begin([__FUNCTION__]);
339
340 4777
	$session = $services->session;
341 4777
	$session->start();
342
343
	// test whether we have a user session
344 4777
	if ($session->has('guid')) {
345
		/** @var ElggUser $user */
346
		$user = $services->entityTable->get($session->get('guid'), 'user');
347
		if (!$user) {
348
			// OMG user has been deleted.
349
			$session->invalidate();
350
			forward('');
351
		}
352
353
		$services->persistentLogin->replaceLegacyToken($user);
354
	} else {
355 4777
		$user = $services->persistentLogin->bootSession();
0 ignored issues
show
Are you sure the assignment to $user is correct as $services->persistentLogin->bootSession() targeting Elgg\PersistentLoginService::bootSession() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
356
	}
357
358 4777
	if ($user) {
359
		$session->setLoggedInUser($user);
0 ignored issues
show
$user of type void is incompatible with the type ElggUser expected by parameter $user of ElggSession::setLoggedInUser(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

359
		$session->setLoggedInUser(/** @scrutinizer ignore-type */ $user);
Loading history...
360
		$user->setLastAction();
361
362
		// logout a user with open session who has been banned
363
		if ($user->isBanned()) {
364
			logout();
365
			return false;
366
		}
367
	}
368
369 4777
	$services->timer->end([__FUNCTION__]);
370 4777
	return true;
371
}
372
373
/**
374
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
375
 */
376
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
377 18
	register_pam_handler('pam_auth_userpass');
378
};
379