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