Test Failed
Pull Request — master (#3)
by Chauncey
01:59
created

AbstractAuthenticator   F

Complexity

Total Complexity 90

Size/Duplication

Total Lines 821
Duplicated Lines 11.69 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 90
lcom 1
cbo 5
dl 96
loc 821
rs 1.779
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 5
B authenticateByToken() 0 38 6
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\AuthTokenInterface
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\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
     * @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 AuthTokenInterface|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 (empty($key) || !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
        if (!($authToken instanceof AuthTokenCookieInterface)) {
530
            return null;
531
        }
532
533
        $tokenData = $authToken->getTokenDataFromCookie();
534
        if (!$tokenData) {
535
            return null;
536
        }
537
538
        $userId = $authToken->getUserIdFromToken($tokenData['ident'], $tokenData['token']);
539
        if (!$userId) {
540
            return null;
541
        }
542
543
        $user = $this->createUser();
544
        $user->load($userId);
545
546
        // Allow model to validate user standing
547
        if (!$this->validateAuthentication($user)) {
548
            return null;
549
        }
550
551
        $this->updateUserSession($user);
552
553
        $this->setUser($user);
554
        $this->authenticatedMethod = static::AUTH_BY_TOKEN;
555
        $this->authenticatedToken  = $authToken;
556
557
        return $user;
558
    }
559
560
    /**
561
     * Delete the user data from the session.
562
     *
563
     * @param  AuthenticatableInterface|null $user The authenticated user to forget.
564
     * @return void
565
     */
566
    protected function deleteUserSession(AuthenticatableInterface $user = null)
567
    {
568
        if ($user === null) {
569
            $user = $this->userFactory()->get($this->userType());
570
        }
571
572
        $key = $user::sessionKey();
573
574
        $_SESSION[$key] = null;
575
        unset($_SESSION[$key]);
576
    }
577
578
    /**
579
     * Delete the user data from the cookie.
580
     *
581
     * @param  AuthenticatableInterface|null $user The authenticated user to forget.
582
     * @return void
583
     */
584
    protected function deleteUserTokens(AuthenticatableInterface $user = null)
585
    {
586
        $authToken = $this->createToken();
587
        if (!$authToken->isEnabled()) {
588
            return;
589
        }
590
591
        $authToken->deleteCookie();
592
593
        if ($user === null) {
594
            return;
595
        }
596
597
        $userId = $user->getAuthId();
598
        if (!$userId) {
599
            return;
600
        }
601
602
        $authToken['userId'] = $userId;
603
        $authToken->deleteUserAuthTokens();
604
    }
605
606
    /**
607
     * Delete the user data from the cookie.
608
     *
609
     * @throws InvalidArgumentException If trying to save a user to cookies without an ID.
610
     * @return void
611
     */
612
    protected function deleteCurrentToken()
613
    {
614
        $authToken = $this->authenticatedToken;
615
        if ($authToken === null) {
616
            return;
617
        }
618
619
        $this->authenticatedToken = null;
620
621
        if (!$authToken->isEnabled()) {
622
            return;
623
        }
624
625
        $authToken->deleteCookie();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\User\AuthTokenInterface as the method deleteCookie() does only exist in the following implementations of said interface: Charcoal\User\AuthToken.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
626
        $authToken->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on Charcoal\User\AuthTokenInterface. Did you maybe mean deleteUserAuthTokens()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
627
    }
628
629
    /**
630
     * Update the session with the given user.
631
     *
632
     * @param  AuthenticatableInterface $user The authenticated user to remember.
633
     * @throws InvalidArgumentException If trying to save a user to session without an ID.
634
     * @return void
635
     */
636
    protected function updateUserSession(AuthenticatableInterface $user)
637
    {
638
        $userId = $user->getAuthId();
639
        if (!$userId) {
640
            throw new InvalidArgumentException(
641
                'Can not save user data to session; no user ID'
642
            );
643
        }
644
645
        $_SESSION[$user::sessionKey()] = $userId;
646
    }
647
648
    /**
649
     * Store the auth token for the given user in a cookie.
650
     *
651
     * @param  AuthenticatableInterface $user The authenticated user to remember.
652
     * @throws InvalidArgumentException If trying to save a user to cookies without an ID.
653
     * @return void
654
     */
655
    protected function updateCurrentToken(AuthenticatableInterface $user)
656
    {
657
        $userId = $user->getAuthId();
658
        if (!$userId) {
659
            throw new InvalidArgumentException(
660
                'Can not save user data to cookie; no user ID'
661
            );
662
        }
663
664
        $authToken = $this->createToken();
665
666
        if (!$authToken->isEnabled()) {
667
            return;
668
        }
669
670
        $authToken->generate($userId);
671
        $authToken->sendCookie();
672
        $authToken->save();
673
674
        $this->authenticatedToken = $authToken;
675
    }
676
677
    /**
678
     * Validate the user login credentials are acceptable.
679
     *
680
     * @param  string $identifier The user identifier to check.
681
     * @param  string $password   The user password to check.
682
     * @return boolean Returns TRUE if the credentials are acceptable, or FALSE otherwise.
683
     */
684
    public function validateLogin($identifier, $password)
685
    {
686
        return ($this->validateAuthIdentifier($identifier) && $this->validateAuthPassword($password));
687
    }
688
689
    /**
690
     * Validate the user identifier is acceptable.
691
     *
692
     * @param  string $identifier The login ID.
693
     * @return boolean Returns TRUE if the identifier is acceptable, or FALSE otherwise.
694
     */
695
    public function validateAuthIdentifier($identifier)
696
    {
697
        return (is_string($identifier) && !empty($identifier));
698
    }
699
700
    /**
701
     * Validate the user password is acceptable.
702
     *
703
     * @param  string $password The password.
704
     * @return boolean Returns TRUE if the password is acceptable, or FALSE otherwise.
705
     */
706
    public function validateAuthPassword($password)
707
    {
708
        return (is_string($password) && !empty($password));
709
    }
710
711
    /**
712
     * Validate the user authentication state is okay.
713
     *
714
     * For example, inactive users can not authenticate.
715
     *
716
     * @param  AuthenticatableInterface $user The user to validate.
717
     * @return boolean
718
     */
719
    public function validateAuthentication(AuthenticatableInterface $user)
720
    {
721
        return ($user->getAuthId() && $user->getAuthIdentifier());
722
    }
723
724
    /**
725
     * Updates the user's password hash.
726
     *
727
     * Assumes that the existing hash needs to be rehashed.
728
     *
729
     * @param  AuthenticatableInterface $user     The user to update.
730
     * @param  string                   $password The plain-text password to hash.
731
     * @param  boolean                  $update   Whether to persist changes to storage.
732
     * @throws InvalidArgumentException If the password is invalid.
733
     * @return boolean Returns TRUE if the password was changed, or FALSE otherwise.
734
     */
735 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...
736
    {
737
        if (!$this->validateAuthPassword($password)) {
738
            throw new InvalidArgumentException(
739
                'Can not rehash password: password is invalid'
740
            );
741
        }
742
743
        $userId = $user->getAuthId();
744
745
        if ($update && $userId) {
746
            $userClass = get_class($user);
747
748
            $this->logger->info(sprintf(
749
                'Rehashing password for user "%s" (%s)',
750
                $userId,
751
                $userClass
752
            ));
753
        }
754
755
        $passwordKey = $user->getAuthPasswordKey();
756
757
        $user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT);
758
759
        if ($update && $userId) {
760
            $result = $user->update([
761
                $passwordKey,
762
            ]);
763
764
            if ($result) {
765
                $this->logger->notice(sprintf(
766
                    'Password was rehashed for user "%s" (%s)',
767
                    $userId,
768
                    $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...
769
                ));
770
            } else {
771
                $this->logger->warning(sprintf(
772
                    'Password failed to be rehashed for user "%s" (%s)',
773
                    $userId,
774
                    $userClass
775
                ));
776
            }
777
778
            return $result;
779
        }
780
781
        return true;
782
    }
