Skip to content

Commit 35895de

Browse files
authored
feat: add universe domain support (#2563)
1 parent 73fa9cf commit 35895de

File tree

8 files changed

+130
-16
lines changed

8 files changed

+130
-16
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
"license": "Apache-2.0",
88
"require": {
99
"php": "^7.4|^8.0",
10-
"google/auth": "^1.33",
11-
"google/apiclient-services": "~0.200",
10+
"google/auth": "^1.37",
11+
"google/apiclient-services": "~0.350",
1212
"firebase/php-jwt": "~6.0",
1313
"monolog/monolog": "^2.9||^3.0",
1414
"phpseclib/phpseclib": "^3.0.36",

src/Client.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Google\Auth\Credentials\UserRefreshCredentials;
2828
use Google\Auth\CredentialsLoader;
2929
use Google\Auth\FetchAuthTokenCache;
30+
use Google\Auth\GetUniverseDomainInterface;
3031
use Google\Auth\HttpHandler\HttpHandlerFactory;
3132
use Google\Auth\OAuth2;
3233
use Google\AuthHandler\AuthHandlerFactory;
@@ -131,6 +132,10 @@ class Client
131132
* @type string $developer_key
132133
* Simple API access key, also from the API console. Ensure you get
133134
* a Server key, and not a Browser key.
135+
* **NOTE:** The universe domain is assumed to be "googleapis.com" unless
136+
* explicitly set. When setting an API ley directly via this option, there
137+
* is no way to verify the universe domain. Be sure to set the
138+
* "universe_domain" option if "googleapis.com" is not intended.
134139
* @type bool $use_application_default_credentials
135140
* For use with Google Cloud Platform
136141
* fetch the ApplicationDefaultCredentials, if applicable
@@ -164,6 +169,10 @@ class Client
164169
* @type bool $api_format_v2
165170
* Setting api_format_v2 will return more detailed error messages
166171
* from certain APIs.
172+
* @type string $universe_domain
173+
* Setting the universe domain will change the default rootUrl of the service.
174+
* If not set explicitly, the universe domain will be the value provided in the
175+
*. "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com".
167176
* }
168177
*/
169178
public function __construct(array $config = [])
@@ -197,7 +206,9 @@ public function __construct(array $config = [])
197206
'cache_config' => [],
198207
'token_callback' => null,
199208
'jwt' => null,
200-
'api_format_v2' => false
209+
'api_format_v2' => false,
210+
'universe_domain' => getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')
211+
?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN,
201212
], $config);
202213

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

459471
if ($this->isUsingApplicationDefaultCredentials()) {
460472
$credentials = $this->createApplicationDefaultCredentials();
473+
$this->checkUniverseDomain($credentials);
461474
return $authHandler->attachCredentialsCache(
462475
$http,
463476
$credentials,
@@ -473,6 +486,7 @@ public function authorize(ClientInterface $http = null)
473486
$scopes,
474487
$token['refresh_token']
475488
);
489+
$this->checkUniverseDomain($credentials);
476490
return $authHandler->attachCredentials(
477491
$http,
478492
$credentials,
@@ -525,6 +539,11 @@ public function isUsingApplicationDefaultCredentials()
525539
* as calling `clear()` will remove all cache items, including any items not
526540
* related to Google API PHP Client.)
527541
*
542+
* **NOTE:** The universe domain is assumed to be "googleapis.com" unless
543+
* explicitly set. When setting an access token directly via this method, there
544+
* is no way to verify the universe domain. Be sure to set the "universe_domain"
545+
* option if "googleapis.com" is not intended.
546+
*
528547
* @param string|array $token
529548
* @throws InvalidArgumentException
530549
*/
@@ -1318,4 +1337,23 @@ private function createUserRefreshCredentials($scope, $refreshToken)
13181337

13191338
return new UserRefreshCredentials($scope, $creds);
13201339
}
1340+
1341+
private function checkUniverseDomain($credentials)
1342+
{
1343+
$credentialsUniverse = $credentials instanceof GetUniverseDomainInterface
1344+
? $credentials->getUniverseDomain()
1345+
: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
1346+
if ($credentialsUniverse !== $this->getUniverseDomain()) {
1347+
throw new DomainException(sprintf(
1348+
'The configured universe domain (%s) does not match the credential universe domain (%s)',
1349+
$this->getUniverseDomain(),
1350+
$credentialsUniverse
1351+
));
1352+
}
1353+
}
1354+
1355+
public function getUniverseDomain()
1356+
{
1357+
return $this->config['universe_domain'];
1358+
}
13211359
}

