PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / firebase / php-jwt / src / CachedKeySet.php
ameliabooking / vendor / firebase / php-jwt / src Last commit date
BeforeValidException.php 6 months ago CachedKeySet.php 6 months ago ExpiredException.php 6 months ago JWK.php 6 months ago JWT.php 6 months ago JWTExceptionWithPayloadInterface.php 6 months ago Key.php 6 months ago SignatureInvalidException.php 6 months ago
CachedKeySet.php
269 lines
1 <?php
2
3 namespace AmeliaVendor\Firebase\JWT;
4
5 use ArrayAccess;
6 use InvalidArgumentException;
7 use LogicException;
8 use OutOfBoundsException;
9 use AmeliaVendor\Psr\Cache\CacheItemInterface;
10 use AmeliaVendor\Psr\Cache\CacheItemPoolInterface;
11 use AmeliaVendor\Psr\Http\Client\ClientInterface;
12 use AmeliaVendor\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 if ($jwksResponse->getStatusCode() !== 200) {
182 throw new UnexpectedValueException(
183 sprintf('HTTP Error: %d %s for URI "%s"',
184 $jwksResponse->getStatusCode(),
185 $jwksResponse->getReasonPhrase(),
186 $this->jwksUri,
187 ),
188 $jwksResponse->getStatusCode()
189 );
190 }
191 $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
192
193 if (!isset($this->keySet[$keyId])) {
194 return false;
195 }
196
197 $item = $this->getCacheItem();
198 $item->set($this->keySet);
199 if ($this->expiresAfter) {
200 $item->expiresAfter($this->expiresAfter);
201 }
202 $this->cache->save($item);
203 }
204
205 return true;
206 }
207
208 private function rateLimitExceeded(): bool
209 {
210 if (!$this->rateLimit) {
211 return false;
212 }
213
214 $cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
215 if (!$cacheItem->isHit()) {
216 $cacheItem->expiresAfter(1); // # of calls are cached each minute
217 }
218
219 $callsPerMinute = (int) $cacheItem->get();
220 if (++$callsPerMinute > $this->maxCallsPerMinute) {
221 return true;
222 }
223 $cacheItem->set($callsPerMinute);
224 $this->cache->save($cacheItem);
225 return false;
226 }
227
228 private function getCacheItem(): CacheItemInterface
229 {
230 if (\is_null($this->cacheItem)) {
231 $this->cacheItem = $this->cache->getItem($this->cacheKey);
232 }
233
234 return $this->cacheItem;
235 }
236
237 private function setCacheKeys(): void
238 {
239 if (empty($this->jwksUri)) {
240 throw new RuntimeException('JWKS URI is empty');
241 }
242
243 // ensure we do not have illegal characters
244 $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
245
246 // add prefix
247 $key = $this->cacheKeyPrefix . $key;
248
249 // Hash keys if they exceed $maxKeyLength of 64
250 if (\strlen($key) > $this->maxKeyLength) {
251 $key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
252 }
253
254 $this->cacheKey = $key;
255
256 if ($this->rateLimit) {
257 // add prefix
258 $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
259
260 // Hash keys if they exceed $maxKeyLength of 64
261 if (\strlen($rateLimitKey) > $this->maxKeyLength) {
262 $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
263 }
264
265 $this->rateLimitCacheKey = $rateLimitKey;
266 }
267 }
268 }
269