Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

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