/mod/lti/locallib.php
PHP | 4509 lines | 3206 code | 452 blank | 851 comment | 553 complexity | b5dd4e80c946c8368343f04546ecfed3 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
Large files files are truncated, but you can click here to view the full file
- <?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 the library of functions and constants for the lti module
- *
- * @package mod_lti
- * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
- * marc.alier@upc.edu
- * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
- * @author Marc Alier
- * @author Jordi Piguillem
- * @author Nikolas Galanis
- * @author Chris Scribner
- * @copyright 2015 Vital Source Technologies http://vitalsource.com
- * @author Stephen Vickers
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die;
- // TODO: Switch to core oauthlib once implemented - MDL-30149.
- 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');
- require_once($CFG->libdir.'/weblib.php');
- require_once($CFG->dirroot . '/course/modlib.php');
- require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
- define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');
- define('LTI_LAUNCH_CONTAINER_DEFAULT', 1);
- define('LTI_LAUNCH_CONTAINER_EMBED', 2);
- define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3);
- define('LTI_LAUNCH_CONTAINER_WINDOW', 4);
- define('LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW', 5);
- define('LTI_TOOL_STATE_ANY', 0);
- define('LTI_TOOL_STATE_CONFIGURED', 1);
- define('LTI_TOOL_STATE_PENDING', 2);
- define('LTI_TOOL_STATE_REJECTED', 3);
- define('LTI_TOOL_PROXY_TAB', 4);
- define('LTI_TOOL_PROXY_STATE_CONFIGURED', 1);
- define('LTI_TOOL_PROXY_STATE_PENDING', 2);
- define('LTI_TOOL_PROXY_STATE_ACCEPTED', 3);
- define('LTI_TOOL_PROXY_STATE_REJECTED', 4);
- define('LTI_SETTING_NEVER', 0);
- define('LTI_SETTING_ALWAYS', 1);
- define('LTI_SETTING_DELEGATE', 2);
- define('LTI_COURSEVISIBLE_NO', 0);
- define('LTI_COURSEVISIBLE_PRECONFIGURED', 1);
- define('LTI_COURSEVISIBLE_ACTIVITYCHOOSER', 2);
- define('LTI_VERSION_1', 'LTI-1p0');
- define('LTI_VERSION_2', 'LTI-2p0');
- define('LTI_VERSION_1P3', '1.3.0');
- define('LTI_RSA_KEY', 'RSA_KEY');
- define('LTI_JWK_KEYSET', 'JWK_KEYSET');
- define('LTI_DEFAULT_ORGID_SITEID', 'SITEID');
- define('LTI_DEFAULT_ORGID_SITEHOST', 'SITEHOST');
- define('LTI_ACCESS_TOKEN_LIFE', 3600);
- // Standard prefix for JWT claims.
- define('LTI_JWT_CLAIM_PREFIX', 'https://purl.imsglobal.org/spec/lti');
- /**
- * Return the mapping for standard message types to JWT message_type claim.
- *
- * @return array
- */
- function lti_get_jwt_message_type_mapping() {
- return array(
- 'basic-lti-launch-request' => 'LtiResourceLinkRequest',
- 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
- 'LtiDeepLinkingResponse' => 'ContentItemSelection',
- );
- }
- /**
- * Return the mapping for standard message parameters to JWT claim.
- *
- * @return array
- */
- function lti_get_jwt_claim_mapping() {
- return array(
- 'accept_copy_advice' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_copy_advice',
- 'isarray' => false,
- 'type' => 'boolean'
- ],
- 'accept_media_types' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_media_types',
- 'isarray' => true
- ],
- 'accept_multiple' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_multiple',
- 'isarray' => false,
- 'type' => 'boolean'
- ],
- 'accept_presentation_document_targets' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_presentation_document_targets',
- 'isarray' => true
- ],
- 'accept_types' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_types',
- 'isarray' => true
- ],
- 'accept_unsigned' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'accept_unsigned',
- 'isarray' => false,
- 'type' => 'boolean'
- ],
- 'auto_create' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'auto_create',
- 'isarray' => false,
- 'type' => 'boolean'
- ],
- 'can_confirm' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'can_confirm',
- 'isarray' => false,
- 'type' => 'boolean'
- ],
- 'content_item_return_url' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'deep_link_return_url',
- 'isarray' => false
- ],
- 'content_items' => [
- 'suffix' => 'dl',
- 'group' => '',
- 'claim' => 'content_items',
- 'isarray' => true
- ],
- 'data' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'data',
- 'isarray' => false
- ],
- 'text' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'text',
- 'isarray' => false
- ],
- 'title' => [
- 'suffix' => 'dl',
- 'group' => 'deep_linking_settings',
- 'claim' => 'title',
- 'isarray' => false
- ],
- 'lti_msg' => [
- 'suffix' => 'dl',
- 'group' => '',
- 'claim' => 'msg',
- 'isarray' => false
- ],
- 'lti_log' => [
- 'suffix' => 'dl',
- 'group' => '',
- 'claim' => 'log',
- 'isarray' => false
- ],
- 'lti_errormsg' => [
- 'suffix' => 'dl',
- 'group' => '',
- 'claim' => 'errormsg',
- 'isarray' => false
- ],
- 'lti_errorlog' => [
- 'suffix' => 'dl',
- 'group' => '',
- 'claim' => 'errorlog',
- 'isarray' => false
- ],
- 'context_id' => [
- 'suffix' => '',
- 'group' => 'context',
- 'claim' => 'id',
- 'isarray' => false
- ],
- 'context_label' => [
- 'suffix' => '',
- 'group' => 'context',
- 'claim' => 'label',
- 'isarray' => false
- ],
- 'context_title' => [
- 'suffix' => '',
- 'group' => 'context',
- 'claim' => 'title',
- 'isarray' => false
- ],
- 'context_type' => [
- 'suffix' => '',
- 'group' => 'context',
- 'claim' => 'type',
- 'isarray' => true
- ],
- 'lis_course_offering_sourcedid' => [
- 'suffix' => '',
- 'group' => 'lis',
- 'claim' => 'course_offering_sourcedid',
- 'isarray' => false
- ],
- 'lis_course_section_sourcedid' => [
- 'suffix' => '',
- 'group' => 'lis',
- 'claim' => 'course_section_sourcedid',
- 'isarray' => false
- ],
- 'launch_presentation_css_url' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'css_url',
- 'isarray' => false
- ],
- 'launch_presentation_document_target' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'document_target',
- 'isarray' => false
- ],
- 'launch_presentation_height' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'height',
- 'isarray' => false
- ],
- 'launch_presentation_locale' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'locale',
- 'isarray' => false
- ],
- 'launch_presentation_return_url' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'return_url',
- 'isarray' => false
- ],
- 'launch_presentation_width' => [
- 'suffix' => '',
- 'group' => 'launch_presentation',
- 'claim' => 'width',
- 'isarray' => false
- ],
- 'lis_person_contact_email_primary' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'email',
- 'isarray' => false
- ],
- 'lis_person_name_family' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'family_name',
- 'isarray' => false
- ],
- 'lis_person_name_full' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'name',
- 'isarray' => false
- ],
- 'lis_person_name_given' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'given_name',
- 'isarray' => false
- ],
- 'lis_person_sourcedid' => [
- 'suffix' => '',
- 'group' => 'lis',
- 'claim' => 'person_sourcedid',
- 'isarray' => false
- ],
- 'user_id' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'sub',
- 'isarray' => false
- ],
- 'user_image' => [
- 'suffix' => '',
- 'group' => null,
- 'claim' => 'picture',
- 'isarray' => false
- ],
- 'roles' => [
- 'suffix' => '',
- 'group' => '',
- 'claim' => 'roles',
- 'isarray' => true
- ],
- 'role_scope_mentor' => [
- 'suffix' => '',
- 'group' => '',
- 'claim' => 'role_scope_mentor',
- 'isarray' => false
- ],
- 'deployment_id' => [
- 'suffix' => '',
- 'group' => '',
- 'claim' => 'deployment_id',
- 'isarray' => false
- ],
- 'lti_message_type' => [
- 'suffix' => '',
- 'group' => '',
- 'claim' => 'message_type',
- 'isarray' => false
- ],
- 'lti_version' => [
- 'suffix' => '',
- 'group' => '',
- 'claim' => 'version',
- 'isarray' => false
- ],
- 'resource_link_description' => [
- 'suffix' => '',
- 'group' => 'resource_link',
- 'claim' => 'description',
- 'isarray' => false
- ],
- 'resource_link_id' => [
- 'suffix' => '',
- 'group' => 'resource_link',
- 'claim' => 'id',
- 'isarray' => false
- ],
- 'resource_link_title' => [
- 'suffix' => '',
- 'group' => 'resource_link',
- 'claim' => 'title',
- 'isarray' => false
- ],
- 'tool_consumer_info_product_family_code' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'product_family_code',
- 'isarray' => false
- ],
- 'tool_consumer_info_version' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'version',
- 'isarray' => false
- ],
- 'tool_consumer_instance_contact_email' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'contact_email',
- 'isarray' => false
- ],
- 'tool_consumer_instance_description' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'description',
- 'isarray' => false
- ],
- 'tool_consumer_instance_guid' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'guid',
- 'isarray' => false
- ],
- 'tool_consumer_instance_name' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'name',
- 'isarray' => false
- ],
- 'tool_consumer_instance_url' => [
- 'suffix' => '',
- 'group' => 'tool_platform',
- 'claim' => 'url',
- 'isarray' => false
- ],
- 'custom_context_memberships_url' => [
- 'suffix' => 'nrps',
- 'group' => 'namesroleservice',
- 'claim' => 'context_memberships_url',
- 'isarray' => false
- ],
- 'custom_context_memberships_versions' => [
- 'suffix' => 'nrps',
- 'group' => 'namesroleservice',
- 'claim' => 'service_versions',
- 'isarray' => true
- ],
- 'custom_gradebookservices_scope' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'scope',
- 'isarray' => true
- ],
- 'custom_lineitems_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'lineitems',
- 'isarray' => false
- ],
- 'custom_lineitem_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'lineitem',
- 'isarray' => false
- ],
- 'custom_results_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'results',
- 'isarray' => false
- ],
- 'custom_result_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'result',
- 'isarray' => false
- ],
- 'custom_scores_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'scores',
- 'isarray' => false
- ],
- 'custom_score_url' => [
- 'suffix' => 'ags',
- 'group' => 'endpoint',
- 'claim' => 'score',
- 'isarray' => false
- ],
- 'lis_outcome_service_url' => [
- 'suffix' => 'bo',
- 'group' => 'basicoutcome',
- 'claim' => 'lis_outcome_service_url',
- 'isarray' => false
- ],
- 'lis_result_sourcedid' => [
- 'suffix' => 'bo',
- 'group' => 'basicoutcome',
- 'claim' => 'lis_result_sourcedid',
- 'isarray' => false
- ],
- );
- }
- /**
- * Return the type of the instance, using domain matching if no explicit type is set.
- *
- * @param object $instance the external tool activity settings
- * @return object|null
- * @since Moodle 3.9
- */
- function lti_get_instance_type(object $instance) : ?object {
- if (empty($instance->typeid)) {
- if (!$tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course)) {
- $tool = lti_get_tool_by_url_match($instance->securetoolurl, $instance->course);
- }
- return $tool;
- }
- return lti_get_type($instance->typeid);
- }
- /**
- * Return the launch data required for opening the external tool.
- *
- * @param stdClass $instance the external tool activity settings
- * @param string $nonce the nonce value to use (applies to LTI 1.3 only)
- * @return array the endpoint URL and parameters (including the signature)
- * @since Moodle 3.0
- */
- function lti_get_launch_data($instance, $nonce = '') {
- global $PAGE, $CFG, $USER;
- $tool = lti_get_instance_type($instance);
- if ($tool) {
- $typeid = $tool->id;
- $ltiversion = $tool->ltiversion;
- } else {
- $typeid = null;
- $ltiversion = LTI_VERSION_1;
- }
- if ($typeid) {
- $typeconfig = lti_get_type_config($typeid);
- } else {
- // There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults.
- $typeconfig = (array)$instance;
- $typeconfig['sendname'] = $instance->instructorchoicesendname;
- $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr;
- $typeconfig['customparameters'] = $instance->instructorcustomparameters;
- $typeconfig['acceptgrades'] = $instance->instructorchoiceacceptgrades;
- $typeconfig['allowroster'] = $instance->instructorchoiceallowroster;
- $typeconfig['forcessl'] = '0';
- }
- if (isset($tool->toolproxyid)) {
- $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
- $key = $toolproxy->guid;
- $secret = $toolproxy->secret;
- } else {
- $toolproxy = null;
- if (!empty($instance->resourcekey)) {
- $key = $instance->resourcekey;
- } else if ($ltiversion === LTI_VERSION_1P3) {
- $key = $tool->clientid;
- } else if (!empty($typeconfig['resourcekey'])) {
- $key = $typeconfig['resourcekey'];
- } else {
- $key = '';
- }
- if (!empty($instance->password)) {
- $secret = $instance->password;
- } else if (!empty($typeconfig['password'])) {
- $secret = $typeconfig['password'];
- } else {
- $secret = '';
- }
- }
- $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl'];
- $endpoint = trim($endpoint);
- // If the current request is using SSL and a secure tool URL is specified, use it.
- if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) {
- $endpoint = trim($instance->securetoolurl);
- }
- // If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL.
- if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
- if (!empty($instance->securetoolurl)) {
- $endpoint = trim($instance->securetoolurl);
- }
- $endpoint = lti_ensure_url_is_https($endpoint);
- } else {
- if (!strstr($endpoint, '://')) {
- $endpoint = 'http://' . $endpoint;
- }
- }
- $orgid = lti_get_organizationid($typeconfig);
- $course = $PAGE->course;
- $islti2 = isset($tool->toolproxyid);
- $allparams = lti_build_request($instance, $typeconfig, $course, $typeid, $islti2);
- if ($islti2) {
- $requestparams = lti_build_request_lti2($tool, $allparams);
- } else {
- $requestparams = $allparams;
- }
- $requestparams = array_merge($requestparams, lti_build_standard_message($instance, $orgid, $ltiversion));
- $customstr = '';
- if (isset($typeconfig['customparameters'])) {
- $customstr = $typeconfig['customparameters'];
- }
- $requestparams = array_merge($requestparams, lti_build_custom_parameters($toolproxy, $tool, $instance, $allparams, $customstr,
- $instance->instructorcustomparameters, $islti2));
- $launchcontainer = lti_get_launch_container($instance, $typeconfig);
- $returnurlparams = array('course' => $course->id,
- 'launch_container' => $launchcontainer,
- 'instanceid' => $instance->id,
- 'sesskey' => sesskey());
- // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
- $url = new \moodle_url('/mod/lti/return.php', $returnurlparams);
- $returnurl = $url->out(false);
- if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
- $returnurl = lti_ensure_url_is_https($returnurl);
- }
- $target = '';
- switch($launchcontainer) {
- case LTI_LAUNCH_CONTAINER_EMBED:
- case LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS:
- $target = 'iframe';
- break;
- case LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW:
- $target = 'frame';
- break;
- case LTI_LAUNCH_CONTAINER_WINDOW:
- $target = 'window';
- break;
- }
- if (!empty($target)) {
- $requestparams['launch_presentation_document_target'] = $target;
- }
- $requestparams['launch_presentation_return_url'] = $returnurl;
- // Add the parameters configured by the LTI services.
- if ($typeid && !$islti2) {
- $services = lti_get_services();
- foreach ($services as $service) {
- $serviceparameters = $service->get_launch_parameters('basic-lti-launch-request',
- $course->id, $USER->id , $typeid, $instance->id);
- foreach ($serviceparameters as $paramkey => $paramvalue) {
- $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
- $islti2);
- }
- }
- }
- // Allow request params to be updated by sub-plugins.
- $plugins = core_component::get_plugin_list('ltisource');
- foreach (array_keys($plugins) as $plugin) {
- $pluginparams = component_callback('ltisource_'.$plugin, 'before_launch',
- array($instance, $endpoint, $requestparams), array());
- if (!empty($pluginparams) && is_array($pluginparams)) {
- $requestparams = array_merge($requestparams, $pluginparams);
- }
- }
- if ((!empty($key) && !empty($secret)) || ($ltiversion === LTI_VERSION_1P3)) {
- if ($ltiversion !== LTI_VERSION_1P3) {
- $parms = lti_sign_parameters($requestparams, $endpoint, 'POST', $key, $secret);
- } else {
- $parms = lti_sign_jwt($requestparams, $endpoint, $key, $typeid, $nonce);
- }
- $endpointurl = new \moodle_url($endpoint);
- $endpointparams = $endpointurl->params();
- // Strip querystring params in endpoint url from $parms to avoid duplication.
- if (!empty($endpointparams) && !empty($parms)) {
- foreach (array_keys($endpointparams) as $paramname) {
- if (isset($parms[$paramname])) {
- unset($parms[$paramname]);
- }
- }
- }
- } else {
- // If no key and secret, do the launch unsigned.
- $returnurlparams['unsigned'] = '1';
- $parms = $requestparams;
- }
- return array($endpoint, $parms);
- }
- /**
- * Launch an external tool activity.
- *
- * @param stdClass $instance the external tool activity settings
- * @return string The HTML code containing the javascript code for the launch
- */
- function lti_launch_tool($instance) {
- list($endpoint, $parms) = lti_get_launch_data($instance);
- $debuglaunch = ( $instance->debuglaunch == 1 );
- $content = lti_post_launch_html($parms, $endpoint, $debuglaunch);
- echo $content;
- }
- /**
- * Prepares an LTI registration request message
- *
- * @param object $toolproxy Tool Proxy instance object
- */
- function lti_register($toolproxy) {
- $endpoint = $toolproxy->regurl;
- // Change the status to pending.
- $toolproxy->state = LTI_TOOL_PROXY_STATE_PENDING;
- lti_update_tool_proxy($toolproxy);
- $requestparams = lti_build_registration_request($toolproxy);
- $content = lti_post_launch_html($requestparams, $endpoint, false);
- echo $content;
- }
- /**
- * Gets the parameters for the regirstration request
- *
- * @param object $toolproxy Tool Proxy instance object
- * @return array Registration request parameters
- */
- function lti_build_registration_request($toolproxy) {
- $key = $toolproxy->guid;
- $secret = $toolproxy->secret;
- $requestparams = array();
- $requestparams['lti_message_type'] = 'ToolProxyRegistrationRequest';
- $requestparams['lti_version'] = 'LTI-2p0';
- $requestparams['reg_key'] = $key;
- $requestparams['reg_password'] = $secret;
- $requestparams['reg_url'] = $toolproxy->regurl;
- // Add the profile URL.
- $profileservice = lti_get_service_by_name('profile');
- $profileservice->set_tool_proxy($toolproxy);
- $requestparams['tc_profile_url'] = $profileservice->parse_value('$ToolConsumerProfile.url');
- // Add the return URL.
- $returnurlparams = array('id' => $toolproxy->id, 'sesskey' => sesskey());
- $url = new \moodle_url('/mod/lti/externalregistrationreturn.php', $returnurlparams);
- $returnurl = $url->out(false);
- $requestparams['launch_presentation_return_url'] = $returnurl;
- return $requestparams;
- }
- /** get Organization ID using default if no value provided
- * @param object $typeconfig
- * @return string
- */
- function lti_get_organizationid($typeconfig) {
- global $CFG;
- // Default the organizationid if not specified.
- if (empty($typeconfig['organizationid'])) {
- if (($typeconfig['organizationid_default'] ?? LTI_DEFAULT_ORGID_SITEHOST) == LTI_DEFAULT_ORGID_SITEHOST) {
- $urlparts = parse_url($CFG->wwwroot);
- return $urlparts['host'];
- } else {
- return md5(get_site_identifier());
- }
- }
- return $typeconfig['organizationid'];
- }
- /**
- * Build source ID
- *
- * @param int $instanceid
- * @param int $userid
- * @param string $servicesalt
- * @param null|int $typeid
- * @param null|int $launchid
- * @return stdClass
- */
- function lti_build_sourcedid($instanceid, $userid, $servicesalt, $typeid = null, $launchid = null) {
- $data = new \stdClass();
- $data->instanceid = $instanceid;
- $data->userid = $userid;
- $data->typeid = $typeid;
- if (!empty($launchid)) {
- $data->launchid = $launchid;
- } else {
- $data->launchid = mt_rand();
- }
- $json = json_encode($data);
- $hash = hash('sha256', $json . $servicesalt, false);
- $container = new \stdClass();
- $container->data = $data;
- $container->hash = $hash;
- return $container;
- }
- /**
- * This function builds the request that must be sent to the tool producer
- *
- * @param object $instance Basic LTI instance object
- * @param array $typeconfig Basic LTI tool configuration
- * @param object $course Course object
- * @param int|null $typeid Basic LTI tool ID
- * @param boolean $islti2 True if an LTI 2 tool is being launched
- *
- * @return array Request details
- */
- function lti_build_request($instance, $typeconfig, $course, $typeid = null, $islti2 = false) {
- global $USER, $CFG;
- if (empty($instance->cmid)) {
- $instance->cmid = 0;
- }
- $role = lti_get_ims_role($USER, $instance->cmid, $instance->course, $islti2);
- $requestparams = array(
- 'user_id' => $USER->id,
- 'lis_person_sourcedid' => $USER->idnumber,
- 'roles' => $role,
- 'context_id' => $course->id,
- 'context_label' => trim(html_to_text($course->shortname, 0)),
- 'context_title' => trim(html_to_text($course->fullname, 0)),
- );
- if (!empty($instance->name)) {
- $requestparams['resource_link_title'] = trim(html_to_text($instance->name, 0));
- }
- if (!empty($instance->cmid)) {
- $intro = format_module_intro('lti', $instance, $instance->cmid);
- $intro = trim(html_to_text($intro, 0, false));
- // This may look weird, but this is required for new lines
- // so we generate the same OAuth signature as the tool provider.
- $intro = str_replace("\n", "\r\n", $intro);
- $requestparams['resource_link_description'] = $intro;
- }
- if (!empty($instance->id)) {
- $requestparams['resource_link_id'] = $instance->id;
- }
- if (!empty($instance->resource_link_id)) {
- $requestparams['resource_link_id'] = $instance->resource_link_id;
- }
- if ($course->format == 'site') {
- $requestparams['context_type'] = 'Group';
- } else {
- $requestparams['context_type'] = 'CourseSection';
- $requestparams['lis_course_section_sourcedid'] = $course->idnumber;
- }
- if (!empty($instance->id) && !empty($instance->servicesalt) && ($islti2 ||
- $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS ||
- ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))
- ) {
- $placementsecret = $instance->servicesalt;
- $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid));
- $requestparams['lis_result_sourcedid'] = $sourcedid;
- // Add outcome service URL.
- $serviceurl = new \moodle_url('/mod/lti/service.php');
- $serviceurl = $serviceurl->out();
- $forcessl = false;
- if (!empty($CFG->mod_lti_forcessl)) {
- $forcessl = true;
- }
- if ((isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) or $forcessl) {
- $serviceurl = lti_ensure_url_is_https($serviceurl);
- }
- $requestparams['lis_outcome_service_url'] = $serviceurl;
- }
- // Send user's name and email data if appropriate.
- if ($islti2 || $typeconfig['sendname'] == LTI_SETTING_ALWAYS ||
- ($typeconfig['sendname'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendname)
- && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS)
- ) {
- $requestparams['lis_person_name_given'] = $USER->firstname;
- $requestparams['lis_person_name_family'] = $USER->lastname;
- $requestparams['lis_person_name_full'] = fullname($USER);
- $requestparams['ext_user_username'] = $USER->username;
- }
- if ($islti2 || $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS ||
- ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendemailaddr)
- && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS)
- ) {
- $requestparams['lis_person_contact_email_primary'] = $USER->email;
- }
- return $requestparams;
- }
- /**
- * This function builds the request that must be sent to an LTI 2 tool provider
- *
- * @param object $tool Basic LTI tool object
- * @param array $params Custom launch parameters
- *
- * @return array Request details
- */
- function lti_build_request_lti2($tool, $params) {
- $requestparams = array();
- $capabilities = lti_get_capabilities();
- $enabledcapabilities = explode("\n", $tool->enabledcapability);
- foreach ($enabledcapabilities as $capability) {
- if (array_key_exists($capability, $capabilities)) {
- $val = $capabilities[$capability];
- if ($val && (substr($val, 0, 1) != '$')) {
- if (isset($params[$val])) {
- $requestparams[$capabilities[$capability]] = $params[$capabilities[$capability]];
- }
- }
- }
- }
- return $requestparams;
- }
- /**
- * This function builds the standard parameters for an LTI 1 or 2 request that must be sent to the tool producer
- *
- * @param stdClass $instance Basic LTI instance object
- * @param string $orgid Organisation ID
- * @param boolean $islti2 True if an LTI 2 tool is being launched
- * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty.
- *
- * @return array Request details
- * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
- * @see lti_build_standard_message()
- */
- function lti_build_standard_request($instance, $orgid, $islti2, $messagetype = 'basic-lti-launch-request') {
- if (!$islti2) {
- $ltiversion = LTI_VERSION_1;
- } else {
- $ltiversion = LTI_VERSION_2;
- }
- return lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype);
- }
- /**
- * This function builds the standard parameters for an LTI message that must be sent to the tool producer
- *
- * @param stdClass $instance Basic LTI instance object
- * @param string $orgid Organisation ID
- * @param boolean $ltiversion LTI version to be used for tool messages
- * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty.
- *
- * @return array Message parameters
- */
- function lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype = 'basic-lti-launch-request') {
- global $CFG;
- $requestparams = array();
- if ($instance) {
- $requestparams['resource_link_id'] = $instance->id;
- if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) {
- $requestparams['resource_link_id'] = $instance->resource_link_id;
- }
- }
- $requestparams['launch_presentation_locale'] = current_language();
- // Make sure we let the tool know what LMS they are being called from.
- $requestparams['ext_lms'] = 'moodle-2';
- $requestparams['tool_consumer_info_product_family_code'] = 'moodle';
- $requestparams['tool_consumer_info_version'] = strval($CFG->version);
- // Add oauth_callback to be compliant with the 1.0A spec.
- $requestparams['oauth_callback'] = 'about:blank';
- $requestparams['lti_version'] = $ltiversion;
- $requestparams['lti_message_type'] = $messagetype;
- if ($orgid) {
- $requestparams["tool_consumer_instance_guid"] = $orgid;
- }
- if (!empty($CFG->mod_lti_institution_name)) {
- $requestparams['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0));
- } else {
- $requestparams['tool_consumer_instance_name'] = get_site()->shortname;
- }
- $requestparams['tool_consumer_instance_description'] = trim(html_to_text(get_site()->fullname, 0));
- return $requestparams;
- }
- /**
- * This function builds the custom parameters
- *
- * @param object $toolproxy Tool proxy instance object
- * @param object $tool Tool instance object
- * @param object $instance Tool placement instance object
- * @param array $params LTI launch parameters
- * @param string $customstr Custom parameters defined for tool
- * @param string $instructorcustomstr Custom parameters defined for this placement
- * @param boolean $islti2 True if an LTI 2 tool is being launched
- *
- * @return array Custom parameters
- */
- function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $customstr, $instructorcustomstr, $islti2) {
- // Concatenate the custom parameters from the administrator and the instructor
- // Instructor parameters are only taken into consideration if the administrator
- // has given permission.
- $custom = array();
- if ($customstr) {
- $custom = lti_split_custom_parameters($toolproxy, $tool, $params, $customstr, $islti2);
- }
- if ($instructorcustomstr) {
- $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
- $instructorcustomstr, $islti2), $custom);
- }
- if ($islti2) {
- $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
- $tool->parameter, true), $custom);
- $settings = lti_get_tool_settings($tool->toolproxyid);
- $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
- if (!empty($instance->course)) {
- $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course);
- $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
- if (!empty($instance->id)) {
- $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id);
- $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
- }
- }
- }
- return $custom;
- }
- /**
- * Builds a standard LTI Content-Item selection request.
- *
- * @param int $id The tool type ID.
- * @param stdClass $course The course object.
- * @param moodle_url $returnurl The return URL in the tool consumer (TC) that the tool provider (TP)
- * will use to return the Content-Item message.
- * @param string $title The tool's title, if available.
- * @param string $text The text to display to represent the content item. This value may be a long description of the content item.
- * @param array $mediatypes Array of MIME types types supported by the TC. If empty, the TC will support ltilink by default.
- * @param array $presentationtargets Array of ways in which the selected content item(s) can be requested to be opened
- * (via the presentationDocumentTarget element for a returned content item).
- * If empty, "frame", "iframe", and "window" will be supported by default.
- * @param bool $autocreate Indicates whether any content items returned by the TP would be automatically persisted without
- * @param bool $multiple Indicates whether the user should be permitted to select more than one item. False by default.
- * any option for the user to cancel the operation. False by default.
- * @param bool $unsigned Indicates whether the TC is willing to accept an unsigned return message, or not.
- * A signed message should always be required when the content item is being created automatically in the
- * TC without further interaction from the user. False by default.
- * @param bool $canconfirm Flag for can_confirm parameter. False by default.
- * @param bool $copyadvice Indicates whether the TC is able and willing to make a local copy of a content item. False by default.
- * @param string $nonce
- * @return stdClass The object containing the signed request parameters and the URL to the TP's Content-Item selection interface.
- * @throws moodle_exception When the LTI tool type does not exist.`
- * @throws coding_exception For invalid media type and presentation target parameters.
- */
- function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
- $presentationtargets = [], $autocreate = false, $multiple = true,
- $unsigned = false, $canconfirm = false, $copyadvice = false, $nonce = '') {
- global $USER;
- $tool = lti_get_type($id);
- // Validate parameters.
- if (!$tool) {
- throw new moodle_exception('errortooltypenotfound', 'mod_lti');
- }
- if (!is_array($mediatypes)) {
- throw new coding_exception('The list of accepted media types should be in an array');
- }
- if (!is_array($presentationtargets)) {
- throw new coding_exception('The list of accepted presentation targets should be in an array');
- }
- // Check title. If empty, use the tool's name.
- if (empty($title)) {
- $title = $tool->name;
- }
- $typeconfig = lti_get_type_config($id);
- $key = '';
- $secret = '';
- $islti2 = false;
- $islti13 = false;
- if (isset($tool->toolproxyid)) {
- $islti2 = true;
- $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
- $key = $toolproxy->guid;
- $secret = $toolproxy->secret;
- } else {
- $islti13 = $tool->ltiversion === LTI_VERSION_1P3;
- $toolproxy = null;
- if ($islti13 && !empty($tool->clientid)) {
- $key = $tool->clientid;
- } else if (!$islti13 && !empty($typeconfig['resourcekey'])) {
- $key = $typeconfig['resourcekey'];
- }
- if (!empty($typeconfig['password'])) {
- $secret = $typeconfig['password'];
- }
- }
- $tool->enabledcapability = '';
- if (!empty($typeconfig['enabledcapability_ContentItemSelectionRequest'])) {
- $tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest'];
- }
- $tool->parameter = '';
- if (!empty($typeconfig['parameter_ContentItemSelectionRequest'])) {
- $tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest'];
- }
- // Set the tool URL.
- if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) {
- $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']);
- } else {
- $toolurl = new moodle_url($typeconfig['toolurl']);
- }
- // Check if SSL is forced.
- if (!empty($typeconfig['forcessl'])) {
- // Make sure the tool URL is set to https.
- if (strtolower($toolurl->get_scheme()) === 'http') {
- $toolurl->set_scheme('https');
- }
- // Make sure the return URL is set to https.
- if (strtolower($returnurl->get_scheme()) === 'http') {
- $returnurl->set_scheme('https');
- }
- }
- $toolurlout = $toolurl->out(false);
- // Get base request parameters.
- $instance = new stdClass();
- $instance->course = $course->id;
- $requestparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2);
- // Get LTI2-specific request parameters and merge to the request parameters if applicable.
- if ($islti2) {
- $lti2params = lti_build_request_lti2($tool, $requestparams);
- $requestparams = array_merge($requestparams, $lti2params);
- }
- // Get standard request parameters and merge to the request parameters.
- $orgid = lti_get_organizationid($typeconfig);
- $standardparams = lti_build_standard_message(null, $orgid, $tool->ltiversion, 'ContentItemSelectionRequest');
- $requestparams = array_merge($requestparams, $standardparams);
- // Get custom request parameters and merge to the request parameters.
- $customstr = '';
- if (!empty($typeconfig['customparameters'])) {
- $customstr = $typeconfig['customparameters'];
- }
- $customparams = lti_build_custom_parameters($toolproxy, $tool, $instance, $requestparams, $customstr, '', $islti2);
- $requestparams = array_merge($requestparams, $customparams);
- // Add the parameters configured by the LTI services.
- if ($id && !$islti2) {
- $services = lti_get_services();
- foreach ($services as $service) {
- $serviceparameters = $service->get_launch_parameters('ContentItemSelectionRequest',
- $course->id, $USER->id , $id);
- foreach ($serviceparameters as $paramkey => $paramvalue) {
- $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
- $islti2);
- }
- }
- }
- // Allow request params to be updated by sub-plugins.
- $plugins = core_component::get_plugin_list('ltisource');
- foreach (array_keys($plugins) as $plugin) {
- $pluginparams = component_callback('ltisource_' . $plugin, 'before_launch', [$instance, $toolurlout, $requestparams], []);
- if (!empty($pluginparams) && is_array($pluginparams)) {
- $requestparams = array_merge($requestparams, $pluginparams);
- }
- }
- if (!$islti13) {
- // Media types. Set to ltilink by default if empty.
- if (empty($mediatypes)) {
- $mediatypes = [
- 'application/vnd.ims.lti.v1.ltilink',
- ];
- }
- $requestparams['accept_media_types'] = implode(',', $mediatypes);
- } else {
- // Only LTI links are currently supported.
- $requestparams['accept_types'] = 'ltiResourceLink';
- }
- // Presentation targets. Supports frame, iframe, window by default if empty.
- if (empty($presentationtargets)) {
- $presentationtargets = [
- 'frame',
- 'iframe',
- 'window',
- ];
- }
- $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets);
- // Other request parameters.
- $requestparams['accept_copy_advice'] = $copyadvice === true ? 'true' : 'false';
- $requestparams['accept_multiple'] = $multiple === true ? 'true' : 'false';
- $requestparams['accept_unsigned'] = $unsigned === true ? 'true' : 'false';
- $requestparams['auto_create'] = $autocreate === true ? 'true' : 'false';
- $requestparams['can_confirm'] = $canconfirm === true ? 'true' : 'false';
- $requestparams['content_item_return_url'] = $returnurl->out(false);
- $requestparams['title'] = $title;
- $requestparams['text'] = $text;
- if (!$islti13) {
- $signedparams = lti_sign_parameters($requestparams, $toolurlout, 'POST', $key, $secret);
- } else {
- $signedparams = lti_sign_jwt($requestparams, $toolurlout, $key, $id, $nonce);
- }
- $toolurlparams = $toolurl->params();
- // Strip querystring params in endpoint url from $signedparams to avoid duplication.
- if (!empty($toolurlparams) && !empty($signedparams)) {
- foreach (array_keys($toolurlparams) as $paramname) {
- if (isset($signedparams[$paramname])) {
- unset($signedparams[$paramname]);
- }
- }
- }
- // Check for params that should not be passed. Unset if they are set.
- $unwantedparams = [
- 'resource_link_id',
- 'resource_link_title',
- 'resource_link_description',
- 'launch_presentation_return_url',
- 'lis_result_sourcedid',
- ];
- foreach ($unwantedparams as $param) {
- if (isset($signedparams[$param])) {
- unset($signedparams[$param]);
- }
- }
- // Prepare result object.
- $result = new stdClass();
- $result->params = $signedparams;
- $result->url = $toolurlout;
- return $result;
- }
- /**
- * Verifies the OAuth signature of an incoming message.
- *
- * @param int $typeid The tool type ID.
- * @param string $consumerkey The consumer key.
- * @return stdClass Tool type
- * @throws moodle_exception
- * @throws lti\OAuthException
- */
- function lti_verify_oauth_signature($typeid, $consumerkey) {
- $tool = lti_get_type($typeid);
- // Validate parameters.
- if (!$tool) {
- throw new moodle_exception('errortooltypenotfound', 'mod_lti');
- }
- $typeconfig = lti_get_type_config($typeid);
- if (isset($tool->toolproxyid)) {
- $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
- $key = $toolproxy->guid;
- $secret = $toolproxy->secret;
- } else {
- $toolproxy = null;
- if (!empty($typeconfig['resourcekey'])) {
- $key = $typeconfig['resourcekey'];
- } else {
- $key = '';
- }
- if (!empty($typeconfig['password'])) {
- $secret = $typeconfig['password'];
- } else {
- $secret = '';
- }
- }
- if ($consumerkey !== $key) {
- throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti');
- }
- $store = new lti\TrivialOAuthDataStore();
- $store->add_consumer($key, $secret);
- $server = new lti\OAuthServer($store);
- $method = new lti\OAuthSignatureMethod_HMAC_SHA1();
- $server->add_signature_method($method);
- $request = lti\OAuthRequest::from_request();
- try {
- $server->verify_request($request);
- } catch (lti\OAuthException $e) {
- throw new lti\OAuthException("OAuth signature failed: " . $e->getMessage());
- }
- return $tool;
- }
- /**
- * Verifies the JWT signature using a JWK keyset.
- *
- * @param string $jwtparam JWT parameter value.
- * @param string $keyseturl The tool keyseturl.
- * @param string $clientid The tool client id.
- *
- * @return object The JWT's payload as a PHP object
- * @throws moodle_exception
- * @throws UnexpectedValueException Provided JWT was invalid
- * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
- * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
- * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
- * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
- */
- function lti_verify_with_keyset($jwtparam, $keyseturl, $clientid) {
- // Attempts to retrieve cached keyset.
- $cache = cache::make('mod_lti', 'keyset');
- $keyset = $cache->get($clientid);
- try {
- if (empty($keyset)) {
- throw new moodle_exception('errornocachedkeysetfound', 'mod_lti');
- }
- $keysetarr = json_decode($keyset, true);
- $keys = JWK::parseKeySet($keysetarr);
- $jwt = JWT::decode($jwtparam, $keys, ['RS256']);
- } catch (Exception $e) {
- // Something went wrong, so attempt to update cached keyset and then try again.
- $keyset = file_get_contents($keyseturl);
- $keysetarr = json_decode($keyset, true);
- $keys = JWK::parseKeySet($keysetarr);
- $jwt = JWT::decode($jwtparam, $keys, ['RS256']);
- // If sucessful, updates the cached keyset.
- $cache->set($clientid, $key…
Large files files are truncated, but you can click here to view the full file