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 universe domain support #2563

Merged
merged 14 commits into from
Apr 24, 2024
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
Loading