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