Passed
Push — master ( c858e1...becd64 )
by Filipe
12:31 queued 19s
created

AccessTokenAuthenticator   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 103
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
eloc 28
dl 0
loc 103
rs 10
c 1
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A supports() 0 3 1
A __construct() 0 4 1
A onAuthenticationSuccess() 0 3 1
A clear() 0 2 1
A authenticate() 0 9 2
A createToken() 0 9 1
A onAuthenticationFailure() 0 8 1
A getAuthenticateHeader() 0 15 4
1
<?php
2
3
/**
4
 * This file is part of web-stack
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 Psr\Http\Message\ResponseInterface;
15
use Psr\Http\Message\ServerRequestInterface;
16
use Slick\Http\Message\Response;
17
use Slick\WebStack\Domain\Security\Authentication\Token\UserToken;
18
use Slick\WebStack\Domain\Security\Authentication\TokenInterface;
19
use Slick\WebStack\Domain\Security\Exception\AuthenticationException;
20
use Slick\WebStack\Domain\Security\Exception\BadCredentialsException;
21
use Slick\WebStack\Domain\Security\Http\AccessToken\AccessTokenExtractorInterface;
22
use Slick\WebStack\Domain\Security\Http\AccessToken\AccessTokenHandlerInterface;
23
use Slick\WebStack\Domain\Security\Http\Authenticator\PassportInterface;
24
use Slick\WebStack\Domain\Security\Http\Authenticator\SelfValidatingPassport;
25
use Slick\WebStack\Domain\Security\Http\AuthenticatorInterface;
26
use Slick\WebStack\Domain\Security\SecurityException;
27
use Slick\WebStack\Domain\Security\UserInterface;
28
29
/**
30
 * AccessTokenAuthenticator
31
 *
32
 * @package Slick\WebStack\Infrastructure\Http\Authenticator
33
 * @implements AuthenticatorInterface<UserInterface>
34
 */
35
final class AccessTokenAuthenticator implements AuthenticatorInterface
36
{
37
38
    use AuthenticatorHandlerTrait;
39
40
    /**
41
     * Creates a AccessTokenAuthenticator
42
     *
43
     * @param AccessTokenExtractorInterface $extractor
44
     * @param AccessTokenHandlerInterface $tokenHandler
45
     */
46
    public function __construct(
47
        private readonly AccessTokenExtractorInterface $extractor,
48
        private readonly AccessTokenHandlerInterface   $tokenHandler
49
    ) {
50
    }
51
52
    /**
53
     * @inheritDoc
54
     */
55
    public function supports(ServerRequestInterface $request): bool
56
    {
57
        return is_string($this->extractor->extractAccessToken($request));
58
    }
59
60
    /**
61
     * @inheritDoc
62
     */
63
    public function authenticate(ServerRequestInterface $request): PassportInterface
64
    {
65
        $accessToken = $this->extractor->extractAccessToken($request);
66
        if (null === $accessToken) {
67
            throw new BadCredentialsException("Invalid credentials");
68
        }
69
70
        $userBadge = $this->tokenHandler->userBadgeFromToken($accessToken);
71
        return new SelfValidatingPassport($userBadge);
72
    }
73
74
    /**
75
     * @inheritDoc
76
     * @throws SecurityException
77
     */
78
    public function createToken(PassportInterface $passport): TokenInterface
79
    {
80
        $userToken = new UserToken($passport->user());
81
        $userToken->withAttributes([
82
            'IS_AUTHENTICATED_FULLY' => 'true',
83
            'IS_AUTHENTICATED_REMEMBERED' => 'false',
84
            'IS_AUTHENTICATED' => 'true'
85
        ]);
86
        return $userToken;
87
    }
88
89
    /**
90
     * @inheritDoc
91
     */
92
    public function onAuthenticationSuccess(ServerRequestInterface $request, TokenInterface $token): ?ResponseInterface
93
    {
94
        return null;
95
    }
96
97
    /**
98
     * @inheritDoc
99
     */
100
    public function onAuthenticationFailure(
101
        ServerRequestInterface $request,
102
        AuthenticationException $exception
103
    ): ResponseInterface {
104
        return new Response(
105
            401,
106
            'Unauthorized',
107
            ['WWW-Authenticate' => $this->getAuthenticateHeader($exception->getMessage())]
108
        );
109
    }
110
111
112
    /**
113
     * @inheritDoc
114
     */
115
    public function clear(): void
116
    {
117
        // noop
118
    }
119
120
    /**
121
     * @see https://datatracker.ietf.org/doc/html/rfc6750#section-3
122
     */
123
    private function getAuthenticateHeader(?string $errorDescription = null): string
124
    {
125
        $data = [
126
            'error' => 'invalid_token',
127
            'error_description' => $errorDescription,
128
        ];
129
        $values = [];
130
        foreach ($data as $k => $v) {
131
            if (null === $v || '' === $v) {
132
                continue;
133
            }
134
            $values[] = sprintf('%s="%s"', $k, $v);
135
        }
136
137
        return sprintf('Bearer %s', implode(',', $values));
138
    }
139
}
140