PluginProbe
WP-Stateless – Google Cloud Storage / 3.1.1
WP-Stateless – Google Cloud Storage v3.1.1
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / Google / src / Google / Client.php

Client.php in WP-Stateless – Google Cloud Storage 3.1.1, at lib/Google/src/Google/Client.php

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