PageRenderTime 22ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/oauthlib.php

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