Passed
Push — master ( 6151ba...17a32e )
by Chauncey
52s queued 10s
created

AbstractAuthenticator::authenticateByPassword()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 53

Duplication

Lines 9
Ratio 16.98 %

Importance

Changes 0
Metric Value
dl 9
loc 53
rs 8.0921
c 0
b 0
f 0
cc 7
nc 7
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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();
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...
671
        $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...
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)
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...
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
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...
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)
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...
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
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...
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