[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/ -> oauthlib.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  
  18  defined('MOODLE_INTERNAL') || die();
  19  
  20  require_once($CFG->libdir.'/filelib.php');
  21  
  22  /**
  23   * OAuth helper class
  24   *
  25   * 1. You can extends oauth_helper to add specific functions, such as twitter extends oauth_helper
  26   * 2. Call request_token method to get oauth_token and oauth_token_secret, and redirect user to authorize_url,
  27   *    developer needs to store oauth_token and oauth_token_secret somewhere, we will use them to request
  28   *    access token later on
  29   * 3. User approved the request, and get back to moodle
  30   * 4. Call get_access_token, it takes previous oauth_token and oauth_token_secret as arguments, oauth_token
  31   *    will be used in OAuth request, oauth_token_secret will be used to bulid signature, this method will
  32   *    return access_token and access_secret, store these two values in database or session
  33   * 5. Now you can access oauth protected resources by access_token and access_secret using oauth_helper::request
  34   *    method (or get() post())
  35   *
  36   * Note:
  37   * 1. This class only support HMAC-SHA1
  38   * 2. oauth_helper class don't store tokens and secrets, you must store them manually
  39   * 3. Some functions are based on http://code.google.com/p/oauth/
  40   *
  41   * @package    moodlecore
  42   * @copyright  2010 Dongsheng Cai <dongsheng@moodle.com>
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  
  46  class oauth_helper {
  47      /** @var string consumer key, issued by oauth provider*/
  48      protected $consumer_key;
  49      /** @var string consumer secret, issued by oauth provider*/
  50      protected $consumer_secret;
  51      /** @var string oauth root*/
  52      protected $api_root;
  53      /** @var string request token url*/
  54      protected $request_token_api;
  55      /** @var string authorize url*/
  56      protected $authorize_url;
  57      protected $http_method;
  58      /** @var string */
  59      protected $access_token_api;
  60      /** @var curl */
  61      protected $http;
  62      /** @var array options to pass to the next curl request */
  63      protected $http_options;
  64  
  65      /**
  66       * Contructor for oauth_helper.
  67       * Subclass can override construct to build its own $this->http
  68       *
  69       * @param array $args requires at least three keys, oauth_consumer_key
  70       *                    oauth_consumer_secret and api_root, oauth_helper will
  71       *                    guess request_token_api, authrize_url and access_token_api
  72       *                    based on api_root, but it not always works
  73       */
  74      function __construct($args) {
  75          if (!empty($args['api_root'])) {
  76              $this->api_root = $args['api_root'];
  77          } else {
  78              $this->api_root = '';
  79          }
  80          $this->consumer_key = $args['oauth_consumer_key'];
  81          $this->consumer_secret = $args['oauth_consumer_secret'];
  82  
  83          if (empty($args['request_token_api'])) {
  84              $this->request_token_api = $this->api_root . '/request_token';
  85          } else {
  86              $this->request_token_api = $args['request_token_api'];
  87          }
  88  
  89          if (empty($args['authorize_url'])) {
  90              $this->authorize_url = $this->api_root . '/authorize';
  91          } else {
  92              $this->authorize_url = $args['authorize_url'];
  93          }
  94  
  95          if (empty($args['access_token_api'])) {
  96              $this->access_token_api = $this->api_root . '/access_token';
  97          } else {
  98              $this->access_token_api = $args['access_token_api'];
  99          }
 100  
 101          if (!empty($args['oauth_callback'])) {
 102              $this->oauth_callback = new moodle_url($args['oauth_callback']);
 103          }
 104          if (!empty($args['access_token'])) {
 105              $this->access_token = $args['access_token'];
 106          }
 107          if (!empty($args['access_token_secret'])) {
 108              $this->access_token_secret = $args['access_token_secret'];
 109          }
 110          $this->http = new curl(array('debug'=>false));
 111          $this->http_options = array();
 112      }
 113  
 114      /**
 115       * Build parameters list:
 116       *    oauth_consumer_key="0685bd9184jfhq22",
 117       *    oauth_nonce="4572616e48616d6d65724c61686176",
 118       *    oauth_token="ad180jjd733klru7",
 119       *    oauth_signature_method="HMAC-SHA1",
 120       *    oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
 121       *    oauth_timestamp="137131200",
 122       *    oauth_version="1.0"
 123       *    oauth_verifier="1.0"
 124       * @param array $param
 125       * @return string
 126       */
 127      function get_signable_parameters($params){
 128          $sorted = $params;
 129          ksort($sorted);
 130  
 131          $total = array();
 132          foreach ($sorted as $k => $v) {
 133              if ($k == 'oauth_signature') {
 134                  continue;
 135              }
 136  
 137              $total[] = rawurlencode($k) . '=' . rawurlencode($v);
 138          }
 139          return implode('&', $total);
 140      }
 141  
 142      /**
 143       * Create signature for oauth request
 144       * @param string $url
 145       * @param string $secret
 146       * @param array $params
 147       * @return string
 148       */
 149      public function sign($http_method, $url, $params, $secret) {
 150          $sig = array(
 151              strtoupper($http_method),
 152              preg_replace('/%7E/', '~', rawurlencode($url)),
 153              rawurlencode($this->get_signable_parameters($params)),
 154          );
 155  
 156          $base_string = implode('&', $sig);
 157          $sig = base64_encode(hash_hmac('sha1', $base_string, $secret, true));
 158          return $sig;
 159      }
 160  
 161      /**
 162       * Initilize oauth request parameters, including:
 163       *    oauth_consumer_key="0685bd9184jfhq22",
 164       *    oauth_token="ad180jjd733klru7",
 165       *    oauth_signature_method="HMAC-SHA1",
 166       *    oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
 167       *    oauth_timestamp="137131200",
 168       *    oauth_nonce="4572616e48616d6d65724c61686176",
 169       *    oauth_version="1.0"
 170       * To access protected resources, oauth_token should be defined
 171       *
 172       * @param string $url
 173       * @param string $token
 174       * @param string $http_method
 175       * @return array
 176       */
 177      public function prepare_oauth_parameters($url, $params, $http_method = 'POST') {
 178          if (is_array($params)) {
 179              $oauth_params = $params;
 180          } else {
 181              $oauth_params = array();
 182          }
 183          $oauth_params['oauth_version']        = '1.0';
 184          $oauth_params['oauth_nonce']        = $this->get_nonce();
 185          $oauth_params['oauth_timestamp']    = $this->get_timestamp();
 186          $oauth_params['oauth_consumer_key'] = $this->consumer_key;
 187          if (!empty($this->oauth_callback)) {
 188              $oauth_params['oauth_callback'] = $this->oauth_callback->out(false);
 189          }
 190          $oauth_params['oauth_signature_method']    = 'HMAC-SHA1';
 191          $oauth_params['oauth_signature']    = $this->sign($http_method, $url, $oauth_params, $this->sign_secret);
 192          return $oauth_params;
 193      }
 194  
 195      public function setup_oauth_http_header($params) {
 196  
 197          $total = array();
 198          ksort($params);
 199          foreach ($params as $k => $v) {
 200              $total[] = rawurlencode($k) . '="' . rawurlencode($v).'"';
 201          }
 202          $str = implode(', ', $total);
 203          $str = 'Authorization: OAuth '.$str;
 204          $this->http->setHeader('Expect:');
 205          $this->http->setHeader($str);
 206      }
 207  
 208      /**
 209       * Sets the options for the next curl request
 210       *
 211       * @param array $options
 212       */
 213      public function setup_oauth_http_options($options) {
 214          $this->http_options = $options;
 215      }
 216  
 217      /**
 218       * Request token for authentication
 219       * This is the first step to use OAuth, it will return oauth_token and oauth_token_secret
 220       * @return array
 221       */
 222      public function request_token() {
 223          $this->sign_secret = $this->consumer_secret.'&';
 224          $params = $this->prepare_oauth_parameters($this->request_token_api, array(), 'GET');
 225          $content = $this->http->get($this->request_token_api, $params, $this->http_options);
 226          // Including:
 227          //     oauth_token
 228          //     oauth_token_secret
 229          $result = $this->parse_result($content);
 230          if (empty($result['oauth_token'])) {
 231              throw new moodle_exception('Error while requesting an oauth token');
 232          }
 233          // build oauth authrize url
 234          if (!empty($this->oauth_callback)) {
 235              // url must be rawurlencode
 236              $result['authorize_url'] = $this->authorize_url . '?oauth_token='.$result['oauth_token'].'&oauth_callback='.rawurlencode($this->oauth_callback->out(false));
 237          } else {
 238              // no callback
 239              $result['authorize_url'] = $this->authorize_url . '?oauth_token='.$result['oauth_token'];
 240          }
 241          return $result;
 242      }
 243  
 244      /**
 245       * Set oauth access token for oauth request
 246       * @param string $token
 247       * @param string $secret
 248       */
 249      public function set_access_token($token, $secret) {
 250          $this->access_token = $token;
 251          $this->access_token_secret = $secret;
 252      }
 253  
 254      /**
 255       * Request oauth access token from server
 256       * @param string $method
 257       * @param string $url
 258       * @param string $token
 259       * @param string $secret
 260       */
 261      public function get_access_token($token, $secret, $verifier='') {
 262          $this->sign_secret = $this->consumer_secret.'&'.$secret;
 263          $params = $this->prepare_oauth_parameters($this->access_token_api, array('oauth_token'=>$token, 'oauth_verifier'=>$verifier), 'POST');
 264          $this->setup_oauth_http_header($params);
 265          // Should never send the callback in this request.
 266          unset($params['oauth_callback']);
 267          $content = $this->http->post($this->access_token_api, $params, $this->http_options);
 268          $keys = $this->parse_result($content);
 269          $this->set_access_token($keys['oauth_token'], $keys['oauth_token_secret']);
 270          return $keys;
 271      }
 272  
 273      /**
 274       * Request oauth protected resources
 275       * @param string $method
 276       * @param string $url
 277       * @param string $token
 278       * @param string $secret
 279       */
 280      public function request($method, $url, $params=array(), $token='', $secret='') {
 281          if (empty($token)) {
 282              $token = $this->access_token;
 283          }
 284          if (empty($secret)) {
 285              $secret = $this->access_token_secret;
 286          }
 287          // to access protected resource, sign_secret will alwasy be consumer_secret+token_secret
 288          $this->sign_secret = $this->consumer_secret.'&'.$secret;
 289          if (strtolower($method) === 'post' && !empty($params)) {
 290              $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token) + $params, $method);
 291          } else {
 292              $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token), $method);
 293          }
 294          $this->setup_oauth_http_header($oauth_params);
 295          $content = call_user_func_array(array($this->http, strtolower($method)), array($url, $params, $this->http_options));
 296          // reset http header and options to prepare for the next request
 297          $this->http->resetHeader();
 298          // return request return value
 299          return $content;
 300      }
 301  
 302      /**
 303       * shortcut to start http get request
 304       */
 305      public function get($url, $params=array(), $token='', $secret='') {
 306          return $this->request('GET', $url, $params, $token, $secret);
 307      }
 308  
 309      /**
 310       * shortcut to start http post request
 311       */
 312      public function post($url, $params=array(), $token='', $secret='') {
 313          return $this->request('POST', $url, $params, $token, $secret);
 314      }
 315  
 316      /**
 317       * A method to parse oauth response to get oauth_token and oauth_token_secret
 318       * @param string $str
 319       * @return array
 320       */
 321      public function parse_result($str) {
 322          if (empty($str)) {
 323              throw new moodle_exception('error');
 324          }
 325          $parts = explode('&', $str);
 326          $result = array();
 327          foreach ($parts as $part){
 328              list($k, $v) = explode('=', $part, 2);
 329              $result[urldecode($k)] = urldecode($v);
 330          }
 331          if (empty($result)) {
 332              throw new moodle_exception('error');
 333          }
 334          return $result;
 335      }
 336  
 337      /**
 338       * Set nonce
 339       */
 340      function set_nonce($str) {
 341          $this->nonce = $str;
 342      }
 343      /**
 344       * Set timestamp
 345       */
 346      function set_timestamp($time) {
 347          $this->timestamp = $time;
 348      }
 349      /**
 350       * Generate timestamp
 351       */
 352      function get_timestamp() {
 353          if (!empty($this->timestamp)) {
 354              $timestamp = $this->timestamp;
 355              unset($this->timestamp);
 356              return $timestamp;
 357          }
 358          return time();
 359      }
 360      /**
 361       * Generate nonce for oauth request
 362       */
 363      function get_nonce() {
 364          if (!empty($this->nonce)) {
 365              $nonce = $this->nonce;
 366              unset($this->nonce);
 367              return $nonce;
 368          }
 369          $mt = microtime();
 370          $rand = mt_rand();
 371  
 372          return md5($mt . $rand);
 373      }
 374  }
 375  
 376  /**
 377   * OAuth 2.0 Client for using web access tokens.
 378   *
 379   * http://tools.ietf.org/html/draft-ietf-oauth-v2-22
 380   *
 381   * @package   core
 382   * @copyright Dan Poltawski <talktodan@gmail.com>
 383   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 384   */
 385  abstract class oauth2_client extends curl {
 386      /** var string client identifier issued to the client */
 387      private $clientid = '';
 388      /** var string The client secret. */
 389      private $clientsecret = '';
 390      /** var moodle_url URL to return to after authenticating */
 391      private $returnurl = null;
 392      /** var string scope of the authentication request */
 393      private $scope = '';
 394      /** var stdClass access token object */
 395      private $accesstoken = null;
 396  
 397      /**
 398       * Returns the auth url for OAuth 2.0 request
 399       * @return string the auth url
 400       */
 401      abstract protected function auth_url();
 402  
 403      /**
 404       * Returns the token url for OAuth 2.0 request
 405       * @return string the auth url
 406       */
 407      abstract protected function token_url();
 408  
 409      /**
 410       * Constructor.
 411       *
 412       * @param string $clientid
 413       * @param string $clientsecret
 414       * @param moodle_url $returnurl
 415       * @param string $scope
 416       */
 417      public function __construct($clientid, $clientsecret, moodle_url $returnurl, $scope) {
 418          parent::__construct();
 419          $this->clientid = $clientid;
 420          $this->clientsecret = $clientsecret;
 421          $this->returnurl = $returnurl;
 422          $this->scope = $scope;
 423          $this->accesstoken = $this->get_stored_token();
 424      }
 425  
 426      /**
 427       * Is the user logged in? Note that if this is called
 428       * after the first part of the authorisation flow the token
 429       * is upgraded to an accesstoken.
 430       *
 431       * @return boolean true if logged in
 432       */
 433      public function is_logged_in() {
 434          // Has the token expired?
 435          if (isset($this->accesstoken->expires) && time() >= $this->accesstoken->expires) {
 436              $this->log_out();
 437              return false;
 438          }
 439  
 440          // We have a token so we are logged in.
 441          if (isset($this->accesstoken->token)) {
 442              return true;
 443          }
 444  
 445          // If we've been passed then authorization code generated by the
 446          // authorization server try and upgrade the token to an access token.
 447          $code = optional_param('oauth2code', null, PARAM_RAW);
 448          if ($code && $this->upgrade_token($code)) {
 449              return true;
 450          }
 451  
 452          return false;
 453      }
 454  
 455      /**
 456       * Callback url where the request is returned to.
 457       *
 458       * @return moodle_url url of callback
 459       */
 460      public static function callback_url() {
 461          global $CFG;
 462  
 463          return new moodle_url('/admin/oauth2callback.php');
 464      }
 465  
 466      /**
 467       * Returns the login link for this oauth request
 468       *
 469       * @return moodle_url login url
 470       */
 471      public function get_login_url() {
 472  
 473          $callbackurl = self::callback_url();
 474          $url = new moodle_url($this->auth_url(),
 475                          array('client_id' => $this->clientid,
 476                                'response_type' => 'code',
 477                                'redirect_uri' => $callbackurl->out(false),
 478                                'state' => $this->returnurl->out_as_local_url(false),
 479                                'scope' => $this->scope,
 480                            ));
 481  
 482          return $url;
 483      }
 484  
 485      /**
 486       * Upgrade a authorization token from oauth 2.0 to an access token
 487       *
 488       * @param string $code the code returned from the oauth authenticaiton
 489       * @return boolean true if token is upgraded succesfully
 490       */
 491      public function upgrade_token($code) {
 492          $callbackurl = self::callback_url();
 493          $params = array('client_id' => $this->clientid,
 494              'client_secret' => $this->clientsecret,
 495              'grant_type' => 'authorization_code',
 496              'code' => $code,
 497              'redirect_uri' => $callbackurl->out(false),
 498          );
 499  
 500          // Requests can either use http GET or POST.
 501          if ($this->use_http_get()) {
 502              $response = $this->get($this->token_url(), $params);
 503          } else {
 504              $response = $this->post($this->token_url(), $params);
 505          }
 506  
 507          if (!$this->info['http_code'] === 200) {
 508              throw new moodle_exception('Could not upgrade oauth token');
 509          }
 510  
 511          $r = json_decode($response);
 512  
 513          if (!isset($r->access_token)) {
 514              return false;
 515          }
 516  
 517          // Store the token an expiry time.
 518          $accesstoken = new stdClass;
 519          $accesstoken->token = $r->access_token;
 520          $accesstoken->expires = (time() + ($r->expires_in - 10)); // Expires 10 seconds before actual expiry.
 521          $this->store_token($accesstoken);
 522  
 523          return true;
 524      }
 525  
 526      /**
 527       * Logs out of a oauth request, clearing any stored tokens
 528       */
 529      public function log_out() {
 530          $this->store_token(null);
 531      }
 532  
 533      /**
 534       * Make a HTTP request, adding the access token we have
 535       *
 536       * @param string $url The URL to request
 537       * @param array $options
 538       * @return bool
 539       */
 540      protected function request($url, $options = array()) {
 541          $murl = new moodle_url($url);
 542  
 543          if ($this->accesstoken) {
 544              if ($this->use_http_get()) {
 545                  // If using HTTP GET add as a parameter.
 546                  $murl->param('access_token', $this->accesstoken->token);
 547              } else {
 548                  $this->setHeader('Authorization: Bearer '.$this->accesstoken->token);
 549              }
 550          }
 551  
 552          return parent::request($murl->out(false), $options);
 553      }
 554  
 555      /**
 556       * Multiple HTTP Requests
 557       * This function could run multi-requests in parallel.
 558       *
 559       * @param array $requests An array of files to request
 560       * @param array $options An array of options to set
 561       * @return array An array of results
 562       */
 563      protected function multi($requests, $options = array()) {
 564          if ($this->accesstoken) {
 565              $this->setHeader('Authorization: Bearer '.$this->accesstoken->token);
 566          }
 567          return parent::multi($requests, $options);
 568      }
 569  
 570      /**
 571       * Returns the tokenname for the access_token to be stored
 572       * through multiple requests.
 573       *
 574       * The default implentation is to use the classname combiend
 575       * with the scope.
 576       *
 577       * @return string tokenname for prefernce storage
 578       */
 579      protected function get_tokenname() {
 580          // This is unusual but should work for most purposes.
 581          return get_class($this).'-'.md5($this->scope);
 582      }
 583  
 584      /**
 585       * Store a token between requests. Currently uses
 586       * session named by get_tokenname
 587       *
 588       * @param stdClass|null $token token object to store or null to clear
 589       */
 590      protected function store_token($token) {
 591          global $SESSION;
 592  
 593          $this->accesstoken = $token;
 594          $name = $this->get_tokenname();
 595  
 596          if ($token !== null) {
 597              $SESSION->{$name} = $token;
 598          } else {
 599              unset($SESSION->{$name});
 600          }
 601      }
 602  
 603      /**
 604       * Retrieve a token stored.
 605       *
 606       * @return stdClass|null token object
 607       */
 608      protected function get_stored_token() {
 609          global $SESSION;
 610  
 611          $name = $this->get_tokenname();
 612  
 613          if (isset($SESSION->{$name})) {
 614              return $SESSION->{$name};
 615          }
 616  
 617          return null;
 618      }
 619  
 620      /**
 621       * Get access token.
 622       *
 623       * This is just a getter to read the private property.
 624       *
 625       * @return string
 626       */
 627      public function get_accesstoken() {
 628          return $this->accesstoken;
 629      }
 630  
 631      /**
 632       * Get the client ID.
 633       *
 634       * This is just a getter to read the private property.
 635       *
 636       * @return string
 637       */
 638      public function get_clientid() {
 639          return $this->clientid;
 640      }
 641  
 642      /**
 643       * Get the client secret.
 644       *
 645       * This is just a getter to read the private property.
 646       *
 647       * @return string
 648       */
 649      public function get_clientsecret() {
 650          return $this->clientsecret;
 651      }
 652  
 653      /**
 654       * Should HTTP GET be used instead of POST?
 655       * Some APIs do not support POST and want oauth to use
 656       * GET instead (with the auth_token passed as a GET param).
 657       *
 658       * @return bool true if GET should be used
 659       */
 660      protected function use_http_get() {
 661          return false;
 662      }
 663  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1