Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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