PluginProbe
Authorizer / 3.13.0
Authorizer v3.13.0
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
authorizer / vendor / google / apiclient / src / Client.php

Client.php in Authorizer 3.13.0, at vendor/google/apiclient/src/Client.php

1,297 lines 40.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * Copyright 2010 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 namespace Google;
19
20 use BadMethodCallException;
21 use DomainException;
22 use Google\AccessToken\Revoke;
23 use Google\AccessToken\Verify;
24 use Google\Auth\ApplicationDefaultCredentials;
25 use Google\Auth\Cache\MemoryCacheItemPool;
26 use Google\Auth\Credentials\ServiceAccountCredentials;
27 use Google\Auth\Credentials\UserRefreshCredentials;
28 use Google\Auth\CredentialsLoader;
29 use Google\Auth\FetchAuthTokenCache;
30 use Google\Auth\HttpHandler\HttpHandlerFactory;
31 use Google\Auth\OAuth2;
32 use Google\AuthHandler\AuthHandlerFactory;
33 use Google\Http\REST;
34 use GuzzleHttp\Client as GuzzleClient;
35 use GuzzleHttp\ClientInterface;
36 use GuzzleHttp\Ring\Client\StreamHandler;
37 use InvalidArgumentException;
38 use LogicException;
39 use Monolog\Handler\StreamHandler as MonologStreamHandler;
40 use Monolog\Handler\SyslogHandler as MonologSyslogHandler;
41 use Monolog\Logger;
42 use Psr\Cache\CacheItemPoolInterface;
43 use Psr\Http\Message\RequestInterface;
44 use Psr\Http\Message\ResponseInterface;
45 use Psr\Log\LoggerInterface;
46 use UnexpectedValueException;
47
48 /**
49 * The Google API Client
50 * https://github.com/google/google-api-php-client
51 */
52 class Client
53 {
54 const LIBVER = "2.12.6";
55 const USER_AGENT_SUFFIX = "google-api-php-client/";
56 const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
57 const OAUTH2_TOKEN_URI = 'https://oauth2.googleapis.com/token';
58 const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
59 const API_BASE_PATH = 'https://www.googleapis.com';
60
61 /**
62 * @var ?OAuth2 $auth
63 */
64 private $auth;
65
66 /**
67 * @var ClientInterface $http
68 */
69 private $http;
70
71 /**
72 * @var ?CacheItemPoolInterface $cache
73 */
74 private $cache;
75
76 /**
77 * @var array access token
78 */
79 private $token;
80
81 /**
82 * @var array $config
83 */
84 private $config;
85
86 /**
87 * @var ?LoggerInterface $logger
88 */
89 private $logger;
90
91 /**
92 * @var ?CredentialsLoader $credentials
93 */
94 private $credentials;
95
96 /**
97 * @var boolean $deferExecution
98 */
99 private $deferExecution = false;
100
101 /** @var array $scopes */
102 // Scopes requested by the client
103 protected $requestedScopes = [];
104
105 /**
106 * Construct the Google Client.
107 *
108 * @param array $config
109 */
110 public function __construct(array $config = [])
111 {
112 $this->config = array_merge([
113 'application_name' => '',
114
115 // Don't change these unless you're working against a special development
116 // or testing environment.
117 'base_path' => self::API_BASE_PATH,
118
119 // https://developers.google.com/console
120 'client_id' => '',
121 '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 'credentials' => null,
127 // @see Google\Client::setScopes
128 'scopes' => null,
129 // Sets X-Goog-User-Project, which specifies a user project to bill
130 // for access charges associated with the request
131 'quota_project' => null,
132
133 'redirect_uri' => null,
134 '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 '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 'use_application_default_credentials' => false,
144 'signing_key' => null,
145 'signing_algorithm' => null,
146 'subject' => null,
147
148 // Other OAuth2 parameters.
149 'hd' => '',
150 'prompt' => '',
151 'openid.realm' => '',
152 'include_granted_scopes' => null,
153 'login_hint' => '',
154 'request_visible_actions' => '',
155 'access_type' => 'online',
156 'approval_prompt' => 'auto',
157
158 // Task Runner retry configuration
159 // @see Google\Task\Runner
160 'retry' => [],
161 'retry_map' => null,
162
163 // Cache class implementing Psr\Cache\CacheItemPoolInterface.
164 // Defaults to Google\Auth\Cache\MemoryCacheItemPool.
165 'cache' => null,
166 // cache config for downstream auth caching
167 'cache_config' => [],
168
169 // function to be called when an access token is fetched
170 // follows the signature function ($cacheKey, $accessToken)
171 'token_callback' => null,
172
173 // Service class used in Google\Client::verifyIdToken.
174 // Explicitly pass this in to avoid setting JWT::$leeway
175 'jwt' => null,
176
177 // Setting api_format_v2 will return more detailed error messages
178 // from certain APIs.
179 'api_format_v2' => false
180 ], $config);
181
182 if (!is_null($this->config['credentials'])) {
183 if ($this->config['credentials'] instanceof CredentialsLoader) {
184 $this->credentials = $this->config['credentials'];
185 } else {
186 $this->setAuthConfig($this->config['credentials']);
187 }
188 unset($this->config['credentials']);
189 }
190
191 if (!is_null($this->config['scopes'])) {
192 $this->setScopes($this->config['scopes']);
193 unset($this->config['scopes']);
194 }
195
196 // Set a default token callback to update the in-memory access token
197 if (is_null($this->config['token_callback'])) {
198 $this->config['token_callback'] = function ($cacheKey, $newAccessToken) {
199 $this->setAccessToken(
200 [
201 'access_token' => $newAccessToken,
202 'expires_in' => 3600, // Google default
203 'created' => time(),
204 ]
205 );
206 };
207 }
208
209 if (!is_null($this->config['cache'])) {
210 $this->setCache($this->config['cache']);
211 unset($this->config['cache']);
212 }
213 }
214
215 /**
216 * Get a string containing the version of the library.
217 *
218 * @return string
219 */
220 public function getLibraryVersion()
221 {
222 return self::LIBVER;
223 }
224
225 /**
226 * For backwards compatibility
227 * alias for fetchAccessTokenWithAuthCode
228 *
229 * @param string $code string code from accounts.google.com
230 * @return array access token
231 * @deprecated
232 */
233 public function authenticate($code)
234 {
235 return $this->fetchAccessTokenWithAuthCode($code);
236 }
237
238 /**
239 * Attempt to exchange a code for an valid authentication token.
240 * Helper wrapped around the OAuth 2.0 implementation.
241 *
242 * @param string $code code from accounts.google.com
243 * @return array access token
244 */
245 public function fetchAccessTokenWithAuthCode($code)
246 {
247 if (strlen($code) == 0) {
248 throw new InvalidArgumentException("Invalid code");
249 }
250
251 $auth = $this->getOAuth2Service();
252 $auth->setCode($code);
253 $auth->setRedirectUri($this->getRedirectUri());
254
255 $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
256 $creds = $auth->fetchAuthToken($httpHandler);
257 if ($creds && isset($creds['access_token'])) {
258 $creds['created'] = time();
259 $this->setAccessToken($creds);
260 }
261
262 return $creds;
263 }
264
265 /**
266 * For backwards compatibility
267 * alias for fetchAccessTokenWithAssertion
268 *
269 * @return array access token
270 * @deprecated
271 */
272 public function refreshTokenWithAssertion()
273 {
274 return $this->fetchAccessTokenWithAssertion();
275 }
276
277 /**
278 * Fetches a fresh access token with a given assertion token.
279 * @param ClientInterface $authHttp optional.
280 * @return array access token
281 */
282 public function fetchAccessTokenWithAssertion(ClientInterface $authHttp = null)
283 {
284 if (!$this->isUsingApplicationDefaultCredentials()) {
285 throw new DomainException(
286 'set the JSON service account credentials using'
287 . ' Google\Client::setAuthConfig or set the path to your JSON file'
288 . ' with the "GOOGLE_APPLICATION_CREDENTIALS" environment variable'
289 . ' and call Google\Client::useApplicationDefaultCredentials to'
290 . ' refresh a token with assertion.'
291 );
292 }
293
294 $this->getLogger()->log(
295 'info',
296 'OAuth2 access token refresh with Signed JWT assertion grants.'
297 );
298
299 $credentials = $this->createApplicationDefaultCredentials();
300
301 $httpHandler = HttpHandlerFactory::build($authHttp);
302 $creds = $credentials->fetchAuthToken($httpHandler);
303 if ($creds && isset($creds['access_token'])) {
304 $creds['created'] = time();
305 $this->setAccessToken($creds);
306 }
307
308 return $creds;
309 }
310
311 /**
312 * For backwards compatibility
313 * alias for fetchAccessTokenWithRefreshToken
314 *
315 * @param string $refreshToken
316 * @return array access token
317 */
318 public function refreshToken($refreshToken)
319 {
320 return $this->fetchAccessTokenWithRefreshToken($refreshToken);
321 }
322
323 /**
324 * Fetches a fresh OAuth 2.0 access token with the given refresh token.
325 * @param string $refreshToken
326 * @return array access token
327 */
328 public function fetchAccessTokenWithRefreshToken($refreshToken = null)
329 {
330 if (null === $refreshToken) {
331 if (!isset($this->token['refresh_token'])) {
332 throw new LogicException(
333 'refresh token must be passed in or set as part of setAccessToken'
334 );
335 }
336 $refreshToken = $this->token['refresh_token'];
337 }
338 $this->getLogger()->info('OAuth2 access token refresh');
339 $auth = $this->getOAuth2Service();
340 $auth->setRefreshToken($refreshToken);
341
342 $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
343 $creds = $auth->fetchAuthToken($httpHandler);
344 if ($creds && isset($creds['access_token'])) {
345 $creds['created'] = time();
346 if (!isset($creds['refresh_token'])) {
347 $creds['refresh_token'] = $refreshToken;
348 }
349 $this->setAccessToken($creds);
350 }
351
352 return $creds;
353 }
354
355 /**
356 * Create a URL to obtain user authorization.
357 * The authorization endpoint allows the user to first
358 * authenticate, and then grant/deny the access request.
359 * @param string|array $scope The scope is expressed as an array or list of space-delimited strings.
360 * @param array $queryParams Querystring params to add to the authorization URL.
361 * @return string
362 */
363 public function createAuthUrl($scope = null, array $queryParams = [])
364 {
365 if (empty($scope)) {
366 $scope = $this->prepareScopes();
367 }
368 if (is_array($scope)) {
369 $scope = implode(' ', $scope);
370 }
371
372 // only accept one of prompt or approval_prompt
373 $approvalPrompt = $this->config['prompt']
374 ? null
375 : $this->config['approval_prompt'];
376
377 // include_granted_scopes should be string "true", string "false", or null
378 $includeGrantedScopes = $this->config['include_granted_scopes'] === null
379 ? null
380 : var_export($this->config['include_granted_scopes'], true);
381
382 $params = array_filter([
383 'access_type' => $this->config['access_type'],
384 'approval_prompt' => $approvalPrompt,
385 'hd' => $this->config['hd'],
386 'include_granted_scopes' => $includeGrantedScopes,
387 'login_hint' => $this->config['login_hint'],
388 'openid.realm' => $this->config['openid.realm'],
389 'prompt' => $this->config['prompt'],
390 'redirect_uri' => $this->config['redirect_uri'],
391 'response_type' => 'code',
392 'scope' => $scope,
393 'state' => $this->config['state'],
394 ]) + $queryParams;
395
396 // If the list of scopes contains plus.login, add request_visible_actions
397 // to auth URL.
398 $rva = $this->config['request_visible_actions'];
399 if (strlen($rva) > 0 && false !== strpos($scope, 'plus.login')) {
400 $params['request_visible_actions'] = $rva;
401 }
402
403 $auth = $this->getOAuth2Service();
404
405 return (string) $auth->buildFullAuthorizationUri($params);
406 }
407
408 /**
409 * Adds auth listeners to the HTTP client based on the credentials
410 * set in the Google API Client object
411 *
412 * @param ClientInterface $http the http client object.
413 * @return ClientInterface the http client object
414 */
415 public function authorize(ClientInterface $http = null)
416 {
417 $http = $http ?: $this->getHttpClient();
418 $authHandler = $this->getAuthHandler();
419
420 // These conditionals represent the decision tree for authentication
421 // 1. Check if a Google\Auth\CredentialsLoader instance has been supplied via the "credentials" option
422 // 2. Check for Application Default Credentials
423 // 3a. Check for an Access Token
424 // 3b. If access token exists but is expired, try to refresh it
425 // 4. Check for API Key
426 if ($this->credentials) {
427 return $authHandler->attachCredentials(
428 $http,
429 $this->credentials,
430 $this->config['token_callback']
431 );
432 }
433
434 if ($this->isUsingApplicationDefaultCredentials()) {
435 $credentials = $this->createApplicationDefaultCredentials();
436 return $authHandler->attachCredentialsCache(
437 $http,
438 $credentials,
439 $this->config['token_callback']
440 );
441 }
442
443 if ($token = $this->getAccessToken()) {
444 $scopes = $this->prepareScopes();
445 // add refresh subscriber to request a new token
446 if (isset($token['refresh_token']) && $this->isAccessTokenExpired()) {
447 $credentials = $this->createUserRefreshCredentials(
448 $scopes,
449 $token['refresh_token']
450 );
451 return $authHandler->attachCredentials(
452 $http,
453 $credentials,
454 $this->config['token_callback']
455 );
456 }
457
458 return $authHandler->attachToken($http, $token, (array) $scopes);
459 }
460
461 if ($key = $this->config['developer_key']) {
462 return $authHandler->attachKey($http, $key);
463 }
464
465 return $http;
466 }
467
468 /**
469 * Set the configuration to use application default credentials for
470 * authentication
471 *
472 * @see https://developers.google.com/identity/protocols/application-default-credentials
473 * @param boolean $useAppCreds
474 */
475 public function useApplicationDefaultCredentials($useAppCreds = true)
476 {
477 $this->config['use_application_default_credentials'] = $useAppCreds;
478 }
479
480 /**
481 * To prevent useApplicationDefaultCredentials from inappropriately being
482 * called in a conditional
483 *
484 * @see https://developers.google.com/identity/protocols/application-default-credentials
485 */
486 public function isUsingApplicationDefaultCredentials()
487 {
488 return $this->config['use_application_default_credentials'];
489 }
490
491 /**
492 * Set the access token used for requests.
493 *
494 * Note that at the time requests are sent, tokens are cached. A token will be
495 * cached for each combination of service and authentication scopes. If a
496 * cache pool is not provided, creating a new instance of the client will
497 * allow modification of access tokens. If a persistent cache pool is
498 * provided, in order to change the access token, you must clear the cached
499 * token by calling `$client->getCache()->clear()`. (Use caution in this case,
500 * as calling `clear()` will remove all cache items, including any items not
501 * related to Google API PHP Client.)
502 *
503 * @param string|array $token
504 * @throws InvalidArgumentException
505 */
506 public function setAccessToken($token)
507 {
508 if (is_string($token)) {
509 if ($json = json_decode($token, true)) {
510 $token = $json;
511 } else {
512 // assume $token is just the token string
513 $token = [
514 'access_token' => $token,
515 ];
516 }
517 }
518 if ($token == null) {
519 throw new InvalidArgumentException('invalid json token');
520 }
521 if (!isset($token['access_token'])) {
522 throw new InvalidArgumentException("Invalid token format");
523 }
524 $this->token = $token;
525 }
526
527 public function getAccessToken()
528 {
529 return $this->token;
530 }
531
532 /**
533 * @return string|null
534 */
535 public function getRefreshToken()
536 {
537 if (isset($this->token['refresh_token'])) {
538 return $this->token['refresh_token'];
539 }
540
541 return null;
542 }
543
544 /**
545 * Returns if the access_token is expired.
546 * @return bool Returns True if the access_token is expired.
547 */
548 public function isAccessTokenExpired()
549 {
550 if (!$this->token) {
551 return true;
552 }
553
554 $created = 0;
555 if (isset($this->token['created'])) {
556 $created = $this->token['created'];
557 } elseif (isset($this->token['id_token'])) {
558 // check the ID token for "iat"
559 // signature verification is not required here, as we are just
560 // using this for convenience to save a round trip request
561 // to the Google API server
562 $idToken = $this->token['id_token'];
563 if (substr_count($idToken, '.') == 2) {
564 $parts = explode('.', $idToken);
565 $payload = json_decode(base64_decode($parts[1]), true);
566 if ($payload && isset($payload['iat'])) {
567 $created = $payload['iat'];
568 }
569 }
570 }
571 if (!isset($this->token['expires_in'])) {
572 // if the token does not have an "expires_in", then it's considered expired
573 return true;
574 }
575
576 // If the token is set to expire in the next 30 seconds.
577 return ($created + ($this->token['expires_in'] - 30)) < time();
578 }
579
580 /**
581 * @deprecated See UPGRADING.md for more information
582 */
583 public function getAuth()
584 {
585 throw new BadMethodCallException(
586 'This function no longer exists. See UPGRADING.md for more information'
587 );
588 }
589
590 /**
591 * @deprecated See UPGRADING.md for more information
592 */
593 public function setAuth($auth)
594 {
595 throw new BadMethodCallException(
596 'This function no longer exists. See UPGRADING.md for more information'
597 );
598 }
599
600 /**
601 * Set the OAuth 2.0 Client ID.
602 * @param string $clientId
603 */
604 public function setClientId($clientId)
605 {
606 $this->config['client_id'] = $clientId;
607 }
608
609 public function getClientId()
610 {
611 return $this->config['client_id'];
612 }
613
614 /**
615 * Set the OAuth 2.0 Client Secret.
616 * @param string $clientSecret
617 */
618 public function setClientSecret($clientSecret)
619 {
620 $this->config['client_secret'] = $clientSecret;
621 }
622
623 public function getClientSecret()
624 {
625 return $this->config['client_secret'];
626 }
627
628 /**
629 * Set the OAuth 2.0 Redirect URI.
630 * @param string $redirectUri
631 */
632 public function setRedirectUri($redirectUri)
633 {
634 $this->config['redirect_uri'] = $redirectUri;
635 }
636
637 public function getRedirectUri()
638 {
639 return $this->config['redirect_uri'];
640 }
641
642 /**
643 * Set OAuth 2.0 "state" parameter to achieve per-request customization.
644 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
645 * @param string $state
646 */
647 public function setState($state)
648 {
649 $this->config['state'] = $state;
650 }
651
652 /**
653 * @param string $accessType Possible values for access_type include:
654 * {@code "offline"} to request offline access from the user.
655 * {@code "online"} to request online access from the user.
656 */
657 public function setAccessType($accessType)
658 {
659 $this->config['access_type'] = $accessType;
660 }
661
662 /**
663 * @param string $approvalPrompt Possible values for approval_prompt include:
664 * {@code "force"} to force the approval UI to appear.
665 * {@code "auto"} to request auto-approval when possible. (This is the default value)
666 */
667 public function setApprovalPrompt($approvalPrompt)
668 {
669 $this->config['approval_prompt'] = $approvalPrompt;
670 }
671
672 /**
673 * Set the login hint, email address or sub id.
674 * @param string $loginHint
675 */
676 public function setLoginHint($loginHint)
677 {
678 $this->config['login_hint'] = $loginHint;
679 }
680
681 /**
682 * Set the application name, this is included in the User-Agent HTTP header.
683 * @param string $applicationName
684 */
685 public function setApplicationName($applicationName)
686 {
687 $this->config['application_name'] = $applicationName;
688 }
689
690 /**
691 * If 'plus.login' is included in the list of requested scopes, you can use
692 * this method to define types of app activities that your app will write.
693 * You can find a list of available types here:
694 * @link https://developers.google.com/+/api/moment-types
695 *
696 * @param array $requestVisibleActions Array of app activity types
697 */
698 public function setRequestVisibleActions($requestVisibleActions)
699 {
700 if (is_array($requestVisibleActions)) {
701 $requestVisibleActions = implode(" ", $requestVisibleActions);
702 }
703 $this->config['request_visible_actions'] = $requestVisibleActions;
704 }
705
706 /**
707 * Set the developer key to use, these are obtained through the API Console.
708 * @see http://code.google.com/apis/console-help/#generatingdevkeys
709 * @param string $developerKey
710 */
711 public function setDeveloperKey($developerKey)
712 {
713 $this->config['developer_key'] = $developerKey;
714 }
715
716 /**
717 * Set the hd (hosted domain) parameter streamlines the login process for
718 * Google Apps hosted accounts. By including the domain of the user, you
719 * restrict sign-in to accounts at that domain.
720 * @param string $hd the domain to use.
721 */
722 public function setHostedDomain($hd)
723 {
724 $this->config['hd'] = $hd;
725 }
726
727 /**
728 * Set the prompt hint. Valid values are none, consent and select_account.
729 * If no value is specified and the user has not previously authorized
730 * access, then the user is shown a consent screen.
731 * @param string $prompt
732 * {@code "none"} Do not display any authentication or consent screens. Must not be specified with other values.
733 * {@code "consent"} Prompt the user for consent.
734 * {@code "select_account"} Prompt the user to select an account.
735 */
736 public function setPrompt($prompt)
737 {
738 $this->config['prompt'] = $prompt;
739 }
740
741 /**
742 * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
743 * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
744 * an authentication request is valid.
745 * @param string $realm the URL-space to use.
746 */
747 public function setOpenidRealm($realm)
748 {
749 $this->config['openid.realm'] = $realm;
750 }
751
752 /**
753 * If this is provided with the value true, and the authorization request is
754 * granted, the authorization will include any previous authorizations
755 * granted to this user/application combination for other scopes.
756 * @param bool $include the URL-space to use.
757 */
758 public function setIncludeGrantedScopes($include)
759 {
760 $this->config['include_granted_scopes'] = $include;
761 }
762
763 /**
764 * sets function to be called when an access token is fetched
765 * @param callable $tokenCallback - function ($cacheKey, $accessToken)
766 */
767 public function setTokenCallback(callable $tokenCallback)
768 {
769 $this->config['token_callback'] = $tokenCallback;
770 }
771
772 /**
773 * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
774 * token, if a token isn't provided.
775 *
776 * @param string|array|null $token The token (access token or a refresh token) that should be revoked.
777 * @return boolean Returns True if the revocation was successful, otherwise False.
778 */
779 public function revokeToken($token = null)
780 {
781 $tokenRevoker = new Revoke($this->getHttpClient());
782
783 return $tokenRevoker->revokeToken($token ?: $this->getAccessToken());
784 }
785
786 /**
787 * Verify an id_token. This method will verify the current id_token, if one
788 * isn't provided.
789 *
790 * @throws LogicException If no token was provided and no token was set using `setAccessToken`.
791 * @throws UnexpectedValueException If the token is not a valid JWT.
792 * @param string|null $idToken The token (id_token) that should be verified.
793 * @return array|false Returns the token payload as an array if the verification was
794 * successful, false otherwise.
795 */
796 public function verifyIdToken($idToken = null)
797 {
798 $tokenVerifier = new Verify(
799 $this->getHttpClient(),
800 $this->getCache(),
801 $this->config['jwt']
802 );
803
804 if (null === $idToken) {
805 $token = $this->getAccessToken();
806 if (!isset($token['id_token'])) {
807 throw new LogicException(
808 'id_token must be passed in or set as part of setAccessToken'
809 );
810 }
811 $idToken = $token['id_token'];
812 }
813
814 return $tokenVerifier->verifyIdToken(
815 $idToken,
816 $this->getClientId()
817 );
818 }
819
820 /**
821 * Set the scopes to be requested. Must be called before createAuthUrl().
822 * Will remove any previously configured scopes.
823 * @param string|array $scope_or_scopes, ie:
824 * array(
825 * 'https://www.googleapis.com/auth/plus.login',
826 * 'https://www.googleapis.com/auth/moderator'
827 * );
828 */
829 public function setScopes($scope_or_scopes)
830 {
831 $this->requestedScopes = [];
832 $this->addScope($scope_or_scopes);
833 }
834
835 /**
836 * This functions adds a scope to be requested as part of the OAuth2.0 flow.
837 * Will append any scopes not previously requested to the scope parameter.
838 * A single string will be treated as a scope to request. An array of strings
839 * will each be appended.
840 * @param string|string[] $scope_or_scopes e.g. "profile"
841 */
842 public function addScope($scope_or_scopes)
843 {
844 if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
845 $this->requestedScopes[] = $scope_or_scopes;
846 } elseif (is_array($scope_or_scopes)) {
847 foreach ($scope_or_scopes as $scope) {
848 $this->addScope($scope);
849 }
850 }
851 }
852
853 /**
854 * Returns the list of scopes requested by the client
855 * @return array the list of scopes
856 *
857 */
858 public function getScopes()
859 {
860 return $this->requestedScopes;
861 }
862
863 /**
864 * @return string|null
865 * @visible For Testing
866 */
867 public function prepareScopes()
868 {
869 if (empty($this->requestedScopes)) {
870 return null;
871 }
872
873 return implode(' ', $this->requestedScopes);
874 }
875
876 /**
877 * Helper method to execute deferred HTTP requests.
878 *
879 * @template T
880 * @param RequestInterface $request
881 * @param class-string<T>|false|null $expectedClass
882 * @throws \Google\Exception
883 * @return mixed|T|ResponseInterface
884 */
885 public function execute(RequestInterface $request, $expectedClass = null)
886 {
887 $request = $request
888 ->withHeader(
889 'User-Agent',
890 sprintf(
891 '%s %s%s',
892 $this->config['application_name'],
893 self::USER_AGENT_SUFFIX,
894 $this->getLibraryVersion()
895 )
896 )
897 ->withHeader(
898 'x-goog-api-client',
899 sprintf(
900 'gl-php/%s gdcl/%s',
901 phpversion(),
902 $this->getLibraryVersion()
903 )
904 );
905
906 if ($this->config['api_format_v2']) {
907 $request = $request->withHeader(
908 'X-GOOG-API-FORMAT-VERSION',
909 '2'
910 );
911 }
912
913 // call the authorize method
914 // this is where most of the grunt work is done
915 $http = $this->authorize();
916
917 return REST::execute(
918 $http,
919 $request,
920 $expectedClass,
921 $this->config['retry'],
922 $this->config['retry_map']
923 );
924 }
925
926 /**
927 * Declare whether batch calls should be used. This may increase throughput
928 * by making multiple requests in one connection.
929 *
930 * @param boolean $useBatch True if the batch support should
931 * be enabled. Defaults to False.
932 */
933 public function setUseBatch($useBatch)
934 {
935 // This is actually an alias for setDefer.
936 $this->setDefer($useBatch);
937 }
938
939 /**
940 * Are we running in Google AppEngine?
941 * return bool
942 */
943 public function isAppEngine()
944 {
945 return (isset($_SERVER['SERVER_SOFTWARE']) &&
946 strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
947 }
948
949 public function setConfig($name, $value)
950 {
951 $this->config[$name] = $value;
952 }
953
954 public function getConfig($name, $default = null)
955 {
956 return isset($this->config[$name]) ? $this->config[$name] : $default;
957 }
958
959 /**
960 * For backwards compatibility
961 * alias for setAuthConfig
962 *
963 * @param string $file the configuration file
964 * @throws \Google\Exception
965 * @deprecated
966 */
967 public function setAuthConfigFile($file)
968 {
969 $this->setAuthConfig($file);
970 }
971
972 /**
973 * Set the auth config from new or deprecated JSON config.
974 * This structure should match the file downloaded from
975 * the "Download JSON" button on in the Google Developer
976 * Console.
977 * @param string|array $config the configuration json
978 * @throws \Google\Exception
979 */
980 public function setAuthConfig($config)
981 {
982 if (is_string($config)) {
983 if (!file_exists($config)) {
984 throw new InvalidArgumentException(sprintf('file "%s" does not exist', $config));
985 }
986
987 $json = file_get_contents($config);
988
989 if (!$config = json_decode($json, true)) {
990 throw new LogicException('invalid json for auth config');
991 }
992 }
993
994 $key = isset($config['installed']) ? 'installed' : 'web';
995 if (isset($config['type']) && $config['type'] == 'service_account') {
996 // application default credentials
997 $this->useApplicationDefaultCredentials();
998
999 // set the information from the config
1000 $this->setClientId($config['client_id']);
1001 $this->config['client_email'] = $config['client_email'];
1002 $this->config['signing_key'] = $config['private_key'];
1003 $this->config['signing_algorithm'] = 'HS256';
1004 } elseif (isset($config[$key])) {
1005 // old-style
1006 $this->setClientId($config[$key]['client_id']);
1007 $this->setClientSecret($config[$key]['client_secret']);
1008 if (isset($config[$key]['redirect_uris'])) {
1009 $this->setRedirectUri($config[$key]['redirect_uris'][0]);
1010 }
1011 } else {
1012 // new-style
1013 $this->setClientId($config['client_id']);
1014 $this->setClientSecret($config['client_secret']);
1015 if (isset($config['redirect_uris'])) {
1016 $this->setRedirectUri($config['redirect_uris'][0]);
1017 }
1018 }
1019 }
1020
1021 /**
1022 * Use when the service account has been delegated domain wide access.
1023 *
1024 * @param string $subject an email address account to impersonate
1025 */
1026 public function setSubject($subject)
1027 {
1028 $this->config['subject'] = $subject;
1029 }
1030
1031 /**
1032 * Declare whether making API calls should make the call immediately, or
1033 * return a request which can be called with ->execute();
1034 *
1035 * @param boolean $defer True if calls should not be executed right away.
1036 */
1037 public function setDefer($defer)
1038 {
1039 $this->deferExecution = $defer;
1040 }
1041
1042 /**
1043 * Whether or not to return raw requests
1044 * @return boolean
1045 */
1046 public function shouldDefer()
1047 {
1048 return $this->deferExecution;
1049 }
1050
1051 /**
1052 * @return OAuth2 implementation
1053 */
1054 public function getOAuth2Service()
1055 {
1056 if (!isset($this->auth)) {
1057 $this->auth = $this->createOAuth2Service();
1058 }
1059
1060 return $this->auth;
1061 }
1062
1063 /**
1064 * create a default google auth object
1065 */
1066 protected function createOAuth2Service()
1067 {
1068 $auth = new OAuth2([
1069 'clientId' => $this->getClientId(),
1070 'clientSecret' => $this->getClientSecret(),
1071 'authorizationUri' => self::OAUTH2_AUTH_URL,
1072 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI,
1073 'redirectUri' => $this->getRedirectUri(),
1074 'issuer' => $this->config['client_id'],
1075 'signingKey' => $this->config['signing_key'],
1076 'signingAlgorithm' => $this->config['signing_algorithm'],
1077 ]);
1078
1079 return $auth;
1080 }
1081
1082 /**
1083 * Set the Cache object
1084 * @param CacheItemPoolInterface $cache
1085 */
1086 public function setCache(CacheItemPoolInterface $cache)
1087 {
1088 $this->cache = $cache;
1089 }
1090
1091 /**
1092 * @return CacheItemPoolInterface
1093 */
1094 public function getCache()
1095 {
1096 if (!$this->cache) {
1097 $this->cache = $this->createDefaultCache();
1098 }
1099
1100 return $this->cache;
1101 }
1102
1103 /**
1104 * @param array $cacheConfig
1105 */
1106 public function setCacheConfig(array $cacheConfig)
1107 {
1108 $this->config['cache_config'] = $cacheConfig;
1109 }
1110
1111 /**
1112 * Set the Logger object
1113 * @param LoggerInterface $logger
1114 */
1115 public function setLogger(LoggerInterface $logger)
1116 {
1117 $this->logger = $logger;
1118 }
1119
1120 /**
1121 * @return LoggerInterface
1122 */
1123 public function getLogger()
1124 {
1125 if (!isset($this->logger)) {
1126 $this->logger = $this->createDefaultLogger();
1127 }
1128
1129 return $this->logger;
1130 }
1131
1132 protected function createDefaultLogger()
1133 {
1134 $logger = new Logger('google-api-php-client');
1135 if ($this->isAppEngine()) {
1136 $handler = new MonologSyslogHandler('app', LOG_USER, Logger::NOTICE);
1137 } else {
1138 $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE);
1139 }
1140 $logger->pushHandler($handler);
1141
1142 return $logger;
1143 }
1144
1145 protected function createDefaultCache()
1146 {
1147 return new MemoryCacheItemPool();
1148 }
1149
1150 /**
1151 * Set the Http Client object
1152 * @param ClientInterface $http
1153 */
1154 public function setHttpClient(ClientInterface $http)
1155 {
1156 $this->http = $http;
1157 }
1158
1159 /**
1160 * @return ClientInterface
1161 */
1162 public function getHttpClient()
1163 {
1164 if (null === $this->http) {
1165 $this->http = $this->createDefaultHttpClient();
1166 }
1167
1168 return $this->http;
1169 }
1170
1171 /**
1172 * Set the API format version.
1173 *
1174 * `true` will use V2, which may return more useful error messages.
1175 *
1176 * @param bool $value
1177 */
1178 public function setApiFormatV2($value)
1179 {
1180 $this->config['api_format_v2'] = (bool) $value;
1181 }
1182
1183 protected function createDefaultHttpClient()
1184 {
1185 $guzzleVersion = null;
1186 if (defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) {
1187 $guzzleVersion = ClientInterface::MAJOR_VERSION;
1188 } elseif (defined('\GuzzleHttp\ClientInterface::VERSION')) {
1189 $guzzleVersion = (int)substr(ClientInterface::VERSION, 0, 1);
1190 }
1191
1192 if (5 === $guzzleVersion) {
1193 $options = [
1194 'base_url' => $this->config['base_path'],
1195 'defaults' => ['exceptions' => false],
1196 ];
1197 if ($this->isAppEngine()) {
1198 if (class_exists(StreamHandler::class)) {
1199 // set StreamHandler on AppEngine by default
1200 $options['handler'] = new StreamHandler();
1201 $options['defaults']['verify'] = '/etc/ca-certificates.crt';
1202 }
1203 }
1204 } elseif (6 === $guzzleVersion || 7 === $guzzleVersion) {
1205 // guzzle 6 or 7
1206 $options = [
1207 'base_uri' => $this->config['base_path'],
1208 'http_errors' => false,
1209 ];
1210 } else {
1211 throw new LogicException('Could not find supported version of Guzzle.');
1212 }
1213
1214 return new GuzzleClient($options);
1215 }
1216
1217 /**
1218 * @return FetchAuthTokenCache
1219 */
1220 private function createApplicationDefaultCredentials()
1221 {
1222 $scopes = $this->prepareScopes();
1223 $sub = $this->config['subject'];
1224 $signingKey = $this->config['signing_key'];
1225
1226 // create credentials using values supplied in setAuthConfig
1227 if ($signingKey) {
1228 $serviceAccountCredentials = [
1229 'client_id' => $this->config['client_id'],
1230 'client_email' => $this->config['client_email'],
1231 'private_key' => $signingKey,
1232 'type' => 'service_account',
1233 'quota_project_id' => $this->config['quota_project'],
1234 ];
1235 $credentials = CredentialsLoader::makeCredentials(
1236 $scopes,
1237 $serviceAccountCredentials
1238 );
1239 } else {
1240 // When $sub is provided, we cannot pass cache classes to ::getCredentials
1241 // because FetchAuthTokenCache::setSub does not exist.
1242 // The result is when $sub is provided, calls to ::onGce are not cached.
1243 $credentials = ApplicationDefaultCredentials::getCredentials(
1244 $scopes,
1245 null,
1246 $sub ? null : $this->config['cache_config'],
1247 $sub ? null : $this->getCache(),
1248 $this->config['quota_project']
1249 );
1250 }
1251
1252 // for service account domain-wide authority (impersonating a user)
1253 // @see https://developers.google.com/identity/protocols/OAuth2ServiceAccount
1254 if ($sub) {
1255 if (!$credentials instanceof ServiceAccountCredentials) {
1256 throw new DomainException('domain-wide authority requires service account credentials');
1257 }
1258
1259 $credentials->setSub($sub);
1260 }
1261
1262 // If we are not using FetchAuthTokenCache yet, create it now
1263 if (!$credentials instanceof FetchAuthTokenCache) {
1264 $credentials = new FetchAuthTokenCache(
1265 $credentials,
1266 $this->config['cache_config'],
1267 $this->getCache()
1268 );
1269 }
1270 return $credentials;
1271 }
1272
1273 protected function getAuthHandler()
1274 {
1275 // Be very careful using the cache, as the underlying auth library's cache
1276 // implementation is naive, and the cache keys do not account for user
1277 // sessions.
1278 //
1279 // @see https://github.com/google/google-api-php-client/issues/821
1280 return AuthHandlerFactory::build(
1281 $this->getCache(),
1282 $this->config['cache_config']
1283 );
1284 }
1285
1286 private function createUserRefreshCredentials($scope, $refreshToken)
1287 {
1288 $creds = array_filter([
1289 'client_id' => $this->getClientId(),
1290 'client_secret' => $this->getClientSecret(),
1291 'refresh_token' => $refreshToken,
1292 ]);
1293
1294 return new UserRefreshCredentials($scope, $creds);
1295 }
1296 }
1297