Subtree merged git://github.com/skodak/moodle-mod_book.git (master branch) into mod...
[moodle.git] / lib / oauthlib.php
1 <?php
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/>.
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  */
42 class 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;
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'];
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         }
83         if (empty($args['authorize_url'])) {
84             $this->authorize_url = $this->api_root . '/authorize';
85         } else {
86             $this->authorize_url = $args['authorize_url'];
87         }
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         }
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     }
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);
124         $total = array();
125         foreach ($sorted as $k => $v) {
126             if ($k == 'oauth_signature') {
127                 continue;
128             }
130             $total[] = rawurlencode($k) . '=' . rawurlencode($v);
131         }
132         return implode('&', $total);
133     }
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         );
149         $base_string = implode('&', $sig);
150         $sig = base64_encode(hash_hmac('sha1', $base_string, $secret, true));
151         return $sig;
152     }
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     }
188     public function setup_oauth_http_header($params) {
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     }
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     }
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     }
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     }
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     }
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     }
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     }
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     }
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();
349         return md5($mt . $rand);
350     }