Skip to content

Commit

Permalink
feat: add universe domain support (#2563)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer authored Apr 24, 2024
1 parent 73fa9cf commit 35895de
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 16 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"license": "Apache-2.0",
"require": {
"php": "^7.4|^8.0",
"google/auth": "^1.33",
"google/apiclient-services": "~0.200",
"google/auth": "^1.37",
"google/apiclient-services": "~0.350",
"firebase/php-jwt": "~6.0",
"monolog/monolog": "^2.9||^3.0",
"phpseclib/phpseclib": "^3.0.36",
Expand Down
40 changes: 39 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use Google\Auth\Credentials\UserRefreshCredentials;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenCache;
use Google\Auth\GetUniverseDomainInterface;
use Google\Auth\HttpHandler\HttpHandlerFactory;
use Google\Auth\OAuth2;
use Google\AuthHandler\AuthHandlerFactory;
Expand Down Expand Up @@ -131,6 +132,10 @@ class Client
* @type string $developer_key
* Simple API access key, also from the API console. Ensure you get
* a Server key, and not a Browser key.
* **NOTE:** The universe domain is assumed to be "googleapis.com" unless
* explicitly set. When setting an API ley directly via this option, there
* is no way to verify the universe domain. Be sure to set the
* "universe_domain" option if "googleapis.com" is not intended.
* @type bool $use_application_default_credentials
* For use with Google Cloud Platform
* fetch the ApplicationDefaultCredentials, if applicable
Expand Down Expand Up @@ -164,6 +169,10 @@ class Client
* @type bool $api_format_v2
* Setting api_format_v2 will return more detailed error messages
* from certain APIs.
* @type string $universe_domain
* Setting the universe domain will change the default rootUrl of the service.
* If not set explicitly, the universe domain will be the value provided in the
*. "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com".
* }
*/
public function __construct(array $config = [])
Expand Down Expand Up @@ -197,7 +206,9 @@ public function __construct(array $config = [])
'cache_config' => [],
'token_callback' => null,
'jwt' => null,
'api_format_v2' => false
'api_format_v2' => false,
'universe_domain' => getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')
?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN,
], $config);

