MDL-19168, dropbox repository plugin for moodle, and oauth support for filepicker...
[moodle.git] / lib / oauthlib.php
CommitLineData
3e123368
DC
1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
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
42class oauth_helper {
43 /** @var string consumer key, issued by oauth provider*/
44 protected $consumer_key;
45 /** @var string consumer secret, issued by oauth provider*/
46 protected $consumer_secret;
47 /** @var string oauth root*/
48 protected $api_root;
49 /** @var string request token url*/
50 protected $request_token_api;
51 /** @var string authorize url*/
52 protected $authorize_url;
53 protected $http_method;
54 /** @var string */
55 protected $access_token_api;
56 /** @var curl */
57 protected $http;
58
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
77 if (empty($args['request_token_api'])) {
78 $this->request_token_api = $this->api_root . '/request_token';
79 } else {
80 $this->request_token_api = $args['request_token_api'];
81 }
82
83 if (empty($args['authorize_url'])) {
84 $this->authorize_url = $this->api_root . '/authorize';
85 } else {
86 $this->authorize_url = $args['authorize_url'];
87 }
88
89 if (empty($args['access_token_api'])) {
90 $this->access_token_api = $this->api_root . '/access_token';
91 } else {
92 $this->access_token_api = $args['access_token_api'];
93 }
94
95 if (!empty($args['oauth_callback'])) {
96 $this->oauth_callback = new moodle_url($args['oauth_callback']);
97 }
98 if (!empty($args['access_token'])) {
99 $this->access_token = $args['access_token'];
100 }
101 if (!empty($args['access_token_secret'])) {
102 $this->access_token_secret = $args['access_token_secret'];
103 }
104 $this->http = new curl(array('debug'=>false));
105 }
106
107 /**
108 * Build parameters list:
109 * oauth_consumer_key="0685bd9184jfhq22",
110 * oauth_nonce="4572616e48616d6d65724c61686176",
111 * oauth_token="ad180jjd733klru7",
112 * oauth_signature_method="HMAC-SHA1",
113 * oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
114 * oauth_timestamp="137131200",
115 * oauth_version="1.0"
116 * oauth_verifier="1.0"
117 * @param array $param
118 * @return string
119 */
120 function get_signable_parameters($params){
121 $sorted = $params;
122 ksort($sorted);
123
124 $total = array();
125 foreach ($sorted as $k => $v) {
126 if ($k == 'oauth_signature') {
127 continue;
128 }
129
130 $total[] = rawurlencode($k) . '=' . rawurlencode($v);
131 }
132 return implode('&', $total);
133 }
134
135 /**
136 * Create signature for oauth request
137 * @param string $url
138 * @param string $secret
139 * @param array $params
140 * @return string
141 */
142 public function sign($http_method, $url, $params, $secret) {
143 $sig = array(
144 strtoupper($http_method),
145 preg_replace('/%7E/', '~', rawurlencode($url)),
146 rawurlencode($this->get_signable_parameters($params)),
147 );
148
149 $base_string = implode('&', $sig);
150 $sig = base64_encode(hash_hmac('sha1', $base_string, $secret, true));
151 return $sig;
152 }
153
154 /**
155 * Initilize oauth request parameters, including:
156 * oauth_consumer_key="0685bd9184jfhq22",
157 * oauth_token="ad180jjd733klru7",
158 * oauth_signature_method="HMAC-SHA1",
159 * oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
160 * oauth_timestamp="137131200",
161 * oauth_nonce="4572616e48616d6d65724c61686176",
162 * oauth_version="1.0"
163 * To access protected resources, oauth_token should be defined
164 *
165 * @param string $url
166 * @param string $token
167 * @param string $http_method
168 * @return array
169 */
170 public function prepare_oauth_parameters($url, $params, $http_method = 'POST') {
171 if (is_array($params)) {
172 $oauth_params = $params;
173 } else {
174 $oauth_params = array();
175 }
176 $oauth_params['oauth_version'] = '1.0';
177 $oauth_params['oauth_nonce'] = $this->get_nonce();
178 $oauth_params['oauth_timestamp'] = $this->get_timestamp();
179 $oauth_params['oauth_consumer_key'] = $this->consumer_key;
180 if (!empty($this->oauth_callback)) {
181 $oauth_params['oauth_callback'] = $this->oauth_callback->out(false);
182 }
183 $oauth_params['oauth_signature_method'] = 'HMAC-SHA1';
184 $oauth_params['oauth_signature'] = $this->sign($http_method, $url, $oauth_params, $this->sign_secret);
185 return $oauth_params;
186 }
187
188 public function setup_oauth_http_header($params) {
189
190 $total = array();
191 ksort($params);
192 foreach ($params as $k => $v) {
193 $total[] = rawurlencode($k) . '="' . rawurlencode($v).'"';
194 }
195 $str = implode(', ', $total);
196 $str = 'Authorization: OAuth '.$str;
197 $this->http->setHeader('Expect:');
198 $this->http->setHeader($str);
199 }
200
201 /**
202 * Request token for authentication
203 * This is the first step to use OAuth, it will return oauth_token and oauth_token_secret
204 * @return array
205 */
206 public function request_token() {
207 $this->sign_secret = $this->consumer_secret.'&';
208 $params = $this->prepare_oauth_parameters($this->request_token_api, array(), 'GET');
209 $content = $this->http->get($this->request_token_api, $params);
210 // Including:
211 // oauth_token
212 // oauth_token_secret
213 $result = $this->parse_result($content);
214 if (empty($result['oauth_token'])) {
215 // failed
216 var_dump($result);
217 exit;
218 }
219 // build oauth authrize url
220 if (!empty($this->oauth_callback)) {
221 // url must be rawurlencode
222 $result['authorize_url'] = $this->authorize_url . '?oauth_token='.$result['oauth_token'].'&oauth_callback='.rawurlencode($this->oauth_callback->out(false));
223 } else {
224 // no callback
225 $result['authorize_url'] = $this->authorize_url . '?oauth_token='.$result['oauth_token'];
226 }
227 return $result;
228 }
229
230 /**
231 * Set oauth access token for oauth request
232 * @param string $token
233 * @param string $secret
234 */
235 public function set_access_token($token, $secret) {
236 $this->access_token = $token;
237 $this->access_token_secret = $secret;
238 }
239
240 /**
241 * Request oauth access token from server
242 * @param string $method
243 * @param string $url
244 * @param string $token
245 * @param string $secret
246 */
247 public function get_access_token($token, $secret, $verifier='') {
248 $this->sign_secret = $this->consumer_secret.'&'.$secret;
249 $params = $this->prepare_oauth_parameters($this->access_token_api, array('oauth_token'=>$token, 'oauth_verifier'=>$verifier), 'POST');
250 $this->setup_oauth_http_header($params);
251 $content = $this->http->post($this->access_token_api, $params);
252 $keys = $this->parse_result($content);
253 $this->set_access_token($keys['oauth_token'], $keys['oauth_token_secret']);
254 return $keys;
255 }
256
257 /**
258 * Request oauth protected resources
259 * @param string $method
260 * @param string $url
261 * @param string $token
262 * @param string $secret
263 */
264 public function request($method, $url, $params=array(), $token='', $secret='') {
265 if (empty($token)) {
266 $token = $this->access_token;
267 }
268 if (empty($secret)) {
269 $secret = $this->access_token_secret;
270 }
271 // to access protected resource, sign_secret will alwasy be consumer_secret+token_secret
272 $this->sign_secret = $this->consumer_secret.'&'.$secret;
273 $oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token), $method);
274 $this->setup_oauth_http_header($oauth_params);
275 $content = call_user_func_array(array($this->http, strtolower($method)), array($url, $params));
276 return $content;
277 }
278
279 /**
280 * shortcut to start http get request
281 */
282 public function get($url, $params=array(), $token='', $secret='') {
283 return $this->request('GET', $url, $params, $token, $secret);
284 }
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 /**
294 * A method to parse oauth response to get oauth_token and oauth_token_secret
295 * @param string $str
296 * @return array
297 */
298 public function parse_result($str) {
299 if (empty($str)) {
300 throw new moodle_exception('error');
301 }
302 $parts = explode('&', $str);
303 $result = array();
304 foreach ($parts as $part){
305 list($k, $v) = explode('=', $part, 2);
306 $result[urldecode($k)] = urldecode($v);
307 }
308 if (empty($result)) {
309 throw new moodle_exception('error');
310 }
311 return $result;
312 }
313
314 /**
315 * Set nonce
316 */
317 function set_nonce($str) {
318 $this->nonce = $str;
319 }
320 /**
321 * Set timestamp
322 */
323 function set_timestamp($time) {
324 $this->timestamp = $time;
325 }
326 /**
327 * Generate timestamp
328 */
329 function get_timestamp() {
330 if (!empty($this->timestamp)) {
331 $timestamp = $this->timestamp;
332 unset($this->timestamp);
333 return $timestamp;
334 }
335 return time();
336 }
337 /**
338 * Generate nonce for oauth request
339 */
340 function get_nonce() {
341 if (!empty($this->nonce)) {
342 $nonce = $this->nonce;
343 unset($this->nonce);
344 return $nonce;
345 }
346 $mt = microtime();
347 $rand = mt_rand();
348
349 return md5($mt . $rand);
350 }
351}