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 * This file contains an abstract definition of an LTI service
19 *
20 * @package    mod_lti
21 * @copyright  2014 Vital Source Technologies http://vitalsource.com
22 * @author     Stephen Vickers
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26
27namespace mod_lti\local\ltiservice;
28
29defined('MOODLE_INTERNAL') || die();
30
31global $CFG;
32require_once($CFG->dirroot . '/mod/lti/locallib.php');
33require_once($CFG->dirroot . '/mod/lti/OAuthBody.php');
34
35// TODO: Switch to core oauthlib once implemented - MDL-30149.
36use moodle\mod\lti as lti;
37use stdClass;
38
39
40/**
41 * The mod_lti\local\ltiservice\service_base class.
42 *
43 * @package    mod_lti
44 * @since      Moodle 2.8
45 * @copyright  2014 Vital Source Technologies http://vitalsource.com
46 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47 */
48abstract class service_base {
49
50    /** Label representing an LTI 2 message type */
51    const LTI_VERSION2P0 = 'LTI-2p0';
52    /** Service enabled */
53    const SERVICE_ENABLED = 1;
54
55    /** @var string ID for the service. */
56    protected $id;
57    /** @var string Human readable name for the service. */
58    protected $name;
59    /** @var boolean <code>true</code> if requests for this service do not need to be signed. */
60    protected $unsigned;
61    /** @var stdClass Tool proxy object for the current service request. */
62    private $toolproxy;
63    /** @var stdClass LTI type object for the current service request. */
64    private $type;
65    /** @var array LTI type config array for the current service request. */
66    private $typeconfig;
67    /** @var array Instances of the resources associated with this service. */
68    protected $resources;
69
70
71    /**
72     * Class constructor.
73     */
74    public function __construct() {
75
76        $this->id = null;
77        $this->name = null;
78        $this->unsigned = false;
79        $this->toolproxy = null;
80        $this->type = null;
81        $this->typeconfig = null;
82        $this->resources = null;
83
84    }
85
86    /**
87     * Get the service ID.
88     *
89     * @return string
90     */
91    public function get_id() {
92
93        return $this->id;
94
95    }
96
97    /**
98     * Get the service compoent ID.
99     *
100     * @return string
101     */
102    public function get_component_id() {
103
104        return 'ltiservice_' . $this->id;
105
106    }
107
108    /**
109     * Get the service name.
110     *
111     * @return string
112     */
113    public function get_name() {
114
115        return $this->name;
116
117    }
118
119    /**
120     * Get whether the service requests need to be signed.
121     *
122     * @return boolean
123     */
124    public function is_unsigned() {
125
126        return $this->unsigned;
127
128    }
129
130    /**
131     * Get the tool proxy object.
132     *
133     * @return stdClass
134     */
135    public function get_tool_proxy() {
136
137        return $this->toolproxy;
138
139    }
140
141    /**
142     * Set the tool proxy object.
143     *
144     * @param object $toolproxy The tool proxy for this service request
145     *
146     * @var stdClass
147     */
148    public function set_tool_proxy($toolproxy) {
149
150        $this->toolproxy = $toolproxy;
151
152    }
153
154    /**
155     * Get the type object.
156     *
157     * @return stdClass
158     */
159    public function get_type() {
160
161        return $this->type;
162
163    }
164
165    /**
166     * Set the LTI type object.
167     *
168     * @param object $type The LTI type for this service request
169     *
170     * @var stdClass
171     */
172    public function set_type($type) {
173
174        $this->type = $type;
175
176    }
177
178    /**
179     * Get the type config array.
180     *
181     * @return array|null
182     */
183    public function get_typeconfig() {
184
185        return $this->typeconfig;
186
187    }
188
189    /**
190     * Set the LTI type config object.
191     *
192     * @param array $typeconfig The LTI type config for this service request
193     *
194     * @var array
195     */
196    public function set_typeconfig($typeconfig) {
197
198        $this->typeconfig = $typeconfig;
199
200    }
201
202    /**
203     * Get the resources for this service.
204     *
205     * @return resource_base[]
206     */
207    abstract public function get_resources();
208
209    /**
210     * Get the scope(s) permitted for this service in the context of a particular tool type.
211     *
212     * A null value indicates that no scopes are required to access the service.
213     *
214     * @return array|null
215     */
216    public function get_permitted_scopes() {
217        return null;
218    }
219
220    /**
221     * Get the scope(s) permitted for this service.
222     *
223     * A null value indicates that no scopes are required to access the service.
224     *
225     * @return array|null
226     */
227    public function get_scopes() {
228        return null;
229    }
230
231    /**
232     * Returns the configuration options for this service.
233     *
234     * @param \MoodleQuickForm $mform Moodle quickform object definition
235     */
236    public function get_configuration_options(&$mform) {
237
238    }
239
240    /**
241     * Called when a new LTI Instance is added.
242     *
243     * @param object $lti LTI Instance.
244     */
245    public function instance_added(object $lti): void {
246
247    }
248
249    /**
250     * Called when a new LTI Instance is updated.
251     *
252     * @param object $lti LTI Instance.
253     */
254    public function instance_updated(object $lti): void {
255
256    }
257
258    /**
259     * Called when a new LTI Instance is deleted.
260     *
261     * @param int $id LTI Instance.
262     */
263    public function instance_deleted(int $id): void {
264
265    }
266
267    /**
268     * Set the form data when displaying the LTI Instance form.
269     *
270     * @param object $defaultvalues Default form values.
271     */
272    public function set_instance_form_values(object $defaultvalues): void {
273
274    }
275
276    /**
277     * Return an array with the names of the parameters that the service will be saving in the configuration
278     *
279     * @return array  Names list of the parameters that the service will be saving in the configuration
280     * @deprecated since Moodle 3.7 - please do not use this function any more.
281     */
282    public function get_configuration_parameter_names() {
283        debugging('get_configuration_parameter_names() has been deprecated.', DEBUG_DEVELOPER);
284        return array();
285    }
286
287    /**
288     * Default implementation will check for the existence of at least one mod_lti entry for that tool and context.
289     *
290     * It may be overridden if other inferences can be done.
291     *
292     * Ideally a Site Tool should be explicitly engaged with a course, the check on the presence of a link is a proxy
293     * to infer a Site Tool engagement until an explicit Site Tool - Course relationship exists.
294     *
295     * @param int $typeid The tool lti type id.
296     * @param int $courseid The course id.
297     * @return bool returns True if tool is used in context, false otherwise.
298     */
299    public function is_used_in_context($typeid, $courseid) {
300        global $DB;
301
302        $ok = $DB->record_exists('lti', array('course' => $courseid, 'typeid' => $typeid));
303        return $ok || $DB->record_exists('lti_types', array('course' => $courseid, 'id' => $typeid));
304    }
305
306    /**
307     * Checks if there is a site tool or a course tool for this site.
308     *
309     * @param int $typeid The tool lti type id.
310     * @param int $courseid The course id.
311     * @return bool returns True if tool is allowed in context, false otherwise.
312     */
313    public function is_allowed_in_context($typeid, $courseid) {
314        global $DB;
315
316        // Check if it is a Course tool for this course or a Site tool.
317        $type = $DB->get_record('lti_types', array('id' => $typeid));
318
319        return $type && ($type->course == $courseid || $type->course == SITEID);
320    }
321
322    /**
323     * Return an array of key/values to add to the launch parameters.
324     *
325     * @param string $messagetype  'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
326     * @param string $courseid     The course id.
327     * @param string $userid       The user id.
328     * @param string $typeid       The tool lti type id.
329     * @param string $modlti       The id of the lti activity.
330     *
331     * The type is passed to check the configuration and not return parameters for services not used.
332     *
333     * @return array Key/value pairs to add as launch parameters.
334     */
335    public function get_launch_parameters($messagetype, $courseid, $userid, $typeid, $modlti = null) {
336        return array();
337    }
338
339    /**
340     * Get the path for service requests.
341     *
342     * @return string
343     */
344    public static function get_service_path() {
345
346        $url = new \moodle_url('/mod/lti/services.php');
347
348        return $url->out(false);
349
350    }
351
352    /**
353     * Parse a string for custom substitution parameter variables supported by this service's resources.
354     *
355     * @param string $value  Value to be parsed
356     *
357     * @return string
358     */
359    public function parse_value($value) {
360
361        if (empty($this->resources)) {
362            $this->resources = $this->get_resources();
363        }
364        if (!empty($this->resources)) {
365            foreach ($this->resources as $resource) {
366                $value = $resource->parse_value($value);
367            }
368        }
369
370        return $value;
371
372    }
373
374    /**
375     * Check that the request has been properly signed and is permitted.
376     *
377     * @param string $typeid    LTI type ID
378     * @param string $body      Request body (null if none)
379     * @param string[] $scopes  Array of required scope(s) for incoming request
380     *
381     * @return boolean
382     */
383    public function check_tool($typeid, $body = null, $scopes = null) {
384
385        $ok = true;
386        $toolproxy = null;
387        $consumerkey = lti\get_oauth_key_from_headers($typeid, $scopes);
388        if ($consumerkey === false) {
389            $ok = $this->is_unsigned();
390        } else {
391            if (empty($typeid) && is_int($consumerkey)) {
392                $typeid = $consumerkey;
393            }
394            if (!empty($typeid)) {
395                $this->type = lti_get_type($typeid);
396                $this->typeconfig = lti_get_type_config($typeid);
397                $ok = !empty($this->type->id);
398                if ($ok && !empty($this->type->toolproxyid)) {
399                    $this->toolproxy = lti_get_tool_proxy($this->type->toolproxyid);
400                }
401            } else {
402                $toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
403                if ($toolproxy !== false) {
404                    $this->toolproxy = $toolproxy;
405                }
406            }
407        }
408        if ($ok && is_string($consumerkey)) {
409            if (!empty($this->toolproxy)) {
410                $key = $this->toolproxy->guid;
411                $secret = $this->toolproxy->secret;
412            } else {
413                $key = $this->typeconfig['resourcekey'];
414                $secret = $this->typeconfig['password'];
415            }
416            if (!$this->is_unsigned() && ($key == $consumerkey)) {
417                $ok = $this->check_signature($key, $secret, $body);
418            } else {
419                $ok = $this->is_unsigned();
420            }
421        }
422
423        return $ok;
424
425    }
426
427    /**
428     * Check that the request has been properly signed.
429     *
430     * @param string $toolproxyguid  Tool Proxy GUID
431     * @param string $body           Request body (null if none)
432     *
433     * @return boolean
434     * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
435     * @see service_base::check_tool()
436     */
437    public function check_tool_proxy($toolproxyguid, $body = null) {
438
439        debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
440                  'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
441        $ok = false;
442        $toolproxy = null;
443        $consumerkey = lti\get_oauth_key_from_headers();
444        if (empty($toolproxyguid)) {
445            $toolproxyguid = $consumerkey;
446        }
447
448        if (!empty($toolproxyguid)) {
449            $toolproxy = lti_get_tool_proxy_from_guid($toolproxyguid);
450            if ($toolproxy !== false) {
451                if (!$this->is_unsigned() && ($toolproxy->guid == $consumerkey)) {
452                    $ok = $this->check_signature($toolproxy->guid, $toolproxy->secret, $body);
453                } else {
454                    $ok = $this->is_unsigned();
455                }
456            }
457        }
458        if ($ok) {
459            $this->toolproxy = $toolproxy;
460        }
461
462        return $ok;
463
464    }
465
466    /**
467     * Check that the request has been properly signed.
468     *
469     * @param int $typeid The tool id
470     * @param int $courseid The course we are at
471     * @param string $body Request body (null if none)
472     *
473     * @return bool
474     * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
475     * @see service_base::check_tool()
476     */
477    public function check_type($typeid, $courseid, $body = null) {
478        debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
479                  'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
480        $ok = false;
481        $tool = null;
482        $consumerkey = lti\get_oauth_key_from_headers();
483        if (empty($typeid)) {
484            return $ok;
485        } else if ($this->is_allowed_in_context($typeid, $courseid)) {
486            $tool = lti_get_type_type_config($typeid);
487            if ($tool !== false) {
488                if (!$this->is_unsigned() && ($tool->lti_resourcekey == $consumerkey)) {
489                    $ok = $this->check_signature($tool->lti_resourcekey, $tool->lti_password, $body);
490                } else {
491                    $ok = $this->is_unsigned();
492                }
493            }
494        }
495        return $ok;
496    }
497
498    /**
499     * Check the request signature.
500     *
501     * @param string $consumerkey    Consumer key
502     * @param string $secret         Shared secret
503     * @param string $body           Request body
504     *
505     * @return boolean
506     */
507    private function check_signature($consumerkey, $secret, $body) {
508
509        $ok = true;
510        try {
511            // TODO: Switch to core oauthlib once implemented - MDL-30149.
512            lti\handle_oauth_body_post($consumerkey, $secret, $body);
513        } catch (\Exception $e) {
514            debugging($e->getMessage() . "\n");
515            $ok = false;
516        }
517
518        return $ok;
519
520    }
521}