Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

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

   1  <?php
   2  
   3  namespace IMSGlobal\LTI\ToolProvider;
   4  
   5  use IMSGlobal\LTI\ToolProvider\DataConnector\DataConnector;
   6  use IMSGlobal\LTI\ToolProvider\Service;
   7  use IMSGlobal\LTI\HTTPMessage;
   8  use IMSGlobal\LTI\OAuth;
   9  use stdClass;
  10  
  11  /**
  12   * Class to represent a tool consumer
  13   *
  14   * @author  Stephen P Vickers <svickers@imsglobal.org>
  15   * @copyright  IMS Global Learning Consortium Inc
  16   * @date  2016
  17   * @version 3.0.2
  18   * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
  19   */
  20  #[\AllowDynamicProperties]
  21  class ToolConsumer
  22  {
  23  
  24  /**
  25   * Local name of tool consumer.
  26   *
  27   * @var string $name
  28   */
  29      public $name = null;
  30  /**
  31   * Shared secret.
  32   *
  33   * @var string $secret
  34   */
  35      public $secret = null;
  36  /**
  37   * LTI version (as reported by last tool consumer connection).
  38   *
  39   * @var string $ltiVersion
  40   */
  41      public $ltiVersion = null;
  42  /**
  43   * Name of tool consumer (as reported by last tool consumer connection).
  44   *
  45   * @var string $consumerName
  46   */
  47      public $consumerName = null;
  48  /**
  49   * Tool consumer version (as reported by last tool consumer connection).
  50   *
  51   * @var string $consumerVersion
  52   */
  53      public $consumerVersion = null;
  54  /**
  55   * Tool consumer GUID (as reported by first tool consumer connection).
  56   *
  57   * @var string $consumerGuid
  58   */
  59      public $consumerGuid = null;
  60  /**
  61   * Optional CSS path (as reported by last tool consumer connection).
  62   *
  63   * @var string $cssPath
  64   */
  65      public $cssPath = null;
  66  /**
  67   * Whether the tool consumer instance is protected by matching the consumer_guid value in incoming requests.
  68   *
  69   * @var boolean $protected
  70   */
  71      public $protected = false;
  72  /**
  73   * Whether the tool consumer instance is enabled to accept incoming connection requests.
  74   *
  75   * @var boolean $enabled
  76   */
  77      public $enabled = false;
  78  /**
  79   * Date/time from which the the tool consumer instance is enabled to accept incoming connection requests.
  80   *
  81   * @var int $enableFrom
  82   */
  83      public $enableFrom = null;
  84  /**
  85   * Date/time until which the tool consumer instance is enabled to accept incoming connection requests.
  86   *
  87   * @var int $enableUntil
  88   */
  89      public $enableUntil = null;
  90  /**
  91   * Date of last connection from this tool consumer.
  92   *
  93   * @var int $lastAccess
  94   */
  95      public $lastAccess = null;
  96  /**
  97   * Default scope to use when generating an Id value for a user.
  98   *
  99   * @var int $idScope
 100   */
 101      public $idScope = ToolProvider::ID_SCOPE_ID_ONLY;
 102  /**
 103   * Default email address (or email domain) to use when no email address is provided for a user.
 104   *
 105   * @var string $defaultEmail
 106   */
 107      public $defaultEmail = '';
 108  /**
 109   * Setting values (LTI parameters, custom parameters and local parameters).
 110   *
 111   * @var array $settings
 112   */
 113      public $settings = null;
 114  /**
 115   * Date/time when the object was created.
 116   *
 117   * @var int $created
 118   */
 119      public $created = null;
 120  /**
 121   * Date/time when the object was last updated.
 122   *
 123   * @var int $updated
 124   */
 125      public $updated = null;
 126  /**
 127   * The consumer profile data.
 128   *
 129   * @var stdClass
 130   */
 131      public $profile = null;
 132  
 133  /**
 134   * Consumer ID value.
 135   *
 136   * @var int $id
 137   */
 138      private $id = null;
 139  /**
 140   * Consumer key value.
 141   *
 142   * @var string $key
 143   */
 144      private $key = null;
 145  /**
 146   * Whether the settings value have changed since last saved.
 147   *
 148   * @var boolean $settingsChanged
 149   */
 150      private $settingsChanged = false;
 151  /**
 152   * Data connector object or string.
 153   *
 154   * @var mixed $dataConnector
 155   */
 156      private $dataConnector = null;
 157  
 158  /**
 159   * Class constructor.
 160   *
 161   * @param string  $key             Consumer key
 162   * @param DataConnector   $dataConnector   A data connector object
 163   * @param boolean $autoEnable      true if the tool consumers is to be enabled automatically (optional, default is false)
 164   */
 165      public function __construct($key = null, $dataConnector = null, $autoEnable = false)
 166      {
 167  
 168          $this->initialize();
 169          if (empty($dataConnector)) {
 170              $dataConnector = DataConnector::getDataConnector();
 171          }
 172          $this->dataConnector = $dataConnector;
 173          if (!empty($key)) {
 174              $this->load($key, $autoEnable);
 175          } else {
 176              $this->secret = DataConnector::getRandomString(32);
 177          }
 178  
 179      }
 180  
 181  /**
 182   * Initialise the tool consumer.
 183   */
 184      public function initialize()
 185      {
 186  
 187          $this->id = null;
 188          $this->key = null;
 189          $this->name = null;
 190          $this->secret = null;
 191          $this->ltiVersion = null;
 192          $this->consumerName = null;
 193          $this->consumerVersion = null;
 194          $this->consumerGuid = null;
 195          $this->profile = null;
 196          $this->toolProxy = null;
 197          $this->settings = array();
 198          $this->protected = false;
 199          $this->enabled = false;
 200          $this->enableFrom = null;
 201          $this->enableUntil = null;
 202          $this->lastAccess = null;
 203          $this->idScope = ToolProvider::ID_SCOPE_ID_ONLY;
 204          $this->defaultEmail = '';
 205          $this->created = null;
 206          $this->updated = null;
 207  
 208      }
 209  
 210  /**
 211   * Initialise the tool consumer.
 212   *
 213   * Pseudonym for initialize().
 214   */
 215      public function initialise()
 216      {
 217  
 218          $this->initialize();
 219  
 220      }
 221  
 222  /**
 223   * Save the tool consumer to the database.
 224   *
 225   * @return boolean True if the object was successfully saved
 226   */
 227      public function save()
 228      {
 229  
 230          $ok = $this->dataConnector->saveToolConsumer($this);
 231          if ($ok) {
 232              $this->settingsChanged = false;
 233          }
 234  
 235          return $ok;
 236  
 237      }
 238  
 239  /**
 240   * Delete the tool consumer from the database.
 241   *
 242   * @return boolean True if the object was successfully deleted
 243   */
 244      public function delete()
 245      {
 246  
 247          return $this->dataConnector->deleteToolConsumer($this);
 248  
 249      }
 250  
 251  /**
 252   * Get the tool consumer record ID.
 253   *
 254   * @return int Consumer record ID value
 255   */
 256      public function getRecordId()
 257      {
 258  
 259          return $this->id;
 260  
 261      }
 262  
 263  /**
 264   * Sets the tool consumer record ID.
 265   *
 266   * @param int $id  Consumer record ID value
 267   */
 268      public function setRecordId($id)
 269      {
 270  
 271          $this->id = $id;
 272  
 273      }
 274  
 275  /**
 276   * Get the tool consumer key.
 277   *
 278   * @return string Consumer key value
 279   */
 280      public function getKey()
 281      {
 282  
 283          return $this->key;
 284  
 285      }
 286  
 287  /**
 288   * Set the tool consumer key.
 289   *
 290   * @param string $key  Consumer key value
 291   */
 292      public function setKey($key)
 293      {
 294  
 295          $this->key = $key;
 296  
 297      }
 298  
 299  /**
 300   * Get the data connector.
 301   *
 302   * @return mixed Data connector object or string
 303   */
 304      public function getDataConnector()
 305      {
 306  
 307          return $this->dataConnector;
 308  
 309      }
 310  
 311  /**
 312   * Is the consumer key available to accept launch requests?
 313   *
 314   * @return boolean True if the consumer key is enabled and within any date constraints
 315   */
 316      public function getIsAvailable()
 317      {
 318  
 319          $ok = $this->enabled;
 320  
 321          $now = time();
 322          if ($ok && !is_null($this->enableFrom)) {
 323              $ok = $this->enableFrom <= $now;
 324          }
 325          if ($ok && !is_null($this->enableUntil)) {
 326              $ok = $this->enableUntil > $now;
 327          }
 328  
 329          return $ok;
 330  
 331      }
 332  
 333  /**
 334   * Get a setting value.
 335   *
 336   * @param string $name    Name of setting
 337   * @param string $default Value to return if the setting does not exist (optional, default is an empty string)
 338   *
 339   * @return string Setting value
 340   */
 341      public function getSetting($name, $default = '')
 342      {
 343  
 344          if (array_key_exists($name, $this->settings)) {
 345              $value = $this->settings[$name];
 346          } else {
 347              $value = $default;
 348          }
 349  
 350          return $value;
 351  
 352      }
 353  
 354  /**
 355   * Set a setting value.
 356   *
 357   * @param string $name  Name of setting
 358   * @param string $value Value to set, use an empty value to delete a setting (optional, default is null)
 359   */
 360      public function setSetting($name, $value = null)
 361      {
 362  
 363          $old_value = $this->getSetting($name);
 364          if ($value !== $old_value) {
 365              if (!empty($value)) {
 366                  $this->settings[$name] = $value;
 367              } else {
 368                  unset($this->settings[$name]);
 369              }
 370              $this->settingsChanged = true;
 371          }
 372  
 373      }
 374  
 375  /**
 376   * Get an array of all setting values.
 377   *
 378   * @return array Associative array of setting values
 379   */
 380      public function getSettings()
 381      {
 382  
 383          return $this->settings;
 384  
 385      }
 386  
 387  /**
 388   * Set an array of all setting values.
 389   *
 390   * @param array $settings  Associative array of setting values
 391   */
 392      public function setSettings($settings)
 393      {
 394  
 395          $this->settings = $settings;
 396  
 397      }
 398  
 399  /**
 400   * Save setting values.
 401   *
 402   * @return boolean True if the settings were successfully saved
 403   */
 404      public function saveSettings()
 405      {
 406  
 407          if ($this->settingsChanged) {
 408              $ok = $this->save();
 409          } else {
 410              $ok = true;
 411          }
 412  
 413          return $ok;
 414  
 415      }
 416  
 417  /**
 418   * Check if the Tool Settings service is supported.
 419   *
 420   * @return boolean True if this tool consumer supports the Tool Settings service
 421   */
 422      public function hasToolSettingsService()
 423      {
 424  
 425          $url = $this->getSetting('custom_system_setting_url');
 426  
 427          return !empty($url);
 428  
 429      }
 430  
 431  /**
 432   * Get Tool Settings.
 433   *
 434   * @param boolean  $simple     True if all the simple media type is to be used (optional, default is true)
 435   *
 436   * @return mixed The array of settings if successful, otherwise false
 437   */
 438      public function getToolSettings($simple = true)
 439      {
 440  
 441          $url = $this->getSetting('custom_system_setting_url');
 442          $service = new Service\ToolSettings($this, $url, $simple);
 443          $response = $service->get();
 444  
 445          return $response;
 446  
 447      }
 448  
 449  /**
 450   * Perform a Tool Settings service request.
 451   *
 452   * @param array    $settings   An associative array of settings (optional, default is none)
 453   *
 454   * @return boolean True if action was successful, otherwise false
 455   */
 456      public function setToolSettings($settings = array())
 457      {
 458  
 459          $url = $this->getSetting('custom_system_setting_url');
 460          $service = new Service\ToolSettings($this, $url);
 461          $response = $service->set($settings);
 462  
 463          return $response;
 464  
 465      }
 466  
 467  /**
 468   * Add the OAuth signature to an LTI message.
 469   *
 470   * @param string  $url         URL for message request
 471   * @param string  $type        LTI message type
 472   * @param string  $version     LTI version
 473   * @param array   $params      Message parameters
 474   *
 475   * @return array Array of signed message parameters
 476   */
 477      public function signParameters($url, $type, $version, $params)
 478      {
 479  
 480          if (!empty($url)) {
 481  // Check for query parameters which need to be included in the signature
 482              $queryParams = array();
 483              $queryString = parse_url($url, PHP_URL_QUERY);
 484              if (!is_null($queryString)) {
 485                  $queryItems = explode('&', $queryString);
 486                  foreach ($queryItems as $item) {
 487                      if (strpos($item, '=') !== false) {
 488                          list($name, $value) = explode('=', $item);
 489                          $queryParams[urldecode($name)] = urldecode($value);
 490                      } else {
 491                          $queryParams[urldecode($item)] = '';
 492                      }
 493                  }
 494              }
 495              $params = $params + $queryParams;
 496  // Add standard parameters
 497              $params['lti_version'] = $version;
 498              $params['lti_message_type'] = $type;
 499              $params['oauth_callback'] = 'about:blank';
 500  // Add OAuth signature
 501              $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1();
 502              $consumer = new OAuth\OAuthConsumer($this->getKey(), $this->secret, null);
 503              $req = OAuth\OAuthRequest::from_consumer_and_token($consumer, null, 'POST', $url, $params);
 504              $req->sign_request($hmacMethod, $consumer, null);
 505              $params = $req->get_parameters();
 506  // Remove parameters being passed on the query string
 507              foreach (array_keys($queryParams) as $name) {
 508                  unset($params[$name]);
 509              }
 510          }
 511  
 512          return $params;
 513  
 514      }
 515  
 516  /**
 517   * Add the OAuth signature to an array of message parameters or to a header string.
 518   *
 519   * @return mixed Array of signed message parameters or header string
 520   */
 521      public static function addSignature($endpoint, $consumerKey, $consumerSecret, $data, $method = 'POST', $type = null)
 522      {
 523  
 524          $params = array();
 525          if (is_array($data)) {
 526              $params = $data;
 527          }
 528  // Check for query parameters which need to be included in the signature
 529          $queryParams = array();
 530          $queryString = parse_url($endpoint, PHP_URL_QUERY);
 531          if (!is_null($queryString)) {
 532              $queryItems = explode('&', $queryString);
 533              foreach ($queryItems as $item) {
 534                  if (strpos($item, '=') !== false) {
 535                      list($name, $value) = explode('=', $item);
 536                      $queryParams[urldecode($name)] = urldecode($value);
 537                  } else {
 538                      $queryParams[urldecode($item)] = '';
 539                  }
 540              }
 541              $params = $params + $queryParams;
 542          }
 543  
 544          if (!is_array($data)) {
 545  // Calculate body hash
 546              $hash = base64_encode(sha1($data, true));
 547              $params['oauth_body_hash'] = $hash;
 548          }
 549  
 550  // Add OAuth signature
 551          $hmacMethod = new OAuth\OAuthSignatureMethod_HMAC_SHA1();
 552          $oauthConsumer = new OAuth\OAuthConsumer($consumerKey, $consumerSecret, null);
 553          $oauthReq = OAuth\OAuthRequest::from_consumer_and_token($oauthConsumer, null, $method, $endpoint, $params);
 554          $oauthReq->sign_request($hmacMethod, $oauthConsumer, null);
 555          $params = $oauthReq->get_parameters();
 556  // Remove parameters being passed on the query string
 557          foreach (array_keys($queryParams) as $name) {
 558              unset($params[$name]);
 559          }
 560  
 561          if (!is_array($data)) {
 562              $header = $oauthReq->to_header();
 563              if (empty($data)) {
 564                  if (!empty($type)) {
 565                      $header .= "\nAccept: {$type}";
 566                  }
 567              } else if (isset($type)) {
 568                  $header .= "\nContent-Type: {$type}";
 569                  $header .= "\nContent-Length: " . strlen($data);
 570              }
 571              return $header;
 572          } else {
 573              return $params;
 574          }
 575  
 576      }
 577  
 578  /**
 579   * Perform a service request
 580   *
 581   * @param object $service  Service object to be executed
 582   * @param string $method   HTTP action
 583   * @param string $format   Media type
 584   * @param mixed  $data     Array of parameters or body string
 585   *
 586   * @return HTTPMessage HTTP object containing request and response details
 587   */
 588      public function doServiceRequest($service, $method, $format, $data)
 589      {
 590  
 591          $header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format);
 592  
 593  // Connect to tool consumer
 594          $http = new HTTPMessage($service->endpoint, $method, $data, $header);
 595  // Parse JSON response
 596          if ($http->send() && !empty($http->response)) {
 597              $http->responseJson = json_decode($http->response);
 598              $http->ok = !is_null($http->responseJson);
 599          }
 600  
 601          return $http;
 602  
 603      }
 604  
 605  /**
 606   * Load the tool consumer from the database by its record ID.
 607   *
 608   * @param string          $id                The consumer key record ID
 609   * @param DataConnector   $dataConnector    Database connection object
 610   *
 611   * @return object ToolConsumer       The tool consumer object
 612   */
 613      public static function fromRecordId($id, $dataConnector)
 614      {
 615  
 616          $toolConsumer = new ToolConsumer(null, $dataConnector);
 617  
 618          $toolConsumer->initialize();
 619          $toolConsumer->setRecordId($id);
 620          if (!$dataConnector->loadToolConsumer($toolConsumer)) {
 621              $toolConsumer->initialize();
 622          }
 623  
 624          return $toolConsumer;
 625  
 626      }
 627  
 628  
 629  ###
 630  ###  PRIVATE METHOD
 631  ###
 632  
 633  /**
 634   * Load the tool consumer from the database.
 635   *
 636   * @param string  $key        The consumer key value
 637   * @param boolean $autoEnable True if the consumer should be enabled (optional, default if false)
 638   *
 639   * @return boolean True if the consumer was successfully loaded
 640   */
 641      private function load($key, $autoEnable = false)
 642      {
 643  
 644          $this->key = $key;
 645          $ok = $this->dataConnector->loadToolConsumer($this);
 646          if (!$ok) {
 647              $this->enabled = $autoEnable;
 648          }
 649  
 650          return $ok;
 651  
 652      }
 653  
 654  }