1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace phpMyFAQ\User; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Manages authentication process using PHP sessions. |
7
|
|
|
* |
8
|
|
|
* The CurrentUser class is an extension of the User class. It provides methods |
9
|
|
|
* manage user authentication using multiple database accesses. There are three |
10
|
|
|
* ways of making a new current user object, using the login(), getFromSession(), |
11
|
|
|
* getFromCookie() or manually. login(), getFromSession() and getFromCookie() may |
12
|
|
|
* be combined. |
13
|
|
|
* |
14
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public License, |
15
|
|
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can |
16
|
|
|
* obtain one at http://mozilla.org/MPL/2.0/. |
17
|
|
|
* |
18
|
|
|
* @package phpMyFAQ |
19
|
|
|
* @author Lars Tiedemann <[email protected]> |
20
|
|
|
* @author Thorsten Rinne <[email protected]> |
21
|
|
|
* @copyright 2005-2019 phpMyFAQ Team |
22
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
23
|
|
|
* @link https://www.phpmyfaq.de |
24
|
|
|
* @since 2005-09-28 |
25
|
|
|
*/ |
26
|
|
|
|
27
|
|
|
use phpMyFAQ\Configuration; |
28
|
|
|
use phpMyFAQ\Db; |
29
|
|
|
use phpMyFAQ\Session; |
30
|
|
|
use phpMyFAQ\User; |
31
|
|
|
|
32
|
|
|
if (!defined('IS_VALID_PHPMYFAQ')) { |
33
|
|
|
exit(); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
/* user defined constants */ |
37
|
|
|
define('SESSION_CURRENT_USER', 'CURRENT_USER'); |
38
|
|
|
define('SESSION_ID_TIMESTAMP', 'SESSION_TIMESTAMP'); |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Class CurrentUser |
42
|
|
|
* |
43
|
|
|
* @package phpMyFAQ |
44
|
|
|
* @author Lars Tiedemann <[email protected]> |
45
|
|
|
* @author Thorsten Rinne <[email protected]> |
46
|
|
|
* @copyright 2005-2019 phpMyFAQ Team |
47
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
48
|
|
|
* @link https://www.phpmyfaq.de |
49
|
|
|
* @since 2005-09-28 |
50
|
|
|
*/ |
51
|
|
|
class CurrentUser extends User |
52
|
|
|
{ |
53
|
|
|
/** |
54
|
|
|
* true if CurrentUser is logged in, otherwise false. |
55
|
|
|
* @var bool |
56
|
|
|
*/ |
57
|
|
|
private $loggedIn = false; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Specifies the timeout for the session in minutes. If the session ID was |
61
|
|
|
* not updated for the last $this->_sessionTimeout minutes, the CurrentUser |
62
|
|
|
* will be logged out automatically if no cookie was set. |
63
|
|
|
* @var int |
64
|
|
|
*/ |
65
|
|
|
private $sessionTimeout = PMF_AUTH_TIMEOUT; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* The Session class object |
69
|
|
|
* @var Session |
70
|
|
|
*/ |
71
|
|
|
private $session; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Specifies the timeout for the session-ID in minutes. If the session ID |
75
|
|
|
* was not updated for the last $this->_sessionIdTimeout minutes, it will |
76
|
|
|
* be updated. If set to 0, the session ID will be updated on every click. |
77
|
|
|
* The session ID timeout must not be greater than Session timeout. |
78
|
|
|
* @var int |
79
|
|
|
*/ |
80
|
|
|
private $sessionIdTimeout = 1; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* LDAP configuration if available. |
84
|
|
|
* @var array |
85
|
|
|
*/ |
86
|
|
|
private $ldapConfig = []; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Remember me activated or deactivated. |
90
|
|
|
* @var bool |
91
|
|
|
*/ |
92
|
|
|
private $rememberMe = false; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Login successful or auth failure: |
96
|
|
|
* 1 -> success |
97
|
|
|
* 0 -> failure. |
98
|
|
|
* @var int |
99
|
|
|
*/ |
100
|
|
|
private $loginState = 1; |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Number of failed login attempts |
104
|
|
|
* @var int |
105
|
|
|
*/ |
106
|
|
|
private $loginAttempts = 0; |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Lockout time in seconds |
110
|
|
|
* @var integer |
111
|
|
|
*/ |
112
|
|
|
private $lockoutTime = 600; |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Constructor. |
116
|
|
|
* @param Configuration $config |
117
|
|
|
*/ |
118
|
|
|
public function __construct(Configuration $config) |
119
|
|
|
{ |
120
|
|
|
parent::__construct($config); |
121
|
|
|
$this->ldapConfig = $config->getLdapConfig(); |
122
|
|
|
$this->session = new Session($config); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Checks the given login and password in all auth-objects. Returns true |
127
|
|
|
* on success, otherwise false. Raises errors that can be checked using |
128
|
|
|
* the error() method. On success, the CurrentUser instance will be |
129
|
|
|
* labeled as logged in. The name of the successful auth container will |
130
|
|
|
* be stored in the user table. A new auth object may be added by using |
131
|
|
|
* addAuth() method. The given password must not be encrypted, since the |
132
|
|
|
* auth object takes care about the encryption method. |
133
|
|
|
* |
134
|
|
|
* @param string $login Login name |
135
|
|
|
* @param string $password Password |
136
|
|
|
* |
137
|
|
|
* @return bool |
138
|
|
|
*/ |
139
|
|
|
public function login(string $login, string $password): bool |
140
|
|
|
{ |
141
|
|
|
$optData = []; |
142
|
|
|
$loginError = $passwordError = $count = 0; |
143
|
|
|
|
144
|
|
|
// First check for brute force attack |
145
|
|
|
$this->getUserByLogin($login); |
146
|
|
|
if ($this->isFailedLastLoginAttempt()) { |
147
|
|
|
$this->errors[] = parent::ERROR_USER_TOO_MANY_FAILED_LOGINS; |
148
|
|
|
return false; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
// Additional code for LDAP: user\\domain |
152
|
|
|
if ($this->config->get('ldap.ldapSupport') && $this->config->get('ldap.ldap_use_domain_prefix') && |
153
|
|
|
'' !== $password) { |
154
|
|
|
// If LDAP configuration and ldap_use_domain_prefix is true |
155
|
|
|
// and LDAP credentials are provided (password is not empty) |
156
|
|
|
if (($pos = strpos($login, '\\')) !== false) { |
157
|
|
|
if ($pos !== 0) { |
158
|
|
|
$optData['domain'] = substr($login, 0, $pos); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$login = substr($login, $pos + 1); |
162
|
|
|
} |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// Additional code for SSO |
166
|
|
|
if ($this->config->get('security.ssoSupport') && isset($_SERVER['REMOTE_USER']) && '' === $password) { |
167
|
|
|
// if SSO configuration is enabled, REMOTE_USER is provided and we try to login using SSO (no password) |
168
|
|
View Code Duplication |
if (($pos = strpos($login, '@')) !== false) { |
169
|
|
|
if ($pos !== 0) { |
170
|
|
|
$login = substr($login, 0, $pos); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
View Code Duplication |
if (($pos = strpos($login, '\\')) !== false) { |
174
|
|
|
if ($pos !== 0) { |
175
|
|
|
$login = substr($login, $pos + 1); |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// authenticate user by login and password |
181
|
|
|
foreach ($this->authContainer as $name => $auth) { |
182
|
|
|
++$count; |
183
|
|
|
|
184
|
|
|
// $auth is an invalid Auth object, so continue |
185
|
|
|
if (!$this->checkAuth($auth)) { |
186
|
|
|
--$count; |
187
|
|
|
continue; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// $login does not exist, so continue |
191
|
|
|
if (!$auth->checkLogin($login, $optData)) { |
192
|
|
|
++$loginError; |
193
|
|
|
continue; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// $login exists, but $pass is incorrect, so stop! |
197
|
|
|
if (!$auth->checkPassword($login, $password, $optData)) { |
198
|
|
|
++$passwordError; |
199
|
|
|
// Don't stop, as other auth method could work: |
200
|
|
|
continue; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// but hey, this must be a valid match, so get user object |
204
|
|
|
$this->getUserByLogin($login); |
205
|
|
|
$this->loggedIn = true; |
206
|
|
|
$this->updateSessionId(true); |
207
|
|
|
$this->saveToSession(); |
208
|
|
|
$this->saveCrsfTokenToSession(); |
209
|
|
|
|
210
|
|
|
// save remember me cookie if set |
211
|
|
|
if (true === $this->rememberMe) { |
212
|
|
|
$rememberMe = sha1(session_id()); |
213
|
|
|
$this->setRememberMe($rememberMe); |
214
|
|
|
$this->session->setCookie( |
215
|
|
|
Session::PMF_COOKIE_NAME_REMEMBERME, |
216
|
|
|
$rememberMe, |
217
|
|
|
$_SERVER['REQUEST_TIME'] + PMF_REMEMBERME_EXPIRED_TIME |
218
|
|
|
); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// remember the auth container for administration |
222
|
|
|
$update = sprintf(" |
223
|
|
|
UPDATE |
224
|
|
|
%sfaquser |
225
|
|
|
SET |
226
|
|
|
auth_source = '%s' |
227
|
|
|
WHERE |
228
|
|
|
user_id = %d", |
229
|
|
|
Db::getTablePrefix(), |
230
|
|
|
$this->config->getDb()->escape($name), |
231
|
|
|
$this->getUserId() |
232
|
|
|
); |
233
|
|
|
$result = $this->config->getDb()->query($update); |
234
|
|
|
if (!$result) { |
235
|
|
|
$this->setSuccess(false); |
236
|
|
|
return false; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
// Login successful |
240
|
|
|
$this->setSuccess(true); |
241
|
|
|
return true; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
// raise errors and return false |
245
|
|
|
if ($loginError === $count) { |
246
|
|
|
$this->setSuccess(false); |
247
|
|
|
$this->errors[] = parent::ERROR_USER_INCORRECT_LOGIN; |
248
|
|
|
} |
249
|
|
|
if ($passwordError > 0) { |
250
|
|
|
$this->getUserByLogin($login); |
251
|
|
|
$this->setLoginAttempt(); |
252
|
|
|
$this->errors[] = parent::ERROR_USER_INCORRECT_PASSWORD; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return false; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Returns true if CurrentUser is logged in, otherwise false. |
260
|
|
|
* @return bool |
261
|
|
|
*/ |
262
|
|
|
public function isLoggedIn(): bool |
263
|
|
|
{ |
264
|
|
|
return $this->loggedIn; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Returns false if the CurrentUser object stored in the |
269
|
|
|
* session is valid and not timed out. There are two |
270
|
|
|
* parameters for session timeouts: $this->_sessionTimeout |
271
|
|
|
* and $this->_sessionIdTimeout. |
272
|
|
|
* @return bool |
273
|
|
|
*/ |
274
|
|
|
public function sessionIsTimedOut(): bool |
275
|
|
|
{ |
276
|
|
|
if ($this->sessionTimeout <= $this->sessionAge()) { |
277
|
|
|
return true; |
278
|
|
|
} |
279
|
|
|
return false; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Returns false if the session-ID is not timed out. |
284
|
|
|
* |
285
|
|
|
* @return bool |
286
|
|
|
*/ |
287
|
|
|
public function sessionIdIsTimedOut(): bool |
288
|
|
|
{ |
289
|
|
|
if ($this->sessionIdTimeout <= $this->sessionAge()) { |
290
|
|
|
return true; |
291
|
|
|
} |
292
|
|
|
return false; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* Returns the age of the current session-ID in minutes. |
297
|
|
|
* |
298
|
|
|
* @return float |
299
|
|
|
*/ |
300
|
|
|
public function sessionAge(): float |
301
|
|
|
{ |
302
|
|
|
if (!isset($_SESSION[SESSION_ID_TIMESTAMP])) { |
303
|
|
|
return 0; |
304
|
|
|
} |
305
|
|
|
return ($_SERVER['REQUEST_TIME'] - $_SESSION[SESSION_ID_TIMESTAMP])/60; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Returns an associative array with session information stored |
310
|
|
|
* in the user table. The array has the following keys: |
311
|
|
|
* session_id, session_timestamp and ip. |
312
|
|
|
* @return array |
313
|
|
|
*/ |
314
|
|
|
public function getSessionInfo(): array |
315
|
|
|
{ |
316
|
|
|
$select = sprintf(' |
317
|
|
|
SELECT |
318
|
|
|
session_id, |
319
|
|
|
session_timestamp, |
320
|
|
|
ip, |
321
|
|
|
success |
322
|
|
|
FROM |
323
|
|
|
%sfaquser |
324
|
|
|
WHERE |
325
|
|
|
user_id = %d', |
326
|
|
|
Db::getTablePrefix(), |
327
|
|
|
$this->getUserId() |
328
|
|
|
); |
329
|
|
|
|
330
|
|
|
$res = $this->config->getDb()->query($select); |
331
|
|
|
if (!$res or $this->config->getDb()->numRows($res) != 1) { |
332
|
|
|
return []; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
return $this->config->getDb()->fetchArray($res); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Updates the session-ID, does not care about time outs. |
340
|
|
|
* Stores session information in the user table: session_id, |
341
|
|
|
* session_timestamp and ip. |
342
|
|
|
* Optionally it should update the 'last login' time. |
343
|
|
|
* Returns true on success, otherwise false. |
344
|
|
|
* |
345
|
|
|
* @param bool $updateLastLogin Update the last login time? |
346
|
|
|
* |
347
|
|
|
* @return bool |
348
|
|
|
*/ |
349
|
|
|
public function updateSessionId(bool $updateLastLogin = false): bool |
350
|
|
|
{ |
351
|
|
|
// renew the session-ID |
352
|
|
|
$oldSessionId = session_id(); |
353
|
|
|
if (session_regenerate_id(true)) { |
354
|
|
|
$sessionPath = session_save_path(); |
355
|
|
|
if (strpos($sessionPath, ';') !== false) { |
356
|
|
|
$sessionPath = substr($sessionPath, strpos($sessionPath, ';') + 1); |
357
|
|
|
} |
358
|
|
|
$sessionFilename = $sessionPath.'/sess_'.$oldSessionId; |
359
|
|
|
if (@file_exists($sessionFilename)) { |
360
|
|
|
@unlink($sessionFilename); |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
// store session-ID age |
364
|
|
|
$_SESSION[SESSION_ID_TIMESTAMP] = $_SERVER['REQUEST_TIME']; |
365
|
|
|
// save session information in user table |
366
|
|
|
$update = sprintf(" |
367
|
|
|
UPDATE |
368
|
|
|
%sfaquser |
369
|
|
|
SET |
370
|
|
|
session_id = '%s', |
371
|
|
|
session_timestamp = %d, |
372
|
|
|
%s |
373
|
|
|
ip = '%s' |
374
|
|
|
WHERE |
375
|
|
|
user_id = %d", |
376
|
|
|
Db::getTablePrefix(), |
377
|
|
|
session_id(), |
378
|
|
|
$_SERVER['REQUEST_TIME'], |
379
|
|
|
$updateLastLogin ? "last_login = '".date('YmdHis', $_SERVER['REQUEST_TIME'])."'," : '', |
380
|
|
|
$_SERVER['REMOTE_ADDR'], |
381
|
|
|
$this->getUserId() |
382
|
|
|
); |
383
|
|
|
|
384
|
|
|
$res = $this->config->getDb()->query($update); |
385
|
|
|
if (!$res) { |
386
|
|
|
$this->errors[] = $this->config->getDb()->error(); |
387
|
|
|
|
388
|
|
|
return false; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
return true; |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Saves the CurrentUser into the session. This method |
396
|
|
|
* may be called after a successful login. |
397
|
|
|
* @return void |
398
|
|
|
*/ |
399
|
|
|
public function saveToSession() |
400
|
|
|
{ |
401
|
|
|
$_SESSION[SESSION_CURRENT_USER] = $this->getUserId(); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* Deletes the CurrentUser from the session. The user |
406
|
|
|
* will be logged out. Return true on success, otherwise false. |
407
|
|
|
* @param bool $deleteCookie |
408
|
|
|
* @return bool |
409
|
|
|
*/ |
410
|
|
|
public function deleteFromSession(bool $deleteCookie = false): bool |
411
|
|
|
{ |
412
|
|
|
// delete CSRF Token |
413
|
|
|
$this->deleteCsrfTokenFromSession(); |
414
|
|
|
|
415
|
|
|
// delete CurrentUser object from session |
416
|
|
|
$_SESSION[SESSION_CURRENT_USER] = null; |
417
|
|
|
unset($_SESSION[SESSION_CURRENT_USER]); |
418
|
|
|
|
419
|
|
|
// log CurrentUser out |
420
|
|
|
$this->loggedIn = false; |
421
|
|
|
|
422
|
|
|
// delete session-ID |
423
|
|
|
$update = sprintf(' |
424
|
|
|
UPDATE |
425
|
|
|
%sfaquser |
426
|
|
|
SET |
427
|
|
|
session_id = NULL |
428
|
|
|
%s |
429
|
|
|
WHERE |
430
|
|
|
user_id = %d', |
431
|
|
|
Db::getTablePrefix(), |
432
|
|
|
$deleteCookie ? ', remember_me = NULL' : '', |
433
|
|
|
$this->getUserId() |
434
|
|
|
); |
435
|
|
|
|
436
|
|
|
$res = $this->config->getDb()->query($update); |
437
|
|
|
|
438
|
|
|
if (!$res) { |
439
|
|
|
$this->errors[] = $this->config->getDb()->error(); |
440
|
|
|
|
441
|
|
|
return false; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
if ($deleteCookie) { |
445
|
|
|
$this->session->setCookie(Session::PMF_COOKIE_NAME_REMEMBERME); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
session_destroy(); |
449
|
|
|
|
450
|
|
|
return true; |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* This static method returns a valid CurrentUser object if there is one |
455
|
|
|
* in the session that is not timed out. The session-ID is updated if |
456
|
|
|
* necessary. The CurrentUser will be removed from the session, if it is |
457
|
|
|
* timed out. If there is no valid CurrentUser in the session or the |
458
|
|
|
* session is timed out, null will be returned. If the session data is |
459
|
|
|
* correct, but there is no user found in the user table, false will be |
460
|
|
|
* returned. On success, a valid CurrentUser object is returned. |
461
|
|
|
* @static |
462
|
|
|
* @param Configuration $config |
463
|
|
|
* @return null|CurrentUser |
464
|
|
|
*/ |
465
|
|
|
public static function getFromSession(Configuration $config) |
466
|
|
|
{ |
467
|
|
|
// there is no valid user object in session |
468
|
|
|
if (!isset($_SESSION[SESSION_CURRENT_USER]) || !isset($_SESSION[SESSION_ID_TIMESTAMP])) { |
469
|
|
|
return null; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
// create a new CurrentUser object |
473
|
|
|
$user = new self($config); |
474
|
|
|
$user->getUserById($_SESSION[SESSION_CURRENT_USER]); |
475
|
|
|
|
476
|
|
|
// user object is timed out |
477
|
|
|
if ($user->sessionIsTimedOut()) { |
478
|
|
|
$user->deleteFromSession(); |
479
|
|
|
$user->errors[] = 'Session timed out.'; |
480
|
|
|
|
481
|
|
|
return null; |
482
|
|
|
} |
483
|
|
|
// session-id not found in user table |
484
|
|
|
$session_info = $user->getSessionInfo(); |
485
|
|
|
$session_id = (isset($session_info['session_id']) ? $session_info['session_id'] : ''); |
486
|
|
|
if ($session_id == '' || $session_id != session_id()) { |
487
|
|
|
return null; |
488
|
|
|
} |
489
|
|
|
// check ip |
490
|
|
|
if ($config->get('security.ipCheck') && |
491
|
|
|
$session_info['ip'] != $_SERVER['REMOTE_ADDR']) { |
492
|
|
|
return null; |
493
|
|
|
} |
494
|
|
|
// session-id needs to be updated |
495
|
|
|
if ($user->sessionIdIsTimedOut()) { |
496
|
|
|
$user->updateSessionId(); |
497
|
|
|
} |
498
|
|
|
// user is now logged in |
499
|
|
|
$user->loggedIn = true; |
500
|
|
|
// save current user to session and return the instance |
501
|
|
|
$user->saveToSession(); |
502
|
|
|
|
503
|
|
|
return $user; |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
/** |
507
|
|
|
* This static method returns a valid CurrentUser object if there is one |
508
|
|
|
* in the cookie that is not timed out. The session-ID is updated then. |
509
|
|
|
* The CurrentUser will be removed from the session, if it is |
510
|
|
|
* timed out. If there is no valid CurrentUser in the cookie or the |
511
|
|
|
* cookie is timed out, null will be returned. If the cookie is correct, |
512
|
|
|
* but there is no user found in the user table, false will be returned. |
513
|
|
|
* On success, a valid CurrentUser object is returned. |
514
|
|
|
* |
515
|
|
|
* @static |
516
|
|
|
* |
517
|
|
|
* @param Configuration $config |
518
|
|
|
* |
519
|
|
|
* @return null|CurrentUser |
520
|
|
|
*/ |
521
|
|
|
public static function getFromCookie(Configuration $config) |
522
|
|
|
{ |
523
|
|
|
if (!isset($_COOKIE[Session::PMF_COOKIE_NAME_REMEMBERME])) { |
524
|
|
|
return null; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
// create a new CurrentUser object |
528
|
|
|
$user = new self($config); |
529
|
|
|
$user->getUserByCookie($_COOKIE[Session::PMF_COOKIE_NAME_REMEMBERME]); |
530
|
|
|
|
531
|
|
|
if (-1 === $user->getUserId()) { |
532
|
|
|
return null; |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
// sessionId needs to be updated |
536
|
|
|
$user->updateSessionId(true); |
537
|
|
|
// user is now logged in |
538
|
|
|
$user->loggedIn = true; |
539
|
|
|
// save current user to session and return the instance |
540
|
|
|
$user->saveToSession(); |
541
|
|
|
// add CSRF token to session |
542
|
|
|
$user->saveCrsfTokenToSession(); |
543
|
|
|
|
544
|
|
|
return $user; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Sets the number of minutes when the current user stored in |
549
|
|
|
* the session gets invalid. |
550
|
|
|
* |
551
|
|
|
* @param float $timeout Timeout |
552
|
|
|
*/ |
553
|
|
|
public function setSessionTimeout($timeout) |
554
|
|
|
{ |
555
|
|
|
$this->sessionTimeout = abs($timeout); |
|
|
|
|
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* Sets the number of minutes when the session-ID needs to be |
560
|
|
|
* updated. By setting the session-ID timeout to zero, the |
561
|
|
|
* session-ID will be updated on each click. |
562
|
|
|
* |
563
|
|
|
* @param float $timeout Timeout |
564
|
|
|
*/ |
565
|
|
|
public function setSessionIdTimeout($timeout) |
566
|
|
|
{ |
567
|
|
|
$this->sessionIdTimeout = abs($timeout); |
|
|
|
|
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
/** |
571
|
|
|
* Enables the remember me decision. |
572
|
|
|
*/ |
573
|
|
|
public function enableRememberMe() |
574
|
|
|
{ |
575
|
|
|
$this->rememberMe = true; |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Saves remember me token in the database. |
580
|
|
|
* |
581
|
|
|
* @param string $rememberMe |
582
|
|
|
* |
583
|
|
|
* @return bool |
584
|
|
|
*/ |
585
|
|
|
protected function setRememberMe($rememberMe) |
586
|
|
|
{ |
587
|
|
|
$update = sprintf(" |
588
|
|
|
UPDATE |
589
|
|
|
%sfaquser |
590
|
|
|
SET |
591
|
|
|
remember_me = '%s' |
592
|
|
|
WHERE |
593
|
|
|
user_id = %d", |
594
|
|
|
Db::getTablePrefix(), |
595
|
|
|
$this->config->getDb()->escape($rememberMe), |
596
|
|
|
$this->getUserId() |
597
|
|
|
); |
598
|
|
|
|
599
|
|
|
return $this->config->getDb()->query($update); |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* Sets login success/failure. |
604
|
|
|
* |
605
|
|
|
* @param bool $success |
606
|
|
|
* |
607
|
|
|
* @return bool |
608
|
|
|
*/ |
609
|
|
|
protected function setSuccess($success) |
610
|
|
|
{ |
611
|
|
|
$this->loginState = (int)$success; |
612
|
|
|
$this->loginAttempts = 0; |
613
|
|
|
|
614
|
|
|
$update = sprintf(' |
615
|
|
|
UPDATE |
616
|
|
|
%sfaquser |
617
|
|
|
SET |
618
|
|
|
success = %d, |
619
|
|
|
login_attempts = %d |
620
|
|
|
WHERE |
621
|
|
|
user_id = %d', |
622
|
|
|
Db::getTablePrefix(), |
623
|
|
|
$this->loginState, |
624
|
|
|
$this->loginAttempts, |
625
|
|
|
$this->getUserId() |
626
|
|
|
); |
627
|
|
|
|
628
|
|
|
return $this->config->getDb()->query($update); |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
/** |
632
|
|
|
* Sets IP and session timestamp plus lockout time, success flag to |
633
|
|
|
* false. |
634
|
|
|
* |
635
|
|
|
* @return mixed |
636
|
|
|
*/ |
637
|
|
View Code Duplication |
protected function setLoginAttempt() |
|
|
|
|
638
|
|
|
{ |
639
|
|
|
$this->loginAttempts++; |
640
|
|
|
|
641
|
|
|
$update = sprintf(" |
642
|
|
|
UPDATE |
643
|
|
|
%sfaquser |
644
|
|
|
SET |
645
|
|
|
session_timestamp ='%s', |
646
|
|
|
ip = '%s', |
647
|
|
|
success = 0, |
648
|
|
|
login_attempts = login_attempts + 1 |
649
|
|
|
WHERE |
650
|
|
|
user_id = %d", |
651
|
|
|
Db::getTablePrefix(), |
652
|
|
|
$_SERVER['REQUEST_TIME'], |
653
|
|
|
$_SERVER['REMOTE_ADDR'], |
654
|
|
|
$this->getUserId() |
655
|
|
|
); |
656
|
|
|
|
657
|
|
|
return $this->config->getDb()->query($update); |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
/** |
661
|
|
|
* Checks if the last login attempt from current user failed. |
662
|
|
|
* |
663
|
|
|
* @return bool |
664
|
|
|
*/ |
665
|
|
|
protected function isFailedLastLoginAttempt() |
666
|
|
|
{ |
667
|
|
|
$select = sprintf(" |
668
|
|
|
SELECT |
669
|
|
|
session_timestamp, |
670
|
|
|
ip, |
671
|
|
|
success, |
672
|
|
|
login_attempts |
673
|
|
|
FROM |
674
|
|
|
%sfaquser |
675
|
|
|
WHERE |
676
|
|
|
user_id = %d |
677
|
|
|
AND |
678
|
|
|
('%d' - session_timestamp) <= %d |
679
|
|
|
AND |
680
|
|
|
ip = '%s' |
681
|
|
|
AND |
682
|
|
|
success = 0 |
683
|
|
|
AND |
684
|
|
|
login_attempts > 5", |
685
|
|
|
Db::getTablePrefix(), |
686
|
|
|
$this->getUserId(), |
687
|
|
|
$_SERVER['REQUEST_TIME'], |
688
|
|
|
$this->lockoutTime, |
689
|
|
|
$_SERVER['REMOTE_ADDR'] |
690
|
|
|
); |
691
|
|
|
|
692
|
|
|
$result = $this->config->getDb()->query($select); |
693
|
|
|
if ($this->config->getDb()->numRows($result) !== 0) { |
694
|
|
|
return true; |
695
|
|
|
} else { |
696
|
|
|
return false; |
697
|
|
|
} |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
/** |
701
|
|
|
* Returns the CSRF token from session. |
702
|
|
|
* |
703
|
|
|
* @return string |
704
|
|
|
*/ |
705
|
|
|
public function getCsrfTokenFromSession() |
706
|
|
|
{ |
707
|
|
|
return $_SESSION['phpmyfaq_csrf_token']; |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
/** |
711
|
|
|
* Save CSRF token to session. |
712
|
|
|
*/ |
713
|
|
|
public function saveCrsfTokenToSession() |
714
|
|
|
{ |
715
|
|
|
if (!isset($_SESSION['phpmyfaq_csrf_token'])) { |
716
|
|
|
$_SESSION['phpmyfaq_csrf_token'] = $this->createCsrfToken(); |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
/** |
721
|
|
|
* Deletes CSRF token from session. |
722
|
|
|
*/ |
723
|
|
|
protected function deleteCsrfTokenFromSession() |
724
|
|
|
{ |
725
|
|
|
unset($_SESSION['phpmyfaq_csrf_token']); |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
/** |
729
|
|
|
* Creates a CSRF token. |
730
|
|
|
* |
731
|
|
|
* @return string |
732
|
|
|
*/ |
733
|
|
|
private function createCsrfToken() |
734
|
|
|
{ |
735
|
|
|
return sha1(microtime().$this->getLogin()); |
736
|
|
|
} |
737
|
|
|
} |
738
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.