From a70d2369147d232b2c720f293e57d1be4bd5faf9 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Sat, 5 Oct 2024 10:44:09 -0700 Subject: [PATCH] ensure complete test coverage, fix cs --- .../ImpersonatedServiceAccountCredentials.php | 4 +- ...ersonatedServiceAccountCredentialsTest.php | 124 +++++++++++++++--- 2 files changed, 108 insertions(+), 20 deletions(-) diff --git a/src/Credentials/ImpersonatedServiceAccountCredentials.php b/src/Credentials/ImpersonatedServiceAccountCredentials.php index 473fda301..d94e0ddad 100644 --- a/src/Credentials/ImpersonatedServiceAccountCredentials.php +++ b/src/Credentials/ImpersonatedServiceAccountCredentials.php @@ -188,7 +188,7 @@ public function fetchAuthToken(callable $httpHandler = null) 'Authorization' => sprintf('Bearer %s', $authToken['access_token'] ?? $authToken['id_token']), ], $this->isIdTokenRequest() ? 'it' : 'at'); - $body = match($this->isIdTokenRequest()) { + $body = match ($this->isIdTokenRequest()) { true => [ 'audience' => $this->targetAudience, 'includeEmail' => true, @@ -210,7 +210,7 @@ public function fetchAuthToken(callable $httpHandler = null) $response = $httpHandler($request); $body = json_decode((string) $response->getBody(), true); - return match($this->isIdTokenRequest()) { + return match ($this->isIdTokenRequest()) { true => ['id_token' => $body['token']], false => [ 'access_token' => $body['accessToken'], diff --git a/tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php b/tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php index c415cb786..2cf85bb1e 100644 --- a/tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php +++ b/tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php @@ -22,15 +22,15 @@ use Google\Auth\Credentials\ImpersonatedServiceAccountCredentials; use Google\Auth\Credentials\ServiceAccountCredentials; use Google\Auth\Credentials\UserRefreshCredentials; -use Google\Auth\Middleware\AuthTokenMiddleware; use Google\Auth\FetchAuthTokenInterface; +use Google\Auth\Middleware\AuthTokenMiddleware; use Google\Auth\OAuth2; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Response; use LogicException; use PHPUnit\Framework\TestCase; -use Prophecy\PHPUnit\ProphecyTrait; use Prophecy\Argument; +use Prophecy\PHPUnit\ProphecyTrait; use Psr\Http\Message\RequestInterface; use ReflectionClass; @@ -77,9 +77,9 @@ public function testMissingSourceCredentialTypeThrowsException() } /** - * @dataProvider provideServiceAccountImpersonationJson + * @dataProvider provideSourceCredentialsClass */ - public function testSourceCredentialsFromJsonFiles(array $json, string $credClass) + public function testSourceCredentialsClass(array $json, string $credClass) { $creds = new ImpersonatedServiceAccountCredentials(['scope/1', 'scope/2'], $json); @@ -88,18 +88,21 @@ public function testSourceCredentialsFromJsonFiles(array $json, string $credClas $this->assertInstanceOf($credClass, $sourceCredentialsProperty->getValue($creds)); } - public function provideServiceAccountImpersonationJson() + public function provideSourceCredentialsClass() { return [ [self::USER_TO_SERVICE_ACCOUNT_JSON, UserRefreshCredentials::class], [self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON, ServiceAccountCredentials::class], + [self::EXTERNAL_ACCOUNT_TO_SERVICE_ACCOUNT_JSON, ExternalAccountCredentials::class], ]; } /** - * @dataProvider provideServiceAccountImpersonationIdTokenJson + * Test access token impersonation for Service Account and User Refresh Credentials. + * + * @dataProvider provideAuthTokenJson */ - public function testGetIdTokenWithServiceAccountImpersonationCredentials($json, $grantType) + public function testGetAccessTokenWithServiceAccountAndUserRefreshCredentials($json, $grantType) { $requestCount = 0; // getting an id token will take two requests @@ -107,8 +110,45 @@ public function testGetIdTokenWithServiceAccountImpersonationCredentials($json, if (++$requestCount == 1) { // the call to swap the refresh token for an access token $this->assertEquals(UserRefreshCredentials::TOKEN_CREDENTIAL_URI, (string) $request->getUri()); - $body = (string) $request->getBody(); - parse_str($body, $result); + parse_str((string) $request->getBody(), $result); + $this->assertEquals($grantType, $result['grant_type']); + } elseif ($requestCount == 2) { + // the call to swap the access token for an id token + $this->assertEquals($json['service_account_impersonation_url'], (string) $request->getUri()); + $this->assertEquals(self::SCOPE, json_decode($request->getBody(), true)['scope'] ?? ''); + $this->assertEquals('Bearer test-access-token', $request->getHeader('authorization')[0] ?? null); + } + + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(match ($requestCount) { + 1 => ['access_token' => 'test-access-token'], + 2 => ['accessToken' => 'test-impersonated-access-token', 'expireTime' => 123] + }) + ); + }; + + $creds = new ImpersonatedServiceAccountCredentials(self::SCOPE, $json); + $token = $creds->fetchAuthToken($httpHandler); + $this->assertEquals('test-impersonated-access-token', $token['access_token']); + $this->assertEquals(2, $requestCount); + } + + /** + * Test ID token impersonation for Service Account and User Refresh Credentials. + * + * @dataProvider provideAuthTokenJson + */ + public function testGetIdTokenWithServiceAccountAndUserRefreshCredentials($json, $grantType) + { + $requestCount = 0; + // getting an id token will take two requests + $httpHandler = function (RequestInterface $request) use (&$requestCount, $json, $grantType) { + if (++$requestCount == 1) { + // the call to swap the refresh token for an access token + $this->assertEquals(UserRefreshCredentials::TOKEN_CREDENTIAL_URI, (string) $request->getUri()); + parse_str((string) $request->getBody(), $result); $this->assertEquals($grantType, $result['grant_type']); } elseif ($requestCount == 2) { // the call to swap the access token for an id token @@ -133,7 +173,57 @@ public function testGetIdTokenWithServiceAccountImpersonationCredentials($json, $this->assertEquals(2, $requestCount); } - public function testGetIdTokenWithExternalAccountToServiceAccountImpersonationCredentials() + public function provideAuthTokenJson() + { + return [ + [self::USER_TO_SERVICE_ACCOUNT_JSON, 'refresh_token'], + [self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON, OAuth2::JWT_URN], + ]; + } + + /** + * Test access token impersonation for Exernal Account Credentials. + */ + public function testGetAccessTokenWithExternalAccountCredentials() + { + $json = self::EXTERNAL_ACCOUNT_TO_SERVICE_ACCOUNT_JSON; + $httpHandler = function (RequestInterface $request) use (&$requestCount, $json) { + if (++$requestCount == 1) { + // the call to swap the refresh token for an access token + $this->assertEquals( + $json['source_credentials']['credential_source']['url'], + (string) $request->getUri() + ); + } elseif ($requestCount == 2) { + $this->assertEquals($json['source_credentials']['token_url'], (string) $request->getUri()); + } elseif ($requestCount == 3) { + // the call to swap the access token for an id token + $this->assertEquals($json['service_account_impersonation_url'], (string) $request->getUri()); + $this->assertEquals(self::SCOPE, json_decode($request->getBody(), true)['scope'] ?? ''); + $this->assertEquals('Bearer test-access-token', $request->getHeader('authorization')[0] ?? null); + } + + return new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode(match ($requestCount) { + 1 => ['access_token' => 'test-access-token'], + 2 => ['access_token' => 'test-access-token'], + 3 => ['accessToken' => 'test-impersonated-access-token', 'expireTime' => 123] + }) + ); + }; + + $creds = new ImpersonatedServiceAccountCredentials(self::SCOPE, $json); + $token = $creds->fetchAuthToken($httpHandler); + $this->assertEquals('test-impersonated-access-token', $token['access_token']); + $this->assertEquals(3, $requestCount); + } + + /** + * Test ID token impersonation for Exernal Account Credentials. + */ + public function testGetIdTokenWithExternalAccountCredentials() { $json = self::EXTERNAL_ACCOUNT_TO_SERVICE_ACCOUNT_JSON; $httpHandler = function (RequestInterface $request) use (&$requestCount, $json) { @@ -169,6 +259,9 @@ public function testGetIdTokenWithExternalAccountToServiceAccountImpersonationCr $this->assertEquals(3, $requestCount); } + /** + * Test ID token impersonation for an arbitrary credential fetcher. + */ public function testGetIdTokenWithArbitraryCredentials() { $httpHandler = function (RequestInterface $request) { @@ -193,6 +286,9 @@ public function testGetIdTokenWithArbitraryCredentials() $this->assertEquals('test-impersonated-id-token', $token['id_token']); } + /** + * Test access token impersonation for an arbitrary credential fetcher. + */ public function testGetAccessTokenWithArbitraryCredentials() { $httpHandler = function (RequestInterface $request) { @@ -221,14 +317,6 @@ public function testGetAccessTokenWithArbitraryCredentials() $this->assertEquals('test-impersonated-access-token', $token['access_token']); } - public function provideServiceAccountImpersonationIdTokenJson() - { - return [ - [self::USER_TO_SERVICE_ACCOUNT_JSON, 'refresh_token'], - [self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON, OAuth2::JWT_URN], - ]; - } - public function testIdTokenWithAuthTokenMiddleware() { $targetAudience = 'test-target-audience';