Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core; 18 19 use core\oauth2\access_token; 20 use core\oauth2\api; 21 use core\oauth2\endpoint; 22 use core\oauth2\issuer; 23 use core\oauth2\system_account; 24 25 /** 26 * Tests for oauth2 apis (\core\oauth2\*). 27 * 28 * @package core 29 * @copyright 2017 Damyon Wiese 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later. 31 * @coversDefaultClass \core\oauth2\api 32 */ 33 class oauth2_test extends \advanced_testcase { 34 35 /** 36 * Tests the crud operations on oauth2 issuers. 37 */ 38 public function test_create_and_delete_standard_issuers() { 39 $this->resetAfterTest(); 40 $this->setAdminUser(); 41 api::create_standard_issuer('google'); 42 api::create_standard_issuer('facebook'); 43 api::create_standard_issuer('microsoft'); 44 api::create_standard_issuer('nextcloud', 'https://dummy.local/nextcloud/'); 45 46 $issuers = api::get_all_issuers(); 47 48 $this->assertEquals($issuers[0]->get('name'), 'Google'); 49 $this->assertEquals($issuers[1]->get('name'), 'Facebook'); 50 $this->assertEquals($issuers[2]->get('name'), 'Microsoft'); 51 $this->assertEquals($issuers[3]->get('name'), 'Nextcloud'); 52 53 api::move_down_issuer($issuers[0]->get('id')); 54 55 $issuers = api::get_all_issuers(); 56 57 $this->assertEquals($issuers[0]->get('name'), 'Facebook'); 58 $this->assertEquals($issuers[1]->get('name'), 'Google'); 59 $this->assertEquals($issuers[2]->get('name'), 'Microsoft'); 60 $this->assertEquals($issuers[3]->get('name'), 'Nextcloud'); 61 62 api::delete_issuer($issuers[1]->get('id')); 63 64 $issuers = api::get_all_issuers(); 65 66 $this->assertEquals($issuers[0]->get('name'), 'Facebook'); 67 $this->assertEquals($issuers[1]->get('name'), 'Microsoft'); 68 $this->assertEquals($issuers[2]->get('name'), 'Nextcloud'); 69 } 70 71 /** 72 * Tests the crud operations on oauth2 issuers. 73 */ 74 public function test_create_nextcloud_without_url() { 75 $this->resetAfterTest(); 76 $this->setAdminUser(); 77 78 $this->expectException(\moodle_exception::class); 79 api::create_standard_issuer('nextcloud'); 80 } 81 82 /** 83 * Tests we can list and delete each of the persistents related to an issuer. 84 */ 85 public function test_getters() { 86 $this->resetAfterTest(); 87 $this->setAdminUser(); 88 $issuer = api::create_standard_issuer('microsoft'); 89 90 $same = api::get_issuer($issuer->get('id')); 91 92 foreach ($same->properties_definition() as $name => $def) { 93 $this->assertTrue($issuer->get($name) == $same->get($name)); 94 } 95 96 $endpoints = api::get_endpoints($issuer); 97 $same = api::get_endpoint($endpoints[0]->get('id')); 98 $this->assertEquals($endpoints[0]->get('id'), $same->get('id')); 99 $this->assertEquals($endpoints[0]->get('name'), $same->get('name')); 100 101 $todelete = $endpoints[0]; 102 api::delete_endpoint($todelete->get('id')); 103 $endpoints = api::get_endpoints($issuer); 104 $this->assertNotEquals($endpoints[0]->get('id'), $todelete->get('id')); 105 106 $userfields = api::get_user_field_mappings($issuer); 107 $same = api::get_user_field_mapping($userfields[0]->get('id')); 108 $this->assertEquals($userfields[0]->get('id'), $same->get('id')); 109 110 $todelete = $userfields[0]; 111 api::delete_user_field_mapping($todelete->get('id')); 112 $userfields = api::get_user_field_mappings($issuer); 113 $this->assertNotEquals($userfields[0]->get('id'), $todelete->get('id')); 114 } 115 116 /** 117 * Data provider for \core_oauth2_testcase::test_get_system_oauth_client(). 118 * 119 * @return array 120 */ 121 public function system_oauth_client_provider() { 122 return [ 123 [ 124 (object) [ 125 'access_token' => 'fdas...', 126 'token_type' => 'Bearer', 127 'expires_in' => '3600', 128 'id_token' => 'llfsd..', 129 ], HOURSECS - 10 130 ], 131 [ 132 (object) [ 133 'access_token' => 'fdas...', 134 'token_type' => 'Bearer', 135 'id_token' => 'llfsd..', 136 ], WEEKSECS 137 ], 138 ]; 139 } 140 141 /** 142 * Tests we can get a logged in oauth client for a system account. 143 * 144 * @dataProvider system_oauth_client_provider 145 * @param \stdClass $responsedata The response data to be mocked. 146 * @param int $expiresin The expected expiration time. 147 */ 148 public function test_get_system_oauth_client($responsedata, $expiresin) { 149 $this->resetAfterTest(); 150 $this->setAdminUser(); 151 152 $issuer = api::create_standard_issuer('microsoft'); 153 154 $requiredscopes = api::get_system_scopes_for_issuer($issuer); 155 // Fake a system account. 156 $data = (object) [ 157 'issuerid' => $issuer->get('id'), 158 'refreshtoken' => 'abc', 159 'grantedscopes' => $requiredscopes, 160 'email' => 'sys@example.com', 161 'username' => 'sys' 162 ]; 163 $sys = new system_account(0, $data); 164 $sys->create(); 165 166 // Fake a response with an access token. 167 $response = json_encode($responsedata); 168 \curl::mock_response($response); 169 $client = api::get_system_oauth_client($issuer); 170 $this->assertTrue($client->is_logged_in()); 171 172 // Check token expiry. 173 $accesstoken = access_token::get_record(['issuerid' => $issuer->get('id')]); 174 175 // Get the difference between the actual and expected expiry times. 176 // They might differ by a couple of seconds depending on the timing when the token gets actually processed. 177 $expiresdifference = time() + $expiresin - $accesstoken->get('expires'); 178 179 // Assert that the actual token expiration is more or less the same as the expected. 180 $this->assertGreaterThanOrEqual(0, $expiresdifference); 181 $this->assertLessThanOrEqual(3, $expiresdifference); 182 } 183 184 /** 185 * Tests we can enable and disable an issuer. 186 */ 187 public function test_enable_disable_issuer() { 188 $this->resetAfterTest(); 189 $this->setAdminUser(); 190 191 $issuer = api::create_standard_issuer('microsoft'); 192 193 $issuerid = $issuer->get('id'); 194 195 api::enable_issuer($issuerid); 196 $check = api::get_issuer($issuer->get('id')); 197 $this->assertTrue((boolean)$check->get('enabled')); 198 199 api::enable_issuer($issuerid); 200 $check = api::get_issuer($issuer->get('id')); 201 $this->assertTrue((boolean)$check->get('enabled')); 202 203 api::disable_issuer($issuerid); 204 $check = api::get_issuer($issuer->get('id')); 205 $this->assertFalse((boolean)$check->get('enabled')); 206 207 api::enable_issuer($issuerid); 208 $check = api::get_issuer($issuer->get('id')); 209 $this->assertTrue((boolean)$check->get('enabled')); 210 } 211 212 /** 213 * Test the alloweddomains for an issuer. 214 */ 215 public function test_issuer_alloweddomains() { 216 $this->resetAfterTest(); 217 $this->setAdminUser(); 218 219 $issuer = api::create_standard_issuer('microsoft'); 220 221 $issuer->set('alloweddomains', ''); 222 223 // Anything is allowed when domain is empty. 224 $this->assertTrue($issuer->is_valid_login_domain('')); 225 $this->assertTrue($issuer->is_valid_login_domain('a@b')); 226 $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com')); 227 228 $issuer->set('alloweddomains', 'example.com'); 229 230 // One domain - must match exactly - no substrings etc. 231 $this->assertFalse($issuer->is_valid_login_domain('')); 232 $this->assertFalse($issuer->is_valid_login_domain('a@b')); 233 $this->assertFalse($issuer->is_valid_login_domain('longer.example@example')); 234 $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com')); 235 236 $issuer->set('alloweddomains', 'example.com,example.net'); 237 // Multiple domains - must match any exactly - no substrings etc. 238 $this->assertFalse($issuer->is_valid_login_domain('')); 239 $this->assertFalse($issuer->is_valid_login_domain('a@b')); 240 $this->assertFalse($issuer->is_valid_login_domain('longer.example@example')); 241 $this->assertFalse($issuer->is_valid_login_domain('invalid@email@example.net')); 242 $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.net')); 243 $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com')); 244 245 $issuer->set('alloweddomains', '*.example.com'); 246 // Wildcard. 247 $this->assertFalse($issuer->is_valid_login_domain('')); 248 $this->assertFalse($issuer->is_valid_login_domain('a@b')); 249 $this->assertFalse($issuer->is_valid_login_domain('longer.example@example')); 250 $this->assertFalse($issuer->is_valid_login_domain('longer.example@example.com')); 251 $this->assertTrue($issuer->is_valid_login_domain('longer.example@sub.example.com')); 252 } 253 254 /** 255 * Test endpoints creation for issuers. 256 * @dataProvider create_endpoints_for_standard_issuer_provider 257 * 258 * @covers ::create_endpoints_for_standard_issuer 259 * 260 * @param string $type Issuer type to create. 261 * @param string|null $discoveryurl Expected discovery URL or null if this endpoint doesn't exist. 262 * @param bool $hasmappingfields True if it's expected the issuer to create has mapping fields. 263 * @param string|null $baseurl The service URL (mandatory parameter for some issuers, such as NextCloud or IMS OBv2.1). 264 * @param string|null $expectedexception Name of the expected expection or null if no exception will be thrown. 265 */ 266 public function test_create_endpoints_for_standard_issuer(string $type, ?string $discoveryurl = null, 267 bool $hasmappingfields = true, ?string $baseurl = null, ?string $expectedexception = null): void { 268 269 $this->resetAfterTest(); 270 271 // Mark test as long because it connects with external services. 272 if (!PHPUNIT_LONGTEST) { 273 $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); 274 } 275 276 $this->setAdminUser(); 277 278 // Method create_endpoints_for_standard_issuer is called internally from create_standard_issuer. 279 if ($expectedexception) { 280 $this->expectException($expectedexception); 281 } 282 $issuer = api::create_standard_issuer($type, $baseurl); 283 284 // Check endpoints have been created. 285 $endpoints = api::get_endpoints($issuer); 286 $this->assertNotEmpty($endpoints); 287 $this->assertNotEmpty($issuer->get('image')); 288 // Check discovery URL. 289 if ($discoveryurl) { 290 $this->assertStringContainsString($discoveryurl, $issuer->get_endpoint_url('discovery')); 291 } else { 292 $this->assertFalse($issuer->get_endpoint_url('discovery')); 293 } 294 // Check userfield mappings. 295 $userfieldmappings =api::get_user_field_mappings($issuer); 296 if ($hasmappingfields) { 297 $this->assertNotEmpty($userfieldmappings); 298 } else { 299 $this->assertEmpty($userfieldmappings); 300 } 301 } 302 303 /** 304 * Data provider for test_create_endpoints_for_standard_issuer. 305 * 306 * @return array 307 */ 308 public function create_endpoints_for_standard_issuer_provider(): array { 309 return [ 310 'Google' => [ 311 'type' => 'google', 312 'discoveryurl' => '.well-known/openid-configuration', 313 ], 314 'Google will work too with a valid baseurl parameter' => [ 315 'type' => 'google', 316 'discoveryurl' => '.well-known/openid-configuration', 317 'hasmappingfields' => true, 318 'baseurl' => 'https://accounts.google.com/', 319 ], 320 'IMS OBv2.1' => [ 321 'type' => 'imsobv2p1', 322 'discoveryurl' => '.well-known/badgeconnect.json', 323 'hasmappingfields' => false, 324 'baseurl' => 'https://dc.imsglobal.org/', 325 ], 326 'IMS OBv2.1 without slash in baseurl should work too' => [ 327 'type' => 'imsobv2p1', 328 'discoveryurl' => '.well-known/badgeconnect.json', 329 'hasmappingfields' => false, 330 'baseurl' => 'https://dc.imsglobal.org', 331 ], 332 'IMS OBv2.1 with empty baseurl should return an exception' => [ 333 'type' => 'imsobv2p1', 334 'discoveryurl' => null, 335 'hasmappingfields' => false, 336 'baseurl' => null, 337 'expectedexception' => \moodle_exception::class, 338 ], 339 'Microsoft' => [ 340 'type' => 'microsoft', 341 ], 342 'Facebook' => [ 343 'type' => 'facebook', 344 ], 345 'NextCloud' => [ 346 'type' => 'nextcloud', 347 'discoveryurl' => null, 348 'hasmappingfields' => true, 349 'baseurl' => 'https://dummy.local/nextcloud/', 350 ], 351 'NextCloud with empty baseurl should return an exception' => [ 352 'type' => 'nextcloud', 353 'discoveryurl' => null, 354 'hasmappingfields' => true, 355 'baseurl' => null, 356 'expectedexception' => \moodle_exception::class, 357 ], 358 'Invalid type should return an exception' => [ 359 'type' => 'fictitious', 360 'discoveryurl' => null, 361 'hasmappingfields' => true, 362 'baseurl' => null, 363 'expectedexception' => \moodle_exception::class, 364 ], 365 ]; 366 } 367 368 /** 369 * Test for get all issuers. 370 */ 371 public function test_get_all_issuers() { 372 $this->resetAfterTest(); 373 $this->setAdminUser(); 374 $googleissuer = api::create_standard_issuer('google'); 375 api::create_standard_issuer('facebook'); 376 api::create_standard_issuer('microsoft'); 377 378 // Set Google issuer to be shown only on login page. 379 $record = $googleissuer->to_record(); 380 $record->showonloginpage = $googleissuer::LOGINONLY; 381 api::update_issuer($record); 382 383 $issuers = api::get_all_issuers(); 384 $this->assertCount(2, $issuers); 385 $expected = ['Microsoft', 'Facebook']; 386 $this->assertEqualsCanonicalizing($expected, [$issuers[0]->get_display_name(), $issuers[1]->get_display_name()]); 387 388 $issuers = api::get_all_issuers(true); 389 $this->assertCount(3, $issuers); 390 $expected = ['Google', 'Microsoft', 'Facebook']; 391 $this->assertEqualsCanonicalizing($expected, 392 [$issuers[0]->get_display_name(), $issuers[1]->get_display_name(), $issuers[2]->get_display_name()]); 393 } 394 395 /** 396 * Test for is available for login. 397 */ 398 public function test_is_available_for_login() { 399 $this->resetAfterTest(); 400 $this->setAdminUser(); 401 $googleissuer = api::create_standard_issuer('google'); 402 403 // Set Google issuer to be shown only on login page. 404 $record = $googleissuer->to_record(); 405 $record->showonloginpage = $googleissuer::LOGINONLY; 406 api::update_issuer($record); 407 408 $this->assertFalse($googleissuer->is_available_for_login()); 409 410 // Set a clientid and clientsecret. 411 $googleissuer->set('clientid', 'clientid'); 412 $googleissuer->set('clientsecret', 'secret'); 413 $googleissuer->update(); 414 415 $this->assertTrue($googleissuer->is_available_for_login()); 416 417 // Set showonloginpage to service only. 418 $googleissuer->set('showonloginpage', issuer::SERVICEONLY); 419 $googleissuer->update(); 420 421 $this->assertFalse($googleissuer->is_available_for_login()); 422 423 // Set showonloginpage to everywhere (service and login) and disable issuer. 424 $googleissuer->set('showonloginpage', issuer::EVERYWHERE); 425 $googleissuer->set('enabled', 0); 426 $googleissuer->update(); 427 428 $this->assertFalse($googleissuer->is_available_for_login()); 429 430 // Enable issuer. 431 $googleissuer->set('enabled', 1); 432 $googleissuer->update(); 433 434 $this->assertTrue($googleissuer->is_available_for_login()); 435 436 // Remove userinfo endpoint from issuer. 437 $endpoint = endpoint::get_record([ 438 'issuerid' => $googleissuer->get('id'), 439 'name' => 'userinfo_endpoint' 440 ]); 441 api::delete_endpoint($endpoint->get('id')); 442 443 $this->assertFalse($googleissuer->is_available_for_login()); 444 } 445 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body