From 73b1877326b108c754b940687e1edb0d1da34b47 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 14 Nov 2023 08:27:39 -0800 Subject: [PATCH] add universe domain to credentials wrapper --- src/CredentialsWrapper.php | 41 +++++++++++++-------- src/GapicClientTrait.php | 44 ++++++++++------------- tests/Tests/Unit/GapicClientTraitTest.php | 3 ++ 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/CredentialsWrapper.php b/src/CredentialsWrapper.php index cdd849de2..e647aed57 100644 --- a/src/CredentialsWrapper.php +++ b/src/CredentialsWrapper.php @@ -50,7 +50,7 @@ /** * The CredentialsWrapper object provides a wrapper around a FetchAuthTokenInterface. */ -class CredentialsWrapper implements GetUniverseDomainInterface +class CredentialsWrapper { use ValidationTrait; @@ -59,6 +59,9 @@ class CredentialsWrapper implements GetUniverseDomainInterface /** @var callable $authHttpHandle */ private $authHttpHandler; + private ?string $universeDomain; + private bool $hasCheckedUniverse = false; + /** @var int */ private static int $eagerRefreshThresholdSeconds = 10; @@ -71,10 +74,14 @@ class CredentialsWrapper implements GetUniverseDomainInterface * `function (RequestInterface $request, array $options) : ResponseInterface`. * @throws ValidationException */ - public function __construct(FetchAuthTokenInterface $credentialsFetcher, callable $authHttpHandler = null) - { + public function __construct( + FetchAuthTokenInterface $credentialsFetcher, + callable $authHttpHandler = null, + ?string $universeDomain = null + ) { $this->credentialsFetcher = $credentialsFetcher; $this->authHttpHandler = $authHttpHandler ?: self::buildHttpHandlerFactory(); + $this->universeDomain = $universeDomain; } /** @@ -108,10 +115,12 @@ public function __construct(FetchAuthTokenInterface $credentialsFetcher, callabl * Ensures service account credentials use JWT Access (also known as self-signed * JWTs), even when user-defined scopes are supplied. * } + * @param string $universeDomain The expected universe of the credentials. If empty, the + * credentials wrapper will bypass checking that the credentials universe matches this one. * @return CredentialsWrapper * @throws ValidationException */ - public static function build(array $args = []) + public static function build(array $args = [], string $universeDomain = null) { $args += [ 'keyFile' => null, @@ -174,7 +183,7 @@ public static function build(array $args = []) ); } - return new CredentialsWrapper($loader, $authHttpHandler); + return new CredentialsWrapper($loader, $authHttpHandler, $universeDomain); } /** @@ -213,6 +222,19 @@ public function getAuthorizationHeaderCallback($audience = null) return function () use ($credentialsFetcher, $authHttpHandler, $audience) { $token = $credentialsFetcher->getLastReceivedToken(); if (self::isExpired($token)) { + if (false === $this->hasCheckedUniverse + && null !== $this->universeDomain + && $credentialsFetcher instanceof GetUniverseDomainInterface + ) { + if ($credentialsFetcher->getUniverseDomain() !== $this->universeDomain) { + throw new ValidationException(sprintf( + 'The configured universe domain (%s) does not match the credential universe domain (%s)', + $this->universeDomain, + $credentialsFetcher->getUniverseDomain() + )); + } + $this->hasCheckedUniverseDomain = true; + } // Call updateMetadata to take advantage of self-signed JWTs if ($credentialsFetcher instanceof UpdateMetadataInterface) { return $credentialsFetcher->updateMetadata([], $audience); @@ -308,13 +330,4 @@ private static function isExpired($token) && array_key_exists('expires_at', $token) && $token['expires_at'] > time() + self::$eagerRefreshThresholdSeconds); } - - public function getUniverseDomain(): string - { - if ($this->credentialsFetcher instanceof GetUniverseDomainInterface) { - return $this->credentialsFetcher->getUniverseDomain(); - } - - return GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; - } } diff --git a/src/GapicClientTrait.php b/src/GapicClientTrait.php index 0e6ec8ba2..2b20d3329 100644 --- a/src/GapicClientTrait.php +++ b/src/GapicClientTrait.php @@ -241,9 +241,19 @@ private function buildClientOptions(array $options) // if the universe domain hasn't been explicitly set, assume GDU ("googleapis.com") $options['universeDomain'] ??= GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; - // Build the apiEndpoint from the universe domain if it hasn't been explicitly provided if (is_null($apiEndpoint)) { - $apiEndpoint = self::determineApiEndpoint($options['universeDomain'], $defaultOptions['apiEndpoint']); + if (defined('self::SERVICE_ADDRESS_TEMPLATE')) { + // Derive the endpoint from the service address template and the universe domain + $apiEndpoint = str_replace( + 'UNIVERSE_DOMAIN', + $universeDomain, + self::SERVICE_ADDRESS_TEMPLATE + ); + } else { + // For older clients, the service address template does not exist. Use the default + // endpoint instead. + $apiEndpoint = $defaultOptions['apiEndpoint']; + } } if (extension_loaded('sysvshm') @@ -274,25 +284,6 @@ private function buildClientOptions(array $options) return $options; } - /** - * @internal - * @throws ValidationException - */ - private static function determineApiEndpoint(string $universeDomain, string $legacyApiEndpoint): string - { - // If no API endpoint is set, derive the endpoint from the service address template and the - // universe domain - if (defined('self::SERVICE_ADDRESS_TEMPLATE')) { - return str_replace( - 'UNIVERSE_DOMAIN', - $universeDomain, - self::SERVICE_ADDRESS_TEMPLATE - ); - } - // If no serviceAddressTemplate exsts, this is an older client. Use the default endpoint - return $legacyApiEndpoint; - } - private function shouldUseMtlsEndpoint(array $options) { $mtlsEndpointEnvVar = getenv('GOOGLE_API_USE_MTLS_ENDPOINT'); @@ -449,7 +440,8 @@ private function setClientOptions(array $options) $this->credentialsWrapper = $this->createCredentialsWrapper( $options['credentials'], - $options['credentialsConfig'] + $options['credentialsConfig'], + $options['universeDomain'] ); $transport = $options['transport'] ?: self::defaultTransport(); @@ -469,15 +461,15 @@ private function setClientOptions(array $options) * @return CredentialsWrapper * @throws ValidationException */ - private function createCredentialsWrapper($credentials, array $credentialsConfig) + private function createCredentialsWrapper($credentials, array $credentialsConfig, string $universeDomain) { if (is_null($credentials)) { - return CredentialsWrapper::build($credentialsConfig); + return CredentialsWrapper::build($credentialsConfig, $universeDomain); } elseif (is_string($credentials) || is_array($credentials)) { - return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig); + return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig, $universeDomain); } elseif ($credentials instanceof FetchAuthTokenInterface) { $authHttpHandler = $credentialsConfig['authHttpHandler'] ?? null; - return new CredentialsWrapper($credentials, $authHttpHandler); + return new CredentialsWrapper($credentials, $authHttpHandler, $universeDomain); } elseif ($credentials instanceof CredentialsWrapper) { return $credentials; } else { diff --git a/tests/Tests/Unit/GapicClientTraitTest.php b/tests/Tests/Unit/GapicClientTraitTest.php index 325cd80a5..c29a10088 100644 --- a/tests/Tests/Unit/GapicClientTraitTest.php +++ b/tests/Tests/Unit/GapicClientTraitTest.php @@ -624,6 +624,7 @@ public function testCreateCredentialsWrapper($auth, $authConfig, $expectedCreden $actualCredentialsWrapper = $client->createCredentialsWrapper( $auth, $authConfig, + '' ); $this->assertEquals($expectedCredentialsWrapper, $actualCredentialsWrapper); @@ -656,6 +657,7 @@ public function testCreateCredentialsWrapperValidationException($auth, $authConf $client->createCredentialsWrapper( $auth, $authConfig, + '' ); } @@ -679,6 +681,7 @@ public function testCreateCredentialsWrapperInvalidArgumentException($auth, $aut $client->createCredentialsWrapper( $auth, $authConfig, + '' ); }