Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   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       * 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  }