Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for universe domain (phase 1) #477

Merged
merged 18 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 8 additions & 2 deletions src/ApplicationDefaultCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ public static function getMiddleware(
* @param string|string[] $defaultScope The default scope to use if no
* user-defined scopes exist, expressed either as an Array or as a
* space-delimited string.
* @param string $universeDomain Specifies a universe domain to use for the
* calling client library
*
* @return FetchAuthTokenInterface
* @throws DomainException if no implementation can be obtained.
Expand All @@ -154,7 +156,8 @@ public static function getCredentials(
array $cacheConfig = null,
CacheItemPoolInterface $cache = null,
$quotaProject = null,
$defaultScope = null
$defaultScope = null,
string $universeDomain = null
) {
$creds = null;
$jsonKey = CredentialsLoader::fromEnv()
Expand All @@ -179,6 +182,9 @@ public static function getCredentials(
if ($quotaProject) {
$jsonKey['quota_project_id'] = $quotaProject;
}
if ($universeDomain) {
$jsonKey['universe_domain'] = $universeDomain;
}
$creds = CredentialsLoader::makeCredentials(
$scope,
$jsonKey,
Expand All @@ -187,7 +193,7 @@ public static function getCredentials(
} elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) {
$creds = new AppIdentityCredentials($anyScope);
} elseif (self::onGce($httpHandler, $cacheConfig, $cache)) {
$creds = new GCECredentials(null, $anyScope, null, $quotaProject);
$creds = new GCECredentials(null, $anyScope, null, $quotaProject, null, $universeDomain);
$creds->setIsOnGce(true); // save the credentials a trip to the metadata server
}

Expand Down
67 changes: 66 additions & 1 deletion src/Credentials/GCECredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ class GCECredentials extends CredentialsLoader implements
*/
const PROJECT_ID_URI_PATH = 'v1/project/project-id';

/**
* The metadata path of the project ID.
*/
const UNIVERSE_DOMAIN_URI_PATH = 'v1/universe/universe_domain';

/**
* The header whose presence indicates GCE presence.
*/
Expand Down Expand Up @@ -169,6 +174,11 @@ class GCECredentials extends CredentialsLoader implements
*/
private $serviceAccountIdentity;

/**
* @var string
*/
private ?string $universeDomain;

/**
* @param Iam $iam [optional] An IAM instance.
* @param string|string[] $scope [optional] the scope of the access request,
Expand All @@ -178,13 +188,16 @@ class GCECredentials extends CredentialsLoader implements
* charges associated with the request.
* @param string $serviceAccountIdentity [optional] Specify a service
* account identity name to use instead of "default".
* @param string $universeDomain [optional] Specify a universe domain to use
* instead of fetching one from the metadata server.
*/
public function __construct(
Iam $iam = null,
$scope = null,
$targetAudience = null,
$quotaProject = null,
$serviceAccountIdentity = null
$serviceAccountIdentity = null,
string $universeDomain = null
) {
$this->iam = $iam;

Expand Down Expand Up @@ -212,6 +225,7 @@ public function __construct(
$this->tokenUri = $tokenUri;
$this->quotaProject = $quotaProject;
$this->serviceAccountIdentity = $serviceAccountIdentity;
$this->universeDomain = $universeDomain;
}

/**
Expand Down Expand Up @@ -294,6 +308,18 @@ private static function getProjectIdUri()
return $base . self::PROJECT_ID_URI_PATH;
}

/**
* The full uri for accessing the default universe domain.
*
* @return string
*/
private static function getUniverseDomainUri()
{
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';

return $base . self::UNIVERSE_DOMAIN_URI_PATH;
}

/**
* Determines if this an App Engine Flexible instance, by accessing the
* GAE_INSTANCE environment variable.
Expand Down Expand Up @@ -500,6 +526,45 @@ public function getProjectId(callable $httpHandler = null)
return $this->projectId;
}

/**
* Fetch the default universe domain from the metadata server.
*
* Returns null if called outside GCE.
*
* @param callable $httpHandler Callback which delivers psr7 request
* @return string
*/
public function getUniverseDomain(callable $httpHandler = null): string
{
if ($this->universeDomain) {
return $this->universeDomain;
}

$httpHandler = $httpHandler
?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());

if (!$this->hasCheckedOnGce) {
$this->isOnGce = self::onGce($httpHandler);
$this->hasCheckedOnGce = true;
}

if (!$this->isOnGce) {
return self::DEFAULT_UNIVERSE_DOMAIN;
}

// In the case of 404, we return the default universe domain.
try {
$this->universeDomain = $this->getFromMetadata(
$httpHandler,
self::getUniverseDomainUri()
);
} catch (ClientException $e) {
return self::DEFAULT_UNIVERSE_DOMAIN;
bshaffer marked this conversation as resolved.
Show resolved Hide resolved
}

return $this->universeDomain;
}

/**
* Fetch the value of a GCE metadata server URI.
*
Expand Down
16 changes: 16 additions & 0 deletions src/Credentials/ServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ class ServiceAccountCredentials extends CredentialsLoader implements
*/
private $jwtAccessCredentials;

/**
* @var string|null
vishwarajanand marked this conversation as resolved.
Show resolved Hide resolved
*/
private ?string $universeDomain;

/**
* Create a new ServiceAccountCredentials.
*
Expand Down Expand Up @@ -159,6 +164,7 @@ public function __construct(
]);

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

/**
Expand Down Expand Up @@ -328,6 +334,16 @@ public function getQuotaProject()
return $this->quotaProject;
}

/**
* Get the universe domain configured in the JSON credential.
*
* @return string
*/
public function getUniverseDomain(): string
{
return $this->universeDomain ?: self::DEFAULT_UNIVERSE_DOMAIN;
}

/**
* @return bool
*/
Expand Down
16 changes: 16 additions & 0 deletions src/Credentials/UserRefreshCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class UserRefreshCredentials extends CredentialsLoader implements GetQuotaProjec
*/
protected $quotaProject;

/**
* @var string|null
*/
private ?string $universeDomain;

/**
* Create a new UserRefreshCredentials.
*
Expand Down Expand Up @@ -94,6 +99,7 @@ public function __construct(
if (array_key_exists('quota_project_id', $jsonKey)) {
$this->quotaProject = (string) $jsonKey['quota_project_id'];
}
$this->universeDomain = $jsonKey['universe_domain'] ?? null;
}

/**
Expand Down Expand Up @@ -140,6 +146,16 @@ public function getQuotaProject()
return $this->quotaProject;
}

/**
* Get the universe domain configured in the JSON credential.
*
* @return string
*/
public function getUniverseDomain(): string
{
return $this->universeDomain ?: self::DEFAULT_UNIVERSE_DOMAIN;
}

/**
* Get the granted scopes (if they exist) for the last fetched token.
*
Expand Down
12 changes: 12 additions & 0 deletions src/CredentialsLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* credentials files on the file system.
*/
abstract class CredentialsLoader implements
GetUniverseDomainInterface,
FetchAuthTokenInterface,
UpdateMetadataInterface
{
Expand Down Expand Up @@ -273,4 +274,15 @@ private static function loadDefaultClientCertSourceFile()
}
return $clientCertSourceJson;
}

/**
* Get the universe domain from the credential. Defaults to
* "googleapis.com" for all credential types.
*
* @return string
*/
public function getUniverseDomain(): string
{
return self::DEFAULT_UNIVERSE_DOMAIN;
}
}
35 changes: 35 additions & 0 deletions src/GetUniverseDomainInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Auth;

/**
* An interface implemented by objects that can get quota projects.
bshaffer marked this conversation as resolved.
Show resolved Hide resolved
*/
interface GetUniverseDomainInterface
{
const DEFAULT_UNIVERSE_DOMAIN = 'googleapis.com';

/**
* Get the universe domain from the credential. This should always return
* a string, and default to "googleapis.com" if no universe domain is
* configured.
*
* @return string
*/
public function getUniverseDomain(): string;
}
68 changes: 68 additions & 0 deletions tests/ApplicationDefaultCredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -782,4 +782,72 @@ public function provideExternalAccountCredentials()
['url_credentials.json', CredentialSource\UrlSource::class],
];
}

