PluginProbe
Media Cloud Sync / trunk
Media Cloud Sync vtrunk
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / google / firebase / php-jwt / src / CachedKeySet.php

CachedKeySet.php in Media Cloud Sync trunk, at includes/sdk/google/firebase/php-jwt/src/CachedKeySet.php

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