5e70fe57039a14d598895ea6a2f2559c4d05d463
[moodle.git] / badges / classes / backpack_api_mapping.php
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/>.
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  */
26 namespace core_badges;
28 defined('MOODLE_INTERNAL') || die();
30 global $CFG;
31 require_once($CFG->libdir . '/filelib.php');
33 use context_system;
34 use core_badges\external\assertion_exporter;
35 use core_badges\external\collection_exporter;
36 use core_badges\external\issuer_exporter;
37 use core_badges\external\badgeclass_exporter;
38 use curl;
40 /**
41  * Represent a single method for the remote api.
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
46  */
47 class backpack_api_mapping {
49     /** @var string The action of this method. */
50     public $action;
52     /** @var string The base url of this backpack. */
53     private $url;
55     /** @var array List of parameters for this method. */
56     public $params;
58     /** @var string Name of a class to export parameters for this method. */
59     public $requestexporter;
61     /** @var string Name of a class to export response for this method. */
62     public $responseexporter;
64     /** @var boolean This method returns an array of responses. */
65     public $multiple;
67     /** @var string get or post methods. */
68     public $method;
70     /** @var boolean json decode the response. */
71     public $json;
73     /** @var boolean Authentication is required for this request. */
74     public $authrequired;
76     /** @var boolean Differentiate the function that can be called on a user backpack or a site backpack. */
77     private $isuserbackpack;
79     /** @var string Error string from authentication request. */
80     private static $authenticationerror = '';
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     }
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     }
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     }
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     }
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     }
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);
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);
176         return $url;
177     }
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;
190         if ($this->method == 'get') {
191             return '';
192         }
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                 } else if ($value == '[PARAM]') {
213                     $request[$key] = is_array($param) ? $param[0] : $param;
214                 }
215             }
216         }
217         $context = context_system::instance();
218         $exporter = $this->requestexporter;
219         $output = $PAGE->get_renderer('core', 'badges');
220         if (!empty($exporter)) {
221             $exporterinstance = new $exporter($value, ['context' => $context]);
222             $request = $exporterinstance->export($output);
223         }
224         if ($this->json) {
225             return json_encode($request);
226         }
227         return $request;
228     }
230     /**
231      * Read the response from a V1 user request and save the userID.
232      *
233      * @param string $response The request response.
234      * @param integer $backpackid The backpack id.
235      * @return mixed
236      */
237     private function convert_email_response($response, $backpackid) {
238         global $SESSION;
240         if (isset($response->status) && $response->status == 'okay') {
242             // Remember the tokens.
243             $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
244             $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
246             $SESSION->$useridkey = $response->userId;
247             $SESSION->$backpackidkey = $backpackid;
248             return $response->userId;
249         }
250         if (!empty($response->error)) {
251             self::set_authentication_error($response->error);
252         }
253         return false;
254     }
256     /**
257      * Get the user id from a previous user request.
258      *
259      * @return integer
260      */
261     private function get_auth_user_id() {
262         global $USER;
264         if ($this->isuserbackpack) {
265             return $USER->id;
266         } else {
267             // The access tokens for the system backpack are shared.
268             return -1;
269         }
270     }
272     /**
273      * Parse the response from an openbadges 2 login.
274      *
275      * @param string $response The request response data.
276      * @param integer $backpackid The id of the backpack.
277      * @return mixed
278      */
279     private function oauth_token_response($response, $backpackid) {
280         global $SESSION;
282         if (isset($response->access_token) && isset($response->refresh_token)) {
283             // Remember the tokens.
284             $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
285             $refreshkey = $this->get_token_key(BADGE_REFRESH_TOKEN);
286             $expireskey = $this->get_token_key(BADGE_EXPIRES_TOKEN);
287             $useridkey = $this->get_token_key(BADGE_USER_ID_TOKEN);
288             $backpackidkey = $this->get_token_key(BADGE_BACKPACK_ID_TOKEN);
289             if (isset($response->expires_in)) {
290                 $timeout = $response->expires_in;
291             } else {
292                 $timeout = 15 * 60; // 15 minute timeout if none set.
293             }
294             $expires = $timeout + time();
296             $SESSION->$expireskey = $expires;
297             $SESSION->$useridkey = $this->get_auth_user_id();
298             $SESSION->$accesskey = $response->access_token;
299             $SESSION->$refreshkey = $response->refresh_token;
300             $SESSION->$backpackidkey = $backpackid;
301             return -1;
302         } else if (isset($response->error_description)) {
303             self::set_authentication_error($response->error_description);
304         }
305         return $response;
306     }
308     /**
309      * Standard options used for all curl requests.
310      *
311      * @return array
312      */
313     private function get_curl_options() {
314         return array(
315             'FRESH_CONNECT'     => true,
316             'RETURNTRANSFER'    => true,
317             'FOLLOWLOCATION'    => true,
318             'FORBID_REUSE'      => true,
319             'HEADER'            => 0,
320             'CONNECTTIMEOUT'    => 3,
321             'CONNECTTIMEOUT'    => 3,
322             // Follow redirects with the same type of request when sent 301, or 302 redirects.
323             'CURLOPT_POSTREDIR' => 3,
324         );
325     }
327     /**
328      * Make an api request and parse the response.
329      *
330      * @param string $apiurl Raw request url.
331      * @param string $urlparam1 Parameter for the request.
332      * @param string $urlparam2 Parameter for the request.
333      * @param string $email User email for authentication.
334      * @param string $password for authentication.
335      * @param mixed $postparam Raw data for the post body.
336      * @param string $backpackid the id of the backpack to use.
337      * @return mixed
338      */
339     public function request($apiurl, $urlparam1, $urlparam2, $email, $password, $postparam, $backpackid) {
340         global $SESSION, $PAGE;
342         $curl = new curl();
344         $url = $this->get_url($apiurl, $urlparam1, $urlparam2);
346         if ($this->authrequired) {
347             $accesskey = $this->get_token_key(BADGE_ACCESS_TOKEN);
348             if (isset($SESSION->$accesskey)) {
349                 $token = $SESSION->$accesskey;
350                 $curl->setHeader('Authorization: Bearer ' . $token);
351             }
352         }
353         if ($this->json) {
354             $curl->setHeader(array('Content-type: application/json'));
355         }
356         $curl->setHeader(array('Accept: application/json', 'Expect:'));
357         $options = $this->get_curl_options();
359         $post = $this->get_post_params($email, $password, $postparam);
361         if ($this->method == 'get') {
362             $response = $curl->get($url, $post, $options);
363         } else if ($this->method == 'post') {
364             $response = $curl->post($url, $post, $options);
365         }
366         $response = json_decode($response);
367         if (isset($response->result)) {
368             $response = $response->result;
369         }
370         $context = context_system::instance();
371         $exporter = $this->responseexporter;
372         if (class_exists($exporter)) {
373             $output = $PAGE->get_renderer('core', 'badges');
374             if (!$this->multiple) {
375                 if (count($response)) {
376                     $response = $response[0];
377                 }
378                 if (empty($response)) {
379                     return null;
380                 }
381                 $apidata = $exporter::map_external_data($response, $this->backpackapiversion);
382                 $exporterinstance = new $exporter($apidata, ['context' => $context]);
383                 $data = $exporterinstance->export($output);
384                 return $data;
385             } else {
386                 $multiple = [];
387                 if (empty($response)) {
388                     return $multiple;
389                 }
390                 foreach ($response as $data) {
391                     $apidata = $exporter::map_external_data($data, $this->backpackapiversion);
392                     $exporterinstance = new $exporter($apidata, ['context' => $context]);
393                     $multiple[] = $exporterinstance->export($output);
394                 }
395                 return $multiple;
396             }
397         } else if (method_exists($this, $exporter)) {
398             return $this->$exporter($response, $backpackid);
399         }
400         return $response;
401     }