Passed
Pull Request — master (#14)
by An
01:40
created

AuthZeroAuthenticatingHttpClient   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 102
Duplicated Lines 0 %

Test Coverage

Coverage 97.62%

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 33
c 8
b 0
f 0
dl 0
loc 102
ccs 41
cts 42
cp 0.9762
rs 10
wmc 13

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getAccessTokenFromCache() 0 13 2
A __construct() 0 10 2
A getNewAccessTokenForCache() 0 9 2
A requestAccessToken() 0 20 3
A appendAuthBearerToRequestOptions() 0 9 3
A request() 0 5 1
1
<?php
2
3
namespace Superbrave\AuthZeroHttpClient;
4
5
use Symfony\Component\Cache\Adapter\ArrayAdapter;
6
use Symfony\Component\HttpClient\DecoratorTrait;
7
use Symfony\Contracts\Cache\CacheInterface;
8
use Symfony\Contracts\Cache\ItemInterface;
9
use Symfony\Contracts\HttpClient\HttpClientInterface;
10
use Symfony\Contracts\HttpClient\ResponseInterface;
11
12
/**
13
 * Handles authentication with Auth0 before actual requests are made.
14
 *
15
 * @author Niels Nijens <[email protected]>
16
 */
17
class AuthZeroAuthenticatingHttpClient implements HttpClientInterface
18
{
19
    use DecoratorTrait;
20
21 6
    public function __construct(
22
        HttpClientInterface $client,
23
        private readonly AuthZeroConfiguration $authZeroConfiguration,
24
        private ArrayAdapter|CacheInterface|null $accessTokenCache = null,
25
    ) {
26 6
        if ($this->accessTokenCache === null) {
27 6
            $this->accessTokenCache = new ArrayAdapter();
28
        }
29
30 6
        $this->client = $client;
31
    }
32
33
    /**
34
     * Requests a JSON Web Token at Auth0 before executing the requested request.
35
     *
36
     * {@inheritdoc}
37
     */
38 5
    public function request(string $method, string $url, array $options = []): ResponseInterface
39
    {
40 5
        $this->appendAuthBearerToRequestOptions($options);
41
42 5
        return $this->client->request($method, $url, $options);
43
    }
44
45
    /**
46
     * Appends the 'auth_bearer' option with the retrieved access token from Auth0.
47
     */
48 5
    private function appendAuthBearerToRequestOptions(array &$options): void
49
    {
50 5
        if (isset($options['auth_bearer'])) {
51 1
            return;
52
        }
53
54 4
        $accessToken = $this->getAccessTokenFromCache();
55 4
        if ($accessToken instanceof AccessToken) {
56 2
            $options['auth_bearer'] = $accessToken->getToken();
57
        }
58
    }
59
60
    /**
61
     * Returns an access token from the cache by the configured audience in the AuthZeroConfiguration.
62
     */
63 4
    private function getAccessTokenFromCache(): ?AccessToken
64
    {
65
        // Replace invalid cache key characters with an underscore.
66 4
        $cacheKey = preg_replace('#[\{\}\(\)\/\\\@:]+#', '_', $this->authZeroConfiguration->getAudience());
67
68 4
        if (!$this->accessTokenCache instanceof CacheInterface) {
69
            return null;
70
        }
71
72 4
        return $this->accessTokenCache->get(
73 4
            $cacheKey,
74 4
            function (ItemInterface $item) {
75 4
                return $this->getNewAccessTokenForCache($item);
76 4
            }
77 4
        );
78
    }
79
80
    /**
81
     * Requests and returns a new AccessToken.
82
     *
83
     * This method is called by the access token cache on a cache miss.
84
     */
85 4
    private function getNewAccessTokenForCache(ItemInterface $item): ?AccessToken
86
    {
87 4
        $accessToken = $this->requestAccessToken();
88
89 4
        if ($accessToken !== null) {
90 2
            $item->expiresAfter($accessToken->getTtl());
91
        }
92
93 4
        return $accessToken;
94
    }
95
96
    /**
97
     * Requests an access token at Auth0.
98
     */
99 4
    private function requestAccessToken(): ?AccessToken
100
    {
101 4
        $response = $this->client->request(
102 4
            'POST',
103 4
            $this->authZeroConfiguration->getTenantTokenUrl(),
104 4
            [
105 4
                'json' => $this->authZeroConfiguration->getAuthenticationPayload(),
106 4
            ]
107 4
        );
108
109 4
        if ($response->getStatusCode() !== 200) {
110 1
            return null;
111
        }
112
113 3
        $responseJson = $response->toArray();
114 3
        if (isset($responseJson['access_token'], $responseJson['expires_in']) === false) {
115 1
            return null;
116
        }
117
118 2
        return new AccessToken($responseJson['access_token'], $responseJson['expires_in']);
119
    }
120
}
121