PluginProbe
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 3.3.2.2
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v3.3.2.2
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 All 75 releases
atarim-visual-collaboration / src / Client.php
Client.php
1,296 lines 40.2 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/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 * @return string
361 */
362 public function createAuthUrl($scope = null)
363 {
364 if (empty($scope)) {
365 $scope = $this->prepareScopes();
366 }
367 if (is_array($scope)) {
368 $scope = implode(' ', $scope);
369 }
370
371 // only accept one of prompt or approval_prompt
372 $approvalPrompt = $this->config['prompt']
373 ? null
374 : $this->config['approval_prompt'];
375
376 // include_granted_scopes should be string "true", string "false", or null
377 $includeGrantedScopes = $this->config['include_granted_scopes'] === null
378 ? null
379 : var_export($this->config['include_granted_scopes'], true);
380
381 $params = array_filter([
382 'access_type' => $this->config['access_type'],
383 'approval_prompt' => $approvalPrompt,
384 'hd' => $this->config['hd'],
385 'include_granted_scopes' => $includeGrantedScopes,
386 'login_hint' => $this->config['login_hint'],
387 'openid.realm' => $this->config['openid.realm'],
388 'prompt' => $this->config['prompt'],
389 'response_type' => 'code',
390 'scope' => $scope,
391 'state' => $this->config['state'],
392 ]);
393
394 // If the list of scopes contains plus.login, add request_visible_actions
395 // to auth URL.
396 $rva = $this->config['request_visible_actions'];
397 if (strlen($rva) > 0 && false !== strpos($scope, 'plus.login')) {
398 $params['request_visible_actions'] = $rva;
399 }
400
401 $auth = $this->getOAuth2Service();
402
403 return (string) $auth->buildFullAuthorizationUri($params);
404 }
405
406 /**
407 * Adds auth listeners to the HTTP client based on the credentials
408 * set in the Google API Client object
409 *
410 * @param ClientInterface $http the http client object.
411 * @return ClientInterface the http client object
412 */
413 public function authorize(ClientInterface $http = null)
414 {
415 $http = $http ?: $this->getHttpClient();
416 $authHandler = $this->getAuthHandler();
417
418 // These conditionals represent the decision tree for authentication
419 // 1. Check if a Google\Auth\CredentialsLoader instance has been supplied via the "credentials" option
420 // 2. Check for Application Default Credentials
421 // 3a. Check for an Access Token
422 // 3b. If access token exists but is expired, try to refresh it
423 // 4. Check for API Key
424 if ($this->credentials) {
425 return $authHandler->attachCredentials(
426 $http,
427 $this->credentials,
428 $this->config['token_callback']
429 );
430 }
431
432 if ($this->isUsingApplicationDefaultCredentials()) {
433 $credentials = $this->createApplicationDefaultCredentials();
434 return $authHandler->attachCredentialsCache(
435 $http,
436 $credentials,
437 $this->config['token_callback']
438 );
439 }
440
441 if ($token = $this->getAccessToken()) {
442 $scopes = $this->prepareScopes();
443 // add refresh subscriber to request a new token
444 if (isset($token['refresh_token']) && $this->isAccessTokenExpired()) {
445 $credentials = $this->createUserRefreshCredentials(
446 $scopes,
447 $token['refresh_token']
448 );
449 return $authHandler->attachCredentials(
450 $http,
451 $credentials,
452 $this->config['token_callback']
453 );
454 }
455
456 return $authHandler->attachToken($http, $token, (array) $scopes);
457 }
458
459 if ($key = $this->config['developer_key']) {
460 return $authHandler->attachKey($http, $key);
461 }
462
463 return $http;
464 }
465
466 /**
467 * Set the configuration to use application default credentials for
468 * authentication
469 *
470 * @see https://developers.google.com/identity/protocols/application-default-credentials
471 * @param boolean $useAppCreds
472 */
473 public function useApplicationDefaultCredentials($useAppCreds = true)
474 {
475 $this->config['use_application_default_credentials'] = $useAppCreds;
476 }
477
478 /**
479 * To prevent useApplicationDefaultCredentials from inappropriately being
480 * called in a conditional
481 *
482 * @see https://developers.google.com/identity/protocols/application-default-credentials
483 */
484 public function isUsingApplicationDefaultCredentials()
485 {
486 return $this->config['use_application_default_credentials'];
487 }
488
489 /**
490 * Set the access token used for requests.
491 *
492 * Note that at the time requests are sent, tokens are cached. A token will be
493 * cached for each combination of service and authentication scopes. If a
494 * cache pool is not provided, creating a new instance of the client will
495 * allow modification of access tokens. If a persistent cache pool is
496 * provided, in order to change the access token, you must clear the cached
497 * token by calling `$client->getCache()->clear()`. (Use caution in this case,
498 * as calling `clear()` will remove all cache items, including any items not
499 * related to Google API PHP Client.)
500 *
501 * @param string|array $token
502 * @throws InvalidArgumentException
503 */
504 public function setAccessToken($token)
505 {
506 if (is_string($token)) {
507 if ($json = json_decode($token, true)) {
508 $token = $json;
509 } else {
510 // assume $token is just the token string
511 $token = [
512 'access_token' => $token,
513 ];
514 }
515 }
516 if ($token == null) {
517 throw new InvalidArgumentException('invalid json token');
518 }
519 if (!isset($token['access_token'])) {
520 throw new InvalidArgumentException("Invalid token format");
521 }
522 $this->token = $token;
523 }
524
525 public function getAccessToken()
526 {
527 return $this->token;
528 }
529
530 /**
531 * @return string|null
532 */
533 public function getRefreshToken()
534 {
535 if (isset($this->token['refresh_token'])) {
536 return $this->token['refresh_token'];
537 }
538
539 return null;
540 }
541
542 /**
543 * Returns if the access_token is expired.
544 * @return bool Returns True if the access_token is expired.
545 */
546 public function isAccessTokenExpired()
547 {
548 if (!$this->token) {
549 return true;
550 }
551
552 $created = 0;
553 if (isset($this->token['created'])) {
554 $created = $this->token['created'];
555 } elseif (isset($this->token['id_token'])) {
556 // check the ID token for "iat"
557 // signature verification is not required here, as we are just
558 // using this for convenience to save a round trip request
559 // to the Google API server
560 $idToken = $this->token['id_token'];
561 if (substr_count($idToken, '.') == 2) {
562 $parts = explode('.', $idToken);
563 $payload = json_decode(base64_decode($parts[1]), true);
564 if ($payload && isset($payload['iat'])) {
565 $created = $payload['iat'];
566 }
567 }
568 }
569 if (!isset($this->token['expires_in'])) {
570 // if the token does not have an "expires_in", then it's considered expired
571 return true;
572 }
573
574 // If the token is set to expire in the next 30 seconds.
575 return ($created + ($this->token['expires_in'] - 30)) < time();
576 }
577
578 /**
579 * @deprecated See UPGRADING.md for more information
580 */
581 public function getAuth()
582 {
583 throw new BadMethodCallException(
584 'This function no longer exists. See UPGRADING.md for more information'
585 );
586 }
587
588 /**
589 * @deprecated See UPGRADING.md for more information
590 */
591 public function setAuth($auth)
592 {
593 throw new BadMethodCallException(
594 'This function no longer exists. See UPGRADING.md for more information'
595 );
596 }
597
598 /**
599 * Set the OAuth 2.0 Client ID.
600 * @param string $clientId
601 */
602 public function setClientId($clientId)
603 {
604 $this->config['client_id'] = $clientId;
605 }
606
607 public function getClientId()
608 {
609 return $this->config['client_id'];
610 }
611
612 /**
613 * Set the OAuth 2.0 Client Secret.
614 * @param string $clientSecret
615 */
616 public function setClientSecret($clientSecret)
617 {
618 $this->config['client_secret'] = $clientSecret;
619 }
620
621 public function getClientSecret()
622 {
623 return $this->config['client_secret'];
624 }
625
626 /**
627 * Set the OAuth 2.0 Redirect URI.
628 * @param string $redirectUri
629 */
630 public function setRedirectUri($redirectUri)
631 {
632 $this->config['redirect_uri'] = $redirectUri;
633 }
634
635 public function getRedirectUri()
636 {
637 return $this->config['redirect_uri'];
638 }
639
640 /**
641 * Set OAuth 2.0 "state" parameter to achieve per-request customization.
642 * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
643 * @param string $state
644 */
645 public function setState($state)
646 {
647 $this->config['state'] = $state;
648 }
649
650 /**
651 * @param string $accessType Possible values for access_type include:
652 * {@code "offline"} to request offline access from the user.
653 * {@code "online"} to request online access from the user.
654 */
655 public function setAccessType($accessType)
656 {
657 $this->config['access_type'] = $accessType;
658 }
659
660 /**
661 * @param string $approvalPrompt Possible values for approval_prompt include:
662 * {@code "force"} to force the approval UI to appear.
663 * {@code "auto"} to request auto-approval when possible. (This is the default value)
664 */
665 public function setApprovalPrompt($approvalPrompt)
666 {
667 $this->config['approval_prompt'] = $approvalPrompt;
668 }
669
670 /**
671 * Set the login hint, email address or sub id.
672 * @param string $loginHint
673 */
674 public function setLoginHint($loginHint)
675 {
676 $this->config['login_hint'] = $loginHint;
677 }
678
679 /**
680 * Set the application name, this is included in the User-Agent HTTP header.
681 * @param string $applicationName
682 */
683 public function setApplicationName($applicationName)
684 {
685 $this->config['application_name'] = $applicationName;
686 }
687
688 /**
689 * If 'plus.login' is included in the list of requested scopes, you can use
690 * this method to define types of app activities that your app will write.
691 * You can find a list of available types here:
692 * @link https://developers.google.com/+/api/moment-types
693 *
694 * @param array $requestVisibleActions Array of app activity types
695 */
696 public function setRequestVisibleActions($requestVisibleActions)
697 {
698 if (is_array($requestVisibleActions)) {
699 $requestVisibleActions = implode(" ", $requestVisibleActions);
700 }
701 $this->config['request_visible_actions'] = $requestVisibleActions;
702 }
703
704 /**
705 * Set the developer key to use, these are obtained through the API Console.
706 * @see http://code.google.com/apis/console-help/#generatingdevkeys
707 * @param string $developerKey
708 */
709 public function setDeveloperKey($developerKey)
710 {
711 $this->config['developer_key'] = $developerKey;
712 }
713
714 /**
715 * Set the hd (hosted domain) parameter streamlines the login process for
716 * Google Apps hosted accounts. By including the domain of the user, you
717 * restrict sign-in to accounts at that domain.
718 * @param string $hd the domain to use.
719 */
720 public function setHostedDomain($hd)
721 {
722 $this->config['hd'] = $hd;
723 }
724
725 /**
726 * Set the prompt hint. Valid values are none, consent and select_account.
727 * If no value is specified and the user has not previously authorized
728 * access, then the user is shown a consent screen.
729 * @param string $prompt
730 * {@code "none"} Do not display any authentication or consent screens. Must not be specified with other values.
731 * {@code "consent"} Prompt the user for consent.
732 * {@code "select_account"} Prompt the user to select an account.
733 */
734 public function setPrompt($prompt)
735 {
736 $this->config['prompt'] = $prompt;
737 }
738
739 /**
740 * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
741 * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
742 * an authentication request is valid.
743 * @param string $realm the URL-space to use.
744 */
745 public function setOpenidRealm($realm)
746 {
747 $this->config['openid.realm'] = $realm;
748 }
749
750 /**
751 * If this is provided with the value true, and the authorization request is
752 * granted, the authorization will include any previous authorizations
753 * granted to this user/application combination for other scopes.
754 * @param bool $include the URL-space to use.
755 */
756 public function setIncludeGrantedScopes($include)
757 {
758 $this->config['include_granted_scopes'] = $include;
759 }
760
761 /**
762 * sets function to be called when an access token is fetched
763 * @param callable $tokenCallback - function ($cacheKey, $accessToken)
764 */
765 public function setTokenCallback(callable $tokenCallback)
766 {
767 $this->config['token_callback'] = $tokenCallback;
768 }
769
770 /**
771 * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
772 * token, if a token isn't provided.
773 *
774 * @param string|array|null $token The token (access token or a refresh token) that should be revoked.
775 * @return boolean Returns True if the revocation was successful, otherwise False.
776 */
777 public function revokeToken($token = null)
778 {
779 $tokenRevoker = new Revoke($this->getHttpClient());
780
781 return $tokenRevoker->revokeToken($token ?: $this->getAccessToken());
782 }
783
784 /**
785 * Verify an id_token. This method will verify the current id_token, if one
786 * isn't provided.
787 *
788 * @throws LogicException If no token was provided and no token was set using `setAccessToken`.
789 * @throws UnexpectedValueException If the token is not a valid JWT.
790 * @param string|null $idToken The token (id_token) that should be verified.
791 * @return array|false Returns the token payload as an array if the verification was
792 * successful, false otherwise.
793 */
794 public function verifyIdToken($idToken = null)
795 {
796 $tokenVerifier = new Verify(
797 $this->getHttpClient(),
798 $this->getCache(),
799 $this->config['jwt']
800 );
801
802 if (null === $idToken) {
803 $token = $this->getAccessToken();
804 if (!isset($token['id_token'])) {
805 throw new LogicException(
806 'id_token must be passed in or set as part of setAccessToken'
807 );
808 }
809 $idToken = $token['id_token'];
810 }
811
812 return $tokenVerifier->verifyIdToken(
813 $idToken,
814 $this->getClientId()
815 );
816 }
817
818 /**
819 * Set the scopes to be requested. Must be called before createAuthUrl().
820 * Will remove any previously configured scopes.
821 * @param string|array $scope_or_scopes, ie:
822 * array(
823 * 'https://www.googleapis.com/auth/plus.login',
824 * 'https://www.googleapis.com/auth/moderator'
825 * );
826 */
827 public function setScopes($scope_or_scopes)
828 {
829 $this->requestedScopes = [];
830 $this->addScope($scope_or_scopes);
831 }
832
833 /**
834 * This functions adds a scope to be requested as part of the OAuth2.0 flow.
835 * Will append any scopes not previously requested to the scope parameter.
836 * A single string will be treated as a scope to request. An array of strings
837 * will each be appended.
838 * @param string|string[] $scope_or_scopes e.g. "profile"
839 */
840 public function addScope($scope_or_scopes)
841 {
842 if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
843 $this->requestedScopes[] = $scope_or_scopes;
844 } elseif (is_array($scope_or_scopes)) {
845 foreach ($scope_or_scopes as $scope) {
846 $this->addScope($scope);
847 }
848 }
849 }
850
851 /**
852 * Returns the list of scopes requested by the client
853 * @return array the list of scopes
854 *
855 */
856 public function getScopes()
857 {
858 return $this->requestedScopes;
859 }
860
861 /**
862 * @return string|null
863 * @visible For Testing
864 */
865 public function prepareScopes()
866 {
867 if (empty($this->requestedScopes)) {
868 return null;
869 }
870
871 return implode(' ', $this->requestedScopes);
872 }
873
874 /**
875 * Helper method to execute deferred HTTP requests.
876 *
877 * @template T
878 * @param RequestInterface $request
879 * @param class-string<T>|false|null $expectedClass
880 * @throws \Google\Exception
881 * @return mixed|T|ResponseInterface
882 */
883 public function execute(RequestInterface $request, $expectedClass = null)
884 {
885 $request = $request
886 ->withHeader(
887 'User-Agent',
888 sprintf(
889 '%s %s%s',
890 $this->config['application_name'],
891 self::USER_AGENT_SUFFIX,
892 $this->getLibraryVersion()
893 )
894 )
895 ->withHeader(
896 'x-goog-api-client',
897 sprintf(
898 'gl-php/%s gdcl/%s',
899 phpversion(),
900 $this->getLibraryVersion()
901 )
902 );
903
904 if ($this->config['api_format_v2']) {
905 $request = $request->withHeader(
906 'X-GOOG-API-FORMAT-VERSION',
907 '2'
908 );
909 }
910
911 // call the authorize method
912 // this is where most of the grunt work is done
913 $http = $this->authorize();
914
915 return REST::execute(
916 $http,
917 $request,
918 $expectedClass,
919 $this->config['retry'],
920 $this->config['retry_map']
921 );
922 }
923
924 /**
925 * Declare whether batch calls should be used. This may increase throughput
926 * by making multiple requests in one connection.
927 *
928 * @param boolean $useBatch True if the batch support should
929 * be enabled. Defaults to False.
930 */
931 public function setUseBatch($useBatch)
932 {
933 // This is actually an alias for setDefer.
934 $this->setDefer($useBatch);
935 }
936
937 /**
938 * Are we running in Google AppEngine?
939 * return bool
940 */
941 public function isAppEngine()
942 {
943 return (isset($_SERVER['SERVER_SOFTWARE']) &&
944 strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
945 }
946
947 public function setConfig($name, $value)
948 {
949 $this->config[$name] = $value;
950 }
951
952 public function getConfig($name, $default = null)
953 {
954 return isset($this->config[$name]) ? $this->config[$name] : $default;
955 }
956
957 /**
958 * For backwards compatibility
959 * alias for setAuthConfig
960 *
961 * @param string $file the configuration file
962 * @throws \Google\Exception
963 * @deprecated
964 */
965 public function setAuthConfigFile($file)
966 {
967 $this->setAuthConfig($file);
968 }
969
970 /**
971 * Set the auth config from new or deprecated JSON config.
972 * This structure should match the file downloaded from
973 * the "Download JSON" button on in the Google Developer
974 * Console.
975 * @param string|array $config the configuration json
976 * @throws \Google\Exception
977 */
978 public function setAuthConfig($config)
979 {
980 if (is_string($config)) {
981 if (!file_exists($config)) {
982 throw new InvalidArgumentException(sprintf('file "%s" does not exist', $config));
983 }
984
985 $json = file_get_contents($config);
986
987 if (!$config = json_decode($json, true)) {
988 throw new LogicException('invalid json for auth config');
989 }
990 }
991
992 $key = isset($config['installed']) ? 'installed' : 'web';
993 if (isset($config['type']) && $config['type'] == 'service_account') {
994 // application default credentials
995 $this->useApplicationDefaultCredentials();
996
997 // set the information from the config
998 $this->setClientId($config['client_id']);
999 $this->config['client_email'] = $config['client_email'];
1000 $this->config['signing_key'] = $config['private_key'];
1001 $this->config['signing_algorithm'] = 'HS256';
1002 } elseif (isset($config[$key])) {
1003 // old-style
1004 $this->setClientId($config[$key]['client_id']);
1005 $this->setClientSecret($config[$key]['client_secret']);
1006 if (isset($config[$key]['redirect_uris'])) {
1007 $this->setRedirectUri($config[$key]['redirect_uris'][0]);
1008 }
1009 } else {
1010 // new-style
1011 $this->setClientId($config['client_id']);
1012 $this->setClientSecret($config['client_secret']);
1013 if (isset($config['redirect_uris'])) {
1014 $this->setRedirectUri($config['redirect_uris'][0]);
1015 }
1016 }
1017 }
1018
1019 /**
1020 * Use when the service account has been delegated domain wide access.
1021 *
1022 * @param string $subject an email address account to impersonate
1023 */
1024 public function setSubject($subject)
1025 {
1026 $this->config['subject'] = $subject;
1027 }
1028
1029 /**
1030 * Declare whether making API calls should make the call immediately, or
1031 * return a request which can be called with ->execute();
1032 *
1033 * @param boolean $defer True if calls should not be executed right away.
1034 */
1035 public function setDefer($defer)
1036 {
1037 $this->deferExecution = $defer;
1038 }
1039
1040 /**
1041 * Whether or not to return raw requests
1042 * @return boolean
1043 */
1044 public function shouldDefer()
1045 {
1046 return $this->deferExecution;
1047 }
1048
1049 /**
1050 * @return OAuth2 implementation
1051 */
1052 public function getOAuth2Service()
1053 {
1054 if (!isset($this->auth)) {
1055 $this->auth = $this->createOAuth2Service();
1056 }
1057
1058 return $this->auth;
1059 }
1060
1061 /**
1062 * create a default google auth object
1063 */
1064 protected function createOAuth2Service()
1065 {
1066 $auth = new OAuth2([
1067 'clientId' => $this->getClientId(),
1068 'clientSecret' => $this->getClientSecret(),
1069 'authorizationUri' => self::OAUTH2_AUTH_URL,
1070 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI,
1071 'redirectUri' => $this->getRedirectUri(),
1072 'issuer' => $this->config['client_id'],
1073 'signingKey' => $this->config['signing_key'],
1074 'signingAlgorithm' => $this->config['signing_algorithm'],
1075 ]);
1076
1077 return $auth;
1078 }
1079
1080 /**
1081 * Set the Cache object
1082 * @param CacheItemPoolInterface $cache
1083 */
1084 public function setCache(CacheItemPoolInterface $cache)
1085 {
1086 $this->cache = $cache;
1087 }
1088
1089 /**
1090 * @return CacheItemPoolInterface
1091 */
1092 public function getCache()
1093 {
1094 if (!$this->cache) {
1095 $this->cache = $this->createDefaultCache();
1096 }
1097
1098 return $this->cache;
1099 }
1100
1101 /**
1102 * @param array $cacheConfig
1103 */
1104 public function setCacheConfig(array $cacheConfig)
1105 {
1106 $this->config['cache_config'] = $cacheConfig;
1107 }
1108
1109 /**
1110 * Set the Logger object
1111 * @param LoggerInterface $logger
1112 */
1113 public function setLogger(LoggerInterface $logger)
1114 {
1115 $this->logger = $logger;
1116 }
1117
1118 /**
1119 * @return LoggerInterface
1120 */
1121 public function getLogger()
1122 {
1123 if (!isset($this->logger)) {
1124 $this->logger = $this->createDefaultLogger();
1125 }
1126
1127 return $this->logger;
1128 }
1129
1130 protected function createDefaultLogger()
1131 {
1132 $logger = new Logger('google-api-php-client');
1133 if ($this->isAppEngine()) {
1134 $handler = new MonologSyslogHandler('app', LOG_USER, Logger::NOTICE);
1135 } else {
1136 $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE);
1137 }
1138 $logger->pushHandler($handler);
1139
1140 return $logger;
1141 }
1142
1143 protected function createDefaultCache()
1144 {
1145 return new MemoryCacheItemPool();
1146 }
1147
1148 /**
1149 * Set the Http Client object
1150 * @param ClientInterface $http
1151 */
1152 public function setHttpClient(ClientInterface $http)
1153 {
1154 $this->http = $http;
1155 }
1156
1157 /**
1158 * @return ClientInterface
1159 */
1160 public function getHttpClient()
1161 {
1162 if (null === $this->http) {
1163 $this->http = $this->createDefaultHttpClient();
1164 }
1165
1166 return $this->http;
1167 }
1168
1169 /**
1170 * Set the API format version.
1171 *
1172 * `true` will use V2, which may return more useful error messages.
1173 *
1174 * @param bool $value
1175 */
1176 public function setApiFormatV2($value)
1177 {
1178 $this->config['api_format_v2'] = (bool) $value;
1179 }
1180
1181 protected function createDefaultHttpClient()
1182 {
1183 $guzzleVersion = null;
1184 if (defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) {
1185 $guzzleVersion = ClientInterface::MAJOR_VERSION;
1186 } elseif (defined('\GuzzleHttp\ClientInterface::VERSION')) {
1187 // @phpstan-ignore-next-line
1188 $guzzleVersion = (int)substr(ClientInterface::VERSION, 0, 1);
1189 }
1190
1191 if (5 === $guzzleVersion) {
1192 $options = [
1193 'base_url' => $this->config['base_path'],
1194 'defaults' => ['exceptions' => false],
1195 ];
1196 if ($this->isAppEngine()) {
1197 if (class_exists(StreamHandler::class)) {
1198 // set StreamHandler on AppEngine by default
1199 $options['handler'] = new StreamHandler();
1200 $options['defaults']['verify'] = '/etc/ca-certificates.crt';
1201 }
1202 }
1203 } elseif (6 === $guzzleVersion || 7 === $guzzleVersion) {
1204 // guzzle 6 or 7
1205 $options = [
1206 'base_uri' => $this->config['base_path'],
1207 'http_errors' => false,
1208 ];
1209 } else {
1210 throw new LogicException('Could not find supported version of Guzzle.');
1211 }
1212
1213 return new GuzzleClient($options);
1214 }
1215
1216 /**
1217 * @return FetchAuthTokenCache
1218 */
1219 private function createApplicationDefaultCredentials()
1220 {
1221 $scopes = $this->prepareScopes();
1222 $sub = $this->config['subject'];
1223 $signingKey = $this->config['signing_key'];
1224
1225 // create credentials using values supplied in setAuthConfig
1226 if ($signingKey) {
1227 $serviceAccountCredentials = [
1228 'client_id' => $this->config['client_id'],
1229 'client_email' => $this->config['client_email'],
1230 'private_key' => $signingKey,
1231 'type' => 'service_account',
1232 'quota_project_id' => $this->config['quota_project'],
1233 ];
1234 $credentials = CredentialsLoader::makeCredentials(
1235 $scopes,
1236 $serviceAccountCredentials
1237 );
1238 } else {
1239 // When $sub is provided, we cannot pass cache classes to ::getCredentials
1240 // because FetchAuthTokenCache::setSub does not exist.
1241 // The result is when $sub is provided, calls to ::onGce are not cached.
1242 $credentials = ApplicationDefaultCredentials::getCredentials(
1243 $scopes,
1244 null,
1245 $sub ? null : $this->config['cache_config'],
1246 $sub ? null : $this->getCache(),
1247 $this->config['quota_project']
1248 );
1249 }
1250
1251 // for service account domain-wide authority (impersonating a user)
1252 // @see https://developers.google.com/identity/protocols/OAuth2ServiceAccount
1253 if ($sub) {
1254 if (!$credentials instanceof ServiceAccountCredentials) {
1255 throw new DomainException('domain-wide authority requires service account credentials');
1256 }
1257
1258 $credentials->setSub($sub);
1259 }
1260
1261 // If we are not using FetchAuthTokenCache yet, create it now
1262 if (!$credentials instanceof FetchAuthTokenCache) {
1263 $credentials = new FetchAuthTokenCache(
1264 $credentials,
1265 $this->config['cache_config'],
1266 $this->getCache()
1267 );
1268 }
1269 return $credentials;
1270 }
1271
1272 protected function getAuthHandler()
1273 {
1274 // Be very careful using the cache, as the underlying auth library's cache
1275 // implementation is naive, and the cache keys do not account for user
1276 // sessions.
1277 //
1278 // @see https://github.com/google/google-api-php-client/issues/821
1279 return AuthHandlerFactory::build(
1280 $this->getCache(),
1281 $this->config['cache_config']
1282 );
1283 }
1284
1285 private function createUserRefreshCredentials($scope, $refreshToken)
1286 {
1287 $creds = array_filter([
1288 'client_id' => $this->getClientId(),
1289 'client_secret' => $this->getClientSecret(),
1290 'refresh_token' => $refreshToken,
1291 ]);
1292
1293 return new UserRefreshCredentials($scope, $creds);
1294 }
1295 }
1296