mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 13:38:32 +01:00
MDL-70500 lti: dyn reg can be used to update tools
This commit is contained in:
parent
d65ed58e73
commit
ca80d53b38
@ -54,18 +54,29 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
@ -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("<iframe src='startltiadvregistration.php?url="+encodeURIComponent(b)+"'></iframe>"));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("<iframe src='startltiadvregistration.php?url="+encodeURIComponent(b)+"&sesskey="+j.sesskey+"'></iframe>"));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
|
||||
|
File diff suppressed because one or more lines are too long
@ -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($("<iframe src='startltiadvregistration.php?url="
|
||||
+ encodeURIComponent(url) + "'></iframe>"));
|
||||
+ encodeURIComponent(url) + "&sesskey=" + config.sesskey + "'></iframe>"));
|
||||
showExternalRegistration();
|
||||
window.addEventListener("message", closeLTIAdvRegistration, false);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
69
mod/lti/classes/output/registration_upgrade_choice_page.php
Normal file
69
mod/lti/classes/output/registration_upgrade_choice_page.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
94
mod/lti/templates/registration_upgrade_choice_page.mustache
Normal file
94
mod/lti/templates/registration_upgrade_choice_page.mustache
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}}
|
||||
<p>{{#str}}dynreg_update_text, mod_lti {{/str}}</p>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{{#str}}dynreg_update_warn_dupdomain, mod_lti {{/str}}
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{{#str}}dynreg_update_name, mod_lti {{/str}}</th>
|
||||
<th>{{#str}}dynreg_update_url, mod_lti {{/str}}</th>
|
||||
<th>{{#str}}dynreg_update_version, mod_lti {{/str}}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{{#tools}}
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{baseurl}}</td>
|
||||
<td>{{ltiversion}}</td>
|
||||
<td><button data-href="startltiadvregistration.php?url={{startregurlenc}}&type={{id}}" class="btn btn-outline-primary">
|
||||
<span class="btn-loader">{{> mod_lti/loader }}</span>
|
||||
{{#str}}dynreg_update_btn_update, mod_lti {{/str}}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{{/tools}}
|
||||
</table>
|
||||
{{^tools}}
|
||||
{{#str}}dynreg_update_notools, mod_lti {{/str}}
|
||||
{{/tools}}
|
||||
<div>
|
||||
<button data-href="startltiadvregistration.php?url={{startregurlenc}}&type=0" class="btn btn-primary">
|
||||
<span class="btn-loader">{{> mod_lti/loader }}</span>
|
||||
{{#str}}dynreg_update_btn_new, mod_lti {{/str}}
|
||||
</button>
|
||||
</div>
|
||||
<form method="POST" id="startregform">
|
||||
<input type="hidden" name="sesskey" value="{{sesskey}}"/>
|
||||
</form>
|
||||
{{#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}}
|
@ -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']));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user