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