Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 // 17 // This file is part of BasicLTI4Moodle 18 // 19 // BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability) 20 // consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web 21 // based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI 22 // specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS 23 // are already supporting or going to support BasicLTI. This project Implements the consumer 24 // for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas. 25 // BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem 26 // at the GESSI research group at UPC. 27 // SimpleLTI consumer for Moodle is an implementation of the early specification of LTI 28 // by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a 29 // Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier. 30 // 31 // BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis 32 // of the Universitat Politecnica de Catalunya http://www.upc.edu 33 // Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu. 34 35 /** 36 * This file contains unit tests for (some of) lti/locallib.php 37 * 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 48 */ 49 50 defined('MOODLE_INTERNAL') || die; 51 52 global $CFG; 53 require_once($CFG->dirroot . '/mod/lti/locallib.php'); 54 require_once($CFG->dirroot . '/mod/lti/servicelib.php'); 55 56 /** 57 * Local library tests 58 * 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 62 */ 63 class mod_lti_locallib_testcase extends advanced_testcase { 64 65 public function test_split_custom_parameters() { 66 $this->resetAfterTest(); 67 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')); 74 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')); 78 79 // Removed repeat of previous test with a semicolon separator. 80 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')); 85 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')); 91 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)); 97 } 98 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 104 */ 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'); 113 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'); 116 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'])); 122 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); 130 } 131 132 /** 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 137 */ 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 '{"data":{"instanceid":"2","userid":"2"},"hash":' . 153 '"0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b"}' . 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> 166 '; 167 168 $parsed = lti_parse_grade_replace_message(new SimpleXMLElement($message)); 169 170 $this->assertEquals($parsed->userid, '2'); 171 $this->assertEquals($parsed->instanceid, '2'); 172 $this->assertEquals($parsed->sourcedidhash, '0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b'); 173 174 $ltiinstance = (object)array('servicesalt' => '4e5fcc06de1d58.44963230'); 175 176 lti_verify_sourcedid($ltiinstance, $parsed); 177 } 178 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')); 183 } 184 185 /** 186 * Test lti_get_url_thumbprint against various URLs 187 */ 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')); 199 } 200 201 /* 202 * Verify that lti_build_request does handle resource_link_id as expected 203 */ 204 public function test_lti_buid_request_resource_link_id() { 205 $this->resetAfterTest(); 206 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, 213 )); 214 215 $typeconfig = array( 216 'acceptgrades' => 1, 217 'forcessl' => 0, 218 'sendname' => 2, 219 'sendemailaddr' => 2, 220 'customparameters' => '', 221 ); 222 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']); 226 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']); 231 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); 237 } 238 239 /** 240 * Test lti_build_request's resource_link_description and ensure 241 * that the newlines in the description are correct. 242 */ 243 public function test_lti_build_request_description() { 244 $this->resetAfterTest(); 245 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, 252 )); 253 254 $typeconfig = array( 255 'acceptgrades' => 1, 256 'forcessl' => 0, 257 'sendname' => 2, 258 'sendemailaddr' => 2, 259 'customparameters' => '', 260 ); 261 262 $params = lti_build_request($instance, $typeconfig, $course, null); 263 264 $ncount = substr_count($params['resource_link_description'], "\n"); 265 $this->assertGreaterThan(0, $ncount); 266 267 $rcount = substr_count($params['resource_link_description'], "\r"); 268 $this->assertGreaterThan(0, $rcount); 269 270 $this->assertEquals($ncount, $rcount, 'The number of \n characters should be the same as the number of \r characters'); 271 272 $rncount = substr_count($params['resource_link_description'], "\r\n"); 273 $this->assertGreaterThan(0, $rncount); 274 275 $this->assertEquals($ncount, $rncount, 'All newline characters should be a combination of \r\n'); 276 } 277 278 /** 279 * Tests lti_prepare_type_for_save's handling of the "Force SSL" configuration. 280 */ 281 public function test_lti_prepare_type_for_save_forcessl() { 282 $type = new stdClass(); 283 $config = new stdClass(); 284 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); 290 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); 297 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); 304 } 305 306 /** 307 * Tests lti_load_type_from_cartridge and lti_load_type_if_cartridge 308 */ 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'); 312 313 lti_load_type_if_cartridge($type); 314 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); 320 } 321 322 /** 323 * Tests lti_load_tool_from_cartridge and lti_load_tool_if_cartridge 324 */ 325 public function test_lti_load_tool_from_cartridge() { 326 $lti = new stdClass(); 327 $lti->toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'); 328 329 lti_load_tool_if_cartridge($lti); 330 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); 337 } 338 339 /** 340 * Tests for lti_build_content_item_selection_request(). 341 */ 342 public function test_lti_build_content_item_selection_request() { 343 $this->resetAfterTest(); 344 345 $this->setAdminUser(); 346 // Create a tool proxy. 347 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array()); 348 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'); 358 359 $typeid = lti_add_type($type, $data); 360 361 $typeconfig = lti_get_type_config($typeid); 362 363 $course = $this->getDataGenerator()->create_course(); 364 $returnurl = new moodle_url('/'); 365 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('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'); 390 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']); 410 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']); 426 } 427 428 /** 429 * Test for lti_build_content_item_selection_request() with nonexistent tool type ID parameter. 430 */ 431 public function test_lti_build_content_item_selection_request_invalid_tooltype() { 432 $this->resetAfterTest(); 433 434 $this->setAdminUser(); 435 $course = $this->getDataGenerator()->create_course(); 436 $returnurl = new moodle_url('/'); 437 438 // Should throw Exception on non-existent tool type. 439 $this->expectException('moodle_exception'); 440 lti_build_content_item_selection_request(1, $course, $returnurl); 441 } 442 443 /** 444 * Test for lti_build_content_item_selection_request() with invalid media types parameter. 445 */ 446 public function test_lti_build_content_item_selection_request_invalid_mediatypes() { 447 $this->resetAfterTest(); 448 449 $this->setAdminUser(); 450 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'); 459 460 $typeid = lti_add_type($type, $data); 461 $course = $this->getDataGenerator()->create_course(); 462 $returnurl = new moodle_url('/'); 463 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); 468 } 469 470 /** 471 * Test for lti_build_content_item_selection_request() with invalid presentation targets parameter. 472 */ 473 public function test_lti_build_content_item_selection_request_invalid_presentationtargets() { 474 $this->resetAfterTest(); 475 476 $this->setAdminUser(); 477 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'); 486 487 $typeid = lti_add_type($type, $data); 488 $course = $this->getDataGenerator()->create_course(); 489 $returnurl = new moodle_url('/'); 490 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); 495 } 496 497 /** 498 * Provider for test_lti_get_best_tool_by_url. 499 * 500 * @return array of [urlToTest, expectedTool, allTools] 501 */ 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 510 ], 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 517 ], 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 524 ], 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 531 ], 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 538 ], 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 545 ], 546 ]; 547 548 $data = [ 549 [ 550 'url' => $tools[0]->baseurl, 551 'expected' => $tools[0], 552 ], 553 [ 554 'url' => $tools[1]->baseurl, 555 'expected' => $tools[1], 556 ], 557 [ 558 'url' => $tools[2]->baseurl, 559 'expected' => $tools[2], 560 ], 561 [ 562 'url' => $tools[3]->baseurl, 563 'expected' => $tools[3], 564 ], 565 [ 566 'url' => $tools[4]->baseurl, 567 'expected' => $tools[4], 568 ], 569 [ 570 'url' => $tools[5]->baseurl, 571 'expected' => $tools[5], 572 ], 573 [ 574 'url' => 'https://nomatch.com/i/am/', 575 'expected' => null 576 ], 577 [ 578 'url' => 'https://example.com', 579 'expected' => null 580 ], 581 [ 582 'url' => 'https://example.com/i/am/?where=unknown', 583 'expected' => $tools[3] 584 ] 585 ]; 586 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); 593 } 594 595 /** 596 * Test lti_get_best_tool_by_url. 597 * 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. 602 */ 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); 606 } 607 608 /** 609 * Test lti_get_jwt_message_type_mapping(). 610 */ 611 public function test_lti_get_jwt_message_type_mapping() { 612 $mapping = [ 613 'basic-lti-launch-request' => 'LtiResourceLinkRequest', 614 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest', 615 'LtiDeepLinkingResponse' => 'ContentItemSelection', 616 ]; 617 618 $this->assertEquals($mapping, lti_get_jwt_message_type_mapping()); 619 } 620 621 /** 622 * Test lti_get_jwt_claim_mapping() 623 */ 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, 631 'type' => 'boolean' 632 ], 633 'accept_media_types' => [ 634 'suffix' => 'dl', 635 'group' => 'deep_linking_settings', 636 'claim' => 'accept_media_types', 637 'isarray' => true 638 ], 639 'accept_multiple' => [ 640 'suffix' => 'dl', 641 'group' => 'deep_linking_settings', 642 'claim' => 'accept_multiple', 643 'isarray' => false, 644 'type' => 'boolean' 645 ], 646 'accept_presentation_document_targets' => [ 647 'suffix' => 'dl', 648 'group' => 'deep_linking_settings', 649 'claim' => 'accept_presentation_document_targets', 650 'isarray' => true 651 ], 652 'accept_types' => [ 653 'suffix' => 'dl', 654 'group' => 'deep_linking_settings', 655 'claim' => 'accept_types', 656 'isarray' => true 657 ], 658 'accept_unsigned' => [ 659 'suffix' => 'dl', 660 'group' => 'deep_linking_settings', 661 'claim' => 'accept_unsigned', 662 'isarray' => false, 663 'type' => 'boolean' 664 ], 665 'auto_create' => [ 666 'suffix' => 'dl', 667 'group' => 'deep_linking_settings', 668 'claim' => 'auto_create', 669 'isarray' => false, 670 'type' => 'boolean' 671 ], 672 'can_confirm' => [ 673 'suffix' => 'dl', 674 'group' => 'deep_linking_settings', 675 'claim' => 'can_confirm', 676 'isarray' => false, 677 'type' => 'boolean' 678 ], 679 'content_item_return_url' => [ 680 'suffix' => 'dl', 681 'group' => 'deep_linking_settings', 682 'claim' => 'deep_link_return_url', 683 'isarray' => false 684 ], 685 'content_items' => [ 686 'suffix' => 'dl', 687 'group' => '', 688 'claim' => 'content_items', 689 'isarray' => true 690 ], 691 'data' => [ 692 'suffix' => 'dl', 693 'group' => 'deep_linking_settings', 694 'claim' => 'data', 695 'isarray' => false 696 ], 697 'text' => [ 698 'suffix' => 'dl', 699 'group' => 'deep_linking_settings', 700 'claim' => 'text', 701 'isarray' => false 702 ], 703 'title' => [ 704 'suffix' => 'dl', 705 'group' => 'deep_linking_settings', 706 'claim' => 'title', 707 'isarray' => false 708 ], 709 'lti_msg' => [ 710 'suffix' => 'dl', 711 'group' => '', 712 'claim' => 'msg', 713 'isarray' => false 714 ], 715 'lti_log' => [ 716 'suffix' => 'dl', 717 'group' => '', 718 'claim' => 'log', 719 'isarray' => false 720 ], 721 'lti_errormsg' => [ 722 'suffix' => 'dl', 723 'group' => '', 724 'claim' => 'errormsg', 725 'isarray' => false 726 ], 727 'lti_errorlog' => [ 728 'suffix' => 'dl', 729 'group' => '', 730 'claim' => 'errorlog', 731 'isarray' => false 732 ], 733 'context_id' => [ 734 'suffix' => '', 735 'group' => 'context', 736 'claim' => 'id', 737 'isarray' => false 738 ], 739 'context_label' => [ 740 'suffix' => '', 741 'group' => 'context', 742 'claim' => 'label', 743 'isarray' => false 744 ], 745 'context_title' => [ 746 'suffix' => '', 747 'group' => 'context', 748 'claim' => 'title', 749 'isarray' => false 750 ], 751 'context_type' => [ 752 'suffix' => '', 753 'group' => 'context', 754 'claim' => 'type', 755 'isarray' => true 756 ], 757 'lis_course_offering_sourcedid' => [ 758 'suffix' => '', 759 'group' => 'lis', 760 'claim' => 'course_offering_sourcedid', 761 'isarray' => false 762 ], 763 'lis_course_section_sourcedid' => [ 764 'suffix' => '', 765 'group' => 'lis', 766 'claim' => 'course_section_sourcedid', 767 'isarray' => false 768 ], 769 'launch_presentation_css_url' => [ 770 'suffix' => '', 771 'group' => 'launch_presentation', 772 'claim' => 'css_url', 773 'isarray' => false 774 ], 775 'launch_presentation_document_target' => [ 776 'suffix' => '', 777 'group' => 'launch_presentation', 778 'claim' => 'document_target', 779 'isarray' => false 780 ], 781 'launch_presentation_height' => [ 782 'suffix' => '', 783 'group' => 'launch_presentation', 784 'claim' => 'height', 785 'isarray' => false 786 ], 787 'launch_presentation_locale' => [ 788 'suffix' => '', 789 'group' => 'launch_presentation', 790 'claim' => 'locale', 791 'isarray' => false 792 ], 793 'launch_presentation_return_url' => [ 794 'suffix' => '', 795 'group' => 'launch_presentation', 796 'claim' => 'return_url', 797 'isarray' => false 798 ], 799 'launch_presentation_width' => [ 800 'suffix' => '', 801 'group' => 'launch_presentation', 802 'claim' => 'width', 803 'isarray' => false 804 ], 805 'lis_person_contact_email_primary' => [ 806 'suffix' => '', 807 'group' => null, 808 'claim' => 'email', 809 'isarray' => false 810 ], 811 'lis_person_name_family' => [ 812 'suffix' => '', 813 'group' => null, 814 'claim' => 'family_name', 815 'isarray' => false 816 ], 817 'lis_person_name_full' => [ 818 'suffix' => '', 819 'group' => null, 820 'claim' => 'name', 821 'isarray' => false 822 ], 823 'lis_person_name_given' => [ 824 'suffix' => '', 825 'group' => null, 826 'claim' => 'given_name', 827 'isarray' => false 828 ], 829 'lis_person_sourcedid' => [ 830 'suffix' => '', 831 'group' => 'lis', 832 'claim' => 'person_sourcedid', 833 'isarray' => false 834 ], 835 'user_id' => [ 836 'suffix' => '', 837 'group' => null, 838 'claim' => 'sub', 839 'isarray' => false 840 ], 841 'user_image' => [ 842 'suffix' => '', 843 'group' => null, 844 'claim' => 'picture', 845 'isarray' => false 846 ], 847 'roles' => [ 848 'suffix' => '', 849 'group' => '', 850 'claim' => 'roles', 851 'isarray' => true 852 ], 853 'role_scope_mentor' => [ 854 'suffix' => '', 855 'group' => '', 856 'claim' => 'role_scope_mentor', 857 'isarray' => false 858 ], 859 'deployment_id' => [ 860 'suffix' => '', 861 'group' => '', 862 'claim' => 'deployment_id', 863 'isarray' => false 864 ], 865 'lti_message_type' => [ 866 'suffix' => '', 867 'group' => '', 868 'claim' => 'message_type', 869 'isarray' => false 870 ], 871 'lti_version' => [ 872 'suffix' => '', 873 'group' => '', 874 'claim' => 'version', 875 'isarray' => false 876 ], 877 'resource_link_description' => [ 878 'suffix' => '', 879 'group' => 'resource_link', 880 'claim' => 'description', 881 'isarray' => false 882 ], 883 'resource_link_id' => [ 884 'suffix' => '', 885 'group' => 'resource_link', 886 'claim' => 'id', 887 'isarray' => false 888 ], 889 'resource_link_title' => [ 890 'suffix' => '', 891 'group' => 'resource_link', 892 'claim' => 'title', 893 'isarray' => false 894 ], 895 'tool_consumer_info_product_family_code' => [ 896 'suffix' => '', 897 'group' => 'tool_platform', 898 'claim' => 'product_family_code', 899 'isarray' => false 900 ], 901 'tool_consumer_info_version' => [ 902 'suffix' => '', 903 'group' => 'tool_platform', 904 'claim' => 'version', 905 'isarray' => false 906 ], 907 'tool_consumer_instance_contact_email' => [ 908 'suffix' => '', 909 'group' => 'tool_platform', 910 'claim' => 'contact_email', 911 'isarray' => false 912 ], 913 'tool_consumer_instance_description' => [ 914 'suffix' => '', 915 'group' => 'tool_platform', 916 'claim' => 'description', 917 'isarray' => false 918 ], 919 'tool_consumer_instance_guid' => [ 920 'suffix' => '', 921 'group' => 'tool_platform', 922 'claim' => 'guid', 923 'isarray' => false 924 ], 925 'tool_consumer_instance_name' => [ 926 'suffix' => '', 927 'group' => 'tool_platform', 928 'claim' => 'name', 929 'isarray' => false 930 ], 931 'tool_consumer_instance_url' => [ 932 'suffix' => '', 933 'group' => 'tool_platform', 934 'claim' => 'url', 935 'isarray' => false 936 ], 937 'custom_context_memberships_url' => [ 938 'suffix' => 'nrps', 939 'group' => 'namesroleservice', 940 'claim' => 'context_memberships_url', 941 'isarray' => false 942 ], 943 'custom_context_memberships_versions' => [ 944 'suffix' => 'nrps', 945 'group' => 'namesroleservice', 946 'claim' => 'service_versions', 947 'isarray' => true 948 ], 949 'custom_gradebookservices_scope' => [ 950 'suffix' => 'ags', 951 'group' => 'endpoint', 952 'claim' => 'scope', 953 'isarray' => true 954 ], 955 'custom_lineitems_url' => [ 956 'suffix' => 'ags', 957 'group' => 'endpoint', 958 'claim' => 'lineitems', 959 'isarray' => false 960 ], 961 'custom_lineitem_url' => [ 962 'suffix' => 'ags', 963 'group' => 'endpoint', 964 'claim' => 'lineitem', 965 'isarray' => false 966 ], 967 'custom_results_url' => [ 968 'suffix' => 'ags', 969 'group' => 'endpoint', 970 'claim' => 'results', 971 'isarray' => false 972 ], 973 'custom_result_url' => [ 974 'suffix' => 'ags', 975 'group' => 'endpoint', 976 'claim' => 'result', 977 'isarray' => false 978 ], 979 'custom_scores_url' => [ 980 'suffix' => 'ags', 981 'group' => 'endpoint', 982 'claim' => 'scores', 983 'isarray' => false 984 ], 985 'custom_score_url' => [ 986 'suffix' => 'ags', 987 'group' => 'endpoint', 988 'claim' => 'score', 989 'isarray' => false 990 ], 991 'lis_outcome_service_url' => [ 992 'suffix' => 'bo', 993 'group' => 'basicoutcome', 994 'claim' => 'lis_outcome_service_url', 995 'isarray' => false 996 ], 997 'lis_result_sourcedid' => [ 998 'suffix' => 'bo', 999 'group' => 'basicoutcome', 1000 'claim' => 'lis_result_sourcedid', 1001 'isarray' => false 1002 ], 1003 ]; 1004 1005 $this->assertEquals($mapping, lti_get_jwt_claim_mapping()); 1006 } 1007 1008 /** 1009 * Test lti_build_standard_message(). 1010 */ 1011 public function test_lti_build_standard_message_institution_name_set() { 1012 global $CFG; 1013 1014 $this->resetAfterTest(); 1015 1016 $CFG->mod_lti_institution_name = 'some institution name lols'; 1017 1018 $course = $this->getDataGenerator()->create_course(); 1019 $instance = $this->getDataGenerator()->create_module('lti', 1020 [ 1021 'course' => $course->id, 1022 ] 1023 ); 1024 1025 $message = lti_build_standard_message($instance, '2', LTI_VERSION_1); 1026 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']); 1034 } 1035 1036 /** 1037 * Test lti_build_standard_message(). 1038 */ 1039 public function test_lti_build_standard_message_institution_name_not_set() { 1040 $this->resetAfterTest(); 1041 1042 $course = $this->getDataGenerator()->create_course(); 1043 $instance = $this->getDataGenerator()->create_module('lti', 1044 [ 1045 'course' => $course->id, 1046 ] 1047 ); 1048 1049 $message = lti_build_standard_message($instance, '2', LTI_VERSION_2); 1050 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']); 1058 } 1059 1060 /** 1061 * Test lti_verify_jwt_signature(). 1062 */ 1063 public function test_lti_verify_jwt_signature() { 1064 $this->resetAfterTest(); 1065 1066 $this->setAdminUser(); 1067 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'); 1074 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 1083 MwIDAQAB 1084 -----END PUBLIC KEY-----'; 1085 1086 $config->lti_keytype = LTI_RSA_KEY; 1087 1088 $typeid = lti_add_type($type, $config); 1089 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'); 1095 } 1096 1097 /** 1098 * Test lti_verify_jwt_signature_jwk(). 1099 */ 1100 public function test_lti_verify_jwt_signature_jwk() { 1101 $this->resetAfterTest(); 1102 1103 $this->setAdminUser(); 1104 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'); 1111 1112 $config = new stdClass(); 1113 $config->lti_publickeyset = dirname(__FILE__) . '/fixtures/test_keyset'; 1114 1115 $config->lti_keytype = LTI_JWK_KEYSET; 1116 1117 $typeid = lti_add_type($type, $config); 1118 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'; 1133 1134 lti_verify_jwt_signature($typeid, '', $jwt); 1135 } 1136 1137 /** 1138 * Test lti_verify_jwt_signature(). 1139 */ 1140 public function test_lti_verify_jwt_signature_with_lti2() { 1141 $this->resetAfterTest(); 1142 1143 $this->setAdminUser(); 1144 1145 // Create a tool proxy. 1146 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array()); 1147 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'); 1155 1156 $data = new stdClass(); 1157 $data->lti_contentitem = true; 1158 1159 $typeid = lti_add_type($type, $data); 1160 1161 $this->expectExceptionMessage('JWT security not supported with LTI 2'); 1162 lti_verify_jwt_signature($typeid, '', ''); 1163 } 1164 1165 /** 1166 * Test lti_verify_jwt_signature(). 1167 */ 1168 public function test_lti_verify_jwt_signature_no_consumer_key() { 1169 $this->resetAfterTest(); 1170 1171 $this->setAdminUser(); 1172 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'); 1180 1181 $config = new stdClass(); 1182 $typeid = lti_add_type($type, $config); 1183 1184 $this->expectExceptionMessage(get_string('errorincorrectconsumerkey', 'mod_lti')); 1185 lti_verify_jwt_signature($typeid, '', ''); 1186 } 1187 1188 /** 1189 * Test lti_verify_jwt_signature(). 1190 */ 1191 public function test_lti_verify_jwt_signature_no_public_key() { 1192 $this->resetAfterTest(); 1193 $this->setAdminUser(); 1194 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'); 1202 1203 $config = new stdClass(); 1204 $config->lti_keytype = LTI_RSA_KEY; 1205 $typeid = lti_add_type($type, $config); 1206 1207 $this->expectExceptionMessage('No public key configured'); 1208 lti_verify_jwt_signature($typeid, 'consumerkey', ''); 1209 } 1210 1211 /** 1212 * Test lti_convert_content_items(). 1213 */ 1214 public function test_lti_convert_content_items() { 1215 $contentitems = []; 1216 $contentitems[] = [ 1217 'type' => 'ltiResourceLink', 1218 'url' => 'http://example.com/messages/launch', 1219 'title' => 'Test title', 1220 'text' => 'Test text', 1221 'iframe' => [] 1222 ]; 1223 $contentitems[] = [ 1224 'type' => 'ltiResourceLink', 1225 'url' => 'http://example.com/messages/launch2', 1226 'title' => 'Test title2', 1227 'text' => 'Test text2', 1228 'iframe' => [ 1229 'height' => 200, 1230 'width' => 300 1231 ], 1232 'window' => [] 1233 ]; 1234 $contentitems[] = [ 1235 'type' => 'ltiResourceLink', 1236 'url' => 'http://example.com/messages/launch3', 1237 'title' => 'Test title3', 1238 'text' => 'Test text3', 1239 'window' => [ 1240 'targetName' => 'test-win', 1241 'height' => 400 1242 ] 1243 ]; 1244 1245 $contentitems = json_encode($contentitems); 1246 1247 $json = lti_convert_content_items($contentitems); 1248 1249 $jsondecode = json_decode($json); 1250 1251 $strcontext = '@context'; 1252 $strgraph = '@graph'; 1253 $strtype = '@type'; 1254 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'; 1263 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'; 1274 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'; 1285 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; 1292 1293 $this->assertEquals($expected, $jsondecode); 1294 } 1295 1296 /** 1297 * Test adding a single gradable item through content item. 1298 */ 1299 public function test_lti_tool_configuration_from_content_item_single_gradable() { 1300 $this->resetAfterTest(); 1301 $this->setAdminUser(); 1302 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); 1309 1310 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1311 $contentitems = []; 1312 $contentitems[] = [ 1313 'type' => 'ltiResourceLink', 1314 'url' => 'http://example.com/messages/launch', 1315 'title' => 'Test title', 1316 'lineItem' => [ 1317 'resourceId' => 'r12345', 1318 'tag' => 'final', 1319 'scoreMaximum' => 10.0 1320 ], 1321 'frame' => [] 1322 ]; 1323 $contentitemsjson13 = json_encode($contentitems); 1324 $json11 = lti_convert_content_items($contentitemsjson13); 1325 1326 $config = lti_tool_configuration_from_content_item($typeid, 1327 'ContentItemSelection', 1328 $type->ltiversion, 1329 'ConsumerKey', 1330 $json11); 1331 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); 1337 } 1338 1339 /** 1340 * Test adding multiple gradable items through content item. 1341 */ 1342 public function test_lti_tool_configuration_from_content_item_multiple() { 1343 $this->resetAfterTest(); 1344 $this->setAdminUser(); 1345 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); 1352 1353 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1354 $contentitems = []; 1355 $contentitems[] = [ 1356 'type' => 'ltiResourceLink', 1357 'url' => 'http://example.com/messages/launch', 1358 'title' => 'Test title', 1359 'text' => 'Test text', 1360 'icon' => [ 1361 'url' => 'http://lti.example.com/image.jpg', 1362 'width' => 100 1363 ], 1364 'frame' => [] 1365 ]; 1366 $contentitems[] = [ 1367 'type' => 'ltiResourceLink', 1368 'url' => 'http://example.com/messages/launchgraded', 1369 'title' => 'Test Graded', 1370 'lineItem' => [ 1371 'resourceId' => 'r12345', 1372 'tag' => 'final', 1373 'scoreMaximum' => 10.0 1374 ], 1375 'frame' => [] 1376 ]; 1377 $contentitemsjson13 = json_encode($contentitems); 1378 $json11 = lti_convert_content_items($contentitemsjson13); 1379 1380 $config = lti_tool_configuration_from_content_item($typeid, 1381 'ContentItemSelection', 1382 $type->ltiversion, 1383 'ConsumerKey', 1384 $json11); 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); 1395 } 1396 1397 /** 1398 * Test adding a single non gradable item through content item. 1399 */ 1400 public function test_lti_tool_configuration_from_content_item_single() { 1401 $this->resetAfterTest(); 1402 $this->setAdminUser(); 1403 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); 1409 1410 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1411 $contentitems = []; 1412 $contentitems[] = [ 1413 'type' => 'ltiResourceLink', 1414 'url' => 'http://example.com/messages/launch', 1415 'title' => 'Test title', 1416 'text' => 'Test text', 1417 'icon' => [ 1418 'url' => 'http://lti.example.com/image.jpg', 1419 'width' => 100 1420 ], 1421 'frame' => [] 1422 ]; 1423 $contentitemsjson13 = json_encode($contentitems); 1424 $json11 = lti_convert_content_items($contentitemsjson13); 1425 1426 $config = lti_tool_configuration_from_content_item($typeid, 1427 'ContentItemSelection', 1428 $type->ltiversion, 1429 'ConsumerKey', 1430 $json11); 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); 1436 1437 } 1438 1439 /** 1440 * Test lti_sign_jwt(). 1441 */ 1442 public function test_lti_sign_jwt() { 1443 $this->resetAfterTest(); 1444 1445 $this->setAdminUser(); 1446 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'); 1454 1455 $config = new stdClass(); 1456 $typeid = lti_add_type($type, $config); 1457 1458 $params = []; 1459 $params['roles'] = 'urn:lti:role:ims/lis/testrole,' . 1460 'urn:lti:instrole:ims/lis/testinstrole,' . 1461 'urn:lti:sysrole:ims/lis/testsysrole,' . 1462 'hi'; 1463 $params['accept_copy_advice'] = [ 1464 'suffix' => 'dl', 1465 'group' => 'deep_linking_settings', 1466 'claim' => 'accept_copy_advice', 1467 'isarray' => false 1468 ]; 1469 $params['lis_result_sourcedid'] = [ 1470 'suffix' => 'bos', 1471 'group' => 'basicoutcomesservice', 1472 'claim' => 'lis_result_sourcedid', 1473 'isarray' => false 1474 ]; 1475 $endpoint = 'https://www.example.com/moodle'; 1476 $oauthconsumerkey = 'consumerkey'; 1477 $nonce = ''; 1478 1479 $jwt = lti_sign_jwt($params, $endpoint, $oauthconsumerkey, $typeid, $nonce); 1480 1481 $this->assertArrayHasKey('id_token', $jwt); 1482 $this->assertNotEmpty($jwt['id_token']); 1483 } 1484 1485 /** 1486 * Test lti_convert_from_jwt() 1487 */ 1488 public function test_lti_convert_from_jwt() { 1489 $this->resetAfterTest(); 1490 1491 $this->setAdminUser(); 1492 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'); 1500 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 1509 MwIDAQAB 1510 -----END PUBLIC KEY-----'; 1511 $config->lti_keytype = LTI_RSA_KEY; 1512 1513 $typeid = lti_add_type($type, $config); 1514 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'); 1520 1521 $this->assertEquals('sso.example.com', $params['oauth_consumer_key']); 1522 $this->assertEquals('John Doe', $params['lis_person_name_full']); 1523 } 1524 1525 /** 1526 * Test lti_get_permitted_service_scopes(). 1527 */ 1528 public function test_lti_get_permitted_service_scopes() { 1529 $this->resetAfterTest(); 1530 1531 $this->setAdminUser(); 1532 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'); 1539 1540 $typeconfig = new stdClass(); 1541 $typeconfig->lti_acceptgrades = true; 1542 1543 $typeid = lti_add_type($type, $typeconfig); 1544 1545 $tool = lti_get_type($typeid); 1546 1547 $config = lti_get_type_config($typeid); 1548 $permittedscopes = lti_get_permitted_service_scopes($tool, $config); 1549 1550 $expected = [ 1551 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome' 1552 ]; 1553 $this->assertEquals($expected, $permittedscopes); 1554 } 1555 1556 /** 1557 * Test get_tool_type_config(). 1558 */ 1559 public function test_get_tool_type_config() { 1560 $this->resetAfterTest(); 1561 1562 $this->setAdminUser(); 1563 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'); 1571 1572 $config = new stdClass(); 1573 1574 $typeid = lti_add_type($type, $config); 1575 1576 $type = lti_get_type($typeid); 1577 1578 $typeconfig = get_tool_type_config($type); 1579 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']); 1586 } 1587 1588 /** 1589 * Test lti_new_access_token(). 1590 */ 1591 public function test_lti_new_access_token() { 1592 global $DB; 1593 1594 $this->resetAfterTest(); 1595 1596 $this->setAdminUser(); 1597 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'); 1605 1606 $config = new stdClass(); 1607 1608 $typeid = lti_add_type($type, $config); 1609 1610 $scopes = ['lti_some_scope', 'lti_another_scope']; 1611 1612 lti_new_access_token($typeid, $scopes); 1613 1614 $token = $DB->get_records('lti_access_tokens'); 1615 $this->assertEquals(1, count($token)); 1616 1617 $token = reset($token); 1618 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); 1623 } 1624 1625 /** 1626 * Test lti_build_login_request(). 1627 */ 1628 public function test_lti_build_login_request() { 1629 global $USER, $CFG; 1630 1631 $this->resetAfterTest(); 1632 1633 $USER->id = 123456789; 1634 1635 $course = $this->getDataGenerator()->create_course(); 1636 $instance = $this->getDataGenerator()->create_module('lti', 1637 [ 1638 'course' => $course->id, 1639 ] 1640 ); 1641 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'; 1646 1647 $request = lti_build_login_request($course->id, $instance->id, $instance, $config, 'basic-lti-launch-request'); 1648 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']); 1655 } 1656 1657 /** 1658 * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle). 1659 */ 1660 public function test_lti_get_launch_data_default_organizationid_unset_usehost() { 1661 global $DB; 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'); 1671 } 1672 1673 /** 1674 * Test default org id is set to host when config is usehost. 1675 */ 1676 public function test_lti_get_launch_data_default_organizationid_set_usehost() { 1677 global $DB; 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'); 1688 } 1689 1690 /** 1691 * Test default org id is set to site id when config is usesiteid. 1692 */ 1693 public function test_lti_get_launch_data_default_organizationid_set_usesiteid() { 1694 global $DB; 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())); 1705 } 1706 1707 /** 1708 * Test orgid can be overridden in which case default is ignored. 1709 */ 1710 public function test_lti_get_launch_data_default_organizationid_orgid_override() { 1711 global $DB; 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!'); 1722 } 1723 1724 public function test_get_course_history() { 1725 global $DB; 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]); 1741 } 1742 1743 /** 1744 * Test the lti_get_ims_role helper function. 1745 * 1746 * @dataProvider lti_get_ims_role_provider 1747 * @covers ::lti_get_ims_role() 1748 * 1749 * @param bool $islti2 whether the method is called with LTI 2.0 role names or not. 1750 * @param string $rolename the name of the role (student, teacher, admin) 1751 * @param null|string $switchedto the role to switch to, or false if not using the 'switch to' functionality. 1752 * @param string $expected the expected role name. 1753 */ 1754 public function test_lti_get_ims_role(bool $islti2, string $rolename, ?string $switchedto, string $expected) { 1755 global $DB; 1756 $this->resetAfterTest(); 1757 1758 $course = $this->getDataGenerator()->create_course(); 1759 $user = $rolename == 'admin' ? get_admin() : $this->getDataGenerator()->create_and_enrol($course, $rolename); 1760 1761 if ($switchedto) { 1762 $this->setUser($user); 1763 $role = $DB->get_record('role', array('shortname' => $switchedto)); 1764 role_switch($role->id, context_course::instance($course->id)); 1765 } 1766 1767 $this->assertEquals($expected, lti_get_ims_role($user, 0, $course->id, $islti2)); 1768 } 1769 1770 /** 1771 * Data provider for testing lti_get_ims_role. 1772 * 1773 * @return array[] the test case data. 1774 */ 1775 public function lti_get_ims_role_provider() { 1776 return [ 1777 'Student, LTI 1.1, no role switch' => [ 1778 'islti2' => false, 1779 'rolename' => 'student', 1780 'switchedto' => null, 1781 'expected' => 'Learner' 1782 ], 1783 'Student, LTI 2.0, no role switch' => [ 1784 'islti2' => true, 1785 'rolename' => 'student', 1786 'switchedto' => null, 1787 'expected' => 'Learner' 1788 ], 1789 'Teacher, LTI 1.1, no role switch' => [ 1790 'islti2' => false, 1791 'rolename' => 'editingteacher', 1792 'switchedto' => null, 1793 'expected' => 'Instructor' 1794 ], 1795 'Teacher, LTI 2.0, no role switch' => [ 1796 'islti2' => true, 1797 'rolename' => 'editingteacher', 1798 'switchedto' => null, 1799 'expected' => 'Instructor' 1800 ], 1801 'Admin, LTI 1.1, no role switch' => [ 1802 'islti2' => false, 1803 'rolename' => 'admin', 1804 'switchedto' => null, 1805 'expected' => 'Instructor,urn:lti:sysrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Administrator' 1806 ], 1807 'Admin, LTI 2.0, no role switch' => [ 1808 'islti2' => true, 1809 'rolename' => 'admin', 1810 'switchedto' => null, 1811 'expected' => 'Instructor,http://purl.imsglobal.org/vocab/lis/v2/person#Administrator' 1812 ], 1813 'Admin, LTI 1.1, role switch student' => [ 1814 'islti2' => false, 1815 'rolename' => 'admin', 1816 'switchedto' => 'student', 1817 'expected' => 'Learner' 1818 ], 1819 'Admin, LTI 2.0, role switch student' => [ 1820 'islti2' => true, 1821 'rolename' => 'admin', 1822 'switchedto' => 'student', 1823 'expected' => 'Learner' 1824 ], 1825 'Admin, LTI 1.1, role switch teacher' => [ 1826 'islti2' => false, 1827 'rolename' => 'admin', 1828 'switchedto' => 'editingteacher', 1829 'expected' => 'Instructor' 1830 ], 1831 'Admin, LTI 2.0, role switch teacher' => [ 1832 'islti2' => true, 1833 'rolename' => 'admin', 1834 'switchedto' => 'editingteacher', 1835 'expected' => 'Instructor' 1836 ], 1837 ]; 1838 } 1839 1840 /** 1841 * Create an LTI Tool. 1842 * 1843 * @param object $config tool config. 1844 * 1845 * @return object tool. 1846 */ 1847 private function create_type(object $config) { 1848 $type = new stdClass(); 1849 $type->state = LTI_TOOL_STATE_CONFIGURED; 1850 $type->name = "Test tool"; 1851 $type->description = "Example description"; 1852 $type->clientid = "Test client ID"; 1853 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1854 1855 $configbase = new stdClass(); 1856 $configbase->lti_acceptgrades = LTI_SETTING_NEVER; 1857 $configbase->lti_sendname = LTI_SETTING_NEVER; 1858 $configbase->lti_sendemailaddr = LTI_SETTING_NEVER; 1859 $mergedconfig = (object) array_merge( (array) $configbase, (array) $config); 1860 $typeid = lti_add_type($type, $mergedconfig); 1861 return lti_get_type($typeid); 1862 } 1863 1864 /** 1865 * Create an LTI Instance for the tool in a given course. 1866 * 1867 * @param object $type tool for which an instance should be added. 1868 * @param object $course course where the instance should be added. 1869 * 1870 * @return object instance. 1871 */ 1872 private function create_instance(object $type, object $course) { 1873 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1874 return $generator->create_instance(array('course' => $course->id, 1875 'toolurl' => $type->baseurl, 1876 'typeid' => $type->id 1877 ), array()); 1878 } 1879 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body