From 74d5b0b2506610aa8aa853d05400826737cbb933 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 13 Feb 2024 07:15:32 -0800 Subject: [PATCH 1/4] support universe domain in IAM --- src/Iam.php | 14 ++++++++++---- src/IamSignerTrait.php | 8 ++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Iam.php b/src/Iam.php index 943ecf2d6..4002b0582 100644 --- a/src/Iam.php +++ b/src/Iam.php @@ -29,7 +29,7 @@ */ class Iam { - const IAM_API_ROOT = 'https://iamcredentials.googleapis.com/v1'; + const IAM_API_ROOT = 'https://iamcredentials.UNIVERSE_DOMAIN/v1'; const SIGN_BLOB_PATH = '%s:signBlob?alt=json'; const SERVICE_ACCOUNT_NAME = 'projects/-/serviceAccounts/%s'; @@ -38,13 +38,18 @@ class Iam */ private $httpHandler; + private string $universeDomain; + /** * @param callable $httpHandler [optional] The HTTP Handler to send requests. */ - public function __construct(callable $httpHandler = null) - { + public function __construct( + callable $httpHandler = null, + string $universeDomain = GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN + ) { $this->httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + $this->universeDomain = $universeDomain; } /** @@ -66,7 +71,8 @@ public function signBlob($email, $accessToken, $stringToSign, array $delegates = { $httpHandler = $this->httpHandler; $name = sprintf(self::SERVICE_ACCOUNT_NAME, $email); - $uri = self::IAM_API_ROOT . '/' . sprintf(self::SIGN_BLOB_PATH, $name); + $apiRoot = str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT); + $uri = $apiRoot . '/' . sprintf(self::SIGN_BLOB_PATH, $name); if ($delegates) { foreach ($delegates as &$delegate) { diff --git a/src/IamSignerTrait.php b/src/IamSignerTrait.php index 9de18b3fd..0b1dfe567 100644 --- a/src/IamSignerTrait.php +++ b/src/IamSignerTrait.php @@ -27,7 +27,7 @@ trait IamSignerTrait /** * @var Iam|null */ - private $iam; + private ?Iam $iam; /** * Sign a string using the default service account private key. @@ -51,7 +51,11 @@ public function signBlob($stringToSign, $forceOpenSsl = false, $accessToken = nu // Providing a signer is useful for testing, but it's undocumented // because it's not something a user would generally need to do. - $signer = $this->iam ?: new Iam($httpHandler); + if (is_null($this->iam)) { + $this->iam = $this instanceof GetUniverseDomainInterface + ? new Iam($httpHandler, $this->getUniverseDomain()) + : new Iam($httpHandler); + } $email = $this->getClientName($httpHandler); From 6cbd0d8d006996d93a4e14e32d46ec41fe80ed5f Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 13 Feb 2024 07:39:07 -0800 Subject: [PATCH 2/4] add test --- .../ExternalAccountCredentials.php | 2 +- src/Iam.php | 8 +++- src/IamSignerTrait.php | 4 +- tests/Credentials/GCECredentialsTest.php | 39 +++++++++++++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Credentials/ExternalAccountCredentials.php b/src/Credentials/ExternalAccountCredentials.php index bc4a68610..c3a8c628a 100644 --- a/src/Credentials/ExternalAccountCredentials.php +++ b/src/Credentials/ExternalAccountCredentials.php @@ -43,7 +43,7 @@ class ExternalAccountCredentials implements use UpdateMetadataTrait; private const EXTERNAL_ACCOUNT_TYPE = 'external_account'; - private const CLOUD_RESOURCE_MANAGER_URL='https://cloudresourcemanager.UNIVERSE_DOMAIN/v1/projects/%s'; + private const CLOUD_RESOURCE_MANAGER_URL = 'https://cloudresourcemanager.UNIVERSE_DOMAIN/v1/projects/%s'; private OAuth2 $auth; private ?string $quotaProject; diff --git a/src/Iam.php b/src/Iam.php index 4002b0582..2f67f0009 100644 --- a/src/Iam.php +++ b/src/Iam.php @@ -29,9 +29,13 @@ */ class Iam { - const IAM_API_ROOT = 'https://iamcredentials.UNIVERSE_DOMAIN/v1'; + /** + * @deprecated + */ + const IAM_API_ROOT = 'https://iamcredentials.googleapis.com/v1'; const SIGN_BLOB_PATH = '%s:signBlob?alt=json'; const SERVICE_ACCOUNT_NAME = 'projects/-/serviceAccounts/%s'; + private const IAM_API_ROOT_TEMPLATE = 'https://iamcredentials.UNIVERSE_DOMAIN/v1'; /** * @var callable @@ -71,7 +75,7 @@ public function signBlob($email, $accessToken, $stringToSign, array $delegates = { $httpHandler = $this->httpHandler; $name = sprintf(self::SERVICE_ACCOUNT_NAME, $email); - $apiRoot = str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT); + $apiRoot = str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE); $uri = $apiRoot . '/' . sprintf(self::SIGN_BLOB_PATH, $name); if ($delegates) { diff --git a/src/IamSignerTrait.php b/src/IamSignerTrait.php index 0b1dfe567..9035177f9 100644 --- a/src/IamSignerTrait.php +++ b/src/IamSignerTrait.php @@ -51,7 +51,7 @@ public function signBlob($stringToSign, $forceOpenSsl = false, $accessToken = nu // Providing a signer is useful for testing, but it's undocumented // because it's not something a user would generally need to do. - if (is_null($this->iam)) { + if (!isset($this->iam)) { $this->iam = $this instanceof GetUniverseDomainInterface ? new Iam($httpHandler, $this->getUniverseDomain()) : new Iam($httpHandler); @@ -66,6 +66,6 @@ public function signBlob($stringToSign, $forceOpenSsl = false, $accessToken = nu : $this->fetchAuthToken($httpHandler)['access_token']; } - return $signer->signBlob($email, $accessToken, $stringToSign); + return $this->iam->signBlob($email, $accessToken, $stringToSign); } } diff --git a/tests/Credentials/GCECredentialsTest.php b/tests/Credentials/GCECredentialsTest.php index a82e6f5c9..360dbb146 100644 --- a/tests/Credentials/GCECredentialsTest.php +++ b/tests/Credentials/GCECredentialsTest.php @@ -25,6 +25,7 @@ use GuzzleHttp\Psr7; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Utils; +use GuzzleHttp\Psr7\Request; use InvalidArgumentException; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; @@ -380,6 +381,44 @@ public function testSignBlobWithLastReceivedAccessToken() $signature = $creds->signBlob($stringToSign); } + public function testSignBlobWithUniverseDomain() + { + $token = [ + 'access_token' => 'token', + 'expires_in' => '57', + 'token_type' => 'Bearer', + ]; + $signedBlob = ['signedBlob' => 'abc123']; + $client = $this->prophesize('GuzzleHttp\ClientInterface'); + $client->send(Argument::any(), Argument::any()) + ->willReturn( + new Response(200, [], Utils::streamFor('test@test.com')), + new Response(200, [], Utils::streamFor(json_encode($token))) + ); + $client->send( + Argument::that( + fn (Request $request) => $request->getUri()->getHost() === 'iamcredentials.example-universe.com' + ), + Argument::any() + ) + ->shouldBeCalledOnce() + ->willReturn(new Response(200, [], Utils::streamFor(json_encode($signedBlob)))); + + HttpClientCache::setHttpClient($client->reveal()); + + $creds = new GCECredentials( + null, + null, + null, + null, + null, + 'example-universe.com' + ); + $creds->setIsOnGce(true); + $signature = $creds->signBlob('inputString'); + $this->assertEquals('abc123', $signature); + } + public function testGetProjectId() { $expected = 'foobar'; From c73340e42ccbc5ce5ec680e096d5c15ed7760a3a Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 13 Feb 2024 07:41:34 -0800 Subject: [PATCH 3/4] be conservative since IamSignerTrait is not internal --- src/IamSignerTrait.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/IamSignerTrait.php b/src/IamSignerTrait.php index 9035177f9..da3c90903 100644 --- a/src/IamSignerTrait.php +++ b/src/IamSignerTrait.php @@ -27,7 +27,7 @@ trait IamSignerTrait /** * @var Iam|null */ - private ?Iam $iam; + private $iam; /** * Sign a string using the default service account private key. @@ -51,8 +51,9 @@ public function signBlob($stringToSign, $forceOpenSsl = false, $accessToken = nu // Providing a signer is useful for testing, but it's undocumented // because it's not something a user would generally need to do. - if (!isset($this->iam)) { - $this->iam = $this instanceof GetUniverseDomainInterface + $signer = $this->iam; + if (!$signer) { + $signer = $this instanceof GetUniverseDomainInterface ? new Iam($httpHandler, $this->getUniverseDomain()) : new Iam($httpHandler); } @@ -66,6 +67,6 @@ public function signBlob($stringToSign, $forceOpenSsl = false, $accessToken = nu : $this->fetchAuthToken($httpHandler)['access_token']; } - return $this->iam->signBlob($email, $accessToken, $stringToSign); + return $signer->signBlob($email, $accessToken, $stringToSign); } } From 05d3bc661cafcf72329ee08600fcc06029cb9491 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 13 Feb 2024 07:44:07 -0800 Subject: [PATCH 4/4] fix cs --- tests/Credentials/GCECredentialsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Credentials/GCECredentialsTest.php b/tests/Credentials/GCECredentialsTest.php index 360dbb146..5964b9a5c 100644 --- a/tests/Credentials/GCECredentialsTest.php +++ b/tests/Credentials/GCECredentialsTest.php @@ -23,9 +23,9 @@ use Google\Auth\Tests\BaseTest; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Psr7; +use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Utils; -use GuzzleHttp\Psr7\Request; use InvalidArgumentException; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait;