2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 // This file is part of BasicLTI4Moodle
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.
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.
36 * This file contains unit tests for (some of) lti/locallib.php
40 * @copyright 2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
41 * @copyright 2009 Universitat Politecnica de Catalunya http://www.upc.edu
42 * @author Charles Severance csev@unmich.edu
43 * @author Marc Alier (marc.alier@upc.edu)
44 * @author Jordi Piguillem
45 * @author Nikolas Galanis
46 * @author Chris Scribner
47 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50 defined('MOODLE_INTERNAL') || die;
53 require_once($CFG->dirroot . '/mod/lti/locallib.php');
54 require_once($CFG->dirroot . '/mod/lti/servicelib.php');
60 * @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
61 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
63 class mod_lti_locallib_testcase extends advanced_testcase {
65 public function test_split_custom_parameters() {
66 $this->resetAfterTest();
68 $tool = new stdClass();
69 $tool->enabledcapability = '';
70 $tool->parameter = '';
71 $tool->ltiversion = 'LTI-1p0';
72 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "x=1\ny=2", false),
73 array('custom_x' => '1', 'custom_y' => '2'));
75 // Check params with caps.
76 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "X=1", true),
77 array('custom_x' => '1', 'custom_X' => '1'));
79 // Removed repeat of previous test with a semicolon separator.
81 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 'Review:Chapter=1.2.56', true),
83 'custom_review_chapter' => '1.2.56',
84 'custom_Review:Chapter' => '1.2.56'));
86 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(),
87 'Complex!@#$^*(){}[]KEY=Complex!@#$^*;(){}[]½Value', true),
89 'custom_complex____________key' => 'Complex!@#$^*;(){}[]½Value',
90 'custom_Complex!@#$^*(){}[]KEY' => 'Complex!@#$^*;(){}[]½Value'));
92 // Test custom parameter that returns $USER property.
93 $user = $this->getDataGenerator()->create_user(array('middlename' => 'SOMETHING'));
94 $this->setUser($user);
95 $this->assertEquals(array('custom_x' => '1', 'custom_y' => 'SOMETHING'),
96 lti_split_custom_parameters(null, $tool, array(), "x=1\ny=\$Person.name.middle", false));
100 * This test has been disabled because the test-tool is
101 * being moved and probably it won't work anymore for this.
102 * We should be testing here local stuff only and leave
103 * outside-checks to the conformance tests. MDL-30347
105 public function disabled_test_sign_parameters() {
106 $correct = array ( 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing',
107 'ext_submit' => 'Click Me', 'lti_message_type' => 'basic-lti-launch-request', 'lti_version' => 'LTI-1p0',
108 'oauth_consumer_key' => 'lmsng.school.edu', 'oauth_nonce' => '47458148e33a8f9dafb888c3684cf476',
109 'oauth_signature' => 'qWgaBIezihCbeHgcwUy14tZcyDQ=', 'oauth_signature_method' => 'HMAC-SHA1',
110 'oauth_timestamp' => '1307141660', 'oauth_version' => '1.0', 'resource_link_id' => '123',
111 'resource_link_title' => 'Weekly Blog', 'roles' => 'Learner', 'tool_consumer_instance_guid' => 'lmsng.school.edu',
114 $requestparams = array('resource_link_id' => '123', 'resource_link_title' => 'Weekly Blog', 'user_id' => '789',
115 'roles' => 'Learner', 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing');
117 $parms = lti_sign_parameters($requestparams, 'http://www.imsglobal.org/developer/LTI/tool.php', 'POST',
118 'lmsng.school.edu', 'secret', 'Click Me', 'lmsng.school.edu' /*, $org_desc*/);
119 $this->assertTrue(isset($parms['oauth_nonce']));
120 $this->assertTrue(isset($parms['oauth_signature']));
121 $this->assertTrue(isset($parms['oauth_timestamp']));
123 // Those things that are hard to mock.
124 $correct['oauth_nonce'] = $parms['oauth_nonce'];
125 $correct['oauth_signature'] = $parms['oauth_signature'];
126 $correct['oauth_timestamp'] = $parms['oauth_timestamp'];
129 $this->assertEquals($parms, $correct);
133 * This test has been disabled because, since its creation,
134 * the sourceId generation has changed and surely this is outdated.
135 * Some day these should be replaced by proper tests, but until then
136 * conformance tests say this is working. MDL-30347
138 public function disabled_test_parse_grade_replace_message() {
140 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
142 <imsx_POXRequestHeaderInfo>
143 <imsx_version>V1.0</imsx_version>
144 <imsx_messageIdentifier>999998123</imsx_messageIdentifier>
145 </imsx_POXRequestHeaderInfo>
148 <replaceResultRequest>
152 '{"data":{"instanceid":"2","userid":"2"},"hash":' .
153 '"0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b"}' .
158 <language>en-us</language>
159 <textString>0.92</textString>
163 </replaceResultRequest>
165 </imsx_POXEnvelopeRequest>
168 $parsed = lti_parse_grade_replace_message(new SimpleXMLElement($message));
170 $this->assertEquals($parsed->userid, '2');
171 $this->assertEquals($parsed->instanceid, '2');
172 $this->assertEquals($parsed->sourcedidhash, '0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b');
174 $ltiinstance = (object)array('servicesalt' => '4e5fcc06de1d58.44963230');
176 lti_verify_sourcedid($ltiinstance, $parsed);
179 public function test_lti_ensure_url_is_https() {
180 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('http://moodle.org'));
181 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('moodle.org'));
182 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('https://moodle.org'));
186 * Test lti_get_url_thumbprint against various URLs
188 public function test_lti_get_url_thumbprint() {
189 // Note: trailing and double slash are expected right now. Must evaluate if it must be removed at some point.
190 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://MOODLE.ORG'));
191 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://www.moodle.org'));
192 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('https://www.moodle.org'));
193 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('moodle.org'));
194 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('http://moodle.org/this/is/moodle'));
195 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('https://moodle.org/this/is/moodle'));
196 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle'));
197 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle?'));
198 $this->assertEquals('moodle.org//this/is/moodle?foo=bar', lti_get_url_thumbprint('moodle.org/this/is/moodle?foo=bar'));
202 * Verify that lti_build_request does handle resource_link_id as expected
204 public function test_lti_buid_request_resource_link_id() {
205 $this->resetAfterTest();
207 self::setUser($this->getDataGenerator()->create_user());
208 $course = $this->getDataGenerator()->create_course();
209 $instance = $this->getDataGenerator()->create_module('lti', array(
210 'intro' => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines",
211 'introformat' => FORMAT_HTML,
212 'course' => $course->id,
219 'sendemailaddr' => 2,
220 'customparameters' => '',
223 // Normal call, we expect $instance->id to be used as resource_link_id.
224 $params = lti_build_request($instance, $typeconfig, $course, null);
225 $this->assertSame($instance->id, $params['resource_link_id']);
227 // If there is a resource_link_id set, it gets precedence.
228 $instance->resource_link_id = $instance->id + 99;
229 $params = lti_build_request($instance, $typeconfig, $course, null);
230 $this->assertSame($instance->resource_link_id, $params['resource_link_id']);
232 // With none set, resource_link_id is not set either.
233 unset($instance->id);
234 unset($instance->resource_link_id);
235 $params = lti_build_request($instance, $typeconfig, $course, null);
236 $this->assertArrayNotHasKey('resource_link_id', $params);
240 * Test lti_build_request's resource_link_description and ensure
241 * that the newlines in the description are correct.
243 public function test_lti_build_request_description() {
244 $this->resetAfterTest();
246 self::setUser($this->getDataGenerator()->create_user());
247 $course = $this->getDataGenerator()->create_course();
248 $instance = $this->getDataGenerator()->create_module('lti', array(
249 'intro' => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines",
250 'introformat' => FORMAT_HTML,
251 'course' => $course->id,
258 'sendemailaddr' => 2,
259 'customparameters' => '',
262 $params = lti_build_request($instance, $typeconfig, $course, null);
264 $ncount = substr_count($params['resource_link_description'], "\n");
265 $this->assertGreaterThan(0, $ncount);
267 $rcount = substr_count($params['resource_link_description'], "\r");
268 $this->assertGreaterThan(0, $rcount);
270 $this->assertEquals($ncount, $rcount, 'The number of \n characters should be the same as the number of \r characters');
272 $rncount = substr_count($params['resource_link_description'], "\r\n");
273 $this->assertGreaterThan(0, $rncount);
275 $this->assertEquals($ncount, $rncount, 'All newline characters should be a combination of \r\n');
279 * Tests lti_prepare_type_for_save's handling of the "Force SSL" configuration.
281 public function test_lti_prepare_type_for_save_forcessl() {
282 $type = new stdClass();
283 $config = new stdClass();
285 // Try when the forcessl config property is not set.
286 lti_prepare_type_for_save($type, $config);
287 $this->assertObjectHasAttribute('lti_forcessl', $config);
288 $this->assertEquals(0, $config->lti_forcessl);
289 $this->assertEquals(0, $type->forcessl);
291 // Try when forcessl config property is set.
292 $config->lti_forcessl = 1;
293 lti_prepare_type_for_save($type, $config);
294 $this->assertObjectHasAttribute('lti_forcessl', $config);
295 $this->assertEquals(1, $config->lti_forcessl);
296 $this->assertEquals(1, $type->forcessl);
298 // Try when forcessl config property is set to 0.
299 $config->lti_forcessl = 0;
300 lti_prepare_type_for_save($type, $config);
301 $this->assertObjectHasAttribute('lti_forcessl', $config);
302 $this->assertEquals(0, $config->lti_forcessl);
303 $this->assertEquals(0, $type->forcessl);
307 * Tests lti_load_type_from_cartridge and lti_load_type_if_cartridge
309 public function test_lti_load_type_from_cartridge() {
310 $type = new stdClass();
311 $type->lti_toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml');
313 lti_load_type_if_cartridge($type);
315 $this->assertEquals('Example tool', $type->lti_typename);
316 $this->assertEquals('Example tool description', $type->lti_description);
317 $this->assertEquals('http://www.example.com/lti/provider.php', $type->lti_toolurl);
318 $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $type->lti_icon);
319 $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type->lti_secureicon);
323 * Tests lti_load_tool_from_cartridge and lti_load_tool_if_cartridge
325 public function test_lti_load_tool_from_cartridge() {
326 $lti = new stdClass();
327 $lti->toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml');
329 lti_load_tool_if_cartridge($lti);
331 $this->assertEquals('Example tool', $lti->name);
332 $this->assertEquals('Example tool description', $lti->intro);
333 $this->assertEquals('http://www.example.com/lti/provider.php', $lti->toolurl);
334 $this->assertEquals('https://www.example.com/lti/provider.php', $lti->securetoolurl);
335 $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $lti->icon);
336 $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $lti->secureicon);
340 * Tests for lti_build_content_item_selection_request().
342 public function test_lti_build_content_item_selection_request() {
343 $this->resetAfterTest();
345 $this->setAdminUser();
346 // Create a tool proxy.
347 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
349 // Create a tool type, associated with that proxy.
350 $type = new stdClass();
351 $data = new stdClass();
352 $data->lti_contentitem = true;
353 $type->state = LTI_TOOL_STATE_CONFIGURED;
354 $type->name = "Test tool";
355 $type->description = "Example description";
356 $type->toolproxyid = $proxy->id;
357 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
359 $typeid = lti_add_type($type, $data);
361 $typeconfig = lti_get_type_config($typeid);
363 $course = $this->getDataGenerator()->create_course();
364 $returnurl = new moodle_url('/');
366 // Default parameters.
367 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl);
368 $this->assertNotEmpty($result);
369 $this->assertNotEmpty($result->params);
370 $this->assertNotEmpty($result->url);
371 $params = $result->params;
373 $this->assertEquals($typeconfig['toolurl'], $url);
374 $this->assertEquals('ContentItemSelectionRequest', $params['lti_message_type']);
375 $this->assertEquals(LTI_VERSION_1, $params['lti_version']);
376 $this->assertEquals('application/vnd.ims.lti.v1.ltilink', $params['accept_media_types']);
377 $this->assertEquals('frame,iframe,window', $params['accept_presentation_document_targets']);
378 $this->assertEquals($returnurl->out(false), $params['content_item_return_url']);
379 $this->assertEquals('false', $params['accept_unsigned']);
380 $this->assertEquals('true', $params['accept_multiple']);
381 $this->assertEquals('false', $params['accept_copy_advice']);
382 $this->assertEquals('false', $params['auto_create']);
383 $this->assertEquals($type->name, $params['title']);
384 $this->assertFalse(isset($params['resource_link_id']));
385 $this->assertFalse(isset($params['resource_link_title']));
386 $this->assertFalse(isset($params['resource_link_description']));
387 $this->assertFalse(isset($params['launch_presentation_return_url']));
388 $this->assertFalse(isset($params['lis_result_sourcedid']));
389 $this->assertEquals($params['tool_consumer_instance_guid'], 'www.example.com');
391 // Custom parameters.
392 $title = 'My custom title';
393 $text = 'This is the tool description';
394 $mediatypes = ['image/*', 'video/*'];
395 $targets = ['embed', 'iframe'];
396 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets,
397 true, true, true, true, true);
398 $this->assertNotEmpty($result);
399 $this->assertNotEmpty($result->params);
400 $this->assertNotEmpty($result->url);
401 $params = $result->params;
402 $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']);
403 $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']);
404 $this->assertEquals('true', $params['accept_unsigned']);
405 $this->assertEquals('true', $params['accept_multiple']);
406 $this->assertEquals('true', $params['accept_copy_advice']);
407 $this->assertEquals('true', $params['auto_create']);
408 $this->assertEquals($title, $params['title']);
409 $this->assertEquals($text, $params['text']);
411 // Invalid flag values.
412 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets,
413 'aa', -1, 0, 1, 0xabc);
414 $this->assertNotEmpty($result);
415 $this->assertNotEmpty($result->params);
416 $this->assertNotEmpty($result->url);
417 $params = $result->params;
418 $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']);
419 $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']);
420 $this->assertEquals('false', $params['accept_unsigned']);
421 $this->assertEquals('false', $params['accept_multiple']);
422 $this->assertEquals('false', $params['accept_copy_advice']);
423 $this->assertEquals('false', $params['auto_create']);
424 $this->assertEquals($title, $params['title']);
425 $this->assertEquals($text, $params['text']);
429 * Test for lti_build_content_item_selection_request() with nonexistent tool type ID parameter.
431 public function test_lti_build_content_item_selection_request_invalid_tooltype() {
432 $this->resetAfterTest();
434 $this->setAdminUser();
435 $course = $this->getDataGenerator()->create_course();
436 $returnurl = new moodle_url('/');
438 // Should throw Exception on non-existent tool type.
439 $this->expectException('moodle_exception');
440 lti_build_content_item_selection_request(1, $course, $returnurl);
444 * Test for lti_build_content_item_selection_request() with invalid media types parameter.
446 public function test_lti_build_content_item_selection_request_invalid_mediatypes() {
447 $this->resetAfterTest();
449 $this->setAdminUser();
451 // Create a tool type, associated with that proxy.
452 $type = new stdClass();
453 $data = new stdClass();
454 $data->lti_contentitem = true;
455 $type->state = LTI_TOOL_STATE_CONFIGURED;
456 $type->name = "Test tool";
457 $type->description = "Example description";
458 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
460 $typeid = lti_add_type($type, $data);
461 $course = $this->getDataGenerator()->create_course();
462 $returnurl = new moodle_url('/');
464 // Should throw coding_exception on non-array media types.
465 $mediatypes = 'image/*,video/*';
466 $this->expectException('coding_exception');
467 lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', $mediatypes);
471 * Test for lti_build_content_item_selection_request() with invalid presentation targets parameter.
473 public function test_lti_build_content_item_selection_request_invalid_presentationtargets() {
474 $this->resetAfterTest();
476 $this->setAdminUser();
478 // Create a tool type, associated with that proxy.
479 $type = new stdClass();
480 $data = new stdClass();
481 $data->lti_contentitem = true;
482 $type->state = LTI_TOOL_STATE_CONFIGURED;
483 $type->name = "Test tool";
484 $type->description = "Example description";
485 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
487 $typeid = lti_add_type($type, $data);
488 $course = $this->getDataGenerator()->create_course();
489 $returnurl = new moodle_url('/');
491 // Should throw coding_exception on non-array presentation targets.
492 $targets = 'frame,iframe';
493 $this->expectException('coding_exception');
494 lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', [], $targets);
498 * Provider for test_lti_get_best_tool_by_url.
500 * @return array of [urlToTest, expectedTool, allTools]
502 public function lti_get_best_tool_by_url_provider() {
506 'baseurl' => 'https://example.com/i/am/?where=here',
507 'tooldomain' => 'example.com',
508 'state' => LTI_TOOL_STATE_CONFIGURED,
513 'baseurl' => 'https://example.com/i/am/?where=there',
514 'tooldomain' => 'example.com',
515 'state' => LTI_TOOL_STATE_CONFIGURED,
519 'name' => 'Not here',
520 'baseurl' => 'https://example.com/i/am/?where=not/here',
521 'tooldomain' => 'example.com',
522 'state' => LTI_TOOL_STATE_CONFIGURED,
527 'baseurl' => 'https://example.com/i/am/',
528 'tooldomain' => 'example.com',
529 'state' => LTI_TOOL_STATE_CONFIGURED,
534 'baseurl' => 'https://example.com/i/was',
535 'tooldomain' => 'example.com',
536 'state' => LTI_TOOL_STATE_CONFIGURED,
541 'baseurl' => 'https://badexample.com/i/am/?where=here',
542 'tooldomain' => 'badexample.com',
543 'state' => LTI_TOOL_STATE_CONFIGURED,
550 'url' => $tools[0]->baseurl,
551 'expected' => $tools[0],
554 'url' => $tools[1]->baseurl,
555 'expected' => $tools[1],
558 'url' => $tools[2]->baseurl,
559 'expected' => $tools[2],
562 'url' => $tools[3]->baseurl,
563 'expected' => $tools[3],
566 'url' => $tools[4]->baseurl,
567 'expected' => $tools[4],
570 'url' => $tools[5]->baseurl,
571 'expected' => $tools[5],
574 'url' => 'https://nomatch.com/i/am/',
578 'url' => 'https://example.com',
582 'url' => 'https://example.com/i/am/?where=unknown',
583 'expected' => $tools[3]
587 // Construct the final array as required by the provider API. Each row
588 // of the array contains the URL to test, the expected tool, and
589 // the complete list of tools.
590 return array_map(function($data) use ($tools) {
591 return [$data['url'], $data['expected'], $tools];
596 * Test lti_get_best_tool_by_url.
598 * @dataProvider lti_get_best_tool_by_url_provider
599 * @param string $url The URL to test.
600 * @param object $expected The expected tool matching the URL.
601 * @param array $tools The pool of tools to match the URL with.
603 public function test_lti_get_best_tool_by_url($url, $expected, $tools) {
604 $actual = lti_get_best_tool_by_url($url, $tools, null);
605 $this->assertSame($expected, $actual);
609 * Test lti_get_jwt_message_type_mapping().
611 public function test_lti_get_jwt_message_type_mapping() {
613 'basic-lti-launch-request' => 'LtiResourceLinkRequest',
614 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest',
615 'LtiDeepLinkingResponse' => 'ContentItemSelection',
618 $this->assertEquals($mapping, lti_get_jwt_message_type_mapping());
622 * Test lti_get_jwt_claim_mapping()
624 public function test_lti_get_jwt_claim_mapping() {
626 'accept_copy_advice' => [
628 'group' => 'deep_linking_settings',
629 'claim' => 'accept_copy_advice',
633 'accept_media_types' => [
635 'group' => 'deep_linking_settings',
636 'claim' => 'accept_media_types',
639 'accept_multiple' => [
641 'group' => 'deep_linking_settings',
642 'claim' => 'accept_multiple',
646 'accept_presentation_document_targets' => [
648 'group' => 'deep_linking_settings',
649 'claim' => 'accept_presentation_document_targets',
654 'group' => 'deep_linking_settings',
655 'claim' => 'accept_types',
658 'accept_unsigned' => [
660 'group' => 'deep_linking_settings',
661 'claim' => 'accept_unsigned',
667 'group' => 'deep_linking_settings',
668 'claim' => 'auto_create',
674 'group' => 'deep_linking_settings',
675 'claim' => 'can_confirm',
679 'content_item_return_url' => [
681 'group' => 'deep_linking_settings',
682 'claim' => 'deep_link_return_url',
688 'claim' => 'content_items',
693 'group' => 'deep_linking_settings',
699 'group' => 'deep_linking_settings',
705 'group' => 'deep_linking_settings',
724 'claim' => 'errormsg',
730 'claim' => 'errorlog',
735 'group' => 'context',
741 'group' => 'context',
747 'group' => 'context',
753 'group' => 'context',
757 'lis_course_offering_sourcedid' => [
760 'claim' => 'course_offering_sourcedid',
763 'lis_course_section_sourcedid' => [
766 'claim' => 'course_section_sourcedid',
769 'launch_presentation_css_url' => [
771 'group' => 'launch_presentation',
772 'claim' => 'css_url',
775 'launch_presentation_document_target' => [
777 'group' => 'launch_presentation',
778 'claim' => 'document_target',
781 'launch_presentation_height' => [
783 'group' => 'launch_presentation',
787 'launch_presentation_locale' => [
789 'group' => 'launch_presentation',
793 'launch_presentation_return_url' => [
795 'group' => 'launch_presentation',
796 'claim' => 'return_url',
799 'launch_presentation_width' => [
801 'group' => 'launch_presentation',
805 'lis_person_contact_email_primary' => [
811 'lis_person_name_family' => [
814 'claim' => 'family_name',
817 'lis_person_name_full' => [
823 'lis_person_name_given' => [
826 'claim' => 'given_name',
829 'lis_person_sourcedid' => [
832 'claim' => 'person_sourcedid',
844 'claim' => 'picture',
853 'role_scope_mentor' => [
856 'claim' => 'role_scope_mentor',
862 'claim' => 'deployment_id',
865 'lti_message_type' => [
868 'claim' => 'message_type',
874 'claim' => 'version',
877 'resource_link_description' => [
879 'group' => 'resource_link',
880 'claim' => 'description',
883 'resource_link_id' => [
885 'group' => 'resource_link',
889 'resource_link_title' => [
891 'group' => 'resource_link',
895 'tool_consumer_info_product_family_code' => [
897 'group' => 'tool_platform',
898 'claim' => 'product_family_code',
901 'tool_consumer_info_version' => [
903 'group' => 'tool_platform',
904 'claim' => 'version',
907 'tool_consumer_instance_contact_email' => [
909 'group' => 'tool_platform',
910 'claim' => 'contact_email',
913 'tool_consumer_instance_description' => [
915 'group' => 'tool_platform',
916 'claim' => 'description',
919 'tool_consumer_instance_guid' => [
921 'group' => 'tool_platform',
925 'tool_consumer_instance_name' => [
927 'group' => 'tool_platform',
931 'tool_consumer_instance_url' => [
933 'group' => 'tool_platform',
937 'custom_context_memberships_url' => [
939 'group' => 'namesroleservice',
940 'claim' => 'context_memberships_url',
943 'custom_context_memberships_versions' => [
945 'group' => 'namesroleservice',
946 'claim' => 'service_versions',
949 'custom_gradebookservices_scope' => [
951 'group' => 'endpoint',
955 'custom_lineitems_url' => [
957 'group' => 'endpoint',
958 'claim' => 'lineitems',
961 'custom_lineitem_url' => [
963 'group' => 'endpoint',
964 'claim' => 'lineitem',
967 'custom_results_url' => [
969 'group' => 'endpoint',
970 'claim' => 'results',
973 'custom_result_url' => [
975 'group' => 'endpoint',
979 'custom_scores_url' => [
981 'group' => 'endpoint',
985 'custom_score_url' => [
987 'group' => 'endpoint',
991 'lis_outcome_service_url' => [
993 'group' => 'basicoutcome',
994 'claim' => 'lis_outcome_service_url',
997 'lis_result_sourcedid' => [
999 'group' => 'basicoutcome',
1000 'claim' => 'lis_result_sourcedid',
1005 $this->assertEquals($mapping, lti_get_jwt_claim_mapping());
1009 * Test lti_build_standard_message().
1011 public function test_lti_build_standard_message_institution_name_set() {
1014 $this->resetAfterTest();
1016 $CFG->mod_lti_institution_name = 'some institution name lols';
1018 $course = $this->getDataGenerator()->create_course();
1019 $instance = $this->getDataGenerator()->create_module('lti',
1021 'course' => $course->id,
1025 $message = lti_build_standard_message($instance, '2', LTI_VERSION_1);
1027 $this->assertEquals('moodle-2', $message['ext_lms']);
1028 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1029 $this->assertEquals(LTI_VERSION_1, $message['lti_version']);
1030 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1031 $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1032 $this->assertEquals('some institution name lols', $message['tool_consumer_instance_name']);
1033 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1037 * Test lti_build_standard_message().
1039 public function test_lti_build_standard_message_institution_name_not_set() {
1040 $this->resetAfterTest();
1042 $course = $this->getDataGenerator()->create_course();
1043 $instance = $this->getDataGenerator()->create_module('lti',
1045 'course' => $course->id,
1049 $message = lti_build_standard_message($instance, '2', LTI_VERSION_2);
1051 $this->assertEquals('moodle-2', $message['ext_lms']);
1052 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1053 $this->assertEquals(LTI_VERSION_2, $message['lti_version']);
1054 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1055 $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1056 $this->assertEquals('phpunit', $message['tool_consumer_instance_name']);
1057 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1061 * Test lti_verify_jwt_signature().
1063 public function test_lti_verify_jwt_signature() {
1064 $this->resetAfterTest();
1066 $this->setAdminUser();
1068 // Create a tool type, associated with that proxy.
1069 $type = new stdClass();
1070 $type->state = LTI_TOOL_STATE_CONFIGURED;
1071 $type->name = "Test tool";
1072 $type->description = "Example description";
1073 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1075 $config = new stdClass();
1076 $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1077 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1078 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1079 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1080 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1081 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1082 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1084 -----END PUBLIC KEY-----';
1086 $config->lti_keytype = LTI_RSA_KEY;
1088 $typeid = lti_add_type($type, $config);
1090 lti_verify_jwt_signature($typeid, '', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g' .
1091 'RG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOs' .
1092 'S_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMgu' .
1093 'EIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iY' .
1094 'v7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA');
1098 * Test lti_verify_jwt_signature_jwk().
1100 public function test_lti_verify_jwt_signature_jwk() {
1101 $this->resetAfterTest();
1103 $this->setAdminUser();
1105 // Create a tool type, associated with that proxy.
1106 $type = new stdClass();
1107 $type->state = LTI_TOOL_STATE_CONFIGURED;
1108 $type->name = "Test tool";
1109 $type->description = "Example description";
1110 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1112 $config = new stdClass();
1113 $config->lti_publickeyset = dirname(__FILE__) . '/fixtures/test_keyset';
1115 $config->lti_keytype = LTI_JWK_KEYSET;
1117 $typeid = lti_add_type($type, $config);
1119 $jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU3YzExNzdkMmQ1M2EwMjFjNzM';
1120 $jwt .= '3NTY0OTFjMTM3YjE3In0.eyJpc3MiOiJnclJvbkd3RTd1WjRwZ28iLCJzdWIiOiJnclJvb';
1121 $jwt .= 'kd3RTd1WjRwZ28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0L21vb2RsZS9tb2QvbHRpL3R';
1122 $jwt .= 'va2VuLnBocCIsImp0aSI6IjFlMUJPVEczVFJjbFdUem00dERsMGc9PSIsImlhdCI6MTU4M';
1123 $jwt .= 'Dg1NTUwNX0.Lowhc9ovNAXRb2rkAnv1oozDXlRD54Mz2JS1i8Zx4yGWQzmXzam-La19_g0';
1124 $jwt .= 'CTnwlKM6gxaInnRKFRAcwhJVcWec389liLAjMbna6d6iTWYTZr7q_4BIe3CT_oTMWASGta';
1125 $jwt .= 'Paaq53ch1rO4YdueEtmtd1K47ibo4Lhu1jmP_icc3lxjfnqiv4vIYdy7W2JQEzpk1ImuQr';
1126 $jwt .= 'AlO1xR3fZ6bgcJhVIaw5xoaZD3ZgEjuZOQXMkywv1bL-mL17RX336CzHd8rYZg82QXrBzb';
1127 $jwt .= 'NWzAlaZxv9VSug8t6mORvM6TkYYWjqEBKemgkD5rNh1BHrPcjWP7vy2Jz7YMjLsmuvDuLK';
1128 $jwt .= '_PHYIKL--s4gcXWoYmOu1vj-SgoPczTJPoiBD35hAKqVHy5ggHaYHBy95_bbcFd8H1smHw';
1129 $jwt .= 'pejrAFj1QAwGyTISLzUm08oq7Ak0tSxRKKXw4lpZAka1MmYxO3tJ_3-MXw6Bwz12bNgitJ';
1130 $jwt .= 'lQd6n3kkGLCJAmANeRkPsH6eZVwF0n2cjh2O1JAwyNcMD2vs4I8ftM1EqqoE2M3r6kt3AC';
1131 $jwt .= 'EscmqzizI3j80USBCLUUb1UTsfJb2g7oyApJAp-13Q3InR3QyvWO8unG5VraFE7IL5I28h';
1132 $jwt .= 'MkQAHuCI90DFmXB4leflAu7wNlIK_U8xkGl8X8Mnv6MWgg94Ki8jgIq_kA85JAqI';
1134 lti_verify_jwt_signature($typeid, '', $jwt);
1138 * Test lti_verify_jwt_signature().
1140 public function test_lti_verify_jwt_signature_with_lti2() {
1141 $this->resetAfterTest();
1143 $this->setAdminUser();
1145 // Create a tool proxy.
1146 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
1148 // Create a tool type, associated with that proxy.
1149 $type = new stdClass();
1150 $type->state = LTI_TOOL_STATE_CONFIGURED;
1151 $type->name = "Test tool";
1152 $type->description = "Example description";
1153 $type->toolproxyid = $proxy->id;
1154 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1156 $data = new stdClass();
1157 $data->lti_contentitem = true;
1159 $typeid = lti_add_type($type, $data);
1161 $this->expectExceptionMessage('JWT security not supported with LTI 2');
1162 lti_verify_jwt_signature($typeid, '', '');
1166 * Test lti_verify_jwt_signature().
1168 public function test_lti_verify_jwt_signature_no_consumer_key() {
1169 $this->resetAfterTest();
1171 $this->setAdminUser();
1173 // Create a tool type, associated with that proxy.
1174 $type = new stdClass();
1175 $type->state = LTI_TOOL_STATE_CONFIGURED;
1176 $type->name = "Test tool";
1177 $type->description = "Example description";
1178 $type->clientid = 'consumerkey';
1179 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1181 $config = new stdClass();
1182 $typeid = lti_add_type($type, $config);
1184 $this->expectExceptionMessage(get_string('errorincorrectconsumerkey', 'mod_lti'));
1185 lti_verify_jwt_signature($typeid, '', '');
1189 * Test lti_verify_jwt_signature().
1191 public function test_lti_verify_jwt_signature_no_public_key() {
1192 $this->resetAfterTest();
1193 $this->setAdminUser();
1195 // Create a tool type, associated with that proxy.
1196 $type = new stdClass();
1197 $type->state = LTI_TOOL_STATE_CONFIGURED;
1198 $type->name = "Test tool";
1199 $type->description = "Example description";
1200 $type->clientid = 'consumerkey';
1201 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1203 $config = new stdClass();
1204 $config->lti_keytype = LTI_RSA_KEY;
1205 $typeid = lti_add_type($type, $config);
1207 $this->expectExceptionMessage('No public key configured');
1208 lti_verify_jwt_signature($typeid, 'consumerkey', '');
1212 * Test lti_convert_content_items().
1214 public function test_lti_convert_content_items() {
1217 'type' => 'ltiResourceLink',
1218 'url' => 'http://example.com/messages/launch',
1219 'title' => 'Test title',
1220 'text' => 'Test text',
1224 'type' => 'ltiResourceLink',
1225 'url' => 'http://example.com/messages/launch2',
1226 'title' => 'Test title2',
1227 'text' => 'Test text2',
1235 'type' => 'ltiResourceLink',
1236 'url' => 'http://example.com/messages/launch3',
1237 'title' => 'Test title3',
1238 'text' => 'Test text3',
1240 'targetName' => 'test-win',
1245 $contentitems = json_encode($contentitems);
1247 $json = lti_convert_content_items($contentitems);
1249 $jsondecode = json_decode($json);
1251 $strcontext = '@context';
1252 $strgraph = '@graph';
1255 $objgraph = new stdClass();
1256 $objgraph->url = 'http://example.com/messages/launch';
1257 $objgraph->title = 'Test title';
1258 $objgraph->text = 'Test text';
1259 $objgraph->placementAdvice = new stdClass();
1260 $objgraph->placementAdvice->presentationDocumentTarget = 'iframe';
1261 $objgraph->{$strtype} = 'LtiLinkItem';
1262 $objgraph->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1264 $objgraph2 = new stdClass();
1265 $objgraph2->url = 'http://example.com/messages/launch2';
1266 $objgraph2->title = 'Test title2';
1267 $objgraph2->text = 'Test text2';
1268 $objgraph2->placementAdvice = new stdClass();
1269 $objgraph2->placementAdvice->presentationDocumentTarget = 'iframe';
1270 $objgraph2->placementAdvice->displayHeight = 200;
1271 $objgraph2->placementAdvice->displayWidth = 300;
1272 $objgraph2->{$strtype} = 'LtiLinkItem';
1273 $objgraph2->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1275 $objgraph3 = new stdClass();
1276 $objgraph3->url = 'http://example.com/messages/launch3';
1277 $objgraph3->title = 'Test title3';
1278 $objgraph3->text = 'Test text3';
1279 $objgraph3->placementAdvice = new stdClass();
1280 $objgraph3->placementAdvice->presentationDocumentTarget = 'window';
1281 $objgraph3->placementAdvice->displayHeight = 400;
1282 $objgraph3->placementAdvice->windowTarget = 'test-win';
1283 $objgraph3->{$strtype} = 'LtiLinkItem';
1284 $objgraph3->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1286 $expected = new stdClass();
1287 $expected->{$strcontext} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
1288 $expected->{$strgraph} = [];
1289 $expected->{$strgraph}[] = $objgraph;
1290 $expected->{$strgraph}[] = $objgraph2;
1291 $expected->{$strgraph}[] = $objgraph3;
1293 $this->assertEquals($expected, $jsondecode);
1297 * Test adding a single gradable item through content item.
1299 public function test_lti_tool_configuration_from_content_item_single_gradable() {
1300 $this->resetAfterTest();
1301 $this->setAdminUser();
1303 $type = new stdClass();
1304 $type->name = "Test tool";
1305 $type->baseurl = "http://example.com";
1306 $config = new stdClass();
1307 $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
1308 $typeid = lti_add_type($type, $config);
1310 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1313 'type' => 'ltiResourceLink',
1314 'url' => 'http://example.com/messages/launch',
1315 'title' => 'Test title',
1317 'resourceId' => 'r12345',
1319 'scoreMaximum' => 10.0
1323 $contentitemsjson13 = json_encode($contentitems);
1324 $json11 = lti_convert_content_items($contentitemsjson13);
1326 $config = lti_tool_configuration_from_content_item($typeid,
1327 'ContentItemSelection',
1332 $this->assertEquals($contentitems[0]['url'], $config->toolurl);
1333 $this->assertEquals(LTI_SETTING_ALWAYS, $config->instructorchoiceacceptgrades);
1334 $this->assertEquals($contentitems[0]['lineItem']['tag'], $config->lineitemtag);
1335 $this->assertEquals($contentitems[0]['lineItem']['resourceId'], $config->lineitemresourceid);
1336 $this->assertEquals($contentitems[0]['lineItem']['scoreMaximum'], $config->grade_modgrade_point);
1340 * Test adding multiple gradable items through content item.
1342 public function test_lti_tool_configuration_from_content_item_multiple() {
1343 $this->resetAfterTest();
1344 $this->setAdminUser();
1346 $type = new stdClass();
1347 $type->name = "Test tool";
1348 $type->baseurl = "http://example.com";
1349 $config = new stdClass();
1350 $config->lti_acceptgrades = LTI_SETTING_DELEGATE;
1351 $typeid = lti_add_type($type, $config);
1353 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1356 'type' => 'ltiResourceLink',
1357 'url' => 'http://example.com/messages/launch',
1358 'title' => 'Test title',
1359 'text' => 'Test text',
1361 'url' => 'http://lti.example.com/image.jpg',
1367 'type' => 'ltiResourceLink',
1368 'url' => 'http://example.com/messages/launchgraded',
1369 'title' => 'Test Graded',
1371 'resourceId' => 'r12345',
1373 'scoreMaximum' => 10.0
1377 $contentitemsjson13 = json_encode($contentitems);
1378 $json11 = lti_convert_content_items($contentitemsjson13);
1380 $config = lti_tool_configuration_from_content_item($typeid,
1381 'ContentItemSelection',
1385 $this->assertNotNull($config->multiple);
1386 $this->assertEquals(2, count( $config->multiple ));
1387 $this->assertEquals($contentitems[0]['title'], $config->multiple[0]->name);
1388 $this->assertEquals($contentitems[0]['url'], $config->multiple[0]->toolurl);
1389 $this->assertEquals(LTI_SETTING_NEVER, $config->multiple[0]->instructorchoiceacceptgrades);
1390 $this->assertEquals($contentitems[1]['url'], $config->multiple[1]->toolurl);
1391 $this->assertEquals(LTI_SETTING_ALWAYS, $config->multiple[1]->instructorchoiceacceptgrades);
1392 $this->assertEquals($contentitems[1]['lineItem']['tag'], $config->multiple[1]->lineitemtag);
1393 $this->assertEquals($contentitems[1]['lineItem']['resourceId'], $config->multiple[1]->lineitemresourceid);
1394 $this->assertEquals($contentitems[1]['lineItem']['scoreMaximum'], $config->multiple[1]->grade_modgrade_point);
1398 * Test adding a single non gradable item through content item.
1400 public function test_lti_tool_configuration_from_content_item_single() {
1401 $this->resetAfterTest();
1402 $this->setAdminUser();
1404 $type = new stdClass();
1405 $type->name = "Test tool";
1406 $type->baseurl = "http://example.com";
1407 $config = new stdClass();
1408 $typeid = lti_add_type($type, $config);
1410 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1413 'type' => 'ltiResourceLink',
1414 'url' => 'http://example.com/messages/launch',
1415 'title' => 'Test title',
1416 'text' => 'Test text',
1418 'url' => 'http://lti.example.com/image.jpg',
1423 $contentitemsjson13 = json_encode($contentitems);
1424 $json11 = lti_convert_content_items($contentitemsjson13);
1426 $config = lti_tool_configuration_from_content_item($typeid,
1427 'ContentItemSelection',
1431 $this->assertEquals($contentitems[0]['title'], $config->name);
1432 $this->assertEquals($contentitems[0]['text'], $config->introeditor['text']);
1433 $this->assertEquals($contentitems[0]['url'], $config->toolurl);
1434 $this->assertEquals($contentitems[0]['icon']['url'], $config->icon);
1435 $this->assertEquals(LTI_SETTING_NEVER, $config->instructorchoiceacceptgrades);
1440 * Test lti_sign_jwt().
1442 public function test_lti_sign_jwt() {
1443 $this->resetAfterTest();
1445 $this->setAdminUser();
1447 // Create a tool type, associated with that proxy.
1448 $type = new stdClass();
1449 $type->state = LTI_TOOL_STATE_CONFIGURED;
1450 $type->name = "Test tool";
1451 $type->description = "Example description";
1452 $type->clientid = 'consumerkey';
1453 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1455 $config = new stdClass();
1456 $typeid = lti_add_type($type, $config);
1459 $params['roles'] = 'urn:lti:role:ims/lis/testrole,' .
1460 'urn:lti:instrole:ims/lis/testinstrole,' .
1461 'urn:lti:sysrole:ims/lis/testsysrole,' .
1463 $params['accept_copy_advice'] = [
1465 'group' => 'deep_linking_settings',
1466 'claim' => 'accept_copy_advice',
1469 $params['lis_result_sourcedid'] = [
1471 'group' => 'basicoutcomesservice',
1472 'claim' => 'lis_result_sourcedid',
1475 $endpoint = 'https://www.example.com/moodle';
1476 $oauthconsumerkey = 'consumerkey';
1479 $jwt = lti_sign_jwt($params, $endpoint, $oauthconsumerkey, $typeid, $nonce);
1481 $this->assertArrayHasKey('id_token', $jwt);
1482 $this->assertNotEmpty($jwt['id_token']);
1486 * Test lti_convert_from_jwt()
1488 public function test_lti_convert_from_jwt() {
1489 $this->resetAfterTest();
1491 $this->setAdminUser();
1493 // Create a tool type, associated with that proxy.
1494 $type = new stdClass();
1495 $type->state = LTI_TOOL_STATE_CONFIGURED;
1496 $type->name = "Test tool";
1497 $type->description = "Example description";
1498 $type->clientid = 'sso.example.com';
1499 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1501 $config = new stdClass();
1502 $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1503 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1504 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1505 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1506 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1507 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1508 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1510 -----END PUBLIC KEY-----';
1511 $config->lti_keytype = LTI_RSA_KEY;
1513 $typeid = lti_add_type($type, $config);
1515 $params = lti_convert_from_jwt($typeid, 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib' .
1516 'mFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic3NvLmV4YW1wbGUuY29tIn0.XURVvEb5ueAvFsn-S9EB' .
1517 'BSfKbsgUzfRQqmJ6evlrYdx7sXWoZXw1nYjaLTg-mawvBr7MVvrdG9qh6oN8OfkQ7bfMwiz4tjBMJ4B4q_sig5BDYIKwMNjZL5GGCBs89FQrgqZBhxw' .
1518 '3exTjPBEn69__w40o0AhCsBohPMh0ZsAyHug5dhm8vIuOP667repUJzM8uKCD6L4bEL6vQE8EwU6WQOmfJ2SDmRs-1pFkiaFd6hmPn6AVX7ETtzQmlT' .
1519 'X-nXe9weQjU1lH4AQG2Yfnn-7lS94bt6E76Zt-XndP3IY7W48EpnRfUK9Ff1fZlomT4MPahdNP1eP8gT2iMz7vYpCfmA');
1521 $this->assertEquals('sso.example.com', $params['oauth_consumer_key']);
1522 $this->assertEquals('John Doe', $params['lis_person_name_full']);
1526 * Test lti_get_permitted_service_scopes().
1528 public function test_lti_get_permitted_service_scopes() {
1529 $this->resetAfterTest();
1531 $this->setAdminUser();
1533 // Create a tool type, associated with that proxy.
1534 $type = new stdClass();
1535 $type->state = LTI_TOOL_STATE_CONFIGURED;
1536 $type->name = "Test tool";
1537 $type->description = "Example description";
1538 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1540 $typeconfig = new stdClass();
1541 $typeconfig->lti_acceptgrades = true;
1543 $typeid = lti_add_type($type, $typeconfig);
1545 $tool = lti_get_type($typeid);
1547 $config = lti_get_type_config($typeid);
1548 $permittedscopes = lti_get_permitted_service_scopes($tool, $config);
1551 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome'
1553 $this->assertEquals($expected, $permittedscopes);
1557 * Test get_tool_type_config().
1559 public function test_get_tool_type_config() {
1560 $this->resetAfterTest();
1562 $this->setAdminUser();
1564 // Create a tool type, associated with that proxy.
1565 $type = new stdClass();
1566 $type->state = LTI_TOOL_STATE_CONFIGURED;
1567 $type->name = "Test tool";
1568 $type->description = "Example description";
1569 $type->clientid = "Test client ID";
1570 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1572 $config = new stdClass();
1574 $typeid = lti_add_type($type, $config);
1576 $type = lti_get_type($typeid);
1578 $typeconfig = get_tool_type_config($type);
1580 $this->assertEquals('https://www.example.com/moodle', $typeconfig['platformid']);
1581 $this->assertEquals($type->clientid, $typeconfig['clientid']);
1582 $this->assertEquals($typeid, $typeconfig['deploymentid']);
1583 $this->assertEquals('https://www.example.com/moodle/mod/lti/certs.php', $typeconfig['publickeyseturl']);
1584 $this->assertEquals('https://www.example.com/moodle/mod/lti/token.php', $typeconfig['accesstokenurl']);
1585 $this->assertEquals('https://www.example.com/moodle/mod/lti/auth.php', $typeconfig['authrequesturl']);
1589 * Test lti_new_access_token().
1591 public function test_lti_new_access_token() {
1594 $this->resetAfterTest();
1596 $this->setAdminUser();
1598 // Create a tool type, associated with that proxy.
1599 $type = new stdClass();
1600 $type->state = LTI_TOOL_STATE_CONFIGURED;
1601 $type->name = "Test tool";
1602 $type->description = "Example description";
1603 $type->clientid = "Test client ID";
1604 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1606 $config = new stdClass();
1608 $typeid = lti_add_type($type, $config);
1610 $scopes = ['lti_some_scope', 'lti_another_scope'];
1612 lti_new_access_token($typeid, $scopes);
1614 $token = $DB->get_records('lti_access_tokens');
1615 $this->assertEquals(1, count($token));
1617 $token = reset($token);
1619 $this->assertEquals($typeid, $token->typeid);
1620 $this->assertEquals(json_encode(array_values($scopes)), $token->scope);
1621 $this->assertEquals($token->timecreated + LTI_ACCESS_TOKEN_LIFE, $token->validuntil);
1622 $this->assertNull($token->lastaccess);
1626 * Test lti_build_login_request().
1628 public function test_lti_build_login_request() {
1631 $this->resetAfterTest();
1633 $USER->id = 123456789;
1635 $course = $this->getDataGenerator()->create_course();
1636 $instance = $this->getDataGenerator()->create_module('lti',
1638 'course' => $course->id,
1642 $config = new stdClass();
1643 $config->lti_clientid = 'some-client-id';
1644 $config->typeid = 'some-type-id';
1645 $config->lti_toolurl = 'some-lti-tool-url';
1647 $request = lti_build_login_request($course->id, $instance->id, $instance, $config, 'basic-lti-launch-request');
1649 $this->assertEquals($CFG->wwwroot, $request['iss']);
1650 $this->assertEquals('http://some-lti-tool-url', $request['target_link_uri']);
1651 $this->assertEquals(123456789, $request['login_hint']);
1652 $this->assertEquals($instance->id, $request['lti_message_hint']);
1653 $this->assertEquals('some-client-id', $request['client_id']);
1654 $this->assertEquals('some-type-id', $request['lti_deployment_id']);
1658 * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle).
1660 public function test_lti_get_launch_data_default_organizationid_unset_usehost() {
1662 $this->resetAfterTest();
1663 $this->setAdminUser();
1664 $config = new stdClass();
1665 $config->lti_organizationid = '';
1666 $course = $this->getDataGenerator()->create_course();
1667 $type = $this->create_type($config);
1668 $link = $this->create_instance($type, $course);
1669 $launchdata = lti_get_launch_data($link);
1670 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1674 * Test default org id is set to host when config is usehost.
1676 public function test_lti_get_launch_data_default_organizationid_set_usehost() {
1678 $this->resetAfterTest();
1679 $this->setAdminUser();
1680 $config = new stdClass();
1681 $config->lti_organizationid = '';
1682 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
1683 $course = $this->getDataGenerator()->create_course();
1684 $type = $this->create_type($config);
1685 $link = $this->create_instance($type, $course);
1686 $launchdata = lti_get_launch_data($link);
1687 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1691 * Test default org id is set to site id when config is usesiteid.
1693 public function test_lti_get_launch_data_default_organizationid_set_usesiteid() {
1695 $this->resetAfterTest();
1696 $this->setAdminUser();
1697 $config = new stdClass();
1698 $config->lti_organizationid = '';
1699 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1700 $course = $this->getDataGenerator()->create_course();
1701 $type = $this->create_type($config);
1702 $link = $this->create_instance($type, $course);
1703 $launchdata = lti_get_launch_data($link);
1704 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], md5(get_site_identifier()));
1708 * Test orgid can be overridden in which case default is ignored.
1710 public function test_lti_get_launch_data_default_organizationid_orgid_override() {
1712 $this->resetAfterTest();
1713 $this->setAdminUser();
1714 $config = new stdClass();
1715 $config->lti_organizationid = 'overridden!';
1716 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1717 $course = $this->getDataGenerator()->create_course();
1718 $type = $this->create_type($config);
1719 $link = $this->create_instance($type, $course);
1720 $launchdata = lti_get_launch_data($link);
1721 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'overridden!');
1724 public function test_get_course_history() {
1726 $this->resetAfterTest();
1727 $this->setAdminUser();
1728 $parentparentcourse = $this->getDataGenerator()->create_course();
1729 $parentcourse = $this->getDataGenerator()->create_course();
1730 $parentcourse->originalcourseid = $parentparentcourse->id;
1731 $DB->update_record('course', $parentcourse);
1732 $course = $this->getDataGenerator()->create_course();
1733 $course->originalcourseid = $parentcourse->id;
1734 $DB->update_record('course', $course);
1735 $this->assertEquals(get_course_history($parentparentcourse), []);
1736 $this->assertEquals(get_course_history($parentcourse), [$parentparentcourse->id]);
1737 $this->assertEquals(get_course_history($course), [$parentcourse->id, $parentparentcourse->id]);
1738 $course->originalcourseid = 38903;
1739 $DB->update_record('course', $course);
1740 $this->assertEquals(get_course_history($course), [38903]);
1744 * Create an LTI Tool.
1746 * @param object $config tool config.
1748 * @return object tool.
1750 private function create_type(object $config) {
1751 $type = new stdClass();
1752 $type->state = LTI_TOOL_STATE_CONFIGURED;
1753 $type->name = "Test tool";
1754 $type->description = "Example description";
1755 $type->clientid = "Test client ID";
1756 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1758 $configbase = new stdClass();
1759 $configbase->lti_acceptgrades = LTI_SETTING_NEVER;
1760 $configbase->lti_sendname = LTI_SETTING_NEVER;
1761 $configbase->lti_sendemailaddr = LTI_SETTING_NEVER;
1762 $mergedconfig = (object) array_merge( (array) $configbase, (array) $config);
1763 $typeid = lti_add_type($type, $mergedconfig);
1764 return lti_get_type($typeid);
1768 * Create an LTI Instance for the tool in a given course.
1770 * @param object $type tool for which an instance should be added.
1771 * @param object $course course where the instance should be added.
1773 * @return object instance.
1775 private function create_instance(object $type, object $course) {
1776 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1777 return $generator->create_instance(array('course' => $course->id,
1778 'toolurl' => $type->baseurl,
1779 'typeid' => $type->id