if (!is_null($this->config['credentials'])) {
Expand Down Expand Up @@ -449,6 +460,7 @@ public function authorize(ClientInterface $http = null)
// 3b. If access token exists but is expired, try to refresh it
// 4. Check for API Key
if ($this->credentials) {
$this->checkUniverseDomain($this->credentials);
return $authHandler->attachCredentials(
$http,
$this->credentials,
Expand All @@ -458,6 +470,7 @@ public function authorize(ClientInterface $http = null)

if ($this->isUsingApplicationDefaultCredentials()) {
$credentials = $this->createApplicationDefaultCredentials();
$this->checkUniverseDomain($credentials);
return $authHandler->attachCredentialsCache(
$http,
$credentials,
Expand All @@ -473,6 +486,7 @@ public function authorize(ClientInterface $http = null)
$scopes,
$token['refresh_token']
);
$this->checkUniverseDomain($credentials);
return $authHandler->attachCredentials(
$http,
$credentials,
Expand Down Expand Up @@ -525,6 +539,11 @@ public function isUsingApplicationDefaultCredentials()
* as calling `clear()` will remove all cache items, including any items not
* related to Google API PHP Client.)
*
* **NOTE:** The universe domain is assumed to be "googleapis.com" unless
* explicitly set. When setting an access token directly via this method, there
* is no way to verify the universe domain. Be sure to set the "universe_domain"
* option if "googleapis.com" is not intended.
*
* @param string|array $token
* @throws InvalidArgumentException
*/
Expand Down Expand Up @@ -1318,4 +1337,23 @@ private function createUserRefreshCredentials($scope, $refreshToken)

return new UserRefreshCredentials($scope, $creds);
}

private function checkUniverseDomain($credentials)
{
$credentialsUniverse = $credentials instanceof GetUniverseDomainInterface
? $credentials->getUniverseDomain()
: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
if ($credentialsUniverse !== $this->getUniverseDomain()) {
throw new DomainException(sprintf(
'The configured universe domain (%s) does not match the credential universe domain (%s)',
$this->getUniverseDomain(),
$credentialsUniverse
));
}
}

public function getUniverseDomain()
{
return $this->config['universe_domain'];
}
}
7 changes: 6 additions & 1 deletion src/Http/Batch.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ public function __construct(
) {
$this->client = $client;
$this->boundary = $boundary ?: mt_rand();
$this->rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
$rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
$this->rootUrl = str_replace(
'UNIVERSE_DOMAIN',
$this->client->getUniverseDomain(),
$rootUrl
);
$this->batchPath = $batchPath ?: self::BATCH_PATH;
}

Expand Down
6 changes: 5 additions & 1 deletion src/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
class Service
{
public $batchPath;
/**
* Only used in getBatch
*/
public $rootUrl;
public $rootUrlTemplate;
public $version;
public $servicePath;
public $serviceName;
Expand Down Expand Up @@ -65,7 +69,7 @@ public function createBatch()
return new Batch(
$this->client,
false,
$this->rootUrl,
$this->rootUrlTemplate ?? $this->rootUrl,
$this->batchPath
);
}
Expand Down
16 changes: 9 additions & 7 deletions src/Service/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class Resource
'prettyPrint' => ['type' => 'string', 'location' => 'query'],
];

/** @var string $rootUrl */
private $rootUrl;
/** @var string $rootUrlTemplate */
private $rootUrlTemplate;

/** @var \Google\Client $client */
private $client;
Expand All @@ -65,7 +65,7 @@ class Resource

public function __construct($service, $serviceName, $resourceName, $resource)
{
$this->rootUrl = $service->rootUrl;
$this->rootUrlTemplate = $service->rootUrlTemplate ?? $service->rootUrl;
$this->client = $service->getClient();
$this->servicePath = $service->servicePath;
$this->serviceName = $serviceName;
Expand Down Expand Up @@ -268,12 +268,14 @@ public function createRequestUri($restPath, $params)
$requestUrl = $this->servicePath . $restPath;
}

// code for leading slash
if ($this->rootUrl) {
if ('/' !== substr($this->rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) {
if ($this->rootUrlTemplate) {
// code for universe domain
$rootUrl = str_replace('UNIVERSE_DOMAIN', $this->client->getUniverseDomain(), $this->rootUrlTemplate);
// code for leading slash
if ('/' !== substr($rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) {
$requestUrl = '/' . $requestUrl;
}
$requestUrl = $this->rootUrl . $requestUrl;
$requestUrl = $rootUrl . $requestUrl;
}
$uriTemplateVars = [];
$queryVars = [];
Expand Down
31 changes: 31 additions & 0 deletions tests/Google/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
use Google\Service\Drive;
use Google\AuthHandler\AuthHandlerFactory;
use Google\Auth\FetchAuthTokenCache;
use Google\Auth\CredentialsLoader;
use Google\Auth\GCECache;
use Google\Auth\Credentials\GCECredentials;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
Expand All @@ -37,6 +39,7 @@
use ReflectionMethod;
use InvalidArgumentException;
use Exception;
use DomainException;

class ClientTest extends BaseTest
{
Expand Down Expand Up @@ -689,11 +692,20 @@ public function testOnGceCacheAndCacheOptions()
$mockCacheItem->get()
->shouldBeCalledTimes(1)
->willReturn(true);
$mockUniverseDomainCacheItem = $this->prophesize(CacheItemInterface::class);
$mockUniverseDomainCacheItem->isHit()
->willReturn(true);
$mockUniverseDomainCacheItem->get()
->shouldBeCalledTimes(1)
->willReturn('googleapis.com');

$mockCache = $this->prophesize(CacheItemPoolInterface::class);
$mockCache->getItem($prefix . GCECache::GCE_CACHE_KEY)
->shouldBeCalledTimes(1)
->willReturn($mockCacheItem->reveal());
$mockCache->getItem(GCECredentials::cacheKey . 'universe_domain')
->shouldBeCalledTimes(1)
->willReturn($mockUniverseDomainCacheItem->reveal());

$client = new Client(['cache_config' => $cacheConfig]);
$client->setCache($mockCache->reveal());
Expand Down Expand Up @@ -849,6 +861,8 @@ public function testCredentialsOptionWithCredentialsLoader()
$credentials = $this->prophesize('Google\Auth\CredentialsLoader');
$credentials->getCacheKey()
->willReturn('cache-key');
$credentials->getUniverseDomain()
->willReturn('googleapis.com');

// Ensure the access token provided by our credentials loader is used
$credentials->updateMetadata([], null, Argument::any())
Expand Down Expand Up @@ -913,4 +927,21 @@ public function testQueryParamsForAuthUrl()
]);
$this->assertStringContainsString('&enable_serial_consent=true', $authUrl1);
}
public function testUniverseDomainMismatch()
{
$this->expectException(DomainException::class);
$this->expectExceptionMessage(
'The configured universe domain (example.com) does not match the credential universe domain (foo.com)'
);

$credentials = $this->prophesize(CredentialsLoader::class);
$credentials->getUniverseDomain()
->shouldBeCalledOnce()
->willReturn('foo.com');
$client = new Client([
'universe_domain' => 'example.com',
'credentials' => $credentials->reveal(),
]);
$client->authorize();
}
}
41 changes: 37 additions & 4 deletions tests/Google/Service/ResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@

class TestService extends \Google\Service
{
public function __construct(Client $client)
public function __construct(Client $client, $rootUrl = null)
{
parent::__construct($client);
$this->rootUrl = "https://test.example.com";
$this->rootUrl = $rootUrl ?: "https://test.example.com";
$this->rootUrlTemplate = $rootUrl ?: "https://test.UNIVERSE_DOMAIN";
$this->servicePath = "";
$this->version = "v1beta1";
$this->serviceName = "test";
Expand All @@ -59,6 +60,7 @@ public function setUp(): void
$this->client->getLogger()->willReturn($logger->reveal());
$this->client->shouldDefer()->willReturn(true);
$this->client->getHttpClient()->willReturn(new GuzzleClient());
$this->client->getUniverseDomain()->willReturn('example.com');

$this->service = new TestService($this->client->reveal());
}
Expand Down Expand Up @@ -106,6 +108,37 @@ public function testCall()
$this->assertFalse($request->hasHeader('Content-Type'));
}

public function testCallWithUniverseDomainTemplate()
{
$client = $this->prophesize(Client::class);
$logger = $this->prophesize("Monolog\Logger");
$this->client->getLogger()->willReturn($logger->reveal());
$this->client->shouldDefer()->willReturn(true);
$this->client->getHttpClient()->willReturn(new GuzzleClient());
$this->client->getUniverseDomain()->willReturn('example-universe-domain.com');

$this->service = new TestService($this->client->reveal());

$resource = new GoogleResource(
$this->service,
"test",
"testResource",
[
"methods" => [
"testMethod" => [
"parameters" => [],
"path" => "method/path",
"httpMethod" => "POST",
]
]
]
);
$request = $resource->call("testMethod", [[]]);
$this->assertEquals("https://test.example-universe-domain.com/method/path", (string) $request->getUri());
$this->assertEquals("POST", $request->getMethod());
$this->assertFalse($request->hasHeader('Content-Type'));
}

public function testCallWithPostBody()
{
$resource = new GoogleResource(
Expand All @@ -130,9 +163,9 @@ public function testCallWithPostBody()

public function testCallServiceDefinedRoot()
{
$this->service->rootUrl = "https://sample.example.com";
$service = new TestService($this->client->reveal(), "https://sample.example.com");
$resource = new GoogleResource(
$this->service,
$service,
"test",
"testResource",
[
Expand Down
1 change: 1 addition & 0 deletions tests/Google/ServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function ($request) {
)->willReturn($response->reveal());

$client->getConfig('base_path')->willReturn('');
$client->getUniverseDomain()->willReturn('');

$model = new TestService($client->reveal());
$batch = $model->createBatch();
Expand Down

0 comments on commit 35895de

Please sign in to comment.