Completed
Pull Request — master (#3)
by Chauncey
08:54
created

AbstractAuthenticator   F

Complexity

Total Complexity 88

Size/Duplication

Total Lines 817
Duplicated Lines 11.75 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 88
lcom 1
cbo 5
dl 96
loc 817
rs 1.783
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A userType() 0 4 1
A userFactory() 0 4 1
A createUser() 0 4 1
A tokenType() 0 4 1
A tokenFactory() 0 4 1
A createToken() 0 4 1
A setUserType() 0 10 2
A setUserFactory() 0 4 1
A setTokenType() 0 10 2
A setTokenFactory() 0 4 1
A user() 0 12 3
A userId() 0 13 3
A getUser() 0 4 1
A getUserId() 0 9 2
A setUser() 0 5 1
A check() 0 4 1
A isLoggedOut() 0 4 1
A getAuthenticationMethod() 0 4 1
A getAuthenticationToken() 0 4 1
A login() 0 14 3
A logout() 0 9 1
A authenticate() 0 22 4
B authenticateByPassword() 0 46 7
A authenticateBySession() 0 28 4
A authenticateByToken() 0 34 5
A deleteUserSession() 0 11 2
A deleteUserTokens() 0 21 4
A deleteCurrentToken() 0 16 3
A updateUserSession() 0 11 2
A updateCurrentToken() 0 21 3
A validateLogin() 0 4 2
A validateAuthIdentifier() 0 4 2
A validateAuthPassword() 0 4 2
A validateAuthentication() 0 4 2
B rehashUserPassword() 48 48 7
B changeUserPassword() 48 48 7
A clearAuthenticator() 0 7 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractAuthenticator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractAuthenticator, and based on these observations, apply Extract Interface, too.

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
48
     */
49
    private $authenticatedUser;
50
51
    /**
52
     * The token that was last authenticated.
53
     *
54
     * @var \Charcoal\User\AuthToken
55
     */
56
    private $authenticatedToken;
57
58
    /**
59
     * The authentication method of the user that was last authenticated.
60
     *
61
     * @var string
62
     */
63
    private $authenticatedMethod;
64
65
    /**
66
     * Indicates if the logout method has been called.
67
     *
68
     * @var boolean
69
     */
70
    private $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 Class 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\AuthToken
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
     * @param  AuthenticatableInterface $user The authenticated user.
302
     * @return void
303
     */
304
    public function setUser(AuthenticatableInterface $user)
305
    {
306
        $this->authenticatedUser = $user;
307
        $this->isLoggedOut       = false;
308
    }
309
310
    /**
311
     * Determine if the current user is authenticated.
312
     *
313
     * @return boolean
314
     */
315
    public function check()
316
    {
317
        return $this->user() !== null;
318
    }
319
320
    /**
321
     * Determines if the logout method has been called.
322
     *
323
     * @return boolean TRUE if the logout method has been called, FALSE otherwise.
324
     */
325
    protected function isLoggedOut()
326
    {
327
        return $this->isLoggedOut;
328
    }
329
330
    /**
331
     * Retrieve the authentication method of the current user.
332
     *
333
     * If the current user is authenticated, one of the
334
     * `self::AUTH_BY_*` constants is returned.
335
     *
336
     * @return string|null
337
     */
338
    public function getAuthenticationMethod()
339
    {
340
        return $this->authenticatedMethod;
341
    }
342
343
    /**
344
     * Retrieve the authentication token of the current user.
345
     *
346
     * If the current user was authenticated by token,
347
     * the auth token instance is returned.
348
     *
349
     * @return AuthToken|null
350
     */
351
    public function getAuthenticationToken()
352
    {
353
        return $this->authenticatedToken;
354
    }
355
356
    /**
357
     * Log a user into the application.
358
     *
359
     * @param  AuthenticatableInterface $user     The authenticated user to log in.
360
     * @param  boolean                  $remember Whether to "remember" the user or not.
361
     * @return void
362
     */
363
    public function login(AuthenticatableInterface $user, $remember = false)
364
    {
365
        if (!$user->getAuthId()) {
366
            return;
367
        }
368
369
        $this->updateUserSession($user);
370
371
        if ($remember) {
372
            $this->updateCurrentToken($user);
373
        }
374
375
        $this->setUser($user);
376
    }
377
378
    /**
379
     * Log the user out of the application.
380
     *
381
     * @return void
382
     */
383
    public function logout()
384
    {
385
        $user = $this->user();
386
387
        $this->deleteUserSession($user);
388
        $this->deleteUserTokens($user);
389
390
        $this->clearAuthenticator();
391
    }
392
393
    /**
394
     * Attempt to authenticate a user by session or token.
395
     *
396
     * The user is authenticated via _session ID_ or _auth token_.
397
     *
398
     * @return AuthenticatableInterface|null Returns the authenticated user object
399
     *     or NULL if not authenticated.
400
     */
401
    public function authenticate()
402
    {
403
        if ($this->isLoggedOut()) {
404
            return null;
405
        }
406
407
        $user = $this->authenticateBySession();
408
        if ($user) {
409
            return $user;
410
        }
411
412
        $user = $this->authenticateByToken();
413
        if ($user) {
414
            return $user;
415
        }
416
417
        $this->authenticatedMethod = null;
418
        $this->authenticatedToken  = null;
419
        $this->authenticatedUser   = null;
420
421
        return null;
422
    }
423
424
    /**
425
     * Attempt to authenticate a user using the given credentials.
426
     *
427
     * @param  string $identifier The login ID, part of necessary credentials.
428
     * @param  string $password   The password, part of necessary credentials.
429
     * @throws InvalidArgumentException If the credentials are invalid or missing.
430
     * @return AuthenticatableInterface|null Returns the authenticated user object
431
     *     or NULL if not authenticated.
432
     */
433
    public function authenticateByPassword($identifier, $password)
434
    {
435
        if (!$this->validateLogin($identifier, $password)) {
436
            throw new InvalidArgumentException(
437
                'Invalid user login credentials'
438
            );
439
        }
440
441
        $user = $this->createUser();
442
        if (!$user->source()->tableExists()) {
443
            $user->source()->createTable();
444
        }
445
446
        // Load the user by email
447
        $user->loadFrom($user->getAuthIdentifierKey(), $identifier);
448
449
        // Check identifier is as requested
450
        if ($user->getAuthIdentifier() !== $identifier) {
451
            return null;
452
        }
453
454
        // Allow model to validate user standing
455
        if (!$this->validateAuthentication($user)) {
456
            return null;
457
        }
458
459
        // Validate password
460
        $hashedPassword = $user->getAuthPassword();
461
        if (password_verify($password, $hashedPassword)) {
462
            if (password_needs_rehash($hashedPassword, PASSWORD_DEFAULT)) {
463
                $this->rehashUserPassword($user, $password);
464
            }
465
466
            $this->login($user);
467
            $this->authenticatedMethod = static::AUTH_BY_PASSWORD;
468
469
            return $user;
470
        }
471
472
        $this->logger->warning(sprintf(
473
            'Invalid login attempt for user "%s": invalid password.',
474
             $identifier
475
        ));
476
477
        return null;
478
    }
479
480
    /**
481
     * Attempt to authenticate a user using their session ID.
482
     *
483
     * @return AuthenticatableInterface|null Returns the authenticated user object
484
     *     or NULL if not authenticated.
485
     */
486
    protected function authenticateBySession()
487
    {
488
        $user = $this->createUser();
489
        $key  = $user::sessionKey();
490
491
        if (!isset($_SESSION[$key])) {
492
            return null;
493
        }
494
495
        $userId = $_SESSION[$key];
496
        if (!$userId) {
497
            return null;
498
        }
499
500
        $user->load($userId);
501
502
        // Allow model to validate user standing
503
        if (!$this->validateAuthentication($user)) {
504
            return null;
505
        }
506
507
        $this->updateUserSession($user);
508
509
        $this->setUser($user);
510
        $this->authenticatedMethod = static::AUTH_BY_SESSION;
511
512
        return $user;
513
    }
514
515
    /**
516
     * Attempt to authenticate a user using their auth token.
517
     *
518
     * @return AuthenticatableInterface|null Returns the authenticated user object
519
     *     or NULL if not authenticated.
520
     */
521
    protected function authenticateByToken()
522
    {
523
        $authToken = $this->createToken();
524
525
        if (!$authToken->isEnabled()) {
526
            return null;
527
        }
528
529
        $tokenData = $authToken->getTokenDataFromCookie();
530
        if (!$tokenData) {
531
            return null;
532
        }
533
534
        $userId = $authToken->getUserIdFromToken($tokenData['ident'], $tokenData['token']);
535
        if (!$userId) {
536
            return null;
537
        }
538
539
        $user = $this->createUser();
540
        $user->load($userId);
541
542
        // Allow model to validate user standing
543
        if (!$this->validateAuthentication($user)) {
544
            return null;
545
        }
546
547
        $this->updateUserSession($user);
548
549
        $this->setUser($user);
550
        $this->authenticatedMethod = static::AUTH_BY_TOKEN;
551
        $this->authenticatedToken  = $authToken;
552
553
        return $user;
554
    }
555
556
    /**
557
     * Delete the user data from the session.
558
     *
559
     * @param  AuthenticatableInterface|null $user The authenticated user to forget.
560
     * @return void
561
     */
562
    protected function deleteUserSession(AuthenticatableInterface $user = null)
563
    {
564
        if ($user === null) {
565
            $user = $this->userFactory()->get($this->userType());
566
        }
567
568
        $key = $user::sessionKey();
569
570
        $_SESSION[$key] = null;
571
        unset($_SESSION[$key]);
572
    }
573
574
    /**
575
     * Delete the user data from the cookie.
576
     *
577
     * @param  AuthenticatableInterface|null $user The authenticated user to forget.
578
     * @return void
579
     */
580
    protected function deleteUserTokens(AuthenticatableInterface $user = null)
581
    {
582
        $authToken = $this->createToken();
583
        if (!$authToken->isEnabled()) {
584
            return;
585
        }
586
587
        $authToken->deleteCookie();
588
589
        if ($user === null) {
590
            return;
591
        }
592
593
        $userId = $user->getAuthId();
594
        if (!$userId) {
595
            return;
596
        }
597
598
        $authToken['userId'] = $userId;
599
        $authToken->deleteUserAuthTokens();
600
    }
601
602
    /**
603
     * Delete the user data from the cookie.
604
     *
605
     * @throws InvalidArgumentException If trying to save a user to cookies without an ID.
606
     * @return void
607
     */
608
    protected function deleteCurrentToken()
609
    {
610
        $authToken = $this->authenticatedToken;
611
        if ($authToken === null) {
612
            return;
613
        }
614
615
        $this->authenticatedToken = null;
616
617
        if (!$authToken->isEnabled()) {
618
            return;
619
        }
620
621
        $authToken->deleteCookie();
622
        $authToken->delete();
623
    }
624
625
    /**
626
     * Update the session with the given user.
627
     *
628
     * @param  AuthenticatableInterface $user The authenticated user to remember.
629
     * @throws InvalidArgumentException If trying to save a user to session without an ID.
630
     * @return void
631
     */
632
    protected function updateUserSession(AuthenticatableInterface $user)
633
    {
634
        $userId = $user->getAuthId();
635
        if (!$userId) {
636
            throw new InvalidArgumentException(
637
                'Can not save user data to session; no user ID'
638
            );
639
        }
640
641
        $_SESSION[$user::sessionKey()] = $userId;
642
    }
643
644
    /**
645
     * Store the auth token for the given user in a cookie.
646
     *
647
     * @param  AuthenticatableInterface $user The authenticated user to remember.
648
     * @throws InvalidArgumentException If trying to save a user to cookies without an ID.
649
     * @return void
650
     */
651
    protected function updateCurrentToken(AuthenticatableInterface $user)
652
    {
653
        $userId = $user->getAuthId();
654
        if (!$userId) {
655
            throw new InvalidArgumentException(
656
                'Can not save user data to cookie; no user ID'
657
            );
658
        }
659
660
        $authToken = $this->createToken();
661
662
        if (!$authToken->isEnabled()) {
663
            return;
664
        }
665
666
        $authToken->generate($userId);
667
        $authToken->sendCookie();
668
        $authToken->save();
669
670
        $this->authenticatedToken = $authToken;
671
    }
672
673
    /**
674
     * Validate the user login credentials are acceptable.
675
     *
676
     * @param  string $identifier The user identifier to check.
677
     * @param  string $password   The user password to check.
678
     * @return boolean Returns TRUE if the credentials are acceptable, or FALSE otherwise.
679
     */
680
    public function validateLogin($identifier, $password)
681
    {
682
        return ($this->validateAuthIdentifier($identifier) && $this->validateAuthPassword($password));
683
    }
684
685
    /**
686
     * Validate the user identifier is acceptable.
687
     *
688
     * @param  string $identifier The login ID.
689
     * @return boolean Returns TRUE if the identifier is acceptable, or FALSE otherwise.
690
     */
691
    public function validateAuthIdentifier($identifier)
692
    {
693
        return (is_string($identifier) && !empty($identifier));
694
    }
695
696
    /**
697
     * Validate the user password is acceptable.
698
     *
699
     * @param  string $password The password.
700
     * @return boolean Returns TRUE if the password is acceptable, or FALSE otherwise.
701
     */
702
    public function validateAuthPassword($password)
703
    {
704
        return (is_string($password) && !empty($password));
705
    }
706
707
    /**
708
     * Validate the user authentication state is okay.
709
     *
710
     * For example, inactive users can not authenticate.
711
     *
712
     * @param  AuthenticatableInterface $user The user to validate.
713
     * @return boolean
714
     */
715
    public function validateAuthentication(AuthenticatableInterface $user)
716
    {
717
        return ($user->getAuthId() && $user->getAuthIdentifier());
718
    }
719
720
    /**
721
     * Updates the user's password hash.
722
     *
723
     * Assumes that the existing hash needs to be rehashed.
724
     *
725
     * @param  AuthenticatableInterface $user     The user to update.
726
     * @param  string                   $password The plain-text password to hash.
727
     * @param  boolean                  $update   Whether to persist changes to storage.
728
     * @throws InvalidArgumentException If the password is invalid.
729
     * @return boolean Returns TRUE if the password was changed, or FALSE otherwise.
730
     */
731 View Code Duplication
    protected function rehashUserPassword(AuthenticatableInterface $user, $password, $update = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
732
    {
733
        if (!$this->validateAuthPassword($password)) {
734
            throw new InvalidArgumentException(
735
                'Can not rehash password: password is invalid'
736
            );
737
        }
738
739
        $userId = $user->getAuthId();
740
741
        if ($update && $userId) {
742
            $userClass = get_class($user);
743
744
            $this->logger->info(sprintf(
745
                'Rehashing password for user "%s" (%s)',
746
                $userId,
747
                $userClass
748
            ));
749
        }
750
751
        $passwordKey = $user->getAuthPasswordKey();
752
753
        $user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT);
754
755
        if ($update && $userId) {
756
            $result = $user->update([
757
                $passwordKey,
758
            ]);
759
760
            if ($result) {
761
                $this->logger->notice(sprintf(
762
                    'Password was rehashed for user "%s" (%s)',
763
                    $userId,
764
                    $userClass
0 ignored issues
show
Bug introduced by
The variable $userClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
765
                ));
766
            } else {
767
                $this->logger->warning(sprintf(
768
                    'Password failed to be rehashed for user "%s" (%s)',
769
                    $userId,
770
                    $userClass
771
                ));
772
            }
773
774
            return $result;
775
        }
776
777
        return true;
778
    }
779
780
    /**
781
     * Updates the user's password hash.
782
     *
783
     * @param  AuthenticatableInterface $user     The user to update.
784
     * @param  string                   $password The plain-text password to hash.
785
     * @param  boolean                  $update   Whether to persist changes to storage.
786
     * @throws InvalidArgumentException If the password is invalid.
787
     * @return boolean Returns TRUE if the password was changed, or FALSE otherwise.
788
     */
789 View Code Duplication
    protected function changeUserPassword(AuthenticatableInterface $user, $password, $update = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
790
    {
791
        if (!$this->validateAuthPassword($password)) {
792
            throw new InvalidArgumentException(
793
                'Can not change password: password is invalid'
794
            );
795
        }
796
797
        $userId = $user->getAuthId();
798
799
        if ($update && $userId) {
800
            $userClass = get_class($user);
801
802
            $this->logger->info(sprintf(
803
                'Changing password for user "%s" (%s)',
804
                $userId,
805
                $userClass
806
            ));
807
        }
808
809
        $passwordKey = $user->getAuthPasswordKey();
810
811
        $user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT);
812
813
        if ($update && $userId) {
814
            $result = $user->update([
815
                $passwordKey,
816
            ]);
817
818
            if ($result) {
819
                $this->logger->notice(sprintf(
820
                    'Password was changed for user "%s" (%s)',
821
                    $userId,
822
                    $userClass
0 ignored issues
show
Bug introduced by
The variable $userClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
823
                ));
824
            } else {
825
                $this->logger->warning(sprintf(
826
                    'Password failed to be changed for user "%s" (%s)',
827
                    $userId,
828
                    $userClass
829
                ));
830
            }
831
832
            return $result;
833
        }
834
835
        return true;
836
    }
837
838
    /**
839
     * Clear the authenticator's internal cache.
840
     *
841
     * @return void
842
     */
843
    protected function clearAuthenticator()
844
    {
845
        $this->authenticatedMethod = null;
846
        $this->authenticatedToken  = null;
847
        $this->authenticatedUser   = null;
848
        $this->isLoggedOut         = true;
849
    }
850
}
851