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
18if (!class_exists('Google_Client')) {
19  require_once dirname(__FILE__) . '/autoload.php';
20}
21
22/**
23 * The Google API Client
24 * https://github.com/google/google-api-php-client
25 */
26class Google_Client
27{
28  const LIBVER = "1.1.5";
29  const USER_AGENT_SUFFIX = "google-api-php-client/";
30  /**
31   * @var Google_Auth_Abstract $auth
32   */
33  private $auth;
34
35  /**
36   * @var Google_IO_Abstract $io
37   */
38  private $io;
39
40  /**
41   * @var Google_Cache_Abstract $cache
42   */
43  private $cache;
44
45  /**
46   * @var Google_Config $config
47   */
48  private $config;
49
50  /**
51   * @var Google_Logger_Abstract $logger
52   */
53  private $logger;
54
55  /**
56   * @var boolean $deferExecution
57   */
58  private $deferExecution = false;
59
60  /** @var array $scopes */
61  // Scopes requested by the client
62  protected $requestedScopes = array();
63
64  // definitions of services that are discovered.
65  protected $services = array();
66
67  // Used to track authenticated state, can't discover services after doing authenticate()
68  private $authenticated = false;
69
70  /**
71   * Construct the Google Client.
72   *
73   * @param $config Google_Config or string for the ini file to load
74   */
75  public function __construct($config = null)
76  {
77    if (is_string($config) && strlen($config)) {
78      $config = new Google_Config($config);
79    } else if ( !($config instanceof Google_Config)) {
80      $config = new Google_Config();
81
82      if ($this->isAppEngine()) {
83        // Automatically use Memcache if we're in AppEngine.
84        $config->setCacheClass('Google_Cache_Memcache');
85      }
86
87      if (version_compare(phpversion(), "5.3.4", "<=") || $this->isAppEngine()) {
88        // Automatically disable compress.zlib, as currently unsupported.
89        $config->setClassConfig('Google_Http_Request', 'disable_gzip', true);
90      }
91    }
92
93    if ($config->getIoClass() == Google_Config::USE_AUTO_IO_SELECTION) {
94      if (function_exists('curl_version') && function_exists('curl_exec')
95          && !$this->isAppEngine()) {
96        $config->setIoClass("Google_IO_Curl");
97      } else {
98        $config->setIoClass("Google_IO_Stream");
99      }
100    }
101
102    $this->config = $config;
103  }
104
105  /**
106   * Get a string containing the version of the library.
107   *
108   * @return string
109   */
110  public function getLibraryVersion()
111  {
112    return self::LIBVER;
113  }
114
115  /**
116   * Attempt to exchange a code for an valid authentication token.
117   * If $crossClient is set to true, the request body will not include
118   * the request_uri argument
119   * Helper wrapped around the OAuth 2.0 implementation.
120   *
121   * @param $code string code from accounts.google.com
122   * @param $crossClient boolean, whether this is a cross-client authentication
123   * @return string token
124   */
125  public function authenticate($code, $crossClient = false)
126  {
127    $this->authenticated = true;
128    return $this->getAuth()->authenticate($code, $crossClient);
129  }
130
131  /**
132   * Loads a service account key and parameters from a JSON
133   * file from the Google Developer Console. Uses that and the
134   * given array of scopes to return an assertion credential for
135   * use with refreshTokenWithAssertionCredential.
136   *
137   * @param string $jsonLocation File location of the project-key.json.
138   * @param array $scopes The scopes to assert.
139   * @return Google_Auth_AssertionCredentials.
140   * @
141   */
142  public function loadServiceAccountJson($jsonLocation, $scopes)
143  {
144    $data = json_decode(file_get_contents($jsonLocation));
145    if (isset($data->type) && $data->type == 'service_account') {
146      // Service Account format.
147      $cred = new Google_Auth_AssertionCredentials(
148          $data->client_email,
149          $scopes,
150          $data->private_key
151      );
152      return $cred;
153    } else {
154      throw new Google_Exception("Invalid service account JSON file.");
155    }
156  }
157
158  /**
159   * Set the auth config from the JSON string provided.
160   * This structure should match the file downloaded from
161   * the "Download JSON" button on in the Google Developer
162   * Console.
163   * @param string $json the configuration json
164   * @throws Google_Exception
165   */
166  public function setAuthConfig($json)
167  {
168    $data = json_decode($json);
169    $key = isset($data->installed) ? 'installed' : 'web';
170    if (!isset($data->$key)) {
171      throw new Google_Exception("Invalid client secret JSON file.");
172    }
173    $this->setClientId($data->$key->client_id);
174    $this->setClientSecret($data->$key->client_secret);
175    if (isset($data->$key->redirect_uris)) {
176      $this->setRedirectUri($data->$key->redirect_uris[0]);
177    }
178  }
179
180  /**
181   * Set the auth config from the JSON file in the path
182   * provided. This should match the file downloaded from
183   * the "Download JSON" button on in the Google Developer
184   * Console.
185   * @param string $file the file location of the client json
186   */
187  public function setAuthConfigFile($file)
188  {
189    $this->setAuthConfig(file_get_contents($file));
190  }
191
192  /**
193   * @throws Google_Auth_Exception
194   * @return array
195   * @visible For Testing
196   */
197  public function prepareScopes()
198  {
199    if (empty($this->requestedScopes)) {
200      throw new Google_Auth_Exception("No scopes specified");
201    }
202    $scopes = implode(' ', $this->requestedScopes);
203    return $scopes;
204  }
205
206  /**
207   * Set the OAuth 2.0 access token using the string that resulted from calling createAuthUrl()
208   * or Google_Client#getAccessToken().
209   * @param string $accessToken JSON encoded string containing in the following format:
210   * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
211   *  "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
212   */
213  public function setAccessToken($accessToken)
214  {
215    if ($accessToken == 'null') {
216      $accessToken = null;
217    }
218    $this->getAuth()->setAccessToken($accessToken);
219  }
220
221
222
223  /**
224   * Set the authenticator object
225   * @param Google_Auth_Abstract $auth
226   */
227  public function setAuth(Google_Auth_Abstract $auth)
228  {
229    $this->config->setAuthClass(get_class($auth));
230    $this->auth = $auth;
231  }
232
233  /**
234   * Set the IO object
235   * @param Google_IO_Abstract $io
236   */
237  public function setIo(Google_IO_Abstract $io)
238  {
239    $this->config->setIoClass(get_class($io));
240    $this->io = $io;
241  }
242
243  /**
244   * Set the Cache object
245   * @param Google_Cache_Abstract $cache
246   */
247  public function setCache(Google_Cache_Abstract $cache)
248  {
249    $this->config->setCacheClass(get_class($cache));
250    $this->cache = $cache;
251  }
252
253  /**
254   * Set the Logger object
255   * @param Google_Logger_Abstract $logger
256   */
257  public function setLogger(Google_Logger_Abstract $logger)
258  {
259    $this->config->setLoggerClass(get_class($logger));
260    $this->logger = $logger;
261  }
262
263  /**
264   * Construct the OAuth 2.0 authorization request URI.
265   * @return string
266   */
267  public function createAuthUrl()
268  {
269    $scopes = $this->prepareScopes();
270    return $this->getAuth()->createAuthUrl($scopes);
271  }
272
273  /**
274   * Get the OAuth 2.0 access token.
275   * @return string $accessToken JSON encoded string in the following format:
276   * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
277   *  "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
278   */
279  public function getAccessToken()
280  {
281    $token = $this->getAuth()->getAccessToken();
282    // The response is json encoded, so could be the string null.
283    // It is arguable whether this check should be here or lower
284    // in the library.
285    return (null == $token || 'null' == $token || '[]' == $token) ? null : $token;
286  }
287
288  /**
289   * Get the OAuth 2.0 refresh token.
290   * @return string $refreshToken refresh token or null if not available
291   */
292  public function getRefreshToken()
293  {
294    return $this->getAuth()->getRefreshToken();
295  }
296
297  /**
298   * Returns if the access_token is expired.
299   * @return bool Returns True if the access_token is expired.
300   */
301  public function isAccessTokenExpired()
302  {
303    return $this->getAuth()->isAccessTokenExpired();
304  }
305
306  /**
307   * Set OAuth 2.0 "state" parameter to achieve per-request customization.
308   * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
309   * @param string $state
310   */
311  public function setState($state)
312  {
313    $this->getAuth()->setState($state);
314  }
315
316  /**
317   * @param string $accessType Possible values for access_type include:
318   *  {@code "offline"} to request offline access from the user.
319   *  {@code "online"} to request online access from the user.
320   */
321  public function setAccessType($accessType)
322  {
323    $this->config->setAccessType($accessType);
324  }
325
326  /**
327   * @param string $approvalPrompt Possible values for approval_prompt include:
328   *  {@code "force"} to force the approval UI to appear. (This is the default value)
329   *  {@code "auto"} to request auto-approval when possible.
330   */
331  public function setApprovalPrompt($approvalPrompt)
332  {
333    $this->config->setApprovalPrompt($approvalPrompt);
334  }
335
336  /**
337   * Set the login hint, email address or sub id.
338   * @param string $loginHint
339   */
340  public function setLoginHint($loginHint)
341  {
342      $this->config->setLoginHint($loginHint);
343  }
344
345  /**
346   * Set the application name, this is included in the User-Agent HTTP header.
347   * @param string $applicationName
348   */
349  public function setApplicationName($applicationName)
350  {
351    $this->config->setApplicationName($applicationName);
352  }
353
354  /**
355   * Set the OAuth 2.0 Client ID.
356   * @param string $clientId
357   */
358  public function setClientId($clientId)
359  {
360    $this->config->setClientId($clientId);
361  }
362
363  /**
364   * Set the OAuth 2.0 Client Secret.
365   * @param string $clientSecret
366   */
367  public function setClientSecret($clientSecret)
368  {
369    $this->config->setClientSecret($clientSecret);
370  }
371
372  /**
373   * Set the OAuth 2.0 Redirect URI.
374   * @param string $redirectUri
375   */
376  public function setRedirectUri($redirectUri)
377  {
378    $this->config->setRedirectUri($redirectUri);
379  }
380
381  /**
382   * If 'plus.login' is included in the list of requested scopes, you can use
383   * this method to define types of app activities that your app will write.
384   * You can find a list of available types here:
385   * @link https://developers.google.com/+/api/moment-types
386   *
387   * @param array $requestVisibleActions Array of app activity types
388   */
389  public function setRequestVisibleActions($requestVisibleActions)
390  {
391    if (is_array($requestVisibleActions)) {
392      $requestVisibleActions = join(" ", $requestVisibleActions);
393    }
394    $this->config->setRequestVisibleActions($requestVisibleActions);
395  }
396
397  /**
398   * Set the developer key to use, these are obtained through the API Console.
399   * @see http://code.google.com/apis/console-help/#generatingdevkeys
400   * @param string $developerKey
401   */
402  public function setDeveloperKey($developerKey)
403  {
404    $this->config->setDeveloperKey($developerKey);
405  }
406
407  /**
408   * Set the hd (hosted domain) parameter streamlines the login process for
409   * Google Apps hosted accounts. By including the domain of the user, you
410   * restrict sign-in to accounts at that domain.
411   * @param $hd string - the domain to use.
412   */
413  public function setHostedDomain($hd)
414  {
415    $this->config->setHostedDomain($hd);
416  }
417
418  /**
419   * Set the prompt hint. Valid values are none, consent and select_account.
420   * If no value is specified and the user has not previously authorized
421   * access, then the user is shown a consent screen.
422   * @param $prompt string
423   */
424  public function setPrompt($prompt)
425  {
426    $this->config->setPrompt($prompt);
427  }
428
429  /**
430   * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
431   * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
432   * an authentication request is valid.
433   * @param $realm string - the URL-space to use.
434   */
435  public function setOpenidRealm($realm)
436  {
437    $this->config->setOpenidRealm($realm);
438  }
439
440  /**
441   * If this is provided with the value true, and the authorization request is
442   * granted, the authorization will include any previous authorizations
443   * granted to this user/application combination for other scopes.
444   * @param $include boolean - the URL-space to use.
445   */
446  public function setIncludeGrantedScopes($include)
447  {
448    $this->config->setIncludeGrantedScopes($include);
449  }
450
451  /**
452   * Fetches a fresh OAuth 2.0 access token with the given refresh token.
453   * @param string $refreshToken
454   */
455  public function refreshToken($refreshToken)
456  {
457    $this->getAuth()->refreshToken($refreshToken);
458  }
459
460  /**
461   * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
462   * token, if a token isn't provided.
463   * @throws Google_Auth_Exception
464   * @param string|null $token The token (access token or a refresh token) that should be revoked.
465   * @return boolean Returns True if the revocation was successful, otherwise False.
466   */
467  public function revokeToken($token = null)
468  {
469    return $this->getAuth()->revokeToken($token);
470  }
471
472  /**
473   * Verify an id_token. This method will verify the current id_token, if one
474   * isn't provided.
475   * @throws Google_Auth_Exception
476   * @param string|null $token The token (id_token) that should be verified.
477   * @return Google_Auth_LoginTicket Returns an apiLoginTicket if the verification was
478   * successful.
479   */
480  public function verifyIdToken($token = null)
481  {
482    return $this->getAuth()->verifyIdToken($token);
483  }
484
485  /**
486   * Verify a JWT that was signed with your own certificates.
487   *
488   * @param $id_token string The JWT token
489   * @param $cert_location array of certificates
490   * @param $audience string the expected consumer of the token
491   * @param $issuer string the expected issuer, defaults to Google
492   * @param [$max_expiry] the max lifetime of a token, defaults to MAX_TOKEN_LIFETIME_SECS
493   * @return mixed token information if valid, false if not
494   */
495  public function verifySignedJwt($id_token, $cert_location, $audience, $issuer, $max_expiry = null)
496  {
497    $auth = new Google_Auth_OAuth2($this);
498    $certs = $auth->retrieveCertsFromLocation($cert_location);
499    return $auth->verifySignedJwtWithCerts($id_token, $certs, $audience, $issuer, $max_expiry);
500  }
501
502  /**
503   * @param $creds Google_Auth_AssertionCredentials
504   */
505  public function setAssertionCredentials(Google_Auth_AssertionCredentials $creds)
506  {
507    $this->getAuth()->setAssertionCredentials($creds);
508  }
509
510  /**
511   * Set the scopes to be requested. Must be called before createAuthUrl().
512   * Will remove any previously configured scopes.
513   * @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.login',
514   * 'https://www.googleapis.com/auth/moderator')
515   */
516  public function setScopes($scopes)
517  {
518    $this->requestedScopes = array();
519    $this->addScope($scopes);
520  }
521
522  /**
523   * This functions adds a scope to be requested as part of the OAuth2.0 flow.
524   * Will append any scopes not previously requested to the scope parameter.
525   * A single string will be treated as a scope to request. An array of strings
526   * will each be appended.
527   * @param $scope_or_scopes string|array e.g. "profile"
528   */
529  public function addScope($scope_or_scopes)
530  {
531    if (is_string($scope_or_scopes) && !in_array($scope_or_scopes, $this->requestedScopes)) {
532      $this->requestedScopes[] = $scope_or_scopes;
533    } else if (is_array($scope_or_scopes)) {
534      foreach ($scope_or_scopes as $scope) {
535        $this->addScope($scope);
536      }
537    }
538  }
539
540  /**
541   * Returns the list of scopes requested by the client
542   * @return array the list of scopes
543   *
544   */
545  public function getScopes()
546  {
547     return $this->requestedScopes;
548  }
549
550  /**
551   * Declare whether batch calls should be used. This may increase throughput
552   * by making multiple requests in one connection.
553   *
554   * @param boolean $useBatch True if the batch support should
555   * be enabled. Defaults to False.
556   */
557  public function setUseBatch($useBatch)
558  {
559    // This is actually an alias for setDefer.
560    $this->setDefer($useBatch);
561  }
562
563  /**
564   * Declare whether making API calls should make the call immediately, or
565   * return a request which can be called with ->execute();
566   *
567   * @param boolean $defer True if calls should not be executed right away.
568   */
569  public function setDefer($defer)
570  {
571    $this->deferExecution = $defer;
572  }
573
574  /**
575   * Helper method to execute deferred HTTP requests.
576   *
577   * @param $request Google_Http_Request|Google_Http_Batch
578   * @throws Google_Exception
579   * @return object of the type of the expected class or array.
580   */
581  public function execute($request)
582  {
583    if ($request instanceof Google_Http_Request) {
584      $request->setUserAgent(
585          $this->getApplicationName()
586          . " " . self::USER_AGENT_SUFFIX
587          . $this->getLibraryVersion()
588      );
589      if (!$this->getClassConfig("Google_Http_Request", "disable_gzip")) {
590        $request->enableGzip();
591      }
592      $request->maybeMoveParametersToBody();
593      return Google_Http_REST::execute($this, $request);
594    } else if ($request instanceof Google_Http_Batch) {
595      return $request->execute();
596    } else {
597      throw new Google_Exception("Do not know how to execute this type of object.");
598    }
599  }
600
601  /**
602   * Whether or not to return raw requests
603   * @return boolean
604   */
605  public function shouldDefer()
606  {
607    return $this->deferExecution;
608  }
609
610  /**
611   * @return Google_Auth_Abstract Authentication implementation
612   */
613  public function getAuth()
614  {
615    if (!isset($this->auth)) {
616      $class = $this->config->getAuthClass();
617      $this->auth = new $class($this);
618    }
619    return $this->auth;
620  }
621
622  /**
623   * @return Google_IO_Abstract IO implementation
624   */
625  public function getIo()
626  {
627    if (!isset($this->io)) {
628      $class = $this->config->getIoClass();
629      $this->io = new $class($this);
630    }
631    return $this->io;
632  }
633
634  /**
635   * @return Google_Cache_Abstract Cache implementation
636   */
637  public function getCache()
638  {
639    if (!isset($this->cache)) {
640      $class = $this->config->getCacheClass();
641      $this->cache = new $class($this);
642    }
643    return $this->cache;
644  }
645
646  /**
647   * @return Google_Logger_Abstract Logger implementation
648   */
649  public function getLogger()
650  {
651    if (!isset($this->logger)) {
652      $class = $this->config->getLoggerClass();
653      $this->logger = new $class($this);
654    }
655    return $this->logger;
656  }
657
658  /**
659   * Retrieve custom configuration for a specific class.
660   * @param $class string|object - class or instance of class to retrieve
661   * @param $key string optional - key to retrieve
662   * @return array
663   */
664  public function getClassConfig($class, $key = null)
665  {
666    if (!is_string($class)) {
667      $class = get_class($class);
668    }
669    return $this->config->getClassConfig($class, $key);
670  }
671
672  /**
673   * Set configuration specific to a given class.
674   * $config->setClassConfig('Google_Cache_File',
675   *   array('directory' => '/tmp/cache'));
676   * @param $class string|object - The class name for the configuration
677   * @param $config string key or an array of configuration values
678   * @param $value string optional - if $config is a key, the value
679   *
680   */
681  public function setClassConfig($class, $config, $value = null)
682  {
683    if (!is_string($class)) {
684      $class = get_class($class);
685    }
686    $this->config->setClassConfig($class, $config, $value);
687
688  }
689
690  /**
691   * @return string the base URL to use for calls to the APIs
692   */
693  public function getBasePath()
694  {
695    return $this->config->getBasePath();
696  }
697
698  /**
699   * @return string the name of the application
700   */
701  public function getApplicationName()
702  {
703    return $this->config->getApplicationName();
704  }
705
706  /**
707   * Are we running in Google AppEngine?
708   * return bool
709   */
710  public function isAppEngine()
711  {
712    return (isset($_SERVER['SERVER_SOFTWARE']) &&
713        strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== false);
714  }
715}
716