$jsonKey JSON credentials as an associative array. */ public function __construct($scope, array $jsonKey) { if (!\array_key_exists('type', $jsonKey)) { throw new \InvalidArgumentException('json key is missing the type field'); } if ($jsonKey['type'] !== self::EXTERNAL_ACCOUNT_TYPE) { throw new \InvalidArgumentException(\sprintf('expected "%s" type but received "%s"', self::EXTERNAL_ACCOUNT_TYPE, $jsonKey['type'])); } if (!\array_key_exists('token_url', $jsonKey)) { throw new \InvalidArgumentException('json key is missing the token_url field'); } if (!\array_key_exists('audience', $jsonKey)) { throw new \InvalidArgumentException('json key is missing the audience field'); } if (!\array_key_exists('subject_token_type', $jsonKey)) { throw new \InvalidArgumentException('json key is missing the subject_token_type field'); } if (!\array_key_exists('credential_source', $jsonKey)) { throw new \InvalidArgumentException('json key is missing the credential_source field'); } if (\array_key_exists('service_account_impersonation_url', $jsonKey)) { $this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url']; } $this->quotaProject = $jsonKey['quota_project_id'] ?? null; $this->workforcePoolUserProject = $jsonKey['workforce_pool_user_project'] ?? null; $this->universeDomain = $jsonKey['universe_domain'] ?? \Google\Site_Kit_Dependencies\Google\Auth\GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; $this->auth = new \Google\Site_Kit_Dependencies\Google\Auth\OAuth2(['tokenCredentialUri' => $jsonKey['token_url'], 'audience' => $jsonKey['audience'], 'scope' => $scope, 'subjectTokenType' => $jsonKey['subject_token_type'], 'subjectTokenFetcher' => self::buildCredentialSource($jsonKey), 'additionalOptions' => $this->workforcePoolUserProject ? ['userProject' => $this->workforcePoolUserProject] : []]); if (!$this->isWorkforcePool() && $this->workforcePoolUserProject) { throw new \InvalidArgumentException('workforce_pool_user_project should not be set for non-workforce pool credentials.'); } } /** * @param array $jsonKey */ private static function buildCredentialSource(array $jsonKey) : \Google\Site_Kit_Dependencies\Google\Auth\ExternalAccountCredentialSourceInterface { $credentialSource = $jsonKey['credential_source']; if (isset($credentialSource['file'])) { return new \Google\Site_Kit_Dependencies\Google\Auth\CredentialSource\FileSource($credentialSource['file'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null); } if (isset($credentialSource['environment_id']) && 1 === \preg_match('/^aws(\\d+)$/', $credentialSource['environment_id'], $matches)) { if ($matches[1] !== '1') { throw new \InvalidArgumentException("aws version \"{$matches[1]}\" is not supported in the current build."); } if (!\array_key_exists('regional_cred_verification_url', $credentialSource)) { throw new \InvalidArgumentException('The regional_cred_verification_url field is required for aws1 credential source.'); } if (!\array_key_exists('audience', $jsonKey)) { throw new \InvalidArgumentException('aws1 credential source requires an audience to be set in the JSON file.'); } return new \Google\Site_Kit_Dependencies\Google\Auth\CredentialSource\AwsNativeSource( $jsonKey['audience'], $credentialSource['regional_cred_verification_url'], // $regionalCredVerificationUrl $credentialSource['region_url'] ?? null, // $regionUrl $credentialSource['url'] ?? null, // $securityCredentialsUrl $credentialSource['imdsv2_session_token_url'] ?? null ); } if (isset($credentialSource['url'])) { return new \Google\Site_Kit_Dependencies\Google\Auth\CredentialSource\UrlSource($credentialSource['url'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null, $credentialSource['headers'] ?? null); } throw new \InvalidArgumentException('Unable to determine credential source from json key.'); } /** * @param string $stsToken * @param callable $httpHandler * * @return array { * A set of auth related metadata, containing the following * * @type string $access_token * @type int $expires_at * } */ private function getImpersonatedAccessToken(string $stsToken, ?callable $httpHandler = null) : array { if (!isset($this->serviceAccountImpersonationUrl)) { throw new \InvalidArgumentException('service_account_impersonation_url must be set in JSON credentials.'); } $request = new \Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Request('POST', $this->serviceAccountImpersonationUrl, ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $stsToken], (string) \json_encode(['lifetime' => \sprintf('%ss', \Google\Site_Kit_Dependencies\Google\Auth\OAuth2::DEFAULT_EXPIRY_SECONDS), 'scope' => \explode(' ', $this->auth->getScope())])); if (\is_null($httpHandler)) { $httpHandler = \Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory::build(\Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpClientCache::getHttpClient()); } $response = $httpHandler($request); $body = \json_decode((string) $response->getBody(), \true); return ['access_token' => $body['accessToken'], 'expires_at' => \strtotime($body['expireTime'])]; } /** * @param callable $httpHandler * * @return array { * A set of auth related metadata, containing the following * * @type string $access_token * @type int $expires_at (impersonated service accounts only) * @type int $expires_in (identity pool only) * @type string $issued_token_type (identity pool only) * @type string $token_type (identity pool only) * } */ public function fetchAuthToken(?callable $httpHandler = null) { $stsToken = $this->auth->fetchAuthToken($httpHandler); if (isset($this->serviceAccountImpersonationUrl)) { return $this->getImpersonatedAccessToken($stsToken['access_token'], $httpHandler); } return $stsToken; } public function getCacheKey() { return $this->auth->getCacheKey(); } public function getLastReceivedToken() { return $this->auth->getLastReceivedToken(); } /** * Get the quota project used for this API request * * @return string|null */ public function getQuotaProject() { return $this->quotaProject; } /** * Get the universe domain used for this API request * * @return string */ public function getUniverseDomain() : string { return $this->universeDomain; } /** * Get the project ID. * * @param callable $httpHandler Callback which delivers psr7 request * @param string $accessToken The access token to use to sign the blob. If * provided, saves a call to the metadata server for a new access * token. **Defaults to** `null`. * @return string|null */ public function getProjectId(?callable $httpHandler = null, ?string $accessToken = null) { if (isset($this->projectId)) { return $this->projectId; } $projectNumber = $this->getProjectNumber() ?: $this->workforcePoolUserProject; if (!$projectNumber) { return null; } if (\is_null($httpHandler)) { $httpHandler = \Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpHandlerFactory::build(\Google\Site_Kit_Dependencies\Google\Auth\HttpHandler\HttpClientCache::getHttpClient()); } $url = \str_replace('UNIVERSE_DOMAIN', $this->getUniverseDomain(), \sprintf(self::CLOUD_RESOURCE_MANAGER_URL, $projectNumber)); if (\is_null($accessToken)) { $accessToken = $this->fetchAuthToken($httpHandler)['access_token']; } $request = new \Google\Site_Kit_Dependencies\GuzzleHttp\Psr7\Request('GET', $url, ['authorization' => 'Bearer ' . $accessToken]); $response = $httpHandler($request); $body = \json_decode((string) $response->getBody(), \true); return $this->projectId = $body['projectId']; } private function getProjectNumber() : ?string { $parts = \explode('/', $this->auth->getAudience()); $i = \array_search('projects', $parts); return $parts[$i + 1] ?? null; } private function isWorkforcePool() : bool { $regex = '#//iam\\.googleapis\\.com/locations/[^/]+/workforcePools/#'; return \preg_match($regex, $this->auth->getAudience()) === 1; } }