MDL-69240 tool_moodlenet: Clean MoodleNet profile field
[moodle.git] / mod / lti / tests / locallib_test.php
blob1a9800ae803190e7b197bef053a2d4045cd6639a
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.
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.
35 /**
36 * This file contains unit tests for (some of) lti/locallib.php
38 * @package mod_lti
39 * @category phpunit
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;
52 global $CFG;
53 require_once($CFG->dirroot . '/mod/lti/locallib.php');
54 require_once($CFG->dirroot . '/mod/lti/servicelib.php');
56 /**
57 * Local library tests
59 * @package mod_lti
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),
82 array(
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),
88 array(
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));
99 /**
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',
112 'user_id' => '789');
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'];
127 ksort($parms);
128 ksort($correct);
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() {
139 $message = '
140 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
141 <imsx_POXHeader>
142 <imsx_POXRequestHeaderInfo>
143 <imsx_version>V1.0</imsx_version>
144 <imsx_messageIdentifier>999998123</imsx_messageIdentifier>
145 </imsx_POXRequestHeaderInfo>
146 </imsx_POXHeader>
147 <imsx_POXBody>
148 <replaceResultRequest>
149 <resultRecord>
150 <sourcedGUID>
151 <sourcedId>' .
152 '{&quot;data&quot;:{&quot;instanceid&quot;:&quot;2&quot;,&quot;userid&quot;:&quot;2&quot;},&quot;hash&quot;:' .
153 '&quot;0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b&quot;}' .
154 '</sourcedId>
155 </sourcedGUID>
156 <result>
157 <resultScore>
158 <language>en-us</language>
159 <textString>0.92</textString>
160 </resultScore>
161 </result>
162 </resultRecord>
163 </replaceResultRequest>
164 </imsx_POXBody>
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,
215 $typeconfig = array(
216 'acceptgrades' => 1,
217 'forcessl' => 0,
218 'sendname' => 2,
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,
254 $typeconfig = array(
255 'acceptgrades' => 1,
256 'forcessl' => 0,
257 'sendname' => 2,
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;
372 $url = $result->url;
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('false', $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() {
503 $tools = [
504 (object) [
505 'name' => 'Here',
506 'baseurl' => 'https://example.com/i/am/?where=here',
507 'tooldomain' => 'example.com',
508 'state' => LTI_TOOL_STATE_CONFIGURED,
509 'course' => SITEID
511 (object) [
512 'name' => 'There',
513 'baseurl' => 'https://example.com/i/am/?where=there',
514 'tooldomain' => 'example.com',
515 'state' => LTI_TOOL_STATE_CONFIGURED,
516 'course' => SITEID
518 (object) [
519 'name' => 'Not here',
520 'baseurl' => 'https://example.com/i/am/?where=not/here',
521 'tooldomain' => 'example.com',
522 'state' => LTI_TOOL_STATE_CONFIGURED,
523 'course' => SITEID
525 (object) [
526 'name' => 'Here',
527 'baseurl' => 'https://example.com/i/am/',
528 'tooldomain' => 'example.com',
529 'state' => LTI_TOOL_STATE_CONFIGURED,
530 'course' => SITEID
532 (object) [
533 'name' => 'Here',
534 'baseurl' => 'https://example.com/i/was',
535 'tooldomain' => 'example.com',
536 'state' => LTI_TOOL_STATE_CONFIGURED,
537 'course' => SITEID
539 (object) [
540 'name' => 'Here',
541 'baseurl' => 'https://badexample.com/i/am/?where=here',
542 'tooldomain' => 'badexample.com',
543 'state' => LTI_TOOL_STATE_CONFIGURED,
544 'course' => SITEID
548 $data = [
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/',
575 'expected' => null
578 'url' => 'https://example.com',
579 'expected' => null
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];
592 }, $data);
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() {
612 $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() {
625 $mapping = [
626 'accept_copy_advice' => [
627 'suffix' => 'dl',
628 'group' => 'deep_linking_settings',
629 'claim' => 'accept_copy_advice',
630 'isarray' => false
632 'accept_media_types' => [
633 'suffix' => 'dl',
634 'group' => 'deep_linking_settings',
635 'claim' => 'accept_media_types',
636 'isarray' => true
638 'accept_multiple' => [
639 'suffix' => 'dl',
640 'group' => 'deep_linking_settings',
641 'claim' => 'accept_multiple',
642 'isarray' => false
644 'accept_presentation_document_targets' => [
645 'suffix' => 'dl',
646 'group' => 'deep_linking_settings',
647 'claim' => 'accept_presentation_document_targets',
648 'isarray' => true
650 'accept_types' => [
651 'suffix' => 'dl',
652 'group' => 'deep_linking_settings',
653 'claim' => 'accept_types',
654 'isarray' => true
656 'accept_unsigned' => [
657 'suffix' => 'dl',
658 'group' => 'deep_linking_settings',
659 'claim' => 'accept_unsigned',
660 'isarray' => false
662 'auto_create' => [
663 'suffix' => 'dl',
664 'group' => 'deep_linking_settings',
665 'claim' => 'auto_create',
666 'isarray' => false
668 'can_confirm' => [
669 'suffix' => 'dl',
670 'group' => 'deep_linking_settings',
671 'claim' => 'can_confirm',
672 'isarray' => false
674 'content_item_return_url' => [
675 'suffix' => 'dl',
676 'group' => 'deep_linking_settings',
677 'claim' => 'deep_link_return_url',
678 'isarray' => false
680 'content_items' => [
681 'suffix' => 'dl',
682 'group' => '',
683 'claim' => 'content_items',
684 'isarray' => true
686 'data' => [
687 'suffix' => 'dl',
688 'group' => 'deep_linking_settings',
689 'claim' => 'data',
690 'isarray' => false
692 'text' => [
693 'suffix' => 'dl',
694 'group' => 'deep_linking_settings',
695 'claim' => 'text',
696 'isarray' => false
698 'title' => [
699 'suffix' => 'dl',
700 'group' => 'deep_linking_settings',
701 'claim' => 'title',
702 'isarray' => false
704 'lti_msg' => [
705 'suffix' => 'dl',
706 'group' => '',
707 'claim' => 'msg',
708 'isarray' => false
710 'lti_log' => [
711 'suffix' => 'dl',
712 'group' => '',
713 'claim' => 'log',
714 'isarray' => false
716 'lti_errormsg' => [
717 'suffix' => 'dl',
718 'group' => '',
719 'claim' => 'errormsg',
720 'isarray' => false
722 'lti_errorlog' => [
723 'suffix' => 'dl',
724 'group' => '',
725 'claim' => 'errorlog',
726 'isarray' => false
728 'context_id' => [
729 'suffix' => '',
730 'group' => 'context',
731 'claim' => 'id',
732 'isarray' => false
734 'context_label' => [
735 'suffix' => '',
736 'group' => 'context',
737 'claim' => 'label',
738 'isarray' => false
740 'context_title' => [
741 'suffix' => '',
742 'group' => 'context',
743 'claim' => 'title',
744 'isarray' => false
746 'context_type' => [
747 'suffix' => '',
748 'group' => 'context',
749 'claim' => 'type',
750 'isarray' => true
752 'lis_course_offering_sourcedid' => [
753 'suffix' => '',
754 'group' => 'lis',
755 'claim' => 'course_offering_sourcedid',
756 'isarray' => false
758 'lis_course_section_sourcedid' => [
759 'suffix' => '',
760 'group' => 'lis',
761 'claim' => 'course_section_sourcedid',
762 'isarray' => false
764 'launch_presentation_css_url' => [
765 'suffix' => '',
766 'group' => 'launch_presentation',
767 'claim' => 'css_url',
768 'isarray' => false
770 'launch_presentation_document_target' => [
771 'suffix' => '',
772 'group' => 'launch_presentation',
773 'claim' => 'document_target',
774 'isarray' => false
776 'launch_presentation_height' => [
777 'suffix' => '',
778 'group' => 'launch_presentation',
779 'claim' => 'height',
780 'isarray' => false
782 'launch_presentation_locale' => [
783 'suffix' => '',
784 'group' => 'launch_presentation',
785 'claim' => 'locale',
786 'isarray' => false
788 'launch_presentation_return_url' => [
789 'suffix' => '',
790 'group' => 'launch_presentation',
791 'claim' => 'return_url',
792 'isarray' => false
794 'launch_presentation_width' => [
795 'suffix' => '',
796 'group' => 'launch_presentation',
797 'claim' => 'width',
798 'isarray' => false
800 'lis_person_contact_email_primary' => [
801 'suffix' => '',
802 'group' => null,
803 'claim' => 'email',
804 'isarray' => false
806 'lis_person_name_family' => [
807 'suffix' => '',
808 'group' => null,
809 'claim' => 'family_name',
810 'isarray' => false
812 'lis_person_name_full' => [
813 'suffix' => '',
814 'group' => null,
815 'claim' => 'name',
816 'isarray' => false
818 'lis_person_name_given' => [
819 'suffix' => '',
820 'group' => null,
821 'claim' => 'given_name',
822 'isarray' => false
824 'lis_person_sourcedid' => [
825 'suffix' => '',
826 'group' => 'lis',
827 'claim' => 'person_sourcedid',
828 'isarray' => false
830 'user_id' => [
831 'suffix' => '',
832 'group' => null,
833 'claim' => 'sub',
834 'isarray' => false
836 'user_image' => [
837 'suffix' => '',
838 'group' => null,
839 'claim' => 'picture',
840 'isarray' => false
842 'roles' => [
843 'suffix' => '',
844 'group' => '',
845 'claim' => 'roles',
846 'isarray' => true
848 'role_scope_mentor' => [
849 'suffix' => '',
850 'group' => '',
851 'claim' => 'role_scope_mentor',
852 'isarray' => false
854 'deployment_id' => [
855 'suffix' => '',
856 'group' => '',
857 'claim' => 'deployment_id',
858 'isarray' => false
860 'lti_message_type' => [
861 'suffix' => '',
862 'group' => '',
863 'claim' => 'message_type',
864 'isarray' => false
866 'lti_version' => [
867 'suffix' => '',
868 'group' => '',
869 'claim' => 'version',
870 'isarray' => false
872 'resource_link_description' => [
873 'suffix' => '',
874 'group' => 'resource_link',
875 'claim' => 'description',
876 'isarray' => false
878 'resource_link_id' => [
879 'suffix' => '',
880 'group' => 'resource_link',
881 'claim' => 'id',
882 'isarray' => false
884 'resource_link_title' => [
885 'suffix' => '',
886 'group' => 'resource_link',
887 'claim' => 'title',
888 'isarray' => false
890 'tool_consumer_info_product_family_code' => [
891 'suffix' => '',
892 'group' => 'tool_platform',
893 'claim' => 'family_code',
894 'isarray' => false
896 'tool_consumer_info_version' => [
897 'suffix' => '',
898 'group' => 'tool_platform',
899 'claim' => 'version',
900 'isarray' => false
902 'tool_consumer_instance_contact_email' => [
903 'suffix' => '',
904 'group' => 'tool_platform',
905 'claim' => 'contact_email',
906 'isarray' => false
908 'tool_consumer_instance_description' => [
909 'suffix' => '',
910 'group' => 'tool_platform',
911 'claim' => 'description',
912 'isarray' => false
914 'tool_consumer_instance_guid' => [
915 'suffix' => '',
916 'group' => 'tool_platform',
917 'claim' => 'guid',
918 'isarray' => false
920 'tool_consumer_instance_name' => [
921 'suffix' => '',
922 'group' => 'tool_platform',
923 'claim' => 'name',
924 'isarray' => false
926 'tool_consumer_instance_url' => [
927 'suffix' => '',
928 'group' => 'tool_platform',
929 'claim' => 'url',
930 'isarray' => false
932 'custom_context_memberships_url' => [
933 'suffix' => 'nrps',
934 'group' => 'namesroleservice',
935 'claim' => 'context_memberships_url',
936 'isarray' => false
938 'custom_context_memberships_versions' => [
939 'suffix' => 'nrps',
940 'group' => 'namesroleservice',
941 'claim' => 'service_versions',
942 'isarray' => true
944 'custom_gradebookservices_scope' => [
945 'suffix' => 'ags',
946 'group' => 'endpoint',
947 'claim' => 'scope',
948 'isarray' => true
950 'custom_lineitems_url' => [
951 'suffix' => 'ags',
952 'group' => 'endpoint',
953 'claim' => 'lineitems',
954 'isarray' => false
956 'custom_lineitem_url' => [
957 'suffix' => 'ags',
958 'group' => 'endpoint',
959 'claim' => 'lineitem',
960 'isarray' => false
962 'custom_results_url' => [
963 'suffix' => 'ags',
964 'group' => 'endpoint',
965 'claim' => 'results',
966 'isarray' => false
968 'custom_result_url' => [
969 'suffix' => 'ags',
970 'group' => 'endpoint',
971 'claim' => 'result',
972 'isarray' => false
974 'custom_scores_url' => [
975 'suffix' => 'ags',
976 'group' => 'endpoint',
977 'claim' => 'scores',
978 'isarray' => false
980 'custom_score_url' => [
981 'suffix' => 'ags',
982 'group' => 'endpoint',
983 'claim' => 'score',
984 'isarray' => false
986 'lis_outcome_service_url' => [
987 'suffix' => 'bos',
988 'group' => 'basicoutcomesservice',
989 'claim' => 'lis_outcome_service_url',
990 'isarray' => false
992 'lis_result_sourcedid' => [
993 'suffix' => 'bos',
994 'group' => 'basicoutcomesservice',
995 'claim' => 'lis_result_sourcedid',
996 'isarray' => false
1000 $this->assertEquals($mapping, lti_get_jwt_claim_mapping());
1004 * Test lti_build_standard_message().
1006 public function test_lti_build_standard_message_institution_name_set() {
1007 global $CFG;
1009 $this->resetAfterTest();
1011 $CFG->mod_lti_institution_name = 'some institution name lols';
1013 $course = $this->getDataGenerator()->create_course();
1014 $instance = $this->getDataGenerator()->create_module('lti',
1016 'course' => $course->id,
1020 $message = lti_build_standard_message($instance, '2', LTI_VERSION_1);
1022 $this->assertEquals('moodle-2', $message['ext_lms']);
1023 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1024 $this->assertEquals(LTI_VERSION_1, $message['lti_version']);
1025 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1026 $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1027 $this->assertEquals('some institution name lols', $message['tool_consumer_instance_name']);
1028 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1032 * Test lti_build_standard_message().
1034 public function test_lti_build_standard_message_institution_name_not_set() {
1035 $this->resetAfterTest();
1037 $course = $this->getDataGenerator()->create_course();
1038 $instance = $this->getDataGenerator()->create_module('lti',
1040 'course' => $course->id,
1044 $message = lti_build_standard_message($instance, '2', LTI_VERSION_2);
1046 $this->assertEquals('moodle-2', $message['ext_lms']);
1047 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']);
1048 $this->assertEquals(LTI_VERSION_2, $message['lti_version']);
1049 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']);
1050 $this->assertEquals('2', $message['tool_consumer_instance_guid']);
1051 $this->assertEquals('phpunit', $message['tool_consumer_instance_name']);
1052 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']);
1056 * Test lti_verify_jwt_signature().
1058 public function test_lti_verify_jwt_signature() {
1059 $this->resetAfterTest();
1061 $this->setAdminUser();
1063 // Create a tool type, associated with that proxy.
1064 $type = new stdClass();
1065 $type->state = LTI_TOOL_STATE_CONFIGURED;
1066 $type->name = "Test tool";
1067 $type->description = "Example description";
1068 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1070 $config = new stdClass();
1071 $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1072 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1073 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1074 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1075 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1076 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1077 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1078 MwIDAQAB
1079 -----END PUBLIC KEY-----';
1081 $config->lti_keytype = LTI_RSA_KEY;
1083 $typeid = lti_add_type($type, $config);
1085 lti_verify_jwt_signature($typeid, '', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g' .
1086 'RG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOs' .
1087 'S_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMgu' .
1088 'EIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iY' .
1089 'v7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA');
1093 * Test lti_verify_jwt_signature_jwk().
1095 public function test_lti_verify_jwt_signature_jwk() {
1096 $this->resetAfterTest();
1098 $this->setAdminUser();
1100 // Create a tool type, associated with that proxy.
1101 $type = new stdClass();
1102 $type->state = LTI_TOOL_STATE_CONFIGURED;
1103 $type->name = "Test tool";
1104 $type->description = "Example description";
1105 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1107 $config = new stdClass();
1108 $config->lti_publickeyset = dirname(__FILE__) . '/fixtures/test_keyset';
1110 $config->lti_keytype = LTI_JWK_KEYSET;
1112 $typeid = lti_add_type($type, $config);
1114 $jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU3YzExNzdkMmQ1M2EwMjFjNzM';
1115 $jwt .= '3NTY0OTFjMTM3YjE3In0.eyJpc3MiOiJnclJvbkd3RTd1WjRwZ28iLCJzdWIiOiJnclJvb';
1116 $jwt .= 'kd3RTd1WjRwZ28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0L21vb2RsZS9tb2QvbHRpL3R';
1117 $jwt .= 'va2VuLnBocCIsImp0aSI6IjFlMUJPVEczVFJjbFdUem00dERsMGc9PSIsImlhdCI6MTU4M';
1118 $jwt .= 'Dg1NTUwNX0.Lowhc9ovNAXRb2rkAnv1oozDXlRD54Mz2JS1i8Zx4yGWQzmXzam-La19_g0';
1119 $jwt .= 'CTnwlKM6gxaInnRKFRAcwhJVcWec389liLAjMbna6d6iTWYTZr7q_4BIe3CT_oTMWASGta';
1120 $jwt .= 'Paaq53ch1rO4YdueEtmtd1K47ibo4Lhu1jmP_icc3lxjfnqiv4vIYdy7W2JQEzpk1ImuQr';
1121 $jwt .= 'AlO1xR3fZ6bgcJhVIaw5xoaZD3ZgEjuZOQXMkywv1bL-mL17RX336CzHd8rYZg82QXrBzb';
1122 $jwt .= 'NWzAlaZxv9VSug8t6mORvM6TkYYWjqEBKemgkD5rNh1BHrPcjWP7vy2Jz7YMjLsmuvDuLK';
1123 $jwt .= '_PHYIKL--s4gcXWoYmOu1vj-SgoPczTJPoiBD35hAKqVHy5ggHaYHBy95_bbcFd8H1smHw';
1124 $jwt .= 'pejrAFj1QAwGyTISLzUm08oq7Ak0tSxRKKXw4lpZAka1MmYxO3tJ_3-MXw6Bwz12bNgitJ';
1125 $jwt .= 'lQd6n3kkGLCJAmANeRkPsH6eZVwF0n2cjh2O1JAwyNcMD2vs4I8ftM1EqqoE2M3r6kt3AC';
1126 $jwt .= 'EscmqzizI3j80USBCLUUb1UTsfJb2g7oyApJAp-13Q3InR3QyvWO8unG5VraFE7IL5I28h';
1127 $jwt .= 'MkQAHuCI90DFmXB4leflAu7wNlIK_U8xkGl8X8Mnv6MWgg94Ki8jgIq_kA85JAqI';
1129 lti_verify_jwt_signature($typeid, '', $jwt);
1133 * Test lti_verify_jwt_signature().
1135 public function test_lti_verify_jwt_signature_with_lti2() {
1136 $this->resetAfterTest();
1138 $this->setAdminUser();
1140 // Create a tool proxy.
1141 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
1143 // Create a tool type, associated with that proxy.
1144 $type = new stdClass();
1145 $type->state = LTI_TOOL_STATE_CONFIGURED;
1146 $type->name = "Test tool";
1147 $type->description = "Example description";
1148 $type->toolproxyid = $proxy->id;
1149 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1151 $data = new stdClass();
1152 $data->lti_contentitem = true;
1154 $typeid = lti_add_type($type, $data);
1156 $this->expectExceptionMessage('JWT security not supported with LTI 2');
1157 lti_verify_jwt_signature($typeid, '', '');
1161 * Test lti_verify_jwt_signature().
1163 public function test_lti_verify_jwt_signature_no_consumer_key() {
1164 $this->resetAfterTest();
1166 $this->setAdminUser();
1168 // Create a tool type, associated with that proxy.
1169 $type = new stdClass();
1170 $type->state = LTI_TOOL_STATE_CONFIGURED;
1171 $type->name = "Test tool";
1172 $type->description = "Example description";
1173 $type->clientid = 'consumerkey';
1174 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1176 $config = new stdClass();
1177 $typeid = lti_add_type($type, $config);
1179 $this->expectExceptionMessage(get_string('errorincorrectconsumerkey', 'mod_lti'));
1180 lti_verify_jwt_signature($typeid, '', '');
1184 * Test lti_verify_jwt_signature().
1186 public function test_lti_verify_jwt_signature_no_public_key() {
1187 $this->resetAfterTest();
1189 $this->setAdminUser();
1191 // Create a tool type, associated with that proxy.
1192 $type = new stdClass();
1193 $type->state = LTI_TOOL_STATE_CONFIGURED;
1194 $type->name = "Test tool";
1195 $type->description = "Example description";
1196 $type->clientid = 'consumerkey';
1197 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1199 $config = new stdClass();
1200 $config->lti_keytype = LTI_RSA_KEY;
1201 $typeid = lti_add_type($type, $config);
1203 $this->expectExceptionMessage('No public key configured');
1204 lti_verify_jwt_signature($typeid, 'consumerkey', '');
1208 * Test lti_convert_content_items().
1210 public function test_lti_convert_content_items() {
1211 $contentitems = [];
1212 $contentitems[] = [
1213 'type' => 'ltiResourceLink',
1214 'url' => 'http://example.com/messages/launch',
1215 'title' => 'Test title',
1216 'text' => 'Test text',
1217 'frame' => []
1220 $contentitems = json_encode($contentitems);
1222 $json = lti_convert_content_items($contentitems);
1224 $jsondecode = json_decode($json);
1226 $strcontext = '@context';
1227 $strgraph = '@graph';
1228 $strtype = '@type';
1230 $objgraph = new stdClass();
1231 $objgraph->url = 'http://example.com/messages/launch';
1232 $objgraph->title = 'Test title';
1233 $objgraph->text = 'Test text';
1234 $objgraph->frame = [];
1235 $objgraph->{$strtype} = 'LtiLinkItem';
1236 $objgraph->mediaType = 'application\/vnd.ims.lti.v1.ltilink';
1238 $expected = new stdClass();
1239 $expected->{$strcontext} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem';
1240 $expected->{$strgraph} = [];
1241 $expected->{$strgraph}[] = $objgraph;
1243 $this->assertEquals($expected, $jsondecode);
1247 * Test lti_sign_jwt().
1249 public function test_lti_sign_jwt() {
1250 $this->resetAfterTest();
1252 $this->setAdminUser();
1254 // Create a tool type, associated with that proxy.
1255 $type = new stdClass();
1256 $type->state = LTI_TOOL_STATE_CONFIGURED;
1257 $type->name = "Test tool";
1258 $type->description = "Example description";
1259 $type->clientid = 'consumerkey';
1260 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1262 $config = new stdClass();
1263 $typeid = lti_add_type($type, $config);
1265 $params = [];
1266 $params['roles'] = 'urn:lti:role:ims/lis/testrole,' .
1267 'urn:lti:instrole:ims/lis/testinstrole,' .
1268 'urn:lti:sysrole:ims/lis/testsysrole,' .
1269 'hi';
1270 $params['accept_copy_advice'] = [
1271 'suffix' => 'dl',
1272 'group' => 'deep_linking_settings',
1273 'claim' => 'accept_copy_advice',
1274 'isarray' => false
1276 $params['lis_result_sourcedid'] = [
1277 'suffix' => 'bos',
1278 'group' => 'basicoutcomesservice',
1279 'claim' => 'lis_result_sourcedid',
1280 'isarray' => false
1282 $endpoint = 'https://www.example.com/moodle';
1283 $oauthconsumerkey = 'consumerkey';
1284 $nonce = '';
1286 $jwt = lti_sign_jwt($params, $endpoint, $oauthconsumerkey, $typeid, $nonce);
1288 $this->assertArrayHasKey('id_token', $jwt);
1289 $this->assertNotEmpty($jwt['id_token']);
1293 * Test lti_convert_from_jwt()
1295 public function test_lti_convert_from_jwt() {
1296 $this->resetAfterTest();
1298 $this->setAdminUser();
1300 // Create a tool type, associated with that proxy.
1301 $type = new stdClass();
1302 $type->state = LTI_TOOL_STATE_CONFIGURED;
1303 $type->name = "Test tool";
1304 $type->description = "Example description";
1305 $type->clientid = 'sso.example.com';
1306 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1308 $config = new stdClass();
1309 $config->lti_publickey = '-----BEGIN PUBLIC KEY-----
1310 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
1311 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
1312 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
1313 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
1314 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
1315 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
1316 MwIDAQAB
1317 -----END PUBLIC KEY-----';
1318 $config->lti_keytype = LTI_RSA_KEY;
1320 $typeid = lti_add_type($type, $config);
1322 $params = lti_convert_from_jwt($typeid, 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib' .
1323 'mFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic3NvLmV4YW1wbGUuY29tIn0.XURVvEb5ueAvFsn-S9EB' .
1324 'BSfKbsgUzfRQqmJ6evlrYdx7sXWoZXw1nYjaLTg-mawvBr7MVvrdG9qh6oN8OfkQ7bfMwiz4tjBMJ4B4q_sig5BDYIKwMNjZL5GGCBs89FQrgqZBhxw' .
1325 '3exTjPBEn69__w40o0AhCsBohPMh0ZsAyHug5dhm8vIuOP667repUJzM8uKCD6L4bEL6vQE8EwU6WQOmfJ2SDmRs-1pFkiaFd6hmPn6AVX7ETtzQmlT' .
1326 'X-nXe9weQjU1lH4AQG2Yfnn-7lS94bt6E76Zt-XndP3IY7W48EpnRfUK9Ff1fZlomT4MPahdNP1eP8gT2iMz7vYpCfmA');
1328 $this->assertEquals('sso.example.com', $params['oauth_consumer_key']);
1329 $this->assertEquals('John Doe', $params['lis_person_name_full']);
1333 * Test lti_get_permitted_service_scopes().
1335 public function test_lti_get_permitted_service_scopes() {
1336 $this->resetAfterTest();
1338 $this->setAdminUser();
1340 // Create a tool type, associated with that proxy.
1341 $type = new stdClass();
1342 $type->state = LTI_TOOL_STATE_CONFIGURED;
1343 $type->name = "Test tool";
1344 $type->description = "Example description";
1345 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1347 $typeconfig = new stdClass();
1348 $typeconfig->lti_acceptgrades = true;
1350 $typeid = lti_add_type($type, $typeconfig);
1352 $tool = lti_get_type($typeid);
1354 $config = lti_get_type_config($typeid);
1355 $permittedscopes = lti_get_permitted_service_scopes($tool, $config);
1357 $expected = [
1358 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome'
1360 $this->assertEquals($expected, $permittedscopes);
1364 * Test get_tool_type_config().
1366 public function test_get_tool_type_config() {
1367 $this->resetAfterTest();
1369 $this->setAdminUser();
1371 // Create a tool type, associated with that proxy.
1372 $type = new stdClass();
1373 $type->state = LTI_TOOL_STATE_CONFIGURED;
1374 $type->name = "Test tool";
1375 $type->description = "Example description";
1376 $type->clientid = "Test client ID";
1377 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1379 $config = new stdClass();
1381 $typeid = lti_add_type($type, $config);
1383 $type = lti_get_type($typeid);
1385 $typeconfig = get_tool_type_config($type);
1387 $this->assertEquals('https://www.example.com/moodle', $typeconfig['platformid']);
1388 $this->assertEquals($type->clientid, $typeconfig['clientid']);
1389 $this->assertEquals($typeid, $typeconfig['deploymentid']);
1390 $this->assertEquals('https://www.example.com/moodle/mod/lti/certs.php', $typeconfig['publickeyseturl']);
1391 $this->assertEquals('https://www.example.com/moodle/mod/lti/token.php', $typeconfig['accesstokenurl']);
1392 $this->assertEquals('https://www.example.com/moodle/mod/lti/auth.php', $typeconfig['authrequesturl']);
1396 * Test lti_new_access_token().
1398 public function test_lti_new_access_token() {
1399 global $DB;
1401 $this->resetAfterTest();
1403 $this->setAdminUser();
1405 // Create a tool type, associated with that proxy.
1406 $type = new stdClass();
1407 $type->state = LTI_TOOL_STATE_CONFIGURED;
1408 $type->name = "Test tool";
1409 $type->description = "Example description";
1410 $type->clientid = "Test client ID";
1411 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1413 $config = new stdClass();
1415 $typeid = lti_add_type($type, $config);
1417 $scopes = ['lti_some_scope', 'lti_another_scope'];
1419 lti_new_access_token($typeid, $scopes);
1421 $token = $DB->get_records('lti_access_tokens');
1422 $this->assertEquals(1, count($token));
1424 $token = reset($token);
1426 $this->assertEquals($typeid, $token->typeid);
1427 $this->assertEquals(json_encode(array_values($scopes)), $token->scope);
1428 $this->assertEquals($token->timecreated + LTI_ACCESS_TOKEN_LIFE, $token->validuntil);
1429 $this->assertNull($token->lastaccess);
1433 * Test lti_build_login_request().
1435 public function test_lti_build_login_request() {
1436 global $USER, $CFG;
1438 $this->resetAfterTest();
1440 $USER->id = 123456789;
1442 $course = $this->getDataGenerator()->create_course();
1443 $instance = $this->getDataGenerator()->create_module('lti',
1445 'course' => $course->id,
1449 $config = new stdClass();
1450 $config->lti_clientid = 'some-client-id';
1451 $config->typeid = 'some-type-id';
1452 $config->lti_toolurl = 'some-lti-tool-url';
1454 $request = lti_build_login_request($course->id, $instance->id, $instance, $config, 'basic-lti-launch-request');
1456 $this->assertEquals($CFG->wwwroot, $request['iss']);
1457 $this->assertEquals('http://some-lti-tool-url', $request['target_link_uri']);
1458 $this->assertEquals(123456789, $request['login_hint']);
1459 $this->assertEquals($instance->id, $request['lti_message_hint']);
1460 $this->assertEquals('some-client-id', $request['client_id']);
1461 $this->assertEquals('some-type-id', $request['lti_deployment_id']);
1465 * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle).
1467 public function test_lti_get_launch_data_default_organizationid_unset_usehost() {
1468 global $DB;
1469 $this->resetAfterTest();
1470 $this->setAdminUser();
1471 $config = new stdClass();
1472 $config->lti_organizationid = '';
1473 $course = $this->getDataGenerator()->create_course();
1474 $type = $this->create_type($config);
1475 $link = $this->create_instance($type, $course);
1476 $launchdata = lti_get_launch_data($link);
1477 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1481 * Test default org id is set to host when config is usehost.
1483 public function test_lti_get_launch_data_default_organizationid_set_usehost() {
1484 global $DB;
1485 $this->resetAfterTest();
1486 $this->setAdminUser();
1487 $config = new stdClass();
1488 $config->lti_organizationid = '';
1489 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST;
1490 $course = $this->getDataGenerator()->create_course();
1491 $type = $this->create_type($config);
1492 $link = $this->create_instance($type, $course);
1493 $launchdata = lti_get_launch_data($link);
1494 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com');
1498 * Test default org id is set to site id when config is usesiteid.
1500 public function test_lti_get_launch_data_default_organizationid_set_usesiteid() {
1501 global $DB;
1502 $this->resetAfterTest();
1503 $this->setAdminUser();
1504 $config = new stdClass();
1505 $config->lti_organizationid = '';
1506 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1507 $course = $this->getDataGenerator()->create_course();
1508 $type = $this->create_type($config);
1509 $link = $this->create_instance($type, $course);
1510 $launchdata = lti_get_launch_data($link);
1511 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], md5(get_site_identifier()));
1515 * Test orgid can be overridden in which case default is ignored.
1517 public function test_lti_get_launch_data_default_organizationid_orgid_override() {
1518 global $DB;
1519 $this->resetAfterTest();
1520 $this->setAdminUser();
1521 $config = new stdClass();
1522 $config->lti_organizationid = 'overridden!';
1523 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID;
1524 $course = $this->getDataGenerator()->create_course();
1525 $type = $this->create_type($config);
1526 $link = $this->create_instance($type, $course);
1527 $launchdata = lti_get_launch_data($link);
1528 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'overridden!');
1532 * Create an LTI Tool.
1534 * @param object $config tool config.
1536 * @return object tool.
1538 private function create_type(object $config) {
1539 $type = new stdClass();
1540 $type->state = LTI_TOOL_STATE_CONFIGURED;
1541 $type->name = "Test tool";
1542 $type->description = "Example description";
1543 $type->clientid = "Test client ID";
1544 $type->baseurl = $this->getExternalTestFileUrl('/test.html');
1546 $configbase = new stdClass();
1547 $configbase->lti_acceptgrades = LTI_SETTING_NEVER;
1548 $configbase->lti_sendname = LTI_SETTING_NEVER;
1549 $configbase->lti_sendemailaddr = LTI_SETTING_NEVER;
1550 $mergedconfig = (object) array_merge( (array) $configbase, (array) $config);
1551 $typeid = lti_add_type($type, $mergedconfig);
1552 return lti_get_type($typeid);
1556 * Create an LTI Instance for the tool in a given course.
1558 * @param object $type tool for which an instance should be added.
1559 * @param object $course course where the instance should be added.
1561 * @return object instance.
1563 private function create_instance(object $type, object $course) {
1564 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
1565 return $generator->create_instance(array('course' => $course->id,
1566 'toolurl' => $type->baseurl,
1567 'typeid' => $type->id
1568 ), array());