From 1a51a527b9b28ff8350b85950a73bb7d90eaf89e Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Thu, 5 Oct 2023 11:27:59 -0700 Subject: [PATCH] add testing for service account URL and integration tests for file/url creds --- .../ExternalAccountCredentials.php | 6 +- .../ExternalAccountCredentialsTest.php | 137 ++++++++++++++++++ 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/Credentials/ExternalAccountCredentials.php b/src/Credentials/ExternalAccountCredentials.php index afa23ad28..54095c7d2 100644 --- a/src/Credentials/ExternalAccountCredentials.php +++ b/src/Credentials/ExternalAccountCredentials.php @@ -163,7 +163,7 @@ private static function buildCredentialSource(array $jsonKey): ExternalAccountCr */ private function getImpersonatedAccessToken(string $stsToken, callable $httpHandler = null): array { - if (is_null($this->serviceAccountImpersonationUrl)) { + if (!isset($this->serviceAccountImpersonationUrl)) { throw new InvalidArgumentException( 'service_account_impersonation_url must be set in JSON credentials.' ); @@ -175,7 +175,7 @@ private function getImpersonatedAccessToken(string $stsToken, callable $httpHand 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $stsToken, ], - json_encode([ + (string) json_encode([ 'lifetime' => sprintf('%ss', OAuth2::DEFAULT_EXPIRY_SECONDS), 'scope' => $this->auth->getScope(), ]), @@ -208,7 +208,7 @@ public function fetchAuthToken(callable $httpHandler = null) { $stsToken = $this->auth->fetchAuthToken($httpHandler); - if ($this->serviceAccountImpersonationUrl) { + if (isset($this->serviceAccountImpersonationUrl)) { return $this->getImpersonatedAccessToken($stsToken['access_token'], $httpHandler); } diff --git a/tests/Credentials/ExternalAccountCredentialsTest.php b/tests/Credentials/ExternalAccountCredentialsTest.php index a7a1a25e4..577a4c825 100644 --- a/tests/Credentials/ExternalAccountCredentialsTest.php +++ b/tests/Credentials/ExternalAccountCredentialsTest.php @@ -23,7 +23,11 @@ use Google\Auth\CredentialSource\UrlSource; use Google\Auth\OAuth2; use InvalidArgumentException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; /** * @group credentials @@ -31,6 +35,8 @@ */ class ExternalAccountCredentialsTest extends TestCase { + use ProphecyTrait; + /** * @dataProvider provideCredentialSourceFromCredentials */ @@ -186,4 +192,135 @@ public function provideInvalidCredentialsJson() ], ]; } + + public function testFetchAuthTokenFileCredentials() + { + $tmpFile = tempnam(sys_get_temp_dir(), 'test'); + file_put_contents($tmpFile, 'abc'); + + $jsonCreds = [ + 'type' => 'external_account', + 'token_url' => 'token-url.com', + 'audience' => '', + 'subject_token_type' => '', + 'credential_source' => ['file' => $tmpFile], + ]; + + $creds = new ExternalAccountCredentials('a-scope', $jsonCreds); + + $httpHandler = function (RequestInterface $request) { + $this->assertEquals('token-url.com', (string) $request->getUri()); + parse_str((string) $request->getBody(), $requestBody); + $this->assertEquals('abc', $requestBody['subject_token']); + + $responseBody = $this->prophesize(StreamInterface::class); + $responseBody->__toString()->willReturn(json_encode(['access_token' => 'def', 'expires_in' => 1000])); + + $response = $this->prophesize(ResponseInterface::class); + $response->getBody()->willReturn($responseBody->reveal()); + $response->hasHeader('Content-Type')->willReturn(false); + + return $response->reveal(); + }; + + $authToken = $creds->fetchAuthToken($httpHandler); + $this->assertArrayHasKey('access_token', $authToken); + $this->assertEquals('def', $authToken['access_token']); + } + + public function testFetchAuthTokenUrlCredentials() + { + $url = 'sts-url.com'; + $jsonCreds = [ + 'type' => 'external_account', + 'token_url' => 'token-url.com', + 'audience' => '', + 'subject_token_type' => '', + 'credential_source' => ['url' => $url], + ]; + + $creds = new ExternalAccountCredentials('a-scope', $jsonCreds); + + $requestCount = 0; + $httpHandler = function (RequestInterface $request) use (&$requestCount) { + switch (++$requestCount) { + case 1: + $this->assertEquals('sts-url.com', (string) $request->getUri()); + $responseBody = 'abc'; + break; + + case 2: + $this->assertEquals('token-url.com', (string) $request->getUri()); + parse_str((string) $request->getBody(), $requestBody); + $this->assertEquals('abc', $requestBody['subject_token']); + $responseBody = '{"access_token": "def"}'; + break; + } + + $body = $this->prophesize(StreamInterface::class); + $body->__toString()->willReturn($responseBody); + + $response = $this->prophesize(ResponseInterface::class); + $response->getBody()->willReturn($body->reveal()); + if ($requestCount === 2) { + $response->hasHeader('Content-Type')->willReturn(false); + } + + return $response->reveal(); + }; + + $authToken = $creds->fetchAuthToken($httpHandler); + $this->assertArrayHasKey('access_token', $authToken); + $this->assertEquals('def', $authToken['access_token']); + } + + public function testFetchAuthTokenWithImpersonation() + { + $tmpFile = tempnam(sys_get_temp_dir(), 'test'); + file_put_contents($tmpFile, 'abc'); + + $jsonCreds = [ + 'type' => 'external_account', + 'token_url' => 'token-url.com', + 'audience' => '', + 'subject_token_type' => '', + 'credential_source' => ['file' => $tmpFile], + 'service_account_impersonation_url' => 'service-account-impersonation-url.com', + ]; + + $creds = new ExternalAccountCredentials('a-scope', $jsonCreds); + + $requestCount = 0; + $expiry = '2023-10-05T18:00:01Z'; + $httpHandler = function (RequestInterface $request) use (&$requestCount, $expiry) { + switch (++$requestCount) { + case 1: + $this->assertEquals('token-url.com', (string) $request->getUri()); + parse_str((string) $request->getBody(), $requestBody); + $this->assertEquals('abc', $requestBody['subject_token']); + $responseBody = '{"access_token": "def"}'; + break; + case 2: + $this->assertEquals('service-account-impersonation-url.com', (string) $request->getUri()); + $responseBody = json_encode(['accessToken' => 'def', 'expireTime' => $expiry]); + break; + } + + $body = $this->prophesize(StreamInterface::class); + $body->__toString()->willReturn($responseBody); + + $response = $this->prophesize(ResponseInterface::class); + $response->getBody()->willReturn($body->reveal()); + if ($requestCount === 1) { + $response->hasHeader('Content-Type')->willReturn(false); + } + + return $response->reveal(); + }; + + $authToken = $creds->fetchAuthToken($httpHandler); + $this->assertArrayHasKey('access_token', $authToken); + $this->assertEquals('def', $authToken['access_token']); + $this->assertEquals(strtotime($expiry), $authToken['expires_at']); + } }