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