Passed
Push — master ( 3c8625...c93166 )
by Divine Niiquaye
12:46
created

Authenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
nc 1
nop 8
dl 0
loc 18
ccs 0
cts 9
cp 0
crap 2
rs 10
c 1
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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;
20
21
use Biurad\Http\Request;
22
use Biurad\Security\Event\AuthenticationFailureEvent;
23
use Biurad\Security\Interfaces\AuthenticatorInterface;
24
use Psr\EventDispatcher\EventDispatcherInterface;
25
use Psr\Http\Message\ResponseInterface;
26
use Psr\Http\Message\ServerRequestInterface;
27
use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface;
28
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
29
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
30
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
31
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
32
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
33
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
34
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
35
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
36
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
37
use Symfony\Component\Security\Core\Exception\AccountStatusException;
38
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
39
use Symfony\Component\Security\Core\Exception\AuthenticationException;
40
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
41
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
42
use Symfony\Component\Security\Core\Exception\TooManyLoginAttemptsAuthenticationException;
43
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
44
use Symfony\Component\Security\Core\User\InMemoryUserChecker;
45
use Symfony\Component\Security\Core\User\UserCheckerInterface;
46
use Symfony\Component\Security\Core\User\UserInterface;
47
48
/**
49
 * Authenticate a user with a set of authenticators.
50
 *
51
 * @author Divine Niiquaye Ibok <[email protected]>
52
 */
