PluginProbe
Authorizer / 3.9.0
Authorizer v3.9.0
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
authorizer / vendor / firebase / php-jwt / src / CachedKeySet.php

CachedKeySet.php in Authorizer 3.9.0, at vendor/firebase/php-jwt/src/CachedKeySet.php

259 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Firebase\JWT;
4
5 use ArrayAccess;
6 use InvalidArgumentException;
7 use LogicException;
8 use OutOfBoundsException;
9 use Psr\Cache\CacheItemInterface;
10 use Psr\Cache\CacheItemPoolInterface;
11 use Psr\Http\Client\ClientInterface;
12 use Psr\Http\Message\RequestFactoryInterface;
13 use RuntimeException;
14 use UnexpectedValueException;
15
16 /**
17 * @implements ArrayAccess<string, Key>
18 */
19 class CachedKeySet implements ArrayAccess
20 {
21 /**
22 * @var string
23 */
24 private $jwksUri;
25 /**
26 * @var ClientInterface
27 */
28 private $httpClient;
29 /**
30 * @var RequestFactoryInterface
31 */
32 private $httpFactory;
33 /**
34 * @var CacheItemPoolInterface
35 */
36 private $cache;
37 /**
38 * @var ?int
39 */
40 private $expiresAfter;
41 /**
42 * @var ?CacheItemInterface
43 */
44 private $cacheItem;
45 /**
46 * @var array<string, array<mixed>>
47 */
48 private $keySet;
49 /**
50 * @var string
51 */
52 private $cacheKey;
53 /**
54 * @var string
55 */
56 private $cacheKeyPrefix = 'jwks';
57 /**
58 * @var int
59 */
60 private $maxKeyLength = 64;
61 /**
62 * @var bool
63 */
64 private $rateLimit;
65 /**
66 * @var string
67 */
68 private $rateLimitCacheKey;
69 /**
70 * @var int
71 */
72 private $maxCallsPerMinute = 10;
73 /**
74 * @var string|null
75 */
76 private $defaultAlg;
77
78 public function __construct(
79 string $jwksUri,
80 ClientInterface $httpClient,
81 RequestFactoryInterface $httpFactory,
82 CacheItemPoolInterface $cache,
83 int $expiresAfter = null,
84 bool $rateLimit = false,
85 string $defaultAlg = null
86 ) {
87 $this->jwksUri = $jwksUri;
88 $this->httpClient = $httpClient;
89 $this->httpFactory = $httpFactory;
90 $this->cache = $cache;
91 $this->expiresAfter = $expiresAfter;
92 $this->rateLimit = $rateLimit;
93 $this->defaultAlg = $defaultAlg;
94 $this->setCacheKeys();
95 }
96
97 /**
98 * @param string $keyId
99 * @return Key
100 */
101 public function offsetGet($keyId): Key
102 {
103 if (!$this->keyIdExists($keyId)) {
104 throw new OutOfBoundsException('Key ID not found');
105 }
106 return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
107 }
108
109 /**
110 * @param string $keyId
111 * @return bool
112 */
113 public function offsetExists($keyId): bool
114 {
115 return $this->keyIdExists($keyId);
116 }
117
118 /**
119 * @param string $offset
120 * @param Key $value
121 */
122 public function offsetSet($offset, $value): void
123 {
124 throw new LogicException('Method not implemented');
125 }
126
127 /**
128 * @param string $offset
129 */
130 public function offsetUnset($offset): void
131 {
132 throw new LogicException('Method not implemented');
133 }
134
135 /**
136 * @return array<mixed>
137 */
138 private function formatJwksForCache(string $jwks): array
139 {
140 $jwks = json_decode($jwks, true);
141
142 if (!isset($jwks['keys'])) {
143 throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
144 }
145
146 if (empty($jwks['keys'])) {
147 throw new InvalidArgumentException('JWK Set did not contain any keys');
148 }
149
150 $keys = [];
151 foreach ($jwks['keys'] as $k => $v) {
152 $kid = isset($v['kid']) ? $v['kid'] : $k;
153 $keys[(string) $kid] = $v;
154 }
155
156 return $keys;
157 }
158
159 private function keyIdExists(string $keyId): bool
160 {
161 if (null === $this->keySet) {
162 $item = $this->getCacheItem();
163 // Try to load keys from cache
164 if ($item->isHit()) {
165 // item found! retrieve it
166 $this->keySet = $item->get();
167 // If the cached item is a string, the JWKS response was cached (previous behavior).
168 // Parse this into expected format array<kid, jwk> instead.
169 if (\is_string($this->keySet)) {
170 $this->keySet = $this->formatJwksForCache($this->keySet);
171 }
172 }
173 }
174
175 if (!isset($this->keySet[$keyId])) {
176 if ($this->rateLimitExceeded()) {
177 return false;
178 }
179 $request = $this->httpFactory->createRequest('GET', $this->jwksUri);
180 $jwksResponse = $this->httpClient->sendRequest($request);
181 $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
182
183 if (!isset($this->keySet[$keyId])) {
184 return false;
185 }
186
187 $item = $this->getCacheItem();
188 $item->set($this->keySet);
189 if ($this->expiresAfter) {
190 $item->expiresAfter($this->expiresAfter);
191 }
192 $this->cache->save($item);
193 }
194
195 return true;
196 }
197
198 private function rateLimitExceeded(): bool
199 {
200 if (!$this->rateLimit) {
201 return false;
202 }
203
204 $cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
205 if (!$cacheItem->isHit()) {
206 $cacheItem->expiresAfter(1); // # of calls are cached each minute
207 }
208
209 $callsPerMinute = (int) $cacheItem->get();
210 if (++$callsPerMinute > $this->maxCallsPerMinute) {
211 return true;
212 }
213 $cacheItem->set($callsPerMinute);
214 $this->cache->save($cacheItem);
215 return false;
216 }
217
218 private function getCacheItem(): CacheItemInterface
219 {
220 if (\is_null($this->cacheItem)) {
221 $this->cacheItem = $this->cache->getItem($this->cacheKey);
222 }
223
224 return $this->cacheItem;
225 }
226
227 private function setCacheKeys(): void
228 {
229 if (empty($this->jwksUri)) {
230 throw new RuntimeException('JWKS URI is empty');
231 }
232
233 // ensure we do not have illegal characters
234 $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
235
236 // add prefix
237 $key = $this->cacheKeyPrefix . $key;
238
239 // Hash keys if they exceed $maxKeyLength of 64
240 if (\strlen($key) > $this->maxKeyLength) {
241 $key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
242 }
243
244 $this->cacheKey = $key;
245
246 if ($this->rateLimit) {
247 // add prefix
248 $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
249
250 // Hash keys if they exceed $maxKeyLength of 64
251 if (\strlen($rateLimitKey) > $this->maxKeyLength) {
252 $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
253 }
254
255 $this->rateLimitCacheKey = $rateLimitKey;
256 }
257 }
258 }
259