LTI Integration Library 4.10.3
PHP class library for building LTI integrations
 
Loading...
Searching...
No Matches
Platform.php
1<?php
2
3namespace ceLTIc\LTI;
4
9
18{
19 use System;
20 use ApiHook;
21
27 public static $MESSAGE_TYPES = array(
28 'ContentItemSelection',
29 'LtiStartAssessment'
30 );
31
37 public static $browserStorageFrame = null;
38
44 public $name = null;
45
51 public $platformId = null;
52
58 public $clientId = null;
59
65 public $deploymentId = null;
66
72 public $authorizationServerId = null;
73
79 public $authenticationUrl = null;
80
86 public $accessTokenUrl = null;
87
93 public $consumerName = null;
94
100 public $consumerVersion = null;
101
107 public $profile = null;
108
114 public $toolProxy = null;
115
121 public $consumerGuid = null;
122
128 public $cssPath = null;
129
135 public $protected = false;
136
143
149 public $defaultEmail = '';
150
156 public $lastServiceRequest = null;
157
163 private $accessToken = null;
164
170 public function __construct($dataConnector = null)
171 {
172 $this->initialize();
173 if (empty($dataConnector)) {
175 }
176 $this->dataConnector = $dataConnector;
177 }
178
182 public function initialize()
183 {
184 $this->id = null;
185 $this->key = null;
186 $this->name = null;
187 $this->secret = null;
188 $this->signatureMethod = 'HMAC-SHA1';
189 $this->encryptionMethod = null;
190 $this->rsaKey = null;
191 $this->kid = null;
192 $this->jku = null;
193 $this->platformId = null;
194 $this->clientId = null;
195 $this->deploymentId = null;
196 $this->ltiVersion = null;
197 $this->consumerName = null;
198 $this->consumerVersion = null;
199 $this->consumerGuid = null;
200 $this->profile = null;
201 $this->toolProxy = null;
202 $this->settings = array();
203 $this->protected = false;
204 $this->enabled = false;
205 $this->enableFrom = null;
206 $this->enableUntil = null;
207 $this->lastAccess = null;
208 $this->idScope = Tool::ID_SCOPE_ID_ONLY;
209 $this->defaultEmail = '';
210 $this->created = null;
211 $this->updated = null;
212 }
213
219 public function initialise()
220 {
221 $this->initialize();
222 }
223
229 public function save()
230 {
231 return $this->dataConnector->savePlatform($this);
232 }
233
239 public function delete()
240 {
241 return $this->dataConnector->deletePlatform($this);
242 }
243
251 public function getId()
252 {
253 if (!empty($this->key)) {
254 $id = $this->key;
255 } elseif (!empty($this->platformId)) {
256 $id = $this->platformId;
257 if (!empty($this->clientId)) {
258 $id .= '/' . $this->clientId;
259 }
260 if (!empty($this->deploymentId)) {
261 $id .= '#' . $this->deploymentId;
262 }
263 } else {
264 $id = null;
265 }
266
267 return $id;
268 }
269
275 public function getFamilyCode()
276 {
277 $familyCode = '';
278 if (!empty($this->consumerVersion)) {
279 $familyCode = $this->consumerVersion;
280 $pos = strpos($familyCode, '-');
281 if ($pos !== false) {
282 $familyCode = substr($familyCode, 0, $pos);
283 }
284 }
285
286 return $familyCode;
287 }
288
294 public function getDataConnector()
295 {
297 }
298
304 public function getAccessToken()
305 {
306 return $this->accessToken;
307 }
308
314 public function setAccessToken($accessToken)
315 {
316 $this->accessToken = $accessToken;
317 }
318
324 public function getIsAvailable()
325 {
327
328 $now = time();
329 if ($ok && !is_null($this->enableFrom)) {
330 $ok = $this->enableFrom <= $now;
331 }
332 if ($ok && !is_null($this->enableUntil)) {
333 $ok = $this->enableUntil > $now;
334 }
335
336 return $ok;
337 }
338
344 public function hasToolSettingsService()
345 {
346 $has = !empty($this->getSetting('custom_system_setting_url'));
347 if (!$has) {
348 $has = self::hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this);
349 }
350 return $has;
351 }
352
360 public function getToolSettings($simple = true)
361 {
362 $ok = false;
363 $settings = array();
364 if (!empty($this->getSetting('custom_system_setting_url'))) {
365 $url = $this->getSetting('custom_system_setting_url');
366 $service = new Service\ToolSettings($this, $url, $simple);
367 $settings = $service->get();
368 $this->lastServiceRequest = $service->getHttpMessage();
369 $ok = $settings !== false;
370 }
371 if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this)) {
372 $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode());
373 $hook = new $className($this);
374 $settings = $hook->getToolSettings($simple);
375 }
376
377 return $settings;
378 }
379
387 public function setToolSettings($settings = array())
388 {
389 $ok = false;
390 if (!empty($this->getSetting('custom_system_setting_url'))) {
391 $url = $this->getSetting('custom_system_setting_url');
392 $service = new Service\ToolSettings($this, $url);
393 $ok = $service->set($settings);
394 $this->lastServiceRequest = $service->getHttpMessage();
395 }
396 if (!$ok && $this->hasConfiguredApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode(), $this)) {
397 $className = $this->getApiHook(self::$TOOL_SETTINGS_SERVICE_HOOK, $this->getFamilyCode());
398 $hook = new $className($this);
399 $ok = $hook->setToolSettings($settings);
400 }
401
402 return $ok;
403 }
404
410 public function getTools()
411 {
412 return $this->dataConnector->getTools();
413 }
414
420 public function hasAccessTokenService()
421 {
422 $has = !empty($this->getSetting('custom_oauth2_access_token_url'));
423 if (!$has) {
424 $has = self::hasConfiguredApiHook(self::$ACCESS_TOKEN_SERVICE_HOOK, $this->getFamilyCode(), $this);
425 }
426 return $has;
427 }
428
434 public function getMessageParameters()
435 {
436 if ($this->ok && is_null($this->messageParameters)) {
437 $this->parseMessage(true, true, false);
438 }
439
441 }
442
446 public function handleRequest()
447 {
448 $parameters = Util::getRequestParameters();
449 if ($this->debugMode) {
451 }
452 if ($this->ok) {
453 if (!empty($parameters['client_id'])) { // Authentication request
455 $this->handleAuthenticationRequest();
456 } else { // LTI message
457 $this->getMessageParameters();
459 if ($this->ok && $this->authenticate()) {
460 $this->doCallback();
461 }
462 }
463 }
464 if (!$this->ok) {
465 $this->onError();
466 }
467 if (!$this->ok) {
468 $errorMessage = "Request failed with reason: '{$this->reason}'";
469 if (!empty($this->details)) {
470 $errorMessage .= PHP_EOL . 'Debug information:';
471 foreach ($this->details as $detail) {
472 $errorMessage .= PHP_EOL . " {$detail}";
473 }
474 }
475 Util::logError($errorMessage);
476 }
477 }
478
488 public static function fromConsumerKey($key = null, $dataConnector = null, $autoEnable = false)
489 {
490 $platform = new static($dataConnector);
491 $platform->key = $key;
492 if (!empty($dataConnector)) {
493 $ok = $dataConnector->loadPlatform($platform);
494 if ($ok && $autoEnable) {
495 $platform->enabled = true;
496 }
497 }
498
499 return $platform;
500 }
501
513 public static function fromPlatformId($platformId, $clientId, $deploymentId, $dataConnector = null, $autoEnable = false)
514 {
515 $platform = new static($dataConnector);
516 $platform->platformId = $platformId;
517 $platform->clientId = $clientId;
518 $platform->deploymentId = $deploymentId;
519 if ($dataConnector->loadPlatform($platform)) {
520 if ($autoEnable) {
521 $platform->enabled = true;
522 }
523 }
524
525 return $platform;
526 }
527
536 public static function fromRecordId($id, $dataConnector)
537 {
538 $platform = new static($dataConnector);
539 $platform->setRecordId($id);
540 $dataConnector->loadPlatform($platform);
541
542 return $platform;
543 }
544
550 public static function getStorageJS()
551 {
552 $javascript = <<< EOD
553(function () {
554 let storageData = {};
555
556 window.addEventListener('message', function (event) {
557 let ok = true;
558 if (typeof event.data !== "object") {
559 ok = false;
560 event.source.postMessage({
561 subject: '.response',
562 message_id: 0,
563 error: {
564 code: 'bad_request',
565 message: 'Event data is not an object'
566 }
567 }, event.origin);
568 }
569 let messageid = '';
570 if (event.data.message_id) {
571 messageid = event.data.message_id;
572 }
573 if (!event.data.subject) {
574 ok = false;
575 event.source.postMessage({
576 subject: '.response',
577 message_id: messageid,
578 error: {
579 code: 'bad_request',
580 message: 'There is no subject specified'
581 }
582 }, event.origin);
583 } else if (!event.data.message_id) {
584 ok = false;
585 event.source.postMessage({
586 subject: event.data.subject + '.response',
587 message_id: messageid,
588 error: {
589 code: 'bad_request',
590 message: 'There is no message ID specified'
591 }
592 }, event.origin);
593 }
594 if (ok) {
595 switch (event.data.subject) {
596 case 'lti.capabilities':
597 event.source.postMessage({
598 subject: 'lti.capabilities.response',
599 message_id: event.data.message_id,
600 supported_messages: [
601 {
602 subject: 'lti.capabilities'
603 },
604 {
605 subject: 'lti.get_data'
606 },
607 {
608 subject: 'lti.put_data'
609 }
610 ]
611 }, event.origin);
612 break;
613 case 'lti.put_data':
614 if (!event.data.key) {
615 event.source.postMessage({
616 subject: event.data.subject + '.response',
617 message_id: messageid,
618 error: {
619 code: 'bad_request',
620 message: 'There is no key specified'
621 }
622 }, event.origin);
623 } else if (!event.data.value) {
624 event.source.postMessage({
625 subject: event.data.subject + '.response',
626 message_id: messageid,
627 error: {
628 code: 'bad_request',
629 message: 'There is no value specified'
630 }
631 }, event.origin);
632 } else {
633 if (!storageData[event.origin]) {
634 storageData[event.origin] = {};
635 }
636 storageData[event.origin][event.data.key] = event.data.value;
637 event.source.postMessage({
638 subject: 'lti.put_data.response',
639 message_id: event.data.message_id,
640 key: event.data.key,
641 value: event.data.value
642 }, event.origin);
643 }
644 break;
645 case 'lti.get_data':
646 if (!event.data.key) {
647 event.source.postMessage({
648 subject: event.data.subject + '.response',
649 message_id: messageid,
650 error: {
651 code: 'bad_request',
652 message: 'There is no key specified'
653 }
654 }, event.origin);
655 } else if (storageData[event.origin] && storageData[event.origin][event.data.key]) {
656 event.source.postMessage({
657 subject: 'lti.get_data.response',
658 message_id: event.data.message_id,
659 key: event.data.key,
660 value: storageData[event.origin][event.data.key]
661 }, event.origin);
662 } else {
663 console.log('There is no value stored with origin/key of \'' + event.origin + '/' + event.data.key + '\'');
664 event.source.postMessage({
665 subject: 'lti.get_data.response',
666 message_id: event.data.message_id,
667 error: {
668 code: 'bad_request',
669 message: 'There is no value stored for this key'
670 }
671 }, event.origin);
672 }
673 break;
674 default:
675 event.source.postMessage({
676 subject: event.data.subject + '.response',
677 message_id: event.data.message_id,
678 error: {
679 code: 'unsupported_subject',
680 message: 'Subject \'' + event.data.subject + '\' not recognised'
681 }
682 }, event.origin);
683 break;
684 }
685 }
686 }, false);
687})();
688
689EOD;
690
691 return $javascript;
692 }
693
694###
695### PROTECTED METHODS
696###
697
708 protected function onInitiateLogin(&$url, &$loginHint, &$ltiMessageHint, $params)
709 {
710 $hasSession = !empty(session_id());
711 if (!$hasSession) {
712 session_start();
713 }
714 $_SESSION['ceLTIc_lti_initiated_login'] = array(
715 'messageUrl' => $url,
716 'login_hint' => $loginHint,
717 'lti_message_hint' => $ltiMessageHint,
718 'params' => $params
719 );
720 if (!$hasSession) {
721 session_write_close();
722 }
723 }
724
730 protected function onAuthenticate()
731 {
732 $hasSession = !empty(session_id());
733 if (!$hasSession) {
734 session_start();
735 }
736 if (isset($_SESSION['ceLTIc_lti_initiated_login'])) {
737 $login = $_SESSION['ceLTIc_lti_initiated_login'];
738 $parameters = Util::getRequestParameters();
739 if ($parameters['login_hint'] !== $login['login_hint'] ||
740 (isset($login['lti_message_hint']) && (!isset($parameters['lti_message_hint']) || ($parameters['lti_message_hint'] !== $login['lti_message_hint'])))) {
741 $this->ok = false;
742 $this->messageParameters['error'] = 'access_denied';
743 } else {
744 Tool::$defaultTool->messageUrl = $login['messageUrl'];
745 $this->messageParameters = $login['params'];
746 }
747 unset($_SESSION['ceLTIc_lti_initiated_login']);
748 }
749 if (!$hasSession) {
750 session_write_close();
751 }
752 }
753
757 protected function onContentItem()
758 {
759 $this->reason = 'No onContentItem method found for platform';
760 $this->onError();
761 }
762
766 protected function onLtiStartAssessment()
767 {
768 $this->reason = 'No onLtiStartAssessment method found for platform';
769 $this->onError();
770 }
771
775 protected function onError()
776 {
777 $this->ok = false;
778 }
779
780###
781### PRIVATE METHODS
782###
783
791 private function authenticate()
792 {
793 $this->ok = $this->checkMessage();
794 if ($this->ok) {
795 $this->ok = $this->verifySignature();
796 }
797
798 return $this->ok;
799 }
800
806 private function handleAuthenticationRequest()
807 {
808 $this->messageParameters = array();
809 $parameters = Util::getRequestParameters();
810 $this->ok = isset($parameters['scope']) && isset($parameters['response_type']) &&
811 isset($parameters['client_id']) && isset($parameters['redirect_uri']) &&
812 isset($parameters['login_hint']) && isset($parameters['nonce']);
813 if (!$this->ok) {
814 $this->messageParameters['error'] = 'invalid_request';
815 }
816 if ($this->ok) {
817 $scopes = explode(' ', $parameters['scope']);
818 $this->ok = in_array('openid', $scopes);
819 if (!$this->ok) {
820 $this->messageParameters['error'] = 'invalid_scope';
821 }
822 }
823 if ($this->ok && ($parameters['response_type'] !== 'id_token')) {
824 $this->ok = false;
825 $this->messageParameters['error'] = 'unsupported_response_type';
826 }
827 if ($this->ok && ($parameters['client_id'] !== $this->clientId)) {
828 $this->ok = false;
829 $this->messageParameters['error'] = 'unauthorized_client';
830 }
831 if ($this->ok) {
832 $this->ok = in_array($parameters['redirect_uri'], Tool::$defaultTool->redirectionUris);
833 if (!$this->ok) {
834 $this->messageParameters['error'] = 'invalid_request';
835 $this->messageParameters['error_description'] = 'Unregistered redirect_uri';
836 }
837 }
838 if ($this->ok) {
839 if (isset($parameters['response_mode'])) {
840 $this->ok = ($parameters['response_mode'] === 'form_post');
841 } else {
842 $this->ok = false;
843 }
844 if (!$this->ok) {
845 $this->messageParameters['error'] = 'invalid_request';
846 $this->messageParameters['error_description'] = 'Invalid response_mode';
847 }
848 }
849 if ($this->ok && (!isset($parameters['prompt']) || ($parameters['prompt'] !== 'none'))) {
850 $this->ok = false;
851 $this->messageParameters['error'] = 'invalid_request';
852 $this->messageParameters['error_description'] = 'Invalid prompt';
853 }
854
855 if ($this->ok) {
856 $this->onAuthenticate();
857 }
858 if ($this->ok) {
859 $this->messageParameters = $this->addSignature(Tool::$defaultTool->messageUrl, $this->messageParameters, 'POST', null,
860 $parameters['nonce']);
861 }
862 if (isset($parameters['state'])) {
863 $this->messageParameters['state'] = $parameters['state'];
864 }
865 if (!empty(static::$browserStorageFrame)) {
866 if (strpos($parameters['redirect_uri'], '?') === false) {
867 $sep = '?';
868 } else {
869 $sep = '&';
870 }
871 $parameters['redirect_uri'] .= "{$sep}lti_storage_target=" . static::$browserStorageFrame;
872 }
873 $html = Util::sendForm($parameters['redirect_uri'], $this->messageParameters);
874 echo $html;
875 exit;
876 }
877
878}
Trait to handle API hook registrations.
Definition ApiHook.php:13
getDataConnector()
Get the data connector.
Definition Context.php:287
Class to provide a connection to a persistent store for LTI objects.
Class to represent an HTTP message request.
Class to represent a platform.
Definition Platform.php:18
$consumerVersion
Platform version (as reported by last platform connection).
Definition Platform.php:98
onError()
Process a response to an invalid message.
Definition Platform.php:773
$authenticationUrl
Login authentication URL.
Definition Platform.php:77
getId()
Get the platform ID.
Definition Platform.php:249
$authorizationServerId
Authorization server ID.
Definition Platform.php:70
$name
Local name of platform.
Definition Platform.php:42
static fromPlatformId($platformId, $clientId, $deploymentId, $dataConnector=null, $autoEnable=false)
Load the platform from the database by its platform, client and deployment IDs.
Definition Platform.php:511
getFamilyCode()
Get platform family code (as reported by last platform connection).
Definition Platform.php:273
hasToolSettingsService()
Check if the Tool Settings service is supported.
Definition Platform.php:342
hasAccessTokenService()
Check if the Access Token service is supported.
Definition Platform.php:418
$lastServiceRequest
HttpMessage object for last service request.
Definition Platform.php:154
$cssPath
Optional CSS path (as reported by last platform connection).
Definition Platform.php:126
getIsAvailable()
Is the platform available to accept launch requests?
Definition Platform.php:322
getAccessToken()
Get the authorization access token.
Definition Platform.php:302
$platformId
Platform ID.
Definition Platform.php:49
getDataConnector()
Get the data connector.
Definition Platform.php:292
$consumerGuid
Platform GUID (as reported by first platform connection).
Definition Platform.php:119
getMessageParameters()
Get the message parameters.
Definition Platform.php:432
onLtiStartAssessment()
Process a valid start assessment message.
Definition Platform.php:764
$defaultEmail
Default email address (or email domain) to use when no email address is provided for a user.
Definition Platform.php:147
onContentItem()
Process a valid content-item message.
Definition Platform.php:755
static $MESSAGE_TYPES
List of supported incoming message types.
Definition Platform.php:25
getTools()
Get an array of defined tools.
Definition Platform.php:408
setToolSettings($settings=array())
Set Tool Settings.
Definition Platform.php:385
$clientId
Client ID.
Definition Platform.php:56
$deploymentId
Deployment ID.
Definition Platform.php:63
static $browserStorageFrame
Name of browser storage frame.
Definition Platform.php:35
initialize()
Initialise the platform.
Definition Platform.php:180
$profile
The platform profile data.
Definition Platform.php:105
static getStorageJS()
Get the JavaScript for handling storage postMessages from a tool.
Definition Platform.php:548
$toolProxy
The tool proxy.
Definition Platform.php:112
handleRequest()
Process an incoming request.
Definition Platform.php:444
getToolSettings($simple=true)
Get Tool Settings.
Definition Platform.php:358
$idScope
Default scope to use when generating an Id value for a user.
Definition Platform.php:140
initialise()
Initialise the platform.
Definition Platform.php:217
$consumerName
Name of platform (as reported by last platform connection).
Definition Platform.php:91
$protected
Whether the platform instance is protected by matching the consumer_guid value in incoming requests.
Definition Platform.php:133
$accessTokenUrl
Access Token service URL.
Definition Platform.php:84
setAccessToken($accessToken)
Set the authorization access token.
Definition Platform.php:312
static fromConsumerKey($key=null, $dataConnector=null, $autoEnable=false)
Load the platform from the database by its consumer key.
Definition Platform.php:486
__construct($dataConnector=null)
Class constructor.
Definition Platform.php:168
save()
Save the platform to the database.
Definition Platform.php:227
static fromRecordId($id, $dataConnector)
Load the platform from the database by its record ID.
Definition Platform.php:534
Class to implement a service.
Definition Service.php:19
Class to implement the Tool Settings service.
Class to represent an LTI system.
Definition System.php:22
$enabled
Whether the system instance is enabled to accept connection requests.
Definition System.php:137
$dataConnector
Data connector object.
Definition System.php:71
$messageParameters
LTI message parameters.
Definition System.php:193
$ok
True if the last request was successful.
Definition System.php:29
getSetting($name, $default='')
Get a setting value.
Definition System.php:271
const ID_SCOPE_ID_ONLY
Use ID value only.
Definition Tool.php:34
static logRequest($debugLevel=false)
Log a request received.
Definition Util.php:286
static $logLevel
Current logging level.
Definition Util.php:202
static logError($message, $showSource=true)
Log an error message.
Definition Util.php:248
static getRequestParameters()
Return GET and POST request parameters (POST parameters take precedence).
Definition Util.php:233
const LOGLEVEL_DEBUG
Log all messages.
Definition Util.php:159