/** @runInSeparateProcess */
public function testUniverseDomainInKeyFile()
{
// Test no universe domain in keyfile defaults to "googleapis.com"
$keyFile = __DIR__ . '/fixtures/private.json';
putenv(ServiceAccountCredentials::ENV_VAR . '=' . $keyFile);
$creds = ApplicationDefaultCredentials::getCredentials();
$this->assertEquals(CredentialsLoader::DEFAULT_UNIVERSE_DOMAIN, $creds->getUniverseDomain());

// Test universe domain from keyfile
$keyFile = __DIR__ . '/fixtures2/private.json';
putenv(ServiceAccountCredentials::ENV_VAR . '=' . $keyFile);
$creds2 = ApplicationDefaultCredentials::getCredentials();
$this->assertEquals('example-universe.com', $creds2->getUniverseDomain());

// test passing in a different universe domain overrides keyfile
$creds3 = ApplicationDefaultCredentials::getCredentials(
null,
null,
null,
null,
null,
null,
'example-universe2.com'
);
$this->assertEquals('example-universe2.com', $creds3->getUniverseDomain());
}

/** @runInSeparateProcess */
public function testUniverseDomainInGceCredentials()
{
putenv('HOME');

$expectedUniverseDomain = 'example-universe.com';
$creds = ApplicationDefaultCredentials::getCredentials(
null, // $scope
$httpHandler = getHandler([
new Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']),
new Response(200, [], Utils::streamFor($expectedUniverseDomain)),
]) // $httpHandler
);
$this->assertEquals('example-universe.com', $creds->getUniverseDomain($httpHandler));

// test passing in a different universe domain overrides metadata server
$creds2 = ApplicationDefaultCredentials::getCredentials(
null, // $scope
$httpHandler = getHandler([
new Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']),
]), // $httpHandler
null, // $cacheConfig
null, // $cache
null, // $quotaProject
null, // $defaultScope
'example-universe2.com' // $universeDomain
);
$this->assertEquals('example-universe2.com', $creds2->getUniverseDomain($httpHandler));

// test error response returns default universe domain
$creds2 = ApplicationDefaultCredentials::getCredentials(
null, // $scope
$httpHandler = getHandler([
new Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']),
new Response(404),
]), // $httpHandler
);
$this->assertEquals(CredentialsLoader::DEFAULT_UNIVERSE_DOMAIN, $creds2->getUniverseDomain($httpHandler));
}
}
Loading