Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Credentials/ImpersonatedServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ class ImpersonatedServiceAccountCredentials extends CredentialsLoader implements
* @type string[] $delegates The delegates to impersonate
* }
* @param string|null $targetAudience The audience to request an ID token.
* @param string|string[]|null $defaultScope The scopes to be used if no "scopes" field exists
* in the `$jsonKey`.
*/
public function __construct(
string|array|null $scope,
string|array $jsonKey,
private ?string $targetAudience = null
private ?string $targetAudience = null,
string|array|null $defaultScope = null,
) {
if (is_string($jsonKey)) {
if (!file_exists($jsonKey)) {
Expand All @@ -110,6 +113,9 @@ public function __construct(
if (!array_key_exists('source_credentials', $jsonKey)) {
throw new LogicException('json key is missing the source_credentials field');
}

$jsonKeyScope = $jsonKey['scopes'] ?? null;
$scope = $scope ?: $jsonKeyScope ?: $defaultScope;
if ($scope && $targetAudience) {
throw new InvalidArgumentException(
'Scope and targetAudience cannot both be supplied'
Expand Down
3 changes: 1 addition & 2 deletions src/CredentialsLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ public static function makeCredentials(
}

if ($jsonKey['type'] == 'impersonated_service_account') {
$anyScope = $scope ?: $defaultScope;
return new ImpersonatedServiceAccountCredentials($anyScope, $jsonKey);
return new ImpersonatedServiceAccountCredentials($scope, $jsonKey, null, $defaultScope);
}

if ($jsonKey['type'] == 'external_account') {
Expand Down
66 changes: 66 additions & 0 deletions tests/Credentials/ImpersonatedServiceAccountCredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,4 +485,70 @@ public function testIdTokenWithAuthTokenMiddleware()

$this->assertEquals(1, $requestCount);
}

/**
* @dataProvider provideScopePrecedence
*/
public function testScopePrecedence(
string|array|null $userScope,
string|array|null $jsonKeyScope,
string|null $defaultScope,
string|array $expectedScope
) {
$jsonKey = self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON;
$jsonKey['scopes'] = $jsonKeyScope;
$credentials = new ImpersonatedServiceAccountCredentials(
scope: $userScope,
jsonKey: $jsonKey,
defaultScope: $defaultScope,
);

$scopeProp = (new ReflectionClass($credentials))->getProperty('targetScope');
$this->assertEquals($expectedScope, $scopeProp->getValue($credentials));
}

public function testScopePrecedenceWithNoJsonKey()
{
$defaultScope = 'a-default-scope';
$jsonKey = self::SERVICE_ACCOUNT_TO_SERVICE_ACCOUNT_JSON;
$credentials = new ImpersonatedServiceAccountCredentials(
scope: null,
jsonKey: $jsonKey,
defaultScope: $defaultScope,
);

$scopeProp = (new ReflectionClass($credentials))->getProperty('targetScope');
$this->assertEquals($defaultScope, $scopeProp->getValue($credentials));
}

public function provideScopePrecedence()
{
$userScope = 'a-user-scope';
$jsonKeyScope = 'a-json-key-scope';
$defaultScope = 'a-default-scope';
return [
// User scope always takes precendence
[$userScope, $jsonKeyScope, $defaultScope, 'expectedScope' => $userScope],
[$userScope, null, $defaultScope, 'expectedScope' => $userScope],
[$userScope, $jsonKeyScope, null, 'expectedScope' => $userScope],
[$userScope, null, null, 'expectedScope' => $userScope],

// JSON Key Scope is next
[null, $jsonKeyScope, $defaultScope, 'expectedScope' => $jsonKeyScope],
[null, $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],

// Default Scope is last
[null, null, $defaultScope, 'expectedScope' => $defaultScope],
// JSON Key scope is exists but is an empty array, still return default
[null, [], $defaultScope, 'expectedScope' => $defaultScope],

// No scope is empty array
[null, null, null, 'expectedScope' => []],

// Test empty strings and arrays
['', $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],
[[], $jsonKeyScope, null, 'expectedScope' => $jsonKeyScope],
[[], '', $defaultScope, 'expectedScope' => $defaultScope],
];
}
}