See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 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 namespace quizaccess_seb; 18 19 use quizaccess_seb; 20 21 defined('MOODLE_INTERNAL') || die(); 22 23 require_once (__DIR__ . '/test_helper_trait.php'); 24 25 /** 26 * PHPUnit tests for plugin rule class. 27 * 28 * @package quizaccess_seb 29 * @author Andrew Madden <andrewmadden@catalyst-au.net> 30 * @copyright 2020 Catalyst IT 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 * @covers \quizaccess_seb 33 */ 34 class rule_test extends \advanced_testcase { 35 use \quizaccess_seb_test_helper_trait; 36 37 /** 38 * Called before every test. 39 */ 40 public function setUp(): void { 41 parent::setUp(); 42 43 $this->resetAfterTest(); 44 $this->course = $this->getDataGenerator()->create_course(); 45 } 46 47 /** 48 * Called after every test. 49 */ 50 public function tearDown(): void { 51 global $SESSION; 52 53 if (!empty($this->quiz)) { 54 unset($SESSION->quizaccess_seb_access); 55 } 56 } 57 58 /** 59 * Helper method to get SEB download link for testing. 60 * 61 * @return string 62 */ 63 private function get_seb_download_link() { 64 return 'https://safeexambrowser.org/download_en.html'; 65 } 66 67 /** 68 * Helper method to get SEB launch link for testing. 69 * 70 * @return string 71 */ 72 private function get_seb_launch_link() { 73 return 'sebs://www.example.com/moodle/mod/quiz/accessrule/seb/config.php'; 74 } 75 76 /** 77 * Helper method to get SEB config download link for testing. 78 * 79 * @return string 80 */ 81 private function get_seb_config_download_link() { 82 return 'https://www.example.com/moodle/mod/quiz/accessrule/seb/config.php'; 83 } 84 85 /** 86 * Provider to return valid form field data when saving settings. 87 * 88 * @return array 89 */ 90 public function valid_form_data_provider() : array { 91 return [ 92 'valid seb_requiresafeexambrowser' => ['seb_requiresafeexambrowser', '0'], 93 'valid seb_linkquitseb0' => ['seb_linkquitseb', 'http://safeexambrowser.org/macosx'], 94 'valid seb_linkquitseb1' => ['seb_linkquitseb', 'safeexambrowser.org/macosx'], 95 'valid seb_linkquitseb2' => ['seb_linkquitseb', 'www.safeexambrowser.org/macosx'], 96 'valid seb_linkquitseb3' => ['seb_linkquitseb', 'any.type.of.url.looking.thing'], 97 'valid seb_linkquitseb4' => ['seb_linkquitseb', 'http://any.type.of.url.looking.thing'], 98 ]; 99 } 100 101 /** 102 * Provider to return invalid form field data when saving settings. 103 * 104 * @return array 105 */ 106 public function invalid_form_data_provider() : array { 107 return [ 108 'invalid seb_requiresafeexambrowser' => ['seb_requiresafeexambrowser', 'Uh oh!'], 109 'invalid seb_linkquitseb0' => ['seb_linkquitseb', '\0'], 110 'invalid seb_linkquitseb1' => ['seb_linkquitseb', 'invalid url'], 111 'invalid seb_linkquitseb2' => ['seb_linkquitseb', 'http]://safeexambrowser.org/macosx'], 112 'invalid seb_linkquitseb3' => ['seb_linkquitseb', '0'], 113 'invalid seb_linkquitseb4' => ['seb_linkquitseb', 'seb://any.type.of.url.looking.thing'], 114 ]; 115 } 116 117 /** 118 * Test no errors are found with valid data. 119 * 120 * @param string $setting 121 * @param string $data 122 * 123 * @dataProvider valid_form_data_provider 124 */ 125 public function test_validate_settings_with_valid_data(string $setting, string $data) { 126 $this->setAdminUser(); 127 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 128 129 $form = $this->createMock('mod_quiz_mod_form'); 130 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 131 132 // Validate settings with a dummy form. 133 $errors = quizaccess_seb::validate_settings_form_fields([], [ 134 'instance' => $this->quiz->id, 135 'coursemodule' => $this->quiz->cmid, 136 $setting => $data 137 ], [], $form); 138 $this->assertEmpty($errors); 139 } 140 141 /** 142 * Test errors are found with invalid data. 143 * 144 * @param string $setting 145 * @param string $data 146 * 147 * @dataProvider invalid_form_data_provider 148 */ 149 public function test_validate_settings_with_invalid_data(string $setting, string $data) { 150 $this->setAdminUser(); 151 152 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 153 $form = $this->createMock('mod_quiz_mod_form'); 154 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 155 156 // Validate settings with a dummy form and quiz instance. 157 $errors = quizaccess_seb::validate_settings_form_fields([], [ 158 'instance' => $this->quiz->id, 159 'coursemodule' => $this->quiz->cmid, 160 $setting => $data 161 ], [], $form); 162 $this->assertEquals([$setting => 'Data submitted is invalid'], $errors); 163 } 164 165 /** 166 * Test settings validation is not run if settings are locked. 167 */ 168 public function test_settings_validation_is_not_run_if_settings_are_locked() { 169 $user = $this->getDataGenerator()->create_user(); 170 $this->quiz = $this->create_test_quiz($this->course); 171 $this->attempt_quiz($this->quiz, $user); 172 173 $this->setAdminUser(); 174 175 $form = $this->createMock('mod_quiz_mod_form'); 176 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 177 178 // Validate settings with a dummy form and quiz instance. 179 $errors = quizaccess_seb::validate_settings_form_fields([], [ 180 'instance' => $this->quiz->id, 181 'coursemodule' => $this->quiz->cmid, 'seb_requiresafeexambrowser' => 'Uh oh!' 182 ], [], $form); 183 $this->assertEmpty($errors); 184 } 185 186 /** 187 * Test settings validation is not run if settings are conflicting. 188 */ 189 public function test_settings_validation_is_not_run_if_conflicting_permissions() { 190 $this->setAdminUser(); 191 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 192 193 $form = $this->createMock('mod_quiz_mod_form'); 194 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 195 196 $user = $this->getDataGenerator()->create_user(); 197 $roleid = $this->getDataGenerator()->create_role(); 198 $context = \context_module::instance($this->quiz->cmid); 199 assign_capability('quizaccess/seb:manage_seb_requiresafeexambrowser', CAP_ALLOW, $roleid, $context->id); 200 $this->getDataGenerator()->role_assign($roleid, $user->id, $context->id); 201 202 // By default The user won't have permissions to configure manually. 203 $this->setUser($user); 204 205 // Validate settings with a dummy form and quiz instance. 206 $errors = quizaccess_seb::validate_settings_form_fields([], [ 207 'instance' => $this->quiz->id, 208 'coursemodule' => $this->quiz->cmid, 209 'seb_requiresafeexambrowser' => 'Uh oh!' 210 ], [], $form); 211 $this->assertEmpty($errors); 212 } 213 214 /** 215 * Test bypassing validation if user don't have permissions to manage seb settings. 216 */ 217 public function test_validate_settings_is_not_run_if_a_user_do_not_have_permissions_to_manage_seb_settings() { 218 // Set the user who can't change seb settings. So validation should be bypassed. 219 $user = $this->getDataGenerator()->create_user(); 220 $this->setUser($user); 221 222 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 223 $form = $this->createMock('mod_quiz_mod_form'); 224 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 225 226 // Validate settings with a dummy form and quiz instance. 227 $errors = quizaccess_seb::validate_settings_form_fields([], [ 228 'instance' => $this->quiz->id, 229 'coursemodule' => $this->quiz->cmid, 'seb_requiresafeexambrowser' => 'Uh oh!' 230 ], [], $form); 231 $this->assertEmpty($errors); 232 } 233 234 /** 235 * Test settings are saved to DB. 236 */ 237 public function test_create_settings_with_existing_quiz() { 238 global $DB; 239 240 $this->setAdminUser(); 241 242 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO); 243 $this->assertFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $this->quiz->id])); 244 245 $this->quiz->seb_requiresafeexambrowser = settings_provider::USE_SEB_CONFIG_MANUALLY; 246 quizaccess_seb::save_settings($this->quiz); 247 $this->assertNotFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $this->quiz->id])); 248 } 249 250 /** 251 * Test settings are not saved to DB if settings are locked. 252 */ 253 public function test_settings_are_not_saved_if_settings_are_locked() { 254 global $DB; 255 256 $this->setAdminUser(); 257 $this->quiz = $this->create_test_quiz($this->course); 258 259 $user = $this->getDataGenerator()->create_user(); 260 $this->setUser($user); 261 $this->attempt_quiz($this->quiz, $user); 262 263 $this->setAdminUser(); 264 $this->quiz->seb_requiresafeexambrowser = settings_provider::USE_SEB_CONFIG_MANUALLY; 265 quizaccess_seb::save_settings($this->quiz); 266 $this->assertFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $this->quiz->id])); 267 } 268 269 /** 270 * Test settings are not saved to DB if conflicting permissions. 271 */ 272 public function test_settings_are_not_saved_if_conflicting_permissions() { 273 $this->setAdminUser(); 274 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 275 276 $user = $this->getDataGenerator()->create_user(); 277 $roleid = $this->getDataGenerator()->create_role(); 278 $context = \context_module::instance($this->quiz->cmid); 279 assign_capability('quizaccess/seb:manage_seb_requiresafeexambrowser', CAP_ALLOW, $roleid, $context->id); 280 $this->getDataGenerator()->role_assign($roleid, $user->id, $context->id); 281 282 // By default The user won't have permissions to configure manually. 283 $this->setUser($user); 284 285 $this->quiz->seb_requiresafeexambrowser = settings_provider::USE_SEB_NO; 286 quizaccess_seb::save_settings($this->quiz); 287 288 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 289 $this->assertEquals(settings_provider::USE_SEB_CONFIG_MANUALLY, $quizsettings->get('requiresafeexambrowser')); 290 } 291 292 /** 293 * Test exception thrown if cm could not be found while saving settings. 294 */ 295 public function test_save_settings_throw_an_exception_if_cm_not_found() { 296 global $DB; 297 298 $this->expectException(\dml_missing_record_exception::class); 299 $this->expectExceptionMessage('Can\'t find data record in database.'); 300 301 $this->setAdminUser(); 302 303 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 304 $DB->delete_records('quiz', ['id' => $this->quiz->id]); 305 $this->quiz->seb_requiresafeexambrowser = settings_provider::USE_SEB_NO; 306 quizaccess_seb::save_settings($this->quiz); 307 } 308 309 /** 310 * Test nothing happens when deleted is called without settings saved. 311 */ 312 public function test_delete_settings_without_existing_settings() { 313 global $DB; 314 $this->setAdminUser(); 315 316 $quiz = new \stdClass(); 317 $quiz->id = 1; 318 quizaccess_seb::delete_settings($quiz); 319 $this->assertFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $quiz->id])); 320 } 321 322 /** 323 * Test settings are deleted from DB. 324 */ 325 public function test_delete_settings_with_existing_settings() { 326 global $DB; 327 $this->setAdminUser(); 328 329 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 330 331 // Using a generator will create the quiz_settings record. 332 $this->assertNotFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $this->quiz->id])); 333 quizaccess_seb::delete_settings($this->quiz); 334 $this->assertFalse($DB->record_exists('quizaccess_seb_quizsettings', ['quizid' => $this->quiz->id])); 335 } 336 337 /** 338 * A helper method to check invalid config key. 339 */ 340 protected function check_invalid_config_key() { 341 // Create an event sink, trigger event and retrieve event. 342 $sink = $this->redirectEvents(); 343 344 // Check that correct error message is returned. 345 $errormsg = $this->make_rule()->prevent_access(); 346 $this->assertNotEmpty($errormsg); 347 $this->assertStringContainsString("The Safe Exam Browser keys could not be validated. " 348 . "Check that you're using Safe Exam Browser with the correct configuration file.", $errormsg); 349 $this->assertStringContainsString($this->get_seb_download_link(), $errormsg); 350 $this->assertStringContainsString($this->get_seb_launch_link(), $errormsg); 351 $this->assertStringContainsString($this->get_seb_config_download_link(), $errormsg); 352 353 $events = $sink->get_events(); 354 $this->assertEquals(1, count($events)); 355 $event = reset($events); 356 357 // Test that the event data is as expected. 358 $this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event); 359 $this->assertEquals('Invalid SEB config key', $event->other['reason']); 360 } 361 362 /** 363 * Test access prevented if config key is invalid. 364 */ 365 public function test_access_prevented_if_config_key_invalid() { 366 global $FULLME; 367 368 $this->setAdminUser(); 369 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 370 371 // Set up dummy request. 372 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 373 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = 'Broken config key'; 374 375 $user = $this->getDataGenerator()->create_user(); 376 $this->setUser($user); 377 378 $this->check_invalid_config_key(); 379 } 380 381 /** 382 * Test access prevented if config keys is invalid and using uploaded config. 383 */ 384 public function test_access_prevented_if_config_key_invalid_uploaded_config() { 385 global $FULLME; 386 387 $this->setAdminUser(); 388 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 389 390 // Set quiz setting to require seb and save BEK. 391 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 392 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); 393 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 394 $this->create_module_test_file($xml, $this->quiz->cmid); 395 $quizsettings->save(); 396 397 // Set up dummy request. 398 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 399 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = 'Broken config key'; 400 401 $user = $this->getDataGenerator()->create_user(); 402 $this->setUser($user); 403 404 $this->check_invalid_config_key(); 405 } 406 407 /** 408 * Test access prevented if config keys is invalid and using template. 409 */ 410 public function test_access_prevented_if_config_key_invalid_uploaded_template() { 411 global $FULLME; 412 413 $this->setAdminUser(); 414 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 415 416 // Set quiz setting to require seb and save BEK. 417 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 418 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE); 419 $quizsettings->set('templateid', $this->create_template()->get('id')); 420 $quizsettings->save(); 421 422 // Set up dummy request. 423 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 424 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = 'Broken config key'; 425 426 $user = $this->getDataGenerator()->create_user(); 427 $this->setUser($user); 428 429 $this->check_invalid_config_key(); 430 } 431 432 /** 433 * Test access not prevented if config key matches header. 434 */ 435 public function test_access_allowed_if_config_key_valid() { 436 global $FULLME; 437 438 $this->setAdminUser(); 439 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 440 441 $user = $this->getDataGenerator()->create_user(); 442 $this->setUser($user); 443 444 // Set quiz setting to require seb. 445 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 446 447 // Set up dummy request. 448 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 449 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 450 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 451 452 // Check that correct error message is returned. 453 $this->assertFalse($this->make_rule()->prevent_access()); 454 } 455 456 /** 457 * Test access not prevented if config key matches header. 458 */ 459 public function test_access_allowed_if_config_key_valid_uploaded_config() { 460 global $FULLME; 461 462 $this->setAdminUser(); 463 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 464 465 // Set quiz setting to require seb and save BEK. 466 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 467 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE); 468 $quizsettings->set('templateid', $this->create_template()->get('id')); 469 $quizsettings->save(); 470 471 $user = $this->getDataGenerator()->create_user(); 472 $this->setUser($user); 473 474 // Set up dummy request. 475 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 476 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 477 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 478 479 // Check that correct error message is returned. 480 $this->assertFalse($this->make_rule()->prevent_access()); 481 } 482 483 /** 484 * Test access not prevented if config key matches header. 485 */ 486 public function test_access_allowed_if_config_key_valid_template() { 487 global $FULLME; 488 489 $this->setAdminUser(); 490 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 491 492 // Set quiz setting to require seb and save BEK. 493 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 494 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); 495 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 496 $this->create_module_test_file($xml, $this->quiz->cmid); 497 $quizsettings->save(); 498 499 $user = $this->getDataGenerator()->create_user(); 500 $this->setUser($user); 501 502 // Set up dummy request. 503 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 504 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 505 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 506 507 // Check that correct error message is returned. 508 $this->assertFalse($this->make_rule()->prevent_access()); 509 } 510 511 /** 512 * Test access not prevented if browser exam keys match headers. 513 */ 514 public function test_access_allowed_if_browser_exam_keys_valid() { 515 global $FULLME; 516 517 $this->setAdminUser(); 518 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 519 520 $user = $this->getDataGenerator()->create_user(); 521 $this->setUser($user); 522 523 // Set quiz setting to require seb and save BEK. 524 $browserexamkey = hash('sha256', 'testkey'); 525 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 526 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key. 527 $quizsettings->set('allowedbrowserexamkeys', $browserexamkey); 528 $quizsettings->save(); 529 530 // Set up dummy request. 531 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 532 $expectedhash = hash('sha256', $FULLME . $browserexamkey); 533 $_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = $expectedhash; 534 $_SERVER['HTTP_USER_AGENT'] = 'SEB'; 535 536 // Check that correct error message is returned. 537 $this->assertFalse($this->make_rule()->prevent_access()); 538 } 539 540 /** 541 * Test access not prevented if browser exam keys match headers. 542 */ 543 public function test_access_allowed_if_browser_exam_keys_valid_use_uploaded_file() { 544 global $FULLME; 545 546 $this->setAdminUser(); 547 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 548 549 // Set quiz setting to require seb and save BEK. 550 $browserexamkey = hash('sha256', 'testkey'); 551 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 552 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); 553 $quizsettings->set('allowedbrowserexamkeys', $browserexamkey); 554 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 555 $this->create_module_test_file($xml, $this->quiz->cmid); 556 $quizsettings->save(); 557 558 // Set up dummy request. 559 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 560 $expectedbrowserkey = hash('sha256', $FULLME . $browserexamkey); 561 $_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = $expectedbrowserkey; 562 $expectedconfigkey = hash('sha256', $FULLME . $quizsettings->get_config_key()); 563 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedconfigkey; 564 565 $user = $this->getDataGenerator()->create_user(); 566 $this->setUser($user); 567 568 // Check that correct error message is returned. 569 $this->assertFalse($this->make_rule()->prevent_access()); 570 } 571 572 public function test_access_allowed_if_access_state_stored_in_session() { 573 global $SESSION; 574 575 $this->setAdminUser(); 576 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG); 577 578 $user = $this->getDataGenerator()->create_user(); 579 $this->setUser($user); 580 581 // Check that access is prevented. 582 $this->check_invalid_basic_header(); 583 584 $SESSION->quizaccess_seb_access = [$this->quiz->cmid => true]; 585 586 // Check access is now not prevented. 587 $this->assertFalse($this->make_rule()->prevent_access()); 588 } 589 590 /** 591 * A helper method to check invalid browser key. 592 * 593 * @param bool $downloadseblink Make sure download SEB link is present. 594 * @param bool $launchlink Make sure launch SEB link is present. 595 * @param bool $downloadconfiglink Make download config link is present. 596 */ 597 protected function check_invalid_browser_exam_key($downloadseblink = true, $launchlink = true, $downloadconfiglink = true) { 598 // Create an event sink, trigger event and retrieve event. 599 $sink = $this->redirectEvents(); 600 601 // Check that correct error message is returned. 602 $errormsg = $this->make_rule()->prevent_access(); 603 $this->assertNotEmpty($errormsg); 604 $this->assertStringContainsString("The Safe Exam Browser keys could not be validated. " 605 . "Check that you're using Safe Exam Browser with the correct configuration file.", $errormsg); 606 607 if ($downloadseblink) { 608 $this->assertStringContainsString($this->get_seb_download_link(), $errormsg); 609 } else { 610 $this->assertStringNotContainsString($this->get_seb_download_link(), $errormsg); 611 } 612 613 if ($launchlink) { 614 $this->assertStringContainsString($this->get_seb_launch_link(), $errormsg); 615 } else { 616 $this->assertStringNotContainsString($this->get_seb_launch_link(), $errormsg); 617 } 618 619 if ($downloadconfiglink) { 620 $this->assertStringContainsString($this->get_seb_config_download_link(), $errormsg); 621 } else { 622 $this->assertStringNotContainsString($this->get_seb_config_download_link(), $errormsg); 623 } 624 625 $events = $sink->get_events(); 626 $this->assertEquals(1, count($events)); 627 $event = reset($events); 628 629 // Test that the event data is as expected. 630 $this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event); 631 $this->assertEquals('Invalid SEB browser key', $event->other['reason']); 632 } 633 634 /** 635 * Test access prevented if browser exam keys do not match headers. 636 */ 637 public function test_access_prevented_if_browser_exam_keys_are_invalid() { 638 $this->setAdminUser(); 639 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 640 641 $user = $this->getDataGenerator()->create_user(); 642 $this->setUser($user); 643 644 // Set quiz setting to require seb and save BEK. 645 $browserexamkey = hash('sha256', 'testkey'); 646 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 647 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key. 648 $quizsettings->set('allowedbrowserexamkeys', $browserexamkey); 649 $quizsettings->save(); 650 651 // Set up dummy request. 652 $_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = 'Broken browser key'; 653 $_SERVER['HTTP_USER_AGENT'] = 'SEB'; 654 655 $this->check_invalid_browser_exam_key(true, false, false); 656 } 657 658 /** 659 * Test access prevented if browser exam keys do not match headers and using uploaded config. 660 */ 661 public function test_access_prevented_if_browser_exam_keys_are_invalid_use_uploaded_file() { 662 global $FULLME; 663 664 $this->setAdminUser(); 665 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 666 667 // Set quiz setting to require seb and save BEK. 668 $browserexamkey = hash('sha256', 'testkey'); 669 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 670 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); 671 $quizsettings->set('allowedbrowserexamkeys', $browserexamkey); 672 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 673 $this->create_module_test_file($xml, $this->quiz->cmid); 674 $quizsettings->save(); 675 676 // Set up dummy request. 677 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 678 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 679 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 680 681 // Set up broken browser key. 682 $_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = 'Broken browser key'; 683 684 $user = $this->getDataGenerator()->create_user(); 685 $this->setUser($user); 686 687 $this->check_invalid_browser_exam_key(); 688 } 689 690 /** 691 * Test access not prevented if browser exam keys do not match headers and using template. 692 */ 693 public function test_access_prevented_if_browser_exam_keys_are_invalid_use_template() { 694 global $FULLME; 695 696 $this->setAdminUser(); 697 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 698 699 // Set quiz setting to require seb and save BEK. 700 $browserexamkey = hash('sha256', 'testkey'); 701 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 702 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE); 703 $quizsettings->set('allowedbrowserexamkeys', $browserexamkey); 704 $quizsettings->set('templateid', $this->create_template()->get('id')); 705 $quizsettings->save(); 706 707 // Set up dummy request. 708 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 709 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 710 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 711 712 // Set up broken browser key. 713 $_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = 'Broken browser key'; 714 715 $user = $this->getDataGenerator()->create_user(); 716 $this->setUser($user); 717 718 // Check that correct error message is returned. 719 $this->assertFalse($this->make_rule()->prevent_access()); 720 } 721 722 /** 723 * Test access allowed if using client configuration and SEB user agent header is valid. 724 */ 725 public function test_access_allowed_if_using_client_config_basic_header_is_valid() { 726 $this->setAdminUser(); 727 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 728 729 $user = $this->getDataGenerator()->create_user(); 730 $this->setUser($user); 731 732 // Set quiz setting to require seb. 733 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 734 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key. 735 $quizsettings->save(); 736 737 // Set up basic dummy request. 738 $_SERVER['HTTP_USER_AGENT'] = 'SEB_TEST_SITE'; 739 740 // Check that correct error message is returned. 741 $this->assertFalse($this->make_rule()->prevent_access()); 742 } 743 744 /** 745 * Test access prevented if using client configuration and SEB user agent header is invalid. 746 */ 747 public function test_access_prevented_if_using_client_configuration_and_basic_head_is_invalid() { 748 $this->setAdminUser(); 749 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 750 751 $user = $this->getDataGenerator()->create_user(); 752 $this->setUser($user); 753 754 // Set quiz setting to require seb. 755 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 756 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); // Doesn't check config key. 757 $quizsettings->save(); 758 759 // Set up basic dummy request. 760 $_SERVER['HTTP_USER_AGENT'] = 'WRONG_TEST_SITE'; 761 762 // Create an event sink, trigger event and retrieve event. 763 $this->check_invalid_basic_header(); 764 } 765 766 /** 767 * A helper method to check invalid basic header. 768 */ 769 protected function check_invalid_basic_header() { 770 // Create an event sink, trigger event and retrieve event. 771 $sink = $this->redirectEvents(); 772 773 // Check that correct error message is returned. 774 $this->assertStringContainsString( 775 'This quiz has been configured to use the Safe Exam Browser with client configuration.', 776 $this->make_rule()->prevent_access() 777 ); 778 779 $events = $sink->get_events(); 780 $this->assertEquals(1, count($events)); 781 $event = reset($events); 782 783 // Test that the event data is as expected. 784 $this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event); 785 $this->assertEquals('No Safe Exam Browser is being used.', $event->other['reason']); 786 } 787 788 /** 789 * Test access allowed if using client configuration and SEB user agent header is invalid and use uploaded file. 790 */ 791 public function test_access_allowed_if_using_client_configuration_and_basic_head_is_invalid_use_uploaded_config() { 792 global $FULLME; 793 794 $this->setAdminUser(); 795 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 796 797 // Set quiz setting to require seb. 798 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 799 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); // Doesn't check basic header. 800 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 801 $this->create_module_test_file($xml, $this->quiz->cmid); 802 $quizsettings->save(); 803 804 // Set up dummy request. 805 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 806 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 807 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 808 $_SERVER['HTTP_USER_AGENT'] = 'WRONG_TEST_SITE'; 809 810 $user = $this->getDataGenerator()->create_user(); 811 $this->setUser($user); 812 813 // Check that correct error message is returned. 814 $this->assertFalse($this->make_rule()->prevent_access()); 815 } 816 817 /** 818 * Test access allowed if using client configuration and SEB user agent header is invalid and use template. 819 */ 820 public function test_access_allowed_if_using_client_configuration_and_basic_head_is_invalid_use_template() { 821 global $FULLME; 822 823 $this->setAdminUser(); 824 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 825 826 // Set quiz setting to require seb. 827 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 828 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE); 829 $quizsettings->set('templateid', $this->create_template()->get('id')); 830 $quizsettings->save(); 831 832 // Set up dummy request. 833 $FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4'; 834 $expectedhash = hash('sha256', $FULLME . $quizsettings->get_config_key()); 835 $_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash; 836 $_SERVER['HTTP_USER_AGENT'] = 'WRONG_TEST_SITE'; 837 838 $user = $this->getDataGenerator()->create_user(); 839 $this->setUser($user); 840 841 // Check that correct error message is returned. 842 $this->assertFalse($this->make_rule()->prevent_access()); 843 } 844 845 /** 846 * Test access not prevented if SEB not required. 847 */ 848 public function test_access_allowed_if_seb_not_required() { 849 $this->setAdminUser(); 850 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 851 852 $user = $this->getDataGenerator()->create_user(); 853 $this->setUser($user); 854 855 // Set quiz setting to not require seb. 856 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 857 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO); 858 $quizsettings->save(); 859 860 // The rule will not exist as the settings are not configured for SEB usage. 861 $this->assertNull($this->make_rule()); 862 } 863 864 /** 865 * Test access not prevented if USER has bypass capability. 866 */ 867 public function test_access_allowed_if_user_has_bypass_capability() { 868 $this->setAdminUser(); 869 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 870 871 $user = $this->getDataGenerator()->create_user(); 872 $this->setUser($user); 873 874 // Set the bypass SEB check capability to $USER. 875 $this->assign_user_capability('quizaccess/seb:bypassseb', \context_module::instance($this->quiz->cmid)->id); 876 877 // Check that correct error message is returned. 878 $this->assertFalse($this->make_rule()->prevent_access()); 879 } 880 881 /** 882 * Test that quiz form cannot be saved if using template, but not actually pick one. 883 */ 884 public function test_mod_quiz_form_cannot_be_saved_using_template_and_template_is_not_set() { 885 $this->setAdminUser(); 886 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 887 888 $form = $this->createMock('mod_quiz_mod_form'); 889 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 890 891 // Validate settings with a dummy form. 892 $errors = quizaccess_seb::validate_settings_form_fields([], [ 893 'instance' => $this->quiz->id, 894 'coursemodule' => $this->quiz->cmid, 895 'seb_requiresafeexambrowser' => settings_provider::USE_SEB_TEMPLATE 896 ], [], $form); 897 898 $this->assertContains(get_string('invalidtemplate', 'quizaccess_seb'), $errors); 899 } 900 901 /** 902 * Test that quiz form cannot be saved if uploaded invalid file. 903 */ 904 public function test_mod_quiz_form_cannot_be_saved_using_uploaded_file_and_file_is_not_valid() { 905 $this->setAdminUser(); 906 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 907 908 $form = $this->createMock('mod_quiz_mod_form'); 909 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 910 911 // Validate settings with a dummy form. 912 $errors = quizaccess_seb::validate_settings_form_fields([], [ 913 'instance' => $this->quiz->id, 914 'coursemodule' => $this->quiz->cmid, 915 'seb_requiresafeexambrowser' => settings_provider::USE_SEB_UPLOAD_CONFIG, 916 'filemanager_sebconfigfile' => 0, 917 ], [], $form); 918 919 $this->assertContainsEquals(get_string('filenotpresent', 'quizaccess_seb'), $errors); 920 } 921 922 /** 923 * Test that quiz form cannot be saved if the global settings are set to require a password and no password is set. 924 */ 925 public function test_mod_quiz_form_cannot_be_saved_if_global_settings_force_quiz_password_and_none_is_set() { 926 $this->setAdminUser(); 927 // Set global settings to require quiz password but set password to be empty. 928 set_config('quizpasswordrequired', '1', 'quizaccess_seb'); 929 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 930 931 $form = $this->createMock('mod_quiz_mod_form'); 932 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 933 934 // Validate settings with a dummy form. 935 $errors = quizaccess_seb::validate_settings_form_fields([], [ 936 'instance' => $this->quiz->id, 937 'coursemodule' => $this->quiz->cmid, 938 'seb_requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY, 939 ], [], $form); 940 941 $this->assertContains(get_string('passwordnotset', 'quizaccess_seb'), $errors); 942 } 943 944 /** 945 * Test that access to quiz is allowed if global setting is set to restrict quiz if no quiz password is set, and global quiz 946 * password is set. 947 */ 948 public function test_mod_quiz_form_can_be_saved_if_global_settings_force_quiz_password_and_is_set() { 949 $this->setAdminUser(); 950 // Set global settings to require quiz password but set password to be empty. 951 set_config('quizpasswordrequired', '1', 'quizaccess_seb'); 952 953 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 954 955 $form = $this->createMock('mod_quiz_mod_form'); 956 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 957 958 // Validate settings with a dummy form. 959 $errors = quizaccess_seb::validate_settings_form_fields([], [ 960 'instance' => $this->quiz->id, 961 'coursemodule' => $this->quiz->cmid, 962 'quizpassword' => 'set', 963 'seb_requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY, 964 ], [], $form); 965 $this->assertNotContains(get_string('passwordnotset', 'quizaccess_seb'), $errors); 966 } 967 968 /** 969 * Test that quiz form can be saved if the global settings are set to require a password and no seb usage selected. 970 */ 971 public function test_mod_quiz_form_can_be_saved_if_global_settings_force_quiz_password_and_none_no_seb() { 972 $this->setAdminUser(); 973 // Set global settings to require quiz password but set password to be empty. 974 set_config('quizpasswordrequired', '1', 'quizaccess_seb'); 975 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO); 976 977 $form = $this->createMock('mod_quiz_mod_form'); 978 $form->method('get_context')->willReturn(\context_module::instance($this->quiz->cmid)); 979 980 // Validate settings with a dummy form. 981 $errors = quizaccess_seb::validate_settings_form_fields([], [ 982 'instance' => $this->quiz->id, 983 'coursemodule' => $this->quiz->cmid, 984 'seb_requiresafeexambrowser' => settings_provider::USE_SEB_NO, 985 ], [], $form); 986 987 $this->assertNotContains(get_string('passwordnotset', 'quizaccess_seb'), $errors); 988 } 989 990 /** 991 * Test get_download_seb_button, checks for empty config setting quizaccess_seb/downloadlink. 992 */ 993 public function test_get_download_seb_button() { 994 $this->setAdminUser(); 995 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 996 997 $user = $this->getDataGenerator()->create_user(); 998 $this->setUser($user); 999 1000 $reflection = new \ReflectionClass('quizaccess_seb'); 1001 $method = $reflection->getMethod('get_download_seb_button'); 1002 $method->setAccessible(true); 1003 1004 // The current default contents. 1005 $this->assertStringContainsString($this->get_seb_download_link(), $method->invoke($this->make_rule())); 1006 1007 set_config('downloadlink', '', 'quizaccess_seb'); 1008 1009 // Will not return any button if the URL is empty. 1010 $this->assertSame('', $method->invoke($this->make_rule())); 1011 } 1012 1013 /** 1014 * Test get_download_seb_button shows download SEB link when required, 1015 */ 1016 public function test_get_get_action_buttons_shows_download_seb_link() { 1017 $this->setAdminUser(); 1018 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 1019 1020 $user = $this->getDataGenerator()->create_user(); 1021 $this->setUser($user); 1022 1023 $reflection = new \ReflectionClass('quizaccess_seb'); 1024 $method = $reflection->getMethod('get_action_buttons'); 1025 $method->setAccessible(true); 1026 1027 $this->assertStringContainsString($this->get_seb_download_link(), $method->invoke($this->make_rule())); 1028 1029 $this->quiz->seb_showsebdownloadlink = 0; 1030 $this->assertStringNotContainsString($this->get_seb_download_link(), $method->invoke($this->make_rule())); 1031 } 1032 1033 /** 1034 * Test get_download_seb_button shows SEB config related links when required. 1035 */ 1036 public function test_get_get_action_buttons_shows_launch_and_download_config_links() { 1037 $this->setAdminUser(); 1038 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 1039 1040 $user = $this->getDataGenerator()->create_user(); 1041 $this->setUser($user); 1042 1043 $reflection = new \ReflectionClass('quizaccess_seb'); 1044 $method = $reflection->getMethod('get_action_buttons'); 1045 $method->setAccessible(true); 1046 1047 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 1048 1049 // Should see link when using manually. 1050 $this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1051 $this->assertStringContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1052 1053 // Should see links when using template. 1054 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE); 1055 $quizsettings->set('templateid', $this->create_template()->get('id')); 1056 $quizsettings->save(); 1057 $this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1058 $this->assertStringContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1059 1060 // Should see links when using uploaded config. 1061 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); 1062 $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb'); 1063 $this->create_module_test_file($xml, $this->quiz->cmid); 1064 $quizsettings->save(); 1065 $this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1066 $this->assertStringContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1067 1068 // Shouldn't see links if using client config. 1069 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); 1070 $quizsettings->save(); 1071 $this->assertStringNotContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1072 $this->assertStringNotContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1073 } 1074 1075 /** 1076 * Test get_download_seb_button shows SEB config related links as configured in "showseblinks". 1077 */ 1078 public function test_get_get_action_buttons_shows_launch_and_download_config_links_as_configured() { 1079 $this->setAdminUser(); 1080 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 1081 1082 $user = $this->getDataGenerator()->create_user(); 1083 $this->setUser($user); 1084 1085 $reflection = new \ReflectionClass('quizaccess_seb'); 1086 $method = $reflection->getMethod('get_action_buttons'); 1087 $method->setAccessible(true); 1088 1089 set_config('showseblinks', 'seb,http', 'quizaccess_seb'); 1090 $this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1091 $this->assertStringContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1092 1093 set_config('showseblinks', 'http', 'quizaccess_seb'); 1094 $this->assertStringNotContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1095 $this->assertStringContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1096 1097 set_config('showseblinks', 'seb', 'quizaccess_seb'); 1098 $this->assertStringContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1099 $this->assertStringNotContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1100 1101 set_config('showseblinks', '', 'quizaccess_seb'); 1102 $this->assertStringNotContainsString($this->get_seb_launch_link(), $method->invoke($this->make_rule())); 1103 $this->assertStringNotContainsString($this->get_seb_config_download_link(), $method->invoke($this->make_rule())); 1104 } 1105 1106 /** 1107 * Test get_quit_button. If attempt count is greater than 0 1108 */ 1109 public function test_get_quit_button() { 1110 $this->setAdminUser(); 1111 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG); 1112 $this->quiz->seb_linkquitseb = "http://test.quit.link"; 1113 1114 $user = $this->getDataGenerator()->create_user(); 1115 $this->attempt_quiz($this->quiz, $user); 1116 $this->setUser($user); 1117 1118 // Set-up the button to be called. 1119 $reflection = new \ReflectionClass('quizaccess_seb'); 1120 $method = $reflection->getMethod('get_quit_button'); 1121 $method->setAccessible(true); 1122 1123 $button = $method->invoke($this->make_rule()); 1124 $this->assertStringContainsString("http://test.quit.link", $button); 1125 } 1126 1127 /** 1128 * Test description, checks for a valid SEB session and attempt count . 1129 */ 1130 public function test_description() { 1131 $this->setAdminUser(); 1132 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG); 1133 1134 $this->quiz->seb_linkquitseb = "http://test.quit.link"; 1135 1136 // Set up basic dummy request. 1137 $_SERVER['HTTP_USER_AGENT'] = 'SEB_TEST_SITE'; 1138 1139 $user = $this->getDataGenerator()->create_user(); 1140 $this->attempt_quiz($this->quiz, $user); 1141 1142 $description = $this->make_rule()->description(); 1143 $this->assertCount(2, $description); 1144 $this->assertEquals($description[0], get_string('sebrequired', 'quizaccess_seb')); 1145 $this->assertEquals($description[1], ''); 1146 1147 // Set the user as display_quit_button() uses the global $USER. 1148 $this->setUser($user); 1149 $description = $this->make_rule()->description(); 1150 $this->assertCount(2, $description); 1151 $this->assertEquals($description[0], get_string('sebrequired', 'quizaccess_seb')); 1152 1153 // The button is contained in the description when a quiz attempt is finished. 1154 $this->assertStringContainsString("http://test.quit.link", $description[1]); 1155 } 1156 1157 /** 1158 * Test description displays download SEB config button when required. 1159 */ 1160 public function test_description_shows_download_config_link_when_required() { 1161 $this->setAdminUser(); 1162 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 1163 1164 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 1165 1166 $user = $this->getDataGenerator()->create_user(); 1167 $roleid = $this->getDataGenerator()->create_role(); 1168 $context = \context_module::instance($this->quiz->cmid); 1169 assign_capability('quizaccess/seb:bypassseb', CAP_ALLOW, $roleid, $context->id); 1170 1171 $this->setUser($user); 1172 1173 // Can see just basic description with standard perms. 1174 $description = $this->make_rule()->description(); 1175 $this->assertCount(1, $description); 1176 $this->assertEquals($description[0], get_string('sebrequired', 'quizaccess_seb')); 1177 1178 // Can see download config link as have bypass SEB permissions. 1179 $this->getDataGenerator()->role_assign($roleid, $user->id, $context->id); 1180 $description = $this->make_rule()->description(); 1181 $this->assertCount(3, $description); 1182 $this->assertEquals($description[0], get_string('sebrequired', 'quizaccess_seb')); 1183 $this->assertStringContainsString($this->get_seb_config_download_link(), $description[1]); 1184 1185 // Can't see download config link as usage method doesn't have SEB config to download. 1186 $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG); 1187 $quizsettings->save(); 1188 $description = $this->make_rule()->description(); 1189 $this->assertCount(2, $description); 1190 $this->assertEquals($description[0], get_string('sebrequired', 'quizaccess_seb')); 1191 } 1192 1193 /** 1194 * Test block display before a quiz started. 1195 */ 1196 public function test_blocks_display_before_attempt_started() { 1197 global $PAGE; 1198 1199 $this->setAdminUser(); 1200 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG); 1201 1202 $user = $this->getDataGenerator()->create_user(); 1203 $this->setUser($user); 1204 1205 // We will check if we show only fake blocks. Which means no other blocks on a page. 1206 $reflection = new \ReflectionClass('block_manager'); 1207 $property = $reflection->getProperty('fakeblocksonly'); 1208 $property->setAccessible(true); 1209 1210 $this->assertFalse($property->getValue($PAGE->blocks)); 1211 1212 // Don't display blocks before start. 1213 set_config('displayblocksbeforestart', 0, 'quizaccess_seb'); 1214 $this->set_up_quiz_view_page(); 1215 $this->make_rule()->prevent_access(); 1216 $this->assertEquals('secure', $PAGE->pagelayout); 1217 $this->assertTrue($property->getValue($PAGE->blocks)); 1218 1219 // Display blocks before start. 1220 set_config('displayblocksbeforestart', 1, 'quizaccess_seb'); 1221 $this->set_up_quiz_view_page(); 1222 $this->make_rule()->prevent_access(); 1223 $this->assertEquals('secure', $PAGE->pagelayout); 1224 $this->assertFalse($property->getValue($PAGE->blocks)); 1225 } 1226 1227 /** 1228 * Test block display after a quiz completed. 1229 */ 1230 public function test_blocks_display_after_attempt_finished() { 1231 global $PAGE; 1232 1233 $this->setAdminUser(); 1234 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG); 1235 1236 // Finish the quiz. 1237 $user = $this->getDataGenerator()->create_user(); 1238 $this->attempt_quiz($this->quiz, $user); 1239 $this->setUser($user); 1240 1241 // We will check if we show only fake blocks. Which means no other blocks on a page. 1242 $reflection = new \ReflectionClass('block_manager'); 1243 $property = $reflection->getProperty('fakeblocksonly'); 1244 $property->setAccessible(true); 1245 1246 $this->assertFalse($property->getValue($PAGE->blocks)); 1247 1248 // Don't display blocks after finish. 1249 set_config('displayblockswhenfinished', 0, 'quizaccess_seb'); 1250 $this->set_up_quiz_view_page(); 1251 $this->make_rule()->prevent_access(); 1252 $this->assertEquals('secure', $PAGE->pagelayout); 1253 $this->assertTrue($property->getValue($PAGE->blocks)); 1254 1255 // Display blocks after finish. 1256 set_config('displayblockswhenfinished', 1, 'quizaccess_seb'); 1257 $this->set_up_quiz_view_page(); 1258 $this->make_rule()->prevent_access(); 1259 $this->assertEquals('secure', $PAGE->pagelayout); 1260 $this->assertFalse($property->getValue($PAGE->blocks)); 1261 } 1262 1263 /** 1264 * Test cleanup when quiz is completed. 1265 */ 1266 public function test_current_attempt_finished() { 1267 global $SESSION; 1268 $this->setAdminUser(); 1269 1270 $this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY); 1271 $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]); 1272 $quizsettings->save(); 1273 // Set access for Moodle session. 1274 $SESSION->quizaccess_seb_access = [$this->quiz->cmid => true]; 1275 $this->make_rule()->current_attempt_finished(); 1276 1277 $this->assertTrue(empty($SESSION->quizaccess_seb_access[$this->quiz->cmid])); 1278 } 1279 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body