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 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace quizaccess_seb;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  require_once (__DIR__ . '/test_helper_trait.php');
  22  
  23  /**
  24   * PHPUnit tests for quiz_settings class.
  25   *
  26   * @package   quizaccess_seb
  27   * @author    Andrew Madden <andrewmadden@catalyst-au.net>
  28   * @copyright 2020 Catalyst IT
  29   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class quiz_settings_test extends \advanced_testcase {
  32      use \quizaccess_seb_test_helper_trait;
  33  
  34      /** @var context_module $context Test context. */
  35      protected $context;
  36  
  37      /** @var moodle_url $url Test quiz URL. */
  38      protected $url;
  39  
  40      /**
  41       * Called before every test.
  42       */
  43      public function setUp(): void {
  44          parent::setUp();
  45  
  46          $this->resetAfterTest();
  47  
  48          $this->setAdminUser();
  49          $this->course = $this->getDataGenerator()->create_course();
  50          $this->quiz = $this->getDataGenerator()->create_module('quiz', [
  51              'course' => $this->course->id,
  52              'seb_requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
  53          ]);
  54          $this->context = \context_module::instance($this->quiz->cmid);
  55          $this->url = new \moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
  56      }
  57  
  58      /**
  59       * Test that config is generated immediately prior to saving quiz settings.
  60       */
  61      public function test_config_is_created_from_quiz_settings() {
  62          // Test settings to populate the in the object.
  63          $settings = $this->get_test_settings();
  64          $settings->quizid = $this->quiz->id;
  65          $settings->cmid = $this->quiz->cmid;
  66  
  67          // Obtain the existing record that is created when using a generator.
  68          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
  69  
  70          // Update the settings with values from the test function.
  71          $quizsettings->from_record($settings);
  72          $quizsettings->save();
  73  
  74          $config = $quizsettings->get_config();
  75          $this->assertEquals(
  76              "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  77  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
  78  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
  79                  . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
  80                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
  81                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
  82                  . "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
  83                  . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
  84                  . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
  85                  . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
  86                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
  87                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
  88              $config);
  89      }
  90  
  91      /**
  92       * Test that config string gets updated from quiz settings.
  93       */
  94      public function test_config_is_updated_from_quiz_settings() {
  95          // Test settings to populate the in the object.
  96          $settings = $this->get_test_settings();
  97          $settings->quizid = $this->quiz->id;
  98          $settings->cmid = $this->quiz->cmid;
  99  
 100          // Obtain the existing record that is created when using a generator.
 101          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 102  
 103          // Update the settings with values from the test function.
 104          $quizsettings->from_record($settings);
 105          $quizsettings->save();
 106  
 107          $config = $quizsettings->get_config();
 108          $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
 109  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
 110  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
 111              . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 112              . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
 113              . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
 114              . "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
 115              . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
 116              . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
 117              . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
 118              . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 119              . "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
 120  
 121          $quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
 122          $quizsettings->save();
 123          $config = $quizsettings->get_config();
 124          $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
 125  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
 126  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
 127              . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 128              . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
 129              . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
 130              . "<key>URLFilterEnableContentFilter</key><true/><key>hashedQuitPassword</key>"
 131              . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
 132              . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
 133              . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
 134              . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 135              . "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
 136      }
 137  
 138      /**
 139       * Test that config key is generated immediately prior to saving quiz settings.
 140       */
 141      public function test_config_key_is_created_from_quiz_settings() {
 142          $settings = $this->get_test_settings();
 143  
 144          $quizsettings = new quiz_settings(0, $settings);
 145          $configkey = $quizsettings->get_config_key();
 146          $this->assertEquals("b35510bd754f9d106ff88b9d2dc1bb297cddc9fc7b4bdde2dbda4e7d9e4b50d8",
 147              $configkey
 148          );
 149      }
 150  
 151      /**
 152       * Test that config key is generated immediately prior to saving quiz settings.
 153       */
 154      public function test_config_key_is_updated_from_quiz_settings() {
 155          $settings = $this->get_test_settings();
 156  
 157          $quizsettings = new quiz_settings(0, $settings);
 158          $configkey = $quizsettings->get_config_key();
 159          $this->assertEquals("b35510bd754f9d106ff88b9d2dc1bb297cddc9fc7b4bdde2dbda4e7d9e4b50d8",
 160                  $configkey);
 161  
 162          $quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
 163          $configkey = $quizsettings->get_config_key();
 164          $this->assertEquals("58010792504cccc18f7b0e5c9680fe60b567e8c1b5fb9798654cc9bad9ddf30c",
 165              $configkey);
 166      }
 167  
 168      /**
 169       * Test that different URL filter expressions are turned into config XML.
 170       *
 171       * @param \stdClass $settings Quiz settings
 172       * @param string $expectedxml SEB Config XML.
 173       *
 174       * @dataProvider filter_rules_provider
 175       */
 176      public function test_filter_rules_added_to_config(\stdClass $settings, string $expectedxml) {
 177          $quizsettings = new quiz_settings(0, $settings);
 178          $config = $quizsettings->get_config();
 179          $this->assertEquals($expectedxml, $config);
 180      }
 181  
 182      /**
 183       * Test that browser keys are validated and retrieved as an array instead of string.
 184       */
 185      public function test_browser_exam_keys_are_retrieved_as_array() {
 186          $quizsettings = new quiz_settings();
 187          $quizsettings->set('allowedbrowserexamkeys', "one two,three\nfour");
 188          $retrievedkeys = $quizsettings->get('allowedbrowserexamkeys');
 189          $this->assertEquals(['one', 'two', 'three', 'four'], $retrievedkeys);
 190      }
 191  
 192      /**
 193       * Test validation of Browser Exam Keys.
 194       *
 195       * @param string $bek Browser Exam Key.
 196       * @param string $expectederrorstring Expected error.
 197       *
 198       * @dataProvider bad_browser_exam_key_provider
 199       */
 200      public function test_browser_exam_keys_validation_errors($bek, $expectederrorstring) {
 201          $quizsettings = new quiz_settings();
 202          $quizsettings->set('allowedbrowserexamkeys', $bek);
 203          $quizsettings->validate();
 204          $errors = $quizsettings->get_errors();
 205          $this->assertContainsEquals($expectederrorstring, $errors);
 206      }
 207  
 208      /**
 209       * Test that uploaded seb file gets converted to config string.
 210       */
 211      public function test_config_file_uploaded_converted_to_config() {
 212          $url = new \moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
 213          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 214                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 215                  . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 216                  . "<key>allowWlan</key><false/><key>startURL</key><string>$url</string>"
 217                  . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 218          $itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
 219          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 220          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 221          $quizsettings->save();
 222          $config = $quizsettings->get_config();
 223          $this->assertEquals($xml, $config);
 224      }
 225  
 226      /**
 227       * Test test_no_config_file_uploaded
 228       */
 229      public function test_no_config_file_uploaded() {
 230          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 231          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 232          $cmid = $quizsettings->get('cmid');
 233          $this->expectException(\moodle_exception::class);
 234          $this->expectExceptionMessage("No uploaded SEB config file could be found for quiz with cmid: {$cmid}");
 235          $quizsettings->get_config();
 236      }
 237  
 238      /**
 239       * A helper function to build a config file.
 240       *
 241       * @param mixed $allowuserquitseb Required allowQuit setting.
 242       * @param mixed $quitpassword Required hashedQuitPassword setting.
 243       *
 244       * @return string
 245       */
 246      protected function get_config_xml($allowuserquitseb = null, $quitpassword = null) {
 247          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 248              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 249              . "<plist version=\"1.0\"><dict><key>allowWlan</key><false/><key>startURL</key>"
 250              . "<string>https://safeexambrowser.org/start</string>"
 251              . "<key>sendBrowserExamKey</key><true/>";
 252  
 253          if (!is_null($allowuserquitseb)) {
 254              $allowuserquitseb = empty($allowuserquitseb) ? 'false' : 'true';
 255              $xml .= "<key>allowQuit</key><{$allowuserquitseb}/>";
 256          }
 257  
 258          if (!is_null($quitpassword)) {
 259              $xml .= "<key>hashedQuitPassword</key><string>{$quitpassword}</string>";
 260          }
 261  
 262          $xml .= "</dict></plist>\n";
 263  
 264          return $xml;
 265      }
 266  
 267      /**
 268       * Test using USE_SEB_TEMPLATE and have it override settings from the template when they are set.
 269       */
 270      public function test_using_seb_template_override_settings_when_they_set_in_template() {
 271          $xml = $this->get_config_xml(true, 'password');
 272          $template = $this->create_template($xml);
 273  
 274          $this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
 275          $this->assertStringContainsString("<key>allowQuit</key><true/>", $template->get('content'));
 276          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
 277  
 278          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 279          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 280          $quizsettings->set('templateid', $template->get('id'));
 281          $quizsettings->set('allowuserquitseb', 1);
 282          $quizsettings->save();
 283  
 284          $this->assertStringContainsString(
 285              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 286              $quizsettings->get_config()
 287          );
 288  
 289          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 290          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 291  
 292          $quizsettings->set('quitpassword', 'new password');
 293          $quizsettings->save();
 294          $hashedpassword = hash('SHA256', 'new password');
 295          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 296          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 297          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 298  
 299          $quizsettings->set('allowuserquitseb', 0);
 300          $quizsettings->set('quitpassword', '');
 301          $quizsettings->save();
 302          $this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
 303          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 304      }
 305  
 306      /**
 307       * Test using USE_SEB_TEMPLATE and have it override settings from the template when they are not set.
 308       */
 309      public function test_using_seb_template_override_settings_when_not_set_in_template() {
 310          $xml = $this->get_config_xml();
 311          $template = $this->create_template($xml);
 312  
 313          $this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
 314          $this->assertStringNotContainsString("<key>allowQuit</key><true/>", $template->get('content'));
 315          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
 316  
 317          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 318          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 319          $quizsettings->set('templateid', $template->get('id'));
 320          $quizsettings->set('allowuserquitseb', 1);
 321          $quizsettings->save();
 322  
 323          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 324          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 325  
 326          $quizsettings->set('quitpassword', 'new password');
 327          $quizsettings->save();
 328          $hashedpassword = hash('SHA256', 'new password');
 329          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 330          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 331  
 332          $quizsettings->set('allowuserquitseb', 0);
 333          $quizsettings->set('quitpassword', '');
 334          $quizsettings->save();
 335          $this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
 336          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 337      }
 338  
 339      /**
 340       * Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are set.
 341       */
 342      public function test_using_own_config_settings_are_not_overridden_if_set() {
 343          $xml = $this->get_config_xml(true, 'password');
 344          $this->create_module_test_file($xml, $this->quiz->cmid);
 345  
 346          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 347          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 348          $quizsettings->set('allowuserquitseb', 0);
 349          $quizsettings->set('quitpassword', '');
 350          $quizsettings->save();
 351  
 352          $this->assertStringContainsString(
 353              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 354              $quizsettings->get_config()
 355          );
 356  
 357          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 358          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 359  
 360          $quizsettings->set('quitpassword', 'new password');
 361          $quizsettings->save();
 362          $hashedpassword = hash('SHA256', 'new password');
 363  
 364          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 365          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 366          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 367  
 368          $quizsettings->set('allowuserquitseb', 0);
 369          $quizsettings->set('quitpassword', '');
 370          $quizsettings->save();
 371  
 372          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 373          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 374      }
 375  
 376      /**
 377       * Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are not set.
 378       */
 379      public function test_using_own_config_settings_are_not_overridden_if_not_set() {
 380          $xml = $this->get_config_xml();
 381          $this->create_module_test_file($xml, $this->quiz->cmid);
 382  
 383          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 384          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 385          $quizsettings->set('allowuserquitseb', 1);
 386          $quizsettings->set('quitpassword', '');
 387          $quizsettings->save();
 388  
 389          $this->assertStringContainsString(
 390              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 391              $quizsettings->get_config()
 392          );
 393  
 394          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 395          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 396  
 397          $quizsettings->set('quitpassword', 'new password');
 398          $quizsettings->save();
 399  
 400          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 401          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 402  
 403          $quizsettings->set('allowuserquitseb', 0);
 404          $quizsettings->set('quitpassword', '');
 405          $quizsettings->save();
 406  
 407          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 408          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 409      }
 410  
 411      /**
 412       * Test using USE_SEB_TEMPLATE populates the linkquitseb setting if a quitURL is found.
 413       */
 414      public function test_template_has_quit_url_set() {
 415          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 416              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 417              . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 418              . "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
 419              . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 420  
 421          $template = $this->create_template($xml);
 422  
 423          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 424          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 425          $quizsettings->set('templateid', $template->get('id'));
 426  
 427          $this->assertEmpty($quizsettings->get('linkquitseb'));
 428          $quizsettings->save();
 429  
 430          $this->assertNotEmpty($quizsettings->get('linkquitseb'));
 431          $this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
 432      }
 433  
 434      /**
 435       * Test using USE_SEB_UPLOAD_CONFIG populates the linkquitseb setting if a quitURL is found.
 436       */
 437      public function test_config_file_uploaded_has_quit_url_set() {
 438          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 439              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 440              . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 441              . "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
 442              . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 443  
 444          $itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
 445          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 446          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 447  
 448          $this->assertEmpty($quizsettings->get('linkquitseb'));
 449          $quizsettings->save();
 450  
 451          $this->assertNotEmpty($quizsettings->get('linkquitseb'));
 452          $this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
 453      }
 454  
 455      /**
 456       * Test template id set correctly.
 457       */
 458      public function test_templateid_set_correctly_when_save_settings() {
 459          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 460          $this->assertEquals(0, $quizsettings->get('templateid'));
 461  
 462          $template = $this->create_template();
 463          $templateid = $template->get('id');
 464  
 465          // Initially set to USE_SEB_TEMPLATE with a template id.
 466          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 467          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 468          $this->assertEquals($templateid, $quizsettings->get('templateid'));
 469  
 470          // Case for USE_SEB_NO, ensure template id reverts to 0.
 471          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_NO);
 472          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 473          $this->assertEquals(0, $quizsettings->get('templateid'));
 474  
 475          // Reverting back to USE_SEB_TEMPLATE.
 476          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 477  
 478          // Case for USE_SEB_CONFIG_MANUALLY, ensure template id reverts to 0.
 479          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CONFIG_MANUALLY);
 480          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 481          $this->assertEquals(0, $quizsettings->get('templateid'));
 482  
 483          // Reverting back to USE_SEB_TEMPLATE.
 484          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 485  
 486          // Case for USE_SEB_CLIENT_CONFIG, ensure template id reverts to 0.
 487          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CLIENT_CONFIG);
 488          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 489          $this->assertEquals(0, $quizsettings->get('templateid'));
 490  
 491          // Reverting back to USE_SEB_TEMPLATE.
 492          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 493  
 494          // Case for USE_SEB_UPLOAD_CONFIG, ensure template id reverts to 0.
 495          $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
 496          $this->create_module_test_file($xml, $this->quiz->cmid);
 497          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_UPLOAD_CONFIG);
 498          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 499          $this->assertEquals(0, $quizsettings->get('templateid'));
 500  
 501          // Case for USE_SEB_TEMPLATE, ensure template id is correct.
 502          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 503          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 504          $this->assertEquals($templateid, $quizsettings->get('templateid'));
 505      }
 506  
 507      /**
 508       * Helper function in tests to set USE_SEB_TEMPLATE and a template id on the quiz settings.
 509       *
 510       * @param quiz_settings $quizsettings Given quiz settings instance.
 511       * @param int $savetype Type of SEB usage.
 512       * @param int $templateid Template ID.
 513       */
 514      public function save_settings_with_optional_template($quizsettings, $savetype, $templateid = 0) {
 515          $quizsettings->set('requiresafeexambrowser', $savetype);
 516          if (!empty($templateid)) {
 517              $quizsettings->set('templateid', $templateid);
 518          }
 519          $quizsettings->save();
 520      }
 521  
 522      /**
 523       * Bad browser exam key data provider.
 524       *
 525       * @return array
 526       */
 527      public function bad_browser_exam_key_provider() : array {
 528          return [
 529              'Short string' => ['fdsf434r',
 530                      'A key should be a 64-character hex string.'],
 531              'Non hex string' => ['aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf678!',
 532                      'A key should be a 64-character hex string.'],
 533              'Non unique' => ["aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789"
 534                      . "\naadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789", 'The keys must all be different.'],
 535          ];
 536      }
 537  
 538      /**
 539       * Provide settings for different filter rules.
 540       *
 541       * @return array Test data.
 542       */
 543      public function filter_rules_provider() : array {
 544          return [
 545              'enabled simple expessions' => [
 546                  (object) [
 547                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 548                      'quizid' => 1,
 549                      'cmid' => 1,
 550                      'expressionsallowed' => "test.com\r\nsecond.hello",
 551                      'regexallowed' => '',
 552                      'expressionsblocked' => '',
 553                      'regexblocked' => '',
 554                  ],
 555                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 556                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 557                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 558                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 559                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 560                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 561                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 562                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 563                  . "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
 564                  . "<key>expression</key><string>test.com</string>"
 565                  . "<key>regex</key><false/></dict><dict><key>action</key><integer>1</integer>"
 566                  . "<key>active</key><true/><key>expression</key>"
 567                  . "<string>second.hello</string><key>regex</key><false/></dict></array>"
 568                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 569                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 570                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 571              ],
 572              'blocked simple expessions' => [
 573                  (object) [
 574                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 575                      'quizid' => 1,
 576                      'cmid' => 1,
 577                      'expressionsallowed' => '',
 578                      'regexallowed' => '',
 579                      'expressionsblocked' => "test.com\r\nsecond.hello",
 580                      'regexblocked' => '',
 581                  ],
 582                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 583                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 584                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 585                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 586                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 587                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 588                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 589                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 590                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 591                  . "<key>expression</key><string>test.com</string>"
 592                  . "<key>regex</key><false/></dict><dict><key>action</key><integer>0</integer>"
 593                  . "<key>active</key><true/><key>expression</key>"
 594                  . "<string>second.hello</string><key>regex</key><false/></dict></array>"
 595                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 596                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 597                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 598              ],
 599              'enabled regex expessions' => [
 600                  (object) [
 601                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 602                      'quizid' => 1,
 603                      'cmid' => 1,
 604                      'expressionsallowed' => '',
 605                      'regexallowed' => "test.com\r\nsecond.hello",
 606                      'expressionsblocked' => '',
 607                      'regexblocked' => '',
 608                  ],
 609                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 610                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 611                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 612                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 613                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 614                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 615                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 616                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 617                  . "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
 618                  . "<key>expression</key><string>test.com</string>"
 619                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>1</integer>"
 620                  . "<key>active</key><true/><key>expression</key>"
 621                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 622                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 623                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 624                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 625              ],
 626              'blocked regex expessions' => [
 627                  (object) [
 628                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 629                      'quizid' => 1,
 630                      'cmid' => 1,
 631                      'expressionsallowed' => '',
 632                      'regexallowed' => '',
 633                      'expressionsblocked' => '',
 634                      'regexblocked' => "test.com\r\nsecond.hello",
 635                  ],
 636                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 637                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 638                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 639                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 640                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 641                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 642                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 643                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 644                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 645                  . "<key>expression</key><string>test.com</string>"
 646                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
 647                  . "<key>active</key><true/><key>expression</key>"
 648                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 649                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 650                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 651                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 652              ],
 653              'multiple simple expessions' => [
 654                  (object) [
 655                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 656                      'quizid' => 1,
 657                      'cmid' => 1,
 658                      'expressionsallowed' => "*",
 659                      'regexallowed' => '',
 660                      'expressionsblocked' => '',
 661                      'regexblocked' => "test.com\r\nsecond.hello",
 662                  ],
 663                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 664                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 665                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 666                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 667                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 668                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 669                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 670                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array><dict><key>action</key>"
 671                  . "<integer>1</integer><key>active</key><true/><key>expression</key><string>*</string>"
 672                  . "<key>regex</key><false/></dict>"
 673                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 674                  . "<key>expression</key><string>test.com</string>"
 675                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
 676                  . "<key>active</key><true/><key>expression</key>"
 677                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 678                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 679                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 680                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 681              ],
 682          ];
 683      }
 684  
 685      /**
 686       * Test that config and config key are null when expected.
 687       */
 688      public function test_generates_config_values_as_null_when_expected() {
 689          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 690          $this->assertNotNull($quizsettings->get_config());
 691          $this->assertNotNull($quizsettings->get_config_key());
 692  
 693          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO);
 694          $quizsettings->save();
 695          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 696          $this->assertNull($quizsettings->get_config());
 697          $this->assertNull($quizsettings->get_config());
 698  
 699          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 700          $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
 701          $this->create_module_test_file($xml, $this->quiz->cmid);
 702          $quizsettings->save();
 703          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 704          $this->assertNotNull($quizsettings->get_config());
 705          $this->assertNotNull($quizsettings->get_config_key());
 706  
 707          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG);
 708          $quizsettings->save();
 709          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 710          $this->assertNull($quizsettings->get_config());
 711          $this->assertNull($quizsettings->get_config_key());
 712  
 713          $template = $this->create_template();
 714          $templateid = $template->get('id');
 715          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 716          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 717          $this->assertNotNull($quizsettings->get_config());
 718          $this->assertNotNull($quizsettings->get_config_key());
 719      }
 720  
 721      /**
 722       * Test that quizsettings cache exists after creation.
 723       */
 724      public function test_quizsettings_cache_exists_after_creation() {
 725          $expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 726          $this->assertEquals($expected->to_record(), \cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 727      }
 728  
 729      /**
 730       * Test that quizsettings cache gets deleted after deletion.
 731       */
 732      public function test_quizsettings_cache_purged_after_deletion() {
 733          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 734  
 735          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 736          $quizsettings->delete();
 737  
 738          $this->assertFalse(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 739      }
 740  
 741      /**
 742       * Test that we can get quiz_settings by quiz id.
 743       */
 744      public function test_get_quiz_settings_by_quiz_id() {
 745          $expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 746  
 747          $this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 748  
 749          // Check that data is getting from cache.
 750          $expected->set('showsebtaskbar', 0);
 751          $this->assertNotEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 752  
 753          // Now save and check that cached as been updated.
 754          $expected->save();
 755          $this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 756  
 757          // Returns false for non existing quiz.
 758          $this->assertFalse(quiz_settings::get_by_quiz_id(7777777));
 759      }
 760  
 761      /**
 762       * Test that SEB config cache exists after creation of the quiz.
 763       */
 764      public function test_config_cache_exists_after_creation() {
 765          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 766      }
 767  
 768      /**
 769       * Test that SEB config cache gets deleted after deletion.
 770       */
 771      public function test_config_cache_purged_after_deletion() {
 772          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 773  
 774          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 775          $quizsettings->delete();
 776  
 777          $this->assertFalse(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 778      }
 779  
 780      /**
 781       * Test that we can get SEB config by quiz id.
 782       */
 783      public function test_get_config_by_quiz_id() {
 784          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 785          $expected = $quizsettings->get_config();
 786  
 787          $this->assertEquals($expected, quiz_settings::get_config_by_quiz_id($this->quiz->id));
 788  
 789          // Check that data is getting from cache.
 790          $quizsettings->set('showsebtaskbar', 0);
 791          $this->assertNotEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
 792  
 793          // Now save and check that cached as been updated.
 794          $quizsettings->save();
 795          $this->assertEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
 796  
 797          // Returns null for non existing quiz.
 798          $this->assertNull(quiz_settings::get_config_by_quiz_id(7777777));
 799      }
 800  
 801      /**
 802       * Test that SEB config key cache exists after creation of the quiz.
 803       */
 804      public function test_config_key_cache_exists_after_creation() {
 805          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 806      }
 807  
 808      /**
 809       * Test that SEB config key cache gets deleted after deletion.
 810       */
 811      public function test_config_key_cache_purged_after_deletion() {
 812          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 813  
 814          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 815          $quizsettings->delete();
 816  
 817          $this->assertFalse(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 818      }
 819  
 820      /**
 821       * Test that we can get SEB config key by quiz id.
 822       */
 823      public function test_get_config_key_by_quiz_id() {
 824          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 825          $expected = $quizsettings->get_config_key();
 826  
 827          $this->assertEquals($expected, quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 828  
 829          // Check that data is getting from cache.
 830          $quizsettings->set('showsebtaskbar', 0);
 831          $this->assertNotEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 832  
 833          // Now save and check that cached as been updated.
 834          $quizsettings->save();
 835          $this->assertEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 836  
 837          // Returns null for non existing quiz.
 838          $this->assertNull(quiz_settings::get_config_key_by_quiz_id(7777777));
 839      }
 840  
 841  }