Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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 the launch data is created, offering a possibility to alter the
 260       * target link URI.
 261       *
 262       * @param string $messagetype message type for this launch
 263       * @param string $targetlinkuri current target link uri
 264       * @param null|string $customstr concatenated list of custom parameters
 265       * @param int $courseid
 266       * @param null|object $lti LTI Instance.
 267       *
 268       * @return array containing the target link URL and the custom params string to use.
 269       */
 270      public function override_endpoint(string $messagetype, string $targetlinkuri,
 271              ?string $customstr, int $courseid, ?object $lti = null): array {
 272          return [$targetlinkuri, $customstr];
 273      }
 274  
 275      /**
 276       * Called when a new LTI Instance is deleted.
 277       *
 278       * @param int $id LTI Instance.
 279       */
 280      public function instance_deleted(int $id): void {
 281  
 282      }
 283  
 284      /**
 285       * Set the form data when displaying the LTI Instance form.
 286       *
 287       * @param object $defaultvalues Default form values.
 288       */
 289      public function set_instance_form_values(object $defaultvalues): void {
 290  
 291      }
 292  
 293      /**
 294       * Return an array with the names of the parameters that the service will be saving in the configuration
 295       *
 296       * @return array  Names list of the parameters that the service will be saving in the configuration
 297       * @deprecated since Moodle 3.7 - please do not use this function any more.
 298       */
 299      public function get_configuration_parameter_names() {
 300          debugging('get_configuration_parameter_names() has been deprecated.', DEBUG_DEVELOPER);
 301          return array();
 302      }
 303  
 304      /**
 305       * Default implementation will check for the existence of at least one mod_lti entry for that tool and context.
 306       *
 307       * It may be overridden if other inferences can be done.
 308       *
 309       * Ideally a Site Tool should be explicitly engaged with a course, the check on the presence of a link is a proxy
 310       * to infer a Site Tool engagement until an explicit Site Tool - Course relationship exists.
 311       *
 312       * @param int $typeid The tool lti type id.
 313       * @param int $courseid The course id.
 314       * @return bool returns True if tool is used in context, false otherwise.
 315       */
 316      public function is_used_in_context($typeid, $courseid) {
 317          global $DB;
 318  
 319          $ok = $DB->record_exists('lti', array('course' => $courseid, 'typeid' => $typeid));
 320          return $ok || $DB->record_exists('lti_types', array('course' => $courseid, 'id' => $typeid));
 321      }
 322  
 323      /**
 324       * Checks if there is a site tool or a course tool for this site.
 325       *
 326       * @param int $typeid The tool lti type id.
 327       * @param int $courseid The course id.
 328       * @return bool returns True if tool is allowed in context, false otherwise.
 329       */
 330      public function is_allowed_in_context($typeid, $courseid) {
 331          global $DB;
 332  
 333          // Check if it is a Course tool for this course or a Site tool.
 334          $type = $DB->get_record('lti_types', array('id' => $typeid));
 335  
 336          return $type && ($type->course == $courseid || $type->course == SITEID);
 337      }
 338  
 339      /**
 340       * Return an array of key/values to add to the launch parameters.
 341       *
 342       * @param string $messagetype  'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
 343       * @param string $courseid     The course id.
 344       * @param string $userid       The user id.
 345       * @param string $typeid       The tool lti type id.
 346       * @param string $modlti       The id of the lti activity.
 347       *
 348       * The type is passed to check the configuration and not return parameters for services not used.
 349       *
 350       * @return array Key/value pairs to add as launch parameters.
 351       */
 352      public function get_launch_parameters($messagetype, $courseid, $userid, $typeid, $modlti = null) {
 353          return array();
 354      }
 355  
 356      /**
 357       * Return an array of key/claim mapping allowing LTI 1.1 custom parameters
 358       * to be transformed to LTI 1.3 claims.
 359       *
 360       * @return array Key/value pairs of params to claim mapping.
 361       */
 362      public function get_jwt_claim_mappings(): array {
 363          return [];
 364      }
 365  
 366      /**
 367       * Get the path for service requests.
 368       *
 369       * @return string
 370       */
 371      public static function get_service_path() {
 372  
 373          $url = new \moodle_url('/mod/lti/services.php');
 374  
 375          return $url->out(false);
 376  
 377      }
 378  
 379      /**
 380       * Parse a string for custom substitution parameter variables supported by this service's resources.
 381       *
 382       * @param string $value  Value to be parsed
 383       *
 384       * @return string
 385       */
 386      public function parse_value($value) {
 387  
 388          if (empty($this->resources)) {
 389              $this->resources = $this->get_resources();
 390          }
 391          if (!empty($this->resources)) {
 392              foreach ($this->resources as $resource) {
 393                  $value = $resource->parse_value($value);
 394              }
 395          }
 396  
 397          return $value;
 398  
 399      }
 400  
 401      /**
 402       * Check that the request has been properly signed and is permitted.
 403       *
 404       * @param string $typeid    LTI type ID
 405       * @param string $body      Request body (null if none)
 406       * @param string[] $scopes  Array of required scope(s) for incoming request
 407       *
 408       * @return boolean
 409       */
 410      public function check_tool($typeid, $body = null, $scopes = null) {
 411  
 412          $ok = true;
 413          $toolproxy = null;
 414          $consumerkey = lti\get_oauth_key_from_headers($typeid, $scopes);
 415          if ($consumerkey === false) {
 416              $ok = $this->is_unsigned();
 417          } else {
 418              if (empty($typeid) && is_int($consumerkey)) {
 419                  $typeid = $consumerkey;
 420              }
 421              if (!empty($typeid)) {
 422                  $this->type = lti_get_type($typeid);
 423                  $this->typeconfig = lti_get_type_config($typeid);
 424                  $ok = !empty($this->type->id);
 425                  if ($ok && !empty($this->type->toolproxyid)) {
 426                      $this->toolproxy = lti_get_tool_proxy($this->type->toolproxyid);
 427                  }
 428              } else {
 429                  $toolproxy = lti_get_tool_proxy_from_guid($consumerkey);
 430                  if ($toolproxy !== false) {
 431                      $this->toolproxy = $toolproxy;
 432                  }
 433              }
 434          }
 435          if ($ok && is_string($consumerkey)) {
 436              if (!empty($this->toolproxy)) {
 437                  $key = $this->toolproxy->guid;
 438                  $secret = $this->toolproxy->secret;
 439              } else {
 440                  $key = $this->typeconfig['resourcekey'];
 441                  $secret = $this->typeconfig['password'];
 442              }
 443              if (!$this->is_unsigned() && ($key == $consumerkey)) {
 444                  $ok = $this->check_signature($key, $secret, $body);
 445              } else {
 446                  $ok = $this->is_unsigned();
 447              }
 448          }
 449  
 450          return $ok;
 451  
 452      }
 453  
 454      /**
 455       * Check that the request has been properly signed.
 456       *
 457       * @param string $toolproxyguid  Tool Proxy GUID
 458       * @param string $body           Request body (null if none)
 459       *
 460       * @return boolean
 461       * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
 462       * @see service_base::check_tool()
 463       */
 464      public function check_tool_proxy($toolproxyguid, $body = null) {
 465  
 466          debugging('check_tool_proxy() is deprecated to allow LTI 1 connections to support services. ' .
 467                    'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
 468          $ok = false;
 469          $toolproxy = null;
 470          $consumerkey = lti\get_oauth_key_from_headers();
 471          if (empty($toolproxyguid)) {
 472              $toolproxyguid = $consumerkey;
 473          }
 474  
 475          if (!empty($toolproxyguid)) {
 476              $toolproxy = lti_get_tool_proxy_from_guid($toolproxyguid);
 477              if ($toolproxy !== false) {
 478                  if (!$this->is_unsigned() && ($toolproxy->guid == $consumerkey)) {
 479                      $ok = $this->check_signature($toolproxy->guid, $toolproxy->secret, $body);
 480                  } else {
 481                      $ok = $this->is_unsigned();
 482                  }
 483              }
 484          }
 485          if ($ok) {
 486              $this->toolproxy = $toolproxy;
 487          }
 488  
 489          return $ok;
 490  
 491      }
 492  
 493      /**
 494       * Check that the request has been properly signed.
 495       *
 496       * @param int $typeid The tool id
 497       * @param int $courseid The course we are at
 498       * @param string $body Request body (null if none)
 499       *
 500       * @return bool
 501       * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
 502       * @see service_base::check_tool()
 503       */
 504      public function check_type($typeid, $courseid, $body = null) {
 505          debugging('check_type() is deprecated to allow LTI 1 connections to support services. ' .
 506                    'Please use service_base::check_tool() instead.', DEBUG_DEVELOPER);
 507          $ok = false;
 508          $tool = null;
 509          $consumerkey = lti\get_oauth_key_from_headers();
 510          if (empty($typeid)) {
 511              return $ok;
 512          } else if ($this->is_allowed_in_context($typeid, $courseid)) {
 513              $tool = lti_get_type_type_config($typeid);
 514              if ($tool !== false) {
 515                  if (!$this->is_unsigned() && ($tool->lti_resourcekey == $consumerkey)) {
 516                      $ok = $this->check_signature($tool->lti_resourcekey, $tool->lti_password, $body);
 517                  } else {
 518                      $ok = $this->is_unsigned();
 519                  }
 520              }
 521          }
 522          return $ok;
 523      }
 524  
 525      /**
 526       * Check the request signature.
 527       *
 528       * @param string $consumerkey    Consumer key
 529       * @param string $secret         Shared secret
 530       * @param string $body           Request body
 531       *
 532       * @return boolean
 533       */
 534      private function check_signature($consumerkey, $secret, $body) {
 535  
 536          $ok = true;
 537          try {
 538              // TODO: Switch to core oauthlib once implemented - MDL-30149.
 539              lti\handle_oauth_body_post($consumerkey, $secret, $body);
 540          } catch (\Exception $e) {
 541              debugging($e->getMessage() . "\n");
 542              $ok = false;
 543          }
 544  
 545          return $ok;
 546  
 547      }
 548  }