Passed
Pull Request — master (#6)
by
unknown
07:40 queued 11s
created

Google_Client::getClientSecret()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 3
rs 10
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;
0 ignored issues
show
Bug introduced by
The type GuzzleHttp\Ring\Client\StreamHandler was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
use Psr\Cache\CacheItemPoolInterface;
29
use Psr\Http\Message\RequestInterface;
30
use Psr\Log\LoggerInterface;
31
use Monolog\Logger;
32
use Monolog\Handler\StreamHandler as MonologStreamHandler;
33
use Monolog\Handler\SyslogHandler as MonologSyslogHandler;
34
35
/**
36
 * The Google API Client
37
 * https://github.com/google/google-api-php-client
38
 */
39
class Google_Client
40
{
41
  const LIBVER = "2.2.3";
42
  const USER_AGENT_SUFFIX = "google-api-php-client/";
43
  const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
44
  const OAUTH2_TOKEN_URI = 'https://oauth2.googleapis.com/token';
45
  const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
46
  const API_BASE_PATH = 'https://www.googleapis.com';
47
48
  /**
49
   * @var Google\Auth\OAuth2 $auth
50
   */
51
  private $auth;
52
53
  /**
54
   * @var GuzzleHttp\ClientInterface $http
55
   */
56
  private $http;
57
58
  /**
59
   * @var Psr\Cache\CacheItemPoolInterface $cache
60
   */
61
  private $cache;
62
63
  /**
64
   * @var array access token
65
   */
66
  private $token;
67
68
  /**
69
   * @var array $config
70
   */
71
  private $config;
72
73
  /**
74
   * @var Psr\Log\LoggerInterface $logger
75
   */
76
  private $logger;
77
78
  /**
79
   * @var boolean $deferExecution
80
   */
81
  private $deferExecution = false;
82
83
  /** @var array $scopes */
84
  // Scopes requested by the client
85
  protected $requestedScopes = [];
86
87
  /**
88
   * Construct the Google Client.
89
   *
90
   * @param array $config
91
   */
92
  public function __construct(array $config = array())
93
  {
94
    $this->config = array_merge(
95
        [
96
          'application_name' => '',
97
98
          // Don't change these unless you're working against a special development
99
          // or testing environment.
100
          'base_path' => self::API_BASE_PATH,
101
102
          // https://developers.google.com/console
103
          'client_id' => '',
104
          'client_secret' => '',
105
          'redirect_uri' => null,
106
          'state' => null,
107
108
          // Simple API access key, also from the API console. Ensure you get
109
          // a Server key, and not a Browser key.
110
          'developer_key' => '',
111
112
          // For use with Google Cloud Platform
113
          // fetch the ApplicationDefaultCredentials, if applicable
114
          // @see https://developers.google.com/identity/protocols/application-default-credentials
115
          'use_application_default_credentials' => false,
116
          'signing_key' => null,
117
          'signing_algorithm' => null,
118
          'subject' => null,
119
120
          // Other OAuth2 parameters.
121
          'hd' => '',
122
          'prompt' => '',
123
          'openid.realm' => '',
124
          'include_granted_scopes' => null,
125
          'login_hint' => '',
126
          'request_visible_actions' => '',
127
          'access_type' => 'online',
128
          'approval_prompt' => 'auto',
129
130
          // Task Runner retry configuration
131
          // @see Google_Task_Runner
132
          'retry' => array(),
133
          'retry_map' => null,
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
          // Setting api_format_v2 will return more detailed error messages
147
          // from certain APIs.
148
          'api_format_v2' => false
149
        ],
150
        $config
151
    );
152
  }
153
154
  /**
155
   * Get a string containing the version of the library.
156
   *
157
   * @return string
158
   */
159
  public function getLibraryVersion()
160
  {
161
    return self::LIBVER;
162
  }
163
164
  /**
165
   * For backwards compatibility
166
   * alias for fetchAccessTokenWithAuthCode
167
   *
168
   * @param $code string code from accounts.google.com
169
   * @return array access token
170
   * @deprecated
171
   */
172
  public function authenticate($code)
173
  {
174
    return $this->fetchAccessTokenWithAuthCode($code);
175
  }
176
177
  /**
178
   * Attempt to exchange a code for an valid authentication token.
179
   * Helper wrapped around the OAuth 2.0 implementation.
180
   *
181
   * @param $code string code from accounts.google.com
182
   * @return array access token
183
   */
184
  public function fetchAccessTokenWithAuthCode($code)
185
  {
186
    if (strlen($code) == 0) {
187
      throw new InvalidArgumentException("Invalid code");
188
    }
189
190
    $auth = $this->getOAuth2Service();
191
    $auth->setCode($code);
192
    $auth->setRedirectUri($this->getRedirectUri());
193
194
    $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
195
    $creds = $auth->fetchAuthToken($httpHandler);
196
    if ($creds && isset($creds['access_token'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $creds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
197
      $creds['created'] = time();
198
      $this->setAccessToken($creds);
199
    }
200
201
    return $creds;
202
  }
203
204
  /**
205
   * For backwards compatibility
206
   * alias for fetchAccessTokenWithAssertion
207
   *
208
   * @return array access token
209
   * @deprecated
210
   */
211
  public function refreshTokenWithAssertion()
212
  {
213
    return $this->fetchAccessTokenWithAssertion();
214
  }
215
216
  /**
217
   * Fetches a fresh access token with a given assertion token.
218
   * @param ClientInterface $authHttp optional.
219
   * @return array access token
220
   */
221
  public function fetchAccessTokenWithAssertion(ClientInterface $authHttp = null)
222
  {
223
    if (!$this->isUsingApplicationDefaultCredentials()) {
224
      throw new DomainException(
225
          'set the JSON service account credentials using'
226
          . ' Google_Client::setAuthConfig or set the path to your JSON file'
227
          . ' with the "GOOGLE_APPLICATION_CREDENTIALS" environment variable'
228
          . ' and call Google_Client::useApplicationDefaultCredentials to'
229
          . ' refresh a token with assertion.'
230
      );
231
    }
232
233
    $this->getLogger()->log(
234
        'info',
235
        'OAuth2 access token refresh with Signed JWT assertion grants.'
236
    );
237
238
    $credentials = $this->createApplicationDefaultCredentials();
239
240
    $httpHandler = HttpHandlerFactory::build($authHttp);
241
    $creds = $credentials->fetchAuthToken($httpHandler);
242
    if ($creds && isset($creds['access_token'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $creds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
243
      $creds['created'] = time();
244
      $this->setAccessToken($creds);
245
    }
246
247
    return $creds;
248
  }
249
250
  /**
251
   * For backwards compatibility
252
   * alias for fetchAccessTokenWithRefreshToken
253
   *
254
   * @param string $refreshToken
255
   * @return array access token
256
   */
257
  public function refreshToken($refreshToken)
258
  {
259
    return $this->fetchAccessTokenWithRefreshToken($refreshToken);
260
  }
261
262
  /**
263
   * Fetches a fresh OAuth 2.0 access token with the given refresh token.
264
   * @param string $refreshToken
265
   * @return array access token
266
   */
267
  public function fetchAccessTokenWithRefreshToken($refreshToken = null)
268
  {
269
    if (null === $refreshToken) {
270
      if (!isset($this->token['refresh_token'])) {
271
        throw new LogicException(
272
            'refresh token must be passed in or set as part of setAccessToken'
273
        );
274
      }
275
      $refreshToken = $this->token['refresh_token'];
276
    }
277
    $this->getLogger()->info('OAuth2 access token refresh');
278
    $auth = $this->getOAuth2Service();
279
    $auth->setRefreshToken($refreshToken);
280
281
    $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
282
    $creds = $auth->fetchAuthToken($httpHandler);
283
    if ($creds && isset($creds['access_token'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $creds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
284
      $creds['created'] = time();
285
      if (!isset($creds['refresh_token'])) {
286
        $creds['refresh_token'] = $refreshToken;
287
      }
288
      $this->setAccessToken($creds);
289
    }
290
291
    return $creds;
292
  }
293
294
  /**
295
   * Create a URL to obtain user authorization.
296
   * The authorization endpoint allows the user to first
297
   * authenticate, and then grant/deny the access request.
298
   * @param string|array $scope The scope is expressed as an array or list of space-delimited strings.
299
   * @return string
300
   */
301
  public function createAuthUrl($scope = null)
302
  {
303
    if (empty($scope)) {
304
      $scope = $this->prepareScopes();
305
    }
306
    if (is_array($scope)) {
307
      $scope = implode(' ', $scope);
308
    }
309
310
    // only accept one of prompt or approval_prompt
311
    $approvalPrompt = $this->config['prompt']
312
      ? null
313
      : $this->config['approval_prompt'];
314
315
    // include_granted_scopes should be string "true", string "false", or null
316
    $includeGrantedScopes = $this->config['include_granted_scopes'] === null
317
      ? null
318
      : var_export($this->config['include_granted_scopes'], true);
319
320
    $params = array_filter(
321
        [
322
          'access_type' => $this->config['access_type'],
323
          'approval_prompt' => $approvalPrompt,
324
          'hd' => $this->config['hd'],
325
          'include_granted_scopes' => $includeGrantedScopes,
326
          'login_hint' => $this->config['login_hint'],
327
          'openid.realm' => $this->config['openid.realm'],
328
          'prompt' => $this->config['prompt'],
329
          'response_type' => 'code',
330
          'scope' => $scope,
331
          'state' => $this->config['state'],
332
        ]
333
    );
334
335
    // If the list of scopes contains plus.login, add request_visible_actions
336
    // to auth URL.
337
    $rva = $this->config['request_visible_actions'];
338
    if (strlen($rva) > 0 && false !== strpos($scope, 'plus.login')) {
0 ignored issues
show
Bug introduced by
It seems like $scope can also be of type array; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

338
    if (strlen($rva) > 0 && false !== strpos(/** @scrutinizer ignore-type */ $scope, 'plus.login')) {
Loading history...
339
        $params['request_visible_actions'] = $rva;
340
    }
341
342
    $auth = $this->getOAuth2Service();
343
344
    return (string) $auth->buildFullAuthorizationUri($params);
345
  }
346
347
  /**
348
   * Adds auth listeners to the HTTP client based on the credentials
349
   * set in the Google API Client object
350
   *
351
   * @param GuzzleHttp\ClientInterface $http the http client object.
352
   * @return GuzzleHttp\ClientInterface the http client object
353
   */
354
  public function authorize(ClientInterface $http = null)
355
  {
356
    $credentials = null;
357
    $token = null;
358
    $scopes = null;
359
    if (null === $http) {
360
      $http = $this->getHttpClient();
361
    }
362
363
    // These conditionals represent the decision tree for authentication
364
    //   1.  Check for Application Default Credentials
365
    //   2.  Check for API Key
366
    //   3a. Check for an Access Token
367
    //   3b. If access token exists but is expired, try to refresh it
368
    if ($this->isUsingApplicationDefaultCredentials()) {
369
      $credentials = $this->createApplicationDefaultCredentials();
370
    } elseif ($token = $this->getAccessToken()) {
371
      $scopes = $this->prepareScopes();
372
      // add refresh subscriber to request a new token
373
      if (isset($token['refresh_token']) && $this->isAccessTokenExpired()) {
374
        $credentials = $this->createUserRefreshCredentials(
375
            $scopes,
376
            $token['refresh_token']
377
        );
378
      }
379
    }
380
381
    $authHandler = $this->getAuthHandler();
382
383
    if ($credentials) {
384
      $callback = $this->config['token_callback'];
385
      $http = $authHandler->attachCredentials($http, $credentials, $callback);
386
    } elseif ($token) {
387
      $http = $authHandler->attachToken($http, $token, (array) $scopes);
388
    } elseif ($key = $this->config['developer_key']) {
389
      $http = $authHandler->attachKey($http, $key);
390
    }
391
392
    return $http;
393
  }
394
395
  /**
396
   * Set the configuration to use application default credentials for
397
   * authentication
398
   *
399
   * @see https://developers.google.com/identity/protocols/application-default-credentials
400
   * @param boolean $useAppCreds
401
   */
402
  public function useApplicationDefaultCredentials($useAppCreds = true)
403
  {
404
    $this->config['use_application_default_credentials'] = $useAppCreds;
405
  }
406
407
  /**
408
   * To prevent useApplicationDefaultCredentials from inappropriately being
409
   * called in a conditional
410
   *
411
   * @see https://developers.google.com/identity/protocols/application-default-credentials
412
   */
413
  public function isUsingApplicationDefaultCredentials()
414
  {
415
    return $this->config['use_application_default_credentials'];
416
  }
417
418
  /**
419
   * @param string|array $token
420
   * @throws InvalidArgumentException
421
   */
422
  public function setAccessToken($token)
423
  {
424
    if (is_string($token)) {
425
      if ($json = json_decode($token, true)) {
426
        $token = $json;
427
      } else {
428
        // assume $token is just the token string
429
        $token = array(
430
          'access_token' => $token,
431
        );
432
      }
433
    }
434
    if ($token == null) {
435
      throw new InvalidArgumentException('invalid json token');
436
    }
437
    if (!isset($token['access_token'])) {
438
      throw new InvalidArgumentException("Invalid token format");
439
    }
440
    $this->token = $token;
441
  }
442
443
  public function getAccessToken()
444
  {
445
    return $this->token;
446
  }
447
448
  /**
449
   * @return string|null
450
   */
451
  public function getRefreshToken()
452
  {
453
    if (isset($this->token['refresh_token'])) {
454
      return $this->token['refresh_token'];
455
    }
456
457
    return null;
458
  }
459
460
  /**
461
   * Returns if the access_token is expired.
462
   * @return bool Returns True if the access_token is expired.
463
   */
464
  public function isAccessTokenExpired()
465
  {
466
    if (!$this->token) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->token of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
467
      return true;
468
    }
469
470
    $created = 0;
471
    if (isset($this->token['created'])) {
472
      $created = $this->token['created'];
473
    } elseif (isset($this->token['id_token'])) {
474
      // check the ID token for "iat"
475
      // signature verification is not required here, as we are just
476
      // using this for convenience to save a round trip request
477
      // to the Google API server
478
      $idToken = $this->token['id_token'];
479
      if (substr_count($idToken, '.') == 2) {
480
        $parts = explode('.', $idToken);
481
        $payload = json_decode(base64_decode($parts[1]), true);
482
        if ($payload && isset($payload['iat'])) {
483
          $created = $payload['iat'];
484
        }
485
      }
486
    }
487
488
    // If the token is set to expire in the next 30 seconds.
489
    return ($created + ($this->token['expires_in'] - 30)) < time();
490
  }
491
492
  /**
493
   * @deprecated See UPGRADING.md for more information
494
   */
495
  public function getAuth()
496
  {
497
    throw new BadMethodCallException(
498
        'This function no longer exists. See UPGRADING.md for more information'
499
    );
500
  }
501
502
  /**
503
   * @deprecated See UPGRADING.md for more information
504
   */
505
  public function setAuth($auth)
0 ignored issues
show
Unused Code introduced by
The parameter $auth is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

505
  public function setAuth(/** @scrutinizer ignore-unused */ $auth)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
506
  {
507
    throw new BadMethodCallException(
508
        'This function no longer exists. See UPGRADING.md for more information'
509
    );
510
  }
511
512
  /**
513
   * Set the OAuth 2.0 Client ID.
514
   * @param string $clientId
515
   */
516
  public function setClientId($clientId)
517
  {
518
    $this->config['client_id'] = $clientId;
519
  }
520
521
  public function getClientId()
522
  {
523
    return $this->config['client_id'];
524
  }
525
526
  /**
527
   * Set the OAuth 2.0 Client Secret.
528
   * @param string $clientSecret
529
   */
530
  public function setClientSecret($clientSecret)
531
  {
532
    $this->config['client_secret'] = $clientSecret;
533
  }
534
535
  public function getClientSecret()
536
  {
537
    return $this->config['client_secret'];
538
  }
539
540
  /**
541
   * Set the OAuth 2.0 Redirect URI.
542
   * @param string $redirectUri
543
   */
544
  public function setRedirectUri($redirectUri)
545
  {
546
    $this->config['redirect_uri'] = $redirectUri;
547
  }
548
549
  public function getRedirectUri()
550
  {
551
    return $this->config['redirect_uri'];
552
  }
553
554
  /**
555
   * Set OAuth 2.0 "state" parameter to achieve per-request customization.
556
   * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
557
   * @param string $state
558
   */
559
  public function setState($state)
560
  {
561
    $this->config['state'] = $state;
562
  }
563
564
  /**
565
   * @param string $accessType Possible values for access_type include:
566
   *  {@code "offline"} to request offline access from the user.
567
   *  {@code "online"} to request online access from the user.
568
   */
569
  public function setAccessType($accessType)
570
  {
571
    $this->config['access_type'] = $accessType;
572
  }
573
574
  /**
575
   * @param string $approvalPrompt Possible values for approval_prompt include:
576
   *  {@code "force"} to force the approval UI to appear.
577
   *  {@code "auto"} to request auto-approval when possible. (This is the default value)
578
   */
579
  public function setApprovalPrompt($approvalPrompt)
580
  {
581
    $this->config['approval_prompt'] = $approvalPrompt;
582
  }
583
584
  /**
585
   * Set the login hint, email address or sub id.
586
   * @param string $loginHint
587
   */
588
  public function setLoginHint($loginHint)
589
  {
590
    $this->config['login_hint'] = $loginHint;
591
  }
592
593
  /**
594
   * Set the application name, this is included in the User-Agent HTTP header.
595
   * @param string $applicationName
596
   */
597
  public function setApplicationName($applicationName)
598
  {
599
    $this->config['application_name'] = $applicationName;
600
  }
601
602
  /**
603
   * If 'plus.login' is included in the list of requested scopes, you can use
604
   * this method to define types of app activities that your app will write.
605
   * You can find a list of available types here:
606
   * @link https://developers.google.com/+/api/moment-types
607
   *
608
   * @param array $requestVisibleActions Array of app activity types
609
   */
610
  public function setRequestVisibleActions($requestVisibleActions)
611
  {
612
    if (is_array($requestVisibleActions)) {
0 ignored issues
show
introduced by
The condition is_array($requestVisibleActions) is always true.
Loading history...
613
      $requestVisibleActions = implode(" ", $requestVisibleActions);
614
    }
615
    $this->config['request_visible_actions'] = $requestVisibleActions;
616
  }
617
618
  /**
619
   * Set the developer key to use, these are obtained through the API Console.
620
   * @see http://code.google.com/apis/console-help/#generatingdevkeys
621
   * @param string $developerKey
622
   */
623
  public function setDeveloperKey($developerKey)
624
  {
625
    $this->config['developer_key'] = $developerKey;
626
  }
627
628
  /**
629
   * Set the hd (hosted domain) parameter streamlines the login process for
630
   * Google Apps hosted accounts. By including the domain of the user, you
631
   * restrict sign-in to accounts at that domain.
632
   * @param $hd string - the domain to use.
633
   */
634
  public function setHostedDomain($hd)
635
  {
636
    $this->config['hd'] = $hd;
637
  }
638
639
  /**
640
   * Set the prompt hint. Valid values are none, consent and select_account.
641
   * If no value is specified and the user has not previously authorized
642
   * access, then the user is shown a consent screen.
643
   * @param $prompt string
644
   *  {@code "none"} Do not display any authentication or consent screens. Must not be specified with other values.
645
   *  {@code "consent"} Prompt the user for consent.
646
   *  {@code "select_account"} Prompt the user to select an account.
647
   */
648
  public function setPrompt($prompt)
649
  {
650
    $this->config['prompt'] = $prompt;
651
  }
652
653
  /**
654
   * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
655
   * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
656
   * an authentication request is valid.
657
   * @param $realm string - the URL-space to use.
658
   */
659
  public function setOpenidRealm($realm)
660
  {
661
    $this->config['openid.realm'] = $realm;
662
  }
663
664
  /**
665
   * If this is provided with the value true, and the authorization request is
666
   * granted, the authorization will include any previous authorizations
667
   * granted to this user/application combination for other scopes.
668
   * @param $include boolean - the URL-space to use.
669
   */
670
  public function setIncludeGrantedScopes($include)
671
  {
672
    $this->config['include_granted_scopes'] = $include;
673
  }
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
  /**
685
   * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
686
   * token, if a token isn't provided.
687
   *
688
   * @param string|array|null $token The token (access token or a refresh token) that should be revoked.
689
   * @return boolean Returns True if the revocation was successful, otherwise False.
690
   */
691
  public function revokeToken($token = null)
692
  {
693
    $tokenRevoker = new Google_AccessToken_Revoke(
694
        $this->getHttpClient()
695
    );
696
697
    return $tokenRevoker->revokeToken($token ?: $this->getAccessToken());
698
  }
699
700
  /**
701
   * Verify an id_token. This method will verify the current id_token, if one
702
   * isn't provided.
703
   *
704
   * @throws LogicException If no token was provided and no token was set using `setAccessToken`.
705
   * @throws UnexpectedValueException If the token is not a valid JWT.
706
   * @param string|null $idToken The token (id_token) that should be verified.
707
   * @return array|false Returns the token payload as an array if the verification was
708
   * successful, false otherwise.
709
   */
710
  public function verifyIdToken($idToken = null)
711
  {
712
    $tokenVerifier = new Google_AccessToken_Verify(
713
        $this->getHttpClient(),
714
        $this->getCache(),
715
        $this->config['jwt']
716
    );
717
718
    if (null === $idToken) {
719
      $token = $this->getAccessToken();
720
      if (!isset($token['id_token'])) {
721
        throw new LogicException(
722
            'id_token must be passed in or set as part of setAccessToken'
723
        );
724
      }
725
      $idToken = $token['id_token'];
726
    }
727
728
    return $tokenVerifier->verifyIdToken(
729
        $idToken,
730
        $this->getClientId()
731
    );
732
  }
733
734
  /**
735
   * Set the scopes to be requested. Must be called before createAuthUrl().
736
   * Will remove any previously configured scopes.
737
   * @param string|array $scope_or_scopes, ie: array('https://www.googleapis.com/auth/plus.login',
738
   * 'https://www.googleapis.com/auth/moderator')
739
   */
740
  public function setScopes($scope_or_scopes)
741
  {
742
    $this->requestedScopes = array();
743
    $this->addScope($scope_or_scopes);
744
  }
745
746
  /**
747
   * This functions adds a scope to be requested as part of the OAuth2.0 flow.
748
   * Will append any scopes not previously requested to the scope parameter.
749
   * A single string will be treated as a scope to request. An array of strings
750
   * will each be appended.
751
   * @param $scope_or_scopes string|array e.g. "profile"
752
   */
753
  public function addScope($scope_or_scopes)
754
  {
755
    if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
756
      $this->requestedScopes[] = $scope_or_scopes;
757
    } else if (is_array($scope_or_scopes)) {
758
      foreach ($scope_or_scopes as $scope) {
759
        $this->addScope($scope);
760
      }
761
    }
762
  }
763
764
  /**
765
   * Returns the list of scopes requested by the client
766
   * @return array the list of scopes
767
   *
768
   */
769
  public function getScopes()
770
  {
771
     return $this->requestedScopes;
772
  }
773
774
  /**
775
   * @return string|null
776
   * @visible For Testing
777
   */
778
  public function prepareScopes()
779
  {
780
    if (empty($this->requestedScopes)) {
781
      return null;
782
    }
783
784
    return implode(' ', $this->requestedScopes);
785
  }
786
787
  /**
788
   * Helper method to execute deferred HTTP requests.
789
   *
790
   * @param $request Psr\Http\Message\RequestInterface|Google_Http_Batch
791
   * @throws Google_Exception
792
   * @return object of the type of the expected class or Psr\Http\Message\ResponseInterface.
793
   */
794
  public function execute(RequestInterface $request, $expectedClass = null)
795
  {
796
    $request = $request->withHeader(
797
        'User-Agent',
798
        $this->config['application_name']
799
        . " " . self::USER_AGENT_SUFFIX
800
        . $this->getLibraryVersion()
801
    );
802
803
    if ($this->config['api_format_v2']) {
804
        $request = $request->withHeader(
805
            'X-GOOG-API-FORMAT-VERSION',
806
            2
807
        );
808
    }
809
810
    // call the authorize method
811
    // this is where most of the grunt work is done
812
    $http = $this->authorize();
813
814
    return Google_Http_REST::execute(
0 ignored issues
show
Bug Best Practice introduced by
The expression return Google_Http_REST:...s->config['retry_map']) returns the type array which is incompatible with the documented return type object.
Loading history...
815
        $http,
816
        $request,
817
        $expectedClass,
818
        $this->config['retry'],
819
        $this->config['retry_map']
820
    );
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
  /**
837
   * Are we running in Google AppEngine?
838
   * return bool
839
   */
840
  public function isAppEngine()
841
  {
842
    return (isset($_SERVER['SERVER_SOFTWARE']) &&
843
        strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
844
  }
845
846
  public function setConfig($name, $value)
847
  {
848
    $this->config[$name] = $value;
849
  }
850
851
  public function getConfig($name, $default = null)
852
  {
853
    return isset($this->config[$name]) ? $this->config[$name] : $default;
854
  }
855
856
  /**
857
   * For backwards compatibility
858
   * alias for setAuthConfig
859
   *
860
   * @param string $file the configuration file
861
   * @throws Google_Exception
862
   * @deprecated
863
   */
864
  public function setAuthConfigFile($file)
865
  {
866
    $this->setAuthConfig($file);
867
  }
868
869
  /**
870
   * Set the auth config from new or deprecated JSON config.
871
   * This structure should match the file downloaded from
872
   * the "Download JSON" button on in the Google Developer
873
   * Console.
874
   * @param string|array $config the configuration json
875
   * @throws Google_Exception
876
   */
877
  public function setAuthConfig($config)
878
  {
879
    if (is_string($config)) {
880
      if (!file_exists($config)) {
881
        throw new InvalidArgumentException(sprintf('file "%s" does not exist', $config));
882
      }
883
884
      $json = file_get_contents($config);
885
886
      if (!$config = json_decode($json, true)) {
887
        throw new LogicException('invalid json for auth config');
888
      }
889
    }
890
891
    $key = isset($config['installed']) ? 'installed' : 'web';
892
    if (isset($config['type']) && $config['type'] == 'service_account') {
893
      // application default credentials
894
      $this->useApplicationDefaultCredentials();
895
896
      // set the information from the config
897
      $this->setClientId($config['client_id']);
898
      $this->config['client_email'] = $config['client_email'];
899
      $this->config['signing_key'] = $config['private_key'];
900
      $this->config['signing_algorithm'] = 'HS256';
901
    } elseif (isset($config[$key])) {
902
      // old-style
903
      $this->setClientId($config[$key]['client_id']);
904
      $this->setClientSecret($config[$key]['client_secret']);
905
      if (isset($config[$key]['redirect_uris'])) {
906
        $this->setRedirectUri($config[$key]['redirect_uris'][0]);
907
      }
908
    } else {
909
      // new-style
910
      $this->setClientId($config['client_id']);
911
      $this->setClientSecret($config['client_secret']);
912
      if (isset($config['redirect_uris'])) {
913
        $this->setRedirectUri($config['redirect_uris'][0]);
914
      }
915
    }
916
  }
917
918
  /**
919
   * Use when the service account has been delegated domain wide access.
920
   *
921
   * @param string $subject an email address account to impersonate
922
   */
923
  public function setSubject($subject)
924
  {
925
    $this->config['subject'] = $subject;
926
  }
927
928
  /**
929
   * Declare whether making API calls should make the call immediately, or
930
   * return a request which can be called with ->execute();
931
   *
932
   * @param boolean $defer True if calls should not be executed right away.
933
   */
934
  public function setDefer($defer)
935
  {
936
    $this->deferExecution = $defer;
937
  }
938
939
  /**
940
   * Whether or not to return raw requests
941
   * @return boolean
942
   */
943
  public function shouldDefer()
944
  {
945
    return $this->deferExecution;
946
  }
947
948
  /**
949
   * @return Google\Auth\OAuth2 implementation
950
   */
951
  public function getOAuth2Service()
952
  {
953
    if (!isset($this->auth)) {
954
      $this->auth = $this->createOAuth2Service();
955
    }
956
957
    return $this->auth;
958
  }
959
960
  /**
961
   * create a default google auth object
962
   */
963
  protected function createOAuth2Service()
964
  {
965
    $auth = new OAuth2(
966
        [
967
          'clientId'          => $this->getClientId(),
968
          'clientSecret'      => $this->getClientSecret(),
969
          'authorizationUri'   => self::OAUTH2_AUTH_URL,
970
          'tokenCredentialUri' => self::OAUTH2_TOKEN_URI,
971
          'redirectUri'       => $this->getRedirectUri(),
972
          'issuer'            => $this->config['client_id'],
973
          'signingKey'        => $this->config['signing_key'],
974
          'signingAlgorithm'  => $this->config['signing_algorithm'],
975
        ]
976
    );
977
978
    return $auth;
979
  }
980
981
  /**
982
   * Set the Cache object
983
   * @param Psr\Cache\CacheItemPoolInterface $cache
984
   */
985
  public function setCache(CacheItemPoolInterface $cache)
986
  {
987
    $this->cache = $cache;
988
  }
989
990
  /**
991
   * @return Psr\Cache\CacheItemPoolInterface Cache implementation
992
   */
993
  public function getCache()
994
  {
995
    if (!$this->cache) {
996
      $this->cache = $this->createDefaultCache();
997
    }
998
999
    return $this->cache;
1000
  }
1001
1002
  /**
1003
   * @param array $cacheConfig
1004
   */
1005
  public function setCacheConfig(array $cacheConfig)
1006
  {
1007
    $this->config['cache_config'] = $cacheConfig;
1008
  }
1009
1010
  /**
1011
   * Set the Logger object
1012
   * @param Psr\Log\LoggerInterface $logger
1013
   */
1014
  public function setLogger(LoggerInterface $logger)
1015
  {
1016
    $this->logger = $logger;
1017
  }
1018
1019
  /**
1020
   * @return Psr\Log\LoggerInterface implementation
1021
   */
1022
  public function getLogger()
1023
  {
1024
    if (!isset($this->logger)) {
1025
      $this->logger = $this->createDefaultLogger();
1026
    }
1027
1028
    return $this->logger;
1029
  }
1030
1031
  protected function createDefaultLogger()
1032
  {
1033
    $logger = new Logger('google-api-php-client');
1034
    if ($this->isAppEngine()) {
1035
      $handler = new MonologSyslogHandler('app', LOG_USER, Logger::NOTICE);
1036
    } else {
1037
      $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE);
1038
    }
1039
    $logger->pushHandler($handler);
1040
1041
    return $logger;
1042
  }
1043
1044
  protected function createDefaultCache()
1045
  {
1046
    return new MemoryCacheItemPool;
1047
  }
1048
1049
  /**
1050
   * Set the Http Client object
1051
   * @param GuzzleHttp\ClientInterface $http
1052
   */
1053
  public function setHttpClient(ClientInterface $http)
1054
  {
1055
    $this->http = $http;
1056
  }
1057
1058
  /**
1059
   * @return GuzzleHttp\ClientInterface implementation
1060
   */
1061
  public function getHttpClient()
1062
  {
1063
    if (null === $this->http) {
1064
      $this->http = $this->createDefaultHttpClient();
1065
    }
1066
1067
    return $this->http;
1068
  }
1069
1070
  /**
1071
   * Set the API format version.
1072
   *
1073
   * `true` will use V2, which may return more useful error messages.
1074
   *
1075
   * @param bool $value
1076
   */
1077
  public function setApiFormatV2($value)
1078
  {
1079
    $this->config['api_format_v2'] = (bool) $value;
1080
  }
1081
1082
  protected function createDefaultHttpClient()
1083
  {
1084
    $options = ['exceptions' => false];
1085
1086
    $version = ClientInterface::VERSION;
0 ignored issues
show
Deprecated Code introduced by
The constant GuzzleHttp\ClientInterface::VERSION has been deprecated: Will be removed in Guzzle 7.0.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1086
    $version = /** @scrutinizer ignore-deprecated */ ClientInterface::VERSION;

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1087
    if ('5' === $version[0]) {
1088
      $options = [
1089
        'base_url' => $this->config['base_path'],
1090
        'defaults' => $options,
1091
      ];
1092
      if ($this->isAppEngine()) {
1093
        // set StreamHandler on AppEngine by default
1094
        $options['handler']  = new StreamHandler();
1095
        $options['defaults']['verify'] = '/etc/ca-certificates.crt';
1096
      }
1097
    } else {
1098
      // guzzle 6
1099
      $options['base_uri'] = $this->config['base_path'];
1100
    }
1101
1102
    return new Client($options);
1103
  }
1104
1105
  private function createApplicationDefaultCredentials()
1106
  {
1107
    $scopes = $this->prepareScopes();
1108
    $sub = $this->config['subject'];
1109
    $signingKey = $this->config['signing_key'];
1110
1111
    // create credentials using values supplied in setAuthConfig
1112
    if ($signingKey) {
1113
      $serviceAccountCredentials = array(
1114
        'client_id' => $this->config['client_id'],
1115
        'client_email' => $this->config['client_email'],
1116
        'private_key' => $signingKey,
1117
        'type' => 'service_account',
1118
      );
1119
      $credentials = CredentialsLoader::makeCredentials($scopes, $serviceAccountCredentials);
1120
    } else {
1121
      $credentials = ApplicationDefaultCredentials::getCredentials($scopes);
1122
    }
1123
1124
    // for service account domain-wide authority (impersonating a user)
1125
    // @see https://developers.google.com/identity/protocols/OAuth2ServiceAccount
1126
    if ($sub) {
1127
      if (!$credentials instanceof ServiceAccountCredentials) {
1128
        throw new DomainException('domain-wide authority requires service account credentials');
1129
      }
1130
1131
      $credentials->setSub($sub);
1132
    }
1133
1134
    return $credentials;
1135
  }
1136
1137
  protected function getAuthHandler()
1138
  {
1139
    // Be very careful using the cache, as the underlying auth library's cache
1140
    // implementation is naive, and the cache keys do not account for user
1141
    // sessions.
1142
    //
1143
    // @see https://github.com/google/google-api-php-client/issues/821
1144
    return Google_AuthHandler_AuthHandlerFactory::build(
1145
        $this->getCache(),
1146
        $this->config['cache_config']
1147
    );
1148
  }
1149
1150
  private function createUserRefreshCredentials($scope, $refreshToken)
1151
  {
1152
    $creds = array_filter(
1153
        array(
1154
          'client_id' => $this->getClientId(),
1155
          'client_secret' => $this->getClientSecret(),
1156
          'refresh_token' => $refreshToken,
1157
        )
1158
    );
1159
1160
    return new UserRefreshCredentials($scope, $creds);
1161
  }
1162
}
1163