[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |