vendor/google/apiclient/src/Client.php line 363

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