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