Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

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