FormLoginAuthenticator::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
nc 1
nop 5
dl 0
loc 12
ccs 0
cts 6
cp 0
crap 2
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 *
17
 */
18
19
namespace Biurad\Security\Authenticator;
20
21
use Biurad\Security\Handler\RememberMeHandler;
22
use Biurad\Security\Interfaces\AuthenticatorInterface;
23
use Biurad\Security\Interfaces\RequireTokenInterface;
24
use Psr\Http\Message\ServerRequestInterface;
25
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
27
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
28
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
29
use Symfony\Component\Security\Core\Exception\AuthenticationException;
30
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
31
use Symfony\Component\Security\Core\Security;
32
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
33
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
34
use Symfony\Component\Security\Core\User\UserProviderInterface;
35
36
/**
37
 * This authenticator authenticates a user via form request.
38
 *
39
 * @author Divine Niiquaye Ibok <[email protected]>
40
 */
41
class FormLoginAuthenticator implements AuthenticatorInterface, RequireTokenInterface
42
{
43
    private UserProviderInterface $provider;
44
    private ?TokenInterface $token = null;
45
    private PasswordHasherFactoryInterface $hasherFactory;
46
    private ?RememberMeHandler $rememberMeHandler;
47
    private string $userParameter, $passwordParameter;
48
49
    public function __construct(
50
        UserProviderInterface $provider,
51
        PasswordHasherFactoryInterface $hasherFactory,
52
        RememberMeHandler $rememberMeHandler = null,
53
        string $userParameter = '_identifier',
54
        string $passwordParameter = '_password'
55
    ) {
56
        $this->provider = $provider;
57
        $this->hasherFactory = $hasherFactory;
58
        $this->rememberMeHandler = $rememberMeHandler;
59
        $this->userParameter = $userParameter;
60
        $this->passwordParameter = $passwordParameter;
61
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function setToken(?TokenInterface $token): void
67
    {
68
        $this->token = $token;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function supports(ServerRequestInterface $request): bool
75
    {
76
        return 'POST' === $request->getMethod();
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function authenticate(ServerRequestInterface $request, array $credentials, $firewallName): ?TokenInterface
83
    {
84
        if (empty($credentials)) {
85
            return null;
86
        }
87
88
        $username = $credentials[$this->userParameter] ?? null;
89
        $password = $credentials[$this->passwordParameter] ?? null;
90
91
        if (empty($username) xor empty($password)) {
92
            throw new BadCredentialsException('The presented username or password cannot be empty.');
93
        }
94
95
        if (!\is_string($username) || \strlen($username) > Security::MAX_USERNAME_LENGTH) {
0 ignored issues
show
Deprecated Code introduced by
The constant Symfony\Component\Securi...ty::MAX_USERNAME_LENGTH has been deprecated: since Symfony 6.2, use \Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge::MAX_USERNAME_LENGTH instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

95
        if (!\is_string($username) || \strlen($username) > /** @scrutinizer ignore-deprecated */ Security::MAX_USERNAME_LENGTH) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
96
            throw new BadCredentialsException('Invalid username.');
97
        }
98
99
        $user = $this->provider->loadUserByIdentifier($username);
100
101
        if ($user instanceof PasswordAuthenticatedUserInterface) {
102
            $userHasher = $this->hasherFactory->getPasswordHasher($user);
103
104
            if (!$userHasher->verify($user->getPassword(), $password)) {
0 ignored issues
show
Bug introduced by
It seems like $user->getPassword() can also be of type null; however, parameter $hashedPassword of Symfony\Component\Passwo...sherInterface::verify() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

104
            if (!$userHasher->verify(/** @scrutinizer ignore-type */ $user->getPassword(), $password)) {
Loading history...
Bug introduced by
It seems like $password can also be of type null; however, parameter $plainPassword of Symfony\Component\Passwo...sherInterface::verify() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

104
            if (!$userHasher->verify($user->getPassword(), /** @scrutinizer ignore-type */ $password)) {
Loading history...
105
                throw new BadCredentialsException('The presented password is invalid.');
106
            }
107
108
            if ($this->provider instanceof PasswordUpgraderInterface && $userHasher->needsRehash($password)) {
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type null; however, parameter $hashedPassword of Symfony\Component\Passwo...nterface::needsRehash() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
            if ($this->provider instanceof PasswordUpgraderInterface && $userHasher->needsRehash(/** @scrutinizer ignore-type */ $password)) {
Loading history...
109
                $this->provider->upgradePassword($user, $userHasher->hash($password));
0 ignored issues
show
Bug introduced by
The method upgradePassword() does not exist on Symfony\Component\Securi...r\UserProviderInterface. It seems like you code against a sub-type of Symfony\Component\Securi...r\UserProviderInterface such as Symfony\Component\Securi...\User\ChainUserProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

109
                $this->provider->/** @scrutinizer ignore-call */ 
110
                                 upgradePassword($user, $userHasher->hash($password));
Loading history...
Bug introduced by
It seems like $password can also be of type null; however, parameter $plainPassword of Symfony\Component\Passwo...HasherInterface::hash() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

109
                $this->provider->upgradePassword($user, $userHasher->hash(/** @scrutinizer ignore-type */ $password));
Loading history...
110
            }
111
        }
112
113
        if ($request->hasHeader('X-Switch-User') && $oldToken = $this->token) {
114
            if ($username === $oldToken->getUserIdentifier()) {
115
                throw new AuthenticationException('The current user is already authenticated.');
116
            }
117
            $token = new SwitchUserToken($user, $firewallName, $user->getRoles(), $oldToken, (string) $request->getUri());
118
        } else {
119
            $token = new UsernamePasswordToken($user, $firewallName, $user->getRoles());
120
        }
121
122
        if (null !== $this->rememberMeHandler) {
123
            $rememberMe = $credentials[$this->rememberMeHandler->getParameterName()] ?? false;
124
125
            if ('true' === $rememberMe || 'on' === $rememberMe || '1' === $rememberMe || 'yes' === $rememberMe || true === $rememberMe) {
126
                $rememberMeCookie = $this->rememberMeHandler->createRememberMeCookie($token->getUser());
0 ignored issues
show
Bug introduced by
It seems like $token->getUser() can also be of type null; however, parameter $user of Biurad\Security\Handler\...reateRememberMeCookie() does only seem to accept Symfony\Component\Security\Core\User\UserInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

126
                $rememberMeCookie = $this->rememberMeHandler->createRememberMeCookie(/** @scrutinizer ignore-type */ $token->getUser());
Loading history...
127
                $token->setAttribute(RememberMeHandler::REMEMBER_ME, $rememberMeCookie->withSecure('https' === $request->getUri()->getScheme()));
128
            }
129
        }
130
131
        return $token;
132
    }
133
}
134