Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

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