53
class Authenticator implements AuthorizationCheckerInterface
54
{
55
    private TokenStorageInterface $tokenStorage;
56
    private AccessDecisionManagerInterface $accessDecisionManager;
57
    private UserCheckerInterface $userChecker;
58
    private ?PropertyAccessorInterface $propertyAccessor;
59
    private ?RequestRateLimiterInterface $limiter;
60
    private ?EventDispatcherInterface $eventDispatcher;
61
    private bool $hideUserNotFoundExceptions;
62
63
    /** @var array<int,Interfaces\AuthenticatorInterface> */
64
    private array $authenticators;
65
66
    /**
67
     * @param array<int,Interfaces\AuthenticatorInterface> $authenticators
68
     */
69
    public function __construct(
70
        array $authenticators,
71
        TokenStorageInterface $tokenStorage,
72
        AccessDecisionManagerInterface $accessDecisionManager,
73
        UserCheckerInterface $userChecker = null,
74
        RequestRateLimiterInterface $limiter = null,
75
        EventDispatcherInterface $eventDispatcher = null,
76
        PropertyAccessorInterface $propertyAccessor = null,
77
        bool $hideUserNotFoundExceptions = true
78
    ) {
79
        $this->authenticators = $authenticators;
80
        $this->tokenStorage = $tokenStorage;
81
        $this->accessDecisionManager = $accessDecisionManager;
82
        $this->userChecker = $userChecker ?? new InMemoryUserChecker();
83
        $this->limiter = $limiter;
84
        $this->eventDispatcher = $eventDispatcher;
85
        $this->propertyAccessor = $propertyAccessor;
86
        $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
87
    }
88
89
    public function add(AuthenticatorInterface $authenticator): void
90
    {
91
        $this->authenticators[\get_class($authenticator)] = $authenticator;
92
    }
93
94
    public function has(string $authenticatorClass): bool
95
    {
96
        return isset($this->authenticators[$authenticatorClass]);
97
    }
98
99
    public function getTokenStorage(): TokenStorageInterface
100
    {
101
        return $this->tokenStorage;
102
    }
103
104
    /**
105
     * Returns the user(s) representation.
106
     *
107
     * @return UserInterface|array<int,UserInterface>|null
108
     */
109
    public function getUser(bool $current = true)
110
    {
111
        $token = $this->getToken($current);
112
113
        if (!\is_array($token)) {
0 ignored issues
show
introduced by
The condition is_array($token) is always false.
Loading history...
114
            return null !== $token ? $token->getUser() : $token;
115
        }
116
        $users = [];
117
118
        foreach ($token as $tk) {
119
            $users[] = $tk->getUser();
120
        }
121
122
        return $users;
123
    }
124
125
    /**
126
     * Returns the current security token(s).
127
     *
128
     * @return TokenInterface|array<int,TokenInterface>|null
129
     */
130
    public function getToken(bool $current = true)
131
    {
132
        $token = $this->tokenStorage->getToken();
133
134
        if ($current) {
135
            return $token;
136
        }
137
        $tokens = [];
138
        $tokenExist = -1;
139
140
        do {
141
            if (-1 === $tokenExist || $token !== $tokens[$tokenExist]) {
142
                $tokens[++$tokenExist] = $token;
143
            }
144
145
            if ($token instanceof SwitchUserToken) {
146
                $tokens[++$tokenExist] = $token = $token->getOriginalToken();
147
            }
148
        } while ($token instanceof SwitchUserToken);
149
150
        return \array_filter($tokens);
151
    }
152
153
    /**
154
     * Convenience method to programmatically authenticate a user and return
155
     * true if any success or a Response on failure.
156
     *
157
     * @param array<int,string> $credentials The credentials to use
158
     *
159
     * @throw AuthenticationException if the authentication fails
160
     *
161
     * @return ResponseInterface|bool The response of the authentication
162
     */
163
    public function authenticate(ServerRequestInterface $request, array $credentials)
164
    {
165
        $previousToken = $this->tokenStorage->getToken();
166
        $credentials = Helper::getParameterValues($request, $credentials, $this->propertyAccessor);
167
168
        if ($throttling = (null !== $this->limiter && $request instanceof Request)) {
169
            $limit = $this->limiter->consume($request->getRequest());
170
171
            if (!$limit->isAccepted()) {
172
                throw new TooManyLoginAttemptsAuthenticationException((int) \ceil(($limit->getRetryAfter()->getTimestamp() - \time()) / 60));
173
            }
174
        }
175
176
        foreach ($this->authenticators as $authenticator) {
177
            $authenticator->setToken($previousToken);
178
179
            if (!$authenticator->supports($request)) {
180
                continue;
181
            }
182
183
            try {
184
                if (null === $token = $authenticator->authenticate($request, $credentials)) {
185
                    continue; // Allow an authenticator without a token.
186
                }
187
188
                if (!$token instanceof PreAuthenticatedToken) {
189
                    $this->userChecker->checkPreAuth($token->getUser());
190
                }
191
192
                if (null !== $this->eventDispatcher) {
193
                    $this->eventDispatcher->dispatch($event = new AuthenticationSuccessEvent($token));
194
                    $token = $event->getAuthenticationToken();
195
                }
196
197
                if ($throttling) {
198
                    $this->limiter->reset($request->getRequest());
0 ignored issues
show
Bug introduced by
The method reset() does not exist on null. ( Ignorable by Annotation )

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

198
                    $this->limiter->/** @scrutinizer ignore-call */ 
199
                                    reset($request->getRequest());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method getRequest() does not exist on Psr\Http\Message\ServerRequestInterface. Did you maybe mean getRequestTarget()? ( Ignorable by Annotation )

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

198
                    $this->limiter->reset($request->/** @scrutinizer ignore-call */ getRequest());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
                }
200
201
                if ($token !== $previousToken) {
202
                    $this->tokenStorage->setToken($token);
203
                }
204
                $this->userChecker->checkPostAuth($token->getUser());
205
206
                return true;
207
            } catch (AuthenticationException $e) {
208
                // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
209
                // to prevent user enumeration via response content comparison
210
                if ($this->hideUserNotFoundExceptions && ($e instanceof UserNotFoundException || ($e instanceof AccountStatusException && !$e instanceof CustomUserMessageAccountStatusException))) {
211
                    $e = new BadCredentialsException('Bad credentials.', 0, $e);
212
                }
213
214
                $response = $authenticator->failure($request, $e);
215
216
                if (null !== $this->eventDispatcher) {
217
                    $this->eventDispatcher->dispatch($event = new AuthenticationFailureEvent($e, $authenticator, $request, $response));
218
                    $response = $event->getResponse();
219
                }
220
221
                if (!$response instanceof ResponseInterface) {
222
                    throw $e;
223
                }
224
225
                return $response;
226
            }
227
        }
228
229
        return false;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     *
235
     * Checks if the attributes are granted against the current authentication token and optionally supplied subject.
236
     *
237
     * @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true
238
     */
239
    public function isGranted($attribute, $subject = null): bool
240
    {
241
        $token = $this->tokenStorage->getToken();
242
243
        if (!$token || !$token->getUser()) {
244
            $token = new NullToken();
245
        }
246
247
        return $this->accessDecisionManager->decide($token, [$attribute], $subject);
248
    }
249
}
250