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 lti/openidregistrationlib.php 37 * 38 * @package mod_lti 39 * @copyright 2020 Claude Vervoort, Cengage 40 * @author Claude Vervoort 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 44 use mod_lti\local\ltiopenid\registration_exception; 45 use mod_lti\local\ltiopenid\registration_helper; 46 47 /** 48 * OpenId LTI Registration library tests 49 */ 50 class mod_lti_openidregistrationlib_testcase extends advanced_testcase { 51 52 /** 53 * @var string A has-it-all client registration. 54 */ 55 private $registrationfulljson = <<<EOD 56 { 57 "application_type": "web", 58 "response_types": ["id_token"], 59 "grant_types": ["implict", "client_credentials"], 60 "initiate_login_uri": "https://client.example.org/lti/init", 61 "redirect_uris": 62 ["https://client.example.org/callback", 63 "https://client.example.org/callback2"], 64 "client_name": "Virtual Garden", 65 "client_name#ja": "バーチャルガーデン", 66 "jwks_uri": "https://client.example.org/.well-known/jwks.json", 67 "logo_uri": "https://client.example.org/logo.png", 68 "policy_uri": "https://client.example.org/privacy", 69 "policy_uri#ja": "https://client.example.org/privacy?lang=ja", 70 "tos_uri": "https://client.example.org/tos", 71 "tos_uri#ja": "https://client.example.org/tos?lang=ja", 72 "token_endpoint_auth_method": "private_key_jwt", 73 "contacts": ["ve7jtb@example.org", "mary@example.org"], 74 "scope": "https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", 75 "https://purl.imsglobal.org/spec/lti-tool-configuration": { 76 "domain": "client.example.org", 77 "description": "Learn Botany by tending to your little (virtual) garden.", 78 "description#ja": "小さな(仮想)庭に行くことで植物学を学びましょう。", 79 "target_link_uri": "https://client.example.org/lti", 80 "custom_parameters": { 81 "context_history": "\$Context.id.history" 82 }, 83 "claims": ["iss", "sub", "name", "given_name", "family_name", "email"], 84 "messages": [ 85 { 86 "type": "LtiDeepLinkingRequest", 87 "target_link_uri": "https://client.example.org/lti/dl", 88 "label": "Add a virtual garden", 89 "label#ja": "バーチャルガーデンを追加する" 90 } 91 ] 92 } 93 } 94 EOD; 95 96 /** 97 * @var string A minimalist client registration. 98 */ 99 private $registrationminimaljson = <<<EOD 100 { 101 "application_type": "web", 102 "response_types": ["id_token"], 103 "grant_types": ["implict", "client_credentials"], 104 "initiate_login_uri": "https://client.example.org/lti/init", 105 "redirect_uris": 106 ["https://client.example.org/callback"], 107 "client_name": "Virtual Garden", 108 "jwks_uri": "https://client.example.org/.well-known/jwks.json", 109 "token_endpoint_auth_method": "private_key_jwt", 110 "https://purl.imsglobal.org/spec/lti-tool-configuration": { 111 "domain": "client.example.org", 112 "target_link_uri": "https://client.example.org/lti" 113 } 114 } 115 EOD; 116 117 /** 118 * @var string A minimalist with deep linking client registration. 119 */ 120 private $registrationminimaldljson = <<<EOD 121 { 122 "application_type": "web", 123 "response_types": ["id_token"], 124 "grant_types": ["implict", "client_credentials"], 125 "initiate_login_uri": "https://client.example.org/lti/init", 126 "redirect_uris": 127 ["https://client.example.org/callback"], 128 "client_name": "Virtual Garden", 129 "jwks_uri": "https://client.example.org/.well-known/jwks.json", 130 "token_endpoint_auth_method": "private_key_jwt", 131 "https://purl.imsglobal.org/spec/lti-tool-configuration": { 132 "domain": "client.example.org", 133 "target_link_uri": "https://client.example.org/lti", 134 "messages": [ 135 { 136 "type": "LtiDeepLinkingRequest" 137 } 138 ] 139 } 140 } 141 EOD; 142 143 /** 144 * Test the mapping from Registration JSON to LTI Config for a has-it-all tool registration. 145 */ 146 public function test_to_config_full() { 147 $registration = json_decode($this->registrationfulljson, true); 148 $registration['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'; 149 $config = registration_helper::registration_to_config($registration, 'TheClientId'); 150 $this->assertEquals('JWK_KEYSET', $config->lti_keytype); 151 $this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion); 152 $this->assertEquals('TheClientId', $config->lti_clientid); 153 $this->assertEquals('Virtual Garden', $config->lti_typename); 154 $this->assertEquals('Learn Botany by tending to your little (virtual) garden.', $config->lti_description); 155 $this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin); 156 $this->assertEquals(implode(PHP_EOL, ["https://client.example.org/callback", 157 "https://client.example.org/callback2"]), $config->lti_redirectionuris); 158 $this->assertEquals("context_history=\$Context.id.history", $config->lti_customparameters); 159 $this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset); 160 $this->assertEquals("https://client.example.org/logo.png", $config->lti_icon); 161 $this->assertEquals(2, $config->ltiservice_gradesynchronization); 162 $this->assertEquals(LTI_SETTING_DELEGATE, $config->lti_acceptgrades); 163 $this->assertEquals(1, $config->ltiservice_memberships); 164 $this->assertEquals(0, $config->ltiservice_toolsettings); 165 $this->assertEquals('client.example.org', $config->lti_tooldomain); 166 $this->assertEquals('https://client.example.org/lti', $config->lti_toolurl); 167 $this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendname); 168 $this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendemailaddr); 169 $this->assertEquals(1, $config->lti_contentitem); 170 $this->assertEquals('https://client.example.org/lti/dl', $config->lti_toolurl_ContentItemSelectionRequest); 171 } 172 173 /** 174 * Test the mapping from Registration JSON to LTI Config for a minimal tool registration. 175 */ 176 public function test_to_config_minimal() { 177 $registration = json_decode($this->registrationminimaljson, true); 178 $config = registration_helper::registration_to_config($registration, 'TheClientId'); 179 $this->assertEquals('JWK_KEYSET', $config->lti_keytype); 180 $this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion); 181 $this->assertEquals('TheClientId', $config->lti_clientid); 182 $this->assertEquals('Virtual Garden', $config->lti_typename); 183 $this->assertEmpty($config->lti_description); 184 $this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin); 185 $this->assertEquals('https://client.example.org/callback', $config->lti_redirectionuris); 186 $this->assertEmpty($config->lti_customparameters); 187 $this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset); 188 $this->assertEmpty($config->lti_icon); 189 $this->assertEquals(0, $config->ltiservice_gradesynchronization); 190 $this->assertEquals(LTI_SETTING_NEVER, $config->lti_acceptgrades); 191 $this->assertEquals(0, $config->ltiservice_memberships); 192 $this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendname); 193 $this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendemailaddr); 194 $this->assertEquals(0, $config->lti_contentitem); 195 } 196 197 /** 198 * Test the mapping from Registration JSON to LTI Config for a minimal tool with 199 * deep linking support registration. 200 */ 201 public function test_to_config_minimal_with_deeplinking() { 202 $registration = json_decode($this->registrationminimaldljson, true); 203 $config = registration_helper::registration_to_config($registration, 'TheClientId'); 204 $this->assertEquals(1, $config->lti_contentitem); 205 $this->assertEmpty($config->lti_toolurl_ContentItemSelectionRequest); 206 } 207 208 /** 209 * Validation Test: initiation login. 210 */ 211 public function test_validation_initlogin() { 212 $registration = json_decode($this->registrationfulljson, true); 213 $this->expectException(registration_exception::class); 214 $this->expectExceptionCode(400); 215 unset($registration['initiate_login_uri']); 216 registration_helper::registration_to_config($registration, 'TheClientId'); 217 } 218 219 /** 220 * Validation Test: redirect uris. 221 */ 222 public function test_validation_redirecturis() { 223 $registration = json_decode($this->registrationfulljson, true); 224 $this->expectException(registration_exception::class); 225 $this->expectExceptionCode(400); 226 unset($registration['redirect_uris']); 227 registration_helper::registration_to_config($registration, 'TheClientId'); 228 } 229 230 /** 231 * Validation Test: jwks uri empty. 232 */ 233 public function test_validation_jwks() { 234 $registration = json_decode($this->registrationfulljson, true); 235 $this->expectException(registration_exception::class); 236 $this->expectExceptionCode(400); 237 $registration['jwks_uri'] = ''; 238 registration_helper::registration_to_config($registration, 'TheClientId'); 239 } 240 241 /** 242 * Validation Test: no domain nor targetlinkuri is rejected. 243 */ 244 public function test_validation_missing_domain_targetlinkuri() { 245 $registration = json_decode($this->registrationminimaljson, true); 246 $this->expectException(registration_exception::class); 247 $this->expectExceptionCode(400); 248 unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']); 249 unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']); 250 registration_helper::registration_to_config($registration, 'TheClientId'); 251 } 252 253 /** 254 * Validation Test: mismatch between domain and targetlinkuri is rejected. 255 */ 256 public function test_validation_domain_targetlinkuri_match() { 257 $registration = json_decode($this->registrationminimaljson, true); 258 $this->expectException(registration_exception::class); 259 $this->expectExceptionCode(400); 260 $registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain'] = 'not.the.right.domain'; 261 registration_helper::registration_to_config($registration, 'TheClientId'); 262 } 263 264 /** 265 * Validation Test: domain is required. 266 */ 267 public function test_validation_domain_targetlinkuri_onlylink() { 268 $registration = json_decode($this->registrationminimaljson, true); 269 unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']); 270 $this->expectException(registration_exception::class); 271 $this->expectExceptionCode(400); 272 $config = registration_helper::registration_to_config($registration, 'TheClientId'); 273 } 274 275 /** 276 * Validation Test: base url (targetlinkuri) is built from domain if not present. 277 */ 278 public function test_validation_domain_targetlinkuri_onlydomain() { 279 $registration = json_decode($this->registrationminimaljson, true); 280 unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']); 281 $config = registration_helper::registration_to_config($registration, 'TheClientId'); 282 $this->assertEquals('client.example.org', $config->lti_tooldomain); 283 $this->assertEquals('https://client.example.org', $config->lti_toolurl); 284 } 285 286 /** 287 * Test the transformation from lti config to OpenId LTI Client Registration response. 288 */ 289 public function test_config_to_registration() { 290 $orig = json_decode($this->registrationfulljson, true); 291 $orig['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly'; 292 $reg = registration_helper::config_to_registration(registration_helper::registration_to_config($orig, 'clid'), 12); 293 $this->assertEquals('clid', $reg['client_id']); 294 $this->assertEquals($orig['response_types'], $reg['response_types']); 295 $this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']); 296 $this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']); 297 $this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']); 298 $this->assertEquals($orig['logo_uri'], $reg['logo_uri']); 299 $this->assertEquals('https://purl.imsglobal.org/spec/lti-ags/scope/score '. 300 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly '. 301 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly '. 302 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem '. 303 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly', $reg['scope']); 304 $ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration']; 305 $lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration']; 306 $this->assertEquals("12", $lti['deployment_id']); 307 $this->assertEquals($ltiorig['target_link_uri'], $lti['target_link_uri']); 308 $this->assertEquals($ltiorig['domain'], $lti['domain']); 309 $this->assertEquals($ltiorig['custom_parameters'], $lti['custom_parameters']); 310 $this->assertEquals($ltiorig['description'], $lti['description']); 311 $dlmsgorig = $ltiorig['messages'][0]; 312 $dlmsg = $lti['messages'][0]; 313 $this->assertEquals($dlmsgorig['type'], $dlmsg['type']); 314 $this->assertEquals($dlmsgorig['target_link_uri'], $dlmsg['target_link_uri']); 315 $this->assertTrue(in_array('iss', $lti['claims'])); 316 $this->assertTrue(in_array('sub', $lti['claims'])); 317 $this->assertTrue(in_array('email', $lti['claims'])); 318 $this->assertTrue(in_array('family_name', $lti['claims'])); 319 $this->assertTrue(in_array('given_name', $lti['claims'])); 320 $this->assertTrue(in_array('name', $lti['claims'])); 321 } 322 323 /** 324 * Test the transformation from lti config to OpenId LTI Client Registration response for the minimal version. 325 */ 326 public function test_config_to_registration_minimal() { 327 $orig = json_decode($this->registrationminimaljson, true); 328 $reg = registration_helper::config_to_registration(registration_helper::registration_to_config($orig, 'clid'), 12); 329 $this->assertEquals('clid', $reg['client_id']); 330 $this->assertEquals($orig['response_types'], $reg['response_types']); 331 $this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']); 332 $this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']); 333 $this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']); 334 $this->assertEquals('', $reg['scope']); 335 $ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration']; 336 $lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration']; 337 $this->assertTrue(in_array('iss', $lti['claims'])); 338 $this->assertTrue(in_array('sub', $lti['claims'])); 339 $this->assertFalse(in_array('email', $lti['claims'])); 340 $this->assertFalse(in_array('family_name', $lti['claims'])); 341 $this->assertFalse(in_array('given_name', $lti['claims'])); 342 $this->assertFalse(in_array('name', $lti['claims'])); 343 } 344 345 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body