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