Skip to content
28 changes: 22 additions & 6 deletions src/ApplicationDefaultCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public static function getMiddleware(
* @param string|null $universeDomain Specifies a universe domain to use for the
* calling client library.
* @param null|false|LoggerInterface $logger A PSR3 compliant LoggerInterface.
* @param bool $enableTrustBoundary Lookup and include the trust boundary header.
*
* @return FetchAuthTokenInterface
* @throws DomainException if no implementation can be obtained.
Expand All @@ -166,6 +167,7 @@ public static function getCredentials(
$defaultScope = null,
?string $universeDomain = null,
null|false|LoggerInterface $logger = null,
bool $enableTrustBoundary = false
) {
$creds = null;
$jsonKey = CredentialsLoader::fromEnv()
Expand Down Expand Up @@ -196,12 +198,18 @@ public static function getCredentials(
$creds = CredentialsLoader::makeCredentials(
$scope,
$jsonKey,
$defaultScope
$defaultScope,
$enableTrustBoundary
);
} elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) {
$creds = new AppIdentityCredentials($anyScope);
} elseif (self::onGce($httpHandler, $cacheConfig, $cache)) {
$creds = new GCECredentials(null, $anyScope, null, $quotaProject, null, $universeDomain);
$creds = new GCECredentials(
scope: $anyScope,
quotaProject: $quotaProject,
universeDomain: $universeDomain,
enableTrustBoundary: $enableTrustBoundary,
);
$creds->setIsOnGce(true); // save the credentials a trip to the metadata server
}

Expand Down Expand Up @@ -286,7 +294,7 @@ public static function getIdTokenCredentials(
$targetAudience,
?callable $httpHandler = null,
?array $cacheConfig = null,
?CacheItemPoolInterface $cache = null
?CacheItemPoolInterface $cache = null,
) {
$creds = null;
$jsonKey = CredentialsLoader::fromEnv()
Expand All @@ -308,12 +316,20 @@ public static function getIdTokenCredentials(

$creds = match ($jsonKey['type']) {
'authorized_user' => new UserRefreshCredentials(null, $jsonKey, $targetAudience),
'impersonated_service_account' => new ImpersonatedServiceAccountCredentials(null, $jsonKey, $targetAudience),
'service_account' => new ServiceAccountCredentials(null, $jsonKey, null, $targetAudience),
'impersonated_service_account' => new ImpersonatedServiceAccountCredentials(
scope: null,
jsonKey: $jsonKey,
targetAudience: $targetAudience,
),
'service_account' => new ServiceAccountCredentials(
scope: null,
jsonKey: $jsonKey,
targetAudience: $targetAudience,
),
default => throw new InvalidArgumentException('invalid value in the type field')
};
} elseif (self::onGce($httpHandler, $cacheConfig, $cache)) {
$creds = new GCECredentials(null, null, $targetAudience);
$creds = new GCECredentials(targetAudience: $targetAudience);
$creds->setIsOnGce(true); // save the credentials a trip to the metadata server
}

Expand Down
34 changes: 33 additions & 1 deletion src/Credentials/GCECredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Google\Auth\IamSignerTrait;
use Google\Auth\ProjectIdProviderInterface;
use Google\Auth\SignBlobInterface;
use Google\Auth\TrustBoundaryTrait;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
Expand Down Expand Up @@ -64,6 +65,7 @@ class GCECredentials extends CredentialsLoader implements
GetQuotaProjectInterface
{
use IamSignerTrait;
use TrustBoundaryTrait;

// phpcs:disable
const cacheKey = 'GOOGLE_AUTH_PHP_GCE';
Expand Down Expand Up @@ -209,14 +211,16 @@ class GCECredentials extends CredentialsLoader implements
* account identity name to use instead of "default".
* @param string|null $universeDomain [optional] Specify a universe domain to use
* instead of fetching one from the metadata server.
* @param bool $enableTrustBoundary Lookup and include the trust boundary header.
*/
public function __construct(
?Iam $iam = null,
$scope = null,
$targetAudience = null,
$quotaProject = null,
$serviceAccountIdentity = null,
?string $universeDomain = null
?string $universeDomain = null,
bool $enableTrustBoundary = false
) {
$this->iam = $iam;

Expand Down Expand Up @@ -245,6 +249,7 @@ public function __construct(
$this->quotaProject = $quotaProject;
$this->serviceAccountIdentity = $serviceAccountIdentity;
$this->universeDomain = $universeDomain;
$this->enableTrustBoundary = $enableTrustBoundary;
}

/**
Expand Down Expand Up @@ -629,6 +634,33 @@ public function getUniverseDomain(?callable $httpHandler = null): string
return $this->universeDomain;
}

/**
* Updates metadata with the authorization token.
*
* @param array<mixed> $metadata metadata hashmap
* @param string $authUri optional auth uri
* @param callable|null $httpHandler callback which delivers psr7 request
* @return array<mixed> updated metadata hashmap
*/
public function updateMetadata(
$metadata,
$authUri = null,
?callable $httpHandler = null
) {
$metadata = parent::updateMetadata($metadata, $authUri, $httpHandler);

if ($this->enableTrustBoundary) {
$metadata = $this->updateTrustBoundaryMetadata(
$metadata,
$this->getClientName($httpHandler),
$this->getUniverseDomain($httpHandler),
$httpHandler,
);
}

return $metadata;
}

/**
* Fetch the value of a GCE metadata server URI.
*
Expand Down
40 changes: 38 additions & 2 deletions src/Credentials/ImpersonatedServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Google\Auth\IamSignerTrait;
use Google\Auth\SignBlobInterface;
use Google\Auth\TrustBoundaryTrait;
use Google\Auth\UpdateMetadataInterface;
use Google\Auth\UpdateMetadataTrait;
use GuzzleHttp\Psr7\Request;
use InvalidArgumentException;
use LogicException;
Expand All @@ -41,10 +44,13 @@
*/
class ImpersonatedServiceAccountCredentials extends CredentialsLoader implements
SignBlobInterface,
GetUniverseDomainInterface
GetUniverseDomainInterface,
UpdateMetadataInterface
{
use CacheTrait;
use IamSignerTrait;
use UpdateMetadataTrait;
use TrustBoundaryTrait;

private const CRED_TYPE = 'imp';
private const IAM_SCOPE = 'https://www.googleapis.com/auth/iam';
Expand Down Expand Up @@ -95,6 +101,7 @@ public function __construct(
string|array $jsonKey,
private ?string $targetAudience = null,
string|array|null $defaultScope = null,
bool $enableTrustBoundary = false
) {
if (is_string($jsonKey)) {
if (!file_exists($jsonKey)) {
Expand Down Expand Up @@ -135,7 +142,10 @@ public function __construct(
}
$jsonKey['source_credentials'] = match ($jsonKey['source_credentials']['type'] ?? null) {
// Do not pass $defaultScope to ServiceAccountCredentials
'service_account' => new ServiceAccountCredentials($scope, $jsonKey['source_credentials']),
'service_account' => new ServiceAccountCredentials(
scope: $scope,
jsonKey: $jsonKey['source_credentials'],
),
'authorized_user' => new UserRefreshCredentials($scope, $jsonKey['source_credentials']),
'external_account' => new ExternalAccountCredentials($scope, $jsonKey['source_credentials']),
default => throw new \InvalidArgumentException('invalid value in the type field'),
Expand All @@ -152,6 +162,7 @@ public function __construct(
);

$this->sourceCredentials = $jsonKey['source_credentials'];
$this->enableTrustBoundary = $enableTrustBoundary;
}

/**
Expand Down Expand Up @@ -298,4 +309,29 @@ public function getUniverseDomain(): string
? $this->sourceCredentials->getUniverseDomain()
: self::DEFAULT_UNIVERSE_DOMAIN;
}

/**
* Updates metadata with the authorization token.
*
* @param array<mixed> $metadata metadata hashmap
* @param string $authUri optional auth uri
* @param callable|null $httpHandler callback which delivers psr7 request
* @return array<mixed> updated metadata hashmap
*/
public function updateMetadata(
$metadata,
$authUri = null,
?callable $httpHandler = null
) {
$metatadata = parent::updateMetadata($metadata, $authUri, $httpHandler);

$metatadata = $this->updateTrustBoundaryMetadata(
$metatadata,
$this->impersonatedServiceAccountName,
$this->getUniverseDomain(),
$httpHandler,
);

return $metatadata;
}
}
56 changes: 44 additions & 12 deletions src/Credentials/ServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
use Firebase\JWT\JWT;
use Google\Auth\CredentialsLoader;
use Google\Auth\GetQuotaProjectInterface;
use Google\Auth\HttpHandler\HttpClientCache;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Google\Auth\Iam;
use Google\Auth\OAuth2;
use Google\Auth\ProjectIdProviderInterface;
use Google\Auth\ServiceAccountSignerTrait;
use Google\Auth\SignBlobInterface;
use Google\Auth\TrustBoundaryTrait;
use InvalidArgumentException;

/**
Expand Down Expand Up @@ -66,6 +69,7 @@ class ServiceAccountCredentials extends CredentialsLoader implements
ProjectIdProviderInterface
{
use ServiceAccountSignerTrait;
use TrustBoundaryTrait;

/**
* Used in observability metric headers
Expand Down Expand Up @@ -130,12 +134,14 @@ class ServiceAccountCredentials extends CredentialsLoader implements
* @param string $sub an email address account to impersonate, in situations when
* the service account has been delegated domain wide access.
* @param string $targetAudience The audience for the ID token.
* @param bool $enableTrustBoundary Lookup and include the trust boundary header.
*/
public function __construct(
$scope,
$jsonKey,
$sub = null,
$targetAudience = null
$targetAudience = null,
bool $enableTrustBoundary = false
) {
if (is_string($jsonKey)) {
if (!file_exists($jsonKey)) {
Expand Down Expand Up @@ -183,6 +189,7 @@ public function __construct(

$this->projectId = $jsonKey['project_id'] ?? null;
$this->universeDomain = $jsonKey['universe_domain'] ?? self::DEFAULT_UNIVERSE_DOMAIN;
$this->enableTrustBoundary = $enableTrustBoundary;
}

/**
Expand Down Expand Up @@ -214,9 +221,11 @@ public function useJwtAccessWithScope()
*/
public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
{
$httpHandler = $httpHandler
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());

if ($this->useSelfSignedJwt()) {
$jwtCreds = $this->createJwtAccessCredentials();

$accessToken = $jwtCreds->fetchAuthToken($httpHandler);

if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) {
Expand Down Expand Up @@ -317,25 +326,48 @@ public function updateMetadata(
$authUri = null,
?callable $httpHandler = null
) {
// scope exists. use oauth implementation
if (!$this->useSelfSignedJwt()) {
return parent::updateMetadata($metadata, $authUri, $httpHandler);
}
$metadata = $this->useSelfSignedJwt()
? $this->updateMetadataSelfSignedJwt($metadata, $authUri, $httpHandler)
: parent::updateMetadata($metadata, $authUri, $httpHandler);

$metadata = $this->updateTrustBoundaryMetadata(
$metadata,
$this->auth->getIssuer(),
$this->getUniverseDomain(),
$httpHandler,
);

return $metadata;
}

/**
* Updates metadata with the authorization token for SSJWTs.
*
* @param array<mixed> $metadata metadata hashmap
* @param string $authUri optional auth uri
* @param callable|null $httpHandler callback which delivers psr7 request
* @return array<mixed> updated metadata hashmap
*/
private function updateMetadataSelfSignedJwt(
$metadata,
$authUri = null,
?callable $httpHandler = null
) {
$jwtCreds = $this->createJwtAccessCredentials();
if ($this->auth->getScope()) {

$metadata = $jwtCreds->updateMetadata(
$metadata,
// Prefer user-provided "scope" to "audience"
$updatedMetadata = $jwtCreds->updateMetadata($metadata, null, $httpHandler);
} else {
$updatedMetadata = $jwtCreds->updateMetadata($metadata, $authUri, $httpHandler);
}
$this->auth->getScope() ? null : $authUri,
$httpHandler
);

if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) {
// Keep self-signed JWTs in memory as the last received token
$this->lastReceivedJwtAccessToken = $lastReceivedToken;
}

return $updatedMetadata;
return $metadata;
}

/**
Expand Down
13 changes: 10 additions & 3 deletions src/CredentialsLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,22 @@ public static function fromWellKnownFile()
* @param string|string[] $scope
* @param array<mixed> $jsonKey
* @param string|string[] $defaultScope
* @param bool $enableTrustBoundary Lookup and include the trust boundary header.
* @return ServiceAccountCredentials|UserRefreshCredentials|ImpersonatedServiceAccountCredentials|ExternalAccountCredentials
*/
public static function makeCredentials(
$scope,
array $jsonKey,
$defaultScope = null
$defaultScope = null,
bool $enableTrustBoundary = false
) {
if (!array_key_exists('type', $jsonKey)) {
throw new \InvalidArgumentException('json key is missing the type field');
}

if ($jsonKey['type'] == 'service_account') {
// Do not pass $defaultScope to ServiceAccountCredentials
return new ServiceAccountCredentials($scope, $jsonKey);
return new ServiceAccountCredentials($scope, $jsonKey, enableTrustBoundary: $enableTrustBoundary);
}

if ($jsonKey['type'] == 'authorized_user') {
Expand All @@ -174,7 +176,12 @@ public static function makeCredentials(
}

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

if ($jsonKey['type'] == 'external_account') {
Expand Down
Loading
Loading