MDL-65959 core_badges: Unrestricted user's badger account
[moodle.git] / badges / classes / backpack_api_mapping.php
CommitLineData
aae219ac
DW
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 * Represent the url for each method and the encoding of the parameters and response.
19 *
20 * @package core_badges
21 * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 * @author Yuliya Bozhko <yuliya.bozhko@totaralms.com>
24 */
25
26namespace core_badges;
27
28defined('MOODLE_INTERNAL') || die();
29
30global $CFG;
31require_once($CFG->libdir . '/filelib.php');
32
33use context_system;
34use core_badges\external\assertion_exporter;
35use core_badges\external\collection_exporter;
36use core_badges\external\issuer_exporter;
37use core_badges\external\badgeclass_exporter;
38use curl;
39
40/**
41 * Represent a single method for the remote api.
1837b1d5
DW
42 *
43 * @package core_badges
44 * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/}
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
aae219ac
DW
46 */
47class backpack_api_mapping {
48
49 /** @var string The action of this method. */
50 public $action;
51
52 /** @var string The base url of this backpack. */
53 private $url;
54
55 /** @var array List of parameters for this method. */
56 public $params;
57
58 /** @var string Name of a class to export parameters for this method. */
59 public $requestexporter;
60
61 /** @var string Name of a class to export response for this method. */
62 public $responseexporter;
63
64 /** @var boolean This method returns an array of responses. */
65 public $multiple;
66
67 /** @var string get or post methods. */
68 public $method;
69
70 /** @var boolean json decode the response. */
71 public $json;
72
73 /** @var boolean Authentication is required for this request. */
74 public $authrequired;
75
76 /** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
77 private $isuserbackpack;
78
7444ba74
DW
79 /** @var string Error string from authentication request. */
80 private static $authenticationerror = '';
81
aae219ac
DW
82 /**
83 * Create a mapping.
84 *
85 * @param string $action The action of this method.
86 * @param string $url The base url of this backpack.
87 * @param mixed $postparams List of parameters for this method.
88 * @param string $requestexporter Name of a class to export parameters for this method.
89 * @param string $responseexporter Name of a class to export response for this method.
90 * @param boolean $multiple This method returns an array of responses.
91 * @param string $method get or post methods.
92 * @param boolean $json json decode the response.
93 * @param boolean $authrequired Authentication is required for this request.
94 * @param boolean $isuserbackpack user backpack or a site backpack.
95 * @param integer $backpackapiversion OpenBadges version 1 or 2.
96 */
97 public function __construct($action, $url, $postparams, $requestexporter, $responseexporter,
98 $multiple, $method, $json, $authrequired, $isuserbackpack, $backpackapiversion) {
99 $this->action = $action;
100 $this->url = $url;
101 $this->postparams = $postparams;
102 $this->requestexporter = $requestexporter;
103 $this->responseexporter = $responseexporter;
104 $this->multiple = $multiple;
105 $this->method = $method;
106 $this->json = $json;
107 $this->authrequired = $authrequired;
108 $this->isuserbackpack = $isuserbackpack;
109 $this->backpackapiversion = $backpackapiversion;
110 }
111
112 /**
113 * Get the unique key for the token.
114 *
115 * @param string $type The type of token.
116 * @return string
117 */
118 private function get_token_key($type) {
119 $prefix = 'badges_';
120 if ($this->isuserbackpack) {
121 $prefix .= 'user_backpack_';
122 } else {
123 $prefix .= 'site_backpack_';
124 }
125 $prefix .= $type . '_token';
126 return $prefix;
127 }
128
7444ba74
DW
129 /**
130 * Remember the error message in a static variable.
131 *
132 * @param string $msg The message.
133 */
134 public static function set_authentication_error($msg) {
135 self::$authenticationerror = $msg;
136 }
137
138 /**
139 * Get the last authentication error in this request.
140 *
141 * @return string
142 */
143 public static function get_authentication_error() {
144 return self::$authenticationerror;
145 }
146
aae219ac
DW
147 /**
148 * Does the action match this mapping?
149 *
150 * @param string $action The action.
151 * @return boolean
152 */
153 public function is_match($action) {
154 return $this->action == $action;
155 }
156
157 /**
158 * Parse the method url and insert parameters.
159 *
160 * @param string $apiurl The raw apiurl.
161 * @param string $param1 The first parameter.
162 * @param string $param2 The second parameter.
163 * @return string
164 */
165 private function get_url($apiurl, $param1, $param2) {
166 $urlscheme = parse_url($apiurl, PHP_URL_SCHEME);
167 $urlhost = parse_url($apiurl, PHP_URL_HOST);
168
169 $url = $this->url;
170 $url = str_replace('[SCHEME]', $urlscheme, $url);
171 $url = str_replace('[HOST]', $urlhost, $url);
172 $url = str_replace('[URL]', $apiurl, $url);
173 $url = str_replace('[PARAM1]', $param1, $url);
174 $url = str_replace('[PARAM2]', $param2, $url);
175
176 return $url;
177 }
178
179 /**
180 * Parse the post parameters and insert replacements.
181 *
182 * @param string $email The api username.
183 * @param string $password The api password.
184 * @param string $param The parameter.
185 * @return mixed
186 */
187 private function get_post_params($email, $password, $param) {
188 global $PAGE;
189
190 if ($this->method == 'get') {
191 return '';
192 }
193
194 $request = $this->postparams;
195 if ($request === '[PARAM]') {
196 $value = $param;
197 foreach ($value as $key => $keyvalue) {
198 if (gettype($value[$key]) == 'array') {
199 $newkey = 'related_' . $key;
200 $value[$newkey] = $value[$key];
201 unset($value[$key]);
202 }
203 }
204 } else if (is_array($request)) {
205 foreach ($request as $key => $value) {
206 if ($value == '[EMAIL]') {
207 $value = $email;
208 $request[$key] = $value;
209 } else if ($value == '[PASSWORD]') {
210 $value = $password;
211 $request[$key] = $value;
212 }
213 }
214 }
215 $context = context_system::instance();
216 $exporter = $this->requestexporter;
217 $output = $PAGE->get_renderer('core', 'badges');
218 if (!empty($exporter)) {
219 $exporterinstance = new $exporter($value, ['context' => $context]);
220 $request = $exporterinstance->export($output);
221 }
222 if ($this->json) {
223 return json_encode($request);
224 }
225 return $request;
226 }
227
228 /**
229 * Read the response from a V1 user request and save the userID.
230 *
231 * @param string $response The request response.
232 * @param integer $backpackid The backpack id.
7444ba74 233 * @return mixed
aae219ac
DW
234 */
235 private function convert_email_response($response, $backpackid) {
236 global $SESSION;
237
238 if (isset($response->status) && $response->status == 'okay') {
239
240 // Remember the tokens.
241 $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
242 $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
243
244 $SESSION->$useridkey = $response->userId;
245 $SESSION->$backpackidkey = $backpackid;
246 return $response->userId;
247 }
7444ba74
DW
248 if (!empty($response->error)) {
249 self::set_authentication_error($response->error);
250 }
aae219ac
DW
251 return false;
252 }
253
254 /**
255 * Get the user id from a previous user request.
256 *
257 * @return integer
258 */
259 private function get_auth_user_id() {
260 global $USER;
261
262 if ($this->isuserbackpack) {
263 return $USER->id;
264 } else {
265 // The access tokens for the system backpack are shared.
266 return -1;
267 }
268 }
269
270 /**
271 * Parse the response from an openbadges 2 login.
272 *
273 * @param string $response The request response data.
274 * @param integer $backpackid The id of the backpack.
275 * @return mixed
276 */
277 private function oauth_token_response($response, $backpackid) {
278 global $SESSION;
279
280 if (isset($response->access_token) && isset($response->refresh_token)) {
281 // Remember the tokens.
282 $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
283 $refreshkey = $this->get_token_key(BADGE_REFRESH_TOKEN);
284 $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
285 $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
286 $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
287 if (isset($response->expires_in)) {
288 $timeout = $response->expires_in;
289 } else {
290 $timeout = 15 * 60; // 15 minute timeout if none set.
291 }
292 $expires = $timeout + time();
293
294 $SESSION->$expireskey = $expires;
295 $SESSION->$useridkey = $this->get_auth_user_id();
296 $SESSION->$accesskey = $response->access_token;
297 $SESSION->$refreshkey = $response->refresh_token;
298 $SESSION->$backpackidkey = $backpackid;
299 return -1;
7444ba74
DW
300 } else if (isset($response->error_description)) {
301 self::set_authentication_error($response->error_description);
aae219ac
DW
302 }
303 return $response;
304 }
305
306 /**
307 * Standard options used for all curl requests.
308 *
309 * @return array
310 */
311 private function get_curl_options() {
312 return array(
313 'FRESH_CONNECT' => true,
314 'RETURNTRANSFER' => true,
315 'FORBID_REUSE' => true,
316 'HEADER' => 0,
317 'CONNECTTIMEOUT' => 3,
318 'CONNECTTIMEOUT' => 3,
319 // Follow redirects with the same type of request when sent 301, or 302 redirects.
320 'CURLOPT_POSTREDIR' => 3,
321 );
322 }
323
324 /**
325 * Make an api request and parse the response.
326 *
327 * @param string $apiurl Raw request url.
328 * @param string $urlparam1 Parameter for the request.
329 * @param string $urlparam2 Parameter for the request.
330 * @param string $email User email for authentication.
331 * @param string $password for authentication.
332 * @param mixed $postparam Raw data for the post body.
333 * @param string $backpackid the id of the backpack to use.
334 * @return mixed
335 */
336 public function request($apiurl, $urlparam1, $urlparam2, $email, $password, $postparam, $backpackid) {
337 global $SESSION, $PAGE;
338
339 $curl = new curl();
340
341 $url = $this->get_url($apiurl, $urlparam1, $urlparam2);
342
343 if ($this->authrequired) {
344 $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
345 if (isset($SESSION->$accesskey)) {
346 $token = $SESSION->$accesskey;
347 $curl->setHeader('Authorization: Bearer ' . $token);
348 }
349 }
350 if ($this->json) {
351 $curl->setHeader(array('Content-type: application/json'));
352 }
353 $curl->setHeader(array('Accept: application/json', 'Expect:'));
354 $options = $this->get_curl_options();
355
356 $post = $this->get_post_params($email, $password, $postparam);
58c7d2ce 357
aae219ac
DW
358 if ($this->method == 'get') {
359 $response = $curl->get($url, $post, $options);
360 } else if ($this->method == 'post') {
361 $response = $curl->post($url, $post, $options);
362 }
363 $response = json_decode($response);
aae219ac
DW
364 if (isset($response->result)) {
365 $response = $response->result;
366 }
367 $context = context_system::instance();
368 $exporter = $this->responseexporter;
369 if (class_exists($exporter)) {
370 $output = $PAGE->get_renderer('core', 'badges');
371 if (!$this->multiple) {
372 if (count($response)) {
373 $response = $response[0];
374 }
375 if (empty($response)) {
376 return null;
377 }
378 $apidata = $exporter::map_external_data($response, $this->backpackapiversion);
379 $exporterinstance = new $exporter($apidata, ['context' => $context]);
380 $data = $exporterinstance->export($output);
381 return $data;
382 } else {
383 $multiple = [];
384 if (empty($response)) {
385 return $multiple;
386 }
387 foreach ($response as $data) {
388 $apidata = $exporter::map_external_data($data, $this->backpackapiversion);
389 $exporterinstance = new $exporter($apidata, ['context' => $context]);
390 $multiple[] = $exporterinstance->export($output);
391 }
392 return $multiple;
393 }
394 } else if (method_exists($this, $exporter)) {
395 return $this->$exporter($response, $backpackid);
396 }
397 return $response;
398 }
399}