1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\User; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
use RuntimeException; |
7
|
|
|
|
8
|
|
|
// From PSR-3 |
9
|
|
|
use Psr\Log\LoggerAwareInterface; |
10
|
|
|
use Psr\Log\LoggerAwareTrait; |
11
|
|
|
|
12
|
|
|
// From 'charcoal-factory' |
13
|
|
|
use Charcoal\Factory\FactoryInterface; |
14
|
|
|
|
15
|
|
|
// From 'charcoal-user' |
16
|
|
|
use Charcoal\User\Access\AuthenticatableInterface; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* The base Authenticator service |
20
|
|
|
* |
21
|
|
|
* Helps with user authentication / login. |
22
|
|
|
* |
23
|
|
|
* ## Constructor dependencies |
24
|
|
|
* |
25
|
|
|
* Constructor dependencies are passed as an array of `key=>value` pair. |
26
|
|
|
* The required dependencies are: |
27
|
|
|
* |
28
|
|
|
* - `logger` A PSR3 logger instance |
29
|
|
|
* - `user_type` The user object type (FQN or ident) |
30
|
|
|
* - `user_factory` The Factory used to instanciate new users. |
31
|
|
|
* - `token_type` The auth token object type (FQN or ident) |
32
|
|
|
* - `token_factory` The Factory used to instanciate new auth tokens. |
33
|
|
|
*/ |
34
|
|
|
abstract class AbstractAuthenticator implements |
35
|
|
|
AuthenticatorInterface, |
36
|
|
|
LoggerAwareInterface |
37
|
|
|
{ |
38
|
|
|
use LoggerAwareTrait; |
39
|
|
|
|
40
|
|
|
const AUTH_BY_PASSWORD = 'password'; |
41
|
|
|
const AUTH_BY_SESSION = 'session'; |
42
|
|
|
const AUTH_BY_TOKEN = 'token'; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* The user that was last authenticated. |
46
|
|
|
* |
47
|
|
|
* @var AuthenticatableInterface|null |
48
|
|
|
*/ |
49
|
|
|
protected $authenticatedUser; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* The token that was last authenticated. |
53
|
|
|
* |
54
|
|
|
* @var \Charcoal\User\AuthTokenInterface|null |
55
|
|
|
*/ |
56
|
|
|
protected $authenticatedToken; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* The authentication method of the user that was last authenticated. |
60
|
|
|
* |
61
|
|
|
* @var string|null |
62
|
|
|
*/ |
63
|
|
|
protected $authenticatedMethod; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Indicates if the logout method has been called. |
67
|
|
|
* |
68
|
|
|
* @var boolean |
69
|
|
|
*/ |
70
|
|
|
protected $isLoggedOut = false; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* The user object type. |
74
|
|
|
* |
75
|
|
|
* @var string |
76
|
|
|
*/ |
77
|
|
|
private $userType; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Store the user model factory instance for the current class. |
81
|
|
|
* |
82
|
|
|
* @var FactoryInterface |
83
|
|
|
*/ |
84
|
|
|
private $userFactory; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* The auth-token object type. |
88
|
|
|
* |
89
|
|
|
* @var string |
90
|
|
|
*/ |
91
|
|
|
private $tokenType; |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Store the auth-token model factory instance for the current class. |
95
|
|
|
* |
96
|
|
|
* @var FactoryInterface |
97
|
|
|
*/ |
98
|
|
|
private $tokenFactory; |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param array $data Authenticator dependencies. |
102
|
|
|
*/ |
103
|
|
|
public function __construct(array $data) |
104
|
|
|
{ |
105
|
|
|
$this->setLogger($data['logger']); |
106
|
|
|
$this->setUserType($data['user_type']); |
107
|
|
|
$this->setUserFactory($data['user_factory']); |
108
|
|
|
$this->setTokenType($data['token_type']); |
109
|
|
|
$this->setTokenFactory($data['token_factory']); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Retrieve the user object type. |
114
|
|
|
* |
115
|
|
|
* @return string |
116
|
|
|
*/ |
117
|
|
|
public function userType() |
118
|
|
|
{ |
119
|
|
|
return $this->userType; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Retrieve the user model factory. |
124
|
|
|
* |
125
|
|
|
* @throws RuntimeException If the model factory was not previously set. |
126
|
|
|
* @return FactoryInterface |
127
|
|
|
*/ |
128
|
|
|
public function userFactory() |
129
|
|
|
{ |
130
|
|
|
return $this->userFactory; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Create a new user model. |
135
|
|
|
* |
136
|
|
|
* @return \Charcoal\User\Access\AuthenticatableInterface |
137
|
|
|
*/ |
138
|
|
|
public function createUser() |
139
|
|
|
{ |
140
|
|
|
return $this->userFactory()->create($this->userType()); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Retrieve the auth-token object type. |
145
|
|
|
* |
146
|
|
|
* @return string |
147
|
|
|
*/ |
148
|
|
|
public function tokenType() |
149
|
|
|
{ |
150
|
|
|
return $this->tokenType; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Retrieve the auth-token model factory. |
155
|
|
|
* |
156
|
|
|
* @throws RuntimeException If the token factory was not previously set. |
157
|
|
|
* @return FactoryInterface |
158
|
|
|
*/ |
159
|
|
|
public function tokenFactory() |
160
|
|
|
{ |
161
|
|
|
return $this->tokenFactory; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Create a new auth-token model. |
166
|
|
|
* |
167
|
|
|
* @return \Charcoal\User\AuthTokenInterface |
168
|
|
|
*/ |
169
|
|
|
public function createToken() |
170
|
|
|
{ |
171
|
|
|
return $this->tokenFactory()->create($this->tokenType()); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Set the user object type (model). |
176
|
|
|
* |
177
|
|
|
* @param string $type The user object type. |
178
|
|
|
* @throws InvalidArgumentException If the user object type parameter is not a string. |
179
|
|
|
* @return void |
180
|
|
|
*/ |
181
|
|
|
protected function setUserType($type) |
182
|
|
|
{ |
183
|
|
|
if (!is_string($type)) { |
184
|
|
|
throw new InvalidArgumentException( |
185
|
|
|
'User object type must be a string' |
186
|
|
|
); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
$this->userType = $type; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Set a user model factory. |
194
|
|
|
* |
195
|
|
|
* @param FactoryInterface $factory The factory used to create new user instances. |
196
|
|
|
* @return void |
197
|
|
|
*/ |
198
|
|
|
protected function setUserFactory(FactoryInterface $factory) |
199
|
|
|
{ |
200
|
|
|
$this->userFactory = $factory; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Set the authorization token type (model). |
205
|
|
|
* |
206
|
|
|
* @param string $type The auth-token object type. |
207
|
|
|
* @throws InvalidArgumentException If the token object type parameter is not a string. |
208
|
|
|
* @return void |
209
|
|
|
*/ |
210
|
|
|
protected function setTokenType($type) |
211
|
|
|
{ |
212
|
|
|
if (!is_string($type)) { |
213
|
|
|
throw new InvalidArgumentException( |
214
|
|
|
'Token object type must be a string' |
215
|
|
|
); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
$this->tokenType = $type; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Set a model factory for token-based authentication. |
223
|
|
|
* |
224
|
|
|
* @param FactoryInterface $factory The factory used to create new auth-token instances. |
225
|
|
|
* @return void |
226
|
|
|
*/ |
227
|
|
|
protected function setTokenFactory(FactoryInterface $factory) |
228
|
|
|
{ |
229
|
|
|
$this->tokenFactory = $factory; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Retrieve the currently authenticated user. |
234
|
|
|
* |
235
|
|
|
* The method will attempt to authenticate a user. |
236
|
|
|
* |
237
|
|
|
* @return AuthenticatableInterface|null |
238
|
|
|
*/ |
239
|
|
|
public function user() |
240
|
|
|
{ |
241
|
|
|
if ($this->isLoggedOut()) { |
242
|
|
|
return null; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if ($this->authenticatedUser === null) { |
246
|
|
|
$this->authenticate(); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return $this->authenticatedUser; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Retrieve the ID for the currently authenticated user. |
254
|
|
|
* |
255
|
|
|
* The method will attempt to authenticate a user. |
256
|
|
|
* |
257
|
|
|
* @return mixed |
258
|
|
|
*/ |
259
|
|
|
public function userId() |
260
|
|
|
{ |
261
|
|
|
if ($this->isLoggedOut()) { |
262
|
|
|
return null; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
$user = $this->user(); |
266
|
|
|
if (!$user) { |
267
|
|
|
return null; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
return $user->getAuthId(); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Retrieve the currently cached user. |
275
|
|
|
* |
276
|
|
|
* @return AuthenticatableInterface|null |
277
|
|
|
*/ |
278
|
|
|
public function getUser() |
279
|
|
|
{ |
280
|
|
|
return $this->authenticatedUser; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Retrieve the ID for the currently cached user. |
285
|
|
|
* |
286
|
|
|
* @return mixed |
287
|
|
|
*/ |
288
|
|
|
public function getUserId() |
289
|
|
|
{ |
290
|
|
|
$user = $this->authenticatedUser; |
291
|
|
|
if (!$user) { |
292
|
|
|
return null; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
return $user->getAuthId(); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Set the authenticated user. |
300
|
|
|
* |
301
|
|
|
* Log a user into the application without sessions or cookies. |
302
|
|
|
* |
303
|
|
|
* @param AuthenticatableInterface $user The authenticated user. |
304
|
|
|
* @return void |
305
|
|
|
*/ |
306
|
|
|
public function setUser(AuthenticatableInterface $user) |
307
|
|
|
{ |
308
|
|
|
$this->authenticatedUser = $user; |
309
|
|
|
$this->isLoggedOut = false; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Set the authenticated user from the given user ID. |
314
|
|
|
* |
315
|
|
|
* Log a user into the application without sessions or cookies. |
316
|
|
|
* |
317
|
|
|
* @param mixed $userId The authenticated user ID. |
318
|
|
|
* @return void |
319
|
|
|
*/ |
320
|
|
|
public function setUserById($userId) |
321
|
|
|
{ |
322
|
|
|
$user = $this->createUser(); |
323
|
|
|
$user->loadFrom($user->getAuthIdKey(), $userId); |
324
|
|
|
|
325
|
|
|
// Allow model to validate user standing |
326
|
|
|
if ($this->validateAuthentication($user)) { |
327
|
|
|
$this->setUser($user); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* Determine if the current user is authenticated. |
333
|
|
|
* |
334
|
|
|
* @return boolean |
335
|
|
|
*/ |
336
|
|
|
public function check() |
337
|
|
|
{ |
338
|
|
|
return $this->user() !== null; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Determines if the logout method has been called. |
343
|
|
|
* |
344
|
|
|
* @return boolean TRUE if the logout method has been called, FALSE otherwise. |
345
|
|
|
*/ |
346
|
|
|
protected function isLoggedOut() |
347
|
|
|
{ |
348
|
|
|
return $this->isLoggedOut; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Retrieve the authentication method of the current user. |
353
|
|
|
* |
354
|
|
|
* If the current user is authenticated, one of the |
355
|
|
|
* `self::AUTH_BY_*` constants is returned. |
356
|
|
|
* |
357
|
|
|
* @return string|null |
358
|
|
|
*/ |
359
|
|
|
public function getAuthenticationMethod() |
360
|
|
|
{ |
361
|
|
|
return $this->authenticatedMethod; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Retrieve the authentication token of the current user. |
366
|
|
|
* |
367
|
|
|
* If the current user was authenticated by token, |
368
|
|
|
* the auth token instance is returned. |
369
|
|
|
* |
370
|
|
|
* @return AuthTokenInterface|null |
371
|
|
|
*/ |
372
|
|
|
public function getAuthenticationToken() |
373
|
|
|
{ |
374
|
|
|
return $this->authenticatedToken; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
/** |
378
|
|
|
* Log a user into the application. |
379
|
|
|
* |
380
|
|
|
* @param AuthenticatableInterface $user The authenticated user to log in. |
381
|
|
|
* @param boolean $remember Whether to "remember" the user or not. |
382
|
|
|
* @return void |
383
|
|
|
*/ |
384
|
|
|
public function login(AuthenticatableInterface $user, $remember = false) |
385
|
|
|
{ |
386
|
|
|
if (!$user->getAuthId()) { |
387
|
|
|
return; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
$this->updateUserSession($user); |
391
|
|
|
|
392
|
|
|
if ($remember) { |
393
|
|
|
$this->updateCurrentToken($user); |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
$this->setUser($user); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Log the user out of the application. |
401
|
|
|
* |
402
|
|
|
* @return void |
403
|
|
|
*/ |
404
|
|
|
public function logout() |
405
|
|
|
{ |
406
|
|
|
$user = $this->user(); |
407
|
|
|
|
408
|
|
|
$this->deleteUserSession($user); |
409
|
|
|
$this->deleteUserTokens($user); |
410
|
|
|
|
411
|
|
|
$this->clearAuthenticator(); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* Attempt to authenticate a user by session or token. |
416
|
|
|
* |
417
|
|
|
* The user is authenticated via _session ID_ or _auth token_. |
418
|
|
|
* |
419
|
|
|
* @return AuthenticatableInterface|null Returns the authenticated user object |
420
|
|
|
* or NULL if not authenticated. |
421
|
|
|
*/ |
422
|
|
|
public function authenticate() |
423
|
|
|
{ |
424
|
|
|
if ($this->isLoggedOut()) { |
425
|
|
|
return null; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
$user = $this->authenticateBySession(); |
429
|
|
|
if ($user) { |
430
|
|
|
return $user; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
$user = $this->authenticateByToken(); |
434
|
|
|
if ($user) { |
435
|
|
|
return $user; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
$this->authenticatedMethod = null; |
439
|
|
|
$this->authenticatedToken = null; |
440
|
|
|
$this->authenticatedUser = null; |
441
|
|
|
|
442
|
|
|
return null; |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
/** |
446
|
|
|
* Attempt to authenticate a user using the given credentials. |
447
|
|
|
* |
448
|
|
|
* @param string $identifier The login ID, part of necessary credentials. |
449
|
|
|
* @param string $password The password, part of necessary credentials. |
450
|
|
|
* @throws InvalidArgumentException If the credentials are invalid or missing. |
451
|
|
|
* @return AuthenticatableInterface|null Returns the authenticated user object |
452
|
|
|
* or NULL if not authenticated. |
453
|
|
|
*/ |
454
|
|
|
public function authenticateByPassword($identifier, $password) |
455
|
|
|
{ |
456
|
|
|
if (!$this->validateLogin($identifier, $password)) { |
457
|
|
|
throw new InvalidArgumentException( |
458
|
|
|
'Invalid user login credentials' |
459
|
|
|
); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
$user = $this->createUser(); |
463
|
|
View Code Duplication |
if (!$user->source()->tableExists()) { |
|
|
|
|
464
|
|
|
$this->logger->warning(sprintf( |
465
|
|
|
'[Authenticator] Invalid login attempt for user "%s" (%s): The table "%s" does not exist.', |
466
|
|
|
$identifier, |
467
|
|
|
get_class($user), |
468
|
|
|
$user->source()->table() |
469
|
|
|
)); |
470
|
|
|
return null; |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
// Load the user by email |
474
|
|
|
$user->loadFrom($user->getAuthIdentifierKey(), $identifier); |
475
|
|
|
|
476
|
|
|
// Check identifier is as requested |
477
|
|
|
if ($user->getAuthIdentifier() !== $identifier) { |
478
|
|
|
return null; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
// Allow model to validate user standing |
482
|
|
|
if (!$this->validateAuthentication($user)) { |
483
|
|
|
return null; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
// Validate password |
487
|
|
|
$hashedPassword = $user->getAuthPassword(); |
488
|
|
|
if (password_verify($password, $hashedPassword)) { |
489
|
|
|
if (password_needs_rehash($hashedPassword, PASSWORD_DEFAULT)) { |
490
|
|
|
$this->rehashUserPassword($user, $password); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
$this->login($user); |
494
|
|
|
$this->authenticatedMethod = static::AUTH_BY_PASSWORD; |
495
|
|
|
|
496
|
|
|
return $user; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
$this->logger->warning(sprintf( |
500
|
|
|
'[Authenticator] Invalid login attempt for user "%s" (%s): invalid password.', |
501
|
|
|
$identifier, |
502
|
|
|
get_class($user) |
503
|
|
|
)); |
504
|
|
|
|
505
|
|
|
return null; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
/** |
509
|
|
|
* Attempt to authenticate a user using their session ID. |
510
|
|
|
* |
511
|
|
|
* @return AuthenticatableInterface|null Returns the authenticated user object |
512
|
|
|
* or NULL if not authenticated. |
513
|
|
|
*/ |
514
|
|
|
protected function authenticateBySession() |
515
|
|
|
{ |
516
|
|
|
$user = $this->createUser(); |
517
|
|
View Code Duplication |
if (!$user->source()->tableExists()) { |
|
|
|
|
518
|
|
|
$this->logger->warning(sprintf( |
519
|
|
|
'[Authenticator] Invalid login attempt by session for a user (%s): The table "%s" does not exist.', |
520
|
|
|
get_class($user), |
521
|
|
|
$user->source()->table() |
522
|
|
|
)); |
523
|
|
|
return null; |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
$key = $user::sessionKey(); |
527
|
|
|
if (empty($key) || !isset($_SESSION[$key])) { |
528
|
|
|
return null; |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
$userId = $_SESSION[$key]; |
532
|
|
|
if (!$userId) { |
533
|
|
|
return null; |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
$user->loadFrom($user->getAuthIdKey(), $userId); |
537
|
|
|
|
538
|
|
|
// Allow model to validate user standing |
539
|
|
|
if (!$this->validateAuthentication($user)) { |
540
|
|
|
return null; |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
$this->updateUserSession($user); |
544
|
|
|
|
545
|
|
|
$this->setUser($user); |
546
|
|
|
$this->authenticatedMethod = static::AUTH_BY_SESSION; |
547
|
|
|
|
548
|
|
|
return $user; |
549
|
|
|
} |
550
|
|
|
|
551
|
|
|
/** |
552
|
|
|
* Attempt to authenticate a user using their auth token. |
553
|
|
|
* |
554
|
|
|
* @return AuthenticatableInterface|null Returns the authenticated user object |
555
|
|
|
* or NULL if not authenticated. |
556
|
|
|
*/ |
557
|
|
|
protected function authenticateByToken() |
558
|
|
|
{ |
559
|
|
|
$authToken = $this->createToken(); |
560
|
|
|
|
561
|
|
|
if (!$authToken->isEnabled()) { |
562
|
|
|
return null; |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
if (!($authToken instanceof AuthTokenCookieInterface)) { |
566
|
|
|
return null; |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
$tokenData = $authToken->getTokenDataFromCookie(); |
570
|
|
|
if (!$tokenData) { |
571
|
|
|
return null; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
$userId = $authToken->getUserIdFromToken($tokenData['ident'], $tokenData['token']); |
575
|
|
|
if (!$userId) { |
576
|
|
|
return null; |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
$user = $this->createUser(); |
580
|
|
View Code Duplication |
if (!$user->source()->tableExists()) { |
|
|
|
|
581
|
|
|
$this->logger->warning(sprintf( |
582
|
|
|
'[Authenticator] Invalid login attempt by token for a user (%s): The table "%s" does not exist.', |
583
|
|
|
get_class($user), |
584
|
|
|
$user->source()->table() |
585
|
|
|
)); |
586
|
|
|
return null; |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
$user->loadFrom($user->getAuthIdKey(), $userId); |
590
|
|
|
|
591
|
|
|
// Allow model to validate user standing |
592
|
|
|
if (!$this->validateAuthentication($user)) { |
593
|
|
|
return null; |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
$this->updateUserSession($user); |
597
|
|
|
|
598
|
|
|
$this->setUser($user); |
599
|
|
|
$this->authenticatedMethod = static::AUTH_BY_TOKEN; |
600
|
|
|
$this->authenticatedToken = $authToken; |
601
|
|
|
|
602
|
|
|
return $user; |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Delete the user data from the session. |
607
|
|
|
* |
608
|
|
|
* @param AuthenticatableInterface|null $user The authenticated user to forget. |
609
|
|
|
* @return void |
610
|
|
|
*/ |
611
|
|
|
protected function deleteUserSession(AuthenticatableInterface $user = null) |
612
|
|
|
{ |
613
|
|
|
if ($user === null) { |
614
|
|
|
$user = $this->userFactory()->get($this->userType()); |
615
|
|
|
} |
616
|
|
|
|
617
|
|
|
$key = $user::sessionKey(); |
618
|
|
|
|
619
|
|
|
$_SESSION[$key] = null; |
620
|
|
|
unset($_SESSION[$key]); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Delete the user data from the cookie. |
625
|
|
|
* |
626
|
|
|
* @param AuthenticatableInterface|null $user The authenticated user to forget. |
627
|
|
|
* @return void |
628
|
|
|
*/ |
629
|
|
|
protected function deleteUserTokens(AuthenticatableInterface $user = null) |
630
|
|
|
{ |
631
|
|
|
$authToken = $this->createToken(); |
632
|
|
|
if (!$authToken->isEnabled()) { |
633
|
|
|
return; |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
$authToken->deleteCookie(); |
637
|
|
|
|
638
|
|
|
if ($user === null) { |
639
|
|
|
return; |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
$userId = $user->getAuthId(); |
643
|
|
|
if (!$userId) { |
644
|
|
|
return; |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
$authToken['userId'] = $userId; |
648
|
|
|
$authToken->deleteUserAuthTokens(); |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
/** |
652
|
|
|
* Delete the user data from the cookie. |
653
|
|
|
* |
654
|
|
|
* @throws InvalidArgumentException If trying to save a user to cookies without an ID. |
655
|
|
|
* @return void |
656
|
|
|
*/ |
657
|
|
|
protected function deleteCurrentToken() |
658
|
|
|
{ |
659
|
|
|
$authToken = $this->authenticatedToken; |
660
|
|
|
if ($authToken === null) { |
661
|
|
|
return; |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
$this->authenticatedToken = null; |
665
|
|
|
|
666
|
|
|
if (!$authToken->isEnabled()) { |
667
|
|
|
return; |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
$authToken->deleteCookie(); |
|
|
|
|
671
|
|
|
$authToken->delete(); |
|
|
|
|
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
/** |
675
|
|
|
* Update the session with the given user. |
676
|
|
|
* |
677
|
|
|
* @param AuthenticatableInterface $user The authenticated user to remember. |
678
|
|
|
* @throws InvalidArgumentException If trying to save a user to session without an ID. |
679
|
|
|
* @return void |
680
|
|
|
*/ |
681
|
|
|
protected function updateUserSession(AuthenticatableInterface $user) |
682
|
|
|
{ |
683
|
|
|
$userId = $user->getAuthId(); |
684
|
|
|
if (!$userId) { |
685
|
|
|
throw new InvalidArgumentException( |
686
|
|
|
'Can not save user data to session; no user ID' |
687
|
|
|
); |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
$_SESSION[$user::sessionKey()] = $userId; |
691
|
|
|
} |
692
|
|
|
|
693
|
|
|
/** |
694
|
|
|
* Store the auth token for the given user in a cookie. |
695
|
|
|
* |
696
|
|
|
* @param AuthenticatableInterface $user The authenticated user to remember. |
697
|
|
|
* @throws InvalidArgumentException If trying to save a user to cookies without an ID. |
698
|
|
|
* @return void |
699
|
|
|
*/ |
700
|
|
|
protected function updateCurrentToken(AuthenticatableInterface $user) |
701
|
|
|
{ |
702
|
|
|
$userId = $user->getAuthId(); |
703
|
|
|
if (!$userId) { |
704
|
|
|
throw new InvalidArgumentException( |
705
|
|
|
'Can not save user data to cookie; no user ID' |
706
|
|
|
); |
707
|
|
|
} |
708
|
|
|
|
709
|
|
|
$authToken = $this->createToken(); |
710
|
|
|
|
711
|
|
|
if (!$authToken->isEnabled()) { |
712
|
|
|
return; |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
$authToken->generate($userId); |
716
|
|
|
$authToken->sendCookie(); |
717
|
|
|
$authToken->save(); |
718
|
|
|
|
719
|
|
|
$this->authenticatedToken = $authToken; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* Validate the user login credentials are acceptable. |
724
|
|
|
* |
725
|
|
|
* @param string $identifier The user identifier to check. |
726
|
|
|
* @param string $password The user password to check. |
727
|
|
|
* @return boolean Returns TRUE if the credentials are acceptable, or FALSE otherwise. |
728
|
|
|
*/ |
729
|
|
|
public function validateLogin($identifier, $password) |
730
|
|
|
{ |
731
|
|
|
return ($this->validateAuthIdentifier($identifier) && $this->validateAuthPassword($password)); |
732
|
|
|
} |
733
|
|
|
|
734
|
|
|
/** |
735
|
|
|
* Validate the user identifier is acceptable. |
736
|
|
|
* |
737
|
|
|
* @param string $identifier The login ID. |
738
|
|
|
* @return boolean Returns TRUE if the identifier is acceptable, or FALSE otherwise. |
739
|
|
|
*/ |
740
|
|
|
public function validateAuthIdentifier($identifier) |
741
|
|
|
{ |
742
|
|
|
return (is_string($identifier) && !empty($identifier)); |
743
|
|
|
} |
744
|
|
|
|
745
|
|
|
/** |
746
|
|
|
* Validate the user password is acceptable. |
747
|
|
|
* |
748
|
|
|
* @param string $password The password. |
749
|
|
|
* @return boolean Returns TRUE if the password is acceptable, or FALSE otherwise. |
750
|
|
|
*/ |
751
|
|
|
public function validateAuthPassword($password) |
752
|
|
|
{ |
753
|
|
|
return (is_string($password) && !empty($password)); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Validate the user authentication state is okay. |
758
|
|
|
* |
759
|
|
|
* For example, inactive users can not authenticate. |
760
|
|
|
* |
761
|
|
|
* @param AuthenticatableInterface $user The user to validate. |
762
|
|
|
* @return boolean |
763
|
|
|
*/ |
764
|
|
|
public function validateAuthentication(AuthenticatableInterface $user) |
765
|
|
|
{ |
766
|
|
|
return ($user->getAuthId() && $user->getAuthIdentifier()); |
767
|
|
|
} |
768
|
|
|
|
769
|
|
|
/** |
770
|
|
|
* Updates the user's password hash. |
771
|
|
|
* |
772
|
|
|
* Assumes that the existing hash needs to be rehashed. |
773
|
|
|
* |
774
|
|
|
* @param AuthenticatableInterface $user The user to update. |
775
|
|
|
* @param string $password The plain-text password to hash. |
776
|
|
|
* @param boolean $update Whether to persist changes to storage. |
777
|
|
|
* @throws InvalidArgumentException If the password is invalid. |
778
|
|
|
* @return boolean Returns TRUE if the password was changed, or FALSE otherwise. |
779
|
|
|
*/ |
780
|
|
View Code Duplication |
public function rehashUserPassword(AuthenticatableInterface $user, $password, $update = true) |
|
|
|
|
781
|
|
|
{ |
782
|
|
|
if (!$this->validateAuthPassword($password)) { |
783
|
|
|
throw new InvalidArgumentException( |
784
|
|
|
'Can not rehash password: password is invalid' |
785
|
|
|
); |
786
|
|
|
} |
787
|
|
|
|
788
|
|
|
$userId = $user->getAuthId(); |
789
|
|
|
|
790
|
|
|
if ($update && $userId) { |
791
|
|
|
$userClass = get_class($user); |
792
|
|
|
|
793
|
|
|
$this->logger->info(sprintf( |
794
|
|
|
'[Authenticator] Rehashing password for user "%s" (%s)', |
795
|
|
|
$userId, |
796
|
|
|
$userClass |
797
|
|
|
)); |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
$passwordKey = $user->getAuthPasswordKey(); |
801
|
|
|
|
802
|
|
|
$user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT); |
803
|
|
|
|
804
|
|
|
if ($update && $userId) { |
805
|
|
|
$result = $user->update([ |
806
|
|
|
$passwordKey, |
807
|
|
|
]); |
808
|
|
|
|
809
|
|
|
if ($result) { |
810
|
|
|
$this->logger->notice(sprintf( |
811
|
|
|
'[Authenticator] Password was rehashed for user "%s" (%s)', |
812
|
|
|
$userId, |
813
|
|
|
$userClass |
|
|
|
|
814
|
|
|
)); |
815
|
|
|
} else { |
816
|
|
|
$this->logger->warning(sprintf( |
817
|
|
|
'[Authenticator] Password failed to be rehashed for user "%s" (%s)', |
818
|
|
|
$userId, |
819
|
|
|
$userClass |
820
|
|
|
)); |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
return $result; |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
return true; |
827
|
|
|
} |
828
|
|
|
|
829
|
|
|
/** |
830
|
|
|
* Updates the user's password hash. |
831
|
|
|
* |
832
|
|
|
* @param AuthenticatableInterface $user The user to update. |
833
|
|
|
* @param string $password The plain-text password to hash. |
834
|
|
|
* @param boolean $update Whether to persist changes to storage. |
835
|
|
|
* @throws InvalidArgumentException If the password is invalid. |
836
|
|
|
* @return boolean Returns TRUE if the password was changed, or FALSE otherwise. |
837
|
|
|
*/ |
838
|
|
View Code Duplication |
public function changeUserPassword(AuthenticatableInterface $user, $password, $update = true) |
|
|
|
|
839
|
|
|
{ |
840
|
|
|
if (!$this->validateAuthPassword($password)) { |
841
|
|
|
throw new InvalidArgumentException( |
842
|
|
|
'Can not change password: password is invalid' |
843
|
|
|
); |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
$userId = $user->getAuthId(); |
847
|
|
|
|
848
|
|
|
if ($update && $userId) { |
849
|
|
|
$userClass = get_class($user); |
850
|
|
|
|
851
|
|
|
$this->logger->info(sprintf( |
852
|
|
|
'[Authenticator] Changing password for user "%s" (%s)', |
853
|
|
|
$userId, |
854
|
|
|
$userClass |
855
|
|
|
)); |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
$passwordKey = $user->getAuthPasswordKey(); |
859
|
|
|
|
860
|
|
|
$user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT); |
861
|
|
|
|
862
|
|
|
if ($update && $userId) { |
863
|
|
|
$result = $user->update([ |
864
|
|
|
$passwordKey, |
865
|
|
|
]); |
866
|
|
|
|
867
|
|
|
if ($result) { |
868
|
|
|
$this->logger->notice(sprintf( |
869
|
|
|
'[Authenticator] Password was changed for user "%s" (%s)', |
870
|
|
|
$userId, |
871
|
|
|
$userClass |
|
|
|
|
872
|
|
|
)); |
873
|
|
|
} else { |
874
|
|
|
$this->logger->warning(sprintf( |
875
|
|
|
'[Authenticator] Password failed to be changed for user "%s" (%s)', |
876
|
|
|
$userId, |
877
|
|
|
$userClass |
878
|
|
|
)); |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
return $result; |
882
|
|
|
} |
883
|
|
|
|
884
|
|
|
return true; |
885
|
|
|
} |
886
|
|
|
|
887
|
|
|
/** |
888
|
|
|
* Clear the authenticator's internal cache. |
889
|
|
|
* |
890
|
|
|
* @return void |
891
|
|
|
*/ |
892
|
|
|
protected function clearAuthenticator() |
893
|
|
|
{ |
894
|
|
|
$this->authenticatedMethod = null; |
895
|
|
|
$this->authenticatedToken = null; |
896
|
|
|
$this->authenticatedUser = null; |
897
|
|
|
$this->isLoggedOut = true; |
898
|
|
|
} |
899
|
|
|
} |
900
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.