PluginProbe
Gmail SMTP / trunk
Gmail SMTP vtrunk
1.2.3.21 1.2.3.20 trunk 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.9 1.2.0 1.2.3.14 1.2.3.15 1.2.3.16 1.2.3.18 1.2.3.5
gmail-smtp / google-api-php-client / src / Client.php

Client.php in Gmail SMTP trunk, at google-api-php-client/src/Client.php

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