From ca80d53b38313edfe067184521c67320dbb9bf60 Mon Sep 17 00:00:00 2001 From: Claude Vervoort Date: Fri, 30 Oct 2020 15:51:42 -0400 Subject: [PATCH] MDL-70500 lti: dyn reg can be used to update tools --- mod/lti/OAuth.php | 99 +++++++-- .../build/tool_configure_controller.min.js | 2 +- .../tool_configure_controller.min.js.map | 2 +- mod/lti/amd/src/tool_configure_controller.js | 6 +- .../local/ltiopenid/registration_helper.php | 197 +++++++++++++----- .../registration_upgrade_choice_page.php | 69 ++++++ mod/lti/classes/output/renderer.php | 12 ++ mod/lti/lang/en/lti.php | 9 + mod/lti/locallib.php | 11 +- mod/lti/openid-configuration.php | 2 +- mod/lti/openid-registration.php | 61 ++++-- mod/lti/startltiadvregistration.php | 57 +++-- .../registration_upgrade_choice_page.mustache | 94 +++++++++ mod/lti/tests/openidregistration_test.php | 131 ++++++++++-- 14 files changed, 621 insertions(+), 131 deletions(-) create mode 100644 mod/lti/classes/output/registration_upgrade_choice_page.php create mode 100644 mod/lti/templates/registration_upgrade_choice_page.mustache diff --git a/mod/lti/OAuth.php b/mod/lti/OAuth.php index 0ba4e85b091..6dd60f0bec0 100644 --- a/mod/lti/OAuth.php +++ b/mod/lti/OAuth.php @@ -54,18 +54,29 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +/** + * This file contains the OAuth 1.0a implementation used for support for LTI 1.1. + * + * @package mod_lti + * @copyright moodle + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ namespace moodle\mod\lti;//Using a namespace as the basicLTI module imports classes with the same names defined('MOODLE_INTERNAL') || die; -$oauth_last_computed_signature = false; +$lastcomputedsignature = false; -/* Generic exception class +/** + * Generic exception class */ class OAuthException extends \Exception { // pass } +/** + * OAuth 1.0 Consumer class + */ class OAuthConsumer { public $key; public $secret; @@ -118,17 +129,25 @@ class OAuthSignatureMethod { } } -class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { - function get_name() { - return "HMAC-SHA1"; - } + +/** + * Base class for the HMac based signature methods. + */ +abstract class OAuthSignatureMethod_HMAC extends OAuthSignatureMethod { + + /** + * Name of the Algorithm used. + * + * @return string algorithm name. + */ + abstract public function get_name(): string; public function build_signature($request, $consumer, $token) { - global $oauth_last_computed_signature; - $oauth_last_computed_signature = false; + global $lastcomputedsignature; + $lastcomputedsignature = false; - $base_string = $request->get_signature_base_string(); - $request->base_string = $base_string; + $basestring = $request->get_signature_base_string(); + $request->base_string = $basestring; $key_parts = array( $consumer->secret, @@ -138,15 +157,48 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod { $key_parts = OAuthUtil::urlencode_rfc3986($key_parts); $key = implode('&', $key_parts); - $computed_signature = base64_encode(hash_hmac('sha1', $base_string, $key, true)); - $oauth_last_computed_signature = $computed_signature; - return $computed_signature; + $computedsignature = base64_encode(hash_hmac(strtolower(substr($this->get_name(), 5)), $basestring, $key, true)); + $lastcomputedsignature = $computedsignature; + return $computedsignature; } } +/** + * Implementation for SHA 1. + */ +class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod_HMAC { + /** + * Name of the Algorithm used. + * + * @return string algorithm name. + */ + public function get_name(): string { + return "HMAC-SHA1"; + } +} + +/** + * Implementation for SHA 256. + */ +class OAuthSignatureMethod_HMAC_SHA256 extends OAuthSignatureMethod_HMAC { + /** + * Name of the Algorithm used. + * + * @return string algorithm name. + */ + public function get_name(): string { + return "HMAC-SHA256"; + } +} + class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { - public function get_name() { + /** + * Name of the Algorithm used. + * + * @return string algorithm name. + */ + public function get_name(): string { return "PLAINTEXT"; } @@ -170,7 +222,12 @@ class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod { } class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod { - public function get_name() { + /** + * Name of the Algorithm used. + * + * @return string algorithm name. + */ + public function get_name(): string { return "RSA-SHA1"; } @@ -539,8 +596,8 @@ class OAuthServer { * verify an api call, checks all the parameters */ public function verify_request(&$request) { - global $oauth_last_computed_signature; - $oauth_last_computed_signature = false; + global $lastcomputedsignature; + $lastcomputedsignature = false; $this->get_version($request); $consumer = $this->get_consumer($request); $token = $this->get_token($request, $consumer, "access"); @@ -620,8 +677,8 @@ class OAuthServer { */ private function check_signature(&$request, $consumer, $token) { // this should probably be in a different method - global $oauth_last_computed_signature; - $oauth_last_computed_signature = false; + global $lastcomputedsignature; + $lastcomputedsignature = false; $timestamp = @ $request->get_parameter('oauth_timestamp'); $nonce = @ $request->get_parameter('oauth_nonce'); @@ -636,8 +693,8 @@ class OAuthServer { if (!$valid_sig) { $ex_text = "Invalid signature"; - if ($oauth_last_computed_signature) { - $ex_text = $ex_text . " ours= $oauth_last_computed_signature yours=$signature"; + if ($lastcomputedsignature) { + $ex_text = $ex_text . " ours= $lastcomputedsignature yours=$signature"; } throw new OAuthException($ex_text); } diff --git a/mod/lti/amd/build/tool_configure_controller.min.js b/mod/lti/amd/build/tool_configure_controller.min.js index cf707f9a49a..42c7ef5ff6f 100644 --- a/mod/lti/amd/build/tool_configure_controller.min.js +++ b/mod/lti/amd/build/tool_configure_controller.min.js @@ -1,2 +1,2 @@ -define ("mod_lti/tool_configure_controller",["jquery","core/ajax","core/notification","core/templates","mod_lti/events","mod_lti/keys","mod_lti/tool_type","mod_lti/tool_proxy","core/str"],function(a,b,c,d,e,f,g,h,i){var j={EXTERNAL_REGISTRATION_CONTAINER:"#external-registration-container",EXTERNAL_REGISTRATION_PAGE_CONTAINER:"#external-registration-page-container",EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER:"#external-registration-template-container",CARTRIDGE_REGISTRATION_CONTAINER:"#cartridge-registration-container",CARTRIDGE_REGISTRATION_FORM:"#cartridge-registration-form",ADD_TOOL_FORM:"#add-tool-form",TOOL_LIST_CONTAINER:"#tool-list-container",TOOL_CREATE_BUTTON:"#tool-create-button",TOOL_CREATE_LTILEGACY_BUTTON:"#tool-createltilegacy-button",REGISTRATION_CHOICE_CONTAINER:"#registration-choice-container",TOOL_URL:"#tool-url"},k=function(){return a(j.TOOL_LIST_CONTAINER)},l=function(){return a(j.EXTERNAL_REGISTRATION_CONTAINER)},m=function(){return a(j.CARTRIDGE_REGISTRATION_CONTAINER)},n=function(){return a(j.REGISTRATION_CHOICE_CONTAINER)},o=function(b){if(b.data&&"org.imsglobal.lti.close"===b.data.subject){a(j.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();r();w();z();w();D()}},p=function(b){a(j.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass("hidden");var c=a(j.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);c.append(a(""));u();window.addEventListener("message",o,!1)},q=function(){return a(j.TOOL_URL).val()},r=function(){l().addClass("hidden")},s=function(){m().addClass("hidden")},t=function(){n().addClass("hidden")},u=function(){s();t();l().removeClass("hidden");x(l())},v=function(a){r();t();var b=m();b.find("input").val("");b.removeClass("hidden");b.find(j.CARTRIDGE_REGISTRATION_FORM).attr("data-cartridge-url",a);x(b)},w=function(){r();s();n().removeClass("hidden");x(n())},x=function(a){var b=a.children().detach();b.appendTo(a)},y=function(){k().addClass("hidden")},z=function(){k().removeClass("hidden")},A=function(a){var b=a.error?"error":"success";c.addNotification({message:a.message,type:b})},B=function(a){a.addClass("loading")},C=function(a){a.removeClass("loading")},D=function(){var b=a.Deferred(),e=k();B(e);a.when(g.query(),h.query({orphanedonly:!0})).done(function(a,c){d.render("mod_lti/tool_list",{tools:a,proxies:c}).done(function(a,c){e.empty();e.append(a);d.runTemplateJS(c);b.resolve()}).fail(b.reject)}).fail(b.reject);b.fail(c.exception).always(function(){C(e)})},E=function(){var b=q().trim();if(b){a(j.TOOL_URL).val("");y();p(b)}},F=function(){var b=q().trim();if(""===b){return a.Deferred().resolve()}var d=a(j.TOOL_CREATE_LTILEGACY_BUTTON);B(d);var f=g.isCartridge(b);f.always(function(){C(d)});f.done(function(c){if(c.iscartridge){a(j.TOOL_URL).val("");a(document).trigger(e.START_CARTRIDGE_REGISTRATION,b)}else{a(document).trigger(e.START_EXTERNAL_REGISTRATION,{url:b})}});f.fail(function(){i.get_string("errorbadurl","mod_lti").done(function(b){a(document).trigger(e.REGISTRATION_FEEDBACK,{message:b,error:!0})}).fail(c.exception)});return f},G=function(){a(document).on(e.NEW_TOOL_TYPE,function(){D()});a(document).on(e.START_EXTERNAL_REGISTRATION,function(){u();a(j.TOOL_URL).val("");y()});a(document).on(e.STOP_EXTERNAL_REGISTRATION,function(){z();w()});a(document).on(e.START_CARTRIDGE_REGISTRATION,function(a,b){v(b)});a(document).on(e.STOP_CARTRIDGE_REGISTRATION,function(){m().find(j.CARTRIDGE_REGISTRATION_FORM).removeAttr("data-cartridge-url");w()});a(document).on(e.REGISTRATION_FEEDBACK,function(a,b){A(b)});var b=a(j.TOOL_CREATE_LTILEGACY_BUTTON);b.click(function(a){a.preventDefault();F()});var c=a(j.TOOL_CREATE_BUTTON);c.click(function(a){a.preventDefault();E()})};return{init:function init(){G();D()}}}); +define ("mod_lti/tool_configure_controller",["jquery","core/ajax","core/notification","core/templates","mod_lti/events","mod_lti/keys","mod_lti/tool_type","mod_lti/tool_proxy","core/str","core/config"],function(a,b,c,d,e,f,g,h,i,j){var k={EXTERNAL_REGISTRATION_CONTAINER:"#external-registration-container",EXTERNAL_REGISTRATION_PAGE_CONTAINER:"#external-registration-page-container",EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER:"#external-registration-template-container",CARTRIDGE_REGISTRATION_CONTAINER:"#cartridge-registration-container",CARTRIDGE_REGISTRATION_FORM:"#cartridge-registration-form",ADD_TOOL_FORM:"#add-tool-form",TOOL_LIST_CONTAINER:"#tool-list-container",TOOL_CREATE_BUTTON:"#tool-create-button",TOOL_CREATE_LTILEGACY_BUTTON:"#tool-createltilegacy-button",REGISTRATION_CHOICE_CONTAINER:"#registration-choice-container",TOOL_URL:"#tool-url"},l=function(){return a(k.TOOL_LIST_CONTAINER)},m=function(){return a(k.EXTERNAL_REGISTRATION_CONTAINER)},n=function(){return a(k.CARTRIDGE_REGISTRATION_CONTAINER)},o=function(){return a(k.REGISTRATION_CHOICE_CONTAINER)},p=function(b){if(b.data&&"org.imsglobal.lti.close"===b.data.subject){a(k.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();s();x();A();x();E()}},q=function(b){a(k.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass("hidden");var c=a(k.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);c.append(a(""));v();window.addEventListener("message",p,!1)},r=function(){return a(k.TOOL_URL).val()},s=function(){m().addClass("hidden")},t=function(){n().addClass("hidden")},u=function(){o().addClass("hidden")},v=function(){t();u();m().removeClass("hidden");y(m())},w=function(a){s();u();var b=n();b.find("input").val("");b.removeClass("hidden");b.find(k.CARTRIDGE_REGISTRATION_FORM).attr("data-cartridge-url",a);y(b)},x=function(){s();t();o().removeClass("hidden");y(o())},y=function(a){var b=a.children().detach();b.appendTo(a)},z=function(){l().addClass("hidden")},A=function(){l().removeClass("hidden")},B=function(a){var b=a.error?"error":"success";c.addNotification({message:a.message,type:b})},C=function(a){a.addClass("loading")},D=function(a){a.removeClass("loading")},E=function(){var b=a.Deferred(),e=l();C(e);a.when(g.query(),h.query({orphanedonly:!0})).done(function(a,c){d.render("mod_lti/tool_list",{tools:a,proxies:c}).done(function(a,c){e.empty();e.append(a);d.runTemplateJS(c);b.resolve()}).fail(b.reject)}).fail(b.reject);b.fail(c.exception).always(function(){D(e)})},F=function(){var b=r().trim();if(b){a(k.TOOL_URL).val("");z();q(b)}},G=function(){var b=r().trim();if(""===b){return a.Deferred().resolve()}var d=a(k.TOOL_CREATE_LTILEGACY_BUTTON);C(d);var f=g.isCartridge(b);f.always(function(){D(d)});f.done(function(c){if(c.iscartridge){a(k.TOOL_URL).val("");a(document).trigger(e.START_CARTRIDGE_REGISTRATION,b)}else{a(document).trigger(e.START_EXTERNAL_REGISTRATION,{url:b})}});f.fail(function(){i.get_string("errorbadurl","mod_lti").done(function(b){a(document).trigger(e.REGISTRATION_FEEDBACK,{message:b,error:!0})}).fail(c.exception)});return f},H=function(){a(document).on(e.NEW_TOOL_TYPE,function(){E()});a(document).on(e.START_EXTERNAL_REGISTRATION,function(){v();a(k.TOOL_URL).val("");z()});a(document).on(e.STOP_EXTERNAL_REGISTRATION,function(){A();x()});a(document).on(e.START_CARTRIDGE_REGISTRATION,function(a,b){w(b)});a(document).on(e.STOP_CARTRIDGE_REGISTRATION,function(){n().find(k.CARTRIDGE_REGISTRATION_FORM).removeAttr("data-cartridge-url");x()});a(document).on(e.REGISTRATION_FEEDBACK,function(a,b){B(b)});var b=a(k.TOOL_CREATE_LTILEGACY_BUTTON);b.click(function(a){a.preventDefault();G()});var c=a(k.TOOL_CREATE_BUTTON);c.click(function(a){a.preventDefault();F()})};return{init:function init(){H();E()}}}); //# sourceMappingURL=tool_configure_controller.min.js.map diff --git a/mod/lti/amd/build/tool_configure_controller.min.js.map b/mod/lti/amd/build/tool_configure_controller.min.js.map index d2b2ba4d226..f197ea710d4 100644 --- a/mod/lti/amd/build/tool_configure_controller.min.js.map +++ b/mod/lti/amd/build/tool_configure_controller.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/tool_configure_controller.js"],"names":["define","$","ajax","notification","templates","ltiEvents","KEYS","toolType","toolProxy","str","SELECTORS","EXTERNAL_REGISTRATION_CONTAINER","EXTERNAL_REGISTRATION_PAGE_CONTAINER","EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER","CARTRIDGE_REGISTRATION_CONTAINER","CARTRIDGE_REGISTRATION_FORM","ADD_TOOL_FORM","TOOL_LIST_CONTAINER","TOOL_CREATE_BUTTON","TOOL_CREATE_LTILEGACY_BUTTON","REGISTRATION_CHOICE_CONTAINER","TOOL_URL","getToolListContainer","getExternalRegistrationContainer","getCartridgeRegistrationContainer","getRegistrationChoiceContainer","closeLTIAdvRegistration","e","data","subject","empty","hideExternalRegistration","showRegistrationChoices","showToolList","reloadToolList","initiateRegistration","url","removeClass","container","append","encodeURIComponent","showExternalRegistration","window","addEventListener","getToolURL","val","addClass","hideCartridgeRegistration","hideRegistrationChoices","screenReaderAnnounce","showCartridgeRegistration","find","attr","element","children","detach","appendTo","hideToolList","showRegistrationFeedback","type","error","addNotification","message","startLoading","stopLoading","promise","Deferred","when","query","done","types","proxies","render","tools","html","js","runTemplateJS","resolve","fail","reject","exception","always","addLTIAdvTool","trim","addLTILegacyTool","toolButton","isCartridge","result","iscartridge","document","trigger","START_CARTRIDGE_REGISTRATION","START_EXTERNAL_REGISTRATION","get_string","s","REGISTRATION_FEEDBACK","registerEventListeners","on","NEW_TOOL_TYPE","STOP_EXTERNAL_REGISTRATION","event","STOP_CARTRIDGE_REGISTRATION","removeAttr","addLegacyButton","click","preventDefault","addLTIButton","init"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Standard Ajax wrapper for Moodle. It calls the central Ajax script,\n * which can call any existing webservice using the current session.\n * In addition, it can batch multiple requests and return multiple responses.\n *\n * @module mod_lti/tool_configure_controller\n * @class tool_configure_controller\n * @package mod_lti\n * @copyright 2015 Ryan Wyllie \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'],\n function($, ajax, notification, templates, ltiEvents, KEYS, toolType, toolProxy, str) {\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 +{"version":3,"sources":["../src/tool_configure_controller.js"],"names":["define","$","ajax","notification","templates","ltiEvents","KEYS","toolType","toolProxy","str","config","SELECTORS","EXTERNAL_REGISTRATION_CONTAINER","EXTERNAL_REGISTRATION_PAGE_CONTAINER","EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER","CARTRIDGE_REGISTRATION_CONTAINER","CARTRIDGE_REGISTRATION_FORM","ADD_TOOL_FORM","TOOL_LIST_CONTAINER","TOOL_CREATE_BUTTON","TOOL_CREATE_LTILEGACY_BUTTON","REGISTRATION_CHOICE_CONTAINER","TOOL_URL","getToolListContainer","getExternalRegistrationContainer","getCartridgeRegistrationContainer","getRegistrationChoiceContainer","closeLTIAdvRegistration","e","data","subject","empty","hideExternalRegistration","showRegistrationChoices","showToolList","reloadToolList","initiateRegistration","url","removeClass","container","append","encodeURIComponent","sesskey","showExternalRegistration","window","addEventListener","getToolURL","val","addClass","hideCartridgeRegistration","hideRegistrationChoices","screenReaderAnnounce","showCartridgeRegistration","find","attr","element","children","detach","appendTo","hideToolList","showRegistrationFeedback","type","error","addNotification","message","startLoading","stopLoading","promise","Deferred","when","query","done","types","proxies","render","tools","html","js","runTemplateJS","resolve","fail","reject","exception","always","addLTIAdvTool","trim","addLTILegacyTool","toolButton","isCartridge","result","iscartridge","document","trigger","START_CARTRIDGE_REGISTRATION","START_EXTERNAL_REGISTRATION","get_string","s","REGISTRATION_FEEDBACK","registerEventListeners","on","NEW_TOOL_TYPE","STOP_EXTERNAL_REGISTRATION","event","STOP_CARTRIDGE_REGISTRATION","removeAttr","addLegacyButton","click","preventDefault","addLTIButton","init"],"mappings":"AA2BAA,OAAM,qCAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,mBAAxB,CAA6C,gBAA7C,CAA+D,gBAA/D,CAAiF,cAAjF,CAAiG,mBAAjG,CACC,oBADD,CACuB,UADvB,CACmC,aADnC,CAAD,CAEE,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAAgCC,CAAhC,CAA2CC,CAA3C,CAAsDC,CAAtD,CAA4DC,CAA5D,CAAsEC,CAAtE,CAAiFC,CAAjF,CAAsFC,CAAtF,CAA8F,IAE9FC,CAAAA,CAAS,CAAG,CACZC,+BAA+B,CAAE,kCADrB,CAEZC,oCAAoC,CAAE,uCAF1B,CAGZC,wCAAwC,CAAE,2CAH9B,CAIZC,gCAAgC,CAAE,mCAJtB,CAKZC,2BAA2B,CAAE,8BALjB,CAMZC,aAAa,CAAE,gBANH,CAOZC,mBAAmB,CAAE,sBAPT,CAQZC,kBAAkB,CAAE,qBARR,CASZC,4BAA4B,CAAE,8BATlB,CAUZC,6BAA6B,CAAE,gCAVnB,CAWZC,QAAQ,CAAE,WAXE,CAFkF,CAuB9FC,CAAoB,CAAG,UAAW,CAClC,MAAOtB,CAAAA,CAAC,CAACU,CAAS,CAACO,mBAAX,CACX,CAzBiG,CAkC9FM,CAAgC,CAAG,UAAW,CAC9C,MAAOvB,CAAAA,CAAC,CAACU,CAAS,CAACC,+BAAX,CACX,CApCiG,CA6C9Fa,CAAiC,CAAG,UAAW,CAC/C,MAAOxB,CAAAA,CAAC,CAACU,CAAS,CAACI,gCAAX,CACX,CA/CiG,CAwD9FW,CAA8B,CAAG,UAAW,CAC5C,MAAOzB,CAAAA,CAAC,CAACU,CAAS,CAACU,6BAAX,CACX,CA1DiG,CAkE9FM,CAAuB,CAAG,SAASC,CAAT,CAAY,CACtC,GAAIA,CAAC,CAACC,IAAF,EAAU,4BAA8BD,CAAC,CAACC,IAAF,CAAOC,OAAnD,CAA4D,CACxD7B,CAAC,CAACU,CAAS,CAACG,wCAAX,CAAD,CAAsDiB,KAAtD,GACAC,CAAwB,GACxBC,CAAuB,GACvBC,CAAY,GACZD,CAAuB,GACvBE,CAAc,EACjB,CACJ,CA3EiG,CAoF9FC,CAAoB,CAAG,SAASC,CAAT,CAAc,CAErCpC,CAAC,CAACU,CAAS,CAACE,oCAAX,CAAD,CAAkDyB,WAAlD,CAA8D,QAA9D,EACA,GAAIC,CAAAA,CAAS,CAAGtC,CAAC,CAACU,CAAS,CAACG,wCAAX,CAAjB,CACAyB,CAAS,CAACC,MAAV,CAAiBvC,CAAC,CAAC,gDACAwC,kBAAkB,CAACJ,CAAD,CADlB,CAC0B,WAD1B,CACwC3B,CAAM,CAACgC,OAD/C,CACyD,aAD1D,CAAlB,EAEAC,CAAwB,GACxBC,MAAM,CAACC,gBAAP,CAAwB,SAAxB,CAAmClB,CAAnC,IACH,CA5FiG,CAqG9FmB,CAAU,CAAG,UAAW,CACxB,MAAO7C,CAAAA,CAAC,CAACU,CAAS,CAACW,QAAX,CAAD,CAAsByB,GAAtB,EACV,CAvGiG,CA+G9Ff,CAAwB,CAAG,UAAW,CACtCR,CAAgC,GAAGwB,QAAnC,CAA4C,QAA5C,CACH,CAjHiG,CAyH9FC,CAAyB,CAAG,UAAW,CACvCxB,CAAiC,GAAGuB,QAApC,CAA6C,QAA7C,CACH,CA3HiG,CAmI9FE,CAAuB,CAAG,UAAW,CACrCxB,CAA8B,GAAGsB,QAAjC,CAA0C,QAA1C,CACH,CArIiG,CA8I9FL,CAAwB,CAAG,UAAW,CACtCM,CAAyB,GACzBC,CAAuB,GACvB1B,CAAgC,GAAGc,WAAnC,CAA+C,QAA/C,EACAa,CAAoB,CAAC3B,CAAgC,EAAjC,CACvB,CAnJiG,CA6J9F4B,CAAyB,CAAG,SAASf,CAAT,CAAc,CAC1CL,CAAwB,GACxBkB,CAAuB,GAEvB,GAAIX,CAAAA,CAAS,CAAGd,CAAiC,EAAjD,CACAc,CAAS,CAACc,IAAV,CAAe,OAAf,EAAwBN,GAAxB,CAA4B,EAA5B,EACAR,CAAS,CAACD,WAAV,CAAsB,QAAtB,EACAC,CAAS,CAACc,IAAV,CAAe1C,CAAS,CAACK,2BAAzB,EAAsDsC,IAAtD,CAA2D,oBAA3D,CAAiFjB,CAAjF,EACAc,CAAoB,CAACZ,CAAD,CACvB,CAtKiG,CA+K9FN,CAAuB,CAAG,UAAW,CACrCD,CAAwB,GACxBiB,CAAyB,GACzBvB,CAA8B,GAAGY,WAAjC,CAA6C,QAA7C,EACAa,CAAoB,CAACzB,CAA8B,EAA/B,CACvB,CApLiG,CA+L9FyB,CAAoB,CAAG,SAASI,CAAT,CAAkB,CACzC,GAAIC,CAAAA,CAAQ,CAAGD,CAAO,CAACC,QAAR,GAAmBC,MAAnB,EAAf,CACAD,CAAQ,CAACE,QAAT,CAAkBH,CAAlB,CACH,CAlMiG,CA0M9FI,CAAY,CAAG,UAAW,CAC1BpC,CAAoB,GAAGyB,QAAvB,CAAgC,QAAhC,CACH,CA5MiG,CAoN9Fd,CAAY,CAAG,UAAW,CAC1BX,CAAoB,GAAGe,WAAvB,CAAmC,QAAnC,CACH,CAtNiG,CA+N9FsB,CAAwB,CAAG,SAAS/B,CAAT,CAAe,CAC1C,GAAIgC,CAAAA,CAAI,CAAGhC,CAAI,CAACiC,KAAL,CAAa,OAAb,CAAuB,SAAlC,CACA3D,CAAY,CAAC4D,eAAb,CAA6B,CACzBC,OAAO,CAAEnC,CAAI,CAACmC,OADW,CAEzBH,IAAI,CAAEA,CAFmB,CAA7B,CAIH,CArOiG,CA8O9FI,CAAY,CAAG,SAASV,CAAT,CAAkB,CACjCA,CAAO,CAACP,QAAR,CAAiB,SAAjB,CACH,CAhPiG,CAyP9FkB,CAAW,CAAG,SAASX,CAAT,CAAkB,CAChCA,CAAO,CAACjB,WAAR,CAAoB,SAApB,CACH,CA3PiG,CAmQ9FH,CAAc,CAAG,UAAW,IACxBgC,CAAAA,CAAO,CAAGlE,CAAC,CAACmE,QAAF,EADc,CAExB7B,CAAS,CAAGhB,CAAoB,EAFR,CAG5B0C,CAAY,CAAC1B,CAAD,CAAZ,CAEAtC,CAAC,CAACoE,IAAF,CACQ9D,CAAQ,CAAC+D,KAAT,EADR,CAEQ9D,CAAS,CAAC8D,KAAV,CAAgB,CAAC,eAAD,CAAhB,CAFR,EAIKC,IAJL,CAIU,SAASC,CAAT,CAAgBC,CAAhB,CAAyB,CACvBrE,CAAS,CAACsE,MAAV,CAAiB,mBAAjB,CAAsC,CAACC,KAAK,CAAEH,CAAR,CAAeC,OAAO,CAAEA,CAAxB,CAAtC,EACKF,IADL,CACU,SAASK,CAAT,CAAeC,CAAf,CAAmB,CACjBtC,CAAS,CAACR,KAAV,GACAQ,CAAS,CAACC,MAAV,CAAiBoC,CAAjB,EACAxE,CAAS,CAAC0E,aAAV,CAAwBD,CAAxB,EACAV,CAAO,CAACY,OAAR,EACH,CANT,EAMWC,IANX,CAMgBb,CAAO,CAACc,MANxB,CAOH,CAZT,EAaKD,IAbL,CAaUb,CAAO,CAACc,MAblB,EAeAd,CAAO,CAACa,IAAR,CAAa7E,CAAY,CAAC+E,SAA1B,EACKC,MADL,CACY,UAAW,CACXjB,CAAW,CAAC3B,CAAD,CACd,CAHT,CAIH,CA3RiG,CAmS9F6C,CAAa,CAAG,UAAW,CAC3B,GAAI/C,CAAAA,CAAG,CAAGS,CAAU,GAAGuC,IAAb,EAAV,CAEA,GAAIhD,CAAJ,CAAS,CACLpC,CAAC,CAACU,CAAS,CAACW,QAAX,CAAD,CAAsByB,GAAtB,CAA0B,EAA1B,EACAY,CAAY,GACZvB,CAAoB,CAACC,CAAD,CACvB,CAEJ,CA5SiG,CAsT9FiD,CAAgB,CAAG,UAAW,CAC9B,GAAIjD,CAAAA,CAAG,CAAGS,CAAU,GAAGuC,IAAb,EAAV,CAEA,GAAY,EAAR,GAAAhD,CAAJ,CAAgB,CACZ,MAAOpC,CAAAA,CAAC,CAACmE,QAAF,GAAaW,OAAb,EACV,CACD,GAAIQ,CAAAA,CAAU,CAAGtF,CAAC,CAACU,CAAS,CAACS,4BAAX,CAAlB,CACA6C,CAAY,CAACsB,CAAD,CAAZ,CAEA,GAAIpB,CAAAA,CAAO,CAAG5D,CAAQ,CAACiF,WAAT,CAAqBnD,CAArB,CAAd,CAEA8B,CAAO,CAACgB,MAAR,CAAe,UAAW,CACxBjB,CAAW,CAACqB,CAAD,CACZ,CAFD,EAIApB,CAAO,CAACI,IAAR,CAAa,SAASkB,CAAT,CAAiB,CAC1B,GAAIA,CAAM,CAACC,WAAX,CAAwB,CACpBzF,CAAC,CAACU,CAAS,CAACW,QAAX,CAAD,CAAsByB,GAAtB,CAA0B,EAA1B,EACA9C,CAAC,CAAC0F,QAAD,CAAD,CAAYC,OAAZ,CAAoBvF,CAAS,CAACwF,4BAA9B,CAA4DxD,CAA5D,CACH,CAHD,IAGO,CACHpC,CAAC,CAAC0F,QAAD,CAAD,CAAYC,OAAZ,CAAoBvF,CAAS,CAACyF,2BAA9B,CAA2D,CAACzD,GAAG,CAAEA,CAAN,CAA3D,CACH,CACJ,CAPD,EASA8B,CAAO,CAACa,IAAR,CAAa,UAAW,CACpBvE,CAAG,CAACsF,UAAJ,CAAe,aAAf,CAA8B,SAA9B,EACKxB,IADL,CACU,SAASyB,CAAT,CAAY,CACV/F,CAAC,CAAC0F,QAAD,CAAD,CAAYC,OAAZ,CAAoBvF,CAAS,CAAC4F,qBAA9B,CAAqD,CAC7CjC,OAAO,CAAEgC,CADoC,CAE7ClC,KAAK,GAFwC,CAArD,CAIH,CANT,EAOKkB,IAPL,CAOU7E,CAAY,CAAC+E,SAPvB,CAQH,CATD,EAWA,MAAOf,CAAAA,CACV,CA1ViG,CAkW9F+B,CAAsB,CAAG,UAAW,CAIpCjG,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAAC+F,aAAzB,CAAwC,UAAW,CAC/CjE,CAAc,EACjB,CAFD,EAIAlC,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAACyF,2BAAzB,CAAsD,UAAW,CAC7DnD,CAAwB,GACxB1C,CAAC,CAACU,CAAS,CAACW,QAAX,CAAD,CAAsByB,GAAtB,CAA0B,EAA1B,EACAY,CAAY,EACf,CAJD,EAMA1D,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAACgG,0BAAzB,CAAqD,UAAW,CAC5DnE,CAAY,GACZD,CAAuB,EAC1B,CAHD,EAKAhC,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAACwF,4BAAzB,CAAuD,SAASS,CAAT,CAAgBjE,CAAhB,CAAqB,CACxEe,CAAyB,CAACf,CAAD,CAC5B,CAFD,EAIApC,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAACkG,2BAAzB,CAAsD,UAAW,CAC7D9E,CAAiC,GAAG4B,IAApC,CAAyC1C,CAAS,CAACK,2BAAnD,EAAgFwF,UAAhF,CAA2F,oBAA3F,EACAvE,CAAuB,EAC1B,CAHD,EAKAhC,CAAC,CAAC0F,QAAD,CAAD,CAAYQ,EAAZ,CAAe9F,CAAS,CAAC4F,qBAAzB,CAAgD,SAASK,CAAT,CAAgBzE,CAAhB,CAAsB,CAClE+B,CAAwB,CAAC/B,CAAD,CAC3B,CAFD,EAIA,GAAI4E,CAAAA,CAAe,CAAGxG,CAAC,CAACU,CAAS,CAACS,4BAAX,CAAvB,CACAqF,CAAe,CAACC,KAAhB,CAAsB,SAAS9E,CAAT,CAAY,CAC9BA,CAAC,CAAC+E,cAAF,GACArB,CAAgB,EACnB,CAHD,EAKA,GAAIsB,CAAAA,CAAY,CAAG3G,CAAC,CAACU,CAAS,CAACQ,kBAAX,CAApB,CACAyF,CAAY,CAACF,KAAb,CAAmB,SAAS9E,CAAT,CAAY,CAC3BA,CAAC,CAAC+E,cAAF,GACAvB,CAAa,EAChB,CAHD,CAKH,CA9YiG,CAgZlG,MAAgE,CAK5DyB,IAAI,CAAE,eAAW,CACbX,CAAsB,GACtB/D,CAAc,EACjB,CAR2D,CAUnE,CA5ZK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Standard Ajax wrapper for Moodle. It calls the central Ajax script,\n * which can call any existing webservice using the current session.\n * In addition, it can batch multiple requests and return multiple responses.\n *\n * @module mod_lti/tool_configure_controller\n * @class tool_configure_controller\n * @package mod_lti\n * @copyright 2015 Ryan Wyllie \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}}

+ + + + + + + + +{{#tools}} + + + + + + +{{/tools}} +
{{#str}}dynreg_update_name, mod_lti {{/str}}{{#str}}dynreg_update_url, mod_lti {{/str}}{{#str}}dynreg_update_version, mod_lti {{/str}}
{{name}}{{baseurl}}{{ltiversion}} +
+{{^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'])); + } }