PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/lti/locallib.php

https://bitbucket.org/moodle/moodle
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

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. //
  17. // This file is part of BasicLTI4Moodle
  18. //
  19. // BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
  20. // consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
  21. // based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
  22. // specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
  23. // are already supporting or going to support BasicLTI. This project Implements the consumer
  24. // for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
  25. // BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
  26. // at the GESSI research group at UPC.
  27. // SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
  28. // by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
  29. // Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
  30. //
  31. // BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
  32. // of the Universitat Politecnica de Catalunya http://www.upc.edu
  33. // Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
  34. /**
  35. * This file contains the library of functions and constants for the lti module
  36. *
  37. * @package mod_lti
  38. * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
  39. * marc.alier@upc.edu
  40. * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
  41. * @author Marc Alier
  42. * @author Jordi Piguillem
  43. * @author Nikolas Galanis
  44. * @author Chris Scribner
  45. * @copyright 2015 Vital Source Technologies http://vitalsource.com
  46. * @author Stephen Vickers
  47. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  48. */
  49. defined('MOODLE_INTERNAL') || die;
  50. // TODO: Switch to core oauthlib once implemented - MDL-30149.
  51. use moodle\mod\lti as lti;
  52. use Firebase\JWT\JWT;
  53. use Firebase\JWT\JWK;
  54. use mod_lti\local\ltiopenid\jwks_helper;
  55. use mod_lti\local\ltiopenid\registration_helper;
  56. global $CFG;
  57. require_once($CFG->dirroot.'/mod/lti/OAuth.php');
  58. require_once($CFG->libdir.'/weblib.php');
  59. require_once($CFG->dirroot . '/course/modlib.php');
  60. require_once($CFG->dirroot . '/mod/lti/TrivialStore.php');
  61. define('LTI_URL_DOMAIN_REGEX', '/(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i');
  62. define('LTI_LAUNCH_CONTAINER_DEFAULT', 1);
  63. define('LTI_LAUNCH_CONTAINER_EMBED', 2);
  64. define('LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS', 3);
  65. define('LTI_LAUNCH_CONTAINER_WINDOW', 4);
  66. define('LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW', 5);
  67. define('LTI_TOOL_STATE_ANY', 0);
  68. define('LTI_TOOL_STATE_CONFIGURED', 1);
  69. define('LTI_TOOL_STATE_PENDING', 2);
  70. define('LTI_TOOL_STATE_REJECTED', 3);
  71. define('LTI_TOOL_PROXY_TAB', 4);
  72. define('LTI_TOOL_PROXY_STATE_CONFIGURED', 1);
  73. define('LTI_TOOL_PROXY_STATE_PENDING', 2);
  74. define('LTI_TOOL_PROXY_STATE_ACCEPTED', 3);
  75. define('LTI_TOOL_PROXY_STATE_REJECTED', 4);
  76. define('LTI_SETTING_NEVER', 0);
  77. define('LTI_SETTING_ALWAYS', 1);
  78. define('LTI_SETTING_DELEGATE', 2);
  79. define('LTI_COURSEVISIBLE_NO', 0);
  80. define('LTI_COURSEVISIBLE_PRECONFIGURED', 1);
  81. define('LTI_COURSEVISIBLE_ACTIVITYCHOOSER', 2);
  82. define('LTI_VERSION_1', 'LTI-1p0');
  83. define('LTI_VERSION_2', 'LTI-2p0');
  84. define('LTI_VERSION_1P3', '1.3.0');
  85. define('LTI_RSA_KEY', 'RSA_KEY');
  86. define('LTI_JWK_KEYSET', 'JWK_KEYSET');
  87. define('LTI_DEFAULT_ORGID_SITEID', 'SITEID');
  88. define('LTI_DEFAULT_ORGID_SITEHOST', 'SITEHOST');
  89. define('LTI_ACCESS_TOKEN_LIFE', 3600);
  90. // Standard prefix for JWT claims.
  91. define('LTI_JWT_CLAIM_PREFIX', 'https://purl.imsglobal.org/spec/lti');
  92. /**
  93. * Return the mapping for standard message types to JWT message_type claim.
  94. *
  95. * @return array
  96. */
  97. function lti_get_jwt_message_type_mapping() {
  98. return array(
  99. 'basic-lti-launch-request' => 'LtiResourceLinkRequest',
  100. 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
  101. 'LtiDeepLinkingResponse' => 'ContentItemSelection',
  102. );
  103. }
  104. /**
  105. * Return the mapping for standard message parameters to JWT claim.
  106. *
  107. * @return array
  108. */
  109. function lti_get_jwt_claim_mapping() {
  110. return array(
  111. 'accept_copy_advice' => [
  112. 'suffix' => 'dl',
  113. 'group' => 'deep_linking_settings',
  114. 'claim' => 'accept_copy_advice',
  115. 'isarray' => false,
  116. 'type' => 'boolean'
  117. ],
  118. 'accept_media_types' => [
  119. 'suffix' => 'dl',
  120. 'group' => 'deep_linking_settings',
  121. 'claim' => 'accept_media_types',
  122. 'isarray' => true
  123. ],
  124. 'accept_multiple' => [
  125. 'suffix' => 'dl',
  126. 'group' => 'deep_linking_settings',
  127. 'claim' => 'accept_multiple',
  128. 'isarray' => false,
  129. 'type' => 'boolean'
  130. ],
  131. 'accept_presentation_document_targets' => [
  132. 'suffix' => 'dl',
  133. 'group' => 'deep_linking_settings',
  134. 'claim' => 'accept_presentation_document_targets',
  135. 'isarray' => true
  136. ],
  137. 'accept_types' => [
  138. 'suffix' => 'dl',
  139. 'group' => 'deep_linking_settings',
  140. 'claim' => 'accept_types',
  141. 'isarray' => true
  142. ],
  143. 'accept_unsigned' => [
  144. 'suffix' => 'dl',
  145. 'group' => 'deep_linking_settings',
  146. 'claim' => 'accept_unsigned',
  147. 'isarray' => false,
  148. 'type' => 'boolean'
  149. ],
  150. 'auto_create' => [
  151. 'suffix' => 'dl',
  152. 'group' => 'deep_linking_settings',
  153. 'claim' => 'auto_create',
  154. 'isarray' => false,
  155. 'type' => 'boolean'
  156. ],
  157. 'can_confirm' => [
  158. 'suffix' => 'dl',
  159. 'group' => 'deep_linking_settings',
  160. 'claim' => 'can_confirm',
  161. 'isarray' => false,
  162. 'type' => 'boolean'
  163. ],
  164. 'content_item_return_url' => [
  165. 'suffix' => 'dl',
  166. 'group' => 'deep_linking_settings',
  167. 'claim' => 'deep_link_return_url',
  168. 'isarray' => false
  169. ],
  170. 'content_items' => [
  171. 'suffix' => 'dl',
  172. 'group' => '',
  173. 'claim' => 'content_items',
  174. 'isarray' => true
  175. ],
  176. 'data' => [
  177. 'suffix' => 'dl',
  178. 'group' => 'deep_linking_settings',
  179. 'claim' => 'data',
  180. 'isarray' => false
  181. ],
  182. 'text' => [
  183. 'suffix' => 'dl',
  184. 'group' => 'deep_linking_settings',
  185. 'claim' => 'text',
  186. 'isarray' => false
  187. ],
  188. 'title' => [
  189. 'suffix' => 'dl',
  190. 'group' => 'deep_linking_settings',
  191. 'claim' => 'title',
  192. 'isarray' => false
  193. ],
  194. 'lti_msg' => [
  195. 'suffix' => 'dl',
  196. 'group' => '',
  197. 'claim' => 'msg',
  198. 'isarray' => false
  199. ],
  200. 'lti_log' => [
  201. 'suffix' => 'dl',
  202. 'group' => '',
  203. 'claim' => 'log',
  204. 'isarray' => false
  205. ],
  206. 'lti_errormsg' => [
  207. 'suffix' => 'dl',
  208. 'group' => '',
  209. 'claim' => 'errormsg',
  210. 'isarray' => false
  211. ],
  212. 'lti_errorlog' => [
  213. 'suffix' => 'dl',
  214. 'group' => '',
  215. 'claim' => 'errorlog',
  216. 'isarray' => false
  217. ],
  218. 'context_id' => [
  219. 'suffix' => '',
  220. 'group' => 'context',
  221. 'claim' => 'id',
  222. 'isarray' => false
  223. ],
  224. 'context_label' => [
  225. 'suffix' => '',
  226. 'group' => 'context',
  227. 'claim' => 'label',
  228. 'isarray' => false
  229. ],
  230. 'context_title' => [
  231. 'suffix' => '',
  232. 'group' => 'context',
  233. 'claim' => 'title',
  234. 'isarray' => false
  235. ],
  236. 'context_type' => [
  237. 'suffix' => '',
  238. 'group' => 'context',
  239. 'claim' => 'type',
  240. 'isarray' => true
  241. ],
  242. 'lis_course_offering_sourcedid' => [
  243. 'suffix' => '',
  244. 'group' => 'lis',
  245. 'claim' => 'course_offering_sourcedid',
  246. 'isarray' => false
  247. ],
  248. 'lis_course_section_sourcedid' => [
  249. 'suffix' => '',
  250. 'group' => 'lis',
  251. 'claim' => 'course_section_sourcedid',
  252. 'isarray' => false
  253. ],
  254. 'launch_presentation_css_url' => [
  255. 'suffix' => '',
  256. 'group' => 'launch_presentation',
  257. 'claim' => 'css_url',
  258. 'isarray' => false
  259. ],
  260. 'launch_presentation_document_target' => [
  261. 'suffix' => '',
  262. 'group' => 'launch_presentation',
  263. 'claim' => 'document_target',
  264. 'isarray' => false
  265. ],
  266. 'launch_presentation_height' => [
  267. 'suffix' => '',
  268. 'group' => 'launch_presentation',
  269. 'claim' => 'height',
  270. 'isarray' => false
  271. ],
  272. 'launch_presentation_locale' => [
  273. 'suffix' => '',
  274. 'group' => 'launch_presentation',
  275. 'claim' => 'locale',
  276. 'isarray' => false
  277. ],
  278. 'launch_presentation_return_url' => [
  279. 'suffix' => '',
  280. 'group' => 'launch_presentation',
  281. 'claim' => 'return_url',
  282. 'isarray' => false
  283. ],
  284. 'launch_presentation_width' => [
  285. 'suffix' => '',
  286. 'group' => 'launch_presentation',
  287. 'claim' => 'width',
  288. 'isarray' => false
  289. ],
  290. 'lis_person_contact_email_primary' => [
  291. 'suffix' => '',
  292. 'group' => null,
  293. 'claim' => 'email',
  294. 'isarray' => false
  295. ],
  296. 'lis_person_name_family' => [
  297. 'suffix' => '',
  298. 'group' => null,
  299. 'claim' => 'family_name',
  300. 'isarray' => false
  301. ],
  302. 'lis_person_name_full' => [
  303. 'suffix' => '',
  304. 'group' => null,
  305. 'claim' => 'name',
  306. 'isarray' => false
  307. ],
  308. 'lis_person_name_given' => [
  309. 'suffix' => '',
  310. 'group' => null,
  311. 'claim' => 'given_name',
  312. 'isarray' => false
  313. ],
  314. 'lis_person_sourcedid' => [
  315. 'suffix' => '',
  316. 'group' => 'lis',
  317. 'claim' => 'person_sourcedid',
  318. 'isarray' => false
  319. ],
  320. 'user_id' => [
  321. 'suffix' => '',
  322. 'group' => null,
  323. 'claim' => 'sub',
  324. 'isarray' => false
  325. ],
  326. 'user_image' => [
  327. 'suffix' => '',
  328. 'group' => null,
  329. 'claim' => 'picture',
  330. 'isarray' => false
  331. ],
  332. 'roles' => [
  333. 'suffix' => '',
  334. 'group' => '',
  335. 'claim' => 'roles',
  336. 'isarray' => true
  337. ],
  338. 'role_scope_mentor' => [
  339. 'suffix' => '',
  340. 'group' => '',
  341. 'claim' => 'role_scope_mentor',
  342. 'isarray' => false
  343. ],
  344. 'deployment_id' => [
  345. 'suffix' => '',
  346. 'group' => '',
  347. 'claim' => 'deployment_id',
  348. 'isarray' => false
  349. ],
  350. 'lti_message_type' => [
  351. 'suffix' => '',
  352. 'group' => '',
  353. 'claim' => 'message_type',
  354. 'isarray' => false
  355. ],
  356. 'lti_version' => [
  357. 'suffix' => '',
  358. 'group' => '',
  359. 'claim' => 'version',
  360. 'isarray' => false
  361. ],
  362. 'resource_link_description' => [
  363. 'suffix' => '',
  364. 'group' => 'resource_link',
  365. 'claim' => 'description',
  366. 'isarray' => false
  367. ],
  368. 'resource_link_id' => [
  369. 'suffix' => '',
  370. 'group' => 'resource_link',
  371. 'claim' => 'id',
  372. 'isarray' => false
  373. ],
  374. 'resource_link_title' => [
  375. 'suffix' => '',
  376. 'group' => 'resource_link',
  377. 'claim' => 'title',
  378. 'isarray' => false
  379. ],
  380. 'tool_consumer_info_product_family_code' => [
  381. 'suffix' => '',
  382. 'group' => 'tool_platform',
  383. 'claim' => 'product_family_code',
  384. 'isarray' => false
  385. ],
  386. 'tool_consumer_info_version' => [
  387. 'suffix' => '',
  388. 'group' => 'tool_platform',
  389. 'claim' => 'version',
  390. 'isarray' => false
  391. ],
  392. 'tool_consumer_instance_contact_email' => [
  393. 'suffix' => '',
  394. 'group' => 'tool_platform',
  395. 'claim' => 'contact_email',
  396. 'isarray' => false
  397. ],
  398. 'tool_consumer_instance_description' => [
  399. 'suffix' => '',
  400. 'group' => 'tool_platform',
  401. 'claim' => 'description',
  402. 'isarray' => false
  403. ],
  404. 'tool_consumer_instance_guid' => [
  405. 'suffix' => '',
  406. 'group' => 'tool_platform',
  407. 'claim' => 'guid',
  408. 'isarray' => false
  409. ],
  410. 'tool_consumer_instance_name' => [
  411. 'suffix' => '',
  412. 'group' => 'tool_platform',
  413. 'claim' => 'name',
  414. 'isarray' => false
  415. ],
  416. 'tool_consumer_instance_url' => [
  417. 'suffix' => '',
  418. 'group' => 'tool_platform',
  419. 'claim' => 'url',
  420. 'isarray' => false
  421. ],
  422. 'custom_context_memberships_url' => [
  423. 'suffix' => 'nrps',
  424. 'group' => 'namesroleservice',
  425. 'claim' => 'context_memberships_url',
  426. 'isarray' => false
  427. ],
  428. 'custom_context_memberships_versions' => [
  429. 'suffix' => 'nrps',
  430. 'group' => 'namesroleservice',
  431. 'claim' => 'service_versions',
  432. 'isarray' => true
  433. ],
  434. 'custom_gradebookservices_scope' => [
  435. 'suffix' => 'ags',
  436. 'group' => 'endpoint',
  437. 'claim' => 'scope',
  438. 'isarray' => true
  439. ],
  440. 'custom_lineitems_url' => [
  441. 'suffix' => 'ags',
  442. 'group' => 'endpoint',
  443. 'claim' => 'lineitems',
  444. 'isarray' => false
  445. ],
  446. 'custom_lineitem_url' => [
  447. 'suffix' => 'ags',
  448. 'group' => 'endpoint',
  449. 'claim' => 'lineitem',
  450. 'isarray' => false
  451. ],
  452. 'custom_results_url' => [
  453. 'suffix' => 'ags',
  454. 'group' => 'endpoint',
  455. 'claim' => 'results',
  456. 'isarray' => false
  457. ],
  458. 'custom_result_url' => [
  459. 'suffix' => 'ags',
  460. 'group' => 'endpoint',
  461. 'claim' => 'result',
  462. 'isarray' => false
  463. ],
  464. 'custom_scores_url' => [
  465. 'suffix' => 'ags',
  466. 'group' => 'endpoint',
  467. 'claim' => 'scores',
  468. 'isarray' => false
  469. ],
  470. 'custom_score_url' => [
  471. 'suffix' => 'ags',
  472. 'group' => 'endpoint',
  473. 'claim' => 'score',
  474. 'isarray' => false
  475. ],
  476. 'lis_outcome_service_url' => [
  477. 'suffix' => 'bo',
  478. 'group' => 'basicoutcome',
  479. 'claim' => 'lis_outcome_service_url',
  480. 'isarray' => false
  481. ],
  482. 'lis_result_sourcedid' => [
  483. 'suffix' => 'bo',
  484. 'group' => 'basicoutcome',
  485. 'claim' => 'lis_result_sourcedid',
  486. 'isarray' => false
  487. ],
  488. );
  489. }
  490. /**
  491. * Return the type of the instance, using domain matching if no explicit type is set.
  492. *
  493. * @param object $instance the external tool activity settings
  494. * @return object|null
  495. * @since Moodle 3.9
  496. */
  497. function lti_get_instance_type(object $instance) : ?object {
  498. if (empty($instance->typeid)) {
  499. if (!$tool = lti_get_tool_by_url_match($instance->toolurl, $instance->course)) {
  500. $tool = lti_get_tool_by_url_match($instance->securetoolurl, $instance->course);
  501. }
  502. return $tool;
  503. }
  504. return lti_get_type($instance->typeid);
  505. }
  506. /**
  507. * Return the launch data required for opening the external tool.
  508. *
  509. * @param stdClass $instance the external tool activity settings
  510. * @param string $nonce the nonce value to use (applies to LTI 1.3 only)
  511. * @return array the endpoint URL and parameters (including the signature)
  512. * @since Moodle 3.0
  513. */
  514. function lti_get_launch_data($instance, $nonce = '') {
  515. global $PAGE, $CFG, $USER;
  516. $tool = lti_get_instance_type($instance);
  517. if ($tool) {
  518. $typeid = $tool->id;
  519. $ltiversion = $tool->ltiversion;
  520. } else {
  521. $typeid = null;
  522. $ltiversion = LTI_VERSION_1;
  523. }
  524. if ($typeid) {
  525. $typeconfig = lti_get_type_config($typeid);
  526. } else {
  527. // There is no admin configuration for this tool. Use configuration in the lti instance record plus some defaults.
  528. $typeconfig = (array)$instance;
  529. $typeconfig['sendname'] = $instance->instructorchoicesendname;
  530. $typeconfig['sendemailaddr'] = $instance->instructorchoicesendemailaddr;
  531. $typeconfig['customparameters'] = $instance->instructorcustomparameters;
  532. $typeconfig['acceptgrades'] = $instance->instructorchoiceacceptgrades;
  533. $typeconfig['allowroster'] = $instance->instructorchoiceallowroster;
  534. $typeconfig['forcessl'] = '0';
  535. }
  536. if (isset($tool->toolproxyid)) {
  537. $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
  538. $key = $toolproxy->guid;
  539. $secret = $toolproxy->secret;
  540. } else {
  541. $toolproxy = null;
  542. if (!empty($instance->resourcekey)) {
  543. $key = $instance->resourcekey;
  544. } else if ($ltiversion === LTI_VERSION_1P3) {
  545. $key = $tool->clientid;
  546. } else if (!empty($typeconfig['resourcekey'])) {
  547. $key = $typeconfig['resourcekey'];
  548. } else {
  549. $key = '';
  550. }
  551. if (!empty($instance->password)) {
  552. $secret = $instance->password;
  553. } else if (!empty($typeconfig['password'])) {
  554. $secret = $typeconfig['password'];
  555. } else {
  556. $secret = '';
  557. }
  558. }
  559. $endpoint = !empty($instance->toolurl) ? $instance->toolurl : $typeconfig['toolurl'];
  560. $endpoint = trim($endpoint);
  561. // If the current request is using SSL and a secure tool URL is specified, use it.
  562. if (lti_request_is_using_ssl() && !empty($instance->securetoolurl)) {
  563. $endpoint = trim($instance->securetoolurl);
  564. }
  565. // If SSL is forced, use the secure tool url if specified. Otherwise, make sure https is on the normal launch URL.
  566. if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
  567. if (!empty($instance->securetoolurl)) {
  568. $endpoint = trim($instance->securetoolurl);
  569. }
  570. $endpoint = lti_ensure_url_is_https($endpoint);
  571. } else {
  572. if (!strstr($endpoint, '://')) {
  573. $endpoint = 'http://' . $endpoint;
  574. }
  575. }
  576. $orgid = lti_get_organizationid($typeconfig);
  577. $course = $PAGE->course;
  578. $islti2 = isset($tool->toolproxyid);
  579. $allparams = lti_build_request($instance, $typeconfig, $course, $typeid, $islti2);
  580. if ($islti2) {
  581. $requestparams = lti_build_request_lti2($tool, $allparams);
  582. } else {
  583. $requestparams = $allparams;
  584. }
  585. $requestparams = array_merge($requestparams, lti_build_standard_message($instance, $orgid, $ltiversion));
  586. $customstr = '';
  587. if (isset($typeconfig['customparameters'])) {
  588. $customstr = $typeconfig['customparameters'];
  589. }
  590. $requestparams = array_merge($requestparams, lti_build_custom_parameters($toolproxy, $tool, $instance, $allparams, $customstr,
  591. $instance->instructorcustomparameters, $islti2));
  592. $launchcontainer = lti_get_launch_container($instance, $typeconfig);
  593. $returnurlparams = array('course' => $course->id,
  594. 'launch_container' => $launchcontainer,
  595. 'instanceid' => $instance->id,
  596. 'sesskey' => sesskey());
  597. // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
  598. $url = new \moodle_url('/mod/lti/return.php', $returnurlparams);
  599. $returnurl = $url->out(false);
  600. if (isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) {
  601. $returnurl = lti_ensure_url_is_https($returnurl);
  602. }
  603. $target = '';
  604. switch($launchcontainer) {
  605. case LTI_LAUNCH_CONTAINER_EMBED:
  606. case LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS:
  607. $target = 'iframe';
  608. break;
  609. case LTI_LAUNCH_CONTAINER_REPLACE_MOODLE_WINDOW:
  610. $target = 'frame';
  611. break;
  612. case LTI_LAUNCH_CONTAINER_WINDOW:
  613. $target = 'window';
  614. break;
  615. }
  616. if (!empty($target)) {
  617. $requestparams['launch_presentation_document_target'] = $target;
  618. }
  619. $requestparams['launch_presentation_return_url'] = $returnurl;
  620. // Add the parameters configured by the LTI services.
  621. if ($typeid && !$islti2) {
  622. $services = lti_get_services();
  623. foreach ($services as $service) {
  624. $serviceparameters = $service->get_launch_parameters('basic-lti-launch-request',
  625. $course->id, $USER->id , $typeid, $instance->id);
  626. foreach ($serviceparameters as $paramkey => $paramvalue) {
  627. $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
  628. $islti2);
  629. }
  630. }
  631. }
  632. // Allow request params to be updated by sub-plugins.
  633. $plugins = core_component::get_plugin_list('ltisource');
  634. foreach (array_keys($plugins) as $plugin) {
  635. $pluginparams = component_callback('ltisource_'.$plugin, 'before_launch',
  636. array($instance, $endpoint, $requestparams), array());
  637. if (!empty($pluginparams) && is_array($pluginparams)) {
  638. $requestparams = array_merge($requestparams, $pluginparams);
  639. }
  640. }
  641. if ((!empty($key) && !empty($secret)) || ($ltiversion === LTI_VERSION_1P3)) {
  642. if ($ltiversion !== LTI_VERSION_1P3) {
  643. $parms = lti_sign_parameters($requestparams, $endpoint, 'POST', $key, $secret);
  644. } else {
  645. $parms = lti_sign_jwt($requestparams, $endpoint, $key, $typeid, $nonce);
  646. }
  647. $endpointurl = new \moodle_url($endpoint);
  648. $endpointparams = $endpointurl->params();
  649. // Strip querystring params in endpoint url from $parms to avoid duplication.
  650. if (!empty($endpointparams) && !empty($parms)) {
  651. foreach (array_keys($endpointparams) as $paramname) {
  652. if (isset($parms[$paramname])) {
  653. unset($parms[$paramname]);
  654. }
  655. }
  656. }
  657. } else {
  658. // If no key and secret, do the launch unsigned.
  659. $returnurlparams['unsigned'] = '1';
  660. $parms = $requestparams;
  661. }
  662. return array($endpoint, $parms);
  663. }
  664. /**
  665. * Launch an external tool activity.
  666. *
  667. * @param stdClass $instance the external tool activity settings
  668. * @return string The HTML code containing the javascript code for the launch
  669. */
  670. function lti_launch_tool($instance) {
  671. list($endpoint, $parms) = lti_get_launch_data($instance);
  672. $debuglaunch = ( $instance->debuglaunch == 1 );
  673. $content = lti_post_launch_html($parms, $endpoint, $debuglaunch);
  674. echo $content;
  675. }
  676. /**
  677. * Prepares an LTI registration request message
  678. *
  679. * @param object $toolproxy Tool Proxy instance object
  680. */
  681. function lti_register($toolproxy) {
  682. $endpoint = $toolproxy->regurl;
  683. // Change the status to pending.
  684. $toolproxy->state = LTI_TOOL_PROXY_STATE_PENDING;
  685. lti_update_tool_proxy($toolproxy);
  686. $requestparams = lti_build_registration_request($toolproxy);
  687. $content = lti_post_launch_html($requestparams, $endpoint, false);
  688. echo $content;
  689. }
  690. /**
  691. * Gets the parameters for the regirstration request
  692. *
  693. * @param object $toolproxy Tool Proxy instance object
  694. * @return array Registration request parameters
  695. */
  696. function lti_build_registration_request($toolproxy) {
  697. $key = $toolproxy->guid;
  698. $secret = $toolproxy->secret;
  699. $requestparams = array();
  700. $requestparams['lti_message_type'] = 'ToolProxyRegistrationRequest';
  701. $requestparams['lti_version'] = 'LTI-2p0';
  702. $requestparams['reg_key'] = $key;
  703. $requestparams['reg_password'] = $secret;
  704. $requestparams['reg_url'] = $toolproxy->regurl;
  705. // Add the profile URL.
  706. $profileservice = lti_get_service_by_name('profile');
  707. $profileservice->set_tool_proxy($toolproxy);
  708. $requestparams['tc_profile_url'] = $profileservice->parse_value('$ToolConsumerProfile.url');
  709. // Add the return URL.
  710. $returnurlparams = array('id' => $toolproxy->id, 'sesskey' => sesskey());
  711. $url = new \moodle_url('/mod/lti/externalregistrationreturn.php', $returnurlparams);
  712. $returnurl = $url->out(false);
  713. $requestparams['launch_presentation_return_url'] = $returnurl;
  714. return $requestparams;
  715. }
  716. /** get Organization ID using default if no value provided
  717. * @param object $typeconfig
  718. * @return string
  719. */
  720. function lti_get_organizationid($typeconfig) {
  721. global $CFG;
  722. // Default the organizationid if not specified.
  723. if (empty($typeconfig['organizationid'])) {
  724. if (($typeconfig['organizationid_default'] ?? LTI_DEFAULT_ORGID_SITEHOST) == LTI_DEFAULT_ORGID_SITEHOST) {
  725. $urlparts = parse_url($CFG->wwwroot);
  726. return $urlparts['host'];
  727. } else {
  728. return md5(get_site_identifier());
  729. }
  730. }
  731. return $typeconfig['organizationid'];
  732. }
  733. /**
  734. * Build source ID
  735. *
  736. * @param int $instanceid
  737. * @param int $userid
  738. * @param string $servicesalt
  739. * @param null|int $typeid
  740. * @param null|int $launchid
  741. * @return stdClass
  742. */
  743. function lti_build_sourcedid($instanceid, $userid, $servicesalt, $typeid = null, $launchid = null) {
  744. $data = new \stdClass();
  745. $data->instanceid = $instanceid;
  746. $data->userid = $userid;
  747. $data->typeid = $typeid;
  748. if (!empty($launchid)) {
  749. $data->launchid = $launchid;
  750. } else {
  751. $data->launchid = mt_rand();
  752. }
  753. $json = json_encode($data);
  754. $hash = hash('sha256', $json . $servicesalt, false);
  755. $container = new \stdClass();
  756. $container->data = $data;
  757. $container->hash = $hash;
  758. return $container;
  759. }
  760. /**
  761. * This function builds the request that must be sent to the tool producer
  762. *
  763. * @param object $instance Basic LTI instance object
  764. * @param array $typeconfig Basic LTI tool configuration
  765. * @param object $course Course object
  766. * @param int|null $typeid Basic LTI tool ID
  767. * @param boolean $islti2 True if an LTI 2 tool is being launched
  768. *
  769. * @return array Request details
  770. */
  771. function lti_build_request($instance, $typeconfig, $course, $typeid = null, $islti2 = false) {
  772. global $USER, $CFG;
  773. if (empty($instance->cmid)) {
  774. $instance->cmid = 0;
  775. }
  776. $role = lti_get_ims_role($USER, $instance->cmid, $instance->course, $islti2);
  777. $requestparams = array(
  778. 'user_id' => $USER->id,
  779. 'lis_person_sourcedid' => $USER->idnumber,
  780. 'roles' => $role,
  781. 'context_id' => $course->id,
  782. 'context_label' => trim(html_to_text($course->shortname, 0)),
  783. 'context_title' => trim(html_to_text($course->fullname, 0)),
  784. );
  785. if (!empty($instance->name)) {
  786. $requestparams['resource_link_title'] = trim(html_to_text($instance->name, 0));
  787. }
  788. if (!empty($instance->cmid)) {
  789. $intro = format_module_intro('lti', $instance, $instance->cmid);
  790. $intro = trim(html_to_text($intro, 0, false));
  791. // This may look weird, but this is required for new lines
  792. // so we generate the same OAuth signature as the tool provider.
  793. $intro = str_replace("\n", "\r\n", $intro);
  794. $requestparams['resource_link_description'] = $intro;
  795. }
  796. if (!empty($instance->id)) {
  797. $requestparams['resource_link_id'] = $instance->id;
  798. }
  799. if (!empty($instance->resource_link_id)) {
  800. $requestparams['resource_link_id'] = $instance->resource_link_id;
  801. }
  802. if ($course->format == 'site') {
  803. $requestparams['context_type'] = 'Group';
  804. } else {
  805. $requestparams['context_type'] = 'CourseSection';
  806. $requestparams['lis_course_section_sourcedid'] = $course->idnumber;
  807. }
  808. if (!empty($instance->id) && !empty($instance->servicesalt) && ($islti2 ||
  809. $typeconfig['acceptgrades'] == LTI_SETTING_ALWAYS ||
  810. ($typeconfig['acceptgrades'] == LTI_SETTING_DELEGATE && $instance->instructorchoiceacceptgrades == LTI_SETTING_ALWAYS))
  811. ) {
  812. $placementsecret = $instance->servicesalt;
  813. $sourcedid = json_encode(lti_build_sourcedid($instance->id, $USER->id, $placementsecret, $typeid));
  814. $requestparams['lis_result_sourcedid'] = $sourcedid;
  815. // Add outcome service URL.
  816. $serviceurl = new \moodle_url('/mod/lti/service.php');
  817. $serviceurl = $serviceurl->out();
  818. $forcessl = false;
  819. if (!empty($CFG->mod_lti_forcessl)) {
  820. $forcessl = true;
  821. }
  822. if ((isset($typeconfig['forcessl']) && ($typeconfig['forcessl'] == '1')) or $forcessl) {
  823. $serviceurl = lti_ensure_url_is_https($serviceurl);
  824. }
  825. $requestparams['lis_outcome_service_url'] = $serviceurl;
  826. }
  827. // Send user's name and email data if appropriate.
  828. if ($islti2 || $typeconfig['sendname'] == LTI_SETTING_ALWAYS ||
  829. ($typeconfig['sendname'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendname)
  830. && $instance->instructorchoicesendname == LTI_SETTING_ALWAYS)
  831. ) {
  832. $requestparams['lis_person_name_given'] = $USER->firstname;
  833. $requestparams['lis_person_name_family'] = $USER->lastname;
  834. $requestparams['lis_person_name_full'] = fullname($USER);
  835. $requestparams['ext_user_username'] = $USER->username;
  836. }
  837. if ($islti2 || $typeconfig['sendemailaddr'] == LTI_SETTING_ALWAYS ||
  838. ($typeconfig['sendemailaddr'] == LTI_SETTING_DELEGATE && isset($instance->instructorchoicesendemailaddr)
  839. && $instance->instructorchoicesendemailaddr == LTI_SETTING_ALWAYS)
  840. ) {
  841. $requestparams['lis_person_contact_email_primary'] = $USER->email;
  842. }
  843. return $requestparams;
  844. }
  845. /**
  846. * This function builds the request that must be sent to an LTI 2 tool provider
  847. *
  848. * @param object $tool Basic LTI tool object
  849. * @param array $params Custom launch parameters
  850. *
  851. * @return array Request details
  852. */
  853. function lti_build_request_lti2($tool, $params) {
  854. $requestparams = array();
  855. $capabilities = lti_get_capabilities();
  856. $enabledcapabilities = explode("\n", $tool->enabledcapability);
  857. foreach ($enabledcapabilities as $capability) {
  858. if (array_key_exists($capability, $capabilities)) {
  859. $val = $capabilities[$capability];
  860. if ($val && (substr($val, 0, 1) != '$')) {
  861. if (isset($params[$val])) {
  862. $requestparams[$capabilities[$capability]] = $params[$capabilities[$capability]];
  863. }
  864. }
  865. }
  866. }
  867. return $requestparams;
  868. }
  869. /**
  870. * This function builds the standard parameters for an LTI 1 or 2 request that must be sent to the tool producer
  871. *
  872. * @param stdClass $instance Basic LTI instance object
  873. * @param string $orgid Organisation ID
  874. * @param boolean $islti2 True if an LTI 2 tool is being launched
  875. * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty.
  876. *
  877. * @return array Request details
  878. * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
  879. * @see lti_build_standard_message()
  880. */
  881. function lti_build_standard_request($instance, $orgid, $islti2, $messagetype = 'basic-lti-launch-request') {
  882. if (!$islti2) {
  883. $ltiversion = LTI_VERSION_1;
  884. } else {
  885. $ltiversion = LTI_VERSION_2;
  886. }
  887. return lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype);
  888. }
  889. /**
  890. * This function builds the standard parameters for an LTI message that must be sent to the tool producer
  891. *
  892. * @param stdClass $instance Basic LTI instance object
  893. * @param string $orgid Organisation ID
  894. * @param boolean $ltiversion LTI version to be used for tool messages
  895. * @param string $messagetype The request message type. Defaults to basic-lti-launch-request if empty.
  896. *
  897. * @return array Message parameters
  898. */
  899. function lti_build_standard_message($instance, $orgid, $ltiversion, $messagetype = 'basic-lti-launch-request') {
  900. global $CFG;
  901. $requestparams = array();
  902. if ($instance) {
  903. $requestparams['resource_link_id'] = $instance->id;
  904. if (property_exists($instance, 'resource_link_id') and !empty($instance->resource_link_id)) {
  905. $requestparams['resource_link_id'] = $instance->resource_link_id;
  906. }
  907. }
  908. $requestparams['launch_presentation_locale'] = current_language();
  909. // Make sure we let the tool know what LMS they are being called from.
  910. $requestparams['ext_lms'] = 'moodle-2';
  911. $requestparams['tool_consumer_info_product_family_code'] = 'moodle';
  912. $requestparams['tool_consumer_info_version'] = strval($CFG->version);
  913. // Add oauth_callback to be compliant with the 1.0A spec.
  914. $requestparams['oauth_callback'] = 'about:blank';
  915. $requestparams['lti_version'] = $ltiversion;
  916. $requestparams['lti_message_type'] = $messagetype;
  917. if ($orgid) {
  918. $requestparams["tool_consumer_instance_guid"] = $orgid;
  919. }
  920. if (!empty($CFG->mod_lti_institution_name)) {
  921. $requestparams['tool_consumer_instance_name'] = trim(html_to_text($CFG->mod_lti_institution_name, 0));
  922. } else {
  923. $requestparams['tool_consumer_instance_name'] = get_site()->shortname;
  924. }
  925. $requestparams['tool_consumer_instance_description'] = trim(html_to_text(get_site()->fullname, 0));
  926. return $requestparams;
  927. }
  928. /**
  929. * This function builds the custom parameters
  930. *
  931. * @param object $toolproxy Tool proxy instance object
  932. * @param object $tool Tool instance object
  933. * @param object $instance Tool placement instance object
  934. * @param array $params LTI launch parameters
  935. * @param string $customstr Custom parameters defined for tool
  936. * @param string $instructorcustomstr Custom parameters defined for this placement
  937. * @param boolean $islti2 True if an LTI 2 tool is being launched
  938. *
  939. * @return array Custom parameters
  940. */
  941. function lti_build_custom_parameters($toolproxy, $tool, $instance, $params, $customstr, $instructorcustomstr, $islti2) {
  942. // Concatenate the custom parameters from the administrator and the instructor
  943. // Instructor parameters are only taken into consideration if the administrator
  944. // has given permission.
  945. $custom = array();
  946. if ($customstr) {
  947. $custom = lti_split_custom_parameters($toolproxy, $tool, $params, $customstr, $islti2);
  948. }
  949. if ($instructorcustomstr) {
  950. $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
  951. $instructorcustomstr, $islti2), $custom);
  952. }
  953. if ($islti2) {
  954. $custom = array_merge(lti_split_custom_parameters($toolproxy, $tool, $params,
  955. $tool->parameter, true), $custom);
  956. $settings = lti_get_tool_settings($tool->toolproxyid);
  957. $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
  958. if (!empty($instance->course)) {
  959. $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course);
  960. $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
  961. if (!empty($instance->id)) {
  962. $settings = lti_get_tool_settings($tool->toolproxyid, $instance->course, $instance->id);
  963. $custom = array_merge($custom, lti_get_custom_parameters($toolproxy, $tool, $params, $settings));
  964. }
  965. }
  966. }
  967. return $custom;
  968. }
  969. /**
  970. * Builds a standard LTI Content-Item selection request.
  971. *
  972. * @param int $id The tool type ID.
  973. * @param stdClass $course The course object.
  974. * @param moodle_url $returnurl The return URL in the tool consumer (TC) that the tool provider (TP)
  975. * will use to return the Content-Item message.
  976. * @param string $title The tool's title, if available.
  977. * @param string $text The text to display to represent the content item. This value may be a long description of the content item.
  978. * @param array $mediatypes Array of MIME types types supported by the TC. If empty, the TC will support ltilink by default.
  979. * @param array $presentationtargets Array of ways in which the selected content item(s) can be requested to be opened
  980. * (via the presentationDocumentTarget element for a returned content item).
  981. * If empty, "frame", "iframe", and "window" will be supported by default.
  982. * @param bool $autocreate Indicates whether any content items returned by the TP would be automatically persisted without
  983. * @param bool $multiple Indicates whether the user should be permitted to select more than one item. False by default.
  984. * any option for the user to cancel the operation. False by default.
  985. * @param bool $unsigned Indicates whether the TC is willing to accept an unsigned return message, or not.
  986. * A signed message should always be required when the content item is being created automatically in the
  987. * TC without further interaction from the user. False by default.
  988. * @param bool $canconfirm Flag for can_confirm parameter. False by default.
  989. * @param bool $copyadvice Indicates whether the TC is able and willing to make a local copy of a content item. False by default.
  990. * @param string $nonce
  991. * @return stdClass The object containing the signed request parameters and the URL to the TP's Content-Item selection interface.
  992. * @throws moodle_exception When the LTI tool type does not exist.`
  993. * @throws coding_exception For invalid media type and presentation target parameters.
  994. */
  995. function lti_build_content_item_selection_request($id, $course, moodle_url $returnurl, $title = '', $text = '', $mediatypes = [],
  996. $presentationtargets = [], $autocreate = false, $multiple = true,
  997. $unsigned = false, $canconfirm = false, $copyadvice = false, $nonce = '') {
  998. global $USER;
  999. $tool = lti_get_type($id);
  1000. // Validate parameters.
  1001. if (!$tool) {
  1002. throw new moodle_exception('errortooltypenotfound', 'mod_lti');
  1003. }
  1004. if (!is_array($mediatypes)) {
  1005. throw new coding_exception('The list of accepted media types should be in an array');
  1006. }
  1007. if (!is_array($presentationtargets)) {
  1008. throw new coding_exception('The list of accepted presentation targets should be in an array');
  1009. }
  1010. // Check title. If empty, use the tool's name.
  1011. if (empty($title)) {
  1012. $title = $tool->name;
  1013. }
  1014. $typeconfig = lti_get_type_config($id);
  1015. $key = '';
  1016. $secret = '';
  1017. $islti2 = false;
  1018. $islti13 = false;
  1019. if (isset($tool->toolproxyid)) {
  1020. $islti2 = true;
  1021. $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
  1022. $key = $toolproxy->guid;
  1023. $secret = $toolproxy->secret;
  1024. } else {
  1025. $islti13 = $tool->ltiversion === LTI_VERSION_1P3;
  1026. $toolproxy = null;
  1027. if ($islti13 && !empty($tool->clientid)) {
  1028. $key = $tool->clientid;
  1029. } else if (!$islti13 && !empty($typeconfig['resourcekey'])) {
  1030. $key = $typeconfig['resourcekey'];
  1031. }
  1032. if (!empty($typeconfig['password'])) {
  1033. $secret = $typeconfig['password'];
  1034. }
  1035. }
  1036. $tool->enabledcapability = '';
  1037. if (!empty($typeconfig['enabledcapability_ContentItemSelectionRequest'])) {
  1038. $tool->enabledcapability = $typeconfig['enabledcapability_ContentItemSelectionRequest'];
  1039. }
  1040. $tool->parameter = '';
  1041. if (!empty($typeconfig['parameter_ContentItemSelectionRequest'])) {
  1042. $tool->parameter = $typeconfig['parameter_ContentItemSelectionRequest'];
  1043. }
  1044. // Set the tool URL.
  1045. if (!empty($typeconfig['toolurl_ContentItemSelectionRequest'])) {
  1046. $toolurl = new moodle_url($typeconfig['toolurl_ContentItemSelectionRequest']);
  1047. } else {
  1048. $toolurl = new moodle_url($typeconfig['toolurl']);
  1049. }
  1050. // Check if SSL is forced.
  1051. if (!empty($typeconfig['forcessl'])) {
  1052. // Make sure the tool URL is set to https.
  1053. if (strtolower($toolurl->get_scheme()) === 'http') {
  1054. $toolurl->set_scheme('https');
  1055. }
  1056. // Make sure the return URL is set to https.
  1057. if (strtolower($returnurl->get_scheme()) === 'http') {
  1058. $returnurl->set_scheme('https');
  1059. }
  1060. }
  1061. $toolurlout = $toolurl->out(false);
  1062. // Get base request parameters.
  1063. $instance = new stdClass();
  1064. $instance->course = $course->id;
  1065. $requestparams = lti_build_request($instance, $typeconfig, $course, $id, $islti2);
  1066. // Get LTI2-specific request parameters and merge to the request parameters if applicable.
  1067. if ($islti2) {
  1068. $lti2params = lti_build_request_lti2($tool, $requestparams);
  1069. $requestparams = array_merge($requestparams, $lti2params);
  1070. }
  1071. // Get standard request parameters and merge to the request parameters.
  1072. $orgid = lti_get_organizationid($typeconfig);
  1073. $standardparams = lti_build_standard_message(null, $orgid, $tool->ltiversion, 'ContentItemSelectionRequest');
  1074. $requestparams = array_merge($requestparams, $standardparams);
  1075. // Get custom request parameters and merge to the request parameters.
  1076. $customstr = '';
  1077. if (!empty($typeconfig['customparameters'])) {
  1078. $customstr = $typeconfig['customparameters'];
  1079. }
  1080. $customparams = lti_build_custom_parameters($toolproxy, $tool, $instance, $requestparams, $customstr, '', $islti2);
  1081. $requestparams = array_merge($requestparams, $customparams);
  1082. // Add the parameters configured by the LTI services.
  1083. if ($id && !$islti2) {
  1084. $services = lti_get_services();
  1085. foreach ($services as $service) {
  1086. $serviceparameters = $service->get_launch_parameters('ContentItemSelectionRequest',
  1087. $course->id, $USER->id , $id);
  1088. foreach ($serviceparameters as $paramkey => $paramvalue) {
  1089. $requestparams['custom_' . $paramkey] = lti_parse_custom_parameter($toolproxy, $tool, $requestparams, $paramvalue,
  1090. $islti2);
  1091. }
  1092. }
  1093. }
  1094. // Allow request params to be updated by sub-plugins.
  1095. $plugins = core_component::get_plugin_list('ltisource');
  1096. foreach (array_keys($plugins) as $plugin) {
  1097. $pluginparams = component_callback('ltisource_' . $plugin, 'before_launch', [$instance, $toolurlout, $requestparams], []);
  1098. if (!empty($pluginparams) && is_array($pluginparams)) {
  1099. $requestparams = array_merge($requestparams, $pluginparams);
  1100. }
  1101. }
  1102. if (!$islti13) {
  1103. // Media types. Set to ltilink by default if empty.
  1104. if (empty($mediatypes)) {
  1105. $mediatypes = [
  1106. 'application/vnd.ims.lti.v1.ltilink',
  1107. ];
  1108. }
  1109. $requestparams['accept_media_types'] = implode(',', $mediatypes);
  1110. } else {
  1111. // Only LTI links are currently supported.
  1112. $requestparams['accept_types'] = 'ltiResourceLink';
  1113. }
  1114. // Presentation targets. Supports frame, iframe, window by default if empty.
  1115. if (empty($presentationtargets)) {
  1116. $presentationtargets = [
  1117. 'frame',
  1118. 'iframe',
  1119. 'window',
  1120. ];
  1121. }
  1122. $requestparams['accept_presentation_document_targets'] = implode(',', $presentationtargets);
  1123. // Other request parameters.
  1124. $requestparams['accept_copy_advice'] = $copyadvice === true ? 'true' : 'false';
  1125. $requestparams['accept_multiple'] = $multiple === true ? 'true' : 'false';
  1126. $requestparams['accept_unsigned'] = $unsigned === true ? 'true' : 'false';
  1127. $requestparams['auto_create'] = $autocreate === true ? 'true' : 'false';
  1128. $requestparams['can_confirm'] = $canconfirm === true ? 'true' : 'false';
  1129. $requestparams['content_item_return_url'] = $returnurl->out(false);
  1130. $requestparams['title'] = $title;
  1131. $requestparams['text'] = $text;
  1132. if (!$islti13) {
  1133. $signedparams = lti_sign_parameters($requestparams, $toolurlout, 'POST', $key, $secret);
  1134. } else {
  1135. $signedparams = lti_sign_jwt($requestparams, $toolurlout, $key, $id, $nonce);
  1136. }
  1137. $toolurlparams = $toolurl->params();
  1138. // Strip querystring params in endpoint url from $signedparams to avoid duplication.
  1139. if (!empty($toolurlparams) && !empty($signedparams)) {
  1140. foreach (array_keys($toolurlparams) as $paramname) {
  1141. if (isset($signedparams[$paramname])) {
  1142. unset($signedparams[$paramname]);
  1143. }
  1144. }
  1145. }
  1146. // Check for params that should not be passed. Unset if they are set.
  1147. $unwantedparams = [
  1148. 'resource_link_id',
  1149. 'resource_link_title',
  1150. 'resource_link_description',
  1151. 'launch_presentation_return_url',
  1152. 'lis_result_sourcedid',
  1153. ];
  1154. foreach ($unwantedparams as $param) {
  1155. if (isset($signedparams[$param])) {
  1156. unset($signedparams[$param]);
  1157. }
  1158. }
  1159. // Prepare result object.
  1160. $result = new stdClass();
  1161. $result->params = $signedparams;
  1162. $result->url = $toolurlout;
  1163. return $result;
  1164. }
  1165. /**
  1166. * Verifies the OAuth signature of an incoming message.
  1167. *
  1168. * @param int $typeid The tool type ID.
  1169. * @param string $consumerkey The consumer key.
  1170. * @return stdClass Tool type
  1171. * @throws moodle_exception
  1172. * @throws lti\OAuthException
  1173. */
  1174. function lti_verify_oauth_signature($typeid, $consumerkey) {
  1175. $tool = lti_get_type($typeid);
  1176. // Validate parameters.
  1177. if (!$tool) {
  1178. throw new moodle_exception('errortooltypenotfound', 'mod_lti');
  1179. }
  1180. $typeconfig = lti_get_type_config($typeid);
  1181. if (isset($tool->toolproxyid)) {
  1182. $toolproxy = lti_get_tool_proxy($tool->toolproxyid);
  1183. $key = $toolproxy->guid;
  1184. $secret = $toolproxy->secret;
  1185. } else {
  1186. $toolproxy = null;
  1187. if (!empty($typeconfig['resourcekey'])) {
  1188. $key = $typeconfig['resourcekey'];
  1189. } else {
  1190. $key = '';
  1191. }
  1192. if (!empty($typeconfig['password'])) {
  1193. $secret = $typeconfig['password'];
  1194. } else {
  1195. $secret = '';
  1196. }
  1197. }
  1198. if ($consumerkey !== $key) {
  1199. throw new moodle_exception('errorincorrectconsumerkey', 'mod_lti');
  1200. }
  1201. $store = new lti\TrivialOAuthDataStore();
  1202. $store->add_consumer($key, $secret);
  1203. $server = new lti\OAuthServer($store);
  1204. $method = new lti\OAuthSignatureMethod_HMAC_SHA1();
  1205. $server->add_signature_method($method);
  1206. $request = lti\OAuthRequest::from_request();
  1207. try {
  1208. $server->verify_request($request);
  1209. } catch (lti\OAuthException $e) {
  1210. throw new lti\OAuthException("OAuth signature failed: " . $e->getMessage());
  1211. }
  1212. return $tool;
  1213. }
  1214. /**
  1215. * Verifies the JWT signature using a JWK keyset.
  1216. *
  1217. * @param string $jwtparam JWT parameter value.
  1218. * @param string $keyseturl The tool keyseturl.
  1219. * @param string $clientid The tool client id.
  1220. *
  1221. * @return object The JWT's payload as a PHP object
  1222. * @throws moodle_exception
  1223. * @throws UnexpectedValueException Provided JWT was invalid
  1224. * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
  1225. * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
  1226. * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
  1227. * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
  1228. */
  1229. function lti_verify_with_keyset($jwtparam, $keyseturl, $clientid) {
  1230. // Attempts to retrieve cached keyset.
  1231. $cache = cache::make('mod_lti', 'keyset');
  1232. $keyset = $cache->get($clientid);
  1233. try {
  1234. if (empty($keyset)) {
  1235. throw new moodle_exception('errornocachedkeysetfound', 'mod_lti');
  1236. }
  1237. $keysetarr = json_decode($keyset, true);
  1238. $keys = JWK::parseKeySet($keysetarr);
  1239. $jwt = JWT::decode($jwtparam, $keys, ['RS256']);
  1240. } catch (Exception $e) {
  1241. // Something went wrong, so attempt to update cached keyset and then try again.
  1242. $keyset = file_get_contents($keyseturl);
  1243. $keysetarr = json_decode($keyset, true);
  1244. $keys = JWK::parseKeySet($keysetarr);
  1245. $jwt = JWT::decode($jwtparam, $keys, ['RS256']);
  1246. // If sucessful, updates the cached keyset.
  1247. $cache->set($clientid, $key

Large files files are truncated, but you can click here to view the full file