Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
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 namespace mod_lti; 50 51 use mod_lti_external; 52 use mod_lti_testcase; 53 54 defined('MOODLE_INTERNAL') || die; 55 56 global $CFG; 57 require_once($CFG->dirroot . '/mod/lti/locallib.php'); 58 require_once($CFG->dirroot . '/mod/lti/servicelib.php'); 59 require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php'); 60 61 /** 62 * Local library tests 63 * 64 * @package mod_lti 65 * @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com) 66 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 67 */ 68 class locallib_test extends mod_lti_testcase { 69 70 public function test_split_custom_parameters() { 71 $this->resetAfterTest(); 72 73 $tool = new \stdClass(); 74 $tool->enabledcapability = ''; 75 $tool->parameter = ''; 76 $tool->ltiversion = 'LTI-1p0'; 77 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "x=1\ny=2", false), 78 array('custom_x' => '1', 'custom_y' => '2')); 79 80 // Check params with caps. 81 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), "X=1", true), 82 array('custom_x' => '1', 'custom_X' => '1')); 83 84 // Removed repeat of previous test with a semicolon separator. 85 86 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 'Review:Chapter=1.2.56', true), 87 array( 88 'custom_review_chapter' => '1.2.56', 89 'custom_Review:Chapter' => '1.2.56')); 90 91 $this->assertEquals(lti_split_custom_parameters(null, $tool, array(), 92 'Complex!@#$^*(){}[]KEY=Complex!@#$^*;(){}[]½Value', true), 93 array( 94 'custom_complex____________key' => 'Complex!@#$^*;(){}[]½Value', 95 'custom_Complex!@#$^*(){}[]KEY' => 'Complex!@#$^*;(){}[]½Value')); 96 97 // Test custom parameter that returns $USER property. 98 $user = $this->getDataGenerator()->create_user(array('middlename' => 'SOMETHING')); 99 $this->setUser($user); 100 $this->assertEquals(array('custom_x' => '1', 'custom_y' => 'SOMETHING'), 101 lti_split_custom_parameters(null, $tool, array(), "x=1\ny=\$Person.name.middle", false)); 102 } 103 104 /** 105 * This test has been disabled because the test-tool is 106 * being moved and probably it won't work anymore for this. 107 * We should be testing here local stuff only and leave 108 * outside-checks to the conformance tests. MDL-30347 109 */ 110 public function disabled_test_sign_parameters() { 111 $correct = array ( 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing', 112 'ext_submit' => 'Click Me', 'lti_message_type' => 'basic-lti-launch-request', 'lti_version' => 'LTI-1p0', 113 'oauth_consumer_key' => 'lmsng.school.edu', 'oauth_nonce' => '47458148e33a8f9dafb888c3684cf476', 114 'oauth_signature' => 'qWgaBIezihCbeHgcwUy14tZcyDQ=', 'oauth_signature_method' => 'HMAC-SHA1', 115 'oauth_timestamp' => '1307141660', 'oauth_version' => '1.0', 'resource_link_id' => '123', 116 'resource_link_title' => 'Weekly Blog', 'roles' => 'Learner', 'tool_consumer_instance_guid' => 'lmsng.school.edu', 117 'user_id' => '789'); 118 119 $requestparams = array('resource_link_id' => '123', 'resource_link_title' => 'Weekly Blog', 'user_id' => '789', 120 'roles' => 'Learner', 'context_id' => '12345', 'context_label' => 'SI124', 'context_title' => 'Social Computing'); 121 122 $parms = lti_sign_parameters($requestparams, 'http://www.imsglobal.org/developer/LTI/tool.php', 'POST', 123 'lmsng.school.edu', 'secret', 'Click Me', 'lmsng.school.edu' /*, $org_desc*/); 124 $this->assertTrue(isset($parms['oauth_nonce'])); 125 $this->assertTrue(isset($parms['oauth_signature'])); 126 $this->assertTrue(isset($parms['oauth_timestamp'])); 127 128 // Those things that are hard to mock. 129 $correct['oauth_nonce'] = $parms['oauth_nonce']; 130 $correct['oauth_signature'] = $parms['oauth_signature']; 131 $correct['oauth_timestamp'] = $parms['oauth_timestamp']; 132 ksort($parms); 133 ksort($correct); 134 $this->assertEquals($parms, $correct); 135 } 136 137 /** 138 * This test has been disabled because, since its creation, 139 * the sourceId generation has changed and surely this is outdated. 140 * Some day these should be replaced by proper tests, but until then 141 * conformance tests say this is working. MDL-30347 142 */ 143 public function disabled_test_parse_grade_replace_message() { 144 $message = ' 145 <imsx_POXEnvelopeRequest xmlns = "http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0"> 146 <imsx_POXHeader> 147 <imsx_POXRequestHeaderInfo> 148 <imsx_version>V1.0</imsx_version> 149 <imsx_messageIdentifier>999998123</imsx_messageIdentifier> 150 </imsx_POXRequestHeaderInfo> 151 </imsx_POXHeader> 152 <imsx_POXBody> 153 <replaceResultRequest> 154 <resultRecord> 155 <sourcedGUID> 156 <sourcedId>' . 157 '{"data":{"instanceid":"2","userid":"2"},"hash":' . 158 '"0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b"}' . 159 '</sourcedId> 160 </sourcedGUID> 161 <result> 162 <resultScore> 163 <language>en-us</language> 164 <textString>0.92</textString> 165 </resultScore> 166 </result> 167 </resultRecord> 168 </replaceResultRequest> 169 </imsx_POXBody> 170 </imsx_POXEnvelopeRequest> 171 '; 172 173 $parsed = lti_parse_grade_replace_message(new SimpleXMLElement($message)); 174 175 $this->assertEquals($parsed->userid, '2'); 176 $this->assertEquals($parsed->instanceid, '2'); 177 $this->assertEquals($parsed->sourcedidhash, '0b5078feab59b9938c333ceaae21d8e003a7b295e43cdf55338445254421076b'); 178 179 $ltiinstance = (object)array('servicesalt' => '4e5fcc06de1d58.44963230'); 180 181 lti_verify_sourcedid($ltiinstance, $parsed); 182 } 183 184 public function test_lti_ensure_url_is_https() { 185 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('http://moodle.org')); 186 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('moodle.org')); 187 $this->assertEquals('https://moodle.org', lti_ensure_url_is_https('https://moodle.org')); 188 } 189 190 /** 191 * Test lti_get_url_thumbprint against various URLs 192 */ 193 public function test_lti_get_url_thumbprint() { 194 // Note: trailing and double slash are expected right now. Must evaluate if it must be removed at some point. 195 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://MOODLE.ORG')); 196 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('http://www.moodle.org')); 197 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('https://www.moodle.org')); 198 $this->assertEquals('moodle.org/', lti_get_url_thumbprint('moodle.org')); 199 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('http://moodle.org/this/is/moodle')); 200 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('https://moodle.org/this/is/moodle')); 201 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle')); 202 $this->assertEquals('moodle.org//this/is/moodle', lti_get_url_thumbprint('moodle.org/this/is/moodle?')); 203 $this->assertEquals('moodle.org//this/is/moodle?foo=bar', lti_get_url_thumbprint('moodle.org/this/is/moodle?foo=bar')); 204 } 205 206 /* 207 * Verify that lti_build_request does handle resource_link_id as expected 208 */ 209 public function test_lti_buid_request_resource_link_id() { 210 $this->resetAfterTest(); 211 212 self::setUser($this->getDataGenerator()->create_user()); 213 $course = $this->getDataGenerator()->create_course(); 214 $instance = $this->getDataGenerator()->create_module('lti', array( 215 'intro' => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines", 216 'introformat' => FORMAT_HTML, 217 'course' => $course->id, 218 )); 219 220 $typeconfig = array( 221 'acceptgrades' => 1, 222 'forcessl' => 0, 223 'sendname' => 2, 224 'sendemailaddr' => 2, 225 'customparameters' => '', 226 ); 227 228 // Normal call, we expect $instance->id to be used as resource_link_id. 229 $params = lti_build_request($instance, $typeconfig, $course, null); 230 $this->assertSame($instance->id, $params['resource_link_id']); 231 232 // If there is a resource_link_id set, it gets precedence. 233 $instance->resource_link_id = $instance->id + 99; 234 $params = lti_build_request($instance, $typeconfig, $course, null); 235 $this->assertSame($instance->resource_link_id, $params['resource_link_id']); 236 237 // With none set, resource_link_id is not set either. 238 unset($instance->id); 239 unset($instance->resource_link_id); 240 $params = lti_build_request($instance, $typeconfig, $course, null); 241 $this->assertArrayNotHasKey('resource_link_id', $params); 242 } 243 244 /** 245 * Test lti_build_request's resource_link_description and ensure 246 * that the newlines in the description are correct. 247 */ 248 public function test_lti_build_request_description() { 249 $this->resetAfterTest(); 250 251 self::setUser($this->getDataGenerator()->create_user()); 252 $course = $this->getDataGenerator()->create_course(); 253 $instance = $this->getDataGenerator()->create_module('lti', array( 254 'intro' => "<p>This</p>\nhas\r\n<p>some</p>\nnew\n\rlines", 255 'introformat' => FORMAT_HTML, 256 'course' => $course->id, 257 )); 258 259 $typeconfig = array( 260 'acceptgrades' => 1, 261 'forcessl' => 0, 262 'sendname' => 2, 263 'sendemailaddr' => 2, 264 'customparameters' => '', 265 ); 266 267 $params = lti_build_request($instance, $typeconfig, $course, null); 268 269 $ncount = substr_count($params['resource_link_description'], "\n"); 270 $this->assertGreaterThan(0, $ncount); 271 272 $rcount = substr_count($params['resource_link_description'], "\r"); 273 $this->assertGreaterThan(0, $rcount); 274 275 $this->assertEquals($ncount, $rcount, 'The number of \n characters should be the same as the number of \r characters'); 276 277 $rncount = substr_count($params['resource_link_description'], "\r\n"); 278 $this->assertGreaterThan(0, $rncount); 279 280 $this->assertEquals($ncount, $rncount, 'All newline characters should be a combination of \r\n'); 281 } 282 283 /** 284 * Tests lti_prepare_type_for_save's handling of the "Force SSL" configuration. 285 */ 286 public function test_lti_prepare_type_for_save_forcessl() { 287 $type = new \stdClass(); 288 $config = new \stdClass(); 289 290 // Try when the forcessl config property is not set. 291 lti_prepare_type_for_save($type, $config); 292 $this->assertObjectHasAttribute('lti_forcessl', $config); 293 $this->assertEquals(0, $config->lti_forcessl); 294 $this->assertEquals(0, $type->forcessl); 295 296 // Try when forcessl config property is set. 297 $config->lti_forcessl = 1; 298 lti_prepare_type_for_save($type, $config); 299 $this->assertObjectHasAttribute('lti_forcessl', $config); 300 $this->assertEquals(1, $config->lti_forcessl); 301 $this->assertEquals(1, $type->forcessl); 302 303 // Try when forcessl config property is set to 0. 304 $config->lti_forcessl = 0; 305 lti_prepare_type_for_save($type, $config); 306 $this->assertObjectHasAttribute('lti_forcessl', $config); 307 $this->assertEquals(0, $config->lti_forcessl); 308 $this->assertEquals(0, $type->forcessl); 309 } 310 311 /** 312 * Tests lti_load_type_from_cartridge and lti_load_type_if_cartridge 313 */ 314 public function test_lti_load_type_from_cartridge() { 315 $type = new \stdClass(); 316 $type->lti_toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'); 317 318 lti_load_type_if_cartridge($type); 319 320 $this->assertEquals('Example tool', $type->lti_typename); 321 $this->assertEquals('Example tool description', $type->lti_description); 322 $this->assertEquals('http://www.example.com/lti/provider.php', $type->lti_toolurl); 323 $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $type->lti_icon); 324 $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type->lti_secureicon); 325 } 326 327 /** 328 * Tests lti_load_tool_from_cartridge and lti_load_tool_if_cartridge 329 */ 330 public function test_lti_load_tool_from_cartridge() { 331 $lti = new \stdClass(); 332 $lti->toolurl = $this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'); 333 334 lti_load_tool_if_cartridge($lti); 335 336 $this->assertEquals('Example tool', $lti->name); 337 $this->assertEquals('Example tool description', $lti->intro); 338 $this->assertEquals('http://www.example.com/lti/provider.php', $lti->toolurl); 339 $this->assertEquals('https://www.example.com/lti/provider.php', $lti->securetoolurl); 340 $this->assertEquals('http://download.moodle.org/unittest/test.jpg', $lti->icon); 341 $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $lti->secureicon); 342 } 343 344 /** 345 * Tests for lti_build_content_item_selection_request(). 346 */ 347 public function test_lti_build_content_item_selection_request() { 348 $this->resetAfterTest(); 349 350 $this->setAdminUser(); 351 // Create a tool proxy. 352 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array()); 353 354 // Create a tool type, associated with that proxy. 355 $type = new \stdClass(); 356 $data = new \stdClass(); 357 $data->lti_contentitem = true; 358 $type->state = LTI_TOOL_STATE_CONFIGURED; 359 $type->name = "Test tool"; 360 $type->description = "Example description"; 361 $type->toolproxyid = $proxy->id; 362 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 363 364 $typeid = lti_add_type($type, $data); 365 366 $typeconfig = lti_get_type_config($typeid); 367 368 $course = $this->getDataGenerator()->create_course(); 369 $returnurl = new \moodle_url('/'); 370 371 // Default parameters. 372 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl); 373 $this->assertNotEmpty($result); 374 $this->assertNotEmpty($result->params); 375 $this->assertNotEmpty($result->url); 376 $params = $result->params; 377 $url = $result->url; 378 $this->assertEquals($typeconfig['toolurl'], $url); 379 $this->assertEquals('ContentItemSelectionRequest', $params['lti_message_type']); 380 $this->assertEquals(LTI_VERSION_1, $params['lti_version']); 381 $this->assertEquals('application/vnd.ims.lti.v1.ltilink', $params['accept_media_types']); 382 $this->assertEquals('frame,iframe,window', $params['accept_presentation_document_targets']); 383 $this->assertEquals($returnurl->out(false), $params['content_item_return_url']); 384 $this->assertEquals('false', $params['accept_unsigned']); 385 $this->assertEquals('true', $params['accept_multiple']); 386 $this->assertEquals('false', $params['accept_copy_advice']); 387 $this->assertEquals('false', $params['auto_create']); 388 $this->assertEquals($type->name, $params['title']); 389 $this->assertFalse(isset($params['resource_link_id'])); 390 $this->assertFalse(isset($params['resource_link_title'])); 391 $this->assertFalse(isset($params['resource_link_description'])); 392 $this->assertFalse(isset($params['launch_presentation_return_url'])); 393 $this->assertFalse(isset($params['lis_result_sourcedid'])); 394 $this->assertEquals($params['tool_consumer_instance_guid'], 'www.example.com'); 395 396 // Custom parameters. 397 $title = 'My custom title'; 398 $text = 'This is the tool description'; 399 $mediatypes = ['image/*', 'video/*']; 400 $targets = ['embed', 'iframe']; 401 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets, 402 true, true, true, true, true); 403 $this->assertNotEmpty($result); 404 $this->assertNotEmpty($result->params); 405 $this->assertNotEmpty($result->url); 406 $params = $result->params; 407 $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']); 408 $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']); 409 $this->assertEquals('true', $params['accept_unsigned']); 410 $this->assertEquals('true', $params['accept_multiple']); 411 $this->assertEquals('true', $params['accept_copy_advice']); 412 $this->assertEquals('true', $params['auto_create']); 413 $this->assertEquals($title, $params['title']); 414 $this->assertEquals($text, $params['text']); 415 416 // Invalid flag values. 417 $result = lti_build_content_item_selection_request($typeid, $course, $returnurl, $title, $text, $mediatypes, $targets, 418 'aa', -1, 0, 1, 0xabc); 419 $this->assertNotEmpty($result); 420 $this->assertNotEmpty($result->params); 421 $this->assertNotEmpty($result->url); 422 $params = $result->params; 423 $this->assertEquals(implode(',', $mediatypes), $params['accept_media_types']); 424 $this->assertEquals(implode(',', $targets), $params['accept_presentation_document_targets']); 425 $this->assertEquals('false', $params['accept_unsigned']); 426 $this->assertEquals('false', $params['accept_multiple']); 427 $this->assertEquals('false', $params['accept_copy_advice']); 428 $this->assertEquals('false', $params['auto_create']); 429 $this->assertEquals($title, $params['title']); 430 $this->assertEquals($text, $params['text']); 431 } 432 433 /** 434 * Test for lti_build_content_item_selection_request() with nonexistent tool type ID parameter. 435 */ 436 public function test_lti_build_content_item_selection_request_invalid_tooltype() { 437 $this->resetAfterTest(); 438 439 $this->setAdminUser(); 440 $course = $this->getDataGenerator()->create_course(); 441 $returnurl = new \moodle_url('/'); 442 443 // Should throw Exception on non-existent tool type. 444 $this->expectException('moodle_exception'); 445 lti_build_content_item_selection_request(1, $course, $returnurl); 446 } 447 448 /** 449 * Test for lti_build_content_item_selection_request() with invalid media types parameter. 450 */ 451 public function test_lti_build_content_item_selection_request_invalid_mediatypes() { 452 $this->resetAfterTest(); 453 454 $this->setAdminUser(); 455 456 // Create a tool type, associated with that proxy. 457 $type = new \stdClass(); 458 $data = new \stdClass(); 459 $data->lti_contentitem = true; 460 $type->state = LTI_TOOL_STATE_CONFIGURED; 461 $type->name = "Test tool"; 462 $type->description = "Example description"; 463 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 464 465 $typeid = lti_add_type($type, $data); 466 $course = $this->getDataGenerator()->create_course(); 467 $returnurl = new \moodle_url('/'); 468 469 // Should throw coding_exception on non-array media types. 470 $mediatypes = 'image/*,video/*'; 471 $this->expectException('coding_exception'); 472 lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', $mediatypes); 473 } 474 475 /** 476 * Test for lti_build_content_item_selection_request() with invalid presentation targets parameter. 477 */ 478 public function test_lti_build_content_item_selection_request_invalid_presentationtargets() { 479 $this->resetAfterTest(); 480 481 $this->setAdminUser(); 482 483 // Create a tool type, associated with that proxy. 484 $type = new \stdClass(); 485 $data = new \stdClass(); 486 $data->lti_contentitem = true; 487 $type->state = LTI_TOOL_STATE_CONFIGURED; 488 $type->name = "Test tool"; 489 $type->description = "Example description"; 490 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 491 492 $typeid = lti_add_type($type, $data); 493 $course = $this->getDataGenerator()->create_course(); 494 $returnurl = new \moodle_url('/'); 495 496 // Should throw coding_exception on non-array presentation targets. 497 $targets = 'frame,iframe'; 498 $this->expectException('coding_exception'); 499 lti_build_content_item_selection_request($typeid, $course, $returnurl, '', '', [], $targets); 500 } 501 502 /** 503 * Provider for test_lti_get_best_tool_by_url. 504 * 505 * @return array of [urlToTest, expectedTool, allTools] 506 */ 507 public function lti_get_best_tool_by_url_provider() { 508 $tools = [ 509 (object) [ 510 'name' => 'Here', 511 'baseurl' => 'https://example.com/i/am/?where=here', 512 'tooldomain' => 'example.com', 513 'state' => LTI_TOOL_STATE_CONFIGURED, 514 'course' => SITEID 515 ], 516 (object) [ 517 'name' => 'There', 518 'baseurl' => 'https://example.com/i/am/?where=there', 519 'tooldomain' => 'example.com', 520 'state' => LTI_TOOL_STATE_CONFIGURED, 521 'course' => SITEID 522 ], 523 (object) [ 524 'name' => 'Not here', 525 'baseurl' => 'https://example.com/i/am/?where=not/here', 526 'tooldomain' => 'example.com', 527 'state' => LTI_TOOL_STATE_CONFIGURED, 528 'course' => SITEID 529 ], 530 (object) [ 531 'name' => 'Here', 532 'baseurl' => 'https://example.com/i/am/', 533 'tooldomain' => 'example.com', 534 'state' => LTI_TOOL_STATE_CONFIGURED, 535 'course' => SITEID 536 ], 537 (object) [ 538 'name' => 'Here', 539 'baseurl' => 'https://example.com/i/was', 540 'tooldomain' => 'example.com', 541 'state' => LTI_TOOL_STATE_CONFIGURED, 542 'course' => SITEID 543 ], 544 (object) [ 545 'name' => 'Here', 546 'baseurl' => 'https://badexample.com/i/am/?where=here', 547 'tooldomain' => 'badexample.com', 548 'state' => LTI_TOOL_STATE_CONFIGURED, 549 'course' => SITEID 550 ], 551 ]; 552 553 $data = [ 554 [ 555 'url' => $tools[0]->baseurl, 556 'expected' => $tools[0], 557 ], 558 [ 559 'url' => $tools[1]->baseurl, 560 'expected' => $tools[1], 561 ], 562 [ 563 'url' => $tools[2]->baseurl, 564 'expected' => $tools[2], 565 ], 566 [ 567 'url' => $tools[3]->baseurl, 568 'expected' => $tools[3], 569 ], 570 [ 571 'url' => $tools[4]->baseurl, 572 'expected' => $tools[4], 573 ], 574 [ 575 'url' => $tools[5]->baseurl, 576 'expected' => $tools[5], 577 ], 578 [ 579 'url' => 'https://nomatch.com/i/am/', 580 'expected' => null 581 ], 582 [ 583 'url' => 'https://example.com', 584 'expected' => null 585 ], 586 [ 587 'url' => 'https://example.com/i/am/?where=unknown', 588 'expected' => $tools[3] 589 ] 590 ]; 591 592 // Construct the final array as required by the provider API. Each row 593 // of the array contains the URL to test, the expected tool, and 594 // the complete list of tools. 595 return array_map(function($data) use ($tools) { 596 return [$data['url'], $data['expected'], $tools]; 597 }, $data); 598 } 599 600 /** 601 * Test lti_get_best_tool_by_url. 602 * 603 * @dataProvider lti_get_best_tool_by_url_provider 604 * @param string $url The URL to test. 605 * @param object $expected The expected tool matching the URL. 606 * @param array $tools The pool of tools to match the URL with. 607 */ 608 public function test_lti_get_best_tool_by_url($url, $expected, $tools) { 609 $actual = lti_get_best_tool_by_url($url, $tools, null); 610 $this->assertSame($expected, $actual); 611 } 612 613 /** 614 * Test lti_get_jwt_message_type_mapping(). 615 */ 616 public function test_lti_get_jwt_message_type_mapping() { 617 $mapping = [ 618 'basic-lti-launch-request' => 'LtiResourceLinkRequest', 619 'ContentItemSelectionRequest' => 'LtiDeepLinkingRequest', 620 'LtiDeepLinkingResponse' => 'ContentItemSelection', 621 ]; 622 623 $this->assertEquals($mapping, lti_get_jwt_message_type_mapping()); 624 } 625 626 /** 627 * Test lti_get_jwt_claim_mapping() 628 */ 629 public function test_lti_get_jwt_claim_mapping() { 630 $mapping = [ 631 'accept_copy_advice' => [ 632 'suffix' => 'dl', 633 'group' => 'deep_linking_settings', 634 'claim' => 'accept_copy_advice', 635 'isarray' => false, 636 'type' => 'boolean' 637 ], 638 'accept_media_types' => [ 639 'suffix' => 'dl', 640 'group' => 'deep_linking_settings', 641 'claim' => 'accept_media_types', 642 'isarray' => true 643 ], 644 'accept_multiple' => [ 645 'suffix' => 'dl', 646 'group' => 'deep_linking_settings', 647 'claim' => 'accept_multiple', 648 'isarray' => false, 649 'type' => 'boolean' 650 ], 651 'accept_presentation_document_targets' => [ 652 'suffix' => 'dl', 653 'group' => 'deep_linking_settings', 654 'claim' => 'accept_presentation_document_targets', 655 'isarray' => true 656 ], 657 'accept_types' => [ 658 'suffix' => 'dl', 659 'group' => 'deep_linking_settings', 660 'claim' => 'accept_types', 661 'isarray' => true 662 ], 663 'accept_unsigned' => [ 664 'suffix' => 'dl', 665 'group' => 'deep_linking_settings', 666 'claim' => 'accept_unsigned', 667 'isarray' => false, 668 'type' => 'boolean' 669 ], 670 'auto_create' => [ 671 'suffix' => 'dl', 672 'group' => 'deep_linking_settings', 673 'claim' => 'auto_create', 674 'isarray' => false, 675 'type' => 'boolean' 676 ], 677 'can_confirm' => [ 678 'suffix' => 'dl', 679 'group' => 'deep_linking_settings', 680 'claim' => 'can_confirm', 681 'isarray' => false, 682 'type' => 'boolean' 683 ], 684 'content_item_return_url' => [ 685 'suffix' => 'dl', 686 'group' => 'deep_linking_settings', 687 'claim' => 'deep_link_return_url', 688 'isarray' => false 689 ], 690 'content_items' => [ 691 'suffix' => 'dl', 692 'group' => '', 693 'claim' => 'content_items', 694 'isarray' => true 695 ], 696 'data' => [ 697 'suffix' => 'dl', 698 'group' => 'deep_linking_settings', 699 'claim' => 'data', 700 'isarray' => false 701 ], 702 'text' => [ 703 'suffix' => 'dl', 704 'group' => 'deep_linking_settings', 705 'claim' => 'text', 706 'isarray' => false 707 ], 708 'title' => [ 709 'suffix' => 'dl', 710 'group' => 'deep_linking_settings', 711 'claim' => 'title', 712 'isarray' => false 713 ], 714 'lti_msg' => [ 715 'suffix' => 'dl', 716 'group' => '', 717 'claim' => 'msg', 718 'isarray' => false 719 ], 720 'lti_log' => [ 721 'suffix' => 'dl', 722 'group' => '', 723 'claim' => 'log', 724 'isarray' => false 725 ], 726 'lti_errormsg' => [ 727 'suffix' => 'dl', 728 'group' => '', 729 'claim' => 'errormsg', 730 'isarray' => false 731 ], 732 'lti_errorlog' => [ 733 'suffix' => 'dl', 734 'group' => '', 735 'claim' => 'errorlog', 736 'isarray' => false 737 ], 738 'context_id' => [ 739 'suffix' => '', 740 'group' => 'context', 741 'claim' => 'id', 742 'isarray' => false 743 ], 744 'context_label' => [ 745 'suffix' => '', 746 'group' => 'context', 747 'claim' => 'label', 748 'isarray' => false 749 ], 750 'context_title' => [ 751 'suffix' => '', 752 'group' => 'context', 753 'claim' => 'title', 754 'isarray' => false 755 ], 756 'context_type' => [ 757 'suffix' => '', 758 'group' => 'context', 759 'claim' => 'type', 760 'isarray' => true 761 ], 762 'lis_course_offering_sourcedid' => [ 763 'suffix' => '', 764 'group' => 'lis', 765 'claim' => 'course_offering_sourcedid', 766 'isarray' => false 767 ], 768 'lis_course_section_sourcedid' => [ 769 'suffix' => '', 770 'group' => 'lis', 771 'claim' => 'course_section_sourcedid', 772 'isarray' => false 773 ], 774 'launch_presentation_css_url' => [ 775 'suffix' => '', 776 'group' => 'launch_presentation', 777 'claim' => 'css_url', 778 'isarray' => false 779 ], 780 'launch_presentation_document_target' => [ 781 'suffix' => '', 782 'group' => 'launch_presentation', 783 'claim' => 'document_target', 784 'isarray' => false 785 ], 786 'launch_presentation_height' => [ 787 'suffix' => '', 788 'group' => 'launch_presentation', 789 'claim' => 'height', 790 'isarray' => false 791 ], 792 'launch_presentation_locale' => [ 793 'suffix' => '', 794 'group' => 'launch_presentation', 795 'claim' => 'locale', 796 'isarray' => false 797 ], 798 'launch_presentation_return_url' => [ 799 'suffix' => '', 800 'group' => 'launch_presentation', 801 'claim' => 'return_url', 802 'isarray' => false 803 ], 804 'launch_presentation_width' => [ 805 'suffix' => '', 806 'group' => 'launch_presentation', 807 'claim' => 'width', 808 'isarray' => false 809 ], 810 'lis_person_contact_email_primary' => [ 811 'suffix' => '', 812 'group' => null, 813 'claim' => 'email', 814 'isarray' => false 815 ], 816 'lis_person_name_family' => [ 817 'suffix' => '', 818 'group' => null, 819 'claim' => 'family_name', 820 'isarray' => false 821 ], 822 'lis_person_name_full' => [ 823 'suffix' => '', 824 'group' => null, 825 'claim' => 'name', 826 'isarray' => false 827 ], 828 'lis_person_name_given' => [ 829 'suffix' => '', 830 'group' => null, 831 'claim' => 'given_name', 832 'isarray' => false 833 ], 834 'lis_person_sourcedid' => [ 835 'suffix' => '', 836 'group' => 'lis', 837 'claim' => 'person_sourcedid', 838 'isarray' => false 839 ], 840 'user_id' => [ 841 'suffix' => '', 842 'group' => null, 843 'claim' => 'sub', 844 'isarray' => false 845 ], 846 'user_image' => [ 847 'suffix' => '', 848 'group' => null, 849 'claim' => 'picture', 850 'isarray' => false 851 ], 852 'roles' => [ 853 'suffix' => '', 854 'group' => '', 855 'claim' => 'roles', 856 'isarray' => true 857 ], 858 'role_scope_mentor' => [ 859 'suffix' => '', 860 'group' => '', 861 'claim' => 'role_scope_mentor', 862 'isarray' => false 863 ], 864 'deployment_id' => [ 865 'suffix' => '', 866 'group' => '', 867 'claim' => 'deployment_id', 868 'isarray' => false 869 ], 870 'lti_message_type' => [ 871 'suffix' => '', 872 'group' => '', 873 'claim' => 'message_type', 874 'isarray' => false 875 ], 876 'lti_version' => [ 877 'suffix' => '', 878 'group' => '', 879 'claim' => 'version', 880 'isarray' => false 881 ], 882 'resource_link_description' => [ 883 'suffix' => '', 884 'group' => 'resource_link', 885 'claim' => 'description', 886 'isarray' => false 887 ], 888 'resource_link_id' => [ 889 'suffix' => '', 890 'group' => 'resource_link', 891 'claim' => 'id', 892 'isarray' => false 893 ], 894 'resource_link_title' => [ 895 'suffix' => '', 896 'group' => 'resource_link', 897 'claim' => 'title', 898 'isarray' => false 899 ], 900 'tool_consumer_info_product_family_code' => [ 901 'suffix' => '', 902 'group' => 'tool_platform', 903 'claim' => 'product_family_code', 904 'isarray' => false 905 ], 906 'tool_consumer_info_version' => [ 907 'suffix' => '', 908 'group' => 'tool_platform', 909 'claim' => 'version', 910 'isarray' => false 911 ], 912 'tool_consumer_instance_contact_email' => [ 913 'suffix' => '', 914 'group' => 'tool_platform', 915 'claim' => 'contact_email', 916 'isarray' => false 917 ], 918 'tool_consumer_instance_description' => [ 919 'suffix' => '', 920 'group' => 'tool_platform', 921 'claim' => 'description', 922 'isarray' => false 923 ], 924 'tool_consumer_instance_guid' => [ 925 'suffix' => '', 926 'group' => 'tool_platform', 927 'claim' => 'guid', 928 'isarray' => false 929 ], 930 'tool_consumer_instance_name' => [ 931 'suffix' => '', 932 'group' => 'tool_platform', 933 'claim' => 'name', 934 'isarray' => false 935 ], 936 'tool_consumer_instance_url' => [ 937 'suffix' => '', 938 'group' => 'tool_platform', 939 'claim' => 'url', 940 'isarray' => false 941 ], 942 'custom_context_memberships_v2_url' => [ 943 'suffix' => 'nrps', 944 'group' => 'namesroleservice', 945 'claim' => 'context_memberships_url', 946 'isarray' => false 947 ], 948 'custom_context_memberships_versions' => [ 949 'suffix' => 'nrps', 950 'group' => 'namesroleservice', 951 'claim' => 'service_versions', 952 'isarray' => true 953 ], 954 'custom_gradebookservices_scope' => [ 955 'suffix' => 'ags', 956 'group' => 'endpoint', 957 'claim' => 'scope', 958 'isarray' => true 959 ], 960 'custom_lineitems_url' => [ 961 'suffix' => 'ags', 962 'group' => 'endpoint', 963 'claim' => 'lineitems', 964 'isarray' => false 965 ], 966 'custom_lineitem_url' => [ 967 'suffix' => 'ags', 968 'group' => 'endpoint', 969 'claim' => 'lineitem', 970 'isarray' => false 971 ], 972 'custom_results_url' => [ 973 'suffix' => 'ags', 974 'group' => 'endpoint', 975 'claim' => 'results', 976 'isarray' => false 977 ], 978 'custom_result_url' => [ 979 'suffix' => 'ags', 980 'group' => 'endpoint', 981 'claim' => 'result', 982 'isarray' => false 983 ], 984 'custom_scores_url' => [ 985 'suffix' => 'ags', 986 'group' => 'endpoint', 987 'claim' => 'scores', 988 'isarray' => false 989 ], 990 'custom_score_url' => [ 991 'suffix' => 'ags', 992 'group' => 'endpoint', 993 'claim' => 'score', 994 'isarray' => false 995 ], 996 'lis_outcome_service_url' => [ 997 'suffix' => 'bo', 998 'group' => 'basicoutcome', 999 'claim' => 'lis_outcome_service_url', 1000 'isarray' => false 1001 ], 1002 'lis_result_sourcedid' => [ 1003 'suffix' => 'bo', 1004 'group' => 'basicoutcome', 1005 'claim' => 'lis_result_sourcedid', 1006 'isarray' => false 1007 ], 1008 ]; 1009 $actual = lti_get_jwt_claim_mapping(); 1010 $this->assertEquals($mapping, $actual); 1011 } 1012 1013 /** 1014 * Test lti_build_standard_message(). 1015 */ 1016 public function test_lti_build_standard_message_institution_name_set() { 1017 global $CFG; 1018 1019 $this->resetAfterTest(); 1020 1021 $CFG->mod_lti_institution_name = 'some institution name lols'; 1022 1023 $course = $this->getDataGenerator()->create_course(); 1024 $instance = $this->getDataGenerator()->create_module('lti', 1025 [ 1026 'course' => $course->id, 1027 ] 1028 ); 1029 1030 $message = lti_build_standard_message($instance, '2', LTI_VERSION_1); 1031 1032 $this->assertEquals('moodle-2', $message['ext_lms']); 1033 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']); 1034 $this->assertEquals(LTI_VERSION_1, $message['lti_version']); 1035 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']); 1036 $this->assertEquals('2', $message['tool_consumer_instance_guid']); 1037 $this->assertEquals('some institution name lols', $message['tool_consumer_instance_name']); 1038 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']); 1039 } 1040 1041 /** 1042 * Test lti_build_standard_message(). 1043 */ 1044 public function test_lti_build_standard_message_institution_name_not_set() { 1045 $this->resetAfterTest(); 1046 1047 $course = $this->getDataGenerator()->create_course(); 1048 $instance = $this->getDataGenerator()->create_module('lti', 1049 [ 1050 'course' => $course->id, 1051 ] 1052 ); 1053 1054 $message = lti_build_standard_message($instance, '2', LTI_VERSION_2); 1055 1056 $this->assertEquals('moodle-2', $message['ext_lms']); 1057 $this->assertEquals('moodle', $message['tool_consumer_info_product_family_code']); 1058 $this->assertEquals(LTI_VERSION_2, $message['lti_version']); 1059 $this->assertEquals('basic-lti-launch-request', $message['lti_message_type']); 1060 $this->assertEquals('2', $message['tool_consumer_instance_guid']); 1061 $this->assertEquals('phpunit', $message['tool_consumer_instance_name']); 1062 $this->assertEquals('PHPUnit test site', $message['tool_consumer_instance_description']); 1063 } 1064 1065 /** 1066 * Test lti_verify_jwt_signature(). 1067 */ 1068 public function test_lti_verify_jwt_signature() { 1069 $this->resetAfterTest(); 1070 1071 $this->setAdminUser(); 1072 1073 // Create a tool type, associated with that proxy. 1074 $type = new \stdClass(); 1075 $type->state = LTI_TOOL_STATE_CONFIGURED; 1076 $type->name = "Test tool"; 1077 $type->description = "Example description"; 1078 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1079 1080 $config = new \stdClass(); 1081 $config->lti_publickey = '-----BEGIN PUBLIC KEY----- 1082 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv 1083 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc 1084 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy 1085 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0 1086 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb 1087 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9 1088 MwIDAQAB 1089 -----END PUBLIC KEY-----'; 1090 1091 $config->lti_keytype = LTI_RSA_KEY; 1092 1093 $typeid = lti_add_type($type, $config); 1094 1095 lti_verify_jwt_signature($typeid, '', 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g' . 1096 'RG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOs' . 1097 'S_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMgu' . 1098 'EIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iY' . 1099 'v7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA'); 1100 } 1101 1102 /** 1103 * Test lti_verify_jwt_signature_jwk(). 1104 */ 1105 public function test_lti_verify_jwt_signature_jwk() { 1106 $this->resetAfterTest(); 1107 1108 $this->setAdminUser(); 1109 1110 // Create a tool type, associated with that proxy. 1111 $type = new \stdClass(); 1112 $type->state = LTI_TOOL_STATE_CONFIGURED; 1113 $type->name = "Test tool"; 1114 $type->description = "Example description"; 1115 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1116 1117 $config = new \stdClass(); 1118 $config->lti_publickeyset = $this->getExternalTestFileUrl('/lti_keyset.json'); 1119 1120 $config->lti_keytype = LTI_JWK_KEYSET; 1121 1122 $typeid = lti_add_type($type, $config); 1123 1124 $jwt = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU3YzExNzdkMmQ1M2EwMjFjNzM'; 1125 $jwt .= '3NTY0OTFjMTM3YjE3In0.eyJpc3MiOiJnclJvbkd3RTd1WjRwZ28iLCJzdWIiOiJnclJvb'; 1126 $jwt .= 'kd3RTd1WjRwZ28iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0L21vb2RsZS9tb2QvbHRpL3R'; 1127 $jwt .= 'va2VuLnBocCIsImp0aSI6IjFlMUJPVEczVFJjbFdUem00dERsMGc9PSIsImlhdCI6MTU4M'; 1128 $jwt .= 'Dg1NTUwNX0.Lowhc9ovNAXRb2rkAnv1oozDXlRD54Mz2JS1i8Zx4yGWQzmXzam-La19_g0'; 1129 $jwt .= 'CTnwlKM6gxaInnRKFRAcwhJVcWec389liLAjMbna6d6iTWYTZr7q_4BIe3CT_oTMWASGta'; 1130 $jwt .= 'Paaq53ch1rO4YdueEtmtd1K47ibo4Lhu1jmP_icc3lxjfnqiv4vIYdy7W2JQEzpk1ImuQr'; 1131 $jwt .= 'AlO1xR3fZ6bgcJhVIaw5xoaZD3ZgEjuZOQXMkywv1bL-mL17RX336CzHd8rYZg82QXrBzb'; 1132 $jwt .= 'NWzAlaZxv9VSug8t6mORvM6TkYYWjqEBKemgkD5rNh1BHrPcjWP7vy2Jz7YMjLsmuvDuLK'; 1133 $jwt .= '_PHYIKL--s4gcXWoYmOu1vj-SgoPczTJPoiBD35hAKqVHy5ggHaYHBy95_bbcFd8H1smHw'; 1134 $jwt .= 'pejrAFj1QAwGyTISLzUm08oq7Ak0tSxRKKXw4lpZAka1MmYxO3tJ_3-MXw6Bwz12bNgitJ'; 1135 $jwt .= 'lQd6n3kkGLCJAmANeRkPsH6eZVwF0n2cjh2O1JAwyNcMD2vs4I8ftM1EqqoE2M3r6kt3AC'; 1136 $jwt .= 'EscmqzizI3j80USBCLUUb1UTsfJb2g7oyApJAp-13Q3InR3QyvWO8unG5VraFE7IL5I28h'; 1137 $jwt .= 'MkQAHuCI90DFmXB4leflAu7wNlIK_U8xkGl8X8Mnv6MWgg94Ki8jgIq_kA85JAqI'; 1138 1139 lti_verify_jwt_signature($typeid, '', $jwt); 1140 } 1141 1142 /** 1143 * Test lti_verify_jwt_signature(). 1144 */ 1145 public function test_lti_verify_jwt_signature_with_lti2() { 1146 $this->resetAfterTest(); 1147 1148 $this->setAdminUser(); 1149 1150 // Create a tool proxy. 1151 $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array()); 1152 1153 // Create a tool type, associated with that proxy. 1154 $type = new \stdClass(); 1155 $type->state = LTI_TOOL_STATE_CONFIGURED; 1156 $type->name = "Test tool"; 1157 $type->description = "Example description"; 1158 $type->toolproxyid = $proxy->id; 1159 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1160 1161 $data = new \stdClass(); 1162 $data->lti_contentitem = true; 1163 1164 $typeid = lti_add_type($type, $data); 1165 1166 $this->expectExceptionMessage('JWT security not supported with LTI 2'); 1167 lti_verify_jwt_signature($typeid, '', ''); 1168 } 1169 1170 /** 1171 * Test lti_verify_jwt_signature(). 1172 */ 1173 public function test_lti_verify_jwt_signature_no_consumer_key() { 1174 $this->resetAfterTest(); 1175 1176 $this->setAdminUser(); 1177 1178 // Create a tool type, associated with that proxy. 1179 $type = new \stdClass(); 1180 $type->state = LTI_TOOL_STATE_CONFIGURED; 1181 $type->name = "Test tool"; 1182 $type->description = "Example description"; 1183 $type->clientid = 'consumerkey'; 1184 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1185 1186 $config = new \stdClass(); 1187 $typeid = lti_add_type($type, $config); 1188 1189 $this->expectExceptionMessage(get_string('errorincorrectconsumerkey', 'mod_lti')); 1190 lti_verify_jwt_signature($typeid, '', ''); 1191 } 1192 1193 /** 1194 * Test lti_verify_jwt_signature(). 1195 */ 1196 public function test_lti_verify_jwt_signature_no_public_key() { 1197 $this->resetAfterTest(); 1198 $this->setAdminUser(); 1199 1200 // Create a tool type, associated with that proxy. 1201 $type = new \stdClass(); 1202 $type->state = LTI_TOOL_STATE_CONFIGURED; 1203 $type->name = "Test tool"; 1204 $type->description = "Example description"; 1205 $type->clientid = 'consumerkey'; 1206 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1207 1208 $config = new \stdClass(); 1209 $config->lti_keytype = LTI_RSA_KEY; 1210 $typeid = lti_add_type($type, $config); 1211 1212 $this->expectExceptionMessage('No public key configured'); 1213 lti_verify_jwt_signature($typeid, 'consumerkey', ''); 1214 } 1215 1216 /** 1217 * Test lti_convert_content_items(). 1218 */ 1219 public function test_lti_convert_content_items() { 1220 $contentitems = []; 1221 $contentitems[] = [ 1222 'type' => 'ltiResourceLink', 1223 'url' => 'http://example.com/messages/launch', 1224 'title' => 'Test title', 1225 'text' => 'Test text', 1226 'iframe' => [] 1227 ]; 1228 $contentitems[] = [ 1229 'type' => 'ltiResourceLink', 1230 'url' => 'http://example.com/messages/launch2', 1231 'title' => 'Test title2', 1232 'text' => 'Test text2', 1233 'iframe' => [ 1234 'height' => 200, 1235 'width' => 300 1236 ], 1237 'window' => [] 1238 ]; 1239 $contentitems[] = [ 1240 'type' => 'ltiResourceLink', 1241 'url' => 'http://example.com/messages/launch3', 1242 'title' => 'Test title3', 1243 'text' => 'Test text3', 1244 'window' => [ 1245 'targetName' => 'test-win', 1246 'height' => 400 1247 ] 1248 ]; 1249 1250 $contentitems = json_encode($contentitems); 1251 1252 $json = lti_convert_content_items($contentitems); 1253 1254 $jsondecode = json_decode($json); 1255 1256 $strcontext = '@context'; 1257 $strgraph = '@graph'; 1258 $strtype = '@type'; 1259 1260 $objgraph = new \stdClass(); 1261 $objgraph->url = 'http://example.com/messages/launch'; 1262 $objgraph->title = 'Test title'; 1263 $objgraph->text = 'Test text'; 1264 $objgraph->placementAdvice = new \stdClass(); 1265 $objgraph->placementAdvice->presentationDocumentTarget = 'iframe'; 1266 $objgraph->{$strtype} = 'LtiLinkItem'; 1267 $objgraph->mediaType = 'application\/vnd.ims.lti.v1.ltilink'; 1268 1269 $objgraph2 = new \stdClass(); 1270 $objgraph2->url = 'http://example.com/messages/launch2'; 1271 $objgraph2->title = 'Test title2'; 1272 $objgraph2->text = 'Test text2'; 1273 $objgraph2->placementAdvice = new \stdClass(); 1274 $objgraph2->placementAdvice->presentationDocumentTarget = 'iframe'; 1275 $objgraph2->placementAdvice->displayHeight = 200; 1276 $objgraph2->placementAdvice->displayWidth = 300; 1277 $objgraph2->{$strtype} = 'LtiLinkItem'; 1278 $objgraph2->mediaType = 'application\/vnd.ims.lti.v1.ltilink'; 1279 1280 $objgraph3 = new \stdClass(); 1281 $objgraph3->url = 'http://example.com/messages/launch3'; 1282 $objgraph3->title = 'Test title3'; 1283 $objgraph3->text = 'Test text3'; 1284 $objgraph3->placementAdvice = new \stdClass(); 1285 $objgraph3->placementAdvice->presentationDocumentTarget = 'window'; 1286 $objgraph3->placementAdvice->displayHeight = 400; 1287 $objgraph3->placementAdvice->windowTarget = 'test-win'; 1288 $objgraph3->{$strtype} = 'LtiLinkItem'; 1289 $objgraph3->mediaType = 'application\/vnd.ims.lti.v1.ltilink'; 1290 1291 $expected = new \stdClass(); 1292 $expected->{$strcontext} = 'http://purl.imsglobal.org/ctx/lti/v1/ContentItem'; 1293 $expected->{$strgraph} = []; 1294 $expected->{$strgraph}[] = $objgraph; 1295 $expected->{$strgraph}[] = $objgraph2; 1296 $expected->{$strgraph}[] = $objgraph3; 1297 1298 $this->assertEquals($expected, $jsondecode); 1299 } 1300 1301 /** 1302 * Test adding a single gradable item through content item. 1303 */ 1304 public function test_lti_tool_configuration_from_content_item_single_gradable() { 1305 $this->resetAfterTest(); 1306 $this->setAdminUser(); 1307 1308 $type = new \stdClass(); 1309 $type->name = "Test tool"; 1310 $type->baseurl = "http://example.com"; 1311 $config = new \stdClass(); 1312 $config->lti_acceptgrades = LTI_SETTING_DELEGATE; 1313 $typeid = lti_add_type($type, $config); 1314 1315 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1316 $contentitems = []; 1317 $contentitems[] = [ 1318 'type' => 'ltiResourceLink', 1319 'url' => 'http://example.com/messages/launch', 1320 'title' => 'Test title', 1321 'lineItem' => [ 1322 'resourceId' => 'r12345', 1323 'tag' => 'final', 1324 'scoreMaximum' => 10.0 1325 ], 1326 'frame' => [] 1327 ]; 1328 $contentitemsjson13 = json_encode($contentitems); 1329 $json11 = lti_convert_content_items($contentitemsjson13); 1330 1331 $config = lti_tool_configuration_from_content_item($typeid, 1332 'ContentItemSelection', 1333 $type->ltiversion, 1334 'ConsumerKey', 1335 $json11); 1336 1337 $this->assertEquals($contentitems[0]['url'], $config->toolurl); 1338 $this->assertEquals(LTI_SETTING_ALWAYS, $config->instructorchoiceacceptgrades); 1339 $this->assertEquals($contentitems[0]['lineItem']['tag'], $config->lineitemtag); 1340 $this->assertEquals($contentitems[0]['lineItem']['resourceId'], $config->lineitemresourceid); 1341 $this->assertEquals($contentitems[0]['lineItem']['scoreMaximum'], $config->grade_modgrade_point); 1342 } 1343 1344 /** 1345 * Test adding multiple gradable items through content item. 1346 */ 1347 public function test_lti_tool_configuration_from_content_item_multiple() { 1348 $this->resetAfterTest(); 1349 $this->setAdminUser(); 1350 1351 $type = new \stdClass(); 1352 $type->name = "Test tool"; 1353 $type->baseurl = "http://example.com"; 1354 $config = new \stdClass(); 1355 $config->lti_acceptgrades = LTI_SETTING_DELEGATE; 1356 $typeid = lti_add_type($type, $config); 1357 1358 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1359 $contentitems = []; 1360 $contentitems[] = [ 1361 'type' => 'ltiResourceLink', 1362 'url' => 'http://example.com/messages/launch', 1363 'title' => 'Test title', 1364 'text' => 'Test text', 1365 'icon' => [ 1366 'url' => 'http://lti.example.com/image.jpg', 1367 'width' => 100 1368 ], 1369 'frame' => [] 1370 ]; 1371 $contentitems[] = [ 1372 'type' => 'ltiResourceLink', 1373 'url' => 'http://example.com/messages/launchgraded', 1374 'title' => 'Test Graded', 1375 'lineItem' => [ 1376 'resourceId' => 'r12345', 1377 'tag' => 'final', 1378 'scoreMaximum' => 10.0 1379 ], 1380 'frame' => [] 1381 ]; 1382 $contentitemsjson13 = json_encode($contentitems); 1383 $json11 = lti_convert_content_items($contentitemsjson13); 1384 1385 $config = lti_tool_configuration_from_content_item($typeid, 1386 'ContentItemSelection', 1387 $type->ltiversion, 1388 'ConsumerKey', 1389 $json11); 1390 $this->assertNotNull($config->multiple); 1391 $this->assertEquals(2, count( $config->multiple )); 1392 $this->assertEquals($contentitems[0]['title'], $config->multiple[0]->name); 1393 $this->assertEquals($contentitems[0]['url'], $config->multiple[0]->toolurl); 1394 $this->assertEquals(LTI_SETTING_NEVER, $config->multiple[0]->instructorchoiceacceptgrades); 1395 $this->assertEquals($contentitems[1]['url'], $config->multiple[1]->toolurl); 1396 $this->assertEquals(LTI_SETTING_ALWAYS, $config->multiple[1]->instructorchoiceacceptgrades); 1397 $this->assertEquals($contentitems[1]['lineItem']['tag'], $config->multiple[1]->lineitemtag); 1398 $this->assertEquals($contentitems[1]['lineItem']['resourceId'], $config->multiple[1]->lineitemresourceid); 1399 $this->assertEquals($contentitems[1]['lineItem']['scoreMaximum'], $config->multiple[1]->grade_modgrade_point); 1400 } 1401 1402 /** 1403 * Test adding a single non gradable item through content item. 1404 */ 1405 public function test_lti_tool_configuration_from_content_item_single() { 1406 $this->resetAfterTest(); 1407 $this->setAdminUser(); 1408 1409 $type = new \stdClass(); 1410 $type->name = "Test tool"; 1411 $type->baseurl = "http://example.com"; 1412 $config = new \stdClass(); 1413 $typeid = lti_add_type($type, $config); 1414 1415 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1416 $contentitems = []; 1417 $contentitems[] = [ 1418 'type' => 'ltiResourceLink', 1419 'url' => 'http://example.com/messages/launch', 1420 'title' => 'Test title', 1421 'text' => 'Test text', 1422 'icon' => [ 1423 'url' => 'http://lti.example.com/image.jpg', 1424 'width' => 100 1425 ], 1426 'frame' => [] 1427 ]; 1428 $contentitemsjson13 = json_encode($contentitems); 1429 $json11 = lti_convert_content_items($contentitemsjson13); 1430 1431 $config = lti_tool_configuration_from_content_item($typeid, 1432 'ContentItemSelection', 1433 $type->ltiversion, 1434 'ConsumerKey', 1435 $json11); 1436 $this->assertEquals($contentitems[0]['title'], $config->name); 1437 $this->assertEquals($contentitems[0]['text'], $config->introeditor['text']); 1438 $this->assertEquals($contentitems[0]['url'], $config->toolurl); 1439 $this->assertEquals($contentitems[0]['icon']['url'], $config->icon); 1440 $this->assertEquals(LTI_SETTING_NEVER, $config->instructorchoiceacceptgrades); 1441 1442 } 1443 1444 /** 1445 * Test lti_sign_jwt(). 1446 */ 1447 public function test_lti_sign_jwt() { 1448 $this->resetAfterTest(); 1449 1450 $this->setAdminUser(); 1451 1452 // Create a tool type, associated with that proxy. 1453 $type = new \stdClass(); 1454 $type->state = LTI_TOOL_STATE_CONFIGURED; 1455 $type->name = "Test tool"; 1456 $type->description = "Example description"; 1457 $type->clientid = 'consumerkey'; 1458 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1459 1460 $config = new \stdClass(); 1461 $typeid = lti_add_type($type, $config); 1462 1463 $params = []; 1464 $params['roles'] = 'urn:lti:role:ims/lis/testrole,' . 1465 'urn:lti:instrole:ims/lis/testinstrole,' . 1466 'urn:lti:sysrole:ims/lis/testsysrole,' . 1467 'hi'; 1468 $params['accept_copy_advice'] = [ 1469 'suffix' => 'dl', 1470 'group' => 'deep_linking_settings', 1471 'claim' => 'accept_copy_advice', 1472 'isarray' => false 1473 ]; 1474 $params['lis_result_sourcedid'] = [ 1475 'suffix' => 'bos', 1476 'group' => 'basicoutcomesservice', 1477 'claim' => 'lis_result_sourcedid', 1478 'isarray' => false 1479 ]; 1480 $endpoint = 'https://www.example.com/moodle'; 1481 $oauthconsumerkey = 'consumerkey'; 1482 $nonce = ''; 1483 1484 $jwt = lti_sign_jwt($params, $endpoint, $oauthconsumerkey, $typeid, $nonce); 1485 1486 $this->assertArrayHasKey('id_token', $jwt); 1487 $this->assertNotEmpty($jwt['id_token']); 1488 } 1489 1490 /** 1491 * Test lti_convert_from_jwt() 1492 */ 1493 public function test_lti_convert_from_jwt() { 1494 $this->resetAfterTest(); 1495 1496 $this->setAdminUser(); 1497 1498 // Create a tool type, associated with that proxy. 1499 $type = new \stdClass(); 1500 $type->state = LTI_TOOL_STATE_CONFIGURED; 1501 $type->name = "Test tool"; 1502 $type->description = "Example description"; 1503 $type->clientid = 'sso.example.com'; 1504 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1505 1506 $config = new \stdClass(); 1507 $config->lti_publickey = '-----BEGIN PUBLIC KEY----- 1508 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv 1509 vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc 1510 aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy 1511 tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0 1512 e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb 1513 V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9 1514 MwIDAQAB 1515 -----END PUBLIC KEY-----'; 1516 $config->lti_keytype = LTI_RSA_KEY; 1517 1518 $typeid = lti_add_type($type, $config); 1519 1520 $params = lti_convert_from_jwt($typeid, 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib' . 1521 'mFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoic3NvLmV4YW1wbGUuY29tIn0.XURVvEb5ueAvFsn-S9EB' . 1522 'BSfKbsgUzfRQqmJ6evlrYdx7sXWoZXw1nYjaLTg-mawvBr7MVvrdG9qh6oN8OfkQ7bfMwiz4tjBMJ4B4q_sig5BDYIKwMNjZL5GGCBs89FQrgqZBhxw' . 1523 '3exTjPBEn69__w40o0AhCsBohPMh0ZsAyHug5dhm8vIuOP667repUJzM8uKCD6L4bEL6vQE8EwU6WQOmfJ2SDmRs-1pFkiaFd6hmPn6AVX7ETtzQmlT' . 1524 'X-nXe9weQjU1lH4AQG2Yfnn-7lS94bt6E76Zt-XndP3IY7W48EpnRfUK9Ff1fZlomT4MPahdNP1eP8gT2iMz7vYpCfmA'); 1525 1526 $this->assertEquals('sso.example.com', $params['oauth_consumer_key']); 1527 $this->assertEquals('John Doe', $params['lis_person_name_full']); 1528 } 1529 1530 /** 1531 * Test lti_get_permitted_service_scopes(). 1532 */ 1533 public function test_lti_get_permitted_service_scopes() { 1534 $this->resetAfterTest(); 1535 1536 $this->setAdminUser(); 1537 1538 // Create a tool type, associated with that proxy. 1539 $type = new \stdClass(); 1540 $type->state = LTI_TOOL_STATE_CONFIGURED; 1541 $type->name = "Test tool"; 1542 $type->description = "Example description"; 1543 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1544 1545 $typeconfig = new \stdClass(); 1546 $typeconfig->lti_acceptgrades = true; 1547 1548 $typeid = lti_add_type($type, $typeconfig); 1549 1550 $tool = lti_get_type($typeid); 1551 1552 $config = lti_get_type_config($typeid); 1553 $permittedscopes = lti_get_permitted_service_scopes($tool, $config); 1554 1555 $expected = [ 1556 'https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome' 1557 ]; 1558 $this->assertEquals($expected, $permittedscopes); 1559 } 1560 1561 /** 1562 * Test get_tool_type_config(). 1563 */ 1564 public function test_get_tool_type_config() { 1565 $this->resetAfterTest(); 1566 1567 $this->setAdminUser(); 1568 1569 // Create a tool type, associated with that proxy. 1570 $type = new \stdClass(); 1571 $type->state = LTI_TOOL_STATE_CONFIGURED; 1572 $type->name = "Test tool"; 1573 $type->description = "Example description"; 1574 $type->clientid = "Test client ID"; 1575 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1576 1577 $config = new \stdClass(); 1578 1579 $typeid = lti_add_type($type, $config); 1580 1581 $type = lti_get_type($typeid); 1582 1583 $typeconfig = get_tool_type_config($type); 1584 1585 $this->assertEquals('https://www.example.com/moodle', $typeconfig['platformid']); 1586 $this->assertEquals($type->clientid, $typeconfig['clientid']); 1587 $this->assertEquals($typeid, $typeconfig['deploymentid']); 1588 $this->assertEquals('https://www.example.com/moodle/mod/lti/certs.php', $typeconfig['publickeyseturl']); 1589 $this->assertEquals('https://www.example.com/moodle/mod/lti/token.php', $typeconfig['accesstokenurl']); 1590 $this->assertEquals('https://www.example.com/moodle/mod/lti/auth.php', $typeconfig['authrequesturl']); 1591 } 1592 1593 /** 1594 * Test lti_new_access_token(). 1595 */ 1596 public function test_lti_new_access_token() { 1597 global $DB; 1598 1599 $this->resetAfterTest(); 1600 1601 $this->setAdminUser(); 1602 1603 // Create a tool type, associated with that proxy. 1604 $type = new \stdClass(); 1605 $type->state = LTI_TOOL_STATE_CONFIGURED; 1606 $type->name = "Test tool"; 1607 $type->description = "Example description"; 1608 $type->clientid = "Test client ID"; 1609 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1610 1611 $config = new \stdClass(); 1612 1613 $typeid = lti_add_type($type, $config); 1614 1615 $scopes = ['lti_some_scope', 'lti_another_scope']; 1616 1617 lti_new_access_token($typeid, $scopes); 1618 1619 $token = $DB->get_records('lti_access_tokens'); 1620 $this->assertEquals(1, count($token)); 1621 1622 $token = reset($token); 1623 1624 $this->assertEquals($typeid, $token->typeid); 1625 $this->assertEquals(json_encode(array_values($scopes)), $token->scope); 1626 $this->assertEquals($token->timecreated + LTI_ACCESS_TOKEN_LIFE, $token->validuntil); 1627 $this->assertNull($token->lastaccess); 1628 } 1629 1630 /** 1631 * Test lti_build_login_request(). 1632 */ 1633 public function test_lti_build_login_request() { 1634 global $USER, $CFG; 1635 1636 $this->resetAfterTest(); 1637 1638 $USER->id = 123456789; 1639 1640 $course = $this->getDataGenerator()->create_course(); 1641 $instance = $this->getDataGenerator()->create_module('lti', 1642 [ 1643 'course' => $course->id, 1644 ] 1645 ); 1646 1647 $config = new \stdClass(); 1648 $config->lti_clientid = 'some-client-id'; 1649 $config->typeid = 'some-type-id'; 1650 $config->lti_toolurl = 'some-lti-tool-url'; 1651 1652 $request = lti_build_login_request($course->id, $instance->id, $instance, $config, 'basic-lti-launch-request'); 1653 1654 $this->assertEquals($CFG->wwwroot, $request['iss']); 1655 $this->assertEquals('http://some-lti-tool-url', $request['target_link_uri']); 1656 $this->assertEquals(123456789, $request['login_hint']); 1657 $this->assertEquals($instance->id, $request['lti_message_hint']); 1658 $this->assertEquals('some-client-id', $request['client_id']); 1659 $this->assertEquals('some-type-id', $request['lti_deployment_id']); 1660 } 1661 1662 /** 1663 * Test default orgid is host if not specified in config (tool installed in earlier version of Moodle). 1664 */ 1665 public function test_lti_get_launch_data_default_organizationid_unset_usehost() { 1666 global $DB; 1667 $this->resetAfterTest(); 1668 $this->setAdminUser(); 1669 $config = new \stdClass(); 1670 $config->lti_organizationid = ''; 1671 $course = $this->getDataGenerator()->create_course(); 1672 $type = $this->create_type($config); 1673 $link = $this->create_instance($type, $course); 1674 $launchdata = lti_get_launch_data($link); 1675 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com'); 1676 } 1677 1678 /** 1679 * Test default org id is set to host when config is usehost. 1680 */ 1681 public function test_lti_get_launch_data_default_organizationid_set_usehost() { 1682 global $DB; 1683 $this->resetAfterTest(); 1684 $this->setAdminUser(); 1685 $config = new \stdClass(); 1686 $config->lti_organizationid = ''; 1687 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEHOST; 1688 $course = $this->getDataGenerator()->create_course(); 1689 $type = $this->create_type($config); 1690 $link = $this->create_instance($type, $course); 1691 $launchdata = lti_get_launch_data($link); 1692 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'www.example.com'); 1693 } 1694 1695 /** 1696 * Test default org id is set to site id when config is usesiteid. 1697 */ 1698 public function test_lti_get_launch_data_default_organizationid_set_usesiteid() { 1699 global $DB; 1700 $this->resetAfterTest(); 1701 $this->setAdminUser(); 1702 $config = new \stdClass(); 1703 $config->lti_organizationid = ''; 1704 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID; 1705 $course = $this->getDataGenerator()->create_course(); 1706 $type = $this->create_type($config); 1707 $link = $this->create_instance($type, $course); 1708 $launchdata = lti_get_launch_data($link); 1709 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], md5(get_site_identifier())); 1710 } 1711 1712 /** 1713 * Test orgid can be overridden in which case default is ignored. 1714 */ 1715 public function test_lti_get_launch_data_default_organizationid_orgid_override() { 1716 global $DB; 1717 $this->resetAfterTest(); 1718 $this->setAdminUser(); 1719 $config = new \stdClass(); 1720 $config->lti_organizationid = 'overridden!'; 1721 $config->lti_organizationid_default = LTI_DEFAULT_ORGID_SITEID; 1722 $course = $this->getDataGenerator()->create_course(); 1723 $type = $this->create_type($config); 1724 $link = $this->create_instance($type, $course); 1725 $launchdata = lti_get_launch_data($link); 1726 $this->assertEquals($launchdata[1]['tool_consumer_instance_guid'], 'overridden!'); 1727 } 1728 1729 public function test_get_course_history() { 1730 global $DB; 1731 $this->resetAfterTest(); 1732 $this->setAdminUser(); 1733 $parentparentcourse = $this->getDataGenerator()->create_course(); 1734 $parentcourse = $this->getDataGenerator()->create_course(); 1735 $parentcourse->originalcourseid = $parentparentcourse->id; 1736 $DB->update_record('course', $parentcourse); 1737 $course = $this->getDataGenerator()->create_course(); 1738 $course->originalcourseid = $parentcourse->id; 1739 $DB->update_record('course', $course); 1740 $this->assertEquals(get_course_history($parentparentcourse), []); 1741 $this->assertEquals(get_course_history($parentcourse), [$parentparentcourse->id]); 1742 $this->assertEquals(get_course_history($course), [$parentcourse->id, $parentparentcourse->id]); 1743 $course->originalcourseid = 38903; 1744 $DB->update_record('course', $course); 1745 $this->assertEquals(get_course_history($course), [38903]); 1746 } 1747 1748 /** 1749 * Test the lti_get_ims_role helper function. 1750 * 1751 * @dataProvider lti_get_ims_role_provider 1752 * @covers ::lti_get_ims_role() 1753 * 1754 * @param bool $islti2 whether the method is called with LTI 2.0 role names or not. 1755 * @param string $rolename the name of the role (student, teacher, admin) 1756 * @param null|string $switchedto the role to switch to, or false if not using the 'switch to' functionality. 1757 * @param string $expected the expected role name. 1758 */ 1759 public function test_lti_get_ims_role(bool $islti2, string $rolename, ?string $switchedto, string $expected) { 1760 global $DB; 1761 $this->resetAfterTest(); 1762 1763 $course = $this->getDataGenerator()->create_course(); 1764 $user = $rolename == 'admin' ? get_admin() : $this->getDataGenerator()->create_and_enrol($course, $rolename); 1765 1766 if ($switchedto) { 1767 $this->setUser($user); 1768 $role = $DB->get_record('role', array('shortname' => $switchedto)); 1769 role_switch($role->id, \context_course::instance($course->id)); 1770 } 1771 1772 $this->assertEquals($expected, lti_get_ims_role($user, 0, $course->id, $islti2)); 1773 } 1774 1775 /** 1776 * Data provider for testing lti_get_ims_role. 1777 * 1778 * @return array[] the test case data. 1779 */ 1780 public function lti_get_ims_role_provider() { 1781 return [ 1782 'Student, LTI 1.1, no role switch' => [ 1783 'islti2' => false, 1784 'rolename' => 'student', 1785 'switchedto' => null, 1786 'expected' => 'Learner' 1787 ], 1788 'Student, LTI 2.0, no role switch' => [ 1789 'islti2' => true, 1790 'rolename' => 'student', 1791 'switchedto' => null, 1792 'expected' => 'Learner' 1793 ], 1794 'Teacher, LTI 1.1, no role switch' => [ 1795 'islti2' => false, 1796 'rolename' => 'editingteacher', 1797 'switchedto' => null, 1798 'expected' => 'Instructor' 1799 ], 1800 'Teacher, LTI 2.0, no role switch' => [ 1801 'islti2' => true, 1802 'rolename' => 'editingteacher', 1803 'switchedto' => null, 1804 'expected' => 'Instructor' 1805 ], 1806 'Admin, LTI 1.1, no role switch' => [ 1807 'islti2' => false, 1808 'rolename' => 'admin', 1809 'switchedto' => null, 1810 'expected' => 'Instructor,urn:lti:sysrole:ims/lis/Administrator,urn:lti:instrole:ims/lis/Administrator' 1811 ], 1812 'Admin, LTI 2.0, no role switch' => [ 1813 'islti2' => true, 1814 'rolename' => 'admin', 1815 'switchedto' => null, 1816 'expected' => 'Instructor,http://purl.imsglobal.org/vocab/lis/v2/person#Administrator' 1817 ], 1818 'Admin, LTI 1.1, role switch student' => [ 1819 'islti2' => false, 1820 'rolename' => 'admin', 1821 'switchedto' => 'student', 1822 'expected' => 'Learner' 1823 ], 1824 'Admin, LTI 2.0, role switch student' => [ 1825 'islti2' => true, 1826 'rolename' => 'admin', 1827 'switchedto' => 'student', 1828 'expected' => 'Learner' 1829 ], 1830 'Admin, LTI 1.1, role switch teacher' => [ 1831 'islti2' => false, 1832 'rolename' => 'admin', 1833 'switchedto' => 'editingteacher', 1834 'expected' => 'Instructor' 1835 ], 1836 'Admin, LTI 2.0, role switch teacher' => [ 1837 'islti2' => true, 1838 'rolename' => 'admin', 1839 'switchedto' => 'editingteacher', 1840 'expected' => 'Instructor' 1841 ], 1842 ]; 1843 } 1844 1845 /** 1846 * Test lti_get_lti_types_and_proxies with no limit or offset. 1847 */ 1848 public function test_lti_get_lti_types_and_proxies_with_no_limit() { 1849 $this->resetAfterTest(); 1850 $this->setAdminUser(); 1851 $this->generate_tool_types_and_proxies(10); 1852 list($proxies, $types) = lti_get_lti_types_and_proxies(); 1853 1854 $this->assertCount(10, $proxies); 1855 $this->assertCount(10, $types); 1856 } 1857 1858 /** 1859 * Test lti_get_lti_types_and_proxies with limits. 1860 */ 1861 public function test_lti_get_lti_types_and_proxies_with_limit() { 1862 $this->resetAfterTest(); 1863 $this->setAdminUser(); 1864 $this->generate_tool_types_and_proxies(10); 1865 1866 // Get the middle 10 data sets (of 20 total). 1867 list($proxies, $types) = lti_get_lti_types_and_proxies(10, 5); 1868 1869 $this->assertCount(5, $proxies); 1870 $this->assertCount(5, $types); 1871 1872 // Get the last 5 data sets with large limit (of 20 total). 1873 list($proxies, $types) = lti_get_lti_types_and_proxies(50, 15); 1874 1875 $this->assertCount(0, $proxies); 1876 $this->assertCount(5, $types); 1877 1878 // Get the last 13 data sets with large limit (of 20 total). 1879 list($proxies, $types) = lti_get_lti_types_and_proxies(50, 7); 1880 1881 $this->assertCount(3, $proxies); 1882 $this->assertCount(10, $types); 1883 } 1884 1885 /** 1886 * Test lti_get_lti_types_and_proxies with limits and only fetching orphaned proxies. 1887 */ 1888 public function test_lti_get_lti_types_and_proxies_with_limit_and_orphaned_proxies() { 1889 $this->resetAfterTest(); 1890 $this->setAdminUser(); 1891 $this->generate_tool_types_and_proxies(10, 5); 1892 1893 // Get the first 10 data sets (of 15 total). 1894 list($proxies, $types) = lti_get_lti_types_and_proxies(10, 0, true); 1895 1896 $this->assertCount(5, $proxies); 1897 $this->assertCount(5, $types); 1898 1899 // Get the middle 10 data sets with large limit (of 15 total). 1900 list($proxies, $types) = lti_get_lti_types_and_proxies(10, 2, true); 1901 1902 $this->assertCount(3, $proxies); 1903 $this->assertCount(7, $types); 1904 1905 // Get the last 5 data sets with large limit (of 15 total). 1906 list($proxies, $types) = lti_get_lti_types_and_proxies(50, 10, true); 1907 1908 $this->assertCount(0, $proxies); 1909 $this->assertCount(5, $types); 1910 } 1911 1912 /** 1913 * Test lti_get_lti_types_and_proxies_count. 1914 */ 1915 public function test_lti_get_lti_types_and_proxies_count_with_no_filters() { 1916 $this->resetAfterTest(); 1917 $this->setAdminUser(); 1918 $this->generate_tool_types_and_proxies(10, 5); 1919 1920 $totalcount = lti_get_lti_types_and_proxies_count(); 1921 $this->assertEquals(25, $totalcount); // 10 types, 15 proxies. 1922 } 1923 1924 /** 1925 * Test lti_get_lti_types_and_proxies_count only counting orphaned proxies. 1926 */ 1927 public function test_lti_get_lti_types_and_proxies_count_with_only_orphaned_proxies() { 1928 $this->resetAfterTest(); 1929 $this->setAdminUser(); 1930 $this->generate_tool_types_and_proxies(10, 5); 1931 1932 $orphanedcount = lti_get_lti_types_and_proxies_count(true); 1933 $this->assertEquals(15, $orphanedcount); // 10 types, 5 proxies. 1934 } 1935 1936 /** 1937 * Test lti_get_lti_types_and_proxies_count only matching tool type with toolproxyid. 1938 */ 1939 public function test_lti_get_lti_types_and_proxies_count_type_with_proxyid() { 1940 $this->resetAfterTest(); 1941 $this->setAdminUser(); 1942 ['proxies' => $proxies, 'types' => $types] = $this->generate_tool_types_and_proxies(10, 5); 1943 1944 $countwithproxyid = lti_get_lti_types_and_proxies_count(false, $proxies[0]->id); 1945 $this->assertEquals(16, $countwithproxyid); // 1 type, 15 proxies. 1946 } 1947 1948 /** 1949 * Verify that empty curl responses lead to the proper moodle_exception, not to XML ValueError. 1950 * 1951 * @covers ::lti_load_cartridge() 1952 */ 1953 public function test_empty_reponse_lti_load_cartridge() { 1954 // Mock the curl response to empty string, this is hardly 1955 // reproducible in real life (only Windows + GHA). 1956 \curl::mock_response(''); 1957 1958 $this->expectException(\moodle_exception::class); 1959 lti_load_cartridge('http://example.com/mocked/empty/response', []); 1960 } 1961 1962 /** 1963 * Create an LTI Tool. 1964 * 1965 * @param object $config tool config. 1966 * 1967 * @return object tool. 1968 */ 1969 private function create_type(object $config) { 1970 $type = new \stdClass(); 1971 $type->state = LTI_TOOL_STATE_CONFIGURED; 1972 $type->name = "Test tool"; 1973 $type->description = "Example description"; 1974 $type->clientid = "Test client ID"; 1975 $type->baseurl = $this->getExternalTestFileUrl('/test.html'); 1976 1977 $configbase = new \stdClass(); 1978 $configbase->lti_acceptgrades = LTI_SETTING_NEVER; 1979 $configbase->lti_sendname = LTI_SETTING_NEVER; 1980 $configbase->lti_sendemailaddr = LTI_SETTING_NEVER; 1981 $mergedconfig = (object) array_merge( (array) $configbase, (array) $config); 1982 $typeid = lti_add_type($type, $mergedconfig); 1983 return lti_get_type($typeid); 1984 } 1985 1986 /** 1987 * Create an LTI Instance for the tool in a given course. 1988 * 1989 * @param object $type tool for which an instance should be added. 1990 * @param object $course course where the instance should be added. 1991 * 1992 * @return object instance. 1993 */ 1994 private function create_instance(object $type, object $course) { 1995 $generator = $this->getDataGenerator()->get_plugin_generator('mod_lti'); 1996 return $generator->create_instance(array('course' => $course->id, 1997 'toolurl' => $type->baseurl, 1998 'typeid' => $type->id 1999 ), array()); 2000 } 2001 2002 /** 2003 * Generate a number of LTI tool types and proxies. 2004 * 2005 * @param int $toolandproxycount How many tool types and associated proxies to create. E.g. Value of 10 will create 10 types 2006 * and 10 proxies. 2007 * @param int $orphanproxycount How many orphaned proxies to create. 2008 * @return array[] 2009 */ 2010 private function generate_tool_types_and_proxies(int $toolandproxycount = 0, int $orphanproxycount = 0) { 2011 $proxies = []; 2012 $types = []; 2013 for ($i = 0; $i < $toolandproxycount; $i++) { 2014 $proxies[$i] = $this->generate_tool_proxy($i); 2015 $types[$i] = $this->generate_tool_type($i, $proxies[$i]->id); 2016 2017 } 2018 for ($i = $toolandproxycount; $i < ($toolandproxycount + $orphanproxycount); $i++) { 2019 $proxies[$i] = $this->generate_tool_proxy($i); 2020 } 2021 2022 return ['proxies' => $proxies, 'types' => $types]; 2023 } 2024 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body