1 |
efrain |
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 |
/** @var moodle_url oauth callback URL. */
|
|
|
65 |
protected $oauth_callback;
|
|
|
66 |
/** @var string access token. */
|
|
|
67 |
protected $access_token;
|
|
|
68 |
/** @var string access secret token. */
|
|
|
69 |
protected $access_token_secret;
|
|
|
70 |
/** @var string sign secret. */
|
|
|
71 |
protected $sign_secret;
|
|
|
72 |
/** @var string nonce. */
|
|
|
73 |
protected $nonce;
|
|
|
74 |
/** @var int timestamp. */
|
|
|
75 |
protected $timestamp;
|
|
|
76 |
|
|
|
77 |
|
|
|
78 |
/**
|
|
|
79 |
* Contructor for oauth_helper.
|
|
|
80 |
* Subclass can override construct to build its own $this->http
|
|
|
81 |
*
|
|
|
82 |
* @param array $args requires at least three keys, oauth_consumer_key
|
|
|
83 |
* oauth_consumer_secret and api_root, oauth_helper will
|
|
|
84 |
* guess request_token_api, authrize_url and access_token_api
|
|
|
85 |
* based on api_root, but it not always works
|
|
|
86 |
*/
|
|
|
87 |
function __construct($args) {
|
|
|
88 |
if (!empty($args['api_root'])) {
|
|
|
89 |
$this->api_root = $args['api_root'];
|
|
|
90 |
} else {
|
|
|
91 |
$this->api_root = '';
|
|
|
92 |
}
|
|
|
93 |
$this->consumer_key = $args['oauth_consumer_key'];
|
|
|
94 |
$this->consumer_secret = $args['oauth_consumer_secret'];
|
|
|
95 |
|
|
|
96 |
if (empty($args['request_token_api'])) {
|
|
|
97 |
$this->request_token_api = $this->api_root . '/request_token';
|
|
|
98 |
} else {
|
|
|
99 |
$this->request_token_api = $args['request_token_api'];
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
if (empty($args['authorize_url'])) {
|
|
|
103 |
$this->authorize_url = $this->api_root . '/authorize';
|
|
|
104 |
} else {
|
|
|
105 |
$this->authorize_url = $args['authorize_url'];
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
if (empty($args['access_token_api'])) {
|
|
|
109 |
$this->access_token_api = $this->api_root . '/access_token';
|
|
|
110 |
} else {
|
|
|
111 |
$this->access_token_api = $args['access_token_api'];
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
if (!empty($args['oauth_callback'])) {
|
|
|
115 |
$this->oauth_callback = new moodle_url($args['oauth_callback']);
|
|
|
116 |
}
|
|
|
117 |
if (!empty($args['access_token'])) {
|
|
|
118 |
$this->access_token = $args['access_token'];
|
|
|
119 |
}
|
|
|
120 |
if (!empty($args['access_token_secret'])) {
|
|
|
121 |
$this->access_token_secret = $args['access_token_secret'];
|
|
|
122 |
}
|
|
|
123 |
$this->http = new curl(array('debug'=>false));
|
|
|
124 |
if (!empty($args['http_options'])) {
|
|
|
125 |
$this->http_options = $args['http_options'];
|
|
|
126 |
} else {
|
|
|
127 |
$this->http_options = array();
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
/**
|
|
|
132 |
* Build parameters list:
|
|
|
133 |
* oauth_consumer_key="0685bd9184jfhq22",
|
|
|
134 |
* oauth_nonce="4572616e48616d6d65724c61686176",
|
|
|
135 |
* oauth_token="ad180jjd733klru7",
|
|
|
136 |
* oauth_signature_method="HMAC-SHA1",
|
|
|
137 |
* oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
|
|
|
138 |
* oauth_timestamp="137131200",
|
|
|
139 |
* oauth_version="1.0"
|
|
|
140 |
* oauth_verifier="1.0"
|
|
|
141 |
* @param array $param
|
|
|
142 |
* @return string
|
|
|
143 |
*/
|
|
|
144 |
function get_signable_parameters($params){
|
|
|
145 |
$sorted = $params;
|
|
|
146 |
ksort($sorted);
|
|
|
147 |
|
|
|
148 |
$total = array();
|
|
|
149 |
foreach ($sorted as $k => $v) {
|
|
|
150 |
if ($k == 'oauth_signature') {
|
|
|
151 |
continue;
|
|
|
152 |
}
|
|
|
153 |
|
|
|
154 |
$total[] = rawurlencode($k) . '=' . rawurlencode($v);
|
|
|
155 |
}
|
|
|
156 |
return implode('&', $total);
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
* Create signature for oauth request
|
|
|
161 |
* @param string $url
|
|
|
162 |
* @param string $secret
|
|
|
163 |
* @param array $params
|
|
|
164 |
* @return string
|
|
|
165 |
*/
|
|
|
166 |
public function sign($http_method, $url, $params, $secret) {
|
|
|
167 |
$sig = array(
|
|
|
168 |
strtoupper($http_method),
|
|
|
169 |
preg_replace('/%7E/', '~', rawurlencode($url)),
|
|
|
170 |
rawurlencode($this->get_signable_parameters($params)),
|
|
|
171 |
);
|
|
|
172 |
|
|
|
173 |
$base_string = implode('&', $sig);
|
|
|
174 |
$sig = base64_encode(hash_hmac('sha1', $base_string, $secret, true));
|
|
|
175 |
return $sig;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
/**
|
|
|
179 |
* Initilize oauth request parameters, including:
|
|
|
180 |
* oauth_consumer_key="0685bd9184jfhq22",
|
|
|
181 |
* oauth_token="ad180jjd733klru7",
|
|
|
182 |
* oauth_signature_method="HMAC-SHA1",
|
|
|
183 |
* oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
|
|
|
184 |
* oauth_timestamp="137131200",
|
|
|
185 |
* oauth_nonce="4572616e48616d6d65724c61686176",
|
|
|
186 |
* oauth_version="1.0"
|
|
|
187 |
* To access protected resources, oauth_token should be defined
|
|
|
188 |
*
|
|
|
189 |
* @param string $url
|
|
|
190 |
* @param string $token
|
|
|
191 |
* @param string $http_method
|
|
|
192 |
* @return array
|
|
|
193 |
*/
|
|
|
194 |
public function prepare_oauth_parameters($url, $params, $http_method = 'POST') {
|
|
|
195 |
if (is_array($params)) {
|
|
|
196 |
$oauth_params = $params;
|
|
|
197 |
} else {
|
|
|
198 |
$oauth_params = array();
|
|
|
199 |
}
|
|
|
200 |
$oauth_params['oauth_version'] = '1.0';
|
|
|
201 |
$oauth_params['oauth_nonce'] = $this->get_nonce();
|
|
|
202 |
$oauth_params['oauth_timestamp'] = $this->get_timestamp();
|
|
|
203 |
$oauth_params['oauth_consumer_key'] = $this->consumer_key;
|
|
|
204 |
$oauth_params['oauth_signature_method'] = 'HMAC-SHA1';
|
|
|
205 |
$oauth_params['oauth_signature'] = $this->sign($http_method, $url, $oauth_params, $this->sign_secret);
|
|
|
206 |
return $oauth_params;
|
|
|
207 |
}
|
|
|
208 |
|
|
|
209 |
public function setup_oauth_http_header($params) {
|
|
|
210 |
|
|
|
211 |
$total = array();
|
|
|
212 |
ksort($params);
|
|
|
213 |
foreach ($params as $k => $v) {
|
|
|
214 |
$total[] = rawurlencode($k) . '="' . rawurlencode($v).'"';
|
|
|
215 |
}
|
|
|
216 |
$str = implode(', ', $total);
|
|
|
217 |
$str = 'Authorization: OAuth '.$str;
|
|
|
218 |
$this->http->setHeader('Expect:');
|
|
|
219 |
$this->http->setHeader($str);
|
|
|
220 |
}
|
|
|
221 |
|
|
|
222 |
/**
|
|
|
223 |
* Sets the options for the next curl request
|
|
|
224 |
*
|
|
|
225 |
* @param array $options
|
|
|
226 |
*/
|
|
|
227 |
public function setup_oauth_http_options($options) {
|
|
|
228 |
$this->http_options = $options;
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
/**
|
|
|
232 |
* Request token for authentication
|
|
|
233 |
* This is the first step to use OAuth, it will return oauth_token and oauth_token_secret
|
|
|
234 |
* @return array
|
|
|
235 |
*/
|
|
|
236 |
public function request_token() {
|
|
|
237 |
$this->sign_secret = $this->consumer_secret.'&';
|
|
|
238 |
|
|
|
239 |
if (empty($this->oauth_callback)) {
|
|
|
240 |
$params = [];
|
|
|
241 |
} else {
|
|
|
242 |
$params = ['oauth_callback' => $this->oauth_callback->out(false)];
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
$params = $this->prepare_oauth_parameters($this->request_token_api, $params, 'GET');
|
|
|
246 |
$content = $this->http->get($this->request_token_api, $params, $this->http_options);
|
|
|
247 |
// Including:
|
|
|
248 |
// oauth_token
|
|
|
249 |
// oauth_token_secret
|
|
|
250 |
$result = $this->parse_result($content);
|
|
|
251 |
if (empty($result['oauth_token'])) {
|
|
|
252 |
throw new moodle_exception('oauth1requesttoken', 'core_error', '', null, $content);
|
|
|
253 |
}
|
|
|
254 |
// Build oauth authorize url.
|
|
|
255 |
$result['authorize_url'] = $this->authorize_url . '?oauth_token='.$result['oauth_token'];
|
|
|
256 |
|
|
|
257 |
return $result;
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
/**
|
|
|
261 |
* Set oauth access token for oauth request
|
|
|
262 |
* @param string $token
|
|
|
263 |
* @param string $secret
|
|
|
264 |
*/
|
|
|
265 |
public function set_access_token($token, $secret) {
|
|
|
266 |
$this->access_token = $token;
|
|
|
267 |
$this->access_token_secret = $secret;
|
|
|
268 |
}
|
|
|
269 |
|
|
|
270 |
/**
|
|
|
271 |
* Request oauth access token from server
|
|
|
272 |
* @param string $method
|
|
|
273 |
* @param string $url
|
|
|
274 |
* @param string $token
|
|
|
275 |
* @param string $secret
|
|
|
276 |
*/
|
|
|
277 |
public function get_access_token($token, $secret, $verifier='') {
|
|
|
278 |
$this->sign_secret = $this->consumer_secret.'&'.$secret;
|
|
|
279 |
$params = $this->prepare_oauth_parameters($this->access_token_api, array('oauth_token'=>$token, 'oauth_verifier'=>$verifier), 'POST');
|
|
|
280 |
$this->setup_oauth_http_header($params);
|
|
|
281 |
// Should never send the callback in this request.
|
|
|
282 |
unset($params['oauth_callback']);
|
|
|
283 |
$content = $this->http->post($this->access_token_api, $params, $this->http_options);
|
|
|
284 |
$keys = $this->parse_result($content);
|
|
|
285 |
|
|
|
286 |
if (empty($keys['oauth_token']) || empty($keys['oauth_token_secret'])) {
|
|
|
287 |
throw new moodle_exception('oauth1accesstoken', 'core_error', '', null, $content);
|
|
|
288 |
}
|
|
|
289 |
|
|
|
290 |
$this->set_access_token($keys['oauth_token'], $keys['oauth_token_secret']);
|
|
|
291 |
return $keys;
|
|
|
292 |
}
|
|
|
293 |
|
|
|
294 |
/**
|
|
|
295 |
* Request oauth protected resources
|
|
|
296 |
* @param string $method
|
|
|
297 |
* @param string $url
|
|
|
298 |
* @param string $token
|
|
|
299 |
* @param string $secret
|
|
|
300 |
*/
|
|
|
301 |
public function request($method, $url, $params=array(), $token='', $secret='') {
|
|
|
302 |
if (empty($token)) {
|
|
|
303 |
$token = $this->access_token;
|
|
|
304 |
}
|
|
|
305 |
if (empty($secret)) {
|
|
|
306 |
$secret = $this->access_token_secret;
|
|
|
307 |
}
|
|
|
308 |
// to access protected resource, sign_secret will alwasy be consumer_secret+token_secret
|
|
|
309 |
$this->sign_secret = $this->consumer_secret.'&'.$secret;
|
|
|
310 |
if (strtolower($method) === 'post' && !empty($params)) {
|
|
|
311 |
$oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token) + $params, $method);
|
|
|
312 |
} else {
|
|
|
313 |
$oauth_params = $this->prepare_oauth_parameters($url, array('oauth_token'=>$token), $method);
|
|
|
314 |
}
|
|
|
315 |
$this->setup_oauth_http_header($oauth_params);
|
|
|
316 |
$content = call_user_func_array(array($this->http, strtolower($method)), array($url, $params, $this->http_options));
|
|
|
317 |
// reset http header and options to prepare for the next request
|
|
|
318 |
$this->http->resetHeader();
|
|
|
319 |
// return request return value
|
|
|
320 |
return $content;
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
/**
|
|
|
324 |
* shortcut to start http get request
|
|
|
325 |
*/
|
|
|
326 |
public function get($url, $params=array(), $token='', $secret='') {
|
|
|
327 |
return $this->request('GET', $url, $params, $token, $secret);
|
|
|
328 |
}
|
|
|
329 |
|
|
|
330 |
/**
|
|
|
331 |
* shortcut to start http post request
|
|
|
332 |
*/
|
|
|
333 |
public function post($url, $params=array(), $token='', $secret='') {
|
|
|
334 |
return $this->request('POST', $url, $params, $token, $secret);
|
|
|
335 |
}
|
|
|
336 |
|
|
|
337 |
/**
|
|
|
338 |
* A method to parse oauth response to get oauth_token and oauth_token_secret
|
|
|
339 |
* @param string $str
|
|
|
340 |
* @return array
|
|
|
341 |
*/
|
|
|
342 |
public function parse_result($str) {
|
|
|
343 |
if (empty($str)) {
|
|
|
344 |
throw new moodle_exception('error');
|
|
|
345 |
}
|
|
|
346 |
$parts = explode('&', $str);
|
|
|
347 |
$result = array();
|
|
|
348 |
foreach ($parts as $part){
|
|
|
349 |
list($k, $v) = explode('=', $part, 2);
|
|
|
350 |
$result[urldecode($k)] = urldecode($v);
|
|
|
351 |
}
|
|
|
352 |
if (empty($result)) {
|
|
|
353 |
throw new moodle_exception('error');
|
|
|
354 |
}
|
|
|
355 |
return $result;
|
|
|
356 |
}
|
|
|
357 |
|
|
|
358 |
/**
|
|
|
359 |
* Set nonce
|
|
|
360 |
*/
|
|
|
361 |
function set_nonce($str) {
|
|
|
362 |
$this->nonce = $str;
|
|
|
363 |
}
|
|
|
364 |
/**
|
|
|
365 |
* Set timestamp
|
|
|
366 |
*/
|
|
|
367 |
function set_timestamp($time) {
|
|
|
368 |
$this->timestamp = $time;
|
|
|
369 |
}
|
|
|
370 |
/**
|
|
|
371 |
* Generate timestamp
|
|
|
372 |
*/
|
|
|
373 |
function get_timestamp() {
|
|
|
374 |
if (!empty($this->timestamp)) {
|
|
|
375 |
$timestamp = $this->timestamp;
|
|
|
376 |
unset($this->timestamp);
|
|
|
377 |
return $timestamp;
|
|
|
378 |
}
|
|
|
379 |
return time();
|
|
|
380 |
}
|
|
|
381 |
/**
|
|
|
382 |
* Generate nonce for oauth request
|
|
|
383 |
*/
|
|
|
384 |
function get_nonce() {
|
|
|
385 |
if (!empty($this->nonce)) {
|
|
|
386 |
$nonce = $this->nonce;
|
|
|
387 |
unset($this->nonce);
|
|
|
388 |
return $nonce;
|
|
|
389 |
}
|
|
|
390 |
$mt = microtime();
|
|
|
391 |
$rand = mt_rand();
|
|
|
392 |
|
|
|
393 |
return md5($mt . $rand);
|
|
|
394 |
}
|
|
|
395 |
}
|
|
|
396 |
|
|
|
397 |
/**
|
|
|
398 |
* OAuth 2.0 Client for using web access tokens.
|
|
|
399 |
*
|
|
|
400 |
* http://tools.ietf.org/html/draft-ietf-oauth-v2-22
|
|
|
401 |
*
|
|
|
402 |
* @package core
|
|
|
403 |
* @copyright Dan Poltawski <talktodan@gmail.com>
|
|
|
404 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
405 |
*/
|
|
|
406 |
abstract class oauth2_client extends curl {
|
|
|
407 |
/** @var string $clientid client identifier issued to the client */
|
|
|
408 |
private $clientid = '';
|
|
|
409 |
/** @var string $clientsecret The client secret. */
|
|
|
410 |
private $clientsecret = '';
|
|
|
411 |
/** @var moodle_url $returnurl URL to return to after authenticating */
|
|
|
412 |
private $returnurl = null;
|
|
|
413 |
/** @var string $scope of the authentication request */
|
|
|
414 |
protected $scope = '';
|
|
|
415 |
/** @var stdClass $accesstoken access token object */
|
|
|
416 |
protected $accesstoken = null;
|
|
|
417 |
/** @var string $refreshtoken refresh token string */
|
|
|
418 |
protected $refreshtoken = '';
|
|
|
419 |
/** @var string $mocknextresponse string */
|
|
|
420 |
private $mocknextresponse = '';
|
|
|
421 |
/** @var array $upgradedcodes list of upgraded codes in this request */
|
|
|
422 |
private static $upgradedcodes = [];
|
|
|
423 |
/** @var bool basicauth */
|
|
|
424 |
protected $basicauth = false;
|
|
|
425 |
|
|
|
426 |
/**
|
|
|
427 |
* Returns the auth url for OAuth 2.0 request
|
|
|
428 |
* @return string the auth url
|
|
|
429 |
*/
|
|
|
430 |
abstract protected function auth_url();
|
|
|
431 |
|
|
|
432 |
/**
|
|
|
433 |
* Returns the token url for OAuth 2.0 request
|
|
|
434 |
* @return string the auth url
|
|
|
435 |
*/
|
|
|
436 |
abstract protected function token_url();
|
|
|
437 |
|
|
|
438 |
/**
|
|
|
439 |
* Constructor.
|
|
|
440 |
*
|
|
|
441 |
* @param string $clientid
|
|
|
442 |
* @param string $clientsecret
|
|
|
443 |
* @param moodle_url $returnurl
|
|
|
444 |
* @param string $scope
|
|
|
445 |
*/
|
|
|
446 |
public function __construct($clientid, $clientsecret, moodle_url $returnurl, $scope) {
|
|
|
447 |
parent::__construct();
|
|
|
448 |
$this->clientid = $clientid;
|
|
|
449 |
$this->clientsecret = $clientsecret;
|
|
|
450 |
$this->returnurl = $returnurl;
|
|
|
451 |
$this->scope = $scope;
|
|
|
452 |
$this->accesstoken = $this->get_stored_token();
|
|
|
453 |
}
|
|
|
454 |
|
|
|
455 |
/**
|
|
|
456 |
* Is the user logged in? Note that if this is called
|
|
|
457 |
* after the first part of the authorisation flow the token
|
|
|
458 |
* is upgraded to an accesstoken.
|
|
|
459 |
*
|
|
|
460 |
* @return boolean true if logged in
|
|
|
461 |
*/
|
|
|
462 |
public function is_logged_in() {
|
|
|
463 |
// Has the token expired?
|
|
|
464 |
if (isset($this->accesstoken->expires) && time() >= $this->accesstoken->expires) {
|
|
|
465 |
$this->store_token(null);
|
|
|
466 |
return false;
|
|
|
467 |
}
|
|
|
468 |
|
|
|
469 |
// We have a token so we are logged in.
|
|
|
470 |
if (isset($this->accesstoken->token)) {
|
|
|
471 |
// Check that the access token has all the requested scopes.
|
|
|
472 |
$scopemissing = false;
|
|
|
473 |
$scopecheck = ' ' . $this->accesstoken->scope . ' ';
|
|
|
474 |
|
|
|
475 |
$requiredscopes = explode(' ', $this->scope);
|
|
|
476 |
foreach ($requiredscopes as $requiredscope) {
|
|
|
477 |
if (strpos($scopecheck, ' ' . $requiredscope . ' ') === false) {
|
|
|
478 |
$scopemissing = true;
|
|
|
479 |
break;
|
|
|
480 |
}
|
|
|
481 |
}
|
|
|
482 |
if (!$scopemissing) {
|
|
|
483 |
return true;
|
|
|
484 |
}
|
|
|
485 |
}
|
|
|
486 |
|
|
|
487 |
// If we've been passed then authorization code generated by the
|
|
|
488 |
// authorization server try and upgrade the token to an access token.
|
|
|
489 |
$code = optional_param('oauth2code', null, PARAM_RAW);
|
|
|
490 |
// Note - sometimes we may call is_logged_in twice in the same request - we don't want to attempt
|
|
|
491 |
// to upgrade the same token twice.
|
|
|
492 |
if ($code && !in_array($code, self::$upgradedcodes) && $this->upgrade_token($code)) {
|
|
|
493 |
return true;
|
|
|
494 |
}
|
|
|
495 |
|
|
|
496 |
return false;
|
|
|
497 |
}
|
|
|
498 |
|
|
|
499 |
/**
|
|
|
500 |
* Callback url where the request is returned to.
|
|
|
501 |
*
|
|
|
502 |
* @return moodle_url url of callback
|
|
|
503 |
*/
|
|
|
504 |
public static function callback_url() {
|
|
|
505 |
global $CFG;
|
|
|
506 |
|
|
|
507 |
return new moodle_url('/admin/oauth2callback.php');
|
|
|
508 |
}
|
|
|
509 |
|
|
|
510 |
/**
|
|
|
511 |
* An additional array of url params to pass with a login request.
|
|
|
512 |
*
|
|
|
513 |
* @return array of name value pairs.
|
|
|
514 |
*/
|
|
|
515 |
public function get_additional_login_parameters() {
|
|
|
516 |
return [];
|
|
|
517 |
}
|
|
|
518 |
|
|
|
519 |
/**
|
|
|
520 |
* Returns the login link for this oauth request
|
|
|
521 |
*
|
|
|
522 |
* @return moodle_url login url
|
|
|
523 |
*/
|
|
|
524 |
public function get_login_url() {
|
|
|
525 |
|
|
|
526 |
$callbackurl = self::callback_url();
|
|
|
527 |
$defaultparams = [
|
|
|
528 |
'client_id' => $this->clientid,
|
|
|
529 |
'response_type' => 'code',
|
|
|
530 |
'redirect_uri' => $callbackurl->out(false),
|
|
|
531 |
'state' => $this->returnurl->out_as_local_url(false),
|
|
|
532 |
|
|
|
533 |
];
|
|
|
534 |
if (!empty($this->scope)) {
|
|
|
535 |
// The scope should only be included if a value is set.
|
|
|
536 |
// If none provided, the server MUST process the request and provide an appropriate documented response.
|
|
|
537 |
// See spec https://tools.ietf.org/html/rfc6749#section-3.3
|
|
|
538 |
$defaultparams['scope'] = $this->scope;
|
|
|
539 |
}
|
|
|
540 |
|
|
|
541 |
$params = array_merge(
|
|
|
542 |
$defaultparams,
|
|
|
543 |
$this->get_additional_login_parameters()
|
|
|
544 |
);
|
|
|
545 |
|
|
|
546 |
return new moodle_url($this->auth_url(), $params);
|
|
|
547 |
}
|
|
|
548 |
|
|
|
549 |
/**
|
|
|
550 |
* Given an array of name value pairs - build a valid HTTP POST application/x-www-form-urlencoded string.
|
|
|
551 |
*
|
|
|
552 |
* @param array $params Name / value pairs.
|
|
|
553 |
* @return string POST data.
|
|
|
554 |
*/
|
|
|
555 |
public function build_post_data($params) {
|
|
|
556 |
$result = [];
|
|
|
557 |
foreach ($params as $name => $value) {
|
|
|
558 |
$result[] = urlencode($name) . '=' . urlencode($value);
|
|
|
559 |
}
|
|
|
560 |
return implode('&', $result);
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
/**
|
|
|
564 |
* Upgrade a authorization token from oauth 2.0 to an access token
|
|
|
565 |
*
|
|
|
566 |
* @param string $code the code returned from the oauth authenticaiton
|
|
|
567 |
* @return boolean true if token is upgraded succesfully
|
|
|
568 |
*/
|
|
|
569 |
public function upgrade_token($code) {
|
|
|
570 |
$callbackurl = self::callback_url();
|
|
|
571 |
$params = array('code' => $code,
|
|
|
572 |
'grant_type' => 'authorization_code',
|
|
|
573 |
'redirect_uri' => $callbackurl->out(false),
|
|
|
574 |
);
|
|
|
575 |
|
|
|
576 |
if ($this->basicauth) {
|
|
|
577 |
$idsecret = urlencode($this->clientid) . ':' . urlencode($this->clientsecret);
|
|
|
578 |
$this->setHeader('Authorization: Basic ' . base64_encode($idsecret));
|
|
|
579 |
} else {
|
|
|
580 |
$params['client_id'] = $this->clientid;
|
|
|
581 |
$params['client_secret'] = $this->clientsecret;
|
|
|
582 |
}
|
|
|
583 |
|
|
|
584 |
// Requests can either use http GET or POST.
|
|
|
585 |
if ($this->use_http_get()) {
|
|
|
586 |
$response = $this->get($this->token_url(), $params);
|
|
|
587 |
} else {
|
|
|
588 |
$response = $this->post($this->token_url(), $this->build_post_data($params));
|
|
|
589 |
}
|
|
|
590 |
|
|
|
591 |
if ($this->info['http_code'] !== 200) {
|
|
|
592 |
$debuginfo = !empty($this->error) ? $this->error : $response;
|
|
|
593 |
throw new moodle_exception('oauth2upgradetokenerror', 'core_error', '', $this->info['http_code'], $debuginfo);
|
|
|
594 |
}
|
|
|
595 |
|
|
|
596 |
$r = json_decode($response);
|
|
|
597 |
|
|
|
598 |
if (is_null($r)) {
|
|
|
599 |
throw new moodle_exception("Could not decode JSON token response");
|
|
|
600 |
}
|
|
|
601 |
|
|
|
602 |
if (!empty($r->error)) {
|
|
|
603 |
throw new moodle_exception($r->error . ' ' . $r->error_description);
|
|
|
604 |
}
|
|
|
605 |
|
|
|
606 |
if (!isset($r->access_token)) {
|
|
|
607 |
return false;
|
|
|
608 |
}
|
|
|
609 |
|
|
|
610 |
if (isset($r->refresh_token)) {
|
|
|
611 |
$this->refreshtoken = $r->refresh_token;
|
|
|
612 |
}
|
|
|
613 |
|
|
|
614 |
// Store the token an expiry time.
|
|
|
615 |
$accesstoken = new stdClass;
|
|
|
616 |
$accesstoken->token = $r->access_token;
|
|
|
617 |
if (isset($r->expires_in)) {
|
|
|
618 |
// Expires 10 seconds before actual expiry.
|
|
|
619 |
$accesstoken->expires = (time() + ($r->expires_in - 10));
|
|
|
620 |
}
|
|
|
621 |
$accesstoken->scope = $this->scope;
|
|
|
622 |
// Also add the scopes.
|
|
|
623 |
self::$upgradedcodes[] = $code;
|
|
|
624 |
$this->store_token($accesstoken);
|
|
|
625 |
|
|
|
626 |
return true;
|
|
|
627 |
}
|
|
|
628 |
|
|
|
629 |
/**
|
|
|
630 |
* Logs out of a oauth request, clearing any stored tokens
|
|
|
631 |
*/
|
|
|
632 |
public function log_out() {
|
|
|
633 |
$this->store_token(null);
|
|
|
634 |
}
|
|
|
635 |
|
|
|
636 |
/**
|
|
|
637 |
* Make a HTTP request, adding the access token we have
|
|
|
638 |
*
|
|
|
639 |
* @param string $url The URL to request
|
|
|
640 |
* @param array $options
|
|
|
641 |
* @param mixed $acceptheader mimetype (as string) or false to skip sending an accept header.
|
|
|
642 |
* @return string
|
|
|
643 |
*/
|
|
|
644 |
protected function request($url, $options = array(), $acceptheader = 'application/json') {
|
|
|
645 |
$murl = new moodle_url($url);
|
|
|
646 |
|
|
|
647 |
if ($this->accesstoken) {
|
|
|
648 |
if ($this->use_http_get()) {
|
|
|
649 |
// If using HTTP GET add as a parameter.
|
|
|
650 |
$murl->param('access_token', $this->accesstoken->token);
|
|
|
651 |
} else {
|
|
|
652 |
$this->setHeader('Authorization: Bearer '.$this->accesstoken->token);
|
|
|
653 |
}
|
|
|
654 |
}
|
|
|
655 |
|
|
|
656 |
if ($acceptheader) {
|
|
|
657 |
$this->setHeader('Accept: ' . $acceptheader);
|
|
|
658 |
}
|
|
|
659 |
|
|
|
660 |
$response = parent::request($murl->out(false), $options);
|
|
|
661 |
|
|
|
662 |
$this->resetHeader();
|
|
|
663 |
|
|
|
664 |
return $response;
|
|
|
665 |
}
|
|
|
666 |
|
|
|
667 |
/**
|
|
|
668 |
* Multiple HTTP Requests
|
|
|
669 |
* This function could run multi-requests in parallel.
|
|
|
670 |
*
|
|
|
671 |
* @param array $requests An array of files to request
|
|
|
672 |
* @param array $options An array of options to set
|
|
|
673 |
* @return array An array of results
|
|
|
674 |
*/
|
|
|
675 |
protected function multi($requests, $options = array()) {
|
|
|
676 |
if ($this->accesstoken) {
|
|
|
677 |
$this->setHeader('Authorization: Bearer '.$this->accesstoken->token);
|
|
|
678 |
}
|
|
|
679 |
return parent::multi($requests, $options);
|
|
|
680 |
}
|
|
|
681 |
|
|
|
682 |
/**
|
|
|
683 |
* Returns the tokenname for the access_token to be stored
|
|
|
684 |
* through multiple requests.
|
|
|
685 |
*
|
|
|
686 |
* The default implentation is to use the classname combiend
|
|
|
687 |
* with the scope.
|
|
|
688 |
*
|
|
|
689 |
* @return string tokenname for prefernce storage
|
|
|
690 |
*/
|
|
|
691 |
protected function get_tokenname() {
|
|
|
692 |
// This is unusual but should work for most purposes.
|
|
|
693 |
return get_class($this).'-'.md5($this->scope);
|
|
|
694 |
}
|
|
|
695 |
|
|
|
696 |
/**
|
|
|
697 |
* Store a token between requests. Currently uses
|
|
|
698 |
* session named by get_tokenname
|
|
|
699 |
*
|
|
|
700 |
* @param stdClass|null $token token object to store or null to clear
|
|
|
701 |
*/
|
|
|
702 |
protected function store_token($token) {
|
|
|
703 |
global $SESSION;
|
|
|
704 |
|
|
|
705 |
$this->accesstoken = $token;
|
|
|
706 |
$name = $this->get_tokenname();
|
|
|
707 |
|
|
|
708 |
if ($token !== null) {
|
|
|
709 |
$SESSION->{$name} = $token;
|
|
|
710 |
} else {
|
|
|
711 |
unset($SESSION->{$name});
|
|
|
712 |
}
|
|
|
713 |
}
|
|
|
714 |
|
|
|
715 |
/**
|
|
|
716 |
* Get a refresh token!!!
|
|
|
717 |
*
|
|
|
718 |
* @return string
|
|
|
719 |
*/
|
|
|
720 |
public function get_refresh_token() {
|
|
|
721 |
return $this->refreshtoken;
|
|
|
722 |
}
|
|
|
723 |
|
|
|
724 |
/**
|
|
|
725 |
* Retrieve a token stored.
|
|
|
726 |
*
|
|
|
727 |
* @return stdClass|null token object
|
|
|
728 |
*/
|
|
|
729 |
protected function get_stored_token() {
|
|
|
730 |
global $SESSION;
|
|
|
731 |
|
|
|
732 |
$name = $this->get_tokenname();
|
|
|
733 |
|
|
|
734 |
if (isset($SESSION->{$name})) {
|
|
|
735 |
return $SESSION->{$name};
|
|
|
736 |
}
|
|
|
737 |
|
|
|
738 |
return null;
|
|
|
739 |
}
|
|
|
740 |
|
|
|
741 |
/**
|
|
|
742 |
* Get access token object.
|
|
|
743 |
*
|
|
|
744 |
* This is just a getter to read the private property.
|
|
|
745 |
*
|
|
|
746 |
* @return stdClass
|
|
|
747 |
*/
|
|
|
748 |
public function get_accesstoken() {
|
|
|
749 |
return $this->accesstoken;
|
|
|
750 |
}
|
|
|
751 |
|
|
|
752 |
/**
|
|
|
753 |
* Get the client ID.
|
|
|
754 |
*
|
|
|
755 |
* This is just a getter to read the private property.
|
|
|
756 |
*
|
|
|
757 |
* @return string
|
|
|
758 |
*/
|
|
|
759 |
public function get_clientid() {
|
|
|
760 |
return $this->clientid;
|
|
|
761 |
}
|
|
|
762 |
|
|
|
763 |
/**
|
|
|
764 |
* Get the client secret.
|
|
|
765 |
*
|
|
|
766 |
* This is just a getter to read the private property.
|
|
|
767 |
*
|
|
|
768 |
* @return string
|
|
|
769 |
*/
|
|
|
770 |
public function get_clientsecret() {
|
|
|
771 |
return $this->clientsecret;
|
|
|
772 |
}
|
|
|
773 |
|
|
|
774 |
/**
|
|
|
775 |
* Should HTTP GET be used instead of POST?
|
|
|
776 |
* Some APIs do not support POST and want oauth to use
|
|
|
777 |
* GET instead (with the auth_token passed as a GET param).
|
|
|
778 |
*
|
|
|
779 |
* @return bool true if GET should be used
|
|
|
780 |
*/
|
|
|
781 |
protected function use_http_get() {
|
|
|
782 |
return false;
|
|
|
783 |
}
|
|
|
784 |
}
|