Google_Client   F
last analyzed

Complexity

Total Complexity 135

Size/Duplication

Total Lines 1122
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 339
c 1
b 0
f 1
dl 0
loc 1122
rs 2
wmc 135

68 Methods

Rating   Name   Duplication   Size   Complexity  
A setDefer() 0 3 1
A getHttpClient() 0 7 2
A getScopes() 0 3 1
A getLogger() 0 7 2
A setRequestVisibleActions() 0 6 2
A setApplicationName() 0 3 1
A revokeToken() 0 7 2
A useApplicationDefaultCredentials() 0 3 1
A addScope() 0 7 5
A createDefaultCache() 0 3 1
A setCache() 0 3 1
A getClientSecret() 0 3 1
A setAccessToken() 0 19 5
A verifyIdToken() 0 21 3
A fetchAccessTokenWithAssertion() 0 27 4
A createDefaultLogger() 0 11 2
A isUsingApplicationDefaultCredentials() 0 3 1
A isAppEngine() 0 4 2
A setOpenidRealm() 0 3 1
A setConfig() 0 3 1
A setHostedDomain() 0 3 1
A setCacheConfig() 0 3 1
A refreshTokenWithAssertion() 0 3 1
A setScopes() 0 4 1
A fetchAccessTokenWithRefreshToken() 0 25 6
A createApplicationDefaultCredentials() 0 30 4
A createDefaultHttpClient() 0 21 3
A getClientId() 0 3 1
A getOAuth2Service() 0 7 2
B authorize() 0 39 9
A setClientId() 0 3 1
A setAuthConfigFile() 0 3 1
A setAccessType() 0 3 1
A authenticate() 0 3 1
A setLoginHint() 0 3 1
B setAuthConfig() 0 37 10
A setTokenCallback() 0 3 1
A setHttpClient() 0 3 1
A setPrompt() 0 3 1
A setIncludeGrantedScopes() 0 3 1
A fetchAccessTokenWithAuthCode() 0 18 4
A setLogger() 0 3 1
A prepareScopes() 0 7 2
A setRedirectUri() 0 3 1
A execute() 0 26 2
B isAccessTokenExpired() 0 26 7
A getAuth() 0 4 1
A setApprovalPrompt() 0 3 1
A refreshToken() 0 3 1
A createOAuth2Service() 0 16 1
A getLibraryVersion() 0 3 1
A getConfig() 0 3 2
A getRedirectUri() 0 3 1
A shouldDefer() 0 3 1
A getAccessToken() 0 3 1
A setApiFormatV2() 0 3 1
A setUseBatch() 0 4 1
A setAuth() 0 4 1
A setDeveloperKey() 0 3 1
A createUserRefreshCredentials() 0 11 1
A setSubject() 0 3 1
A getCache() 0 7 2
A getAuthHandler() 0 10 1
B createAuthUrl() 0 44 7
A __construct() 0 59 1
A setClientSecret() 0 3 1
A setState() 0 3 1
A getRefreshToken() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like Google_Client often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Google_Client, and based on these observations, apply Extract Interface, too.

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
Bug introduced by
The constant GuzzleHttp\ClientInterface::VERSION was not found. Maybe you did not declare it correctly or list all dependencies?
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