src/Http/Batch.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ public function __construct(
6262
) {
6363
$this->client = $client;
6464
$this->boundary = $boundary ?: mt_rand();
65-
$this->rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
65+
$rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
66+
$this->rootUrl = str_replace(
67+
'UNIVERSE_DOMAIN',
68+
$this->client->getUniverseDomain(),
69+
$rootUrl
70+
);
6671
$this->batchPath = $batchPath ?: self::BATCH_PATH;
6772
}
6873

src/Service.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
class Service
2424
{
2525
public $batchPath;
26+
/**
27+
* Only used in getBatch
28+
*/
2629
public $rootUrl;
30+
public $rootUrlTemplate;
2731
public $version;
2832
public $servicePath;
2933
public $serviceName;
@@ -65,7 +69,7 @@ public function createBatch()
6569
return new Batch(
6670
$this->client,
6771
false,
68-
$this->rootUrl,
72+
$this->rootUrlTemplate ?? $this->rootUrl,
6973
$this->batchPath
7074
);
7175
}

src/Service/Resource.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ class Resource
4545
'prettyPrint' => ['type' => 'string', 'location' => 'query'],
4646
];
4747

48-
/** @var string $rootUrl */
49-
private $rootUrl;
48+
/** @var string $rootUrlTemplate */
49+
private $rootUrlTemplate;
5050

5151
/** @var \Google\Client $client */
5252
private $client;
@@ -65,7 +65,7 @@ class Resource
6565

6666
public function __construct($service, $serviceName, $resourceName, $resource)
6767
{
68-
$this->rootUrl = $service->rootUrl;
68+
$this->rootUrlTemplate = $service->rootUrlTemplate ?? $service->rootUrl;
6969
$this->client = $service->getClient();
7070
$this->servicePath = $service->servicePath;
7171
$this->serviceName = $serviceName;
@@ -268,12 +268,14 @@ public function createRequestUri($restPath, $params)
268268
$requestUrl = $this->servicePath . $restPath;
269269
}
270270

