return toolTypeData;
}).then(function(toolTypeData) {
return templates.render('mod_lti/tool_card', toolTypeData);
- }).then(function(renderResult) {
- var html = renderResult[0];
- var js = renderResult[1];
-
+ }).then(function(html, js) {
templates.replaceNode(element, html, js);
return;
}).catch(function() {
var SELECTORS = {
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'
};
- /**
- * Get the tool create button element.
- *
- * @method getToolCreateButton
- * @private
- * @return {Object} jQuery object
- */
- var getToolCreateButton = function() {
- return $(SELECTORS.TOOL_CREATE_BUTTON);
- };
-
/**
* Get the tool list container element.
*
return $(SELECTORS.REGISTRATION_CHOICE_CONTAINER);
};
+ /**
+ * Close the LTI Advantage Registration IFrame.
+ *
+ * @private
+ * @param {Object} e post message event sent from the registration frame.
+ */
+ var closeLTIAdvRegistration = function(e) {
+ if (e.data && 'org.imsglobal.lti.close' === e.data.subject) {
+ $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER).empty();
+ hideExternalRegistration();
+ showRegistrationChoices();
+ showToolList();
+ showRegistrationChoices();
+ reloadToolList();
+ }
+ };
+
+ /**
+ * Load the external registration template and render it in the DOM and display it.
+ *
+ * @method initiateRegistration
+ * @private
+ * @param {String} url where to send the registration request
+ */
+ var initiateRegistration = function(url) {
+ // Show the external registration page in an iframe.
+ $(SELECTORS.EXTERNAL_REGISTRATION_PAGE_CONTAINER).removeClass('hidden');
+ var container = $(SELECTORS.EXTERNAL_REGISTRATION_TEMPLATE_CONTAINER);
+ container.append($("<iframe src='startltiadvregistration.php?url="
+ + encodeURIComponent(url) + "'></iframe>"));
+ showExternalRegistration();
+ window.addEventListener("message", closeLTIAdvRegistration, false);
+ };
+
/**
* Get the tool type URL.
*
});
};
+ /**
+ * Start the LTI Advantage registration.
+ *
+ * @method addLTIAdvTool
+ * @private
+ */
+ var addLTIAdvTool = function() {
+ var url = $.trim(getToolURL());
+
+ if (url) {
+ $(SELECTORS.TOOL_URL).val('');
+ hideToolList();
+ initiateRegistration(url);
+ }
+
+ };
+
/**
* Trigger appropriate registration process process for the user input
* URL. It can either be a cartridge or a registration url.
*
- * @method addTool
+ * @method addLTILegacyTool
* @private
* @return {Promise} jQuery Deferred object
*/
- var addTool = function() {
+ var addLTILegacyTool = function() {
var url = $.trim(getToolURL());
if (url === "") {
return $.Deferred().resolve();
}
-
- var toolButton = getToolCreateButton();
+ var toolButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
startLoading(toolButton);
var promise = toolType.isCartridge(url);
showRegistrationFeedback(data);
});
- var form = $(SELECTORS.ADD_TOOL_FORM);
- form.submit(function(e) {
+ var addLegacyButton = $(SELECTORS.TOOL_CREATE_LTILEGACY_BUTTON);
+ addLegacyButton.click(function(e) {
+ e.preventDefault();
+ addLTILegacyTool();
+ });
+
+ var addLTIButton = $(SELECTORS.TOOL_CREATE_BUTTON);
+ addLTIButton.click(function(e) {
e.preventDefault();
- addTool();
+ addLTIAdvTool();
});
};
* @copyright 2019 Stephen Vickers
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+use mod_lti\local\ltiopenid\jwks_helper;
define('NO_DEBUG_DISPLAY', true);
define('NO_MOODLE_COOKIES', true);
require_once(__DIR__ . '/../../config.php');
-$jwks = array('keys' => array());
-
-$privatekey = get_config('mod_lti', 'privatekey');
-$res = openssl_pkey_get_private($privatekey);
-$details = openssl_pkey_get_details($res);
-
-$jwk = array();
-$jwk['kty'] = 'RSA';
-$jwk['alg'] = 'RS256';
-$jwk['kid'] = get_config('mod_lti', 'kid');
-$jwk['e'] = rtrim(strtr(base64_encode($details['rsa']['e']), '+/', '-_'), '=');
-$jwk['n'] = rtrim(strtr(base64_encode($details['rsa']['n']), '+/', '-_'), '=');
-$jwk['use'] = 'sig';
-
-$jwks['keys'][] = $jwk;
-
@header('Content-Type: application/json; charset=utf-8');
-echo json_encode($jwks, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+echo json_encode(jwks_helper::get_jwks(), JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
--- /dev/null
+<?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/>.
+
+/**
+ * This files exposes functions for LTI 1.3 Key Management.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_lti\local\ltiopenid;
+
+/**
+ * This class exposes functions for LTI 1.3 Key Management.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class jwks_helper {
+
+ /**
+ * Returns the private key to use to sign outgoing JWT.
+ *
+ * @return array keys are kid and key in PEM format.
+ */
+ public static function get_private_key() {
+ $privatekey = get_config('mod_lti', 'privatekey');
+ $kid = get_config('mod_lti', 'kid');
+ return [
+ "key" => $privatekey,
+ "kid" => $kid
+ ];
+ }
+
+ /**
+ * Returns the JWK Key Set for this site.
+ * @return array keyset exposting the site public key.
+ */
+ public static function get_jwks() {
+ $jwks = array('keys' => array());
+
+ $privatekey = self::get_private_key();
+ $res = openssl_pkey_get_private($privatekey['key']);
+ $details = openssl_pkey_get_details($res);
+
+ $jwk = array();
+ $jwk['kty'] = 'RSA';
+ $jwk['alg'] = 'RS256';
+ $jwk['kid'] = $privatekey['kid'];
+ $jwk['e'] = rtrim(strtr(base64_encode($details['rsa']['e']), '+/', '-_'), '=');
+ $jwk['n'] = rtrim(strtr(base64_encode($details['rsa']['n']), '+/', '-_'), '=');
+ $jwk['use'] = 'sig';
+
+ $jwks['keys'][] = $jwk;
+ return $jwks;
+ }
+
+}
--- /dev/null
+<?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/>.
+
+/**
+ * This library exposes functions for LTI Dynamic Registration.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_lti\local\ltiopenid;
+
+/**
+ * Exception when transforming the registration to LTI config.
+ *
+ * Code is the HTTP Error code.
+ */
+class registration_exception extends \Exception {
+}
--- /dev/null
+<?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/>.
+
+/**
+ * A Helper for LTI Dynamic Registration.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_lti\local\ltiopenid;
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot . '/mod/lti/locallib.php');
+use Firebase\JWT\JWK;
+use Firebase\JWT\JWT;
+use stdClass;
+
+/**
+ * This class exposes functions for LTI Dynamic Registration.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class registration_helper {
+ /** score scope */
+ const SCOPE_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';
+ /** result scope */
+ const SCOPE_RESULT = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
+ /** lineitem read-only scope */
+ const SCOPE_LINEITEM_RO = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
+ /** lineitem full access scope */
+ const SCOPE_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
+ /** Names and Roles (membership) scope */
+ const SCOPE_NRPS = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
+ /** Tool Settings scope */
+ const SCOPE_TOOL_SETTING = 'https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting';
+
+
+ /**
+ * Function used to validate parameters.
+ *
+ * This function is needed because the payload contains nested
+ * objects, and optional_param() does not support arrays of arrays.
+ *
+ * @param array $payload that may contain the parameter key
+ * @param string $key the key of the value to be looked for in the payload
+ * @param bool $required if required, not finding a value will raise a registration_exception
+ *
+ * @return mixed
+ */
+ private static 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);
+ }
+ return null;
+ }
+ $parameter = $payload[$key];
+ // Cleans parameters to avoid XSS and other issues.
+ if (is_array($parameter)) {
+ return clean_param_array($parameter, PARAM_TEXT, true);
+ }
+ return clean_param($parameter, PARAM_TEXT);
+ }
+
+ /**
+ * Transforms an LTI 1.3 Registration to a Moodle LTI Config.
+ *
+ * @param array $registrationpayload the registration data received from the tool.
+ * @param string $clientid the clientid to be issued for that tool.
+ *
+ * @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);
+
+ $applicationtype = self::get_parameter($registrationpayload, 'application_type', false);
+ $logouri = self::get_parameter($registrationpayload, 'logo_uri', false);
+
+ $ltitoolconfiguration = self::get_parameter($registrationpayload,
+ 'https://purl.imsglobal.org/spec/lti-tool-configuration', true);
+
+ $domain = self::get_parameter($ltitoolconfiguration, 'domain', true);
+ $targetlinkuri = self::get_parameter($ltitoolconfiguration, 'target_link_uri', true);
+ $customparameters = self::get_parameter($ltitoolconfiguration, 'custom_parameters', false);
+ $scopes = explode(" ", self::get_parameter($registrationpayload, 'scope', false) ?? '');
+ $claims = self::get_parameter($ltitoolconfiguration, 'claims', false);
+ $messages = $ltitoolconfiguration['messages'] ?? [];
+ $description = self::get_parameter($ltitoolconfiguration, 'description', false);
+
+ // Validate response type.
+ // According to specification, for this scenario, id_token must be explicitly set.
+ if (!in_array('id_token', $responsetypes)) {
+ throw new registration_exception('invalid_response_types', 400);
+ }
+
+ // According to specification, this parameter needs to be an array.
+ if (!is_array($redirecturis)) {
+ throw new registration_exception('invalid_redirect_uris', 400);
+ }
+
+ // According to specification, for this scenario private_key_jwt must be explicitly set.
+ if ($tokenendpointauthmethod !== 'private_key_jwt') {
+ throw new registration_exception('invalid_token_endpoint_auth_method', 400);
+ }
+
+ if (!empty($applicationtype) && $applicationtype !== 'web') {
+ throw new registration_exception('invalid_application_type', 400);
+ }
+
+ $config = new stdClass();
+ $config->lti_clientid = $clientid;
+ $config->lti_toolurl = $targetlinkuri;
+ $config->lti_tooldomain = $domain;
+ $config->lti_typename = $clientname;
+ $config->lti_description = $description;
+ $config->lti_ltiversion = LTI_VERSION_1P3;
+ $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
+ $config->lti_icon = $logouri;
+ $config->lti_coursevisible = LTI_COURSEVISIBLE_PRECONFIGURED;
+ $config->lti_contentitem = 0;
+ // Sets Content Item.
+ if (!empty($messages)) {
+ $messagesresponse = [];
+ foreach ($messages as $value) {
+ if ($value['type'] === 'LtiDeepLinkingRequest') {
+ $config->lti_contentitem = 1;
+ $config->lti_toolurl_ContentItemSelectionRequest = $value['target_link_uri'] ?? '';
+ array_push($messagesresponse, $value);
+ }
+ }
+ }
+
+ $config->lti_keytype = 'JWK_KEYSET';
+ $config->lti_publickeyset = $jwksuri;
+ $config->lti_initiatelogin = $initiateloginuri;
+ $config->lti_redirectionuris = implode(PHP_EOL, $redirecturis);
+ $config->lti_customparameters = '';
+ // Sets custom parameters.
+ if (isset($customparameters)) {
+ $paramssarray = [];
+ foreach ($customparameters as $key => $value) {
+ array_push($paramssarray, $key . '=' . $value);
+ }
+ $config->lti_customparameters = implode(PHP_EOL, $paramssarray);
+ }
+ // Sets launch container.
+ $config->lti_launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
+
+ // Sets Service info based on scopes.
+ $config->lti_acceptgrades = LTI_SETTING_NEVER;
+ $config->ltiservice_gradesynchronization = 0;
+ $config->ltiservice_memberships = 0;
+ $config->ltiservice_toolsettings = 0;
+ if (isset($scopes)) {
+ // Sets Assignment and Grade Services info.
+
+ if (in_array(self::SCOPE_SCORE, $scopes)) {
+ $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
+ $config->ltiservice_gradesynchronization = 1;
+ }
+ if (in_array(self::SCOPE_RESULT, $scopes)) {
+ $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
+ $config->ltiservice_gradesynchronization = 1;
+ }
+ if (in_array(self::SCOPE_LINEITEM_RO, $scopes)) {
+ $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
+ $config->ltiservice_gradesynchronization = 1;
+ }
+ if (in_array(self::SCOPE_LINEITEM, $scopes)) {
+ $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
+ $config->ltiservice_gradesynchronization = 2;
+ }
+
+ // Sets Names and Role Provisioning info.
+ if (in_array(self::SCOPE_NRPS, $scopes)) {
+ $config->ltiservice_memberships = 1;
+ }
+
+ // Sets Tool Settings info.
+ if (in_array(self::SCOPE_TOOL_SETTING, $scopes)) {
+ $config->ltiservice_toolsettings = 1;
+ }
+ }
+
+ // Sets privacy settings.
+ $config->lti_sendname = LTI_SETTING_NEVER;
+ $config->lti_sendemailaddr = LTI_SETTING_NEVER;
+ if (isset($claims)) {
+ // Sets name privacy settings.
+
+ if (in_array('name', $claims)) {
+ $config->lti_sendname = LTI_SETTING_ALWAYS;
+ }
+ if (in_array('given_name', $claims)) {
+ $config->lti_sendname = LTI_SETTING_ALWAYS;
+ }
+ if (in_array('family_name', $claims)) {
+ $config->lti_sendname = LTI_SETTING_ALWAYS;
+ }
+
+ // Sets email privacy settings.
+ if (in_array('email', $claims)) {
+ $config->lti_sendemailaddr = LTI_SETTING_ALWAYS;
+ }
+ }
+ return $config;
+ }
+
+ /**
+ * 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.
+ *
+ * @return array the Client Registration as an associative array.
+ */
+ public static function config_to_registration(object $config, int $typeid): array {
+ $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 = [];
+ $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) {
+ $contentitemmessage = [];
+ $contentitemmessage['type'] = 'LtiDeepLinkingRequest';
+ if (isset($config->lti_toolurl_ContentItemSelectionRequest)) {
+ $contentitemmessage['target_link_uri'] = $config->lti_toolurl_ContentItemSelectionRequest;
+ }
+ $lticonfigurationresponse['messages'] = [$contentitemmessage];
+ }
+ if (isset($config->lti_customparameters) && !empty($config->lti_customparameters)) {
+ $params = [];
+ foreach (explode(PHP_EOL, $config->lti_customparameters) as $param) {
+ $split = explode('=', $param);
+ $params[$split[0]] = $split[1];
+ }
+ $lticonfigurationresponse['custom_parameters'] = $params;
+ }
+ $scopesresponse = [];
+ if ($config->ltiservice_gradesynchronization > 0) {
+ $scopesresponse[] = self::SCOPE_SCORE;
+ $scopesresponse[] = self::SCOPE_RESULT;
+ $scopesresponse[] = self::SCOPE_LINEITEM_RO;
+ }
+ if ($config->ltiservice_gradesynchronization == 2) {
+ $scopesresponse[] = self::SCOPE_LINEITEM;
+ }
+ if ($config->ltiservice_memberships == 1) {
+ $scopesresponse[] = self::SCOPE_NRPS;
+ }
+ if ($config->ltiservice_toolsettings == 1) {
+ $scopesresponse[] = self::SCOPE_TOOL_SETTING;
+ }
+ $registrationresponse['scope'] = implode(' ', $scopesresponse);
+
+ $claimsresponse = ['sub', 'iss'];
+ if ($config->lti_sendname = LTI_SETTING_ALWAYS) {
+ $claimsresponse[] = 'name';
+ $claimsresponse[] = 'family_name';
+ $claimsresponse[] = 'middle_name';
+ }
+ if ($config->lti_sendemailaddr = LTI_SETTING_ALWAYS) {
+ $claimsresponse[] = 'email';
+ }
+ $lticonfigurationresponse['claims'] = $claimsresponse;
+ $registrationresponse['https://purl.imsglobal.org/spec/lti-tool-configuration'] = $lticonfigurationresponse;
+ return $registrationresponse;
+ }
+
+ /**
+ * Validates the registration token is properly signed and not used yet.
+ * Return the client id to use for this registration.
+ *
+ * @param string $registrationtokenjwt registration token
+ *
+ * @return string client id for the registration
+ */
+ public static function validate_registration_token(string $registrationtokenjwt): string {
+ global $DB;
+ $keys = JWK::parseKeySet(jwks_helper::get_jwks());
+ $registrationtoken = JWT::decode($registrationtokenjwt, $keys, ['RS256']);
+
+ // 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);
+ }
+ return $clientid;
+ }
+
+ /**
+ * Initializes an array with the scopes for services supported by the LTI module
+ *
+ * @return array List of scopes
+ */
+ public static function lti_get_service_scopes() {
+
+ $services = lti_get_services();
+ $scopes = array();
+ foreach ($services as $service) {
+ $servicescopes = $service->get_scopes();
+ if (!empty($servicescopes)) {
+ $scopes = array_merge($scopes, $servicescopes);
+ }
+ }
+ return $scopes;
+ }
+
+}
abstract public function get_resources();
/**
- * Get the scope(s) permitted for this service.
+ * Get the scope(s) permitted for this service in the context of a particular tool type.
*
* A null value indicates that no scopes are required to access the service.
*
return null;
}
+ /**
+ * Get the scope(s) permitted for this service.
+ *
+ * A null value indicates that no scopes are required to access the service.
+ *
+ * @return array|null
+ */
+ public function get_scopes() {
+ return null;
+ }
+
/**
* Returns the configuration options for this service.
*
$string['activatetoadddescription'] = 'You will need to activate this tool before you can add a description.';
$string['active'] = 'Active';
$string['activity'] = 'Activity';
+$string['add_ltiadv'] = 'Add LTI Advantage';
+$string['add_ltilegacy'] = 'Add Legacy LTI';
$string['addnewapp'] = 'Enable external application';
$string['addserver'] = 'Add new trusted server';
$string['addtype'] = 'Add preconfigured tool';
use moodle\mod\lti as lti;
use Firebase\JWT\JWT;
use Firebase\JWT\JWK;
+use mod_lti\local\ltiopenid\jwks_helper;
global $CFG;
require_once($CFG->dirroot.'/mod/lti/OAuth.php');
function lti_prepare_type_for_save($type, $config) {
if (isset($config->lti_toolurl)) {
$type->baseurl = $config->lti_toolurl;
- $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl);
+ if (isset($config->lti_tooldomain)) {
+ $type->tooldomain = $config->lti_tooldomain;
+ } else {
+ $type->tooldomain = lti_get_domain_from_url($config->lti_toolurl);
+ }
}
if (isset($config->lti_description)) {
$type->description = $config->lti_description;
}
}
- $privatekey = get_config('mod_lti', 'privatekey');
- $kid = get_config('mod_lti', 'kid');
- $jwt = JWT::encode($payload, $privatekey, 'RS256', $kid);
+ $privatekey = jwks_helper::get_private_key();
+ $jwt = JWT::encode($payload, $privatekey['key'], 'RS256', $privatekey['kid']);
$newparms = array();
$newparms['id_token'] = $jwt;
/**
* Initializes an array with the scopes for services supported by the LTI module
+ * and authorized for this particular tool instance.
*
* @param object $type LTI tool type
* @param array $typeconfig LTI tool type configuration
}
return $scopes;
-
}
/**
return $newtoken;
}
+
--- /dev/null
+<?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/>.
+
+/**
+ * This file returns the OpenId/LTI Configuration for this site.
+ *
+ * It is part of the LTI Tool Dynamic Registration, and used by
+ * tools to get the site configuration and registration end-point.
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use mod_lti\local\ltiopenid\registration_helper;
+
+define('NO_DEBUG_DISPLAY', true);
+define('NO_MOODLE_COOKIES', true);
+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[] = 'openid';
+$conf = [
+ 'issuer' => $CFG->wwwroot,
+ 'token_endpoint' => (new moodle_url('/mod/lti/token.php'))->out(false),
+ 'token_endpoint_auth_methods_supported' => ['private_key_jwt'],
+ 'token_endpoint_auth_signing_alg_values_supported' => ['RS256'],
+ 'jwks_uri' => (new moodle_url('/mod/lti/certs.php'))->out(false),
+ 'registration_endpoint' => (new moodle_url('/mod/lti/openid-registration.php'))->out(false),
+ 'scopes_supported' => $scopes,
+ 'response_types_supported' => ['id_token'],
+ 'subject_types_supported' => ['public', 'pairwise'],
+ 'id_token_signing_alg_values_supported' => ['RS256'],
+ 'claims_supported' => ['sub', 'iss', 'name', 'given_name', 'family_name', 'email'],
+ 'https://purl.imsglobal.org/spec/lti-platform-configuration ' => [
+ 'product_family_code' => 'moodle',
+ 'version' => $CFG->release,
+ 'messages_supported' => ['LtiResourceLink', 'LtiDeepLinkingRequest'],
+ 'placements' => ['AddContentMenu'],
+ 'variables' => array_keys(lti_get_capabilities())
+ ]
+];
+
+@header('Content-Type: application/json; charset=utf-8');
+
+echo json_encode($conf, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
--- /dev/null
+<?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/>.
+
+/**
+ * This file receives a registration request along with the registration token and returns a client_id.
+ *
+ * @copyright 2020 Claude Vervoort (Cengage), Carlos Costa, Adrian Hutchinson (Macgraw Hill)
+ * @package mod_lti
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define('NO_DEBUG_DISPLAY', true);
+define('NO_MOODLE_COOKIES', true);
+
+use mod_lti\local\ltiopenid\registration_helper;
+use mod_lti\local\ltiopenid\registration_exception;
+
+require_once(__DIR__ . '/../../config.php');
+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);
+
+ // 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();
+ }
+}
+$response = new \mod_lti\local\ltiservice\response();
+// Set code.
+$response->set_code($code);
+// Set body.
+$response->set_body($message);
+$response->send();
}
+ /**
+ * Get the scope(s) permitted for the tool relevant to this service.
+ *
+ * @return array
+ */
+ public function get_scopes() {
+ return [self::SCOPE_BASIC_OUTCOMES];
+ }
+
}
}
+ /**
+ * Get the scopes defined by this service.
+ *
+ * @return array
+ */
+ public function get_scopes() {
+ return [self::SCOPE_GRADEBOOKSERVICES_LINEITEM_READ, self::SCOPE_GRADEBOOKSERVICES_RESULT_READ,
+ self::SCOPE_GRADEBOOKSERVICES_SCORE, self::SCOPE_GRADEBOOKSERVICES_LINEITEM];
+ }
+
/**
* Adds form elements for gradebook sync add/edit page.
*
}
+ /**
+ * Get the scope(s) defined by this service.
+ *
+ * @return array
+ */
+ public function get_scopes() {
+ return [self::SCOPE_MEMBERSHIPS_READ];
+ }
+
/**
* Get the JSON for members.
*
}
+ /**
+ * Get the scope(s) defined this service.
+ *
+ * @return array
+ */
+ public function get_scopes() {
+ return [self::SCOPE_TOOL_SETTINGS];
+ }
+
/**
* Get the distinct settings from each level by removing any duplicates from higher levels.
*
--- /dev/null
+<?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/>.
+
+/**
+ * Redirect the user to registration with token and openid config url as query params.
+ *
+ * @package mod_lti
+ * @copyright 2020 Cengage
+ * @author Claude Vervoort
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use Firebase\JWT\JWT;
+
+use mod_lti\local\ltiopenid\jwks_helper;
+
+require_once(__DIR__ . '/../../config.php');
+require_once($CFG->libdir.'/weblib.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));
<div class="controls">
<button id="cartridge-registration-submit" type="submit" class="btn btn-success">
<span class="btn-text">{{#str}} savechanges {{/str}}</span>
- <div class="btn-loader">
+ <span class="btn-loader">
{{> mod_lti/loader }}
- </div>
+ </span>
</button>
<button id="cartridge-registration-cancel" type="button" class="btn">
<span class="btn-text">{{#str}} cancel {{/str}}</span>
- <div class="btn-loader">
+ <span class="btn-loader">
{{> mod_lti/loader }}
- </div>
+ </span>
</button>
</div>
</div>
Context variables required for this template:
*
+ Example context (json):
+ {
+ }
+
}}
<div id="external-registration-page-container">
<button id="cancel-external-registration" class="btn btn-danger">
<span class="btn-text">{{#str}} cancel {{/str}}</span>
- <div class="btn-loader">
+ <span class="btn-loader">
{{> mod_lti/loader }}
- </div>
+ </span>
</button>
<div id="external-registration-template-container"></div>
</div>
}
}}
-<div class="loader">
+<span class="loader">
{{#pix}} i/loading, core, {{#str}} loadinghelp, moodle {{/str}} {{/pix}}
-</div>
+</span>
Context variables required for this template:
*
+ Example context (json):
+ {
+ "configuremanualurl":"https://some.tool.example/mod/lti/typessettings.php?sesskey=OKl37bHflL&returnto=toolconfigure",
+ "managetoolsurl":"https://some.tool.example/admin/settings.php?section=modsettinglti",
+ "managetoolproxiesurl":"https://some.tool.example/mod/lti/toolproxies.php"
+ }
+
}}
<h2>{{#str}} manage_external_tools, mod_lti {{/str}}</h2>
<div id="main-content-container">
placeholder="{{#str}} toolurlplaceholder, mod_lti {{/str}}"
required>
<button id="tool-create-button" type="submit" class="btn btn-success">
- <span class="btn-text">{{#str}} add {{/str}}</span>
- <div class="btn-loader">
+ <span class="btn-text">{{#str}} add_ltiadv, mod_lti {{/str}}</span>
+ <span class="btn-loader">
+ {{> mod_lti/loader }}
+ </span>
+ </button>
+ <button id="tool-createltilegacy-button" type="button" class="btn btn-warning">
+ <span class="btn-text">{{#str}} add_ltilegacy, mod_lti {{/str}}</span>
+ <span class="btn-loader">
{{> mod_lti/loader }}
- </div>
+ </span>
</button>
</div>
</form>
@javascript
Scenario: Add a tool type from a cartridge URL
When I set the field "url" to local url "/mod/lti/tests/fixtures/ims_cartridge_basic_lti_link.xml"
- And I press "Add"
+ And I press "Add Legacy LTI"
Then I should see "Enter your consumer key and shared secret"
And I press "Save changes"
And I should see "Example tool"
@javascript
Scenario: Try to add a non-existant cartridge
When I set the field "url" to local url "/mod/lti/tests/fixtures/nonexistant.xml"
- And I press "Add"
+ And I press "Add Legacy LTI"
Then I should see "Enter your consumer key and shared secret"
And I press "Save changes"
And I should see "Failed to create new tool. Please check the URL and try again."
@javascript
Scenario: Attempt to add a tool type from a configuration URL, then cancel
When I set the field "url" to local url "/mod/lti/tests/fixtures/tool_provider.php"
- And I press "Add"
+ And I press "Add Legacy LTI"
Then I should see "Cancel"
And I press "cancel-external-registration"
--- /dev/null
+<?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/>.
+//
+// This file is part of BasicLTI4Moodle
+//
+// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
+// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
+// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
+// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
+// are already supporting or going to support BasicLTI. This project Implements the consumer
+// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
+// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
+// at the GESSI research group at UPC.
+// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
+// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
+// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
+//
+// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
+// of the Universitat Politecnica de Catalunya http://www.upc.edu
+// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
+
+/**
+ * This file contains unit tests for lti/openidregistrationlib.php
+ *
+ * @package mod_lti
+ * @copyright 2020 Claude Vervoort, Cengage
+ * @author Claude Vervoort
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+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 {
+
+ /**
+ * @var string A has-it-all client registration.
+ */
+ private $registrationfulljson = <<<EOD
+ {
+ "application_type": "web",
+ "response_types": ["id_token"],
+ "grant_types": ["implict", "client_credentials"],
+ "initiate_login_uri": "https://client.example.org/lti/init",
+ "redirect_uris":
+ ["https://client.example.org/callback",
+ "https://client.example.org/callback2"],
+ "client_name": "Virtual Garden",
+ "client_name#ja": "バーチャルガーデン",
+ "jwks_uri": "https://client.example.org/.well-known/jwks.json",
+ "logo_uri": "https://client.example.org/logo.png",
+ "policy_uri": "https://client.example.org/privacy",
+ "policy_uri#ja": "https://client.example.org/privacy?lang=ja",
+ "tos_uri": "https://client.example.org/tos",
+ "tos_uri#ja": "https://client.example.org/tos?lang=ja",
+ "token_endpoint_auth_method": "private_key_jwt",
+ "contacts": ["ve7jtb@example.org", "mary@example.org"],
+ "scope": "https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
+ "https://purl.imsglobal.org/spec/lti-tool-configuration": {
+ "domain": "client.example.org",
+ "description": "Learn Botany by tending to your little (virtual) garden.",
+ "description#ja": "小さな(仮想)庭に行くことで植物学を学びましょう。",
+ "target_link_uri": "https://client.example.org/lti",
+ "custom_parameters": {
+ "context_history": "\$Context.id.history"
+ },
+ "claims": ["iss", "sub", "name", "given_name", "family_name", "email"],
+ "messages": [
+ {
+ "type": "LtiDeepLinkingRequest",
+ "target_link_uri": "https://client.example.org/lti/dl",
+ "label": "Add a virtual garden",
+ "label#ja": "バーチャルガーデンを追加する"
+ }
+ ]
+ }
+ }
+EOD;
+
+ /**
+ * @var string A minimalist client registration.
+ */
+ private $registrationminimaljson = <<<EOD
+ {
+ "application_type": "web",
+ "response_types": ["id_token"],
+ "grant_types": ["implict", "client_credentials"],
+ "initiate_login_uri": "https://client.example.org/lti/init",
+ "redirect_uris":
+ ["https://client.example.org/callback"],
+ "client_name": "Virtual Garden",
+ "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"
+ }
+ }
+EOD;
+
+ /**
+ * @var string A minimalist with deep linking client registration.
+ */
+ private $registrationminimaldljson = <<<EOD
+ {
+ "application_type": "web",
+ "response_types": ["id_token"],
+ "grant_types": ["implict", "client_credentials"],
+ "initiate_login_uri": "https://client.example.org/lti/init",
+ "redirect_uris":
+ ["https://client.example.org/callback"],
+ "client_name": "Virtual Garden",
+ "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",
+ "messages": [
+ {
+ "type": "LtiDeepLinkingRequest"
+ }
+ ]
+ }
+ }
+EOD;
+
+ /**
+ * Test the mapping from Registration JSON to LTI Config for a has-it-all tool registration.
+ */
+ 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');
+ $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->assertEquals('Learn Botany by tending to your little (virtual) garden.', $config->lti_description);
+ $this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin);
+ $this->assertEquals(implode(PHP_EOL, ["https://client.example.org/callback",
+ "https://client.example.org/callback2"]), $config->lti_redirectionuris);
+ $this->assertEquals("context_history=\$Context.id.history", $config->lti_customparameters);
+ $this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset);
+ $this->assertEquals("https://client.example.org/logo.png", $config->lti_icon);
+ $this->assertEquals(2, $config->ltiservice_gradesynchronization);
+ $this->assertEquals(LTI_SETTING_DELEGATE, $config->lti_acceptgrades);
+ $this->assertEquals(1, $config->ltiservice_memberships);
+ $this->assertEquals(0, $config->ltiservice_toolsettings);
+ $this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendname);
+ $this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendemailaddr);
+ $this->assertEquals(1, $config->lti_contentitem);
+ $this->assertEquals('https://client.example.org/lti/dl', $config->lti_toolurl_ContentItemSelectionRequest);
+ }
+
+ /**
+ * Test the mapping from Registration JSON to LTI Config for a minimal tool registration.
+ */
+ public function test_to_config_minimal() {
+ $registration = json_decode($this->registrationminimaljson, true);
+ $config = registration_helper::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);
+ $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);
+ $this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset);
+ $this->assertEmpty($config->lti_icon);
+ $this->assertEquals(0, $config->ltiservice_gradesynchronization);
+ $this->assertEquals(LTI_SETTING_NEVER, $config->lti_acceptgrades);
+ $this->assertEquals(0, $config->ltiservice_memberships);
+ $this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendname);
+ $this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendemailaddr);
+ $this->assertEquals(0, $config->lti_contentitem);
+ }
+
+ /**
+ * Test the mapping from Registration JSON to LTI Config for a minimal tool with
+ * deep linking support registration.
+ */
+ public function test_to_config_minimal_with_deeplinking() {
+ $registration = json_decode($this->registrationminimaldljson, true);
+ $config = registration_helper::registration_to_config($registration, 'TheClientId');
+ $this->assertEquals(1, $config->lti_contentitem);
+ $this->assertEmpty($config->lti_toolurl_ContentItemSelectionRequest);
+ }
+
+ /**
+ * Validation Test: initiation login.
+ */
+ public function test_validation_initlogin() {
+ $registration = json_decode($this->registrationfulljson, true);
+ $this->expectException(registration_exception::class);
+ $this->expectExceptionCode(400);
+ unset($registration['initiate_login_uri']);
+ registration_helper::registration_to_config($registration, 'TheClientId');
+ }
+
+ /**
+ * Validation Test: redirect uris.
+ */
+ public function test_validation_redirecturis() {
+ $registration = json_decode($this->registrationfulljson, true);
+ $this->expectException(registration_exception::class);
+ $this->expectExceptionCode(400);
+ unset($registration['redirect_uris']);
+ registration_helper::registration_to_config($registration, 'TheClientId');
+ }
+
+ /**
+ * Validation Test: jwks uri empty.
+ */
+ public function test_validation_jwks() {
+ $registration = json_decode($this->registrationfulljson, true);
+ $this->expectException(registration_exception::class);
+ $this->expectExceptionCode(400);
+ $registration['jwks_uri'] = '';
+ registration_helper::registration_to_config($registration, 'TheClientId');
+ }
+
+ /**
+ * Test the transformation from lti config to OpenId LTI Client Registration response.
+ */
+ 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);
+ $this->assertEquals('clid', $reg['client_id']);
+ $this->assertEquals($orig['response_types'], $reg['response_types']);
+ $this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
+ $this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']);
+ $this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']);
+ $this->assertEquals($orig['logo_uri'], $reg['logo_uri']);
+ $this->assertEquals('https://purl.imsglobal.org/spec/lti-ags/scope/score '.
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly '.
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly '.
+ 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem '.
+ 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly', $reg['scope']);
+ $ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration'];
+ $lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
+ $this->assertEquals("12", $lti['deployment_id']);
+ $this->assertEquals($ltiorig['target_link_uri'], $lti['target_link_uri']);
+ $this->assertEquals($ltiorig['domain'], $lti['domain']);
+ $this->assertEquals($ltiorig['custom_parameters'], $lti['custom_parameters']);
+ $this->assertEquals($ltiorig['description'], $lti['description']);
+ $dlmsgorig = $ltiorig['messages'][0];
+ $dlmsg = $lti['messages'][0];
+ $this->assertEquals($dlmsgorig['type'], $dlmsg['type']);
+ $this->assertEquals($dlmsgorig['target_link_uri'], $dlmsg['target_link_uri']);
+ }
+}