Skip to content

Commit b6b6966

Browse files
authored
feat: json key scopes in ImpersonatedServiceAccountCredentials (#638)
1 parent 09042be commit b6b6966

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

src/Credentials/ImpersonatedServiceAccountCredentials.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,14 @@ class ImpersonatedServiceAccountCredentials extends CredentialsLoader implements
8787
* @type string[] $delegates The delegates to impersonate
8888
* }
8989
* @param string|null $targetAudience The audience to request an ID token.
90+
* @param string|string[]|null $defaultScope The scopes to be used if no "scopes" field exists
91+
* in the `$jsonKey`.
9092
*/
9193
public function __construct(
9294
string|array|null $scope,
9395
string|array $jsonKey,
94-
private ?string $targetAudience = null
96+
private ?string $targetAudience = null,
97+
string|array|null $defaultScope = null,
9598
) {
9699
if (is_string($jsonKey)) {
97100
if (!file_exists($jsonKey)) {
@@ -110,6 +113,9 @@ public function __construct(
110113
if (!array_key_exists('source_credentials', $jsonKey)) {
111114
throw new LogicException('json key is missing the source_credentials field');
112115
}
116+
117+
$jsonKeyScope = $jsonKey['scopes'] ?? null;
118+
$scope = $scope ?: $jsonKeyScope ?: $defaultScope;
113119
if ($scope && $targetAudience) {
114120
throw new InvalidArgumentException(
115121
'Scope and targetAudience cannot both be supplied'

src/CredentialsLoader.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ public static function makeCredentials(
174174
}
175175

176176
if ($jsonKey['type'] == 'impersonated_service_account') {
177-
$anyScope = $scope ?: $defaultScope;
178-
return new ImpersonatedServiceAccountCredentials($anyScope, $jsonKey);
177+
return new ImpersonatedServiceAccountCredentials($scope, $jsonKey, null, $defaultScope);
179178
}
180179

181180
if ($jsonKey['type'] == 'external_account') {

tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,4 +485,70 @@ public function testIdTokenWithAuthTokenMiddleware()
485485

486486
$this->assertEquals(1, $requestCount);
487487
}
488+
489+
/**
490+
* @dataProvider provideScopePrecedence
491+
*/
492+
public function testScopePrecedence(
493+
string|array|null $userScope,
494+
string|array|null $jsonKeyScope,
495+
string|null $defaultScope,
496+
string|array $expectedScope
497+
) {
498+
$jsonKey = self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON;
499+
$jsonKey['scopes'] = $jsonKeyScope;
500+
$credentials = new ImpersonatedServiceAccountCredentials(
501+
scope: $userScope,
502+
jsonKey: $jsonKey,
503+
defaultScope: $defaultScope,
504+
);
505+
506+
$scopeProp = (new ReflectionClass($credentials))->getProperty('targetScope');
507+
$this->assertEquals($expectedScope, $scopeProp->getValue($credentials));
508+
}
509+
510+
public function testScopePrecedenceWithNoJsonKey()
511+
{
512+
$defaultScope = 'a-default-scope';
513+
$jsonKey = self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON;
514+
$credentials = new ImpersonatedServiceAccountCredentials(
515+
scope: null,
516+
jsonKey: $jsonKey,
517+
defaultScope: $defaultScope,
518+
);
519+
520+
$scopeProp = (new ReflectionClass($credentials))->getProperty('targetScope');
521+
$this->assertEquals($defaultScope, $scopeProp->getValue($credentials));
522+
}
523+
524+
public function provideScopePrecedence()
525+
{
526+
$userScope = 'a-user-scope';
527+
$jsonKeyScope = 'a-json-key-scope';
528+
$defaultScope = 'a-default-scope';
529+
return [
530+
// User scope always takes precendence
531+
[$userScope, $jsonKeyScope, $defaultScope, 'expectedScope' => $userScope],
532+
[$userScope, null, $defaultScope, 'expectedScope' => $userScope],
533+
[$userScope, $jsonKeyScope, null, 'expectedScope' => $userScope],
534+
[$userScope, null, null, 'expectedScope' => $userScope],
535+
536+
// JSON Key Scope is next
537+
[null, $jsonKeyScope, $defaultScope, 'expectedScope' => $jsonKeyScope],
538+
[null, $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],
539+
540+
// Default Scope is last
541+
[null, null, $defaultScope, 'expectedScope' => $defaultScope],
542+
// JSON Key scope is exists but is an empty array, still return default
543+
[null, [], $defaultScope, 'expectedScope' => $defaultScope],
544+
545+
// No scope is empty array
546+
[null, null, null, 'expectedScope' => []],
547+
548+
// Test empty strings and arrays
549+
['', $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],
550+
[[], $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],
551+
[[], '', $defaultScope, 'expectedScope' => $defaultScope],
552+
];
553+
}
488554
}

0 commit comments

Comments
 (0)