Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]

   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  
  27  namespace mod_lti\local\ltiservice;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  global $CFG;
  32  require_once($CFG->dirroot . '/mod/lti/locallib.php');
  33  require_once($CFG->dirroot . '/mod/lti/OAuthBody.php');
  34  
  35  // TODO: Switch to core oauthlib once implemented - MDL-30149.
  36  use moodle\mod\lti as lti;
  37  use 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   */
  48  abstract 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       * Return an array of key/claim mapping allowing LTI 1.1 custom parameters
 341       * to be transformed to LTI 1.3 claims.
 342       *
 343       * @return array Key/value pairs of params to claim mapping.
 344       */
 345      public function get_jwt_claim_mappings(): array {
 346          return [];
 347      }
 348  
 349      /**
 350       * Get the path for service requests.
 351       *
 352       * @return string
 353       */
 354      public static function get_service_path() {
 355  
 356          $url = new \moodle_url('/mod/lti/services.php');
 357  
 358          return $url->out(false);
 359  
 360      }
 361  
 362      /**
 363       * Parse a string for custom substitution parameter variables supported by this service's resources.
 364       *
 365       * @param string $value  Value to be parsed
 366       *
 367       * @return string
 368       */
 369      public function parse_value($value) {
 370  
 371          if (empty($this->resources)) {
 372              $this->resources = $this->get_resources();
 373          }
 374          if (!empty($this->resources)) {
 375              foreach ($this->resources as $resource) {
 376                  $value = $resource->parse_value($value);
 377              }
 378          }
 379  
 380          return $value;
 381  
 382      }
 383  
 384      /**
 385       * Check that the request has been properly signed and is permitted.
 386       *
 387       * @param string $typeid    LTI type ID
 388       * @param string $body      Request body (null if none)
 389       * @param string[] $scopes  Array of required scope(s) for incoming request
 390       *
 391       * @return boolean
 392       */
 393      public function check_tool($typeid, $body = null, $scopes = null) {
 394  
 395          $ok = true;
 396          $toolproxy = null;
 397          $consumerkey = lti\get_oauth_key_from_headers($typeid, $scopes);
 398          if ($consumerkey === false) {
 399              $ok = $this->is_unsigned();
 400          } else {
 401              if (empty($typeid) && is_int($consumerkey)) {
 402                  $typeid = $consumerkey;
 403              }
 404              if (!empty($typeid)) {
 405                  $this->type = lti_get_type($typeid);
 406                  $this->typeconfig = lti_get_type_config($typeid);
 407                  $ok = !empty($this->type->id);
 408                  if ($ok && !empty($this->type->toolproxyid)) {
 409                      $this->toolproxy = lti_get_tool_proxy($this->type->toolproxyid);
 410                  }
 411              } else {
 412                  $toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
 413                  if ($toolproxy !== false) {
 414                      $this->toolproxy = $toolproxy;
 415                  }
 416              }
 417          }
 418          if ($ok && is_string($consumerkey)) {
 419              if (!empty($this->toolproxy)) {
 420                  $key = $this->toolproxy->guid;
 421                  $secret = $this->toolproxy->secret;
 422              } else {
 423                  $key = $this->typeconfig['resourcekey'];
 424                  $secret = $this->typeconfig['password'];
 425              }
 426              if (!$this->is_unsigned() && ($key == $consumerkey)) {
 427                  $ok = $this->check_signature($key, $secret, $body);
 428              } else {
 429                  $ok = $this->is_unsigned();
 430              }
 431          }
 432  
 433          return $ok;
 434  
 435      }
 436  
 437      /**
 438       * Check that the request has been properly signed.
 439       *
 440       * @param string $toolproxyguid  Tool Proxy GUID
 441       * @param string $body           Request body (null if none)
 442       *
 443       * @return boolean
 444       * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
 445       * @see service_base::check_tool()
 446       */
 447      public function check_tool_proxy($toolproxyguid, $body = null) {
 448  
 449          debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
 450                    'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
 451          $ok = false;
 452          $toolproxy = null;
 453          $consumerkey = lti\get_oauth_key_from_headers();
 454          if (empty($toolproxyguid)) {
 455              $toolproxyguid = $consumerkey;
 456          }
 457  
 458          if (!empty($toolproxyguid)) {
 459              $toolproxy = lti_get_tool_proxy_from_guid($toolproxyguid);
 460              if ($toolproxy !== false) {
 461                  if (!$this->is_unsigned() && ($toolproxy->guid == $consumerkey)) {
 462                      $ok = $this->check_signature($toolproxy->guid, $toolproxy->secret, $body);
 463                  } else {
 464                      $ok = $this->is_unsigned();
 465                  }
 466              }
 467          }
 468          if ($ok) {
 469              $this->toolproxy = $toolproxy;
 470          }
 471  
 472          return $ok;
 473  
 474      }
 475  
 476      /**
 477       * Check that the request has been properly signed.
 478       *
 479       * @param int $typeid The tool id
 480       * @param int $courseid The course we are at
 481       * @param string $body Request body (null if none)
 482       *
 483       * @return bool
 484       * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
 485       * @see service_base::check_tool()
 486       */
 487      public function check_type($typeid, $courseid, $body = null) {
 488          debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
 489                    'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
 490          $ok = false;
 491          $tool = null;
 492          $consumerkey = lti\get_oauth_key_from_headers();
 493          if (empty($typeid)) {
 494              return $ok;
 495          } else if ($this->is_allowed_in_context($typeid, $courseid)) {
 496              $tool = lti_get_type_type_config($typeid);
 497              if ($tool !== false) {
 498                  if (!$this->is_unsigned() && ($tool->lti_resourcekey == $consumerkey)) {
 499                      $ok = $this->check_signature($tool->lti_resourcekey, $tool->lti_password, $body);
 500                  } else {
 501                      $ok = $this->is_unsigned();
 502                  }
 503              }
 504          }
 505          return $ok;
 506      }
 507  
 508      /**
 509       * Check the request signature.
 510       *
 511       * @param string $consumerkey    Consumer key
 512       * @param string $secret         Shared secret
 513       * @param string $body           Request body
 514       *
 515       * @return boolean
 516       */
 517      private function check_signature($consumerkey, $secret, $body) {
 518  
 519          $ok = true;
 520          try {
 521              // TODO: Switch to core oauthlib once implemented - MDL-30149.
 522              lti\handle_oauth_body_post($consumerkey, $secret, $body);
 523          } catch (\Exception $e) {
 524              debugging($e->getMessage() . "\n");
 525              $ok = false;
 526          }
 527  
 528          return $ok;
 529  
 530      }
 531  }