\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/events', 'mod_lti/keys', 'mod_lti/tool_type',\n 'mod_lti/tool_proxy', 'core/str', 'core/config'],\n function($, ajax, notification, templates, ltiEvents, KEYS, toolType, toolProxy, str, config) {\n\n var SELECTORS = {\n EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-container',\n EXTERNAL_REGISTRATION_PAGE_CONTAINER: '#external-registration-page-container',\n EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER: '#external-registration-template-container',\n CARTRIDGE_REGISTRATION_CONTAINER: '#cartridge-registration-container',\n CARTRIDGE_REGISTRATION_FORM: '#cartridge-registration-form',\n ADD_TOOL_FORM: '#add-tool-form',\n TOOL_LIST_CONTAINER: '#tool-list-container',\n TOOL_CREATE_BUTTON: '#tool-create-button',\n TOOL_CREATE_LTILEGACY_BUTTON: '#tool-createltilegacy-button',\n REGISTRATION_CHOICE_CONTAINER: '#registration-choice-container',\n TOOL_URL: '#tool-url'\n };\n\n /**\n * Get the tool list container element.\n *\n * @method getToolListContainer\n * @private\n * @return {Object} jQuery object\n */\n var getToolListContainer = function() {\n return $(SELECTORS.TOOL_LIST_CONTAINER);\n };\n\n /**\n * Get the external registration container element.\n *\n * @method getExternalRegistrationContainer\n * @private\n * @return {Object} jQuery object\n */\n var getExternalRegistrationContainer = function() {\n return $(SELECTORS.EXTERNAL_REGISTRATION_CONTAINER);\n };\n\n /**\n * Get the cartridge registration container element.\n *\n * @method getCartridgeRegistrationContainer\n * @private\n * @return {Object} jQuery object\n */\n var getCartridgeRegistrationContainer = function() {\n return $(SELECTORS.CARTRIDGE_REGISTRATION_CONTAINER);\n };\n\n /**\n * Get the registration choice container element.\n *\n * @method getRegistrationChoiceContainer\n * @private\n * @return {Object} jQuery object\n */\n var getRegistrationChoiceContainer = function() {\n return $(SELECTORS.REGISTRATION_CHOICE_CONTAINER);\n };\n\n /**\n * Close the LTI Advantage Registration IFrame.\n *\n * @private\n * @param {Object} e post message event sent from the registration frame.\n */\n var closeLTIAdvRegistration = function(e) {\n if (e.data && 'org.imsglobal.lti.close' === e.data.subject) {\n $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();\n hideExternalRegistration();\n showRegistrationChoices();\n showToolList();\n showRegistrationChoices();\n reloadToolList();\n }\n };\n\n /**\n * Load the external registration template and render it in the DOM and display it.\n *\n * @method initiateRegistration\n * @private\n * @param {String} url where to send the registration request\n */\n var initiateRegistration = function(url) {\n // Show the external registration page in an iframe.\n $(SELECTORS.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass('hidden');\n var container = $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);\n container.append($(\"\"));\n showExternalRegistration();\n window.addEventListener(\"message\", closeLTIAdvRegistration, false);\n };\n\n /**\n * Get the tool type URL.\n *\n * @method getToolURL\n * @private\n * @return {String} the tool type url\n */\n var getToolURL = function() {\n return $(SELECTORS.TOOL_URL).val();\n };\n\n /**\n * Hide the external registration container.\n *\n * @method hideExternalRegistration\n * @private\n */\n var hideExternalRegistration = function() {\n getExternalRegistrationContainer().addClass('hidden');\n };\n\n /**\n * Hide the cartridge registration container.\n *\n * @method hideCartridgeRegistration\n * @private\n */\n var hideCartridgeRegistration = function() {\n getCartridgeRegistrationContainer().addClass('hidden');\n };\n\n /**\n * Hide the registration choice container.\n *\n * @method hideRegistrationChoices\n * @private\n */\n var hideRegistrationChoices = function() {\n getRegistrationChoiceContainer().addClass('hidden');\n };\n\n /**\n * Display the external registration panel and hides the other\n * panels.\n *\n * @method showExternalRegistration\n * @private\n */\n var showExternalRegistration = function() {\n hideCartridgeRegistration();\n hideRegistrationChoices();\n getExternalRegistrationContainer().removeClass('hidden');\n screenReaderAnnounce(getExternalRegistrationContainer());\n };\n\n /**\n * Display the cartridge registration panel and hides the other\n * panels.\n *\n * @method showCartridgeRegistration\n * @param {String} url\n * @private\n */\n var showCartridgeRegistration = function(url) {\n hideExternalRegistration();\n hideRegistrationChoices();\n // Don't save the key and secret from the last tool.\n var container = getCartridgeRegistrationContainer();\n container.find('input').val('');\n container.removeClass('hidden');\n container.find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).attr('data-cartridge-url', url);\n screenReaderAnnounce(container);\n };\n\n /**\n * Display the registration choices panel and hides the other\n * panels.\n *\n * @method showRegistrationChoices\n * @private\n */\n var showRegistrationChoices = function() {\n hideExternalRegistration();\n hideCartridgeRegistration();\n getRegistrationChoiceContainer().removeClass('hidden');\n screenReaderAnnounce(getRegistrationChoiceContainer());\n };\n\n /**\n * JAWS does not notice visibility changes with aria-live.\n * Remove and add the content back to force it to read it out.\n * This function can be removed once JAWS supports visibility.\n *\n * @method screenReaderAnnounce\n * @param {Object} element\n * @private\n */\n var screenReaderAnnounce = function(element) {\n var children = element.children().detach();\n children.appendTo(element);\n };\n\n /**\n * Hides the list of tool types.\n *\n * @method hideToolList\n * @private\n */\n var hideToolList = function() {\n getToolListContainer().addClass('hidden');\n };\n\n /**\n * Display the list of tool types.\n *\n * @method hideToolList\n * @private\n */\n var showToolList = function() {\n getToolListContainer().removeClass('hidden');\n };\n\n /**\n * Display the registration feedback alert and hide the other panels.\n *\n * @method showRegistrationFeedback\n * @param {Object} data\n * @private\n */\n var showRegistrationFeedback = function(data) {\n var type = data.error ? 'error' : 'success';\n notification.addNotification({\n message: data.message,\n type: type\n });\n };\n\n /**\n * Show the loading animation\n *\n * @method startLoading\n * @private\n * @param {Object} element jQuery object\n */\n var startLoading = function(element) {\n element.addClass(\"loading\");\n };\n\n /**\n * Hide the loading animation\n *\n * @method stopLoading\n * @private\n * @param {Object} element jQuery object\n */\n var stopLoading = function(element) {\n element.removeClass(\"loading\");\n };\n\n /**\n * Refresh the list of tool types and render the new ones.\n *\n * @method reloadToolList\n * @private\n */\n var reloadToolList = function() {\n var promise = $.Deferred();\n var container = getToolListContainer();\n startLoading(container);\n\n $.when(\n toolType.query(),\n toolProxy.query({'orphanedonly': true})\n )\n .done(function(types, proxies) {\n templates.render('mod_lti/tool_list', {tools: types, proxies: proxies})\n .done(function(html, js) {\n container.empty();\n container.append(html);\n templates.runTemplateJS(js);\n promise.resolve();\n }).fail(promise.reject);\n })\n .fail(promise.reject);\n\n promise.fail(notification.exception)\n .always(function() {\n stopLoading(container);\n });\n };\n\n /**\n * Start the LTI Advantage registration.\n *\n * @method addLTIAdvTool\n * @private\n */\n var addLTIAdvTool = function() {\n var url = getToolURL().trim();\n\n if (url) {\n $(SELECTORS.TOOL_URL).val('');\n hideToolList();\n initiateRegistration(url);\n }\n\n };\n\n /**\n * Trigger appropriate registration process process for the user input\n * URL. It can either be a cartridge or a registration url.\n *\n * @method addLTILegacyTool\n * @private\n * @return {Promise} jQuery Deferred object\n */\n var addLTILegacyTool = function() {\n var url = getToolURL().trim();\n\n if (url === \"\") {\n return $.Deferred().resolve();\n }\n var toolButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);\n startLoading(toolButton);\n\n var promise = toolType.isCartridge(url);\n\n promise.always(function() {\n stopLoading(toolButton);\n });\n\n promise.done(function(result) {\n if (result.iscartridge) {\n $(SELECTORS.TOOL_URL).val('');\n $(document).trigger(ltiEvents.START_CARTRIDGE_REGISTRATION, url);\n } else {\n $(document).trigger(ltiEvents.START_EXTERNAL_REGISTRATION, {url: url});\n }\n });\n\n promise.fail(function() {\n str.get_string('errorbadurl', 'mod_lti')\n .done(function(s) {\n $(document).trigger(ltiEvents.REGISTRATION_FEEDBACK, {\n message: s,\n error: true\n });\n })\n .fail(notification.exception);\n });\n\n return promise;\n };\n\n /**\n * Sets up the listeners for user interaction on the page.\n *\n * @method registerEventListeners\n * @private\n */\n var registerEventListeners = function() {\n\n // These are events fired by the registration processes. Either\n // the cartridge registration or the external registration url.\n $(document).on(ltiEvents.NEW_TOOL_TYPE, function() {\n reloadToolList();\n });\n\n $(document).on(ltiEvents.START_EXTERNAL_REGISTRATION, function() {\n showExternalRegistration();\n $(SELECTORS.TOOL_URL).val('');\n hideToolList();\n });\n\n $(document).on(ltiEvents.STOP_EXTERNAL_REGISTRATION, function() {\n showToolList();\n showRegistrationChoices();\n });\n\n $(document).on(ltiEvents.START_CARTRIDGE_REGISTRATION, function(event, url) {\n showCartridgeRegistration(url);\n });\n\n $(document).on(ltiEvents.STOP_CARTRIDGE_REGISTRATION, function() {\n getCartridgeRegistrationContainer().find(SELECTORS.CARTRIDGE_REGISTRATION_FORM).removeAttr('data-cartridge-url');\n showRegistrationChoices();\n });\n\n $(document).on(ltiEvents.REGISTRATION_FEEDBACK, function(event, data) {\n showRegistrationFeedback(data);\n });\n\n var addLegacyButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);\n addLegacyButton.click(function(e) {\n e.preventDefault();\n addLTILegacyTool();\n });\n\n var addLTIButton = $(SELECTORS.TOOL_CREATE_BUTTON);\n addLTIButton.click(function(e) {\n e.preventDefault();\n addLTIAdvTool();\n });\n\n };\n\n return /** @alias module:mod_lti/cartridge_registration_form */ {\n\n /**\n * Initialise this module.\n */\n init: function() {\n registerEventListeners();\n reloadToolList();\n }\n };\n});\n"],"file":"tool_configure_controller.min.js"}
\ No newline at end of file
diff --git a/mod/lti/amd/src/tool_configure_controller.js b/mod/lti/amd/src/tool_configure_controller.js
index 1ca2fd36361..072218e70cd 100644
--- a/mod/lti/amd/src/tool_configure_controller.js
+++ b/mod/lti/amd/src/tool_configure_controller.js
@@ -26,8 +26,8 @@
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/events', 'mod_lti/keys', 'mod_lti/tool_type',
- 'mod_lti/tool_proxy', 'core/str'],
- function($, ajax, notification, templates, ltiEvents, KEYS, toolType, toolProxy, str) {
+ 'mod_lti/tool_proxy', 'core/str', 'core/config'],
+ function($, ajax, notification, templates, ltiEvents, KEYS, toolType, toolProxy, str, config) {
var SELECTORS = {
EXTERNAL_REGISTRATION_CONTAINER: '#external-registration-container',
@@ -116,7 +116,7 @@ define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'mod_lti/e
$(SELECTORS.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass('hidden');
var container = $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
container.append($(""));
+ + encodeURIComponent(url) + "&sesskey=" + config.sesskey + "'>"));
showExternalRegistration();
window.addEventListener("message", closeLTIAdvRegistration, false);
};
diff --git a/mod/lti/classes/local/ltiopenid/registration_helper.php b/mod/lti/classes/local/ltiopenid/registration_helper.php
index ffedbbf021b..bbbe9a3d160 100644
--- a/mod/lti/classes/local/ltiopenid/registration_helper.php
+++ b/mod/lti/classes/local/ltiopenid/registration_helper.php
@@ -51,6 +51,19 @@ class registration_helper {
/** Tool Settings scope */
const SCOPE_TOOL_SETTING = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting';
+ /** Indicates the token is to create a new registration */
+ const REG_TOKEN_OP_NEW_REG = 'reg';
+ /** Indicates the token is to update an existing registration */
+ const REG_TOKEN_OP_UPDATE_REG = 'reg-update';
+
+ /**
+ * Get an instance of this helper
+ *
+ * @return object
+ */
+ public static function get() {
+ return new registration_helper();
+ }
/**
* Function used to validate parameters.
@@ -64,7 +77,7 @@ class registration_helper {
*
* @return mixed
*/
- private static function get_parameter(array $payload, string $key, bool $required) {
+ private function get_parameter(array $payload, string $key, bool $required) {
if (!isset($payload[$key]) || empty($payload[$key])) {
if ($required) {
throw new registration_exception('missing required attribute '.$key, 400);
@@ -87,33 +100,36 @@ class registration_helper {
*
* @return object the Moodle LTI config.
*/
- public static function registration_to_config(array $registrationpayload, string $clientid): object {
- $responsetypes = self::get_parameter($registrationpayload, 'response_types', true);
- $initiateloginuri = self::get_parameter($registrationpayload, 'initiate_login_uri', true);
- $redirecturis = self::get_parameter($registrationpayload, 'redirect_uris', true);
- $clientname = self::get_parameter($registrationpayload, 'client_name', true);
- $jwksuri = self::get_parameter($registrationpayload, 'jwks_uri', true);
- $tokenendpointauthmethod = self::get_parameter($registrationpayload, 'token_endpoint_auth_method', true);
+ public function registration_to_config(array $registrationpayload, string $clientid): object {
+ $responsetypes = $this->get_parameter($registrationpayload, 'response_types', true);
+ $initiateloginuri = $this->get_parameter($registrationpayload, 'initiate_login_uri', true);
+ $redirecturis = $this->get_parameter($registrationpayload, 'redirect_uris', true);
+ $clientname = $this->get_parameter($registrationpayload, 'client_name', true);
+ $jwksuri = $this->get_parameter($registrationpayload, 'jwks_uri', true);
+ $tokenendpointauthmethod = $this->get_parameter($registrationpayload, 'token_endpoint_auth_method', true);
- $applicationtype = self::get_parameter($registrationpayload, 'application_type', false);
- $logouri = self::get_parameter($registrationpayload, 'logo_uri', false);
+ $applicationtype = $this->get_parameter($registrationpayload, 'application_type', false);
+ $logouri = $this->get_parameter($registrationpayload, 'logo_uri', false);
- $ltitoolconfiguration = self::get_parameter($registrationpayload,
+ $ltitoolconfiguration = $this->get_parameter($registrationpayload,
'https://purl.imsglobal.org/spec/lti-tool-configuration', true);
- $domain = self::get_parameter($ltitoolconfiguration, 'domain', false);
- $targetlinkuri = self::get_parameter($ltitoolconfiguration, 'target_link_uri', false);
- $customparameters = self::get_parameter($ltitoolconfiguration, 'custom_parameters', false);
- $scopes = explode(" ", self::get_parameter($registrationpayload, 'scope', false) ?? '');
- $claims = self::get_parameter($ltitoolconfiguration, 'claims', false);
+ $domain = $this->get_parameter($ltitoolconfiguration, 'domain', false);
+ $targetlinkuri = $this->get_parameter($ltitoolconfiguration, 'target_link_uri', false);
+ $customparameters = $this->get_parameter($ltitoolconfiguration, 'custom_parameters', false);
+ $scopes = explode(" ", $this->get_parameter($registrationpayload, 'scope', false) ?? '');
+ $claims = $this->get_parameter($ltitoolconfiguration, 'claims', false);
$messages = $ltitoolconfiguration['messages'] ?? [];
- $description = self::get_parameter($ltitoolconfiguration, 'description', false);
+ $description = $this->get_parameter($ltitoolconfiguration, 'description', false);
// Validate domain and target link.
if (empty($domain)) {
throw new registration_exception('missing_domain', 400);
}
+
$targetlinkuri = $targetlinkuri ?: 'https://'.$domain;
+ // Stripping www as this is ignored for domain matching.
+ $domain = lti_get_domain_from_url($domain);
if ($domain !== lti_get_domain_from_url($targetlinkuri)) {
throw new registration_exception('domain_targetlinkuri_mismatch', 400);
}
@@ -237,72 +253,107 @@ class registration_helper {
return $config;
}
+ /**
+ * Adds to the config the LTI 1.1 key and sign it with the 1.1 secret.
+ *
+ * @param array $lticonfig reference to lticonfig to which to add the 1.1 OAuth info.
+ * @param string $key - LTI 1.1 OAuth Key
+ * @param string $secret - LTI 1.1 OAuth Secret
+ *
+ */
+ private function add_previous_key_claim(array &$lticonfig, string $key, string $secret) {
+ if ($key) {
+ $oauthconsumer = [];
+ $oauthconsumer['key'] = $key;
+ $oauthconsumer['nonce'] = random_string(random_int(10, 20));
+ $oauthconsumer['sign'] = hash('sha256', $key.$secret.$oauthconsumer['nonce']);
+ $lticonfig['oauth_consumer'] = $oauthconsumer;
+ }
+ }
+
/**
* Transforms a moodle LTI 1.3 Config to an OAuth/LTI Client Registration.
*
* @param object $config Moodle LTI Config.
* @param int $typeid which is the LTI deployment id.
+ * @param object $type tool instance in case the tool already exists.
*
* @return array the Client Registration as an associative array.
*/
- public static function config_to_registration(object $config, int $typeid): array {
+ public function config_to_registration(object $config, int $typeid, object $type = null): array {
+ $configarray = [];
+ foreach ((array)$config as $k => $v) {
+ if (substr($k, 0, 4) == 'lti_') {
+ $k = substr($k, 4);
+ }
+ $configarray[$k] = $v;
+ }
+ $config = (object) $configarray;
$registrationresponse = [];
- $registrationresponse['client_id'] = $config->lti_clientid;
- $registrationresponse['token_endpoint_auth_method'] = ['private_key_jwt'];
- $registrationresponse['response_types'] = ['id_token'];
- $registrationresponse['jwks_uri'] = $config->lti_publickeyset;
- $registrationresponse['initiate_login_uri'] = $config->lti_initiatelogin;
- $registrationresponse['grant_types'] = ['client_credentials', 'implicit'];
- $registrationresponse['redirect_uris'] = explode(PHP_EOL, $config->lti_redirectionuris);
- $registrationresponse['application_type'] = 'web';
- $registrationresponse['token_endpoint_auth_method'] = 'private_key_jwt';
- $registrationresponse['client_name'] = $config->lti_typename;
- $registrationresponse['logo_uri'] = $config->lti_icon ?? '';
$lticonfigurationresponse = [];
+ $ltiversion = $type ? $type->ltiversion : $config->ltiversion;
+ $lticonfigurationresponse['version'] = $ltiversion;
+ if ($ltiversion === LTI_VERSION_1P3) {
+ $registrationresponse['client_id'] = $type ? $type->clientid : $config->clientid;
+ $registrationresponse['response_types'] = ['id_token'];
+ $registrationresponse['jwks_uri'] = $config->publickeyset;
+ $registrationresponse['initiate_login_uri'] = $config->initiatelogin;
+ $registrationresponse['grant_types'] = ['client_credentials', 'implicit'];
+ $registrationresponse['redirect_uris'] = explode(PHP_EOL, $config->redirectionuris);
+ $registrationresponse['application_type'] = 'web';
+ $registrationresponse['token_endpoint_auth_method'] = 'private_key_jwt';
+ } else if ($ltiversion === LTI_VERSION_1 && $type) {
+ $this->add_previous_key_claim($lticonfigurationresponse, $config->resourcekey, $config->password);
+ } else if ($ltiversion === LTI_VERSION_2 && $type) {
+ $toolproxy = $this->get_tool_proxy($type->toolproxyid);
+ $this->add_previous_key_claim($lticonfigurationresponse, $toolproxy['guid'], $toolproxy['secret']);
+ }
+ $registrationresponse['client_name'] = $type ? $type->name : $config->typename;
+ $registrationresponse['logo_uri'] = $type ? ($type->secureicon ?? $type->icon ?? '') : $config->icon ?? '';
$lticonfigurationresponse['deployment_id'] = strval($typeid);
- $lticonfigurationresponse['target_link_uri'] = $config->lti_toolurl;
- $lticonfigurationresponse['domain'] = $config->lti_tooldomain ?? '';
- $lticonfigurationresponse['description'] = $config->lti_description ?? '';
- if ($config->lti_contentitem == 1) {
+ $lticonfigurationresponse['target_link_uri'] = $type ? $type->baseurl : $config->toolurl ?? '';
+ $lticonfigurationresponse['domain'] = $type ? $type->tooldomain : $config->tooldomain ?? '';
+ $lticonfigurationresponse['description'] = $type ? $type->description ?? '' : $config->description ?? '';
+ if ($config->contentitem ?? 0 == 1) {
$contentitemmessage = [];
$contentitemmessage['type'] = 'LtiDeepLinkingRequest';
- if (isset($config->lti_toolurl_ContentItemSelectionRequest)) {
- $contentitemmessage['target_link_uri'] = $config->lti_toolurl_ContentItemSelectionRequest;
+ if (isset($config->toolurl_ContentItemSelectionRequest)) {
+ $contentitemmessage['target_link_uri'] = $config->toolurl_ContentItemSelectionRequest;
}
$lticonfigurationresponse['messages'] = [$contentitemmessage];
}
- if (isset($config->lti_customparameters) && !empty($config->lti_customparameters)) {
+ if (isset($config->customparameters) && !empty($config->customparameters)) {
$params = [];
- foreach (explode(PHP_EOL, $config->lti_customparameters) as $param) {
+ foreach (explode(PHP_EOL, $config->customparameters) as $param) {
$split = explode('=', $param);
$params[$split[0]] = $split[1];
}
$lticonfigurationresponse['custom_parameters'] = $params;
}
$scopesresponse = [];
- if ($config->ltiservice_gradesynchronization > 0) {
+ if ($config->ltiservice_gradesynchronization ?? 0 > 0) {
$scopesresponse[] = self::SCOPE_SCORE;
$scopesresponse[] = self::SCOPE_RESULT;
$scopesresponse[] = self::SCOPE_LINEITEM_RO;
}
- if ($config->ltiservice_gradesynchronization == 2) {
+ if ($config->ltiservice_gradesynchronization ?? 0 == 2) {
$scopesresponse[] = self::SCOPE_LINEITEM;
}
- if ($config->ltiservice_memberships == 1) {
+ if ($config->ltiservice_memberships ?? 0 == 1) {
$scopesresponse[] = self::SCOPE_NRPS;
}
- if ($config->ltiservice_toolsettings == 1) {
+ if ($config->ltiservice_toolsettings ?? 0 == 1) {
$scopesresponse[] = self::SCOPE_TOOL_SETTING;
}
$registrationresponse['scope'] = implode(' ', $scopesresponse);
$claimsresponse = ['sub', 'iss'];
- if ($config->lti_sendname == LTI_SETTING_ALWAYS) {
+ if ($config->sendname ?? '' == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'name';
$claimsresponse[] = 'family_name';
$claimsresponse[] = 'given_name';
}
- if ($config->lti_sendemailaddr == LTI_SETTING_ALWAYS) {
+ if ($config->sendemailaddr ?? '' == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'email';
}
$lticonfigurationresponse['claims'] = $claimsresponse;
@@ -316,21 +367,32 @@ class registration_helper {
*
* @param string $registrationtokenjwt registration token
*
- * @return string client id for the registration
+ * @return array with 2 keys: clientid for the registration, type but only if it's an update
*/
- public static function validate_registration_token(string $registrationtokenjwt): string {
+ public function validate_registration_token(string $registrationtokenjwt): array {
global $DB;
$keys = JWK::parseKeySet(jwks_helper::get_jwks());
$registrationtoken = JWT::decode($registrationtokenjwt, $keys, ['RS256']);
-
+ $response = [];
// Get clientid from registrationtoken.
$clientid = $registrationtoken->sub;
-
- // Checks if clientid is already registered.
- if (!empty($DB->get_record('lti_types', array('clientid' => $clientid)))) {
- throw new registration_exception("token_already_used", 401);
+ if ($registrationtoken->scope == self::REG_TOKEN_OP_NEW_REG) {
+ // Checks if clientid is already registered.
+ if (!empty($DB->get_record('lti_types', array('clientid' => $clientid)))) {
+ throw new registration_exception("token_already_used", 401);
+ }
+ $response['clientid'] = $clientid;
+ } else if ($registrationtoken->scope == self::REG_TOKEN_OP_UPDATE_REG) {
+ $tool = lti_get_type($registrationtoken->sub);
+ if (!$tool) {
+ throw new registration_exception("Unknown client", 400);
+ }
+ $response['clientid'] = $tool->clientid ?? $this->new_clientid();
+ $response['type'] = $tool;
+ } else {
+ throw new registration_exception("Incorrect scope", 403);
}
- return $clientid;
+ return $response;
}
/**
@@ -338,7 +400,7 @@ class registration_helper {
*
* @return array List of scopes
*/
- public static function lti_get_service_scopes() {
+ public function lti_get_service_scopes() {
$services = lti_get_services();
$scopes = array();
@@ -351,4 +413,35 @@ class registration_helper {
return $scopes;
}
+ /**
+ * Generates a new client id string.
+ *
+ * @return string generated client id
+ */
+ public function new_clientid(): string {
+ return random_string(15);
+ }
+
+ /**
+ * Base64 encoded signature for LTI 1.1 migration.
+ * @param string $key LTI 1.1 key
+ * @param string $salt Salt value
+ * @param string $secret LTI 1.1 secret
+ *
+ * @return string base64encoded hash
+ */
+ public function sign(string $key, string $salt, string $secret): string {
+ return base64_encode(hash_hmac('sha-256', $key.$salt, $secret, true));
+ }
+
+ /**
+ * Returns a tool proxy
+ *
+ * @param int $proxyid
+ *
+ * @return mixed Tool Proxy details
+ */
+ public function get_tool_proxy(int $proxyid) : array {
+ return lti_get_tool_proxy($proxyid);
+ }
}
diff --git a/mod/lti/classes/output/registration_upgrade_choice_page.php b/mod/lti/classes/output/registration_upgrade_choice_page.php
new file mode 100644
index 00000000000..1e7e3a0f4d8
--- /dev/null
+++ b/mod/lti/classes/output/registration_upgrade_choice_page.php
@@ -0,0 +1,69 @@
+.
+
+/**
+ * Class containing data for rendering LTI upgrade choices page.
+ *
+ * @copyright 2021 Cengage
+ * @package mod_lti
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_lti\output;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot.'/mod/lti/locallib.php');
+
+use renderable;
+use templatable;
+use renderer_base;
+use stdClass;
+
+/**
+ * Class containing data for rendering LTI upgrade choices page.
+ *
+ * @copyright 2021 Cengage
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class registration_upgrade_choice_page implements renderable, templatable {
+
+ /**
+ * Constructor
+ *
+ * @param array $tools array of tools that can be upgraded
+ * @param string $startregurl tool URL to start the registration process
+ */
+ public function __construct(array $tools, string $startregurl) {
+ $this->tools = $tools;
+ $this->startregurl = $startregurl;
+ }
+ /**
+ * Export this data so it can be used as the context for a mustache template.
+ *
+ * @param renderer_base $output The renderer
+ * @return stdClass Data to be used by the template
+ */
+ public function export_for_template(renderer_base $output) {
+ $renderdata = new stdClass();
+ $renderdata->startregurlenc = urlencode($this->startregurl);
+ $renderdata->sesskey = sesskey();
+ $renderdata->tools = [];
+ foreach ($this->tools as $tool) {
+ $renderdata->tools[] = (object)$tool;
+ }
+ return $renderdata;
+ }
+}
diff --git a/mod/lti/classes/output/renderer.php b/mod/lti/classes/output/renderer.php
index b94178da9f7..e0bdb59161d 100644
--- a/mod/lti/classes/output/renderer.php
+++ b/mod/lti/classes/output/renderer.php
@@ -59,4 +59,16 @@ class renderer extends plugin_renderer_base {
$data = $page->export_for_template($this);
return parent::render_from_template('mod_lti/external_registration_return', $data);
}
+
+ /**
+ * Render the external registration return page
+ *
+ * @param tool_configure_page $page
+ *
+ * @return string html for the page
+ */
+ public function render_registration_upgrade_choice_page($page) {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('mod_lti/registration_upgrade_choice_page', $data);
+ }
}
diff --git a/mod/lti/lang/en/lti.php b/mod/lti/lang/en/lti.php
index 37467602362..076914d0738 100644
--- a/mod/lti/lang/en/lti.php
+++ b/mod/lti/lang/en/lti.php
@@ -166,6 +166,15 @@ $string['domain_mismatch'] = 'Tool URL\'s domain does not match tool configurati
$string['donot'] = 'Do not send';
$string['donotaccept'] = 'Do not accept';
$string['donotallow'] = 'Do not allow';
+$string['dynreg_update_text'] = 'There are existing tools attached to the registration\'s domain. Do you want to update an already installed
+external tool or create a new external tool?';
+$string['dynreg_update_warn_dupdomain'] = 'It is not recommended to have multiple external tools under the same domain.';
+$string['dynreg_update_name'] = 'Tool name';
+$string['dynreg_update_url'] = 'Base URL';
+$string['dynreg_update_version'] = 'LTI Version';
+$string['dynreg_update_notools'] = 'No tools in context.';
+$string['dynreg_update_btn_update'] = 'Update';
+$string['dynreg_update_btn_new'] = 'Register as a new external tool';
$string['duplicateregurl'] = 'This registration URL is already in use';
$string['editdescription'] = 'Click here to give this tool a description';
$string['edittype'] = 'Edit preconfigured tool';
diff --git a/mod/lti/locallib.php b/mod/lti/locallib.php
index 8182d984079..ae27f4a2528 100644
--- a/mod/lti/locallib.php
+++ b/mod/lti/locallib.php
@@ -55,6 +55,7 @@ use moodle\mod\lti as lti;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
use mod_lti\local\ltiopenid\jwks_helper;
+use mod_lti\local\ltiopenid\registration_helper;
global $CFG;
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
@@ -2752,7 +2753,7 @@ function lti_prepare_type_for_save($type, $config) {
$type->clientid = $config->lti_clientid;
}
if ((!empty($type->ltiversion) && $type->ltiversion === LTI_VERSION_1P3) && empty($type->clientid)) {
- $type->clientid = random_string(15);
+ $type->clientid = registration_helper::get()->new_clientid();
} else if (empty($type->clientid)) {
$type->clientid = null;
}
@@ -2822,6 +2823,14 @@ function lti_update_type($type, $config) {
lti_update_config($record);
}
}
+ if (isset($type->toolproxyid) && $type->ltiversion === LTI_VERSION_1P3) {
+ // We need to remove the tool proxy for this tool to function under 1.3.
+ $toolproxyid = $type->toolproxyid;
+ $DB->delete_records('lti_tool_settings', array('toolproxyid' => $toolproxyid));
+ $DB->delete_records('lti_tool_proxies', array('id' => $toolproxyid));
+ $type->toolproxyid = null;
+ $DB->update_record('lti_types', $type);
+ }
require_once($CFG->libdir.'/modinfolib.php');
if ($clearcache) {
$sql = "SELECT DISTINCT course
diff --git a/mod/lti/openid-configuration.php b/mod/lti/openid-configuration.php
index 7d616c39eec..0e911da79c9 100644
--- a/mod/lti/openid-configuration.php
+++ b/mod/lti/openid-configuration.php
@@ -33,7 +33,7 @@ require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
require_once($CFG->libdir.'/weblib.php');
-$scopes = registration_helper::lti_get_service_scopes();
+$scopes = registration_helper::get()->lti_get_service_scopes();
$scopes[] = 'openid';
$conf = [
'issuer' => $CFG->wwwroot,
diff --git a/mod/lti/openid-registration.php b/mod/lti/openid-registration.php
index c06e531dc1b..a9d748bc32e 100644
--- a/mod/lti/openid-registration.php
+++ b/mod/lti/openid-registration.php
@@ -32,27 +32,50 @@ require_once($CFG->dirroot . '/mod/lti/locallib.php');
$code = 200;
$message = '';
-// Retrieve registration token from Bearer Authorization header.
-$authheader = moodle\mod\lti\OAuthUtil::get_headers() ['Authorization'] ?? '';
-if (!($authheader && substr($authheader, 0, 7) == 'Bearer ')) {
- $message = 'missing_registration_token';
- $code = 401;
-} else {
- $registrationpayload = json_decode(file_get_contents('php://input'), true);
+if ($_SERVER['REQUEST_METHOD'] === 'POST' or ($_SERVER['REQUEST_METHOD'] === 'GET')) {
+ $doregister = $_SERVER['REQUEST_METHOD'] === 'POST';
+ // Retrieve registration token from Bearer Authorization header.
+ $authheader = moodle\mod\lti\OAuthUtil::get_headers()['Authorization'] ?? '';
+ if (!($authheader && substr($authheader, 0, 7) == 'Bearer ')) {
+ $message = 'missing_registration_token';
+ $code = 401;
+ } else {
- // Registers tool.
- $type = new stdClass();
- $type->state = LTI_TOOL_STATE_PENDING;
- try {
- $clientid = registration_helper::validate_registration_token(trim(substr($authheader, 7)));
- $config = registration_helper::registration_to_config($registrationpayload, $clientid);
- $typeid = lti_add_type($type, clone $config);
- $message = json_encode(registration_helper::config_to_registration($config, $typeid));
- header('Content-Type: application/json; charset=utf-8');
- } catch (registration_exception $e) {
- $code = $e->getCode();
- $message = $e->getMessage();
+ // Registers tool.
+ try {
+ $tokenres = registration_helper::get()->validate_registration_token(trim(substr($authheader, 7)));
+ $type = new stdClass();
+ $type->state = LTI_TOOL_STATE_PENDING;
+ if (array_key_exists('type', $tokenres)) {
+ $type = $tokenres['type'];
+ }
+ if ($doregister) {
+ $registrationpayload = json_decode(file_get_contents('php://input'), true);
+ $config = registration_helper::get()->registration_to_config($registrationpayload, $tokenres['clientid']);
+ if ($type->id) {
+ lti_update_type($type, clone $config);
+ $typeid = $type->id;
+ } else {
+ $typeid = lti_add_type($type, clone $config);
+ }
+ header('Content-Type: application/json; charset=utf-8');
+ $message = json_encode(registration_helper::get()->config_to_registration((object)$config, $typeid));
+ } else if ($type) {
+ $config = lti_get_type_config($type->id);
+ header('Content-Type: application/json; charset=utf-8');
+ $message = json_encode(registration_helper::get()->config_to_registration((object)$config, $type->id, $type));
+ } else {
+ $code = 404;
+ $message = "No registration found.";
+ }
+ } catch (registration_exception $e) {
+ $code = $e->getCode();
+ $message = $e->getMessage();
+ }
}
+} else {
+ $code = 400;
+ $message = 'Unsupported operation';
}
$response = new \mod_lti\local\ltiservice\response();
// Set code.
diff --git a/mod/lti/startltiadvregistration.php b/mod/lti/startltiadvregistration.php
index 9644a9479ff..ddf993a78e2 100644
--- a/mod/lti/startltiadvregistration.php
+++ b/mod/lti/startltiadvregistration.php
@@ -26,26 +26,55 @@
use Firebase\JWT\JWT;
use mod_lti\local\ltiopenid\jwks_helper;
+use mod_lti\local\ltiopenid\registration_helper;
require_once(__DIR__ . '/../../config.php');
require_once($CFG->libdir.'/weblib.php');
+require_once($CFG->dirroot . '/mod/lti/locallib.php');
require_login();
$context = context_system::instance();
require_capability('moodle/site:config', $context);
$starturl = required_param('url', PARAM_URL);
-$now = time();
-$token = [
- "sub" => random_string(15),
- "scope" => "reg",
- "iat" => $now,
- "exp" => $now + HOURSECS
-];
-$privatekey = jwks_helper::get_private_key();
-$regtoken = JWT::encode($token, $privatekey['key'], 'RS256', $privatekey['kid']);
-$confurl = new moodle_url('/mod/lti/openid-configuration.php');
-$url = new moodle_url($starturl);
-$url->param('openid_configuration', $confurl->out(false));
-$url->param('registration_token', $regtoken);
-header("Location: ".$url->out(false));
+$typeid = optional_param('type', -1, PARAM_INT);
+
+$types = lti_get_tools_by_url($starturl, null);
+
+if (!empty($types) && $typeid == -1) {
+ // There are matching types for the registration domain, let's prompt the user to upgrade.
+ $pageurl = new moodle_url('/mod/lti/startltiadvregistration.php');
+ $PAGE->set_context($context);
+ $PAGE->set_url($pageurl);
+ $PAGE->set_pagelayout('maintenance');
+ $output = $PAGE->get_renderer('mod_lti');
+ $page = new \mod_lti\output\registration_upgrade_choice_page($types, $starturl);
+ echo $output->header();
+ echo $output->render($page);
+ echo $output->footer();
+} else {
+ // Let's actually start the registration process by launching the tool registration
+ // endpoint with the registration token and the site config url.
+ require_sesskey();
+ $sub = registration_helper::get()->new_clientid();
+ $scope = registration_helper::REG_TOKEN_OP_NEW_REG;
+ if ($typeid > 0) {
+ // In the context of an update, the sub is the id of the type.
+ $sub = strval($typeid);
+ $scope = registration_helper::REG_TOKEN_OP_UPDATE_REG;
+ }
+ $now = time();
+ $token = [
+ "sub" => $sub,
+ "scope" => $scope,
+ "iat" => $now,
+ "exp" => $now + HOURSECS
+ ];
+ $privatekey = jwks_helper::get_private_key();
+ $regtoken = JWT::encode($token, $privatekey['key'], 'RS256', $privatekey['kid']);
+ $confurl = new moodle_url('/mod/lti/openid-configuration.php');
+ $url = new moodle_url($starturl);
+ $url->param('openid_configuration', $confurl->out(false));
+ $url->param('registration_token', $regtoken);
+ header("Location: ".$url->out(false));
+}
diff --git a/mod/lti/templates/registration_upgrade_choice_page.mustache b/mod/lti/templates/registration_upgrade_choice_page.mustache
new file mode 100644
index 00000000000..57ea0465e12
--- /dev/null
+++ b/mod/lti/templates/registration_upgrade_choice_page.mustache
@@ -0,0 +1,94 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_lti/registration_upgrade_choice_page.mustache
+
+ This template provides the layout to display the upgrade choices
+ available when registering a tool for which there exists at least
+ one other tool with the same domain.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * startregurlenc: the URL provided by the tool to start the registration, url encoded
+ * tools: the list of tools matching the domain
+
+ Example context (json):
+ {
+ "startregurlenc":"https%3A%2F%2Fmytool.example%2Fregister",
+ "tools": [
+ {
+ "id": 222,
+ "name": "This is My LTI 1.1 tool",
+ "baseurl": "https://mytool.example",
+ "version": "LTI-1p0",
+ "sesskey": "785t9302"
+ }
+ ]
+ }
+
+}}
+{{#str}}dynreg_update_text, mod_lti {{/str}}
+
+{{#str}}dynreg_update_warn_dupdomain, mod_lti {{/str}}
+
+
+
+| {{#str}}dynreg_update_name, mod_lti {{/str}} |
+{{#str}}dynreg_update_url, mod_lti {{/str}} |
+{{#str}}dynreg_update_version, mod_lti {{/str}} |
+ |
+
+{{#tools}}
+
+ | {{name}} |
+ {{baseurl}} |
+ {{ltiversion}} |
+
+ |
+
+{{/tools}}
+
+{{^tools}}
+{{#str}}dynreg_update_notools, mod_lti {{/str}}
+{{/tools}}
+
+
+
+
+{{#js}}
+ disableAndGo = e => {
+ document.querySelectorAll("button").forEach(e=>e.setAttribute("disabled", true));
+ e.target.querySelector("span.btn-loader").style.display="inline";
+ let form = document.getElementById("startregform");
+ form.action = e.target.getAttribute("data-href");
+ form.submit();
+ };
+ document.querySelectorAll("button").forEach(b=>b.onclick=disableAndGo);
+{{/js}}
diff --git a/mod/lti/tests/openidregistration_test.php b/mod/lti/tests/openidregistration_test.php
index 4f789f5e7ca..6e1a89ac4b1 100644
--- a/mod/lti/tests/openidregistration_test.php
+++ b/mod/lti/tests/openidregistration_test.php
@@ -40,14 +40,14 @@
* @author Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-
+namespace mod_lti\local\ltiopenid;
use mod_lti\local\ltiopenid\registration_exception;
use mod_lti\local\ltiopenid\registration_helper;
/**
* OpenId LTI Registration library tests
*/
-class mod_lti_openidregistrationlib_testcase extends advanced_testcase {
+class mod_lti_openidregistrationlib_testcase extends \advanced_testcase {
/**
* @var string A has-it-all client registration.
@@ -108,8 +108,8 @@ EOD;
"jwks_uri": "https://client.example.org/.well-known/jwks.json",
"token_endpoint_auth_method": "private_key_jwt",
"https://purl.imsglobal.org/spec/lti-tool-configuration": {
- "domain": "client.example.org",
- "target_link_uri": "https://client.example.org/lti"
+ "domain": "www.example.org",
+ "target_link_uri": "https://www.example.org/lti"
}
}
EOD;
@@ -146,7 +146,7 @@ EOD;
public function test_to_config_full() {
$registration = json_decode($this->registrationfulljson, true);
$registration['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
- $config = registration_helper::registration_to_config($registration, 'TheClientId');
+ $config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals('JWK_KEYSET', $config->lti_keytype);
$this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion);
$this->assertEquals('TheClientId', $config->lti_clientid);
@@ -175,12 +175,15 @@ EOD;
*/
public function test_to_config_minimal() {
$registration = json_decode($this->registrationminimaljson, true);
- $config = registration_helper::registration_to_config($registration, 'TheClientId');
+ $config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals('JWK_KEYSET', $config->lti_keytype);
$this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion);
$this->assertEquals('TheClientId', $config->lti_clientid);
$this->assertEquals('Virtual Garden', $config->lti_typename);
$this->assertEmpty($config->lti_description);
+ // Special case here where Moodle ignores www for domains.
+ $this->assertEquals('example.org', $config->lti_tooldomain);
+ $this->assertEquals('https://www.example.org/lti', $config->lti_toolurl);
$this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin);
$this->assertEquals('https://client.example.org/callback', $config->lti_redirectionuris);
$this->assertEmpty($config->lti_customparameters);
@@ -200,7 +203,7 @@ EOD;
*/
public function test_to_config_minimal_with_deeplinking() {
$registration = json_decode($this->registrationminimaldljson, true);
- $config = registration_helper::registration_to_config($registration, 'TheClientId');
+ $config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals(1, $config->lti_contentitem);
$this->assertEmpty($config->lti_toolurl_ContentItemSelectionRequest);
}
@@ -213,7 +216,7 @@ EOD;
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['initiate_login_uri']);
- registration_helper::registration_to_config($registration, 'TheClientId');
+ registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -224,7 +227,7 @@ EOD;
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['redirect_uris']);
- registration_helper::registration_to_config($registration, 'TheClientId');
+ registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -235,7 +238,7 @@ EOD;
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$registration['jwks_uri'] = '';
- registration_helper::registration_to_config($registration, 'TheClientId');
+ registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -247,7 +250,7 @@ EOD;
$this->expectExceptionCode(400);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
- registration_helper::registration_to_config($registration, 'TheClientId');
+ registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -258,7 +261,7 @@ EOD;
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain'] = 'not.the.right.domain';
- registration_helper::registration_to_config($registration, 'TheClientId');
+ registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -269,7 +272,7 @@ EOD;
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
- $config = registration_helper::registration_to_config($registration, 'TheClientId');
+ $config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
@@ -278,9 +281,9 @@ EOD;
public function test_validation_domain_targetlinkuri_onlydomain() {
$registration = json_decode($this->registrationminimaljson, true);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
- $config = registration_helper::registration_to_config($registration, 'TheClientId');
- $this->assertEquals('client.example.org', $config->lti_tooldomain);
- $this->assertEquals('https://client.example.org', $config->lti_toolurl);
+ $config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
+ $this->assertEquals('example.org', $config->lti_tooldomain);
+ $this->assertEquals('https://www.example.org', $config->lti_toolurl);
}
/**
@@ -289,7 +292,8 @@ EOD;
public function test_config_to_registration() {
$orig = json_decode($this->registrationfulljson, true);
$orig['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
- $reg = registration_helper::config_to_registration(registration_helper::registration_to_config($orig, 'clid'), 12);
+ $reghelper = registration_helper::get();
+ $reg = $reghelper->config_to_registration($reghelper->registration_to_config($orig, 'clid'), 12);
$this->assertEquals('clid', $reg['client_id']);
$this->assertEquals($orig['response_types'], $reg['response_types']);
$this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
@@ -325,7 +329,8 @@ EOD;
*/
public function test_config_to_registration_minimal() {
$orig = json_decode($this->registrationminimaljson, true);
- $reg = registration_helper::config_to_registration(registration_helper::registration_to_config($orig, 'clid'), 12);
+ $reghelper = registration_helper::get();
+ $reg = $reghelper->config_to_registration($reghelper->registration_to_config($orig, 'clid'), 12);
$this->assertEquals('clid', $reg['client_id']);
$this->assertEquals($orig['response_types'], $reg['response_types']);
$this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
@@ -342,4 +347,94 @@ EOD;
$this->assertFalse(in_array('name', $lti['claims']));
}
+ /**
+ * Test the transformation from lti config 1.1 to Registration Response.
+ */
+ public function test_config_to_registration_lti11() {
+ $config = [];
+ $config['contentitem'] = 1;
+ $config['toolurl_ContentItemSelectionRequest'] = '';
+ $config['sendname'] = 0;
+ $config['sendemailaddr'] = 1;
+ $config['acceptgrades'] = 2;
+ $config['resourcekey'] = 'testkey';
+ $config['password'] = 'testp@ssw0rd';
+ $config['customparameters'] = 'a1=b1';
+ $type = [];
+ $type['id'] = 130;
+ $type['name'] = 'LTI Test 1.1';
+ $type['baseurl'] = 'https://base.test.url/test';
+ $type['tooldomain'] = 'base.test.url';
+ $type['ltiversion'] = 'LTI-1p0';
+ $type['icon'] = 'https://base.test.url/icon.png';
+
+ $reg = registration_helper::get()->config_to_registration((object)$config, $type['id'], (object)$type);
+ $this->assertFalse(isset($reg['client_id']));
+ $this->assertFalse(isset($reg['initiate_login_uri']));
+ $this->assertEquals($type['name'], $reg['client_name']);
+ $lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
+ $this->assertEquals(LTI_VERSION_1, $lti['version']);
+ $this->assertEquals('b1', $lti['custom_parameters']['a1']);
+ $this->assertEquals('LtiDeepLinkingRequest', $lti['messages'][0]['type']);
+ $this->assertEquals('base.test.url', $lti['domain']);
+ $this->assertEquals($type['baseurl'], $lti['target_link_uri']);
+ $oauth = $lti['oauth_consumer'];
+ $this->assertEquals('testkey', $oauth['key']);
+ $this->assertFalse(empty($oauth['nonce']));
+ $this->assertEquals(hash('sha256', 'testkeytestp@ssw0rd'.$oauth['nonce']), $oauth['sign']);
+ $this->assertTrue(in_array('iss', $lti['claims']));
+ $this->assertTrue(in_array('sub', $lti['claims']));
+ $this->assertTrue(in_array('email', $lti['claims']));
+ $this->assertFalse(in_array('family_name', $lti['claims']));
+ $this->assertFalse(in_array('given_name', $lti['claims']));
+ $this->assertFalse(in_array('name', $lti['claims']));
+ }
+
+ /**
+ * Test the transformation from lti config 2.0 to Registration Response.
+ * For LTI 2.0 we limit to just passing the previous key/secret.
+ */
+ public function test_config_to_registration_lti20() {
+ $config = [];
+ $config['contentitem'] = 1;
+ $config['toolurl_ContentItemSelectionRequest'] = '';
+ $type = [];
+ $type['id'] = 131;
+ $type['name'] = 'LTI Test 1.2';
+ $type['baseurl'] = 'https://base.test.url/test';
+ $type['tooldomain'] = 'base.test.url';
+ $type['ltiversion'] = 'LTI-2p0';
+ $type['icon'] = 'https://base.test.url/icon.png';
+ $type['toolproxyid'] = 9;
+ $toolproxy = [];
+ $toolproxy['id'] = 9;
+ $toolproxy['guid'] = 'lti2guidtest';
+ $toolproxy['secret'] = 'peM7YDx420bo';
+
+ $reghelper = $this->getMockBuilder(registration_helper::class)
+ ->setMethods(['get_tool_proxy'])
+ ->getMock();
+ $map = [[$toolproxy['id'], $toolproxy]];
+ $reghelper->method('get_tool_proxy')
+ ->will($this->returnValueMap($map));
+ $reg = $reghelper->config_to_registration((object)$config, $type['id'], (object)$type);
+ $this->assertFalse(isset($reg['client_id']));
+ $this->assertFalse(isset($reg['initiate_login_uri']));
+ $this->assertEquals($type['name'], $reg['client_name']);
+ $lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
+ $this->assertEquals(LTI_VERSION_2, $lti['version']);
+ $this->assertEquals('LtiDeepLinkingRequest', $lti['messages'][0]['type']);
+ $this->assertEquals('base.test.url', $lti['domain']);
+ $this->assertEquals($type['baseurl'], $lti['target_link_uri']);
+ $oauth = $lti['oauth_consumer'];
+ $this->assertEquals('lti2guidtest', $toolproxy['guid']);
+ $this->assertFalse(empty($oauth['nonce']));
+ $this->assertEquals(hash('sha256', 'lti2guidtestpeM7YDx420bo'.$oauth['nonce']), $oauth['sign']);
+ $this->assertTrue(in_array('iss', $lti['claims']));
+ $this->assertTrue(in_array('sub', $lti['claims']));
+ $this->assertFalse(in_array('email', $lti['claims']));
+ $this->assertFalse(in_array('family_name', $lti['claims']));
+ $this->assertFalse(in_array('given_name', $lti['claims']));
+ $this->assertFalse(in_array('name', $lti['claims']));
+ }
}