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