Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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