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