783
784
    /**
785
     * Updates the user's password hash.
786
     *
787
     * @param  AuthenticatableInterface $user     The user to update.
788
     * @param  string                   $password The plain-text password to hash.
789
     * @param  boolean                  $update   Whether to persist changes to storage.
790
     * @throws InvalidArgumentException If the password is invalid.
791
     * @return boolean Returns TRUE if the password was changed, or FALSE otherwise.
792
     */
793 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...
794
    {
795
        if (!$this->validateAuthPassword($password)) {
796
            throw new InvalidArgumentException(
797
                'Can not change password: password is invalid'
798
            );
799
        }
800
801
        $userId = $user->getAuthId();
802
803
        if ($update && $userId) {
804
            $userClass = get_class($user);
805
806
            $this->logger->info(sprintf(
807
                'Changing password for user "%s" (%s)',
808
                $userId,
809
                $userClass
810
            ));
811
        }
812
813
        $passwordKey = $user->getAuthPasswordKey();
814
815
        $user[$passwordKey] = password_hash($password, PASSWORD_DEFAULT);
816
817
        if ($update && $userId) {
818
            $result = $user->update([
819
                $passwordKey,
820
            ]);
821
822
            if ($result) {
823
                $this->logger->notice(sprintf(
824
                    'Password was changed for user "%s" (%s)',
825
                    $userId,
826
                    $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...
827
                ));
828
            } else {
829
                $this->logger->warning(sprintf(
830
                    'Password failed to be changed for user "%s" (%s)',
831
                    $userId,
832
                    $userClass
833
                ));
834
            }
835
836
            return $result;
837
        }
838
839
        return true;
840
    }
841
842
    /**
843
     * Clear the authenticator's internal cache.
844
     *
845
     * @return void
846
     */
847
    protected function clearAuthenticator()
848
    {
849
        $this->authenticatedMethod = null;
850
        $this->authenticatedToken  = null;
851
        $this->authenticatedUser   = null;
852
        $this->isLoggedOut         = true;
853
    }
854
}
855