Test Failed
Pull Request — master (#14)
by An
02:17
created

AuthZeroAuthenticatingHttpClient::stream()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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
    public function __construct(
22
        HttpClientInterface $client,
23
        private readonly AuthZeroConfiguration $authZeroConfiguration,
24
        private ArrayAdapter|CacheInterface|null $accessTokenCache = null,
25
    ) {
26
        if ($this->accessTokenCache === null) {
27
            $this->accessTokenCache = new ArrayAdapter();
28
        }
29
30
        $this->client = $client;
31
    }
32
33
    /**
34
     * Requests a JSON Web Token at Auth0 before executing the requested request.
35
     *
36
     * {@inheritdoc}
37 6
     */
38
    public function request(string $method, string $url, array $options = []): ResponseInterface
39
    {
40
        $this->appendAuthBearerToRequestOptions($options);
41
42 6
        return $this->client->request($method, $url, $options);
43 6
    }
44
45 6
    /**
46 6
     * Appends the 'auth_bearer' option with the retrieved access token from Auth0.
47
     */
48
    private function appendAuthBearerToRequestOptions(array &$options): void
49 6
    {
50 6
        if (isset($options['auth_bearer'])) {
51
            return;
52
        }
53
54
        $accessToken = $this->getAccessTokenFromCache();
55
        if ($accessToken instanceof AccessToken) {
56
            $options['auth_bearer'] = $accessToken->getToken();
57 5
        }
58
    }
59 5
60
    /**
61 5
     * Returns an access token from the cache by the configured audience in the AuthZeroConfiguration.
62
     */
63
    private function getAccessTokenFromCache(): ?AccessToken
64
    {
65
        // Replace invalid cache key characters with an underscore.
66
        $cacheKey = preg_replace('#[\{\}\(\)\/\\\@:]+#', '_', $this->authZeroConfiguration->getAudience());
67 1
68
        if (!$this->accessTokenCache instanceof CacheInterface) {
69 1
            return null;
70
        }
71
72
        return $this->accessTokenCache->get(
73
            $cacheKey,
74
            function (ItemInterface $item) {
75 5
                return $this->getNewAccessTokenForCache($item);
76
            }
77 5
        );
78 1
    }
79
80
    /**
81 4
     * Requests and returns a new AccessToken.
82 4
     *
83 2
     * This method is called by the access token cache on a cache miss.
84
     */
85 4
    private function getNewAccessTokenForCache(ItemInterface $item): ?AccessToken
86
    {
87
        $accessToken = $this->requestAccessToken();
88
89
        if ($accessToken !== null) {
90 4
            $item->expiresAfter($accessToken->getTtl());
91
        }
92
93 4
        return $accessToken;
94
    }
95 4
96 4
    /**
97
     * Requests an access token at Auth0.
98 4
     */
99 4
    private function requestAccessToken(): ?AccessToken
100
    {
101
        $response = $this->client->request(
102
            'POST',
103
            $this->authZeroConfiguration->getTenantTokenUrl(),
104
            [
105
                'json' => $this->authZeroConfiguration->getAuthenticationPayload(),
106
            ]
107
        );
108 4
109
        if ($response->getStatusCode() !== 200) {
110 4
            return null;
111
        }
112 4
113 2
        $responseJson = $response->toArray();
114
        if (isset($responseJson['access_token'], $responseJson['expires_in']) === false) {
115
            return null;
116 4
        }
117
118
        return new AccessToken($responseJson['access_token'], $responseJson['expires_in']);
119
    }
120
}
121