[f9c482b] | 1 | <?php
|
---|
| 2 | /*
|
---|
| 3 | * Copyright 2019 Google LLC
|
---|
| 4 | *
|
---|
| 5 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
| 6 | * you may not use this file except in compliance with the License.
|
---|
| 7 | * You may obtain a copy of the License at
|
---|
| 8 | *
|
---|
| 9 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
| 10 | *
|
---|
| 11 | * Unless required by applicable law or agreed to in writing, software
|
---|
| 12 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
| 14 | * See the License for the specific language governing permissions and
|
---|
| 15 | * limitations under the License.
|
---|
| 16 | */
|
---|
| 17 |
|
---|
| 18 | namespace Google\Auth;
|
---|
| 19 |
|
---|
| 20 | use DateTime;
|
---|
| 21 | use Firebase\JWT\ExpiredException;
|
---|
| 22 | use Firebase\JWT\JWT;
|
---|
| 23 | use Firebase\JWT\Key;
|
---|
| 24 | use Firebase\JWT\SignatureInvalidException;
|
---|
| 25 | use Google\Auth\Cache\MemoryCacheItemPool;
|
---|
| 26 | use Google\Auth\HttpHandler\HttpClientCache;
|
---|
| 27 | use Google\Auth\HttpHandler\HttpHandlerFactory;
|
---|
| 28 | use GuzzleHttp\Psr7\Request;
|
---|
| 29 | use GuzzleHttp\Psr7\Utils;
|
---|
| 30 | use InvalidArgumentException;
|
---|
| 31 | use phpseclib3\Crypt\PublicKeyLoader;
|
---|
| 32 | use phpseclib3\Crypt\RSA;
|
---|
| 33 | use phpseclib3\Math\BigInteger;
|
---|
| 34 | use Psr\Cache\CacheItemPoolInterface;
|
---|
| 35 | use RuntimeException;
|
---|
| 36 | use SimpleJWT\InvalidTokenException;
|
---|
| 37 | use SimpleJWT\JWT as SimpleJWT;
|
---|
| 38 | use SimpleJWT\Keys\KeyFactory;
|
---|
| 39 | use SimpleJWT\Keys\KeySet;
|
---|
| 40 | use TypeError;
|
---|
| 41 | use UnexpectedValueException;
|
---|
| 42 |
|
---|
| 43 | /**
|
---|
| 44 | * Wrapper around Google Access Tokens which provides convenience functions.
|
---|
| 45 | *
|
---|
| 46 | * @experimental
|
---|
| 47 | */
|
---|
| 48 | class AccessToken
|
---|
| 49 | {
|
---|
| 50 | const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
|
---|
| 51 | const IAP_CERT_URL = 'https://www.gstatic.com/iap/verify/public_key-jwk';
|
---|
| 52 | const IAP_ISSUER = 'https://cloud.google.com/iap';
|
---|
| 53 | const OAUTH2_ISSUER = 'accounts.google.com';
|
---|
| 54 | const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
|
---|
| 55 | const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
|
---|
| 56 |
|
---|
| 57 | /**
|
---|
| 58 | * @var callable
|
---|
| 59 | */
|
---|
| 60 | private $httpHandler;
|
---|
| 61 |
|
---|
| 62 | /**
|
---|
| 63 | * @var CacheItemPoolInterface
|
---|
| 64 | */
|
---|
| 65 | private $cache;
|
---|
| 66 |
|
---|
| 67 | /**
|
---|
| 68 | * @param callable|null $httpHandler [optional] An HTTP Handler to deliver PSR-7 requests.
|
---|
| 69 | * @param CacheItemPoolInterface|null $cache [optional] A PSR-6 compatible cache implementation.
|
---|
| 70 | */
|
---|
| 71 | public function __construct(
|
---|
| 72 | ?callable $httpHandler = null,
|
---|
| 73 | ?CacheItemPoolInterface $cache = null
|
---|
| 74 | ) {
|
---|
| 75 | $this->httpHandler = $httpHandler
|
---|
| 76 | ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
|
---|
| 77 | $this->cache = $cache ?: new MemoryCacheItemPool();
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | /**
|
---|
| 81 | * Verifies an id token and returns the authenticated apiLoginTicket.
|
---|
| 82 | * Throws an exception if the id token is not valid.
|
---|
| 83 | * The audience parameter can be used to control which id tokens are
|
---|
| 84 | * accepted. By default, the id token must have been issued to this OAuth2 client.
|
---|
| 85 | *
|
---|
| 86 | * @param string $token The JSON Web Token to be verified.
|
---|
| 87 | * @param array<mixed> $options [optional] {
|
---|
| 88 | * Configuration options.
|
---|
| 89 | * @type string $audience The indended recipient of the token.
|
---|
| 90 | * @type string $issuer The intended issuer of the token.
|
---|
| 91 | * @type string $cacheKey The cache key of the cached certs. Defaults to
|
---|
| 92 | * the sha1 of $certsLocation if provided, otherwise is set to
|
---|
| 93 | * "federated_signon_certs_v3".
|
---|
| 94 | * @type string $certsLocation The location (remote or local) from which
|
---|
| 95 | * to retrieve certificates, if not cached. This value should only be
|
---|
| 96 | * provided in limited circumstances in which you are sure of the
|
---|
| 97 | * behavior.
|
---|
| 98 | * @type bool $throwException Whether the function should throw an
|
---|
| 99 | * exception if the verification fails. This is useful for
|
---|
| 100 | * determining the reason verification failed.
|
---|
| 101 | * }
|
---|
| 102 | * @return array<mixed>|false the token payload, if successful, or false if not.
|
---|
| 103 | * @throws InvalidArgumentException If certs could not be retrieved from a local file.
|
---|
| 104 | * @throws InvalidArgumentException If received certs are in an invalid format.
|
---|
| 105 | * @throws InvalidArgumentException If the cert alg is not supported.
|
---|
| 106 | * @throws RuntimeException If certs could not be retrieved from a remote location.
|
---|
| 107 | * @throws UnexpectedValueException If the token issuer does not match.
|
---|
| 108 | * @throws UnexpectedValueException If the token audience does not match.
|
---|
| 109 | */
|
---|
| 110 | public function verify($token, array $options = [])
|
---|
| 111 | {
|
---|
| 112 | $audience = $options['audience'] ?? null;
|
---|
| 113 | $issuer = $options['issuer'] ?? null;
|
---|
| 114 | $certsLocation = $options['certsLocation'] ?? self::FEDERATED_SIGNON_CERT_URL;
|
---|
| 115 | $cacheKey = $options['cacheKey'] ?? $this->getCacheKeyFromCertLocation($certsLocation);
|
---|
| 116 | $throwException = $options['throwException'] ?? false; // for backwards compatibility
|
---|
| 117 |
|
---|
| 118 | // Check signature against each available cert.
|
---|
| 119 | $certs = $this->getCerts($certsLocation, $cacheKey, $options);
|
---|
| 120 | $alg = $this->determineAlg($certs);
|
---|
| 121 | if (!in_array($alg, ['RS256', 'ES256'])) {
|
---|
| 122 | throw new InvalidArgumentException(
|
---|
| 123 | 'unrecognized "alg" in certs, expected ES256 or RS256'
|
---|
| 124 | );
|
---|
| 125 | }
|
---|
| 126 | try {
|
---|
| 127 | if ($alg == 'RS256') {
|
---|
| 128 | return $this->verifyRs256($token, $certs, $audience, $issuer);
|
---|
| 129 | }
|
---|
| 130 | return $this->verifyEs256($token, $certs, $audience, $issuer);
|
---|
| 131 | } catch (ExpiredException $e) { // firebase/php-jwt 5+
|
---|
| 132 | } catch (SignatureInvalidException $e) { // firebase/php-jwt 5+
|
---|
| 133 | } catch (InvalidTokenException $e) { // simplejwt
|
---|
| 134 | } catch (InvalidArgumentException $e) {
|
---|
| 135 | } catch (UnexpectedValueException $e) {
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | if ($throwException) {
|
---|
| 139 | throw $e;
|
---|
| 140 | }
|
---|
| 141 |
|
---|
| 142 | return false;
|
---|
| 143 | }
|
---|
| 144 |
|
---|
| 145 | /**
|
---|
| 146 | * Identifies the expected algorithm to verify by looking at the "alg" key
|
---|
| 147 | * of the provided certs.
|
---|
| 148 | *
|
---|
| 149 | * @param array<mixed> $certs Certificate array according to the JWK spec (see
|
---|
| 150 | * https://tools.ietf.org/html/rfc7517).
|
---|
| 151 | * @return string The expected algorithm, such as "ES256" or "RS256".
|
---|
| 152 | */
|
---|
| 153 | private function determineAlg(array $certs)
|
---|
| 154 | {
|
---|
| 155 | $alg = null;
|
---|
| 156 | foreach ($certs as $cert) {
|
---|
| 157 | if (empty($cert['alg'])) {
|
---|
| 158 | throw new InvalidArgumentException(
|
---|
| 159 | 'certs expects "alg" to be set'
|
---|
| 160 | );
|
---|
| 161 | }
|
---|
| 162 | $alg = $alg ?: $cert['alg'];
|
---|
| 163 |
|
---|
| 164 | if ($alg != $cert['alg']) {
|
---|
| 165 | throw new InvalidArgumentException(
|
---|
| 166 | 'More than one alg detected in certs'
|
---|
| 167 | );
|
---|
| 168 | }
|
---|
| 169 | }
|
---|
| 170 | return $alg;
|
---|
| 171 | }
|
---|
| 172 |
|
---|
| 173 | /**
|
---|
| 174 | * Verifies an ES256-signed JWT.
|
---|
| 175 | *
|
---|
| 176 | * @param string $token The JSON Web Token to be verified.
|
---|
| 177 | * @param array<mixed> $certs Certificate array according to the JWK spec (see
|
---|
| 178 | * https://tools.ietf.org/html/rfc7517).
|
---|
| 179 | * @param string|null $audience If set, returns false if the provided
|
---|
| 180 | * audience does not match the "aud" claim on the JWT.
|
---|
| 181 | * @param string|null $issuer If set, returns false if the provided
|
---|
| 182 | * issuer does not match the "iss" claim on the JWT.
|
---|
| 183 | * @return array<mixed> the token payload, if successful, or false if not.
|
---|
| 184 | */
|
---|
| 185 | private function verifyEs256($token, array $certs, $audience = null, $issuer = null)
|
---|
| 186 | {
|
---|
| 187 | $this->checkSimpleJwt();
|
---|
| 188 |
|
---|
| 189 | $jwkset = new KeySet();
|
---|
| 190 | foreach ($certs as $cert) {
|
---|
| 191 | $jwkset->add(KeyFactory::create($cert, 'php'));
|
---|
| 192 | }
|
---|
| 193 |
|
---|
| 194 | // Validate the signature using the key set and ES256 algorithm.
|
---|
| 195 | $jwt = $this->callSimpleJwtDecode([$token, $jwkset, 'ES256']);
|
---|
| 196 | $payload = $jwt->getClaims();
|
---|
| 197 |
|
---|
| 198 | if ($audience) {
|
---|
| 199 | if (!isset($payload['aud']) || $payload['aud'] != $audience) {
|
---|
| 200 | throw new UnexpectedValueException('Audience does not match');
|
---|
| 201 | }
|
---|
| 202 | }
|
---|
| 203 |
|
---|
| 204 | // @see https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
|
---|
| 205 | $issuer = $issuer ?: self::IAP_ISSUER;
|
---|
| 206 | if (!isset($payload['iss']) || $payload['iss'] !== $issuer) {
|
---|
| 207 | throw new UnexpectedValueException('Issuer does not match');
|
---|
| 208 | }
|
---|
| 209 |
|
---|
| 210 | return $payload;
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | /**
|
---|
| 214 | * Verifies an RS256-signed JWT.
|
---|
| 215 | *
|
---|
| 216 | * @param string $token The JSON Web Token to be verified.
|
---|
| 217 | * @param array<mixed> $certs Certificate array according to the JWK spec (see
|
---|
| 218 | * https://tools.ietf.org/html/rfc7517).
|
---|
| 219 | * @param string|null $audience If set, returns false if the provided
|
---|
| 220 | * audience does not match the "aud" claim on the JWT.
|
---|
| 221 | * @param string|null $issuer If set, returns false if the provided
|
---|
| 222 | * issuer does not match the "iss" claim on the JWT.
|
---|
| 223 | * @return array<mixed> the token payload, if successful, or false if not.
|
---|
| 224 | */
|
---|
| 225 | private function verifyRs256($token, array $certs, $audience = null, $issuer = null)
|
---|
| 226 | {
|
---|
| 227 | $this->checkAndInitializePhpsec();
|
---|
| 228 | $keys = [];
|
---|
| 229 | foreach ($certs as $cert) {
|
---|
| 230 | if (empty($cert['kid'])) {
|
---|
| 231 | throw new InvalidArgumentException(
|
---|
| 232 | 'certs expects "kid" to be set'
|
---|
| 233 | );
|
---|
| 234 | }
|
---|
| 235 | if (empty($cert['n']) || empty($cert['e'])) {
|
---|
| 236 | throw new InvalidArgumentException(
|
---|
| 237 | 'RSA certs expects "n" and "e" to be set'
|
---|
| 238 | );
|
---|
| 239 | }
|
---|
| 240 | $publicKey = $this->loadPhpsecPublicKey($cert['n'], $cert['e']);
|
---|
| 241 |
|
---|
| 242 | // create an array of key IDs to certs for the JWT library
|
---|
| 243 | $keys[$cert['kid']] = new Key($publicKey, 'RS256');
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | $payload = $this->callJwtStatic('decode', [
|
---|
| 247 | $token,
|
---|
| 248 | $keys,
|
---|
| 249 | ]);
|
---|
| 250 |
|
---|
| 251 | if ($audience) {
|
---|
| 252 | if (!property_exists($payload, 'aud') || $payload->aud != $audience) {
|
---|
| 253 | throw new UnexpectedValueException('Audience does not match');
|
---|
| 254 | }
|
---|
| 255 | }
|
---|
| 256 |
|
---|
| 257 | // support HTTP and HTTPS issuers
|
---|
| 258 | // @see https://developers.google.com/identity/sign-in/web/backend-auth
|
---|
| 259 | $issuers = $issuer ? [$issuer] : [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
|
---|
| 260 | if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
|
---|
| 261 | throw new UnexpectedValueException('Issuer does not match');
|
---|
| 262 | }
|
---|
| 263 |
|
---|
| 264 | return (array) $payload;
|
---|
| 265 | }
|
---|
| 266 |
|
---|
| 267 | /**
|
---|
| 268 | * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
|
---|
| 269 | * token, if a token isn't provided.
|
---|
| 270 | *
|
---|
| 271 | * @param string|array<mixed> $token The token (access token or a refresh token) that should be revoked.
|
---|
| 272 | * @param array<mixed> $options [optional] Configuration options.
|
---|
| 273 | * @return bool Returns True if the revocation was successful, otherwise False.
|
---|
| 274 | */
|
---|
| 275 | public function revoke($token, array $options = [])
|
---|
| 276 | {
|
---|
| 277 | if (is_array($token)) {
|
---|
| 278 | if (isset($token['refresh_token'])) {
|
---|
| 279 | $token = $token['refresh_token'];
|
---|
| 280 | } else {
|
---|
| 281 | $token = $token['access_token'];
|
---|
| 282 | }
|
---|
| 283 | }
|
---|
| 284 |
|
---|
| 285 | $body = Utils::streamFor(http_build_query(['token' => $token]));
|
---|
| 286 | $request = new Request('POST', self::OAUTH2_REVOKE_URI, [
|
---|
| 287 | 'Cache-Control' => 'no-store',
|
---|
| 288 | 'Content-Type' => 'application/x-www-form-urlencoded',
|
---|
| 289 | ], $body);
|
---|
| 290 |
|
---|
| 291 | $httpHandler = $this->httpHandler;
|
---|
| 292 |
|
---|
| 293 | $response = $httpHandler($request, $options);
|
---|
| 294 |
|
---|
| 295 | return $response->getStatusCode() == 200;
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | /**
|
---|
| 299 | * Gets federated sign-on certificates to use for verifying identity tokens.
|
---|
| 300 | * Returns certs as array structure, where keys are key ids, and values
|
---|
| 301 | * are PEM encoded certificates.
|
---|
| 302 | *
|
---|
| 303 | * @param string $location The location from which to retrieve certs.
|
---|
| 304 | * @param string $cacheKey The key under which to cache the retrieved certs.
|
---|
| 305 | * @param array<mixed> $options [optional] Configuration options.
|
---|
| 306 | * @return array<mixed>
|
---|
| 307 | * @throws InvalidArgumentException If received certs are in an invalid format.
|
---|
| 308 | */
|
---|
| 309 | private function getCerts($location, $cacheKey, array $options = [])
|
---|
| 310 | {
|
---|
| 311 | $cacheItem = $this->cache->getItem($cacheKey);
|
---|
| 312 | $certs = $cacheItem ? $cacheItem->get() : null;
|
---|
| 313 |
|
---|
| 314 | $expireTime = null;
|
---|
| 315 | if (!$certs) {
|
---|
| 316 | list($certs, $expireTime) = $this->retrieveCertsFromLocation($location, $options);
|
---|
| 317 | }
|
---|
| 318 |
|
---|
| 319 | if (!isset($certs['keys'])) {
|
---|
| 320 | if ($location !== self::IAP_CERT_URL) {
|
---|
| 321 | throw new InvalidArgumentException(
|
---|
| 322 | 'federated sign-on certs expects "keys" to be set'
|
---|
| 323 | );
|
---|
| 324 | }
|
---|
| 325 | throw new InvalidArgumentException(
|
---|
| 326 | 'certs expects "keys" to be set'
|
---|
| 327 | );
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | // Push caching off until after verifying certs are in a valid format.
|
---|
| 331 | // Don't want to cache bad data.
|
---|
| 332 | if ($expireTime) {
|
---|
| 333 | $cacheItem->expiresAt(new DateTime($expireTime));
|
---|
| 334 | $cacheItem->set($certs);
|
---|
| 335 | $this->cache->save($cacheItem);
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | return $certs['keys'];
|
---|
| 339 | }
|
---|
| 340 |
|
---|
| 341 | /**
|
---|
| 342 | * Retrieve and cache a certificates file.
|
---|
| 343 | *
|
---|
| 344 | * @param string $url location
|
---|
| 345 | * @param array<mixed> $options [optional] Configuration options.
|
---|
| 346 | * @return array{array<mixed>, string}
|
---|
| 347 | * @throws InvalidArgumentException If certs could not be retrieved from a local file.
|
---|
| 348 | * @throws RuntimeException If certs could not be retrieved from a remote location.
|
---|
| 349 | */
|
---|
| 350 | private function retrieveCertsFromLocation($url, array $options = [])
|
---|
| 351 | {
|
---|
| 352 | // If we're retrieving a local file, just grab it.
|
---|
| 353 | $expireTime = '+1 hour';
|
---|
| 354 | if (strpos($url, 'http') !== 0) {
|
---|
| 355 | if (!file_exists($url)) {
|
---|
| 356 | throw new InvalidArgumentException(sprintf(
|
---|
| 357 | 'Failed to retrieve verification certificates from path: %s.',
|
---|
| 358 | $url
|
---|
| 359 | ));
|
---|
| 360 | }
|
---|
| 361 |
|
---|
| 362 | return [
|
---|
| 363 | json_decode((string) file_get_contents($url), true),
|
---|
| 364 | $expireTime
|
---|
| 365 | ];
|
---|
| 366 | }
|
---|
| 367 |
|
---|
| 368 | $httpHandler = $this->httpHandler;
|
---|
| 369 | $response = $httpHandler(new Request('GET', $url), $options);
|
---|
| 370 |
|
---|
| 371 | if ($response->getStatusCode() == 200) {
|
---|
| 372 | if ($cacheControl = $response->getHeaderLine('Cache-Control')) {
|
---|
| 373 | array_map(function ($value) use (&$expireTime) {
|
---|
| 374 | list($key, $value) = explode('=', $value) + [null, null];
|
---|
| 375 | if (trim($key) == 'max-age') {
|
---|
| 376 | $expireTime = '+' . $value . ' seconds';
|
---|
| 377 | }
|
---|
| 378 | }, explode(',', $cacheControl));
|
---|
| 379 | }
|
---|
| 380 | return [
|
---|
| 381 | json_decode((string) $response->getBody(), true),
|
---|
| 382 | $expireTime
|
---|
| 383 | ];
|
---|
| 384 | }
|
---|
| 385 |
|
---|
| 386 | throw new RuntimeException(sprintf(
|
---|
| 387 | 'Failed to retrieve verification certificates: "%s".',
|
---|
| 388 | $response->getBody()->getContents()
|
---|
| 389 | ), $response->getStatusCode());
|
---|
| 390 | }
|
---|
| 391 |
|
---|
| 392 | /**
|
---|
| 393 | * @return void
|
---|
| 394 | */
|
---|
| 395 | private function checkAndInitializePhpsec()
|
---|
| 396 | {
|
---|
| 397 | if (!class_exists(RSA::class)) {
|
---|
| 398 | throw new RuntimeException('Please require phpseclib/phpseclib v3 to use this utility.');
|
---|
| 399 | }
|
---|
| 400 | }
|
---|
| 401 |
|
---|
| 402 | /**
|
---|
| 403 | * @return string
|
---|
| 404 | * @throws TypeError If the key cannot be initialized to a string.
|
---|
| 405 | */
|
---|
| 406 | private function loadPhpsecPublicKey(string $modulus, string $exponent): string
|
---|
| 407 | {
|
---|
| 408 | $key = PublicKeyLoader::load([
|
---|
| 409 | 'n' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [
|
---|
| 410 | $modulus,
|
---|
| 411 | ]), 256),
|
---|
| 412 | 'e' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [
|
---|
| 413 | $exponent
|
---|
| 414 | ]), 256),
|
---|
| 415 | ]);
|
---|
| 416 | $formattedPublicKey = $key->toString('PKCS8');
|
---|
| 417 | if (!is_string($formattedPublicKey)) {
|
---|
| 418 | throw new TypeError('Failed to initialize the key');
|
---|
| 419 | }
|
---|
| 420 | return $formattedPublicKey;
|
---|
| 421 | }
|
---|
| 422 |
|
---|
| 423 | /**
|
---|
| 424 | * @return void
|
---|
| 425 | */
|
---|
| 426 | private function checkSimpleJwt()
|
---|
| 427 | {
|
---|
| 428 | // @codeCoverageIgnoreStart
|
---|
| 429 | if (!class_exists(SimpleJwt::class)) {
|
---|
| 430 | throw new RuntimeException('Please require kelvinmo/simplejwt ^0.2 to use this utility.');
|
---|
| 431 | }
|
---|
| 432 | // @codeCoverageIgnoreEnd
|
---|
| 433 | }
|
---|
| 434 |
|
---|
| 435 | /**
|
---|
| 436 | * Provide a hook to mock calls to the JWT static methods.
|
---|
| 437 | *
|
---|
| 438 | * @param string $method
|
---|
| 439 | * @param array<mixed> $args
|
---|
| 440 | * @return mixed
|
---|
| 441 | */
|
---|
| 442 | protected function callJwtStatic($method, array $args = [])
|
---|
| 443 | {
|
---|
| 444 | return call_user_func_array([JWT::class, $method], $args); // @phpstan-ignore-line
|
---|
| 445 | }
|
---|
| 446 |
|
---|
| 447 | /**
|
---|
| 448 | * Provide a hook to mock calls to the JWT static methods.
|
---|
| 449 | *
|
---|
| 450 | * @param array<mixed> $args
|
---|
| 451 | * @return mixed
|
---|
| 452 | */
|
---|
| 453 | protected function callSimpleJwtDecode(array $args = [])
|
---|
| 454 | {
|
---|
| 455 | return call_user_func_array([SimpleJwt::class, 'decode'], $args);
|
---|
| 456 | }
|
---|
| 457 |
|
---|
| 458 | /**
|
---|
| 459 | * Generate a cache key based on the cert location using sha1 with the
|
---|
| 460 | * exception of using "federated_signon_certs_v3" to preserve BC.
|
---|
| 461 | *
|
---|
| 462 | * @param string $certsLocation
|
---|
| 463 | * @return string
|
---|
| 464 | */
|
---|
| 465 | private function getCacheKeyFromCertLocation($certsLocation)
|
---|
| 466 | {
|
---|
| 467 | $key = $certsLocation === self::FEDERATED_SIGNON_CERT_URL
|
---|
| 468 | ? 'federated_signon_certs_v3'
|
---|
| 469 | : sha1($certsLocation);
|
---|
| 470 |
|
---|
| 471 | return 'google_auth_certs_cache|' . $key;
|
---|
| 472 | }
|
---|
| 473 | }
|
---|