ILIAS  release_8 Revision v8.19-1-g4e8f2f9140c
All Data Structures Namespaces Files Functions Variables Modules Pages
Tool.php
Go to the documentation of this file.
1 <?php
2 
19 namespace ILIAS\LTI\ToolProvider;
20 
27 use ILIAS\LTIOAuth;
30 
38 class Tool
39 {
40  use System;
41  use ApiHook;
42 
46  public const CONNECTION_ERROR_MESSAGE = 'Sorry, there was an error connecting you to the application.';
47 
51  public const ID_SCOPE_ID_ONLY = 0;
52 
56  public const ID_SCOPE_GLOBAL = 1;
57 
61  public const ID_SCOPE_CONTEXT = 2;
62 
66  public const ID_SCOPE_RESOURCE = 3;
67 
71  public const ID_SCOPE_SEPARATOR = ':';
72 
76  public static array $MESSAGE_TYPES = array(
77  'basic-lti-launch-request',
78  'ConfigureLaunchRequest',
79  'DashboardRequest',
80  'ContentItemSelectionRequest',
81  'ContentItemUpdateRequest',
82  'ToolProxyRegistrationRequest',
83  'LtiStartProctoring',
84  'LtiEndAssessment'
85  );
86 
90  private static array $LTI_CONSUMER_SETTING_NAMES = array('custom_tc_profile_url', 'custom_system_setting_url', 'custom_oauth2_access_token_url');
91 
95  private static array $LTI_CONTEXT_SETTING_NAMES = array('custom_context_setting_url',
96  'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url',
97  'custom_context_memberships_url', 'custom_context_memberships_v2_url',
98  'custom_context_group_sets_url', 'custom_context_groups_url',
99  'custom_lineitems_url', 'custom_ags_scopes'
100  );
101 
105  private static array $LTI_RESOURCE_LINK_SETTING_NAMES = array('lis_result_sourcedid', 'lis_outcome_service_url',
106  'ext_ims_lis_basic_outcome_url', 'ext_ims_lis_resultvalue_sourcedids', 'ext_outcome_data_values_accepted',
107  'ext_ims_lis_memberships_id', 'ext_ims_lis_memberships_url',
108  'ext_ims_lti_tool_setting', 'ext_ims_lti_tool_setting_id', 'ext_ims_lti_tool_setting_url',
109  'custom_link_setting_url', 'custom_link_memberships_url',
110  'custom_lineitems_url', 'custom_lineitem_url', 'custom_ags_scopes',
111  'custom_ap_acs_url'
112  );
113 
117  private static array $LTI_RETAIN_SETTING_NAMES = array('custom_lineitem_url');
118 
122  private static array $CUSTOM_SUBSTITUTION_VARIABLES = array('User.id' => 'user_id',
123  'User.image' => 'user_image',
124  'User.username' => 'username',
125  'User.scope.mentor' => 'role_scope_mentor',
126  'Membership.role' => 'roles',
127  'Person.sourcedId' => 'lis_person_sourcedid',
128  'Person.name.full' => 'lis_person_name_full',
129  'Person.name.family' => 'lis_person_name_family',
130  'Person.name.given' => 'lis_person_name_given',
131  'Person.email.primary' => 'lis_person_contact_email_primary',
132  'Context.id' => 'context_id',
133  'Context.type' => 'context_type',
134  'Context.title' => 'context_title',
135  'Context.label' => 'context_label',
136  'CourseOffering.sourcedId' => 'lis_course_offering_sourcedid',
137  'CourseSection.sourcedId' => 'lis_course_section_sourcedid',
138  'CourseSection.label' => 'context_label',
139  'CourseSection.title' => 'context_title',
140  'ResourceLink.id' => 'resource_link_id',
141  'ResourceLink.title' => 'resource_link_title',
142  'ResourceLink.description' => 'resource_link_description',
143  'Result.sourcedId' => 'lis_result_sourcedid',
144  'BasicOutcome.url' => 'lis_outcome_service_url',
145  'ToolConsumerProfile.url' => 'custom_tc_profile_url',
146  'ToolProxy.url' => 'tool_proxy_url',
147  'ToolProxy.custom.url' => 'custom_system_setting_url',
148  'ToolProxyBinding.custom.url' => 'custom_context_setting_url',
149  'LtiLink.custom.url' => 'custom_link_setting_url',
150  'LineItems.url' => 'custom_lineitems_url',
151  'LineItem.url' => 'custom_lineitem_url',
152  'ToolProxyBinding.memberships.url' => 'custom_context_memberships_url',
153  'ToolProxyBinding.nrps.url' => 'custom_context_memberships_v2_url',
154  'LtiLink.memberships.url' => 'custom_link_memberships_url',
155  'LtiLink.acs.url' => 'custom_ap_acs_url'
156  );
157 
158 // /**
159 // * Tool consumer object.
160 // *
161 // * @deprecated Use Tool::$platform instead
162 // * @see platform
163 // *
164 // * @var ToolConsumer|null $consumer
165 // */
166 // public ?ToolConsumer $consumer = null;
167 
173  public \ilLTIPlatform $platform;
174 
180  public ?string $returnUrl = null;
181 
187  public ?UserResult $userResult = null;
188 
194  public ?ResourceLink $resourceLink = null;
195 
201  public ?Context $context = null;
202 
208  public string $defaultEmail = '';
209 
215  public int $idScope = self::ID_SCOPE_ID_ONLY;
216 
222  public bool $allowSharing = false;
223 
229  public ?string $message = null;
230 
236  public ?string $baseUrl = null;
237 
243  public ?Item $vendor = null;
244 
250  public ?Item $product = null;
251 
257  public ?array $requiredServices = null;
258 
264  public ?array $optionalServices = null;
265 
271  public ?array $resourceHandlers = null;
272 
278  public ?string $messageUrl = null;
279 
285  public ?string $initiateLoginUrl = null;
286 
292  public ?array $redirectionUris = null;
293 
299  public static ?Tool $defaultTool = null;
300 
306  public static bool $authenticateUsingGet = false;
307 
313  protected ?string $redirectUrl = null;
314 
320  protected ?array $mediaTypes = null;
321 
327  protected ?array $contentTypes = null;
328 
334  protected ?array $fileTypes = null;
335 
341  protected ?array $documentTargets = null;
342 
348  protected ?string $output = null;
349 
355  protected ?string $errorOutput = null;
356 
362  private ?array $constraints = null;
363 
368  public function __construct(DataConnector $dataConnector = null)
369  {
370 // $this->consumer = &$this->platform; //UK: deprecated
371  $this->initialize();
372  if (empty($dataConnector)) {
374  }
375  $this->dataConnector = $dataConnector;
376  }
377 
381  public function initialize()
382  {
383  $this->id = null;
384  $this->key = null;
385  $this->name = null;
386  $this->secret = null;
387  $this->messageUrl = null;
388  $this->initiateLoginUrl = null;
389  $this->redirectionUris = null;
390  $this->rsaKey = null;
391  $this->signatureMethod = 'HMAC-SHA1';
392  $this->encryptionMethod = null;
393  $this->ltiVersion = null;
394  $this->settings = array();
395  $this->enabled = false;
396  $this->enableFrom = null;
397  $this->enableUntil = null;
398  $this->lastAccess = null;
399  $this->created = null;
400  $this->updated = null;
401  $this->constraints = array();
402 // $this->vendor = new Profile\Item(); //Changed UK
403  $this->vendor = new \ILIAS\LTI\ToolProvider\Content\Item(null);
404 // $this->product = new Profile\Item();
405  $this->product = new \ILIAS\LTI\ToolProvider\Content\Item(null);
406  $this->requiredServices = array();
407  $this->optionalServices = array();
408  $this->resourceHandlers = array();
409  }
410 
416  public function save(): bool
417  {
418  return $this->dataConnector->saveTool($this);
419  }
420 
426  public function delete(): bool
427  {
428  return $this->dataConnector->deleteTool($this);
429  }
430 
436  public function getMessageParameters(): array
437  {
438  if (is_null($this->messageParameters)) {
439  $this->parseMessage();
440  // Set debug mode
442  $this->debugMode = (isset($this->messageParameters['custom_debug']) &&
443  (strtolower($this->messageParameters['custom_debug']) === 'true'));
444  if ($this->debugMode) {
446  }
447  }
448  // Set return URL if available
449  if (!empty($this->messageParameters['lti_message_type']) &&
450  (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') || ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) &&
451  !empty($this->messageParameters['content_item_return_url'])) {
452  $this->returnUrl = $this->messageParameters['content_item_return_url'];
453  }
454  if (empty($this->returnUrl) && !empty($this->messageParameters['launch_presentation_return_url'])) {
455  $this->returnUrl = $this->messageParameters['launch_presentation_return_url'];
456  }
457  }
458 
460  }
461 
466  public function handleRequest(bool $strictMode = false)
467  {
468  $parameters = Util::getRequestParameters();
469  if ($this->debugMode) {
471  }
472  if ($_SERVER['REQUEST_METHOD'] === 'HEAD') { // Ignore HEAD requests
473  Util::logRequest(true);
474  } elseif (isset($parameters['iss']) && (strlen($parameters['iss']) > 0)) { // Initiate login request
476  if (!isset($parameters['login_hint']) || (strlen($parameters['login_hint']) <= 0)) {
477  $this->ok = false;
478  $this->reason = 'Missing login_hint parameter';
479  } elseif (!isset($parameters['target_link_uri']) || (strlen($parameters['target_link_uri']) <= 0)) {
480  $this->ok = false;
481  $this->reason = 'Missing target_link_uri parameter';
482  } else {
483  $this->ok = $this->sendAuthenticationRequest($parameters);
484  }
485  } elseif (isset($parameters['openid_configuration']) && (strlen($parameters['openid_configuration']) > 0)) { // Dynamic registration request
487  $this->onRegistration();
488  } else { // LTI message
489  $this->getMessageParameters();
491  if ($this->ok && $this->authenticate($strictMode)) {
492  if (empty($this->output)) {
493  $this->doCallback();
494  if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) {
495  $this->platform->save();
496  }
497  }
498  }
499  }
500  if (!$this->ok) {
501  $errorMessage = "Request failed with reason: '{$this->reason}'";
502  if (!empty($this->details)) {
503  $errorMessage .= PHP_EOL . 'Debug information:';
504  foreach ($this->details as $detail) {
505  $errorMessage .= PHP_EOL . " {$detail}";
506  }
507  }
508  Util::logError($errorMessage);
509  }
510 
511  $this->result();
512  }
513 
521  public function setParameterConstraint(string $name, bool $required = true, int $maxLength = null, array $messageTypes = null)
522  {
523  $name = trim($name);
524  if (!empty($name)) {
525  $this->constraints[$name] = array('required' => $required, 'max_length' => $maxLength, 'messages' => $messageTypes);
526  }
527  }
528 
529 // /**
530 // * Get an array of defined tool consumers
531 // *
532 // * @deprecated Use getPlatforms() instead
533 // * @see Tool::getPlatforms()
534 // *
535 // * @return array Array of ToolConsumer objects
536 // */
537 // public function getConsumers() : array
538 // {
539 // Util::logDebug(
540 // 'Method ceLTIc\LTI\Tool::getConsumers() has been deprecated; please use ceLTIc\LTI\Tool::getPlatforms() instead.',
541 // true
542 // );
543 // return $this->getPlatforms();
544 // }
545 
551  public function getPlatforms(): array
552  {
553  return $this->dataConnector->getPlatforms();
554  }
555 
562  public function findService(string $format, array $methods)
563  {
564  $found = false;
565  $services = $this->platform->profile->service_offered;
566  if (is_array($services)) {
567  $n = -1;
568  foreach ($services as $service) {
569  $n++;
570  if (!is_array($service->format) || !in_array($format, $service->format)) {
571  continue;
572  }
573  $missing = array();
574  foreach ($methods as $method) {
575  if (!is_array($service->action) || !in_array($method, $service->action)) {
576  $missing[] = $method;
577  }
578  }
579  $methods = $missing;
580  if (count($methods) <= 0) {
581  $found = $service;
582  break;
583  }
584  }
585  }
586 
587  return $found;
588  }
589 
590  //not necessary because LTI V 2
591 // /**
592 // * Send the tool proxy to the platform
593 // *
594 // * @return bool True if the tool proxy was accepted
595 // */
596 // public function doToolProxyService() : bool
597 // {
598 // // Create tool proxy
599 // $toolProxyService = $this->findService('application/vnd.ims.lti.v2.toolproxy+json', array('POST'));
600 // $secret = Util::getRandomString(12);
601 // $toolProxy = new MediaType\ToolProxy($this, $toolProxyService, $secret);
602 // $http = $this->platform->doServiceRequest($toolProxyService, 'POST', 'application/vnd.ims.lti.v2.toolproxy+json',
603 // json_encode($toolProxy));
604 // $ok = $http->ok && ($http->status === 201) && !empty($http->responseJson->tool_proxy_guid);
605 // if ($ok) {
606 // $this->platform->setKey($http->responseJson->tool_proxy_guid);
607 // $this->platform->secret = $toolProxy->security_contract->shared_secret;
608 // $this->platform->toolProxy = $toolProxy; //UK: changed from json_encode($toolProxy);
609 // $this->platform->save();
610 // }
611 //
612 // return $ok;
613 // }
614 
615 // /**
616 // * Generate a web page containing an auto-submitted form of parameters.
617 // * @param string $url URL to which the form should be submitted
618 // * @param array $params Array of form parameters
619 // * @param string $target Name of target (optional)
620 // * @return string
621 // *@deprecated Use Util::sendForm() instead
622 // * @see Util::sendForm()
623 // */
624 // public static function sendForm(string $url, array $params, string $target = '') : string
625 // {
626 // Util::logDebug('Method ceLTIc\LTI\Tool::sendForm() has been deprecated; please use ceLTIc\LTI\Util::sendForm() instead.',
627 // true);
628 // Util::sendForm($url, $params, $target);
629 // }
630 
631  ###
632  ### PROTECTED METHODS
633  ###
634 
638  protected function onLaunch()
639  {
640  $this->reason = 'No onLaunch method found for tool';
641  $this->onError();
642  }
643 
647  protected function onConfigure()
648  {
649  $this->reason = 'No onConfigure method found for tool';
650  $this->onError();
651  }
652 
656  protected function onDashboard()
657  {
658  $this->reason = 'No onDashboard method found for tool';
659  $this->onError();
660  }
661 
665  protected function onContentItem()
666  {
667  $this->reason = 'No onContentItem method found for tool';
668  $this->onError();
669  }
670 
674  protected function onContentItemUpdate()
675  {
676  $this->reason = 'No onContentItemUpdate method found for tool';
677  $this->onError();
678  }
679 
683  protected function onRegister()
684  {
685  $this->reason = 'No onRegister method found for tool';
686  $this->onError();
687  }
688 
692  protected function onRegistration()
693  {
694  $platformConfig = $this->getPlatformConfiguration();
695  if ($this->ok) {
696  $toolConfig = $this->getConfiguration($platformConfig);
697  $registrationConfig = $this->sendRegistration($platformConfig, $toolConfig);
698  if ($this->ok) {
699  $this->getPlatformToRegister($platformConfig, $registrationConfig);
700  }
701  }
702  $this->getRegistrationResponsePage($toolConfig);
703  $this->ok = true;
704  }
705 
709  protected function onLtiStartProctoring()
710  {
711  $this->reason = 'No onLtiStartProctoring method found for tool';
712  $this->onError();
713  }
714 
718  protected function onLtiEndAssessment()
719  {
720  $this->reason = 'No onLtiEndAssessment method found for tool';
721  $this->onError();
722  }
723 
730  protected function onInitiateLogin(array $requestParameters, array &$authParameters)
731  {
732  }
733 
737  protected function onError()
738  {
739  $this->ok = false;
740  }
741 
747  protected function getPlatformConfiguration(): ?array
748  {
749  if ($this->ok) {
750  $parameters = Util::getRequestParameters();
751  $this->ok = !empty($parameters['openid_configuration']);
752  if ($this->ok) {
753  $http = new HttpMessage($parameters['openid_configuration']);
754  $this->ok = $http->send();
755  if ($this->ok) {
756  $platformConfig = json_decode($http->response, true);
757  $this->ok = !empty($platformConfig);
758  }
759  if (!$this->ok) {
760  $this->reason = 'Unable to access platform configuration details.';
761  }
762  } else {
763  $this->reason = 'Invalid registration request: missing openid_configuration parameter.';
764  }
765  if ($this->ok) {
766  $this->ok = !empty($platformConfig['registration_endpoint']) && !empty($platformConfig['jwks_uri']) && !empty($platformConfig['authorization_endpoint']) &&
767  !empty($platformConfig['token_endpoint']) && !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']) &&
768  !empty($platformConfig['claims_supported']) && !empty($platformConfig['scopes_supported']) &&
769  !empty($platformConfig['id_token_signing_alg_values_supported']) &&
770  !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['product_family_code']) &&
771  !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['version']) &&
772  !empty($platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['messages_supported']);
773  if (!$this->ok) {
774  $this->reason = 'Invalid platform configuration details.';
775  }
776  }
777  if ($this->ok) {
778  Jwt::setJwtClient(); //added - check
779  $jwtClient = Jwt::getJwtClient();
780  $algorithms = \array_intersect(
781  $jwtClient::getSupportedAlgorithms(),
782  $platformConfig['id_token_signing_alg_values_supported']
783  );
784  $this->ok = !empty($algorithms);
785  if ($this->ok) {
786  rsort($platformConfig['id_token_signing_alg_values_supported']);
787  } else {
788  $this->reason = 'None of the signature algorithms offered by the platform is supported.';
789  }
790  }
791  }
792  if (!$this->ok) {
793  $platformConfig = null;
794  }
795 
796  return $platformConfig;
797  }
798 
804  protected function getConfiguration(array $platformConfig): array
805  {
806  $claimsMapping = array(
807  'User.id' => 'sub',
808  'Person.name.full' => 'name',
809  'Person.name.given' => 'given_name',
810  'Person.name.family' => 'family_name',
811  'Person.email.primary' => 'email'
812  );
813  $toolName = (!empty($this->product->name)) ? $this->product->name : 'Unnamed tool';
814  $toolDescription = (!empty($this->product->description)) ? $this->product->description : '';
815 // $oauthRequest = OAuth\OAuthRequest::from_request();
816  $oauthRequest = LTIOAuth\OAuthRequest::from_request();
817  $toolUrl = $oauthRequest->get_normalized_http_url();
818  $pos = strpos($toolUrl, '//');
819  $domain = substr($toolUrl, $pos + 2);
820  $domain = substr($domain, 0, strpos($domain, '/'));
821  $claimsSupported = $platformConfig['claims_supported'];
822  $messagesSupported = $platformConfig['https://purl.imsglobal.org/spec/lti-platform-configuration']['messages_supported'];
823  $scopesSupported = $platformConfig['scopes_supported'];
824  $iconUrl = null;
825  $messages = array();
826  $claims = array('iss');
827  $variables = array();
828  $constants = array();
829  $redirectUris = array();
830  foreach ($this->resourceHandlers as $resourceHandler) {
831  if (empty($iconUrl)) {
832  $iconUrl = $resourceHandler->icon;
833  }
834  foreach (array_merge($resourceHandler->optionalMessages, $resourceHandler->requiredMessages) as $message) {
835  $type = $message->type;
836  if (array_key_exists($type, Util::MESSAGE_TYPE_MAPPING)) {
838  }
839  $capabilities = array();
840  if ($type === 'LtiResourceLinkRequest') {
841  $toolUrl = "{$this->baseUrl}{$message->path}";
842  $redirectUris[] = $toolUrl;
843  $capabilities = $message->capabilities;
844  $variables = array_merge($variables, $message->variables);
845  $constants = array_merge($constants, $message->constants);
846  } elseif (in_array($type, $messagesSupported)) {
847  $redirectUris[] = "{$this->baseUrl}{$message->path}";
848  $capabilities = $message->capabilities;
849  $variables = array_merge($message->variables, $variables);
850  $constants = array_merge($message->constants, $constants);
851  $messages[] = array(
852  'type' => $type,
853  'target_link_uri' => "{$this->baseUrl}{$message->path}",
854  'label' => $toolName
855  );
856  }
857  foreach ($capabilities as $capability) {
858  if (array_key_exists($capability, $claimsMapping) && in_array($claimsMapping[$capability], $claimsSupported)) {
859  $claims[] = $claimsMapping[$capability];
860  }
861  }
862  }
863  }
864  if (empty($redirectUris)) {
865  $redirectUris = array($toolUrl);
866  } else {
867  $redirectUris = array_unique($redirectUris);
868  }
869  if (!empty($claims)) {
870  $claims = array_unique($claims);
871  }
872  $custom = array();
873  foreach ($constants as $name => $value) {
874  $custom[$name] = $value;
875  }
876  foreach ($variables as $name => $value) {
877  $custom[$name] = '$' . $value;
878  }
879  $toolConfig = array();
880  $toolConfig['application_type'] = 'web';
881  $toolConfig['client_name'] = $toolName;
882  $toolConfig['response_types'] = array('id_token');
883  $toolConfig['grant_types'] = array('implicit', 'client_credentials');
884  $toolConfig['initiate_login_uri'] = $toolUrl;
885  $toolConfig['redirect_uris'] = $redirectUris;
886  $toolConfig['jwks_uri'] = $this->jku;
887  $toolConfig['token_endpoint_auth_method'] = 'private_key_jwt';
888  $toolConfig['https://purl.imsglobal.org/spec/lti-tool-configuration'] = array(
889  'domain' => $domain,
890  'target_link_uri' => $toolUrl,
891  'custom_parameters' => $custom,
892  'claims' => $claims,
893  'messages' => $messages,
894  'description' => $toolDescription
895  );
896  $toolConfig['scope'] = implode(' ', array_intersect($this->requiredScopes, $scopesSupported));
897  if (!empty($iconUrl)) {
898  $toolConfig['logo_uri'] = "{$this->baseUrl}{$iconUrl}";
899  }
900 
901  return $toolConfig;
902  }
903 
910  protected function sendRegistration(array $platformConfig, array $toolConfig): ?array
911  {
912  if ($this->ok) {
913  $parameters = Util::getRequestParameters();
914  $this->ok = !empty($parameters['registration_token']);
915  if ($this->ok) {
916  $body = json_encode($toolConfig);
917  $headers = "Content-type: application/json\n" .
918  "Authorization: Bearer {$parameters['registration_token']}";
919  $http = new HttpMessage($platformConfig['registration_endpoint'], 'POST', $body, $headers);
920  $this->ok = $http->send();
921  if ($this->ok) {
922  $registrationConfig = json_decode($http->response, true);
923  $this->ok = !empty($registrationConfig);
924  }
925  if (!$this->ok) {
926  $this->reason = 'Unable to register with platform.';
927  }
928  } else {
929  $this->reason = 'Invalid registration request: missing registration_token parameter.';
930  }
931  }
932  if (!$this->ok) {
933  $registrationConfig = null;
934  }
935 
936  return $registrationConfig;
937  }
938 
946  protected function getPlatformToRegister(array $platformConfig, array $registrationConfig, bool $doSave = true): ?Platform
947  {
948  $domain = $platformConfig['issuer'];
949  $pos = strpos($domain, '//');
950  if ($pos !== false) {
951  $domain = substr($domain, $pos + 2);
952  $pos = strpos($domain, '/');
953  if ($pos !== false) {
954  $domain = substr($domain, 0, $pos);
955  }
956  }
957  $this->platform = new Platform($this->dataConnector);
958  $this->platform->name = $domain;
959  $this->platform->ltiVersion = Util::LTI_VERSION1P3;
960  $this->platform->signatureMethod = reset($platformConfig['id_token_signing_alg_values_supported']);
961  $this->platform->platformId = $platformConfig['issuer'];
962  $this->platform->clientId = $registrationConfig['client_id'];
963  $this->platform->deploymentId = $registrationConfig['https://purl.imsglobal.org/spec/lti-tool-configuration']['deployment_id'];
964  $this->platform->authenticationUrl = $platformConfig['authorization_endpoint'];
965  $this->platform->accessTokenUrl = $platformConfig['token_endpoint'];
966  $this->platform->jku = $platformConfig['jwks_uri'];
967  if ($doSave) {
968  $this->ok = $this->platform->save();
969  if (!$this->ok) {
970  $this->reason = 'Sorry, an error occurred when saving the platform details.';
971  }
972  }
973 
974  return $this->platform;
975  }
976 
981  protected function getRegistrationResponsePage(array $toolConfig)
982  {
983  $enabled = '';
984  if (!empty($this->platform)) {
985  $now = time();
986  if (!$this->platform->enabled) {
987  $enabled = ', but it will need to be enabled by the tool provider before it can be used';
988  } elseif (!empty($this->platform->enableFrom) && ($this->platform->enableFrom > $now)) {
989  $enabled = ', but you will only have access from ' . date('j F Y H:i T', $this->platform->enableFrom);
990  if (!empty($this->platform->enableUntil)) {
991  $enabled .= ' until ' . date('j F Y H:i T', $this->platform->enableUntil);
992  }
993  } elseif (!empty($this->platform->enableUntil)) {
994  if ($this->platform->enableUntil > $now) {
995  $enabled = ', but you will only have access until ' . date('j F Y H:i T', $this->platform->enableUntil);
996  } else {
997  $enabled = ', but your access was set to end at ' . date('j F Y H:i T', $this->platform->enableUntil);
998  }
999  }
1000  }
1001  $html = <<< EOD
1002 <!DOCTYPE html>
1003 <html lang="en">
1004  <head>
1005  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
1006  <title>LTI Tool registration</title>
1007  <style>
1008  h1 {
1009  font-soze: 110%;
1010  font-weight: bold;
1011  }
1012  .success {
1013  color: #155724;
1014  background-color: #d4edda;
1015  border-color: #c3e6cb;
1016  border: 1px solid;
1017  padding: .75rem 1.25rem;
1018  margin-bottom: 1rem;
1019  }
1020  .error {
1021  color: #721c24;
1022  background-color: #f8d7da;
1023  border-color: #f5c6cb;
1024  border: 1px solid;
1025  padding: .75rem 1.25rem;
1026  margin-bottom: 1rem;
1027  }
1028  .centre {
1029  text-align: center;
1030  }
1031  button {
1032  border: 1px solid transparent;
1033  padding: 0.375rem 0.75rem;
1034  font-size: 1rem;
1035  line-height: 1.5;
1036  border-radius: 0.25rem;
1037  color: #fff;
1038  background-color: #007bff;
1039  border-color: #007bff;
1040  text-align: center;
1041  text-decoration: none;
1042  display: inline-block;
1043  cursor: pointer;
1044  }
1045  </style>
1046  <script language="javascript" type="text/javascript">
1047  function doClose(el) {
1048  (window.opener || window.parent).postMessage({subject:'org.imsglobal.lti.close'}, '*');
1049  return true;
1050  }
1051  </script>
1052  </head>
1053  <body>
1054  <h1>{$toolConfig['client_name']} registration</h1>
1055 
1056 EOD;
1057  if ($this->ok) {
1058  $html .= <<< EOD
1059  <p class="success">
1060  The tool registration was successful{$enabled}.
1061  </p>
1062  <p class="centre">
1063  <button type="button" onclick="return doClose();">Close</button>
1064  </p>
1065 
1066 EOD;
1067  } else {
1068  $html .= <<< EOD
1069  <p class="error">
1070  Sorry, the registration was not successful: {$this->reason}
1071  </p>
1072 
1073 EOD;
1074  }
1075  $html .= <<< EOD
1076  </body>
1077 </html>
1078 EOD;
1079  $this->output = $html;
1080  }
1081 
1089  public static function fromConsumerKey(string $key = null, DataConnector $dataConnector = null, bool $autoEnable = false): Tool
1090  {
1091  $tool = new static($dataConnector);
1092  $tool->key = $key;
1093  if (!empty($dataConnector)) {
1094  $ok = $dataConnector->loadTool($tool);
1095  if ($ok && $autoEnable) {
1096  $tool->enabled = true;
1097  }
1098  }
1099 
1100  return $tool;
1101  }
1102 
1110  public static function fromInitiateLoginUrl(string $initiateLoginUrl, DataConnector $dataConnector = null, bool $autoEnable = false): Tool
1111  {
1112  $tool = new static($dataConnector);
1113  $tool->initiateLoginUrl = $initiateLoginUrl;
1114  if ($dataConnector->loadTool($tool)) {
1115  if ($autoEnable) {
1116  $tool->enabled = true;
1117  }
1118  }
1119 
1120  return $tool;
1121  }
1122 
1129  public static function fromRecordId(string $id, DataConnector $dataConnector): Tool
1130  {
1131  $tool = new static($dataConnector);
1132  $tool->setRecordId($id);
1133  $dataConnector->loadTool($tool);
1134 
1135  return $tool;
1136  }
1137 
1138  ###
1139  ### PRIVATE METHODS
1140  ###
1141 
1149  private function result(): void
1150  {
1151  if (!$this->ok) {
1152  $this->message = self::CONNECTION_ERROR_MESSAGE . ' ' . $this->reason;
1153  $this->onError();
1154  }
1155  if (!$this->ok) {
1156  // If not valid, return an error message to the platform if a return URL is provided
1157  if (!empty($this->returnUrl)) {
1158  $errorUrl = $this->returnUrl;
1159  if (!is_null($this->platform) && isset($this->messageParameters['lti_message_type']) &&
1160  (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') ||
1161  ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest'))) {
1162  $formParams = array();
1163  if ($this->debugMode && !is_null($this->reason)) {
1164  $formParams['lti_errormsg'] = "Debug error: {$this->reason}";
1165  } else {
1166  $formParams['lti_errormsg'] = $this->message;
1167  if (!is_null($this->reason)) {
1168  $formParams['lti_errorlog'] = "Debug error: {$this->reason}";
1169  }
1170  }
1171  if (isset($this->messageParameters['data'])) {
1172  $formParams['data'] = $this->messageParameters['data'];
1173  }
1174  $this->version = (isset($this->messageParameters['lti_version'])) ? $this->messageParameters['lti_version'] : Util::LTI_VERSION1;
1175  $page = $this->sendMessage($errorUrl, 'ContentItemSelection', $formParams);
1176  echo $page;
1177  } else {
1178  if (strpos($errorUrl, '?') === false) {
1179  $errorUrl .= '?';
1180  } else {
1181  $errorUrl .= '&';
1182  }
1183  if ($this->debugMode && !is_null($this->reason)) {
1184  $errorUrl .= 'lti_errormsg=' . urlencode("Debug error: $this->reason");
1185  } else {
1186  $errorUrl .= 'lti_errormsg=' . urlencode($this->message);
1187  if (!is_null($this->reason)) {
1188  $errorUrl .= '&lti_errorlog=' . urlencode("Debug error: $this->reason");
1189  }
1190  }
1191  header("Location: {$errorUrl}");
1192  }
1193  exit;
1194  } else {
1195  if (!is_null($this->errorOutput)) {
1196  echo $this->errorOutput;
1197  } elseif ($this->debugMode && !empty($this->reason)) {
1198  echo "Debug error: {$this->reason}";
1199  } else {
1200  echo "Error: {$this->message}";
1201  }
1202  exit;
1203  }
1204  } elseif (!is_null($this->redirectUrl)) {
1205  header("Location: {$this->redirectUrl}");
1206  exit;
1207  } elseif (!is_null($this->output)) {
1208  echo $this->output;
1209  exit;
1210  }
1211  }
1212 
1219  private function authenticate(bool $strictMode): bool
1220  {
1221  $doSavePlatform = false;
1222  $this->ok = $this->checkMessage();
1223  if ($this->ok && $strictMode && !empty($this->jwt) && !empty($this->jwt->hasJwt())) {
1224  if (!empty($this->jwt->getClaim('https://purl.imsglobal.org/spec/lti/claim/context', '')) &&
1225  empty($this->messageParameters['context_id'])) {
1226  $this->ok = false;
1227  $this->reason = 'Missing id property in https://purl.imsglobal.org/spec/lti/claim/context claim';
1228  } elseif (!empty($this->jwt->getClaim('https://purl.imsglobal.org/spec/lti/claim/tool_platform', '')) &&
1229  empty($this->messageParameters['tool_consumer_instance_guid'])) {
1230  $this->ok = false;
1231  $this->reason = 'Missing guid property in https://purl.imsglobal.org/spec/lti/claim/tool_platform claim';
1232  }
1233  }
1234  if ($this->ok) {
1235  if ($this->messageParameters['lti_message_type'] === 'basic-lti-launch-request') {
1236  $this->ok = isset($this->messageParameters['resource_link_id']) && (strlen(trim($this->messageParameters['resource_link_id'])) > 0);
1237  if (!$this->ok) {
1238  $this->reason = 'Missing resource link ID.';
1239  }
1240  if ($this->ok && ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3)) {
1241  $this->ok = isset($this->messageParameters['roles']);
1242  if (!$this->ok) {
1243  $this->reason = 'Missing roles parameter.';
1244  }
1245  }
1246  } elseif (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') ||
1247  ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) {
1248  $isUpdate = ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest');
1249  $mediaTypes = array();
1250  $contentTypes = array();
1251  $fileTypes = array();
1252  if (isset($this->messageParameters['accept_media_types']) && (strlen(trim($this->messageParameters['accept_media_types'])) > 0)) {
1253  $mediaTypes = array_filter(
1254  explode(',', str_replace(' ', '', $this->messageParameters['accept_media_types'])),
1255  'strlen'
1256  );
1257  }
1258  $this->ok = (count($mediaTypes) > 0) || ($this->messageParameters['lti_version'] === Util::LTI_VERSION1P3);
1259  if (!$this->ok) {
1260  $this->reason = 'No content types specified.';
1261  } elseif ($isUpdate) {
1262  if ($this->messageParameters['lti_version'] !== Util::LTI_VERSION1P3) {
1263  if (!$this->checkValue(
1264  $this->messageParameters['accept_media_types'],
1266  'Invalid value in accept_media_types parameter: \'%s\'.',
1267  $strictMode,
1268  true
1269  )) {
1270  $this->ok = false;
1271  }
1272  } elseif (!$this->checkValue(
1273  $this->messageParameters['accept_types'],
1275  'Invalid value in accept_types parameter: \'%s\'.',
1276  $strictMode,
1277  true
1278  )) {
1279  $this->ok = false;
1280  }
1281  }
1282  if ($this->ok) {
1283  $mediaTypes = array_unique($mediaTypes);
1284  foreach ($mediaTypes as $mediaType) {
1285  if (strpos($mediaType, 'application/vnd.ims.lti.') !== 0) {
1286  $fileTypes[] = $mediaType;
1287  }
1288  if (($mediaType === 'text/html') || ($mediaType === '*/*')) {
1289  $contentTypes[] = Item::TYPE_LINK;
1290  $contentTypes[] = Item::TYPE_HTML;
1291  } elseif ((strpos($mediaType, 'image/') === 0) || ($mediaType === '*/*')) {
1292  $contentTypes[] = Item::TYPE_IMAGE;
1293  } elseif ($mediaType === Item::LTI_LINK_MEDIA_TYPE) {
1294  $contentTypes[] = Item::TYPE_LTI_LINK;
1295  } elseif ($mediaType === Item::LTI_ASSIGNMENT_MEDIA_TYPE) {
1296  $contentTypes[] = Item::TYPE_LTI_ASSIGNMENT;
1297  }
1298  }
1299  if (!empty($fileTypes)) {
1300  $contentTypes[] = Item::TYPE_FILE;
1301  }
1302  $contentTypes = array_unique($contentTypes);
1303  }
1304  if ($this->ok) {
1305  if (isset($this->messageParameters['accept_presentation_document_targets']) &&
1306  (strlen(trim($this->messageParameters['accept_presentation_document_targets'])) > 0)) {
1307  $documentTargets = array_filter(explode(
1308  ',',
1309  str_replace(' ', '', $this->messageParameters['accept_presentation_document_targets'])
1310  ), 'strlen');
1311  $documentTargets = array_unique($documentTargets);
1312  $this->ok = count($documentTargets) > 0;
1313  if (!$this->ok) {
1314  $this->reason = 'Missing or empty accept_presentation_document_targets parameter.';
1315  } else {
1316  if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1317  $permittedTargets = array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay', 'none');
1318  } else { // JWT
1319  $permittedTargets = array('embed', 'iframe', 'window');
1320  }
1321  foreach ($documentTargets as $documentTarget) {
1322  if (!$this->checkValue(
1323  $documentTarget,
1324  $permittedTargets,
1325  'Invalid value in accept_presentation_document_targets parameter: \'%s\'.',
1326  $strictMode,
1327  true
1328  )) {
1329  $this->ok = false;
1330  break;
1331  }
1332  }
1333  if ($this->ok) {
1334  $this->documentTargets = $documentTargets;
1335  }
1336  }
1337  } else {
1338  $this->ok = false;
1339  $this->reason = 'No accept_presentation_document_targets parameter found.';
1340  }
1341  }
1342  if ($this->ok) {
1343  $this->ok = !empty($this->messageParameters['content_item_return_url']);
1344  if (!$this->ok) {
1345  $this->reason = 'Missing content_item_return_url parameter.';
1346  } else {
1347  $this->mediaTypes = $mediaTypes;
1348  $this->contentTypes = $contentTypes;
1349  $this->fileTypes = $fileTypes;
1350  }
1351  }
1352  } elseif ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest') {
1353  $this->ok = ((isset($this->messageParameters['reg_key']) && (strlen(trim($this->messageParameters['reg_key'])) > 0)) && (isset($this->messageParameters['reg_password']) && (strlen(trim($this->messageParameters['reg_password'])) >
1354  0)) && (isset($this->messageParameters['tc_profile_url']) && (strlen(trim($this->messageParameters['tc_profile_url'])) >
1355  0)) && (isset($this->messageParameters['launch_presentation_return_url']) && (strlen(trim($this->messageParameters['launch_presentation_return_url'])) > 0)));
1356  if ($this->debugMode && !$this->ok) {
1357  $this->reason = 'Missing message parameters.';
1358  }
1359  } elseif ($this->messageParameters['lti_message_type'] === 'LtiStartProctoring') {
1360  $this->ok = isset($this->messageParameters['resource_link_id']) && (strlen(trim($this->messageParameters['resource_link_id'])) > 0);
1361  if (!$this->ok) {
1362  $this->reason = 'Missing resource link ID.';
1363  } else {
1364  $this->ok = isset($this->messageParameters['custom_ap_attempt_number']) && (strlen(trim($this->messageParameters['custom_ap_attempt_number'])) > 0) &&
1365  is_numeric($this->messageParameters['custom_ap_attempt_number']);
1366  if (!$this->ok) {
1367  $this->reason = 'Missing or invalid value for attempt number.';
1368  }
1369  }
1370  if ($this->ok) {
1371  $this->ok = isset($this->messageParameters['user_id']) && (strlen(trim($this->messageParameters['user_id'])) > 0);
1372  if (!$this->ok) {
1373  $this->reason = 'Missing user ID.';
1374  }
1375  }
1376  }
1377  }
1378  $now = time();
1379  // Check consumer key
1380  if ($this->ok && ($this->messageParameters['lti_message_type'] !== 'ToolProxyRegistrationRequest')) {
1381  $this->ok = isset($this->messageParameters['oauth_consumer_key']);
1382  if (!$this->ok) {
1383  $this->reason = 'Missing consumer key.';
1384  }
1385  if ($this->ok) {
1386  $this->ok = !is_null($this->platform->created);
1387  if (!$this->ok) {
1388  if (empty($this->jwt) || !$this->jwt->hasJwt()) {
1389  $this->reason = "Consumer key not recognised: {$this->messageParameters['oauth_consumer_key']}";
1390  } else {
1391  $this->reason = "Platform not recognised (Platform ID | Client ID | Deployment ID): {$this->messageParameters['platform_id']} | {$this->messageParameters['oauth_consumer_key']} | {$this->messageParameters['deployment_id']}";
1392  }
1393  }
1394  }
1395  if ($this->ok) {
1396  if ($this->messageParameters['oauth_signature_method'] !== $this->platform->signatureMethod) {
1397  $this->platform->signatureMethod = $this->messageParameters['oauth_signature_method'];
1398  $doSavePlatform = true;
1399  }
1400  $today = date('Y-m-d', $now);
1401  if (is_null($this->platform->lastAccess)) {
1402  $doSavePlatform = true;
1403  } else {
1404  $last = date('Y-m-d', $this->platform->lastAccess);
1405  $doSavePlatform = $doSavePlatform || ($last !== $today);
1406  }
1407  $this->platform->lastAccess = $now;
1408  $this->ok = $this->verifySignature();
1409  }
1410  if ($this->ok) {
1411  if ($this->platform->protected) {
1412  if (!is_null($this->platform->consumerGuid)) {
1413  $this->ok = empty($this->messageParameters['tool_consumer_instance_guid']) || ($this->platform->consumerGuid === $this->messageParameters['tool_consumer_instance_guid']);
1414  if (!$this->ok) {
1415  $this->reason = 'Request is from an invalid platform.';
1416  }
1417  } else {
1418  $this->ok = isset($this->messageParameters['tool_consumer_instance_guid']);
1419  if (!$this->ok) {
1420  $this->reason = 'A platform GUID must be included in the launch request.';
1421  }
1422  }
1423  }
1424  if ($this->ok) {
1425  $this->ok = $this->platform->enabled;
1426  if (!$this->ok) {
1427  $this->reason = 'Platform has not been enabled by the tool.';
1428  }
1429  }
1430  if ($this->ok) {
1431  $this->ok = is_null($this->platform->enableFrom) || ($this->platform->enableFrom <= $now);
1432  if ($this->ok) {
1433  $this->ok = is_null($this->platform->enableUntil) || ($this->platform->enableUntil > $now);
1434  if (!$this->ok) {
1435  $this->reason = 'Platform access has expired.';
1436  }
1437  } else {
1438  $this->reason = 'Platform access is not yet available.';
1439  }
1440  }
1441  }
1442  // Validate other message parameter values
1443  if ($this->ok) {
1444  if (($this->messageParameters['lti_message_type'] === 'ContentItemSelectionRequest') ||
1445  ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest')) {
1446  $isUpdate = ($this->messageParameters['lti_message_type'] === 'ContentItemUpdateRequest');
1447  if (isset($this->messageParameters['accept_unsigned'])) {
1448  $this->ok = $this->checkValue(
1449  $this->messageParameters['accept_unsigned'],
1450  array('true', 'false'),
1451  'Invalid value for accept_unsigned parameter: \'%s\'.',
1452  $strictMode
1453  );
1454  }
1455  if ($this->ok && isset($this->messageParameters['accept_multiple'])) {
1456  if (!$isUpdate) {
1457  $this->ok = $this->checkValue(
1458  $this->messageParameters['accept_multiple'],
1459  array('true', 'false'),
1460  'Invalid value for accept_multiple parameter: \'%s\'.',
1461  $strictMode
1462  );
1463  } else {
1464  $this->ok = $this->checkValue(
1465  $this->messageParameters['accept_multiple'],
1466  array('false'),
1467  'Invalid value for accept_multiple parameter: \'%s\'.',
1468  $strictMode
1469  );
1470  }
1471  }
1472  if ($this->ok && isset($this->messageParameters['accept_copy_advice'])) {
1473  if (!$isUpdate) {
1474  $this->ok = $this->checkValue(
1475  $this->messageParameters['accept_copy_advice'],
1476  array('true', 'false'),
1477  'Invalid value for accept_copy_advice parameter: \'%s\'.',
1478  $strictMode
1479  );
1480  } else {
1481  $this->ok = $this->checkValue(
1482  $this->messageParameters['accept_copy_advice'],
1483  array('false'),
1484  'Invalid value for accept_copy_advice parameter: \'%s\'.',
1485  $strictMode
1486  );
1487  }
1488  }
1489  if ($this->ok && isset($this->messageParameters['auto_create'])) {
1490  $this->ok = $this->checkValue(
1491  $this->messageParameters['auto_create'],
1492  array('true', 'false'),
1493  'Invalid value for auto_create parameter: \'%s\'.',
1494  $strictMode
1495  );
1496  }
1497  if ($this->ok && isset($this->messageParameters['can_confirm'])) {
1498  $this->ok = $this->checkValue(
1499  $this->messageParameters['can_confirm'],
1500  array('true', 'false'),
1501  'Invalid value for can_confirm parameter: \'%s\'.',
1502  $strictMode
1503  );
1504  }
1505  }
1506  if ($this->ok && isset($this->messageParameters['launch_presentation_document_target'])) {
1507  $this->ok = $this->checkValue(
1508  $this->messageParameters['launch_presentation_document_target'],
1509  array('embed', 'frame', 'iframe', 'window', 'popup', 'overlay'),
1510  'Invalid value for launch_presentation_document_target parameter: \'%s\'.',
1511  $strictMode,
1512  true
1513  );
1514  if ($this->ok && ($this->messageParameters['lti_message_type'] === 'LtiStartProctoring') &&
1515  ($this->messageParameters['launch_presentation_document_target'] !== 'window')) {
1516  $this->ok = !isset($this->messageParameters['launch_presentation_height']) &&
1517  !isset($this->messageParameters['launch_presentation_width']);
1518  if (!$this->ok) {
1519  $this->reason = 'Height and width parameters must only be included for the window document target.';
1520  }
1521  }
1522  }
1523  }
1524  }
1525 
1526  if ($this->ok && ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest')) {
1527  $this->ok = $this->messageParameters['lti_version'] === Util::LTI_VERSION2;
1528  if (!$this->ok) {
1529  $this->reason = 'Invalid lti_version parameter.';
1530  }
1531  if ($this->ok) {
1532  $url = $this->messageParameters['tc_profile_url'];
1533  if (strpos($url, '?') === false) {
1534  $url .= '?';
1535  } else {
1536  $url .= '&';
1537  }
1538  $url .= 'lti_version=' . Util::LTI_VERSION2;
1539  $http = new HttpMessage($url, 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
1540  $this->ok = $http->send();
1541  if (!$this->ok) {
1542  $this->reason = 'Platform profile not accessible.';
1543  } else {
1544  $tcProfile = json_decode($http->response);
1545  $this->ok = !is_null($tcProfile);
1546  if (!$this->ok) {
1547  $this->reason = 'Invalid JSON in platform profile.';
1548  }
1549  }
1550  }
1551  // Check for required capabilities
1552  if ($this->ok) {
1553  $this->platform = Platform::fromConsumerKey($this->messageParameters['reg_key'], $this->dataConnector);
1554  $this->platform->profile = $tcProfile;
1555  $capabilities = $this->platform->profile->capability_offered;
1556  $missing = array();
1557  foreach ($this->resourceHandlers as $resourceHandler) {
1558  foreach ($resourceHandler->requiredMessages as $message) {
1559  if (!in_array($message->type, $capabilities)) {
1560  $missing[$message->type] = true;
1561  }
1562  }
1563  }
1564  foreach ($this->constraints as $name => $constraint) {
1565  if ($constraint['required']) {
1566  if (empty(array_intersect(
1567  $capabilities,
1568  array_keys(array_intersect(self::$CUSTOM_SUBSTITUTION_VARIABLES, array($name)))
1569  ))) {
1570  $missing[$name] = true;
1571  }
1572  }
1573  }
1574  if (!empty($missing)) {
1575  ksort($missing);
1576  $this->reason = 'Required capability not offered - \'' . implode('\', \'', array_keys($missing)) . '\'';
1577  $this->ok = false;
1578  }
1579  }
1580  // Check for required services
1581  if ($this->ok) {
1582  foreach ($this->requiredServices as $service) {
1583  foreach ($service->formats as $format) {
1584  if (!$this->findService($format, $service->actions)) {
1585  if ($this->ok) {
1586  $this->reason = 'Required service(s) not offered - ';
1587  $this->ok = false;
1588  } else {
1589  $this->reason .= ', ';
1590  }
1591  $this->reason .= "'{$format}' [" . implode(', ', $service->actions) . ']';
1592  }
1593  }
1594  }
1595  }
1596  if ($this->ok) {
1597  if ($this->messageParameters['lti_message_type'] === 'ToolProxyRegistrationRequest') {
1598  $this->platform->profile = $tcProfile;
1599  $this->platform->secret = $this->messageParameters['reg_password'];
1600  $this->platform->ltiVersion = $this->messageParameters['lti_version'];
1601  $this->platform->name = $tcProfile->product_instance->service_owner->service_owner_name->default_value;
1602  $this->platform->consumerName = $this->platform->name;
1603  $this->platform->consumerVersion = "{$tcProfile->product_instance->product_info->product_family->code}-{$tcProfile->product_instance->product_info->product_version}";
1604  $this->platform->consumerGuid = $tcProfile->product_instance->guid;
1605  $this->platform->enabled = true;
1606  $this->platform->protected = true;
1607  $doSavePlatform = true;
1608  }
1609  }
1610  } elseif ($this->ok && !empty($this->messageParameters['custom_tc_profile_url']) && empty($this->platform->profile)) {
1611  $url = $this->messageParameters['custom_tc_profile_url'];
1612  if (strpos($url, '?') === false) {
1613  $url .= '?';
1614  } else {
1615  $url .= '&';
1616  }
1617  $url .= 'lti_version=' . $this->messageParameters['lti_version'];
1618  $http = new HttpMessage($url, 'GET', null, 'Accept: application/vnd.ims.lti.v2.toolconsumerprofile+json');
1619  if ($http->send()) {
1620  $tcProfile = json_decode($http->response);
1621  if (!is_null($tcProfile)) {
1622  $this->platform->profile = $tcProfile;
1623  $doSavePlatform = true;
1624  }
1625  }
1626  }
1627 
1628  if ($this->ok) {
1629  // Check if a relaunch is being requested
1630  if (isset($this->messageParameters['relaunch_url'])) {
1631  if (empty($this->messageParameters['platform_state'])) {
1632  $this->ok = false;
1633  $this->reason = 'Missing or empty platform_state parameter.';
1634  } else {
1635  $this->sendRelaunchRequest();
1636  }
1637  } else {
1638  // Validate message parameter constraints
1639  $invalidParameters = array();
1640  foreach ($this->constraints as $name => $constraint) {
1641  if (empty($constraint['messages']) || in_array(
1642  $this->messageParameters['lti_message_type'],
1643  $constraint['messages']
1644  )) {
1645  $ok = true;
1646  if ($constraint['required']) {
1647  if (!isset($this->messageParameters[$name]) || (strlen(trim($this->messageParameters[$name])) <= 0)) {
1648  $invalidParameters[] = "{$name} (missing)";
1649  $ok = false;
1650  }
1651  }
1652  if ($ok && !is_null($constraint['max_length']) && isset($this->messageParameters[$name])) {
1653  if (strlen(trim($this->messageParameters[$name])) > $constraint['max_length']) {
1654  $invalidParameters[] = "{$name} (too long)";
1655  }
1656  }
1657  }
1658  }
1659  if (count($invalidParameters) > 0) {
1660  $this->ok = false;
1661  if (empty($this->reason)) {
1662  $this->reason = 'Invalid parameter(s): ' . implode(', ', $invalidParameters) . '.';
1663  }
1664  }
1665 
1666  if ($this->ok) {
1667  // Set the request context
1668  $contextId = '';
1669  //UK: Check if necessary
1670  if ($this->hasConfiguredApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode(), $this)) {
1671  $className = $this->getApiHook(self::$CONTEXT_ID_HOOK, $this->platform->getFamilyCode());
1672  $tpHook = new $className($this);
1673  $contextId = $tpHook->getContextId();
1674  }
1675  if (empty($contextId) && isset($this->messageParameters['context_id'])) {
1676  $contextId = trim($this->messageParameters['context_id']);
1677  }
1678  if (!empty($contextId)) {
1679  $this->context = Context::fromPlatform($this->platform, $contextId);
1680  $title = '';
1681  if (isset($this->messageParameters['context_title'])) {
1682  $title = trim($this->messageParameters['context_title']);
1683  }
1684  if (empty($title)) {
1685  $title = "Course {$this->context->getId()}";
1686  }
1687  $this->context->title = $title;
1688  if (isset($this->messageParameters['context_type'])) {
1689  $this->context->type = trim($this->messageParameters['context_type']);
1690  if (strpos($this->context->type, 'http://purl.imsglobal.org/vocab/lis/v2/course#') === 0) {
1691  $this->context->type = substr($this->context->type, 46);
1692  }
1693  }
1694  }
1695 
1696  // Set the request resource link
1697  if (isset($this->messageParameters['resource_link_id'])) {
1698  $contentItemId = '';
1699  if (isset($this->messageParameters['custom_content_item_id'])) {
1700  $contentItemId = $this->messageParameters['custom_content_item_id'];
1701  }
1702  if (empty($this->context)) {
1703  $this->resourceLink = ResourceLink::fromPlatform(
1704  $this->platform,
1705  trim($this->messageParameters['resource_link_id']),
1706  $contentItemId
1707  );
1708  } else {
1709  $this->resourceLink = ResourceLink::fromContext(
1710  $this->context,
1711  trim($this->messageParameters['resource_link_id']),
1712  $contentItemId
1713  );
1714  }
1715  $title = '';
1716  if (isset($this->messageParameters['resource_link_title'])) {
1717  $title = trim($this->messageParameters['resource_link_title']);
1718  }
1719  if (empty($title)) {
1720  $title = "Resource {$this->resourceLink->getId()}";
1721  }
1722  $this->resourceLink->title = $title;
1723  }
1724  // Delete any existing custom parameters
1725  foreach ($this->platform->getSettings() as $name => $value) {
1726  if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1727  $this->platform->setSetting($name);
1728  $doSavePlatform = true;
1729  }
1730  }
1731  if (!empty($this->context)) {
1732  foreach ($this->context->getSettings() as $name => $value) {
1733  if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1734  $this->context->setSetting($name);
1735  }
1736  }
1737  }
1738  if (!empty($this->resourceLink)) {
1739  foreach ($this->resourceLink->getSettings() as $name => $value) {
1740  if ((strpos($name, 'custom_') === 0) && (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES))) {
1741  $this->resourceLink->setSetting($name);
1742  }
1743  }
1744  }
1745  // Save LTI parameters
1746  foreach (self::$LTI_CONSUMER_SETTING_NAMES as $name) {
1747  if (isset($this->messageParameters[$name])) {
1748  $this->platform->setSetting($name, $this->messageParameters[$name]);
1749  } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1750  $this->platform->setSetting($name);
1751  }
1752  }
1753  if (!empty($this->context)) {
1754  foreach (self::$LTI_CONTEXT_SETTING_NAMES as $name) {
1755  if (isset($this->messageParameters[$name])) {
1756  $this->context->setSetting($name, $this->messageParameters[$name]);
1757  } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1758  $this->context->setSetting($name);
1759  }
1760  }
1761  }
1762  if (!empty($this->resourceLink)) {
1763  foreach (self::$LTI_RESOURCE_LINK_SETTING_NAMES as $name) {
1764  if (isset($this->messageParameters[$name])) {
1765  $this->resourceLink->setSetting($name, $this->messageParameters[$name]);
1766  } elseif (!in_array($name, self::$LTI_RETAIN_SETTING_NAMES)) {
1767  $this->resourceLink->setSetting($name);
1768  }
1769  }
1770  }
1771  // Save other custom parameters at all levels
1772  foreach ($this->messageParameters as $name => $value) {
1773  if ((strpos($name, 'custom_') === 0) && !in_array(
1774  $name,
1775  array_merge(
1776  self::$LTI_CONSUMER_SETTING_NAMES,
1777  self::$LTI_CONTEXT_SETTING_NAMES,
1778  self::$LTI_RESOURCE_LINK_SETTING_NAMES
1779  )
1780  )) {
1781  $this->platform->setSetting($name, $value);
1782  if (!empty($this->context)) {
1783  $this->context->setSetting($name, $value);
1784  }
1785  if (!empty($this->resourceLink)) {
1786  $this->resourceLink->setSetting($name, $value);
1787  }
1788  }
1789  }
1790 
1791  // Set the user instance
1792  $userId = '';
1793  if ($this->hasConfiguredApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode(), $this)) {
1794  $className = $this->getApiHook(self::$USER_ID_HOOK, $this->platform->getFamilyCode());
1795  $tpHook = new $className($this);
1796  $userId = $tpHook->getUserId();
1797  }
1798  if (empty($userId) && isset($this->messageParameters['user_id'])) {
1799  $userId = trim($this->messageParameters['user_id']);
1800  }
1801 
1802  $this->userResult = UserResult::fromResourceLink($this->resourceLink, $userId);
1803 
1804  // Set the user name
1805  $firstname = (isset($this->messageParameters['lis_person_name_given'])) ? $this->messageParameters['lis_person_name_given'] : '';
1806  $lastname = (isset($this->messageParameters['lis_person_name_family'])) ? $this->messageParameters['lis_person_name_family'] : '';
1807  $fullname = (isset($this->messageParameters['lis_person_name_full'])) ? $this->messageParameters['lis_person_name_full'] : '';
1808  $this->userResult->setNames($firstname, $lastname, $fullname);
1809 
1810  // Set the sourcedId
1811  if (isset($this->messageParameters['lis_person_sourcedid'])) {
1812  $this->userResult->sourcedId = $this->messageParameters['lis_person_sourcedid'];
1813  }
1814 
1815  // Set the username
1816  if (isset($this->messageParameters['ext_username'])) {
1817  $this->userResult->username = $this->messageParameters['ext_username'];
1818  } elseif (isset($this->messageParameters['ext_user_username'])) {
1819  $this->userResult->username = $this->messageParameters['ext_user_username'];
1820  } elseif (isset($this->messageParameters['custom_username'])) {
1821  $this->userResult->username = $this->messageParameters['custom_username'];
1822  } elseif (isset($this->messageParameters['custom_user_username'])) {
1823  $this->userResult->username = $this->messageParameters['custom_user_username'];
1824  }
1825 
1826  // Set the user email
1827  $email = (isset($this->messageParameters['lis_person_contact_email_primary'])) ? $this->messageParameters['lis_person_contact_email_primary'] : '';
1828  $this->userResult->setEmail($email, $this->defaultEmail);
1829 
1830  // Set the user image URI
1831  if (isset($this->messageParameters['user_image'])) {
1832  $this->userResult->image = $this->messageParameters['user_image'];
1833  }
1834 
1835  // Set the user roles
1836  if (isset($this->messageParameters['roles'])) {
1837  $this->userResult->roles = self::parseRoles(
1838  $this->messageParameters['roles'],
1839  $this->messageParameters['lti_version']
1840  );
1841  }
1842 
1843  // Initialise the platform and check for changes
1844  $this->platform->defaultEmail = $this->defaultEmail;
1845  if ($this->platform->ltiVersion !== $this->messageParameters['lti_version']) {
1846  $this->platform->ltiVersion = $this->messageParameters['lti_version'];
1847  $doSavePlatform = true;
1848  }
1849  if (isset($this->messageParameters['deployment_id'])) {
1850  $this->platform->deploymentId = $this->messageParameters['deployment_id'];
1851  }
1852  if (isset($this->messageParameters['tool_consumer_instance_name'])) {
1853  if ($this->platform->consumerName !== $this->messageParameters['tool_consumer_instance_name']) {
1854  $this->platform->consumerName = $this->messageParameters['tool_consumer_instance_name'];
1855  $doSavePlatform = true;
1856  }
1857  }
1858  if (isset($this->messageParameters['tool_consumer_info_product_family_code'])) {
1859  $version = $this->messageParameters['tool_consumer_info_product_family_code'];
1860  if (isset($this->messageParameters['tool_consumer_info_version'])) {
1861  $version .= "-{$this->messageParameters['tool_consumer_info_version']}";
1862  }
1863  // do not delete any existing consumer version if none is passed
1864  if ($this->platform->consumerVersion !== $version) {
1865  $this->platform->consumerVersion = $version;
1866  $doSavePlatform = true;
1867  }
1868  } elseif (isset($this->messageParameters['ext_lms']) && ($this->platform->consumerName !== $this->messageParameters['ext_lms'])) {
1869  $this->platform->consumerVersion = $this->messageParameters['ext_lms'];
1870  $doSavePlatform = true;
1871  }
1872  if (isset($this->messageParameters['tool_consumer_instance_guid'])) {
1873  if (is_null($this->platform->consumerGuid)) {
1874  $this->platform->consumerGuid = $this->messageParameters['tool_consumer_instance_guid'];
1875  $doSavePlatform = true;
1876  } elseif (!$this->platform->protected && ($this->platform->consumerGuid !== $this->messageParameters['tool_consumer_instance_guid'])) {
1877  $this->platform->consumerGuid = $this->messageParameters['tool_consumer_instance_guid'];
1878  $doSavePlatform = true;
1879  }
1880  }
1881  if (isset($this->messageParameters['launch_presentation_css_url'])) {
1882  if ($this->platform->cssPath !== $this->messageParameters['launch_presentation_css_url']) {
1883  $this->platform->cssPath = $this->messageParameters['launch_presentation_css_url'];
1884  $doSavePlatform = true;
1885  }
1886  } elseif (isset($this->messageParameters['ext_launch_presentation_css_url']) && ($this->platform->cssPath !== $this->messageParameters['ext_launch_presentation_css_url'])) {
1887  $this->platform->cssPath = $this->messageParameters['ext_launch_presentation_css_url'];
1888  $doSavePlatform = true;
1889  } elseif (!empty($this->platform->cssPath)) {
1890  $this->platform->cssPath = null;
1891  $doSavePlatform = true;
1892  }
1893  }
1894 
1895  // Persist changes to platform
1896  if ($doSavePlatform) {
1897  $this->platform->save();
1898  }
1899 
1900  if ($this->ok) {
1901  // Persist changes to cpntext
1902  if (isset($this->context)) {
1903  $this->context->save();
1904  }
1905 
1906  if (isset($this->resourceLink)) {
1907  // Persist changes to resource link
1908  $this->resourceLink->save();
1909 
1910  // Persist changes to user instnce
1911  $this->userResult->setResourceLinkId($this->resourceLink->getRecordId());
1912  if (isset($this->messageParameters['lis_result_sourcedid'])) {
1913  if ($this->userResult->ltiResultSourcedId !== $this->messageParameters['lis_result_sourcedid']) {
1914  $this->userResult->ltiResultSourcedId = $this->messageParameters['lis_result_sourcedid'];
1915  $this->userResult->save();
1916  }
1917  } elseif ($this->userResult->isLearner()) { // Ensure all learners are recorded in case Assignment and Grade services are used
1918  $this->userResult->ltiResultSourcedId = '';
1919  $this->userResult->save();
1920  }
1921 
1922  // Check if a share arrangement is in place for this resource link
1923  $this->ok = $this->checkForShare();
1924  }
1925  }
1926  }
1927  }
1928  return $this->ok;
1929  }
1930 
1936  private function checkForShare(): bool
1937  {
1938  $ok = true;
1939  $doSaveResourceLink = true;
1940 
1941  $id = $this->resourceLink->primaryResourceLinkId;
1942 
1943  $shareRequest = isset($this->messageParameters['custom_share_key']) && !empty($this->messageParameters['custom_share_key']);
1944  if ($shareRequest) {
1945  if (!$this->allowSharing) {
1946  $ok = false;
1947  $this->reason = 'Your sharing request has been refused because sharing is not being permitted.';
1948  } else {
1949  // Check if this is a new share key
1950  $shareKey = new ResourceLinkShareKey($this->resourceLink, $this->messageParameters['custom_share_key']);
1951  if (!is_null($shareKey->resourceLinkId)) {
1952  // Update resource link with sharing primary resource link details
1953  $id = $shareKey->resourceLinkId;
1954  $ok = ($id !== $this->resourceLink->getRecordId());
1955  if ($ok) {
1956  $this->resourceLink->primaryResourceLinkId = $id;
1957  $this->resourceLink->shareApproved = $shareKey->autoApprove;
1958  $ok = $this->resourceLink->save();
1959  if ($ok) {
1960  $doSaveResourceLink = false;
1961  $this->userResult->getResourceLink()->primaryResourceLinkId = $id;
1962  $this->userResult->getResourceLink()->shareApproved = $shareKey->autoApprove;
1963  $this->userResult->getResourceLink()->updated = time();
1964  // Remove share key
1965  $shareKey->delete();
1966  } else {
1967  $this->reason = 'An error occurred initialising your share arrangement.';
1968  }
1969  } else {
1970  $this->reason = 'It is not possible to share your resource link with yourself.';
1971  }
1972  }
1973  if ($ok) {
1974  $ok = !is_null($id);
1975  if (!$ok) {
1976  $this->reason = 'You have requested to share a resource link but none is available.';
1977  } else {
1978  $ok = (!is_null($this->userResult->getResourceLink()->shareApproved) && $this->userResult->getResourceLink()->shareApproved);
1979  if (!$ok) {
1980  $this->reason = 'Your share request is waiting to be approved.';
1981  }
1982  }
1983  }
1984  }
1985  } else {
1986  // Check no share is in place
1987  $ok = is_null($id);
1988  if (!$ok) {
1989  $this->reason = 'You have not requested to share a resource link but an arrangement is currently in place.';
1990  }
1991  }
1992 
1993  // Look up primary resource link
1994  if ($ok && !is_null($id)) {
1995  $resourceLink = ResourceLink::fromRecordId($id, $this->dataConnector);
1996  $ok = !is_null($resourceLink->created);
1997  if ($ok) {
1998  if ($doSaveResourceLink) {
1999  $this->resourceLink->save();
2000  }
2001  $this->resourceLink = $resourceLink;
2002  } else {
2003  $this->reason = 'Unable to load resource link being shared.';
2004  }
2005  }
2006 
2007  return $ok;
2008  }
2009 
2015  private function sendAuthenticationRequest(array $parameters): bool
2016  {
2017  $clientId = null;
2018  if (isset($parameters['client_id'])) {
2019  $clientId = $parameters['client_id'];
2020  }
2021  $deploymentId = null;
2022  if (isset($parameters['lti_deployment_id'])) {
2023  $deploymentId = $parameters['lti_deployment_id'];
2024  }
2025  $currentLogLevel = Util::$logLevel;
2026  $this->platform = \ilLTIPlatform::fromPlatformId($parameters['iss'], $clientId, $deploymentId, $this->dataConnector);
2027  if ($this->platform->debugMode && ($currentLogLevel < Util::LOGLEVEL_INFO)) {
2028  $this->debugMode = true;
2029  Util::logRequest();
2030  }
2031  $ok = !is_null($this->platform) && !empty($this->platform->authenticationUrl);
2032  if (!$ok) {
2033  $this->reason = 'Platform not found or no platform authentication request URL.';
2034  } else {
2035  do {
2036  $nonce = new PlatformNonce($this->platform, Util::getRandomString());
2037  $ok = !$nonce->load();
2038  } while (!$ok);
2039  $nonce->expires = time() + 10; // Expire after 10 seconds
2040  $ok = $nonce->save();
2041  if ($ok) {
2042 // $oauthRequest = OAuth\OAuthRequest::from_request();
2043  $oauthRequest = LTIOAuth\OAuthRequest::from_request();
2044  $redirectUri = $oauthRequest->get_normalized_http_url();
2045  if (!empty($_SERVER['QUERY_STRING'])) {
2046  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
2047  $redirectUri .= "?{$_SERVER['QUERY_STRING']}";
2048  } else { // Remove all parameters added by platform from query string
2049  $queryString = '';
2050  $params = explode('&', $_SERVER['QUERY_STRING']);
2051  $ignore = false; // Only include those query parameters which come before any of the standard OpenID Connect ones
2052  foreach ($params as $param) {
2053  $parts = explode('=', $param, 2);
2054  if (in_array(
2055  $parts[0],
2056  array('iss', 'target_link_uri', 'login_hint', 'lti_message_hint', 'client_id', 'lti_deployment_id')
2057  )) {
2058  $ignore = true;
2059  } elseif (!$ignore) {
2060  if ((count($parts) <= 1) || empty($parts[1])) { // Drop equals sign for empty parameters to workaround Canvas bug
2061  $queryString .= "&{$parts[0]}";
2062  } else {
2063  $queryString .= "&{$parts[0]}={$parts[1]}";
2064  }
2065  }
2066  }
2067  if (!empty($queryString)) {
2068  $queryString = substr($queryString, 1);
2069  $redirectUri .= "?{$queryString}";
2070  }
2071  }
2072  }
2073  $params = array(
2074  'client_id' => $this->platform->clientId,
2075  'login_hint' => $parameters['login_hint'],
2076  'nonce' => Util::getRandomString(32),
2077  'prompt' => 'none',
2078  'redirect_uri' => $redirectUri,
2079  'response_mode' => 'form_post',
2080  'response_type' => 'id_token',
2081  'scope' => 'openid',
2082  'state' => $nonce->getValue()
2083  );
2084  if (isset($parameters['lti_message_hint'])) {
2085  $params['lti_message_hint'] = $parameters['lti_message_hint'];
2086  }
2087  $this->onInitiateLogin($parameters, $params);
2089  $this->output = Util::sendForm($this->platform->authenticationUrl, $params);
2090  } else {
2091  Util::redirect($this->platform->authenticationUrl, $params);
2092  }
2093  } else {
2094  $this->reason = 'Unable to generate a state value.';
2095  }
2096  }
2097 
2098  return $ok;
2099  }
2100 
2104  private function sendRelaunchRequest()
2105  {
2106  do {
2107  $nonce = new PlatformNonce($this->platform, Util::getRandomString());
2108  $ok = !$nonce->load();
2109  } while (!$ok);
2110  $ok = $nonce->save();
2111  if ($ok) {
2112  $params = array(
2113  'tool_state' => $nonce->getValue(),
2114  'platform_state' => $this->messageParameters['platform_state']
2115  );
2116  $params = $this->platform->addSignature($this->messageParameters['relaunch_url'], $params);
2117  $this->output = Util::sendForm($this->messageParameters['relaunch_url'], $params);
2118  } else {
2119  $this->reason = 'Unable to generate a state value.';
2120  }
2121  }
2122 
2132  private function checkValue(&$value, array $values, string $reason, bool $strictMode, bool $ignoreInvalid = false): bool
2133  {
2134  $lookupValue = $value;
2135  if (!$strictMode) {
2136  $lookupValue = strtolower($value);
2137  }
2138  $ok = in_array($lookupValue, $values);
2139  if (!$ok && !$strictMode && $ignoreInvalid) {
2140  Util::logInfo(sprintf($reason, $value) . " [Error ignored]");
2141  $ok = true;
2142  } elseif (!$ok && !empty($reason)) {
2143  $this->reason = sprintf($reason, $value);
2144  } elseif ($lookupValue !== $value) {
2145  Util::logInfo(sprintf($reason, $value) . " [Changed to '{$lookupValue}']");
2146  $value = $lookupValue;
2147  }
2148 
2149  return $ok;
2150  }
2151 }
onDashboard()
Process a valid dashboard request.
Definition: Tool.php:656
setParameterConstraint(string $name, bool $required=true, int $maxLength=null, array $messageTypes=null)
Add a parameter constraint to be checked on launch.
Definition: Tool.php:521
const TYPE_LINK
Type for link content-item.
Definition: Item.php:36
static string $CONTEXT_ID_HOOK
Context Id hook name.
Definition: ApiHook.php:38
getRegistrationResponsePage(array $toolConfig)
Prepare the page to complete a registration request.
Definition: Tool.php:981
Class to represent a platform.
Definition: Platform.php:35
onRegistration()
Process a dynamic registration request.
Definition: Tool.php:692
exit
Definition: login.php:28
bool $enabled
Whether the system instance is enabled to accept connection requests.
Definition: System.php:123
static getDataConnector(object $db=null, string $dbTableNamePrefix='', string $type='')
Create data connector object.
sendRegistration(array $platformConfig, array $toolConfig)
Send the tool registration to the platform.
Definition: Tool.php:910
string $messageUrl
Message URL for Tool.
Definition: Tool.php:278
static bool $authenticateUsingGet
Use GET method for authentication request messages when true.
Definition: Tool.php:306
const LOGLEVEL_DEBUG
Log all messages.
Definition: Util.php:156
static hasConfiguredApiHook(string $hookName, string $familyCode, $sourceObject)
Check if an API hook is registered and configured.
Definition: ApiHook.php:115
string $errorOutput
HTML to be displayed on an unsuccessful completion of the request and no return URL is available...
Definition: Tool.php:355
ResourceLink $resourceLink
Resource link object.
Definition: Tool.php:194
$type
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
Class to represent an LTI Tool.
Definition: Tool.php:38
if(! $DIC->user() ->getId()||!ilLTIConsumerAccess::hasCustomProviderCreationAccess()) $params
Definition: ltiregstart.php:33
sendAuthenticationRequest(array $parameters)
Generate a form to perform an authentication request.
Definition: Tool.php:2015
$clientId
Definition: ltiregend.php:27
int $idScope
Scope to use for user IDs.
Definition: Tool.php:215
const ID_SCOPE_SEPARATOR
Character used to separate each element of an ID.
Definition: Tool.php:71
int $id
System ID value.
Definition: System.php:186
findService(string $format, array $methods)
Find an offered service based on a media type and HTTP action(s)
Definition: Tool.php:562
static redirect(string $url, array $params)
Redirect to a URL with query parameters.
Definition: Util.php:523
string $initiateLoginUrl
Initiate Login request URL for Tool.
Definition: Tool.php:285
checkMessage()
Verify the required properties of an LTI message.
Definition: System.php:728
initialize()
Initialise the tool.
Definition: Tool.php:381
const LTI_ASSIGNMENT_MEDIA_TYPE
Media type for LTI assignment links.
Definition: Item.php:71
const TYPE_HTML
Type for HTML content-item.
Definition: Item.php:56
array $constraints
LTI parameter constraints for auto validation checks.
Definition: Tool.php:362
string $output
Default HTML to be displayed on a successful completion of the request.
Definition: Tool.php:348
array $documentTargets
Document targets accepted by the platform.
Definition: Tool.php:341
static fromInitiateLoginUrl(string $initiateLoginUrl, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the tool from the database by its initiate login URL.
Definition: Tool.php:1110
__construct(DataConnector $dataConnector=null)
Class constructor.
Definition: Tool.php:368
static setJwtClient(\ILIAS\LTI\ToolProvider\Jwt\ClientInterface $jwtClient=null)
Set the JWT client to use for handling JWTs.
Definition: Jwt.php:74
static getApiHook(string $hookName, string $familyCode)
Get the class name for an API hook.
Definition: ApiHook.php:88
const TYPE_IMAGE
Type for image content-item.
Definition: Item.php:61
static fromPlatformId(string $platformId, string $clientId, string $deploymentId, ilLTIDataConnector $dataConnector=null, bool $autoEnable=false)
Load the platform from the database by its platform, client and deployment IDs.
static sendForm(string $url, array $params, string $target='')
Generate a web page containing an auto-submitted form of parameters.
Definition: Util.php:466
getPlatformConfiguration()
Fetch a platform&#39;s configuration data.
Definition: Tool.php:747
const MESSAGE_TYPE_MAPPING
Mapping for standard message types.
Definition: Util.php:48
$claims
Definition: ltitoken.php:71
onInitiateLogin(array $requestParameters, array &$authParameters)
Process a login initiation request.
Definition: Tool.php:730
Item $product
Product details.
Definition: Tool.php:250
static getRequestParameters()
Return GET and POST request parameters (POST parameters take precedence)
Definition: Util.php:224
Class to represent a platform user.
Definition: UserResult.php:30
checkForShare()
Check if a share arrangement is in place.
Definition: Tool.php:1936
sendMessage(string $url, string $type, array $messageParams, string $target='', ?string $userId=null, string $hint='')
Generate a web page containing an auto-submitted form of LTI message parameters.
Definition: System.php:644
onContentItemUpdate()
Process a valid content-item update request.
Definition: Tool.php:674
getMessageParameters()
Get the message parameters.
Definition: Tool.php:436
const LTI_VERSION2
LTI version 2 for messages.
Definition: Util.php:38
if($format !==null) $name
Definition: metadata.php:247
$messages
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: xapiexit.php:22
static Tool $defaultTool
Default tool for use with service requests.
Definition: Tool.php:299
getConfiguration(array $platformConfig)
Prepare the tool&#39;s configuration data.
Definition: Tool.php:804
onLtiEndAssessment()
Process a valid end assessment request.
Definition: Tool.php:718
static http()
Fetches the global http state from ILIAS.
static array $MESSAGE_TYPES
List of supported incoming message types.
Definition: Tool.php:76
doCallback()
Call any callback function for the requested action.
Definition: System.php:1141
static getRandomString(int $length=8)
Generate a random string.
Definition: Util.php:558
string $redirectUrl
URL to redirect user to on successful completion of the request.
Definition: Tool.php:313
static array $LTI_CONSUMER_SETTING_NAMES
Names of LTI parameters to be retained in the consumer settings property.
Definition: Tool.php:90
Class to represent a platform nonce.
static array $LTI_RETAIN_SETTING_NAMES
Names of LTI parameters to be retained even when not passed.
Definition: Tool.php:117
onLaunch()
Send the tool proxy to the platform.
Definition: Tool.php:638
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: AccessToken.php:19
handleRequest(bool $strictMode=false)
Process an incoming request.
Definition: Tool.php:466
array $optionalServices
Optional services used by Tool.
Definition: Tool.php:264
const TYPE_LTI_LINK
Type for LTI link content-item.
Definition: Item.php:41
array $requiredServices
Services required by Tool.
Definition: Tool.php:257
string $jku
Endpoint for public key.
Definition: System.php:95
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
array $messageParameters
LTI message parameters.
Definition: System.php:179
$param
Definition: xapitoken.php:46
const CONNECTION_ERROR_MESSAGE
Default connection error message.
Definition: Tool.php:46
string $key
Consumer key/client ID value.
Definition: System.php:193
static array $LTI_CONTEXT_SETTING_NAMES
Names of LTI parameters to be retained in the context settings property.
Definition: Tool.php:95
const ID_SCOPE_CONTEXT
Prefix the ID with the consumer key and context ID.
Definition: Tool.php:61
$format
Definition: metadata.php:235
Item $vendor
Vendor details.
Definition: Tool.php:243
onError()
Process a response to an invalid request.
Definition: Tool.php:737
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Item.php:19
const ID_SCOPE_ID_ONLY
Use ID value only.
Definition: Tool.php:51
UserResult $userResult
UserResult object.
Definition: Tool.php:187
sendRelaunchRequest()
Generate a form to perform a relaunch request.
Definition: Tool.php:2104
static fromPlatform(Platform $platform, string $ltiContextId)
Class constructor from consumer.
Definition: Context.php:695
Class to represent a content-item object.
Definition: Item.php:31
$http
Definition: raiseError.php:7
checkValue(&$value, array $values, string $reason, bool $strictMode, bool $ignoreInvalid=false)
Validate a parameter value from an array of permitted values.
Definition: Tool.php:2132
array $fileTypes
File types accepted by the platform.
Definition: Tool.php:334
Context $context
Context object.
Definition: Tool.php:201
onRegister()
Process a valid tool proxy registration request.
Definition: Tool.php:683
static array $LTI_RESOURCE_LINK_SETTING_NAMES
Names of LTI parameters to be retained in the resource link settings property.
Definition: Tool.php:105
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:302
string $returnUrl
Return URL provided by platform.
Definition: Tool.php:180
string $defaultEmail
Default email domain.
Definition: Tool.php:208
ilLTIDataConnector $dataConnector
Data connector object.
Definition: System.php:64
static logRequest(bool $debugLevel=false)
Log a request received.
Definition: Util.php:375
onContentItem()
Process a valid content-item request.
Definition: Tool.php:665
array $resourceHandlers
Resource handlers for Tool.
Definition: Tool.php:271
const LTI_VERSION1
LTI version 1 for messages.
Definition: Util.php:28
parseMessage()
Parse the message.
Definition: System.php:885
Class to represent a platform resource link share key.
static fromConsumerKey(string $key=null, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the tool from the database by its consumer key.
Definition: Tool.php:1089
result()
Perform the result of an action.
Definition: Tool.php:1149
trait ApiHook
Trait to handle API hook registrations.
Definition: ApiHook.php:29
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
array $redirectionUris
Redirection URIs for Tool.
Definition: Tool.php:292
array $mediaTypes
Media types accepted by the platform.
Definition: Tool.php:320
const LOGLEVEL_INFO
Log error and information messages.
Definition: Util.php:151
const TYPE_LTI_ASSIGNMENT
Type for LTI assignment content-item.
Definition: Item.php:46
const TYPE_FILE
Type for file content-item.
Definition: Item.php:51
static parseRoles($roles, string $ltiVersion=Util::LTI_VERSION1)
Get an array of fully qualified user roles.
Definition: System.php:538
save()
Save the tool to the database.
Definition: Tool.php:416
onConfigure()
Process a valid configure request.
Definition: Tool.php:647
static array $CUSTOM_SUBSTITUTION_VARIABLES
Names of LTI custom parameter substitution variables (or capabilities) and their associated default m...
Definition: Tool.php:122
const LTI_LINK_MEDIA_TYPE
Media type for LTI launch links.
Definition: Item.php:66
Class to provide a connection to a persistent store for LTI objects.
getPlatformToRegister(array $platformConfig, array $registrationConfig, bool $doSave=true)
Initialise the platform to be registered.
Definition: Tool.php:946
string $reason
Error message for last request processed.
Definition: System.php:102
static logError(string $message, bool $showSource=true)
Log an error message.
Definition: Util.php:337
onLtiStartProctoring()
Process a valid start proctoring request.
Definition: Tool.php:709
static int $logLevel
Current logging level.
Definition: Util.php:189
static fromConsumerKey(string $key=null, DataConnector $dataConnector=null, bool $autoEnable=false)
Load the platform from the database by its consumer key.
Definition: Platform.php:496
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: OAuth.php:21
$url
static logInfo(string $message, bool $showSource=false)
Log an information message.
Definition: Util.php:350
static fromRecordId(string $id, DataConnector $dataConnector)
Load the tool from the database by its record ID.
Definition: Tool.php:1129
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Message.php:19
bool $allowSharing
Whether shared resource link arrangements are permitted.
Definition: Tool.php:222
authenticate(bool $strictMode)
Check the authenticity of the LTI message.
Definition: Tool.php:1219
Class to represent a platform context.
Definition: Context.php:33
$service
Definition: ltiservices.php:43
ilLTIPlatform $platform
Platform object.
Definition: Tool.php:173
getPlatforms()
Get an array of defined tool consumers.
Definition: Tool.php:551
verifySignature()
Verify the signature of a message.
Definition: System.php:781
const ID_SCOPE_GLOBAL
Prefix an ID with the consumer key.
Definition: Tool.php:56
string $message
Message for last request processed.
Definition: Tool.php:229
$version
Definition: plugin.php:24
const LTI_VERSION1P3
LTI version 1.3 for messages.
Definition: Util.php:33
static getJwtClient()
Get the JWT client to use for handling JWTs.
Definition: Jwt.php:85
string $baseUrl
Base URL for tool service.
Definition: Tool.php:236
array $contentTypes
Content item types accepted by the platform.
Definition: Tool.php:327
const ID_SCOPE_RESOURCE
Prefix the ID with the consumer key and resource ID.
Definition: Tool.php:66
static fromResourceLink(ResourceLink $resourceLink, string $ltiUserId)
Class constructor from resource link.
Definition: UserResult.php:265