FormLoginAuthenticator::supports()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 19
rs 9.2222
cc 6
nc 8
nop 1
1
<?php
2
3
/**
4
 * This file is part of php-scaffold
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Slick\WebStack\Infrastructure\Http\Authenticator;
13
14
use Slick\Http\Session\SessionDriverInterface;
15
use Slick\WebStack\Domain\Security\Authentication\Token\UsernamePasswordToken;
16
use Slick\WebStack\Domain\Security\Authentication\TokenInterface;
17
use Slick\WebStack\Domain\Security\Exception\AuthenticationException;
18
use Slick\WebStack\Domain\Security\Http\Authenticator\AuthenticatorHandlerInterface;
19
use Slick\WebStack\Domain\Security\Http\Authenticator\Passport;
20
use Slick\WebStack\Domain\Security\Http\Authenticator\Passport\Badge\Credentials\PasswordCredentials;
21
use Slick\WebStack\Domain\Security\Http\Authenticator\Passport\Badge\UserBadge;
22
use Slick\WebStack\Domain\Security\Http\Authenticator\PassportInterface;
23
use Slick\WebStack\Domain\Security\Http\AuthenticatorInterface;
24
use Slick\WebStack\Domain\Security\SecurityException;
25
use Slick\WebStack\Domain\Security\User\UserProviderInterface;
26
use Slick\WebStack\Domain\Security\UserInterface;
27
use Slick\WebStack\Infrastructure\Http\Authenticator\FormLoginAuthenticator\FormLoginProperties;
28
use Psr\Http\Message\ResponseInterface;
29
use Psr\Http\Message\ServerRequestInterface;
30
use Psr\Log\LoggerInterface;
31
use Slick\WebStack\Infrastructure\Http\Authenticator\FormLoginAuthenticator\LoginFormAuthenticatorHandler;
32
33
/**
34
 * FormLoginAuthenticator
35
 *
36
 * @package Slick\WebStack\Infrastructure\Http\Authenticator
37
 * @template TUser of UserInterface
38
 * @implements AuthenticatorInterface<TUser>
39
 * @SuppressWarnings(PHPMD)
40
 */
41
final class FormLoginAuthenticator implements AuthenticatorInterface
42
{
43
44
    use AuthenticatorHandlerTrait;
45
46
    private FormLoginProperties $properties;
47
48
    /**
49
     * Creates a FormLoginAuthenticator
50
     *
51
     * @param UserProviderInterface<TUser> $provider
52
     * @param AuthenticatorHandlerInterface $handler
53
     * @param SessionDriverInterface $session
54
     * @param FormLoginProperties|null $properties
55
     * @param LoggerInterface|null $logger
56
     */
57
    public function __construct(
58
        private readonly UserProviderInterface  $provider,
59
        AuthenticatorHandlerInterface           $handler,
60
        private readonly SessionDriverInterface $session,
61
        ?FormLoginProperties                    $properties = null,
62
        private readonly ?LoggerInterface       $logger = null,
63
    ) {
64
        $this->properties = $properties ?? new FormLoginProperties([]);
65
        $this->handler = $handler;
66
    }
67
68
    /**
69
     * @inheritDoc
70
     */
71
    public function supports(ServerRequestInterface $request): bool
72
    {
73
        $path = $request->getUri()->getPath();
74
        if (in_array($path, $this->properties->paths())) {
75
            return true;
76
        }
77
78
        $payload = $request->getParsedBody();
79
        if (!is_array($payload)) {
80
            return false;
81
        }
82
83
        $isInvalidPayload = !isset($payload[$this->properties->parameter('username')])
84
            || !isset($payload[$this->properties->parameter('password')]);
85
86
        $isNotPostMethod = $request->getMethod() !== "POST";
87
        $isNotCheckPath = $path !== $this->properties->path('check');
88
89
        return !($isNotPostMethod || $isNotCheckPath || $isInvalidPayload);
90
    }
91
92
    /**
93
     * @inheritDoc
94
     */
95
    public function authenticate(ServerRequestInterface $request): PassportInterface
96
    {
97
        list($userName, $password) = $this->parseCredentials($request);
98
99
        $userBadge = new UserBadge($userName, $this->provider);
100
        $credentials = new PasswordCredentials($password);
101
102
        return $this->handler->onAuthenticate($request, new Passport($userBadge, $credentials));
103
    }
104
105
    /**
106
     * @inheritDoc
107
     * @return TokenInterface<UserInterface>
108
     * @throws SecurityException
109
     */
110
    public function createToken(PassportInterface $passport): TokenInterface
111
    {
112
        $authToken = new UsernamePasswordToken($passport->user(), $passport->user()->roles());
113
        $authToken->withAttributes([
114
            'IS_AUTHENTICATED_FULLY' => 'true',
115
            'IS_AUTHENTICATED_REMEMBERED' => 'false',
116
            'IS_AUTHENTICATED' => 'true'
117
        ]);
118
        return $authToken;
119
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124
    public function onAuthenticationSuccess(ServerRequestInterface $request, TokenInterface $token): ?ResponseInterface
125
    {
126
        return $this->handler->onAuthenticationSuccess($request, $token);
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    public function onAuthenticationFailure(
133
        ServerRequestInterface $request,
134
        AuthenticationException $exception
135
    ): ?ResponseInterface {
136
137
        if ($request->getMethod() !== "POST") {
138
            if ($this->properties->useReferer()) {
139
                $referer = parse_url($request->getHeaderLine('referer'), PHP_URL_PATH);
140
                if (is_string($referer) && !in_array($referer, $this->properties->paths())) {
141
                    $this->session->get(LoginFormAuthenticatorHandler\RedirectHandler::LAST_URI, $referer);
142
                }
143
            }
144
145
            return null;
146
        }
147
148
        list($userName) = $this->parseCredentials($request);
149
        $this->logger?->info(
150
            'Authentication failed for user.',
151
            [
152
                'username' => $userName,
153
                'exception' => $exception,
154
            ]
155
        );
156
157
        return $this->handler->onAuthenticationFailure($request, $exception);
158
    }
159
160
161
    /**
162
     * @param ServerRequestInterface $request
163
     * @return string[]
164
     */
165
    private function parseCredentials(ServerRequestInterface $request): array
166
    {
167
        $parsedBody = $request->getParsedBody();
168
        if (!is_array($parsedBody)) {
169
            $code = $request->getMethod() !== "POST";
170
            throw new AuthenticationException(
171
                message: "The post data has no valid credentials. Please check your form field names in ".
172
                "your security configuration and try again.",
173
                code: $code ? 1 : 0
174
            );
175
        }
176
177
        $userName = $parsedBody[$this->properties->parameter('username')] ?? '';
178
        $password = $parsedBody[$this->properties->parameter('password')] ?? '';
179
180
        return array($userName, $password);
181
    }
182
183
    /**
184
     * @inheritDoc
185
     */
186
    public function clear(): void
187
    {
188
        // Do nothing
189
    }
190
}
191