Passed
Pull Request — master (#4)
by Niels
01:26
created

getAccessTokenFromCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Superbrave\AuthZeroHttpClient;
4
5
use Symfony\Component\Cache\Adapter\ArrayAdapter;
6
use Symfony\Contracts\Cache\CacheInterface;
7
use Symfony\Contracts\Cache\ItemInterface;
8
use Symfony\Contracts\HttpClient\HttpClientInterface;
9
use Symfony\Contracts\HttpClient\ResponseInterface;
10
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
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
    /**
20
     * @var HttpClientInterface
21
     */
22
    private $client;
23
24
    /**
25
     * @var AuthZeroConfiguration
26
     */
27
    private $authZeroConfiguration;
28
29
    /**
30
     * @var CacheInterface
31
     */
32
    private $accessTokenCache;
33
34
    /**
35
     * Constructs a new AuthZeroAuthenticatingHttpClient instance.
36
     *
37
     * @param HttpClientInterface   $client
38
     * @param AuthZeroConfiguration $authZeroConfiguration
39
     * @param CacheInterface|null   $accessTokenCache
40
     */
41 6
    public function __construct(
42
        HttpClientInterface $client,
43
        AuthZeroConfiguration $authZeroConfiguration,
44
        CacheInterface $accessTokenCache = null
45
    ) {
46 6
        $this->client = $client;
47 6
        $this->authZeroConfiguration = $authZeroConfiguration;
48
49 6
        if ($accessTokenCache === null) {
50 6
            $accessTokenCache = new ArrayAdapter();
51
        }
52
53 6
        $this->accessTokenCache = $accessTokenCache;
54 6
    }
55
56
    /**
57
     * Requests a JSON Web Token at Auth0 before executing the requested request.
58
     *
59
     * {@inheritdoc}
60
     */
61 5
    public function request(string $method, string $url, array $options = array()): ResponseInterface
62
    {
63 5
        $this->appendAuthBearerToRequestOptions($options);
64
65 5
        return $this->client->request($method, $url, $options);
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 1
    public function stream($responses, float $timeout = null): ResponseStreamInterface
72
    {
73 1
        return $this->client->stream($responses, $timeout);
74
    }
75
76
    /**
77
     * Appends the 'auth_bearer' option with the retrieved access token from Auth0.
78
     *
79
     * @param array $options
80
     */
81 5
    private function appendAuthBearerToRequestOptions(array &$options): void
82
    {
83 5
        if (isset($options['auth_bearer'])) {
84 1
            return;
85
        }
86
87 4
        $accessToken = $this->getAccessTokenFromCache();
88 4
        if ($accessToken instanceof AccessToken) {
89 2
            $options['auth_bearer'] = $accessToken->getToken();
90
        }
91 4
    }
92
93
    /**
94
     * Returns an access token from the cache by the configured audience in the AuthZeroConfiguration.
95
     *
96
     * @return AccessToken|null
97
     */
98 4
    private function getAccessTokenFromCache(): ?AccessToken
99
    {
100
        // Replace invalid cache key characters with an underscore.
101 4
        $cacheKey = preg_replace('#[\{\}\(\)\/\\\@:]+#', '_', $this->authZeroConfiguration->getAudience());
102
103 4
        return $this->accessTokenCache->get(
104 4
            $cacheKey,
105
            function (ItemInterface $item) {
106 4
                return $this->getNewAccessTokenForCache($item);
107 4
            }
108
        );
109
    }
110
111
    /**
112
     * Requests and returns a new AccessToken.
113
     *
114
     * This method is called by the access token cache on a cache miss.
115
     *
116
     * @param ItemInterface $item
117
     *
118
     * @return AccessToken|null
119
     */
120 4
    private function getNewAccessTokenForCache(ItemInterface $item): ?AccessToken
121
    {
122 4
        $accessToken = $this->requestAccessToken();
123
124 4
        if ($accessToken !== null) {
125 2
            $item->expiresAfter($accessToken->getTtl());
126
        }
127
128 4
        return $accessToken;
129
    }
130
131
    /**
132
     * Requests an access token at Auth0.
133
     *
134
     * @return AccessToken|null
135
     */
136 4
    private function requestAccessToken(): ?AccessToken
137
    {
138 4
        $response = $this->client->request(
139 4
            'POST',
140 4
            $this->authZeroConfiguration->getTenantTokenUrl(),
141
            array(
142 4
                'json' => $this->authZeroConfiguration->getAuthenticationPayload(),
143
            )
144
        );
145
146 4
        if ($response->getStatusCode() !== 200) {
147 1
            return null;
148
        }
149
150 3
        $responseJson = $response->toArray();
151 3
        if (isset($responseJson['access_token'], $responseJson['expires_in']) === false) {
152 1
            return null;
153
        }
154
155 2
        return new AccessToken($responseJson['access_token'], $responseJson['expires_in']);
156
    }
157
}
158