271-
// code for leading slash
272-
if ($this->rootUrl) {
273-
if ('/' !== substr($this->rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) {
271+
if ($this->rootUrlTemplate) {
272+
// code for universe domain
273+
$rootUrl = str_replace('UNIVERSE_DOMAIN', $this->client->getUniverseDomain(), $this->rootUrlTemplate);
274+
// code for leading slash
275+
if ('/' !== substr($rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) {
274276
$requestUrl = '/' . $requestUrl;
275277
}
276-
$requestUrl = $this->rootUrl . $requestUrl;
278+
$requestUrl = $rootUrl . $requestUrl;
277279
}
278280
$uriTemplateVars = [];
279281
$queryVars = [];

tests/Google/ClientTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
use Google\Service\Drive;
2525
use Google\AuthHandler\AuthHandlerFactory;
2626
use Google\Auth\FetchAuthTokenCache;
27+
use Google\Auth\CredentialsLoader;
2728
use Google\Auth\GCECache;
29+
use Google\Auth\Credentials\GCECredentials;
2830
use GuzzleHttp\Client as GuzzleClient;
2931
use GuzzleHttp\Psr7\Request;
3032
use GuzzleHttp\Psr7\Response;
@@ -37,6 +39,7 @@
3739
use ReflectionMethod;
3840
use InvalidArgumentException;
3941
use Exception;
42+
use DomainException;
4043

4144
class ClientTest extends BaseTest
4245
{
@@ -689,11 +692,20 @@ public function testOnGceCacheAndCacheOptions()
689692
$mockCacheItem->get()
690693
->shouldBeCalledTimes(1)
691694
->willReturn(true);
695+
$mockUniverseDomainCacheItem = $this->prophesize(CacheItemInterface::class);
696+
$mockUniverseDomainCacheItem->isHit()
697+
->willReturn(true);
698+
$mockUniverseDomainCacheItem->get()
699+
->shouldBeCalledTimes(1)
700+
->willReturn('googleapis.com');
692701

693702
$mockCache = $this->prophesize(CacheItemPoolInterface::class);
694703
$mockCache->getItem($prefix . GCECache::GCE_CACHE_KEY)
695704
->shouldBeCalledTimes(1)
696705
->willReturn($mockCacheItem->reveal());
706+
$mockCache->getItem(GCECredentials::cacheKey . 'universe_domain')
707+
->shouldBeCalledTimes(1)
708+
->willReturn($mockUniverseDomainCacheItem->reveal());
697709

698710
$client = new Client(['cache_config' => $cacheConfig]);
699711
$client->setCache($mockCache->reveal());
@@ -849,6 +861,8 @@ public function testCredentialsOptionWithCredentialsLoader()
849861
$credentials = $this->prophesize('Google\Auth\CredentialsLoader');
850862
$credentials->getCacheKey()
851863
->willReturn('cache-key');
864+
$credentials->getUniverseDomain()
865+
->willReturn('googleapis.com');
852866

853867
// Ensure the access token provided by our credentials loader is used
854868
$credentials->updateMetadata([], null, Argument::any())
@@ -913,4 +927,21 @@ public function testQueryParamsForAuthUrl()
913927
]);
914928
$this->assertStringContainsString('&enable_serial_consent=true', $authUrl1);
915929
}
930+
public function testUniverseDomainMismatch()
931+
{
932+
$this->expectException(DomainException::class);
933+
$this->expectExceptionMessage(
934+
'The configured universe domain (example.com) does not match the credential universe domain (foo.com)'
935+
);
936+
937+
$credentials = $this->prophesize(CredentialsLoader::class);
938+
$credentials->getUniverseDomain()
939+
->shouldBeCalledOnce()
940+
->willReturn('foo.com');
941+
$client = new Client([
942+
'universe_domain' => 'example.com',
943+
'credentials' => $credentials->reveal(),
944+
]);
945+
$client->authorize();
946+
}
916947
}

tests/Google/Service/ResourceTest.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535

3636
class TestService extends \Google\Service
3737
{
38-
public function __construct(Client $client)
38+
public function __construct(Client $client, $rootUrl = null)
3939
{
4040
parent::__construct($client);
41-
$this->rootUrl = "https://test.example.com";
41+
$this->rootUrl = $rootUrl ?: "https://test.example.com";
42+
$this->rootUrlTemplate = $rootUrl ?: "https://test.UNIVERSE_DOMAIN";
4243
$this->servicePath = "";
4344
$this->version = "v1beta1";
4445
$this->serviceName = "test";
@@ -59,6 +60,7 @@ public function setUp(): void
5960
$this->client->getLogger()->willReturn($logger->reveal());
6061
$this->client->shouldDefer()->willReturn(true);
6162
$this->client->getHttpClient()->willReturn(new GuzzleClient());
63+
$this->client->getUniverseDomain()->willReturn('example.com');
6264

6365
$this->service = new TestService($this->client->reveal());
6466
}
@@ -106,6 +108,37 @@ public function testCall()
106108
$this->assertFalse($request->hasHeader('Content-Type'));
107109
}
108110

111+
public function testCallWithUniverseDomainTemplate()
112+
{
113+
$client = $this->prophesize(Client::class);
114+
$logger = $this->prophesize("Monolog\Logger");
115+
$this->client->getLogger()->willReturn($logger->reveal());
116+
$this->client->shouldDefer()->willReturn(true);
117+
$this->client->getHttpClient()->willReturn(new GuzzleClient());
118+
$this->client->getUniverseDomain()->willReturn('example-universe-domain.com');
119+
120+
$this->service = new TestService($this->client->reveal());
121+
122+
$resource = new GoogleResource(
123+
$this->service,
124+
"test",
125+
"testResource",
126+
[
127+
"methods" => [
128+
"testMethod" => [
129+
"parameters" => [],
130+
"path" => "method/path",
131+
"httpMethod" => "POST",
132+
]
133+
]
134+
]
135+
);
136+
$request = $resource->call("testMethod", [[]]);
137+
$this->assertEquals("https://test.example-universe-domain.com/method/path", (string) $request->getUri());
138+
$this->assertEquals("POST", $request->getMethod());
139+
$this->assertFalse($request->hasHeader('Content-Type'));
140+
}
141+
109142
public function testCallWithPostBody()
110143
{
111144
$resource = new GoogleResource(
@@ -130,9 +163,9 @@ public function testCallWithPostBody()
130163

131164
public function testCallServiceDefinedRoot()
132165
{
133-
$this->service->rootUrl = "https://sample.example.com";
166+
$service = new TestService($this->client->reveal(), "https://sample.example.com");
134167
$resource = new GoogleResource(
135-
$this->service,
168+
$service,
136169
"test",
137170
"testResource",
138171
[

tests/Google/ServiceTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ function ($request) {
8080
)->willReturn($response->reveal());
8181

8282
$client->getConfig('base_path')->willReturn('');
83+
$client->getUniverseDomain()->willReturn('');
8384

8485
$model = new TestService($client->reveal());
8586
$batch = $model->createBatch();

0 commit comments

Comments
 (0)