Skip to content

Commit

Permalink
add test, misc cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer committed Oct 5, 2024
1 parent c8f66d4 commit aefc2fe
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 26 deletions.
29 changes: 23 additions & 6 deletions src/Credentials/ServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

namespace Google\Auth\Credentials;

use Firebase\JWT\JWT;
use Google\Auth\CredentialsLoader;
use Google\Auth\GetQuotaProjectInterface;
use Google\Auth\Iam;
Expand Down Expand Up @@ -72,6 +73,7 @@ class ServiceAccountCredentials extends CredentialsLoader implements
* @var string
*/
private const CRED_TYPE = 'sa';
private const IAM_SCOPE = 'https://www.googleapis.com/auth/iam';

/**
* The OAuth2 instance used to conduct authorization.
Expand Down Expand Up @@ -215,18 +217,33 @@ public function fetchAuthToken(callable $httpHandler = null)

return $accessToken;
}
$authRequestType = $this->isIdTokenRequest() ? 'it' : 'at';
if ($this->isIdTokenRequest() && $this->getUniverseDomain() != self::DEFAULT_UNIVERSE_DOMAIN) {
$idToken = (new Iam($httpHandler, $this->getUniverseDomain()))->generateIdToken(
$this->auth->getIssuer(),
$this->auth->getAdditionalClaims()['target_audience'],

if ($this->isIdTokenRequest() && $this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) {
$now = time();
$jwt = Jwt::encode(
[
'iss' => $this->auth->getIssuer(),
'sub' => $this->auth->getIssuer(),
'scope' => self::IAM_SCOPE,
'exp' => ($now + $this->auth->getExpiry()),
'iat' => ($now - OAuth2::DEFAULT_SKEW_SECONDS),
],
$this->auth->getSigningKey(),
$this->auth->getSigningAlgorithm(),
$this->auth->getSigningKeyId()
);
// We create a new instance of Iam each time because the `$httpHandler` might change.
$idToken = (new Iam($httpHandler, $this->getUniverseDomain()))->generateIdToken(
$this->auth->getIssuer(),
$this->auth->getAdditionalClaims()['target_audience'],
$jwt
);
return ['id_token' => $idToken];
}
return $this->auth->fetchAuthToken($httpHandler, $this->applyTokenEndpointMetrics([], $authRequestType));
return $this->auth->fetchAuthToken(
$httpHandler,
$this->applyTokenEndpointMetrics([], $this->isIdTokenRequest() ? 'it' : 'at')
);
}

/**
Expand Down
26 changes: 6 additions & 20 deletions src/Iam.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,41 +117,27 @@ public function signBlob($email, $accessToken, $stringToSign, array $delegates =
*
* @param string $clientEmail The service account email.
* @param string $targetAudience The audience for the ID token.
* @param string $signingKey The private key to sign the ID token.
* @param string $signingAlg The algorithm to sign the ID token.
* @param string $signingKeyId The key ID to sign the ID token.
* @param string $bearerToken The token to authenticate the IAM request.
*
* @return string The signed string, base64-encoded.
*/
public function generateIdToken(
string $clientEmail,
string $targetAudience,
string $signingKey,
string $signingAlg,
string $signingKeyId
) {
$auth = new OAuth2([
'issuer' => $clientEmail,
'sub' => $clientEmail,
'scope' => 'https://www.googleapis.com/auth/iam',
'signingKey' => $signingKey,
'signingKeyId' => $signingKeyId,
'signingAlgorithm' => $signingAlg,
]);

string $bearerToken
): string {
$name = sprintf(self::SERVICE_ACCOUNT_NAME, $clientEmail);
$apiRoot = str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE);
$uri = $apiRoot . '/' . sprintf(self::GENERATE_ID_TOKEN_PATH, $name);

$headers = ['Authorization' => 'Bearer ' . $bearerToken];

$body = [
'audience' => $targetAudience,
'includeEmail' => true,
'useEmailAzp' => true,
];

$headers = [
'Authorization' => 'Bearer ' . $auth->toJwt(),
];

$request = new Psr7\Request(
'POST',
$uri,
Expand Down
31 changes: 31 additions & 0 deletions tests/Credentials/ServiceAccountCredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Google\Auth\CredentialsLoader;
use Google\Auth\OAuth2;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Utils;
use InvalidArgumentException;
Expand Down Expand Up @@ -307,6 +308,36 @@ public function testShouldBeIdTokenWhenTargetAudienceIsSet()
$this->assertEquals(1, $timesCalled);
}

public function testShouldUseIamWhenTargetAudienceAndUniverseDomainIsSet()
{
$testJson = $this->createTestJson();
$testJson['universe_domain'] = 'abc.xyz';

$timesCalled = 0;
$httpHandler = function (Request $request) use (&$timesCalled) {
$timesCalled++;

// Verify Request
$this->assertStringContainsString(':generateIdToken', $request->getUri());
$json = json_decode($request->getBody(), true);
$this->assertArrayHasKey('audience', $json);
$this->assertEquals('a target audience', $json['audience']);

// Verify JWT Bearer Token
$jwt = str_replace('Bearer ', '', $request->getHeaderLine('Authorization'));
list($header, $payload, $sig) = explode('.', $jwt);
$jwtParams = json_decode(base64_decode($payload), true);
$this->assertArrayHasKey('iss', $jwtParams);
$this->assertEquals('[email protected]', $jwtParams['iss']);

// return expected IAM ID token response
return new Psr7\Response(200, [], json_encode(['token' => 'idtoken12345']));
};
$sa = new ServiceAccountCredentials(null, $testJson, null, 'a target audience');
$this->assertEquals('idtoken12345', $sa->fetchAuthToken($httpHandler)['id_token']);
$this->assertEquals(1, $timesCalled);
}

public function testShouldBeOAuthRequestWhenSubIsSet()
{
$testJson = $this->createTestJson();
Expand Down

0 comments on commit aefc2fe

Please sign in to comment.