| @@ -26,8 +26,10 @@ | ||
| 26 | 26 | use Google\Auth\Credentials\ServiceAccountCredentials; |
| 27 | 27 | use Google\Auth\Credentials\UserRefreshCredentials; |
| 28 | 28 | use Google\Auth\CredentialsLoader; |
| 29 | 29 | use Google\Auth\FetchAuthTokenCache; |
| 30 | +use Google\Auth\FetchAuthTokenInterface; | |
| 31 | +use Google\Auth\GetUniverseDomainInterface; | |
| 30 | 32 | use Google\Auth\HttpHandler\HttpHandlerFactory; |
| 31 | 33 | use Google\Auth\OAuth2; |
| 32 | 34 | use Google\AuthHandler\AuthHandlerFactory; |
| 33 | 35 | use Google\Http\REST; |
| @@ -50,13 +52,13 @@ | ||
| 50 | 52 | * https://github.com/google/google-api-php-client |
| 51 | 53 | */ |
| 52 | 54 | class Client |
| 53 | 55 | { |
| 54 | - const LIBVER = "2.12.1"; | |
| 56 | + const LIBVER = "2.12.6"; | |
| 55 | 57 | const USER_AGENT_SUFFIX = "google-api-php-client/"; |
| 56 | 58 | const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke'; |
| 57 | 59 | const OAUTH2_TOKEN_URI = 'https://oauth2.googleapis.com/token'; |
| 58 | - const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth'; | |
| 60 | + const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'; | |
| 59 | 61 | const API_BASE_PATH = 'https://www.googleapis.com'; |
| 60 | 62 | |
| 61 | 63 | /** |
| 62 | 64 | * @var ?OAuth2 $auth |
| @@ -88,9 +90,9 @@ | ||
| 88 | 90 | */ |
| 89 | 91 | private $logger; |
| 90 | 92 | |
| 91 | 93 | /** |
| 92 | - * @var ?CredentialsLoader $credentials | |
| 94 | + * @var ?FetchAuthTokenInterface $credentials | |
| 93 | 95 | */ |
| 94 | 96 | private $credentials; |
| 95 | 97 | |
| 96 | 98 | /** |
| @@ -104,84 +106,116 @@ | ||
| 104 | 106 | |
| 105 | 107 | /** |
| 106 | 108 | * Construct the Google Client. |
| 107 | 109 | * |
| 108 | - * @param array $config | |
| 110 | + * @param array $config { | |
| 111 | + * An array of required and optional arguments. | |
| 112 | + * | |
| 113 | + * @type string $application_name | |
| 114 | + * The name of your application | |
| 115 | + * @type string $base_path | |
| 116 | + * The base URL for the service. This is only accounted for when calling | |
| 117 | + * {@see Client::authorize()} directly. | |
| 118 | + * @type string $client_id | |
| 119 | + * Your Google Cloud client ID found in https://developers.google.com/console | |
| 120 | + * @type string $client_secret | |
| 121 | + * Your Google Cloud client secret found in https://developers.google.com/console | |
| 122 | + * @type string|array|FetchAuthTokenInterface $credentials | |
| 123 | + * Can be a path to JSON credentials or an array representing those | |
| 124 | + * credentials (@see Google\Client::setAuthConfig), or an instance of | |
| 125 | + * {@see FetchAuthTokenInterface}. | |
| 126 | + * @type string|array $scopes | |
| 127 | + * {@see Google\Client::setScopes} | |
| 128 | + * @type string $quota_project | |
| 129 | + * Sets X-Goog-User-Project, which specifies a user project to bill | |
| 130 | + * for access charges associated with the request. | |
| 131 | + * @type string $redirect_uri | |
| 132 | + * @type string $state | |
| 133 | + * @type string $developer_key | |
| 134 | + * Simple API access key, also from the API console. Ensure you get | |
| 135 | + * a Server key, and not a Browser key. | |
| 136 | + * **NOTE:** The universe domain is assumed to be "googleapis.com" unless | |
| 137 | + * explicitly set. When setting an API ley directly via this option, there | |
| 138 | + * is no way to verify the universe domain. Be sure to set the | |
| 139 | + * "universe_domain" option if "googleapis.com" is not intended. | |
| 140 | + * @type bool $use_application_default_credentials | |
| 141 | + * For use with Google Cloud Platform | |
| 142 | + * fetch the ApplicationDefaultCredentials, if applicable | |
| 143 | + * {@see https://developers.google.com/identity/protocols/application-default-credentials} | |
| 144 | + * @type string $signing_key | |
| 145 | + * @type string $signing_algorithm | |
| 146 | + * @type string $subject | |
| 147 | + * @type string $hd | |
| 148 | + * @type string $prompt | |
| 149 | + * @type string $openid | |
| 150 | + * @type bool $include_granted_scopes | |
| 151 | + * @type string $login_hint | |
| 152 | + * @type string $request_visible_actions | |
| 153 | + * @type string $access_type | |
| 154 | + * @type string $approval_prompt | |
| 155 | + * @type array $retry | |
| 156 | + * Task Runner retry configuration | |
| 157 | + * {@see \Google\Task\Runner} | |
| 158 | + * @type array $retry_map | |
| 159 | + * @type CacheItemPoolInterface $cache | |
| 160 | + * Cache class implementing {@see CacheItemPoolInterface}. Defaults | |
| 161 | + * to {@see MemoryCacheItemPool}. | |
| 162 | + * @type array $cache_config | |
| 163 | + * Cache config for downstream auth caching. | |
| 164 | + * @type callable $token_callback | |
| 165 | + * Function to be called when an access token is fetched. Follows | |
| 166 | + * the signature `function (string $cacheKey, string $accessToken)`. | |
| 167 | + * @type \Firebase\JWT $jwt | |
| 168 | + * Service class used in {@see Client::verifyIdToken()}. Explicitly | |
| 169 | + * pass this in to avoid setting {@see \Firebase\JWT::$leeway} | |
| 170 | + * @type bool $api_format_v2 | |
| 171 | + * Setting api_format_v2 will return more detailed error messages | |
| 172 | + * from certain APIs. | |
| 173 | + * @type string $universe_domain | |
| 174 | + * Setting the universe domain will change the default rootUrl of the service. | |
| 175 | + * If not set explicitly, the universe domain will be the value provided in the | |
| 176 | + *. "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com". | |
| 177 | + * } | |
| 109 | 178 | */ |
| 110 | 179 | public function __construct(array $config = []) |
| 111 | 180 | { |
| 112 | 181 | $this->config = array_merge([ |
| 113 | 182 | 'application_name' => '', |
| 114 | - | |
| 115 | - // Don't change these unless you're working against a special development | |
| 116 | - // or testing environment. | |
| 117 | 183 | 'base_path' => self::API_BASE_PATH, |
| 118 | - | |
| 119 | - // https://developers.google.com/console | |
| 120 | 184 | 'client_id' => '', |
| 121 | 185 | 'client_secret' => '', |
| 122 | - | |
| 123 | - // Can be a path to JSON credentials or an array representing those | |
| 124 | - // credentials (@see Google\Client::setAuthConfig), or an instance of | |
| 125 | - // Google\Auth\CredentialsLoader. | |
| 126 | 186 | 'credentials' => null, |
| 127 | - // @see Google\Client::setScopes | |
| 128 | 187 | 'scopes' => null, |
| 129 | - // Sets X-Goog-User-Project, which specifies a user project to bill | |
| 130 | - // for access charges associated with the request | |
| 131 | 188 | 'quota_project' => null, |
| 132 | - | |
| 133 | 189 | 'redirect_uri' => null, |
| 134 | 190 | 'state' => null, |
| 135 | - | |
| 136 | - // Simple API access key, also from the API console. Ensure you get | |
| 137 | - // a Server key, and not a Browser key. | |
| 138 | 191 | 'developer_key' => '', |
| 139 | - | |
| 140 | - // For use with Google Cloud Platform | |
| 141 | - // fetch the ApplicationDefaultCredentials, if applicable | |
| 142 | - // @see https://developers.google.com/identity/protocols/application-default-credentials | |
| 143 | 192 | 'use_application_default_credentials' => false, |
| 144 | 193 | 'signing_key' => null, |
| 145 | 194 | 'signing_algorithm' => null, |
| 146 | 195 | 'subject' => null, |
| 147 | - | |
| 148 | - // Other OAuth2 parameters. | |
| 149 | 196 | 'hd' => '', |
| 150 | 197 | 'prompt' => '', |
| 151 | 198 | 'openid.realm' => '', |
| 152 | 199 | 'include_granted_scopes' => null, |
| 200 | + 'logger' => null, | |
| 153 | 201 | 'login_hint' => '', |
| 154 | 202 | 'request_visible_actions' => '', |
| 155 | 203 | 'access_type' => 'online', |
| 156 | 204 | 'approval_prompt' => 'auto', |
| 157 | - | |
| 158 | - // Task Runner retry configuration | |
| 159 | - // @see Google\Task\Runner | |
| 160 | 205 | 'retry' => [], |
| 161 | 206 | 'retry_map' => null, |
| 162 | - | |
| 163 | - // Cache class implementing Psr\Cache\CacheItemPoolInterface. | |
| 164 | - // Defaults to Google\Auth\Cache\MemoryCacheItemPool. | |
| 165 | 207 | 'cache' => null, |
| 166 | - // cache config for downstream auth caching | |
| 167 | 208 | 'cache_config' => [], |
| 168 | - | |
| 169 | - // function to be called when an access token is fetched | |
| 170 | - // follows the signature function ($cacheKey, $accessToken) | |
| 171 | 209 | 'token_callback' => null, |
| 172 | - | |
| 173 | - // Service class used in Google\Client::verifyIdToken. | |
| 174 | - // Explicitly pass this in to avoid setting JWT::$leeway | |
| 175 | 210 | 'jwt' => null, |
| 176 | - | |
| 177 | - // Setting api_format_v2 will return more detailed error messages | |
| 178 | - // from certain APIs. | |
| 179 | - 'api_format_v2' => false | |
| 211 | + 'api_format_v2' => false, | |
| 212 | + 'universe_domain' => getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN') | |
| 213 | + ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN, | |
| 180 | 214 | ], $config); |
| 181 | 215 | |
| 182 | 216 | if (!is_null($this->config['credentials'])) { |
| 183 | - if ($this->config['credentials'] instanceof CredentialsLoader) { | |
| 217 | + if ($this->config['credentials'] instanceof FetchAuthTokenInterface) { | |
| 184 | 218 | $this->credentials = $this->config['credentials']; |
| 185 | 219 | } else { |
| 186 | 220 | $this->setAuthConfig($this->config['credentials']); |
| 187 | 221 | } |
| @@ -209,8 +243,13 @@ | ||
| 209 | 243 | if (!is_null($this->config['cache'])) { |
| 210 | 244 | $this->setCache($this->config['cache']); |
| 211 | 245 | unset($this->config['cache']); |
| 212 | 246 | } |
| 247 | + | |
| 248 | + if (!is_null($this->config['logger'])) { | |
| 249 | + $this->setLogger($this->config['logger']); | |
| 250 | + unset($this->config['logger']); | |
| 251 | + } | |
| 213 | 252 | } |
| 214 | 253 | |
| 215 | 254 | /** |
| 216 | 255 | * Get a string containing the version of the library. |
| @@ -239,11 +278,12 @@ | ||
| 239 | 278 | * Attempt to exchange a code for an valid authentication token. |
| 240 | 279 | * Helper wrapped around the OAuth 2.0 implementation. |
| 241 | 280 | * |
| 242 | 281 | * @param string $code code from accounts.google.com |
| 282 | + * @param string $codeVerifier the code verifier used for PKCE (if applicable) | |
| 243 | 283 | * @return array access token |
| 244 | 284 | */ |
| 245 | - public function fetchAccessTokenWithAuthCode($code) | |
| 285 | + public function fetchAccessTokenWithAuthCode($code, $codeVerifier = null) | |
| 246 | 286 | { |
| 247 | 287 | if (strlen($code) == 0) { |
| 248 | 288 | throw new InvalidArgumentException("Invalid code"); |
| 249 | 289 | } |
| @@ -250,8 +290,11 @@ | ||
| 250 | 290 | |
| 251 | 291 | $auth = $this->getOAuth2Service(); |
| 252 | 292 | $auth->setCode($code); |
| 253 | 293 | $auth->setRedirectUri($this->getRedirectUri()); |
| 294 | + if ($codeVerifier) { | |
| 295 | + $auth->setCodeVerifier($codeVerifier); | |
| 296 | + } | |
| 254 | 297 | |
| 255 | 298 | $httpHandler = HttpHandlerFactory::build($this->getHttpClient()); |
| 256 | 299 | $creds = $auth->fetchAuthToken($httpHandler); |
| 257 | 300 | if ($creds && isset($creds['access_token'])) { |
| @@ -278,9 +321,9 @@ | ||
| 278 | 321 | * Fetches a fresh access token with a given assertion token. |
| 279 | 322 | * @param ClientInterface $authHttp optional. |
| 280 | 323 | * @return array access token |
| 281 | 324 | */ |
| 282 | - public function fetchAccessTokenWithAssertion(ClientInterface $authHttp = null) | |
| 325 | + public function fetchAccessTokenWithAssertion(?ClientInterface $authHttp = null) | |
| 283 | 326 | { |
| 284 | 327 | if (!$this->isUsingApplicationDefaultCredentials()) { |
| 285 | 328 | throw new DomainException( |
| 286 | 329 | 'set the JSON service account credentials using' |
| @@ -356,11 +399,12 @@ | ||
| 356 | 399 | * Create a URL to obtain user authorization. |
| 357 | 400 | * The authorization endpoint allows the user to first |
| 358 | 401 | * authenticate, and then grant/deny the access request. |
| 359 | 402 | * @param string|array $scope The scope is expressed as an array or list of space-delimited strings. |
| 403 | + * @param array $queryParams Querystring params to add to the authorization URL. | |
| 360 | 404 | * @return string |
| 361 | 405 | */ |
| 362 | - public function createAuthUrl($scope = null) | |
| 406 | + public function createAuthUrl($scope = null, array $queryParams = []) | |
| 363 | 407 | { |
| 364 | 408 | if (empty($scope)) { |
| 365 | 409 | $scope = $this->prepareScopes(); |
| 366 | 410 | } |
| @@ -385,12 +429,13 @@ | ||
| 385 | 429 | 'include_granted_scopes' => $includeGrantedScopes, |
| 386 | 430 | 'login_hint' => $this->config['login_hint'], |
| 387 | 431 | 'openid.realm' => $this->config['openid.realm'], |
| 388 | 432 | 'prompt' => $this->config['prompt'], |
| 433 | + 'redirect_uri' => $this->config['redirect_uri'], | |
| 389 | 434 | 'response_type' => 'code', |
| 390 | 435 | 'scope' => $scope, |
| 391 | 436 | 'state' => $this->config['state'], |
| 392 | - ]); | |
| 437 | + ]) + $queryParams; | |
| 393 | 438 | |
| 394 | 439 | // If the list of scopes contains plus.login, add request_visible_actions |
| 395 | 440 | // to auth URL. |
| 396 | 441 | $rva = $this->config['request_visible_actions']; |
| @@ -409,20 +454,22 @@ | ||
| 409 | 454 | * |
| 410 | 455 | * @param ClientInterface $http the http client object. |
| 411 | 456 | * @return ClientInterface the http client object |
| 412 | 457 | */ |
| 413 | - public function authorize(ClientInterface $http = null) | |
| 458 | + public function authorize(?ClientInterface $http = null) | |
| 414 | 459 | { |
| 415 | 460 | $http = $http ?: $this->getHttpClient(); |
| 416 | 461 | $authHandler = $this->getAuthHandler(); |
| 417 | 462 | |
| 418 | 463 | // These conditionals represent the decision tree for authentication |
| 419 | - // 1. Check if a Google\Auth\CredentialsLoader instance has been supplied via the "credentials" option | |
| 464 | + // 1. Check if an instance of Google\Auth\FetchAuthTokenInterface has | |
| 465 | + // been supplied via the "credentials" option | |
| 420 | 466 | // 2. Check for Application Default Credentials |
| 421 | 467 | // 3a. Check for an Access Token |
| 422 | 468 | // 3b. If access token exists but is expired, try to refresh it |
| 423 | 469 | // 4. Check for API Key |
| 424 | 470 | if ($this->credentials) { |
| 471 | + $this->checkUniverseDomain($this->credentials); | |
| 425 | 472 | return $authHandler->attachCredentials( |
| 426 | 473 | $http, |
| 427 | 474 | $this->credentials, |
| 428 | 475 | $this->config['token_callback'] |
| @@ -430,8 +477,9 @@ | ||
| 430 | 477 | } |
| 431 | 478 | |
| 432 | 479 | if ($this->isUsingApplicationDefaultCredentials()) { |
| 433 | 480 | $credentials = $this->createApplicationDefaultCredentials(); |
| 481 | + $this->checkUniverseDomain($credentials); | |
| 434 | 482 | return $authHandler->attachCredentialsCache( |
| 435 | 483 | $http, |
| 436 | 484 | $credentials, |
| 437 | 485 | $this->config['token_callback'] |
| @@ -445,8 +493,9 @@ | ||
| 445 | 493 | $credentials = $this->createUserRefreshCredentials( |
| 446 | 494 | $scopes, |
| 447 | 495 | $token['refresh_token'] |
| 448 | 496 | ); |
| 497 | + $this->checkUniverseDomain($credentials); | |
| 449 | 498 | return $authHandler->attachCredentials( |
| 450 | 499 | $http, |
| 451 | 500 | $credentials, |
| 452 | 501 | $this->config['token_callback'] |
| @@ -497,8 +546,13 @@ | ||
| 497 | 546 | * token by calling `$client->getCache()->clear()`. (Use caution in this case, |
| 498 | 547 | * as calling `clear()` will remove all cache items, including any items not |
| 499 | 548 | * related to Google API PHP Client.) |
| 500 | 549 | * |
| 550 | + * **NOTE:** The universe domain is assumed to be "googleapis.com" unless | |
| 551 | + * explicitly set. When setting an access token directly via this method, there | |
| 552 | + * is no way to verify the universe domain. Be sure to set the "universe_domain" | |
| 553 | + * option if "googleapis.com" is not intended. | |
| 554 | + * | |
| 501 | 555 | * @param string|array $token |
| 502 | 556 | * @throws InvalidArgumentException |
| 503 | 557 | */ |
| 504 | 558 | public function setAccessToken($token) |
| @@ -875,9 +929,9 @@ | ||
| 875 | 929 | * Helper method to execute deferred HTTP requests. |
| 876 | 930 | * |
| 877 | 931 | * @template T |
| 878 | 932 | * @param RequestInterface $request |
| 879 | - * @param class-string<T> $expectedClass | |
| 933 | + * @param class-string<T>|false|null $expectedClass | |
| 880 | 934 | * @throws \Google\Exception |
| 881 | 935 | * @return mixed|T|ResponseInterface |
| 882 | 936 | */ |
| 883 | 937 | public function execute(RequestInterface $request, $expectedClass = null) |
| @@ -990,9 +1044,10 @@ | ||
| 990 | 1044 | } |
| 991 | 1045 | |
| 992 | 1046 | $key = isset($config['installed']) ? 'installed' : 'web'; |
| 993 | 1047 | if (isset($config['type']) && $config['type'] == 'service_account') { |
| 994 | - // application default credentials | |
| 1048 | + // @TODO(v3): Remove this, as it isn't accurate. ADC applies only to determining | |
| 1049 | + // credentials based on the user's environment. | |
| 995 | 1050 | $this->useApplicationDefaultCredentials(); |
| 996 | 1051 | |
| 997 | 1052 | // set the information from the config |
| 998 | 1053 | $this->setClientId($config['client_id']); |
| @@ -1183,9 +1238,8 @@ | ||
| 1183 | 1238 | $guzzleVersion = null; |
| 1184 | 1239 | if (defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) { |
| 1185 | 1240 | $guzzleVersion = ClientInterface::MAJOR_VERSION; |
| 1186 | 1241 | } elseif (defined('\GuzzleHttp\ClientInterface::VERSION')) { |
| 1187 | - // @phpstan-ignore-next-line | |
| 1188 | 1242 | $guzzleVersion = (int)substr(ClientInterface::VERSION, 0, 1); |
| 1189 | 1243 | } |
| 1190 | 1244 | |
| 1191 | 1245 | if (5 === $guzzleVersion) { |
| @@ -1290,6 +1344,25 @@ | ||
| 1290 | 1344 | 'refresh_token' => $refreshToken, |
| 1291 | 1345 | ]); |
| 1292 | 1346 | |
| 1293 | 1347 | return new UserRefreshCredentials($scope, $creds); |
| 1348 | + } | |
| 1349 | + | |
| 1350 | + private function checkUniverseDomain($credentials) | |
| 1351 | + { | |
| 1352 | + $credentialsUniverse = $credentials instanceof GetUniverseDomainInterface | |
| 1353 | + ? $credentials->getUniverseDomain() | |
| 1354 | + : GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; | |
| 1355 | + if ($credentialsUniverse !== $this->getUniverseDomain()) { | |
| 1356 | + throw new DomainException(sprintf( | |
| 1357 | + 'The configured universe domain (%s) does not match the credential universe domain (%s)', | |
| 1358 | + $this->getUniverseDomain(), | |
| 1359 | + $credentialsUniverse | |
| 1360 | + )); | |
| 1361 | + } | |
| 1362 | + } | |
| 1363 | + | |
| 1364 | + public function getUniverseDomain() | |
| 1365 | + { | |
| 1366 | + return $this->config['universe_domain']; | |
| 1294 | 1367 | } |
| 1295 | 1368 | } |