first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,587 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/test_helper_trait.php');
/**
* PHPUnit tests for the access manager.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \quizaccess_seb\seb_access_manager
*/
class access_manager_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/**
* Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
$this->course = $this->getDataGenerator()->create_course();
}
/**
* Test access_manager private property quizsettings is null.
*/
public function test_access_manager_quizsettings_null(): void {
$this->quiz = $this->create_test_quiz($this->course);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->seb_required());
$reflection = new \ReflectionClass('\quizaccess_seb\seb_access_manager');
$property = $reflection->getProperty('quizsettings');
$this->assertFalse($property->getValue($accessmanager));
}
/**
* Test that SEB is not required.
*/
public function test_seb_required_false(): void {
$this->quiz = $this->create_test_quiz($this->course);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->seb_required());
}
/**
* Test that SEB is required.
*/
public function test_seb_required_true(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->seb_required());
}
/**
* Test that user has capability to bypass SEB check.
*/
public function test_user_can_bypass_seb_check(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Set the bypass SEB check capability to $USER.
$this->assign_user_capability('quizaccess/seb:bypassseb', \context_module::instance($this->quiz->cmid)->id);
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->can_bypass_seb());
}
/**
* Test that user has capability to bypass SEB check.
*/
public function test_admin_user_can_bypass_seb_check(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
// Test normal user cannot bypass check.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->can_bypass_seb());
// Test with admin user.
$this->setAdminUser();
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->can_bypass_seb());
}
/**
* Test user does not have capability to bypass SEB check.
*/
public function test_user_cannot_bypass_seb_check(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->can_bypass_seb());
}
/**
* Test we can detect SEB usage.
*/
public function test_is_using_seb(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->is_using_seb());
$_SERVER['HTTP_USER_AGENT'] = 'Test';
$this->assertFalse($accessmanager->is_using_seb());
$_SERVER['HTTP_USER_AGENT'] = 'SEB';
$this->assertTrue($accessmanager->is_using_seb());
}
/**
* Test that the quiz Config Key matches the incoming request header.
*/
public function test_access_keys_validate_with_config_key(): void {
global $FULLME;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$configkey = seb_quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
// Set up dummy request.
$FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4';
$expectedhash = hash('sha256', $FULLME . $configkey);
$_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash;
$this->assertTrue($accessmanager->validate_config_key());
}
/**
* Test that the quiz Config Key matches a provided config key with no incoming request header.
*/
public function test_access_keys_validate_with_provided_config_key(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$url = 'https://www.example.com/moodle';
$accessmanager = $this->get_access_manager();
$configkey = seb_quiz_settings::get_record(['quizid' => $this->quiz->id])->get_config_key();
$fullconfigkey = hash('sha256', $url . $configkey);
$this->assertTrue($accessmanager->validate_config_key($fullconfigkey, $url));
}
/**
* Test that the quiz Config Key does not match the incoming request header.
*/
public function test_access_keys_fail_to_validate_with_config_key(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->validate_config_key());
}
/**
* Test that config key is not checked when using client configuration with SEB.
*/
public function test_config_key_not_checked_if_client_requirement_is_selected(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->should_validate_config_key());
}
/**
* Test that if there are no browser exam keys for quiz, check is skipped.
*/
public function test_no_browser_exam_keys_cause_check_to_be_successful(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', '');
$settings->save();
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->should_validate_browser_exam_key());
$this->assertTrue($accessmanager->validate_browser_exam_key());
}
/**
* Test that access fails if there is no hash in header.
*/
public function test_access_keys_fail_if_browser_exam_key_header_does_not_exist(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', hash('sha256', 'one') . "\n" . hash('sha256', 'two'));
$settings->save();
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->validate_browser_exam_key());
}
/**
* Test that access fails if browser exam key doesn't match hash in header.
*/
public function test_access_keys_fail_if_browser_exam_key_header_does_not_match_provided_hash(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$settings->set('allowedbrowserexamkeys', hash('sha256', 'one') . "\n" . hash('sha256', 'two'));
$settings->save();
$accessmanager = $this->get_access_manager();
$_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = hash('sha256', 'notwhatyouwereexpectinghuh');
$this->assertFalse($accessmanager->validate_browser_exam_key());
}
/**
* Test that browser exam key matches hash in header.
*/
public function test_browser_exam_keys_match_header_hash(): void {
global $FULLME;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$browserexamkey = hash('sha256', 'browserexamkey');
$settings->set('allowedbrowserexamkeys', $browserexamkey); // Add a hashed BEK.
$settings->save();
$accessmanager = $this->get_access_manager();
// Set up dummy request.
$FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4';
$expectedhash = hash('sha256', $FULLME . $browserexamkey);
$_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = $expectedhash;
$this->assertTrue($accessmanager->validate_browser_exam_key());
}
/**
* Test that browser exam key matches a provided browser exam key.
*/
public function test_browser_exam_keys_match_provided_browser_exam_key(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$url = 'https://www.example.com/moodle';
$settings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$browserexamkey = hash('sha256', 'browserexamkey');
$fullbrowserexamkey = hash('sha256', $url . $browserexamkey);
$settings->set('allowedbrowserexamkeys', $browserexamkey); // Add a hashed BEK.
$settings->save();
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->validate_browser_exam_key($fullbrowserexamkey, $url));
}
/**
* Test can get received config key.
*/
public function test_get_received_config_key(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertNull($accessmanager->get_received_config_key());
$_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = 'Test key';
$this->assertEquals('Test key', $accessmanager->get_received_config_key());
}
/**
* Test can get received browser key.
*/
public function get_received_browser_exam_key() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertNull($accessmanager->get_received_browser_exam_key());
$_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = 'Test browser key';
$this->assertEquals('Test browser key', $accessmanager->get_received_browser_exam_key());
}
/**
* Test can correctly get type of SEB usage for the quiz.
*/
public function test_get_seb_use_type(): void {
// No SEB.
$this->quiz = $this->create_test_quiz($this->course);
$accessmanager = $this->get_access_manager();
$this->assertEquals(settings_provider::USE_SEB_NO, $accessmanager->get_seb_use_type());
// Manually.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$this->assertEquals(settings_provider::USE_SEB_CONFIG_MANUALLY, $accessmanager->get_seb_use_type());
// Use template.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->create_template()->get('id'));
$quizsettings->save();
$accessmanager = $this->get_access_manager();
$this->assertEquals(settings_provider::USE_SEB_TEMPLATE, $accessmanager->get_seb_use_type());
// Use uploaded config.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG); // Doesn't check basic header.
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings->save();
$accessmanager = $this->get_access_manager();
$this->assertEquals(settings_provider::USE_SEB_UPLOAD_CONFIG, $accessmanager->get_seb_use_type());
// Use client config.
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertEquals(settings_provider::USE_SEB_CLIENT_CONFIG, $accessmanager->get_seb_use_type());
}
/**
* Data provider for self::test_should_validate_basic_header.
*
* @return array
*/
public function should_validate_basic_header_data_provider() {
return [
[settings_provider::USE_SEB_NO, false],
[settings_provider::USE_SEB_CONFIG_MANUALLY, false],
[settings_provider::USE_SEB_TEMPLATE, false],
[settings_provider::USE_SEB_UPLOAD_CONFIG, false],
[settings_provider::USE_SEB_CLIENT_CONFIG, true],
];
}
/**
* Test we know when we should validate basic header.
*
* @param int $type Type of SEB usage.
* @param bool $expected Expected result.
*
* @dataProvider should_validate_basic_header_data_provider
*/
public function test_should_validate_basic_header($type, $expected): void {
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
$accessmanager->method('get_seb_use_type')->willReturn($type);
$this->assertEquals($expected, $accessmanager->should_validate_basic_header());
}
/**
* Data provider for self::test_should_validate_config_key.
*
* @return array
*/
public function should_validate_config_key_data_provider() {
return [
[settings_provider::USE_SEB_NO, false],
[settings_provider::USE_SEB_CONFIG_MANUALLY, true],
[settings_provider::USE_SEB_TEMPLATE, true],
[settings_provider::USE_SEB_UPLOAD_CONFIG, true],
[settings_provider::USE_SEB_CLIENT_CONFIG, false],
];
}
/**
* Test we know when we should validate config key.
*
* @param int $type Type of SEB usage.
* @param bool $expected Expected result.
*
* @dataProvider should_validate_config_key_data_provider
*/
public function test_should_validate_config_key($type, $expected): void {
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
$accessmanager->method('get_seb_use_type')->willReturn($type);
$this->assertEquals($expected, $accessmanager->should_validate_config_key());
}
/**
* Data provider for self::test_should_validate_browser_exam_key.
*
* @return array
*/
public function should_validate_browser_exam_key_data_provider() {
return [
[settings_provider::USE_SEB_NO, false],
[settings_provider::USE_SEB_CONFIG_MANUALLY, false],
[settings_provider::USE_SEB_TEMPLATE, false],
[settings_provider::USE_SEB_UPLOAD_CONFIG, true],
[settings_provider::USE_SEB_CLIENT_CONFIG, true],
];
}
/**
* Test we know when we should browser exam key.
*
* @param int $type Type of SEB usage.
* @param bool $expected Expected result.
*
* @dataProvider should_validate_browser_exam_key_data_provider
*/
public function test_should_validate_browser_exam_key($type, $expected): void {
$accessmanager = $this->getMockBuilder(seb_access_manager::class)
->disableOriginalConstructor()
->onlyMethods(['get_seb_use_type'])
->getMock();
$accessmanager->method('get_seb_use_type')->willReturn($type);
$this->assertEquals($expected, $accessmanager->should_validate_browser_exam_key());
}
/**
* Test that access manager uses cached Config Key.
*/
public function test_access_manager_uses_cached_config_key(): void {
global $FULLME;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$configkey = $accessmanager->get_valid_config_key();
// Set up dummy request.
$FULLME = 'https://example.com/moodle/mod/quiz/attempt.php?attemptid=123&page=4';
$expectedhash = hash('sha256', $FULLME . $configkey);
$_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = $expectedhash;
$this->assertTrue($accessmanager->validate_config_key());
// Change settings (but don't save) and check that still can validate config key.
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config_key(), $configkey);
$this->assertTrue($accessmanager->validate_config_key());
// Now save settings which should purge caches but access manager still has config key.
$quizsettings->save();
$this->assertNotEquals($quizsettings->get_config_key(), $configkey);
$this->assertTrue($accessmanager->validate_config_key());
// Initialise a new access manager. Now validation should fail.
$accessmanager = $this->get_access_manager();
$this->assertFalse($accessmanager->validate_config_key());
}
/**
* Check that valid SEB config key is null if quiz doesn't have SEB settings.
*/
public function test_valid_config_key_is_null_if_no_settings(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO);
$accessmanager = $this->get_access_manager();
$this->assertEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
$this->assertNull($accessmanager->get_valid_config_key());
}
/**
* Test if config key should not be validated.
*/
public function test_if_config_key_should_not_be_validated(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO);
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->validate_config_key());
}
/**
* Test if browser exam key should not be validated.
*/
public function test_if_browser_exam_key_should_not_be_validated(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
$this->assertTrue($accessmanager->validate_browser_exam_key());
}
/**
* Test that access is set correctly in Moodle session.
*/
public function test_set_session_access(): void {
global $SESSION;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertTrue(empty($SESSION->quizaccess_seb_access[$this->quiz->cmid]));
$accessmanager->set_session_access(true);
$this->assertTrue($SESSION->quizaccess_seb_access[$this->quiz->cmid]);
}
/**
* Test that access is set in Moodle session for only course module associated with access manager.
*/
public function test_session_access_set_for_specific_course_module(): void {
global $SESSION;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$quiz2 = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$accessmanager->set_session_access(true);
$this->assertCount(1, $SESSION->quizaccess_seb_access);
$this->assertTrue($SESSION->quizaccess_seb_access[$this->quiz->cmid]);
$this->assertTrue(empty($SESSION->quizaccess_seb_access[$quiz2->cmid]));
}
/**
* Test that access state can be retrieved from Moodle session.
*/
public function test_validate_session_access(): void {
global $SESSION;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$this->assertEmpty($accessmanager->validate_session_access());
$SESSION->quizaccess_seb_access[$this->quiz->cmid] = true;
$this->assertTrue($accessmanager->validate_session_access());
}
/**
* Test that access can be cleared from Moodle session.
*/
public function test_clear_session_access(): void {
global $SESSION;
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CLIENT_CONFIG);
$accessmanager = $this->get_access_manager();
$SESSION->quizaccess_seb_access[$this->quiz->cmid] = true;
$accessmanager->clear_session_access();
$this->assertTrue(empty($SESSION->quizaccess_seb_access[$this->quiz->cmid]));
}
/**
* Test we can decide if need to redirect to SEB config link.
*/
public function test_should_redirect_to_seb_config_link(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = $this->get_access_manager();
set_config('autoreconfigureseb', '1', 'quizaccess_seb');
$_SERVER['HTTP_USER_AGENT'] = 'SEB';
$this->assertFalse($accessmanager->should_redirect_to_seb_config_link());
set_config('autoreconfigureseb', '1', 'quizaccess_seb');
$_SERVER['HTTP_USER_AGENT'] = 'SEB';
$_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = hash('sha256', 'configkey');
$this->assertTrue($accessmanager->should_redirect_to_seb_config_link());
}
}
@@ -0,0 +1,285 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/test_helper_trait.php');
/**
* PHPUnit tests for backup and restore functionality.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_restore_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/** @var template $template A test template. */
protected $template;
/**
* Called before every test.
*/
public function setUp(): void {
global $USER;
parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
$this->course = $this->getDataGenerator()->create_course();
$this->template = $this->create_template();
$this->user = $USER;
}
/**
* A helper method to create a quiz with template usage of SEB.
*
* @return seb_quiz_settings
*/
protected function create_quiz_with_template() {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $this->template->get('id'));
$quizsettings->save();
return $quizsettings;
}
/**
* A helper method to emulate backup and restore of the quiz.
*
* @return \cm_info|null
*/
protected function backup_and_restore_quiz() {
return duplicate_module($this->course, get_fast_modinfo($this->course)->get_cm($this->quiz->cmid));
}
/**
* A helper method to backup test quiz.
*
* @return mixed A backup ID ready to be restored.
*/
protected function backup_quiz() {
global $CFG;
// Get the necessary files to perform backup and restore.
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
$backupid = 'test-seb-backup-restore';
$bc = new \backup_controller(\backup::TYPE_1ACTIVITY, $this->quiz->coursemodule, \backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $this->user->id);
$bc->execute_plan();
$results = $bc->get_results();
$file = $results['backup_destination'];
$fp = get_file_packer('application/vnd.moodle.backup');
$filepath = $CFG->dataroot . '/temp/backup/' . $backupid;
$file->extract_to_pathname($fp, $filepath);
$bc->destroy();
return $backupid;
}
/**
* A helper method to restore provided backup.
*
* @param string $backupid Backup ID to restore.
*/
protected function restore_quiz($backupid) {
$rc = new \restore_controller($backupid, $this->course->id,
\backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $this->user->id, \backup::TARGET_CURRENT_ADDING);
$this->assertTrue($rc->execute_precheck());
$rc->execute_plan();
$rc->destroy();
}
/**
* A helper method to emulate restoring to a different site.
*/
protected function change_site() {
set_config('siteidentifier', random_string(32) . 'not the same site');
}
/**
* A helper method to validate backup and restore results.
*
* @param cm_info $newcm Restored course_module object.
*/
protected function validate_backup_restore(\cm_info $newcm) {
$this->assertEquals(2, seb_quiz_settings::count_records());
$actual = seb_quiz_settings::get_record(['quizid' => $newcm->instance]);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->get('templateid'), $actual->get('templateid'));
$this->assertEquals($expected->get('requiresafeexambrowser'), $actual->get('requiresafeexambrowser'));
$this->assertEquals($expected->get('showsebdownloadlink'), $actual->get('showsebdownloadlink'));
$this->assertEquals($expected->get('allowuserquitseb'), $actual->get('allowuserquitseb'));
$this->assertEquals($expected->get('quitpassword'), $actual->get('quitpassword'));
$this->assertEquals($expected->get('allowedbrowserexamkeys'), $actual->get('allowedbrowserexamkeys'));
// Validate specific SEB config settings.
foreach (settings_provider::get_seb_config_elements() as $name => $notused) {
$name = preg_replace("/^seb_/", "", $name);
$this->assertEquals($expected->get($name), $actual->get($name));
}
}
/**
* Test backup and restore when no seb.
*/
public function test_backup_restore_no_seb(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_NO);
$this->assertEquals(0, seb_quiz_settings::count_records());
$this->backup_and_restore_quiz();
$this->assertEquals(0, seb_quiz_settings::count_records());
}
/**
* Test backup and restore when manually configured.
*/
public function test_backup_restore_manual_config(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected->set('showsebdownloadlink', 0);
$expected->set('quitpassword', '123');
$expected->save();
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
}
/**
* Test backup and restore when using template.
*/
public function test_backup_restore_template_config(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = $this->create_template();
$expected->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$expected->set('templateid', $template->get('id'));
$expected->save();
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
}
/**
* Test backup and restore when using uploaded file.
*/
public function test_backup_restore_uploaded_config(): void {
$this->quiz = $this->create_test_quiz($this->course, settings_provider::USE_SEB_CONFIG_MANUALLY);
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$expected->save();
$this->assertEquals(1, seb_quiz_settings::count_records());
$newcm = $this->backup_and_restore_quiz();
$this->validate_backup_restore($newcm);
$expectedfile = settings_provider::get_module_context_sebconfig_file($this->quiz->cmid);
$actualfile = settings_provider::get_module_context_sebconfig_file($newcm->id);
$this->assertEquals($expectedfile->get_content(), $actualfile->get_content());
}
/**
* No new template should be restored if restoring to a different site,
* but the template with the same name and content exists..
*/
public function test_restore_template_to_a_different_site_when_the_same_template_exists(): void {
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$this->change_site();
$this->restore_quiz($backupid);
// Should see additional setting record, but no new template record.
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
}
/**
* A new template should be restored if restoring to a different site, but existing template
* has the same content, but different name.
*/
public function test_restore_template_to_a_different_site_when_the_same_content_but_different_name(): void {
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$this->template->set('name', 'New name for template');
$this->template->save();
$this->change_site();
$this->restore_quiz($backupid);
// Should see additional setting record, and new template record.
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(2, template::count_records());
}
/**
* A new template should be restored if restoring to a different site, but existing template
* has the same name, but different content.
*/
public function test_restore_template_to_a_different_site_when_the_same_name_but_different_content(): void {
global $CFG;
$this->create_quiz_with_template();
$backupid = $this->backup_quiz();
$this->assertEquals(1, seb_quiz_settings::count_records());
$this->assertEquals(1, template::count_records());
$newxml = file_get_contents($CFG->dirroot . '/mod/quiz/accessrule/seb/tests/fixtures/simpleunencrypted.seb');
$this->template->set('content', $newxml);
$this->template->save();
$this->change_site();
$this->restore_quiz($backupid);
// Should see additional setting record, and new template record.
$this->assertEquals(2, seb_quiz_settings::count_records());
$this->assertEquals(2, template::count_records());
}
}
@@ -0,0 +1,234 @@
@javascript @mod_quiz @quizaccess @quizaccess_seb
Feature: Safe Exam Browser settings in quiz edit form
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "activities" exist:
| activity | course | section | name |
| quiz | C1 | 1 | Quiz 1 |
Scenario: Quiz setting "Require the use of Safe Exam Browser" has all types, except "Use an existing template".
When I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And the "Require the use of Safe Exam Browser" select box should contain "Yes Configure manually"
And the "Require the use of Safe Exam Browser" select box should not contain "Yes Use an existing template"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Upload my own config"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Use SEB client config"
And the field "Require the use of Safe Exam Browser" matches value "No"
Scenario: Quiz setting "Require the use of Safe Exam Browser" has all types if at least one template has been added.
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
When I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And the "Require the use of Safe Exam Browser" select box should contain "Yes Configure manually"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Use an existing template"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Upload my own config"
And the "Require the use of Safe Exam Browser" select box should contain "Yes Use SEB client config"
And the field "Require the use of Safe Exam Browser" matches value "No"
Scenario: Quiz can be edited without capability to select SEB template
Given the following "permission override" exists:
| role | editingteacher |
| capability | quizaccess/seb:manage_seb_templateid |
| permission | Prevent |
| contextlevel | System |
| reference | |
And the following "user" exists:
| username | teacher |
| firstname | Teacher |
| lastname | One |
And the following "course enrolment" exists:
| user | teacher |
| course | C1 |
| role | editingteacher |
And I log in as "teacher"
# Create the quiz.
When I add a quiz activity to course "Course 1" section "0" and I fill the form with:
| Name | My quiz |
Then I should not see "New Quiz"
# Edit the quiz.
And I am on the "My quiz" "quiz activity editing" page
And I set the field "Name" to "My quiz edited"
And I press "Save and return to course"
And I should not see "Edit settings"
Scenario: SEB settings if using No SEB
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "No"
Then I should not see "Upload Safe Exam Browser config file"
Then I should not see "Safe Exam Browser config template"
Then I should not see "Template 1"
Then I should not see "Show Safe Exam Browser download button"
Then I should not see "Enable quitting of SEB"
Then I should not see "Quit password"
Then I should not see "Allowed browser exam keys"
Then I should not see "Show Exit Safe Exam Browser button, configured with this quit link"
Then I should not see "Ask user to confirm quitting"
Then I should not see "Enable reload in exam"
Then I should not see "Show SEB task bar"
Then I should not see "Show reload button"
Then I should not see "Show time"
Then I should not see "Show keyboard layout"
Then I should not see "Show Wi-Fi control"
Then I should not see "Enable audio controls"
Then I should not see "Mute on startup"
Then I should not see "Enable spell checking"
Then I should not see "Enable URL filtering"
Then I should not see "Filter also embedded content"
Then I should not see "Expressions allowed"
Then I should not see "Regex allowed"
Then I should not see "Expressions blocked"
Then I should not see "Regex blocked"
Scenario: SEB settings if using Use SEB client config
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Use SEB client config"
Then I should see "Show Safe Exam Browser download button"
Then I should see "Allowed browser exam keys"
Then I should not see "Upload Safe Exam Browser config file"
Then I should not see "Safe Exam Browser config template"
Then I should not see "Template 1"
Then I should not see "Enable quitting of SEB"
Then I should not see "Quit password"
Then I should not see "Show Exit Safe Exam Browser button, configured with this quit link"
Then I should not see "Ask user to confirm quitting"
Then I should not see "Enable reload in exam"
Then I should not see "Show SEB task bar"
Then I should not see "Show reload button"
Then I should not see "Show time"
Then I should not see "Show keyboard layout"
Then I should not see "Show Wi-Fi control"
Then I should not see "Enable audio controls"
Then I should not see "Mute on startup"
Then I should not see "Enable spell checking"
Then I should not see "Enable URL filtering"
Then I should not see "Filter also embedded content"
Then I should not see "Expressions allowed"
Then I should not see "Regex allowed"
Then I should not see "Expressions blocked"
Then I should not see "Regex blocked"
Scenario: SEB settings if using Upload my own config
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Upload my own config"
Then I should see "Upload Safe Exam Browser config file"
Then I should see "Show Safe Exam Browser download button"
Then I should not see "Enable quitting of SEB"
Then I should not see "Quit password"
Then I should see "Allowed browser exam keys"
Then I should not see "Show Exit Safe Exam Browser button, configured with this quit link"
Then I should not see "Ask user to confirm quitting"
Then I should not see "Enable reload in exam"
Then I should not see "Show SEB task bar"
Then I should not see "Show reload button"
Then I should not see "Show time"
Then I should not see "Show keyboard layout"
Then I should not see "Show Wi-Fi control"
Then I should not see "Enable audio controls"
Then I should not see "Mute on startup"
Then I should not see "Enable spell checking"
Then I should not see "Enable URL filtering"
Then I should not see "Filter also embedded content"
Then I should not see "Expressions allowed"
Then I should not see "Regex allowed"
Then I should not see "Expressions blocked"
Then I should not see "Regex blocked"
Then I should not see "Safe Exam Browser config template"
Then I should not see "Template 1"
Scenario: SEB settings if using Use an existing template
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Use an existing template"
Then I should see "Safe Exam Browser config template"
Then I should see "Template 1"
Then I should see "Show Safe Exam Browser download button"
Then I should see "Enable quitting of SEB"
Then I should see "Quit password"
Then I should not see "Allowed browser exam keys"
Then I should not see "Upload Safe Exam Browser config file"
Then I should not see "Show Exit Safe Exam Browser button, configured with this quit link"
Then I should not see "Ask user to confirm quitting"
Then I should not see "Enable reload in exam"
Then I should not see "Show SEB task bar"
Then I should not see "Show reload button"
Then I should not see "Show time"
Then I should not see "Show keyboard layout"
Then I should not see "Show Wi-Fi control"
Then I should not see "Enable audio controls"
Then I should not see "Mute on startup"
Then I should not see "Enable spell checking"
Then I should not see "Enable URL filtering"
Then I should not see "Filter also embedded content"
Then I should not see "Expressions allowed"
Then I should not see "Regex allowed"
Then I should not see "Expressions blocked"
Then I should not see "Regex blocked"
And I set the field "Enable quitting of SEB" to "No"
Then I should not see "Quit password"
Scenario: SEB settings if using Configure manually
Given the following "quizaccess_seb > seb templates" exist:
| name |
| Template 1 |
And I am on the "Quiz 1" "quiz activity editing" page logged in as admin
And I expand all fieldsets
And I set the field "Require the use of Safe Exam Browser" to "Yes Configure manually"
Then I should see "Show Safe Exam Browser download button"
Then I should see "Enable quitting of SEB"
Then I should see "Quit password"
Then I should see "Show Exit Safe Exam Browser button, configured with this quit link"
Then I should see "Ask user to confirm quitting"
Then I should see "Enable reload in exam"
Then I should see "Show SEB task bar"
Then I should see "Show reload button"
Then I should see "Show time"
Then I should see "Show keyboard layout"
Then I should see "Show Wi-Fi control"
Then I should see "Enable audio controls"
Then I should not see "Mute on startup"
Then I should see "Enable spell checking"
Then I should see "Enable URL filtering"
Then I should not see "Filter also embedded content"
Then I should not see "Expressions allowed"
Then I should not see "Regex allowed"
Then I should not see "Expressions blocked"
Then I should not see "Regex blocked"
And I set the field "Enable quitting of SEB" to "No"
Then I should not see "Quit password"
And I set the field "Show SEB task bar" to "No"
Then I should not see "Show reload button"
Then I should not see "Show time"
Then I should not see "Show keyboard layout"
Then I should not see "Show Wi-Fi control"
And I set the field "Enable audio controls" to "Yes"
Then I should see "Mute on startup"
And I set the field "Enable URL filtering" to "Yes"
Then I should see "Filter also embedded content"
Then I should see "Expressions allowed"
Then I should see "Regex allowed"
Then I should see "Expressions blocked"
Then I should see "Regex blocked"
Then I should not see "Upload Safe Exam Browser config file"
Then I should not see "Allowed browser exam keys"
Then I should not see "Safe Exam Browser config template"
Then I should not see "Template 1"
@@ -0,0 +1,85 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
/**
* PHPUnit Tests for config_key class.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \quizaccess_seb\config_key
*/
class config_key_test extends \advanced_testcase {
/**
* Test that trying to generate the hash key with bad xml will result in an error.
*/
public function test_config_key_not_generated_with_bad_xml(): void {
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessage("Invalid a PList XML string, representing SEB config");
config_key::generate("<?xml This is some bad xml for sure.");
}
/**
* Test that a config key is generated with empty configuration. SEB would be using defaults for all settings.
*/
public function test_config_key_hash_generated_with_empty_string(): void {
$hash = config_key::generate('')->get_hash();
$this->assertEquals('4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945', $hash);
}
/**
* Test config key hash is derived correctly by Moodle.
*
* @param string $config The SEB config file name.
* @param string $hash The correct config key hash for this file.
*
* @dataProvider real_ck_hash_provider
*/
public function test_config_key_hash_is_derived_correctly($config, $hash): void {
$xml = file_get_contents(__DIR__ . '/fixtures/' . $config);
$derivedhash = config_key::generate($xml)->get_hash();
$this->assertEquals($hash, $derivedhash);
}
/**
* Check that the Config Key hash is not altered if the originatorVersion is present in the XML or not.
*/
public function test_presence_of_originator_version_does_not_effect_hash(): void {
$xmlwithoriginatorversion = file_get_contents(__DIR__ . '/fixtures/simpleunencrypted.seb');
$xmlwithoutoriginatorversion = file_get_contents(__DIR__ . '/fixtures/simpleunencryptedwithoutoriginator.seb');
$hashwithorigver = config_key::generate($xmlwithoriginatorversion)->get_hash();
$hashwithoutorigver = config_key::generate($xmlwithoutoriginatorversion)->get_hash();
$this->assertEquals($hashwithorigver, $hashwithoutorigver);
}
/**
* Provide a seb file, the expected Config Key and a password if encrypted.
*
* @return array
*/
public function real_ck_hash_provider(): array {
return [
'unencrypted_mac2.1.4' => ['unencrypted_mac_001.seb',
'4fa9af8ec8759eb7c680752ef4ee5eaf1a860628608fccae2715d519849f9292', ''],
'unencrypted_win2.2.3' => ['unencrypted_win_223.seb',
'2534e4e9f3188f9f9133bf7cf7b4c5d898292bbd7e8d0230f39d1176636a1431', ''],
];
}
}
@@ -0,0 +1,179 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb\event;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/..//test_helper_trait.php');
/**
* PHPUnit tests for all plugin events.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/**
* Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->course = $this->getDataGenerator()->create_course();
}
/**
* Test creating the access_prevented event.
*
* @covers \quizaccess_seb\event\access_prevented
*/
public function test_event_access_prevented(): void {
$this->resetAfterTest();
$this->setAdminUser();
$quiz = $this->create_test_quiz($this->course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = new \quizaccess_seb\seb_access_manager(new quiz_settings($quiz,
get_coursemodule_from_id('quiz', $quiz->cmid), $this->course));
// Set up event with data.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$_SERVER['HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'] = 'configkey';
$_SERVER['HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'] = 'browserexamkey';
$event = \quizaccess_seb\event\access_prevented::create_strict($accessmanager, 'Because I said so.');
// Create an event sink, trigger event and retrieve event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertEquals(1, count($events));
$event = reset($events);
$expectedconfigkey = $accessmanager->get_valid_config_key();
// Test that the event data is as expected.
$this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
$this->assertEquals('Quiz access was prevented', $event->get_name());
$this->assertEquals(
"The user with id '$user->id' has been prevented from accessing quiz with id '$quiz->id' by the "
. "Safe Exam Browser access plugin. The reason was 'Because I said so.'. "
. "Expected config key: '$expectedconfigkey'. "
. "Received config key: 'configkey'. Received browser exam key: 'browserexamkey'.",
$event->get_description());
$this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
$this->assertEquals($user->id, $event->userid);
$this->assertEquals($quiz->id, $event->objectid);
$this->assertEquals($this->course->id, $event->courseid);
$this->assertEquals('Because I said so.', $event->other['reason']);
$this->assertEquals($expectedconfigkey, $event->other['savedconfigkey']);
$this->assertEquals('configkey', $event->other['receivedconfigkey']);
$this->assertEquals('browserexamkey', $event->other['receivedbrowserexamkey']);
}
/**
* Test creating the access_prevented event with provided SEB keys.
*
* @covers \quizaccess_seb\event\access_prevented
*/
public function test_event_access_prevented_with_keys(): void {
$this->resetAfterTest();
$this->setAdminUser();
$quiz = $this->create_test_quiz($this->course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$accessmanager = new \quizaccess_seb\seb_access_manager(new quiz_settings($quiz,
get_coursemodule_from_id('quiz', $quiz->cmid), $this->course));
// Set up event with data.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$event = \quizaccess_seb\event\access_prevented::create_strict($accessmanager, 'Because I said so.',
'configkey', 'browserexamkey');
// Create an event sink, trigger event and retrieve event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertEquals(1, count($events));
$event = reset($events);
$expectedconfigkey = $accessmanager->get_valid_config_key();
// Test that the event data is as expected.
$this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
$this->assertEquals('Quiz access was prevented', $event->get_name());
$this->assertEquals(
"The user with id '$user->id' has been prevented from accessing quiz with id '$quiz->id' by the "
. "Safe Exam Browser access plugin. The reason was 'Because I said so.'. "
. "Expected config key: '$expectedconfigkey'. "
. "Received config key: 'configkey'. Received browser exam key: 'browserexamkey'.",
$event->get_description());
$this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
$this->assertEquals($user->id, $event->userid);
$this->assertEquals($quiz->id, $event->objectid);
$this->assertEquals($this->course->id, $event->courseid);
$this->assertEquals('Because I said so.', $event->other['reason']);
$this->assertEquals($expectedconfigkey, $event->other['savedconfigkey']);
$this->assertEquals('configkey', $event->other['receivedconfigkey']);
$this->assertEquals('browserexamkey', $event->other['receivedbrowserexamkey']);
}
/**
* Test creating the template_created event.
*
* @covers \quizaccess_seb\event\template_created
*/
public function test_event_create_template(): void {
$this->resetAfterTest();
// Set up event with data.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$template = $this->create_template();
$event = \quizaccess_seb\event\template_created::create_strict(
$template,
\context_system::instance());
// Create an event sink, trigger event and retrieve event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertEquals(1, count($events));
$event = reset($events);
// Test that the event data is as expected.
$this->assertInstanceOf('\quizaccess_seb\event\template_created', $event);
$this->assertEquals('SEB template was created', $event->get_name());
$this->assertEquals(
"The user with id '$user->id' has created a template with id '{$template->get('id')}'.",
$event->get_description()
);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($user->id, $event->userid);
$this->assertEquals($template->get('id'), $event->objectid);
}
}
@@ -0,0 +1,258 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb\external;
use quizaccess_seb\seb_quiz_settings;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../test_helper_trait.php');
/**
* PHPUnit tests for external function.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \quizaccess_seb\external\validate_quiz_access
*/
class validate_quiz_access_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/**
* This method runs before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
// Generate data objects.
$this->course = $this->getDataGenerator()->create_course();
$this->quiz = $this->create_test_quiz($this->course);
$this->user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, 'student');
$this->setUser($this->user);
}
/**
* Bad parameter provider.
*
* @return array
*/
public function bad_parameters_provider(): array {
return [
'no params' => [
'cmid' => null,
'url' => null,
'configkey' => null,
'/Invalid parameter value detected \(Missing required key in single structure: cmid\)/'
],
'no course module id' => [
'cmid' => null,
'url' => 'https://www.example.com/moodle',
'configkey' => hash('sha256', 'configkey'),
'/Invalid parameter value detected \(Missing required key in single structure: cmid\)/'
],
'no url' => [
'cmid' => 123,
'url' => null,
'configkey' => hash('sha256', 'configkey'),
'/Invalid parameter value detected \(Missing required key in single structure: url\)/'
],
'cmid is not an int' => [
'cmid' => 'test',
'url' => 'https://www.example.com/moodle',
'configkey' => null,
'/Invalid external api parameter: the value is "test", the server was expecting "int" type/'
],
'url is not a url' => [
'cmid' => 123,
'url' => 123,
'configkey' => hash('sha256', 'configkey'),
'/Invalid external api parameter: the value is "123", the server was expecting "url" type/'
],
];
}
/**
* Test exception thrown for bad parameters.
*
* @param mixed $cmid Course module id.
* @param mixed $url Page URL.
* @param mixed $configkey SEB config key.
* @param mixed $messageregex Error message regex to check.
*
* @dataProvider bad_parameters_provider
*/
public function test_invalid_parameters($cmid, $url, $configkey, $messageregex): void {
$params = [];
if (!empty($cmid)) {
$params['cmid'] = $cmid;
}
if (!empty($url)) {
$params['url'] = $url;
}
if (!empty($configkey)) {
$params['configkey'] = $configkey;
}
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessageMatches($messageregex);
\core_external\external_api::validate_parameters(validate_quiz_keys::execute_parameters(), $params);
}
/**
* Test that the user has permissions to access context.
*/
public function test_context_is_not_valid_for_user(): void {
// Set user as user not enrolled in course and quiz.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->expectException(\require_login_exception::class);
$this->expectExceptionMessage('Course or activity not accessible. (Not enrolled)');
validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', 'configkey');
}
/**
* Test exception thrown when no key provided.
*/
public function test_no_keys_provided(): void {
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessage('At least one Safe Exam Browser key must be provided.');
validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle');
}
/**
* Test exception thrown if cmid doesn't match a quiz.
*/
public function test_quiz_does_not_exist(): void {
$this->setAdminUser();
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $this->course->id]);
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessage('Quiz not found matching course module ID: ' . $forum->cmid);
validate_quiz_keys::execute($forum->cmid, 'https://www.example.com/moodle', 'configkey');
}
/**
* Test config key is valid.
*/
public function test_config_key_valid(): void {
$sink = $this->redirectEvents();
// Test settings to populate the quiz.
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
]);
$url = 'https://www.example.com/moodle';
// Create the quiz settings.
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$fullconfigkey = hash('sha256', $url . $quizsettings->get_config_key());
$result = validate_quiz_keys::execute($this->quiz->cmid, $url, $fullconfigkey);
$this->assertTrue($result['configkey']);
$this->assertTrue($result['browserexamkey']);
$events = $sink->get_events();
$this->assertCount(0, $events);
}
/**
* Test config key is not valid.
*/
public function test_config_key_not_valid(): void {
$sink = $this->redirectEvents();
// Test settings to populate the quiz.
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
]);
// Create the quiz settings.
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', 'badconfigkey');
$this->assertFalse($result['configkey']);
$this->assertTrue($result['browserexamkey']);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
$this->assertStringContainsString('Invalid SEB config key', $event->get_description());
}
/**
* Test browser exam key is valid.
*/
public function test_browser_exam_key_valid(): void {
$sink = $this->redirectEvents();
// Test settings to populate the quiz.
$url = 'https://www.example.com/moodle';
$validbrowserexamkey = hash('sha256', 'validbrowserexamkey');
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
'requiresafeexambrowser' => \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG,
'allowedbrowserexamkeys' => $validbrowserexamkey,
]);
// Create the quiz settings.
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$fullbrowserexamkey = hash('sha256', $url . $validbrowserexamkey);
$result = validate_quiz_keys::execute($this->quiz->cmid, $url, null, $fullbrowserexamkey);
$this->assertTrue($result['configkey']);
$this->assertTrue($result['browserexamkey']);
$events = $sink->get_events();
$this->assertCount(0, $events);
}
/**
* Test browser exam key is not valid.
*/
public function test_browser_exam_key_not_valid(): void {
$sink = $this->redirectEvents();
// Test settings to populate the quiz.
$validbrowserexamkey = hash('sha256', 'validbrowserexamkey');
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
'requiresafeexambrowser' => \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG,
'allowedbrowserexamkeys' => $validbrowserexamkey,
]);
// Create the quiz settings.
$quizsettings = new seb_quiz_settings(0, $settings);
$quizsettings->save();
$result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', null,
hash('sha256', 'badbrowserexamkey'));
$this->assertTrue($result['configkey']);
$this->assertFalse($result['browserexamkey']);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
$this->assertStringContainsString('Invalid SEB browser key', $event->get_description());
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aaaaaa</key>
<date>1940-10-09T22:13:56Z</date>
<key>originatorVersion</key>
<string>SEB_Win_2.1.1</string>
<key>startURL</key>
<string>https://safeexambrowser.org/start</string>
<key>startResource</key>
<string />
<key>sebServerURL</key>
<string />
<key>hashedAdminPassword</key>
<string />
<key>allowQuit</key>
<true />
<key>ignoreExitKeys</key>
<true />
<key>allowUserAppFolderInstall</key>
<false />
<key>allowSiri</key>
<false />
<key>allowDictation</key>
<false />
<key>detectStoppedProcess</key>
<true />
<key>allowDisplayMirroring</key>
<false />
<key>allowedDisplaysMaxNumber</key>
<integer>1</integer>
<key>allowedDisplayBuiltin</key>
<true />
</dict>
</plist>
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aaaaaa</key>
<date>1940-10-09T22:13:56Z</date>
<key>startURL</key>
<string>https://safeexambrowser.org/start</string>
<key>startResource</key>
<string />
<key>sebServerURL</key>
<string />
<key>hashedAdminPassword</key>
<string />
<key>allowQuit</key>
<true />
<key>ignoreExitKeys</key>
<true />
<key>allowUserAppFolderInstall</key>
<false />
<key>allowSiri</key>
<false />
<key>allowDictation</key>
<false />
<key>detectStoppedProcess</key>
<true />
<key>allowDisplayMirroring</key>
<false />
<key>allowedDisplaysMaxNumber</key>
<integer>1</integer>
<key>allowedDisplayBuiltin</key>
<true />
</dict>
</plist>
+797
View File
@@ -0,0 +1,797 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>originatorVersion</key>
<string>SEB_Win_2.1.1</string>
<key>startURL</key>
<string>https://safeexambrowser.org/start</string>
<key>startResource</key>
<string />
<key>sebServerURL</key>
<string />
<key>hashedAdminPassword</key>
<string />
<key>allowQuit</key>
<true />
<key>ignoreExitKeys</key>
<true />
<key>hashedQuitPassword</key>
<string />
<key>exitKey1</key>
<integer>2</integer>
<key>exitKey2</key>
<integer>10</integer>
<key>exitKey3</key>
<integer>5</integer>
<key>sebMode</key>
<integer>0</integer>
<key>browserMessagingSocket</key>
<string>ws://localhost:8706</string>
<key>browserMessagingPingTime</key>
<integer>120000</integer>
<key>sebConfigPurpose</key>
<integer>0</integer>
<key>allowPreferencesWindow</key>
<true />
<key>useAsymmetricOnlyEncryption</key>
<true />
<key>browserViewMode</key>
<integer>0</integer>
<key>mainBrowserWindowWidth</key>
<string>100%</string>
<key>mainBrowserWindowHeight</key>
<string>100%</string>
<key>mainBrowserWindowPositioning</key>
<integer>1</integer>
<key>enableBrowserWindowToolbar</key>
<false />
<key>hideBrowserWindowToolbar</key>
<false />
<key>showMenuBar</key>
<false />
<key>showTaskBar</key>
<true />
<key>taskBarHeight</key>
<integer>40</integer>
<key>touchOptimized</key>
<false />
<key>enableZoomText</key>
<true />
<key>enableZoomPage</key>
<true />
<key>zoomMode</key>
<integer>0</integer>
<key>allowSpellCheck</key>
<false />
<key>allowDictionaryLookup</key>
<false />
<key>allowSpellCheckDictionary</key>
<array></array>
<key>additionalDictionaries</key>
<array></array>
<key>showReloadButton</key>
<true />
<key>showTime</key>
<true />
<key>showInputLanguage</key>
<true />
<key>enableTouchExit</key>
<false />
<key>oskBehavior</key>
<integer>2</integer>
<key>audioControlEnabled</key>
<true />
<key>audioMute</key>
<false />
<key>audioVolumeLevel</key>
<integer>25</integer>
<key>audioSetVolumeLevel</key>
<false />
<key>browserScreenKeyboard</key>
<false />
<key>newBrowserWindowByLinkPolicy</key>
<integer>2</integer>
<key>newBrowserWindowByScriptPolicy</key>
<integer>2</integer>
<key>newBrowserWindowByLinkBlockForeign</key>
<false />
<key>newBrowserWindowByScriptBlockForeign</key>
<false />
<key>newBrowserWindowByLinkWidth</key>
<string>100%</string>
<key>newBrowserWindowByLinkHeight</key>
<string>100%</string>
<key>newBrowserWindowByLinkPositioning</key>
<integer>2</integer>
<key>enablePlugIns</key>
<true />
<key>enableJava</key>
<false />
<key>enableJavaScript</key>
<true />
<key>blockPopUpWindows</key>
<false />
<key>allowVideoCapture</key>
<false />
<key>allowAudioCapture</key>
<false />
<key>allowBrowsingBackForward</key>
<false />
<key>newBrowserWindowNavigation</key>
<true />
<key>removeBrowserProfile</key>
<false />
<key>removeLocalStorage</key>
<false />
<key>enableSebBrowser</key>
<false />
<key>browserWindowAllowReload</key>
<true />
<key>newBrowserWindowAllowReload</key>
<true />
<key>showReloadWarning</key>
<true />
<key>newBrowserWindowShowReloadWarning</key>
<false />
<key>browserUserAgentWinDesktopMode</key>
<integer>0</integer>
<key>browserUserAgentWinDesktopModeCustom</key>
<string />
<key>browserUserAgentWinTouchMode</key>
<integer>0</integer>
<key>browserUserAgentWinTouchModeIPad</key>
<string>Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.3 Mobile/15E216 Safari/605.1.15</string>
<key>browserUserAgentWinTouchModeCustom</key>
<string />
<key>browserUserAgent</key>
<string />
<key>browserUserAgentMac</key>
<integer>0</integer>
<key>browserUserAgentMacCustom</key>
<string />
<key>browserWindowTitleSuffix</key>
<string />
<key>allowDownUploads</key>
<true />
<key>downloadDirectoryOSX</key>
<string>~/Downloads</string>
<key>downloadDirectoryWin</key>
<string />
<key>openDownloads</key>
<false />
<key>chooseFileToUploadPolicy</key>
<integer>0</integer>
<key>downloadPDFFiles</key>
<false />
<key>allowPDFPlugIn</key>
<false />
<key>downloadAndOpenSebConfig</key>
<true />
<key>backgroundOpenSEBConfig</key>
<false />
<key>examKeySalt</key>
<data>0zVdSEqKAnK0ZNd4QFlwVKBTgw60o6xRjpfDokUYYnk=</data>
<key>browserExamKey</key>
<string />
<key>browserURLSalt</key>
<true />
<key>sendBrowserExamKey</key>
<false />
<key>quitURL</key>
<string />
<key>quitURLConfirm</key>
<true />
<key>restartExamURL</key>
<string />
<key>restartExamUseStartURL</key>
<false />
<key>restartExamText</key>
<string />
<key>restartExamPasswordProtected</key>
<true />
<key>additionalResources</key>
<array></array>
<key>monitorProcesses</key>
<true />
<key>allowSwitchToApplications</key>
<false />
<key>allowFlashFullscreen</key>
<false />
<key>permittedProcesses</key>
<array>
<dict>
<key>active</key>
<true />
<key>autostart</key>
<true />
<key>iconInTaskbar</key>
<true />
<key>runInBackground</key>
<false />
<key>allowUserToChooseApp</key>
<false />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>title</key>
<string>SEB</string>
<key>description</key>
<string />
<key>executable</key>
<string>firefox.exe</string>
<key>originalName</key>
<string>firefox.exe</string>
<key>path</key>
<string>../xulrunner/</string>
<key>identifier</key>
<string>Firefox</string>
<key>windowHandlingProcess</key>
<string />
<key>arguments</key>
<array></array>
</dict>
</array>
<key>prohibitedProcesses</key>
<array>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>join.me</string>
<key>originalName</key>
<string>join.me</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RPCSuite</string>
<key>originalName</key>
<string>RPCSuite</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RPCService</string>
<key>originalName</key>
<string>RPCService</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RemotePCDesktop</string>
<key>originalName</key>
<string>RemotePCDesktop</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>beamyourscreen-host</string>
<key>originalName</key>
<string>beamyourscreen-host</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>AeroAdmin</string>
<key>originalName</key>
<string>AeroAdmin</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>Mikogo-host</string>
<key>originalName</key>
<string>Mikogo-host</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>chromoting</string>
<key>originalName</key>
<string>chromoting</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncserverui</string>
<key>originalName</key>
<string>vncserverui</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncviewer</string>
<key>originalName</key>
<string>vncviewer</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncserver</string>
<key>originalName</key>
<string>vncserver</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>TeamViewer</string>
<key>originalName</key>
<string>TeamViewer</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>GotoMeetingWinStore</string>
<key>originalName</key>
<string>GotoMeetingWinStore</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>g2mcomm.exe</string>
<key>originalName</key>
<string>g2mcomm.exe</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>SkypeHost</string>
<key>originalName</key>
<string>SkypeHost</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>Skype</string>
<key>originalName</key>
<string>Skype</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
</array>
<key>enableURLFilter</key>
<false />
<key>enableURLContentFilter</key>
<false />
<key>URLFilterRules</key>
<array></array>
<key>URLFilterEnable</key>
<false />
<key>URLFilterEnableContentFilter</key>
<false />
<key>blacklistURLFilter</key>
<string />
<key>whitelistURLFilter</key>
<string />
<key>urlFilterTrustedContent</key>
<true />
<key>urlFilterRegex</key>
<true />
<key>embeddedCertificates</key>
<array></array>
<key>pinEmbeddedCertificates</key>
<false />
<key>proxySettingsPolicy</key>
<integer>0</integer>
<key>proxies</key>
<dict>
<key>ExceptionsList</key>
<array></array>
<key>ExcludeSimpleHostnames</key>
<false />
<key>AutoDiscoveryEnabled</key>
<false />
<key>AutoConfigurationEnabled</key>
<false />
<key>AutoConfigurationJavaScript</key>
<string />
<key>AutoConfigurationURL</key>
<string />
<key>FTPPassive</key>
<true />
<key>HTTPEnable</key>
<false />
<key>HTTPPort</key>
<integer>80</integer>
<key>HTTPProxy</key>
<string />
<key>HTTPRequiresPassword</key>
<false />
<key>HTTPUsername</key>
<string />
<key>HTTPPassword</key>
<string />
<key>HTTPSEnable</key>
<false />
<key>HTTPSPort</key>
<integer>443</integer>
<key>HTTPSProxy</key>
<string />
<key>HTTPSRequiresPassword</key>
<false />
<key>HTTPSUsername</key>
<string />
<key>HTTPSPassword</key>
<string />
<key>FTPEnable</key>
<false />
<key>FTPPort</key>
<integer>21</integer>
<key>FTPProxy</key>
<string />
<key>FTPRequiresPassword</key>
<false />
<key>FTPUsername</key>
<string />
<key>FTPPassword</key>
<string />
<key>SOCKSEnable</key>
<false />
<key>SOCKSPort</key>
<integer>1080</integer>
<key>SOCKSProxy</key>
<string />
<key>SOCKSRequiresPassword</key>
<false />
<key>SOCKSUsername</key>
<string />
<key>SOCKSPassword</key>
<string />
<key>RTSPEnable</key>
<false />
<key>RTSPPort</key>
<integer>554</integer>
<key>RTSPProxy</key>
<string />
<key>RTSPRequiresPassword</key>
<false />
<key>RTSPUsername</key>
<string />
<key>RTSPPassword</key>
<string />
</dict>
<key>sebServicePolicy</key>
<integer>1</integer>
<key>allowVirtualMachine</key>
<true />
<key>allowScreenSharing</key>
<false />
<key>enablePrivateClipboard</key>
<true />
<key>createNewDesktop</key>
<true />
<key>killExplorerShell</key>
<false />
<key>enableLogging</key>
<true />
<key>logDirectoryOSX</key>
<string>~/Documents</string>
<key>logDirectoryWin</key>
<string />
<key>allowWlan</key>
<false />
<key>lockOnMessageSocketClose</key>
<true />
<key>minMacOSVersion</key>
<integer>4</integer>
<key>enableAppSwitcherCheck</key>
<true />
<key>forceAppFolderInstall</key>
<true />
<key>allowUserAppFolderInstall</key>
<false />
<key>allowSiri</key>
<false />
<key>allowDictation</key>
<false />
<key>detectStoppedProcess</key>
<true />
<key>allowDisplayMirroring</key>
<false />
<key>allowedDisplaysMaxNumber</key>
<integer>1</integer>
<key>allowedDisplayBuiltin</key>
<true />
<key>insideSebEnableSwitchUser</key>
<false />
<key>insideSebEnableLockThisComputer</key>
<false />
<key>insideSebEnableChangeAPassword</key>
<false />
<key>insideSebEnableStartTaskManager</key>
<false />
<key>insideSebEnableLogOff</key>
<false />
<key>insideSebEnableShutDown</key>
<false />
<key>insideSebEnableEaseOfAccess</key>
<false />
<key>insideSebEnableVmWareClientShade</key>
<false />
<key>insideSebEnableEnableNetworkConnectionSelector</key>
<false />
<key>hookKeys</key>
<true />
<key>enableEsc</key>
<true />
<key>enableCtrlEsc</key>
<false />
<key>enableAltEsc</key>
<false />
<key>enableAltTab</key>
<true />
<key>enableAltF4</key>
<false />
<key>enableStartMenu</key>
<false />
<key>enableRightMouse</key>
<true />
<key>enablePrintScreen</key>
<false />
<key>enableAltMouseWheel</key>
<false />
<key>enableF1</key>
<true />
<key>enableF2</key>
<true />
<key>enableF3</key>
<true />
<key>enableF4</key>
<true />
<key>enableF5</key>
<true />
<key>enableF6</key>
<true />
<key>enableF7</key>
<true />
<key>enableF8</key>
<true />
<key>enableF9</key>
<true />
<key>enableF10</key>
<true />
<key>enableF11</key>
<true />
<key>enableF12</key>
<true />
</dict>
</plist>
@@ -0,0 +1,488 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>URLFilterEnable</key>
<false/>
<key>URLFilterEnableContentFilter</key>
<false/>
<key>URLFilterIgnoreList</key>
<array/>
<key>URLFilterMessage</key>
<integer>0</integer>
<key>URLFilterRules</key>
<array/>
<key>additionalResources</key>
<array/>
<key>allowBrowsingBackForward</key>
<false/>
<key>allowDictation</key>
<false/>
<key>allowDictionaryLookup</key>
<false/>
<key>allowDisplayMirroring</key>
<false/>
<key>allowDownUploads</key>
<false/>
<key>allowFlashFullscreen</key>
<false/>
<key>allowPDFPlugIn</key>
<false/>
<key>allowPreferencesWindow</key>
<true/>
<key>allowQuit</key>
<true/>
<key>allowScreenSharing</key>
<false/>
<key>allowSiri</key>
<false/>
<key>allowSpellCheck</key>
<false/>
<key>allowSwitchToApplications</key>
<false/>
<key>allowUserAppFolderInstall</key>
<false/>
<key>allowUserSwitching</key>
<true/>
<key>allowVideoCapture</key>
<false/>
<key>allowVirtualMachine</key>
<false/>
<key>allowWlan</key>
<false/>
<key>allowedDisplayBuiltin</key>
<true/>
<key>allowedDisplaysMaxNumber</key>
<integer>1</integer>
<key>allowiOSBetaVersionNumber</key>
<integer>0</integer>
<key>allowiOSVersionNumberMajor</key>
<integer>9</integer>
<key>allowiOSVersionNumberMinor</key>
<integer>3</integer>
<key>allowiOSVersionNumberPatch</key>
<integer>5</integer>
<key>blacklistURLFilter</key>
<string></string>
<key>blockPopUpWindows</key>
<false/>
<key>browserMediaAutoplay</key>
<false/>
<key>browserMessagingPingTime</key>
<integer>120000</integer>
<key>browserMessagingSocket</key>
<string>ws:\localhost:8706</string>
<key>browserScreenKeyboard</key>
<false/>
<key>browserURLSalt</key>
<true/>
<key>browserUserAgent</key>
<string></string>
<key>browserUserAgentMac</key>
<integer>0</integer>
<key>browserUserAgentMacCustom</key>
<string></string>
<key>browserUserAgentWinDesktopMode</key>
<integer>0</integer>
<key>browserUserAgentWinDesktopModeCustom</key>
<string></string>
<key>browserUserAgentWinTouchMode</key>
<integer>0</integer>
<key>browserUserAgentWinTouchModeCustom</key>
<string></string>
<key>browserUserAgentWinTouchModeIPad</key>
<string>Mozilla/5.0 (iPad; CPU OS 11_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1</string>
<key>browserUserAgentiOS</key>
<integer>0</integer>
<key>browserUserAgentiOSCustom</key>
<string></string>
<key>browserViewMode</key>
<integer>0</integer>
<key>browserWindowAllowReload</key>
<true/>
<key>browserWindowShowURL</key>
<integer>0</integer>
<key>chooseFileToUploadPolicy</key>
<integer>0</integer>
<key>configFileCreateIdentity</key>
<false/>
<key>configFileEncryptUsingIdentity</key>
<false/>
<key>configKeySalt</key>
<data>
</data>
<key>createNewDesktop</key>
<true/>
<key>detectStoppedProcess</key>
<true/>
<key>downloadAndOpenSebConfig</key>
<true/>
<key>downloadDirectoryOSX</key>
<string>/Users/andrewmadden/Downloads</string>
<key>downloadDirectoryWin</key>
<string>Downloads</string>
<key>downloadPDFFiles</key>
<false/>
<key>embeddedCertificates</key>
<array/>
<key>enableAltEsc</key>
<false/>
<key>enableAltF4</key>
<false/>
<key>enableAltMouseWheel</key>
<false/>
<key>enableAltTab</key>
<true/>
<key>enableAppSwitcherCheck</key>
<true/>
<key>enableBrowserWindowToolbar</key>
<false/>
<key>enableCtrlEsc</key>
<false/>
<key>enableDrawingEditor</key>
<false/>
<key>enableEsc</key>
<true/>
<key>enableF1</key>
<true/>
<key>enableF10</key>
<true/>
<key>enableF11</key>
<true/>
<key>enableF12</key>
<true/>
<key>enableF2</key>
<true/>
<key>enableF3</key>
<true/>
<key>enableF4</key>
<true/>
<key>enableF5</key>
<true/>
<key>enableF6</key>
<true/>
<key>enableF7</key>
<true/>
<key>enableF8</key>
<true/>
<key>enableF9</key>
<true/>
<key>enableJava</key>
<false/>
<key>enableJavaScript</key>
<true/>
<key>enableLogging</key>
<true/>
<key>enablePlugIns</key>
<true/>
<key>enablePrintScreen</key>
<false/>
<key>enablePrivateClipboard</key>
<true/>
<key>enableRightMouse</key>
<true/>
<key>enableSebBrowser</key>
<true/>
<key>enableStartMenu</key>
<false/>
<key>enableTouchExit</key>
<false/>
<key>enableZoomPage</key>
<true/>
<key>enableZoomText</key>
<true/>
<key>examKeySalt</key>
<data>
4iLONVAc1yKWLxbe6Vbf2vWYQO6zIRAobKgc3f25LH8=
</data>
<key>examSessionClearSessionCookies</key>
<true/>
<key>examSessionReconfigureAllow</key>
<false/>
<key>examSessionReconfigureConfigURL</key>
<string></string>
<key>exitKey1</key>
<integer>2</integer>
<key>exitKey2</key>
<integer>10</integer>
<key>exitKey3</key>
<integer>5</integer>
<key>forceAppFolderInstall</key>
<true/>
<key>hashedAdminPassword</key>
<string></string>
<key>hashedQuitPassword</key>
<string></string>
<key>hideBrowserWindowToolbar</key>
<false/>
<key>hookKeys</key>
<true/>
<key>ignoreExitKeys</key>
<true/>
<key>ignoreQuitPassword</key>
<false/>
<key>insideSebEnableChangeAPassword</key>
<false/>
<key>insideSebEnableEaseOfAccess</key>
<false/>
<key>insideSebEnableLockThisComputer</key>
<false/>
<key>insideSebEnableLogOff</key>
<false/>
<key>insideSebEnableNetworkConnectionSelector</key>
<false/>
<key>insideSebEnableShutDown</key>
<false/>
<key>insideSebEnableStartTaskManager</key>
<false/>
<key>insideSebEnableSwitchUser</key>
<false/>
<key>insideSebEnableVmWareClientShade</key>
<false/>
<key>killExplorerShell</key>
<false/>
<key>logDirectoryOSX</key>
<string></string>
<key>logDirectoryWin</key>
<string></string>
<key>logLevel</key>
<integer>4</integer>
<key>logSendingRequiresAdminPassword</key>
<false/>
<key>mainBrowserWindowHeight</key>
<string>100%</string>
<key>mainBrowserWindowPositioning</key>
<integer>1</integer>
<key>mainBrowserWindowWidth</key>
<string>100%</string>
<key>minMacOSVersion</key>
<integer>4</integer>
<key>mobileAllowInlineMediaPlayback</key>
<true/>
<key>mobileAllowPictureInPictureMediaPlayback</key>
<false/>
<key>mobileAllowQRCodeConfig</key>
<false/>
<key>mobileAllowSingleAppMode</key>
<false/>
<key>mobileCompactAllowInlineMediaPlayback</key>
<false/>
<key>mobileEnableASAM</key>
<true/>
<key>mobileEnableGuidedAccessLinkTransform</key>
<false/>
<key>mobilePreventAutoLock</key>
<true/>
<key>mobileShowSettings</key>
<false/>
<key>mobileStatusBarAppearance</key>
<integer>1</integer>
<key>mobileStatusBarAppearanceExtended</key>
<integer>1</integer>
<key>mobileSupportedFormFactorsCompact</key>
<true/>
<key>mobileSupportedFormFactorsNonTelephonyCompact</key>
<true/>
<key>mobileSupportedFormFactorsRegular</key>
<true/>
<key>mobileSupportedScreenOrientationsCompactLandscapeLeft</key>
<true/>
<key>mobileSupportedScreenOrientationsCompactLandscapeRight</key>
<true/>
<key>mobileSupportedScreenOrientationsCompactPortrait</key>
<true/>
<key>mobileSupportedScreenOrientationsCompactPortraitUpsideDown</key>
<false/>
<key>mobileSupportedScreenOrientationsRegularLandscapeLeft</key>
<true/>
<key>mobileSupportedScreenOrientationsRegularLandscapeRight</key>
<true/>
<key>mobileSupportedScreenOrientationsRegularPortrait</key>
<true/>
<key>mobileSupportedScreenOrientationsRegularPortraitUpsideDown</key>
<true/>
<key>monitorProcesses</key>
<true/>
<key>newBrowserWindowAllowReload</key>
<true/>
<key>newBrowserWindowByLinkBlockForeign</key>
<false/>
<key>newBrowserWindowByLinkHeight</key>
<string>100%</string>
<key>newBrowserWindowByLinkPolicy</key>
<integer>1</integer>
<key>newBrowserWindowByLinkPositioning</key>
<integer>2</integer>
<key>newBrowserWindowByLinkWidth</key>
<string>1000</string>
<key>newBrowserWindowByScriptBlockForeign</key>
<false/>
<key>newBrowserWindowByScriptPolicy</key>
<integer>2</integer>
<key>newBrowserWindowNavigation</key>
<true/>
<key>newBrowserWindowShowReloadWarning</key>
<false/>
<key>newBrowserWindowShowURL</key>
<integer>2</integer>
<key>openDownloads</key>
<false/>
<key>originatorVersion</key>
<string>SEB_OSX_2.1.4_2B66</string>
<key>oskBehavior</key>
<integer>2</integer>
<key>permittedProcesses</key>
<array/>
<key>pinEmbeddedCertificates</key>
<false/>
<key>prohibitedProcesses</key>
<array/>
<key>proxies</key>
<dict>
<key>org_safeexambrowser_SEB_AutoConfigurationEnabled</key>
<false/>
<key>org_safeexambrowser_SEB_AutoConfigurationJavaScript</key>
<string></string>
<key>org_safeexambrowser_SEB_AutoConfigurationURL</key>
<string></string>
<key>org_safeexambrowser_SEB_AutoDiscoveryEnabled</key>
<false/>
<key>org_safeexambrowser_SEB_ExceptionsList</key>
<array/>
<key>org_safeexambrowser_SEB_ExcludeSimpleHostnames</key>
<false/>
<key>org_safeexambrowser_SEB_FTPEnable</key>
<false/>
<key>org_safeexambrowser_SEB_FTPPassive</key>
<true/>
<key>org_safeexambrowser_SEB_FTPPassword</key>
<string></string>
<key>org_safeexambrowser_SEB_FTPPort</key>
<integer>21</integer>
<key>org_safeexambrowser_SEB_FTPProxy</key>
<string></string>
<key>org_safeexambrowser_SEB_FTPRequiresPassword</key>
<false/>
<key>org_safeexambrowser_SEB_FTPUsername</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPEnable</key>
<false/>
<key>org_safeexambrowser_SEB_HTTPPassword</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPPort</key>
<integer>80</integer>
<key>org_safeexambrowser_SEB_HTTPProxy</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPRequiresPassword</key>
<false/>
<key>org_safeexambrowser_SEB_HTTPSEnable</key>
<false/>
<key>org_safeexambrowser_SEB_HTTPSPassword</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPSPort</key>
<integer>443</integer>
<key>org_safeexambrowser_SEB_HTTPSProxy</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPSRequiresPassword</key>
<false/>
<key>org_safeexambrowser_SEB_HTTPSUsername</key>
<string></string>
<key>org_safeexambrowser_SEB_HTTPUsername</key>
<string></string>
<key>org_safeexambrowser_SEB_RTSPEnable</key>
<false/>
<key>org_safeexambrowser_SEB_RTSPPassword</key>
<string></string>
<key>org_safeexambrowser_SEB_RTSPPort</key>
<integer>554</integer>
<key>org_safeexambrowser_SEB_RTSPProxy</key>
<string></string>
<key>org_safeexambrowser_SEB_RTSPRequiresPassword</key>
<false/>
<key>org_safeexambrowser_SEB_RTSPUsername</key>
<string></string>
<key>org_safeexambrowser_SEB_SOCKSEnable</key>
<false/>
<key>org_safeexambrowser_SEB_SOCKSPassword</key>
<string></string>
<key>org_safeexambrowser_SEB_SOCKSPort</key>
<integer>1080</integer>
<key>org_safeexambrowser_SEB_SOCKSProxy</key>
<string></string>
<key>org_safeexambrowser_SEB_SOCKSRequiresPassword</key>
<false/>
<key>org_safeexambrowser_SEB_SOCKSUsername</key>
<string></string>
</dict>
<key>proxySettingsPolicy</key>
<integer>0</integer>
<key>quitURL</key>
<string></string>
<key>quitURLConfirm</key>
<true/>
<key>quitURLRestart</key>
<false/>
<key>removeBrowserProfile</key>
<false/>
<key>removeLocalStorage</key>
<false/>
<key>restartExamPasswordProtected</key>
<true/>
<key>restartExamText</key>
<string></string>
<key>restartExamURL</key>
<string></string>
<key>restartExamUseStartURL</key>
<false/>
<key>sebConfigPurpose</key>
<integer>0</integer>
<key>sebMode</key>
<integer>0</integer>
<key>sebServerFallback</key>
<false/>
<key>sebServerURL</key>
<string></string>
<key>sebServicePolicy</key>
<integer>2</integer>
<key>sendBrowserExamKey</key>
<true/>
<key>showBackToStartButton</key>
<true/>
<key>showInputLanguage</key>
<false/>
<key>showMenuBar</key>
<true/>
<key>showNavigationButtons</key>
<false/>
<key>showQuitButton</key>
<true/>
<key>showReloadButton</key>
<true/>
<key>showReloadWarning</key>
<false/>
<key>showScanQRCodeButton</key>
<false/>
<key>showTaskBar</key>
<true/>
<key>showTime</key>
<true/>
<key>startURL</key>
<string>https://www.google.com</string>
<key>startURLAllowDeepLink</key>
<false/>
<key>startURLAppendQueryParameter</key>
<false/>
<key>taskBarHeight</key>
<integer>40</integer>
<key>touchOptimized</key>
<false/>
<key>urlFilterRegex</key>
<true/>
<key>urlFilterTrustedContent</key>
<false/>
<key>whitelistURLFilter</key>
<string></string>
<key>zoomMode</key>
<integer>0</integer>
</dict>
</plist>
@@ -0,0 +1,801 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>batteryChargeThresholdCritical</key>
<real>0.1</real>
<key>batteryChargeThresholdLow</key>
<real>0.2</real>
<key>originatorVersion</key>
<string>SEB_Win_2.1.1</string>
<key>startURL</key>
<string>https://safeexambrowser.org/start</string>
<key>startResource</key>
<string />
<key>sebServerURL</key>
<string />
<key>hashedAdminPassword</key>
<string />
<key>allowQuit</key>
<true />
<key>ignoreExitKeys</key>
<true />
<key>hashedQuitPassword</key>
<string />
<key>exitKey1</key>
<integer>2</integer>
<key>exitKey2</key>
<integer>10</integer>
<key>exitKey3</key>
<integer>5</integer>
<key>sebMode</key>
<integer>0</integer>
<key>browserMessagingSocket</key>
<string>ws://localhost:8706</string>
<key>browserMessagingPingTime</key>
<integer>120000</integer>
<key>sebConfigPurpose</key>
<integer>0</integer>
<key>allowPreferencesWindow</key>
<true />
<key>useAsymmetricOnlyEncryption</key>
<false />
<key>browserViewMode</key>
<integer>0</integer>
<key>mainBrowserWindowWidth</key>
<string>100%</string>
<key>mainBrowserWindowHeight</key>
<string>100%</string>
<key>mainBrowserWindowPositioning</key>
<integer>1</integer>
<key>enableBrowserWindowToolbar</key>
<false />
<key>hideBrowserWindowToolbar</key>
<false />
<key>showMenuBar</key>
<false />
<key>showTaskBar</key>
<true />
<key>taskBarHeight</key>
<integer>40</integer>
<key>touchOptimized</key>
<false />
<key>enableZoomText</key>
<true />
<key>enableZoomPage</key>
<true />
<key>zoomMode</key>
<integer>0</integer>
<key>allowSpellCheck</key>
<false />
<key>allowDictionaryLookup</key>
<false />
<key>allowSpellCheckDictionary</key>
<array></array>
<key>additionalDictionaries</key>
<array></array>
<key>showReloadButton</key>
<true />
<key>showTime</key>
<true />
<key>showInputLanguage</key>
<true />
<key>enableTouchExit</key>
<false />
<key>oskBehavior</key>
<integer>2</integer>
<key>audioControlEnabled</key>
<true />
<key>audioMute</key>
<false />
<key>audioVolumeLevel</key>
<integer>25</integer>
<key>audioSetVolumeLevel</key>
<false />
<key>browserScreenKeyboard</key>
<false />
<key>newBrowserWindowByLinkPolicy</key>
<integer>2</integer>
<key>newBrowserWindowByScriptPolicy</key>
<integer>2</integer>
<key>newBrowserWindowByLinkBlockForeign</key>
<false />
<key>newBrowserWindowByScriptBlockForeign</key>
<false />
<key>newBrowserWindowByLinkWidth</key>
<string>1000</string>
<key>newBrowserWindowByLinkHeight</key>
<string>100%</string>
<key>newBrowserWindowByLinkPositioning</key>
<integer>2</integer>
<key>enablePlugIns</key>
<true />
<key>enableJava</key>
<false />
<key>enableJavaScript</key>
<true />
<key>blockPopUpWindows</key>
<false />
<key>allowVideoCapture</key>
<false />
<key>allowAudioCapture</key>
<false />
<key>allowBrowsingBackForward</key>
<false />
<key>newBrowserWindowNavigation</key>
<true />
<key>removeBrowserProfile</key>
<false />
<key>removeLocalStorage</key>
<false />
<key>enableSebBrowser</key>
<true />
<key>browserWindowAllowReload</key>
<true />
<key>newBrowserWindowAllowReload</key>
<true />
<key>showReloadWarning</key>
<true />
<key>newBrowserWindowShowReloadWarning</key>
<false />
<key>browserUserAgentWinDesktopMode</key>
<integer>0</integer>
<key>browserUserAgentWinDesktopModeCustom</key>
<string />
<key>browserUserAgentWinTouchMode</key>
<integer>0</integer>
<key>browserUserAgentWinTouchModeIPad</key>
<string>Mozilla/5.0 (iPad; CPU OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.3 Mobile/15E216 Safari/605.1.15</string>
<key>browserUserAgentWinTouchModeCustom</key>
<string />
<key>browserUserAgent</key>
<string />
<key>browserUserAgentMac</key>
<integer>0</integer>
<key>browserUserAgentMacCustom</key>
<string />
<key>browserWindowTitleSuffix</key>
<string />
<key>allowDownUploads</key>
<true />
<key>downloadDirectoryOSX</key>
<string>~/Downloads</string>
<key>downloadDirectoryWin</key>
<string />
<key>openDownloads</key>
<false />
<key>chooseFileToUploadPolicy</key>
<integer>0</integer>
<key>downloadPDFFiles</key>
<false />
<key>allowPDFPlugIn</key>
<false />
<key>downloadAndOpenSebConfig</key>
<true />
<key>backgroundOpenSEBConfig</key>
<false />
<key>examKeySalt</key>
<data>QJAqvg89YMP6JagAshUm6QqpqpsrVS9ZWUYjdZhfEao=</data>
<key>browserExamKey</key>
<string />
<key>browserURLSalt</key>
<true />
<key>sendBrowserExamKey</key>
<true />
<key>quitURL</key>
<string />
<key>quitURLConfirm</key>
<true />
<key>restartExamURL</key>
<string />
<key>restartExamUseStartURL</key>
<false />
<key>restartExamText</key>
<string />
<key>restartExamPasswordProtected</key>
<true />
<key>additionalResources</key>
<array></array>
<key>monitorProcesses</key>
<true />
<key>allowSwitchToApplications</key>
<false />
<key>allowFlashFullscreen</key>
<false />
<key>permittedProcesses</key>
<array>
<dict>
<key>active</key>
<true />
<key>autostart</key>
<true />
<key>iconInTaskbar</key>
<true />
<key>runInBackground</key>
<false />
<key>allowUserToChooseApp</key>
<false />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>title</key>
<string>SEB</string>
<key>description</key>
<string />
<key>executable</key>
<string>firefox.exe</string>
<key>originalName</key>
<string>firefox.exe</string>
<key>path</key>
<string>../xulrunner/</string>
<key>identifier</key>
<string>Firefox</string>
<key>windowHandlingProcess</key>
<string />
<key>arguments</key>
<array></array>
</dict>
</array>
<key>prohibitedProcesses</key>
<array>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>join.me</string>
<key>originalName</key>
<string>join.me</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RPCSuite</string>
<key>originalName</key>
<string>RPCSuite</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RPCService</string>
<key>originalName</key>
<string>RPCService</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>RemotePCDesktop</string>
<key>originalName</key>
<string>RemotePCDesktop</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>beamyourscreen-host</string>
<key>originalName</key>
<string>beamyourscreen-host</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>AeroAdmin</string>
<key>originalName</key>
<string>AeroAdmin</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>Mikogo-host</string>
<key>originalName</key>
<string>Mikogo-host</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>chromoting</string>
<key>originalName</key>
<string>chromoting</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncserverui</string>
<key>originalName</key>
<string>vncserverui</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncviewer</string>
<key>originalName</key>
<string>vncviewer</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>vncserver</string>
<key>originalName</key>
<string>vncserver</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>TeamViewer</string>
<key>originalName</key>
<string>TeamViewer</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>GotoMeetingWinStore</string>
<key>originalName</key>
<string>GotoMeetingWinStore</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>g2mcomm.exe</string>
<key>originalName</key>
<string>g2mcomm.exe</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>SkypeHost</string>
<key>originalName</key>
<string>SkypeHost</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
<dict>
<key>active</key>
<true />
<key>currentUser</key>
<true />
<key>strongKill</key>
<false />
<key>os</key>
<integer>1</integer>
<key>executable</key>
<string>Skype</string>
<key>originalName</key>
<string>Skype</string>
<key>description</key>
<string />
<key>identifier</key>
<string />
<key>windowHandlingProcess</key>
<string />
<key>user</key>
<string />
</dict>
</array>
<key>enableURLFilter</key>
<false />
<key>enableURLContentFilter</key>
<false />
<key>URLFilterRules</key>
<array></array>
<key>URLFilterEnable</key>
<false />
<key>URLFilterEnableContentFilter</key>
<false />
<key>blacklistURLFilter</key>
<string />
<key>whitelistURLFilter</key>
<string />
<key>urlFilterTrustedContent</key>
<true />
<key>urlFilterRegex</key>
<true />
<key>embeddedCertificates</key>
<array></array>
<key>pinEmbeddedCertificates</key>
<false />
<key>proxySettingsPolicy</key>
<integer>0</integer>
<key>proxies</key>
<dict>
<key>ExceptionsList</key>
<array></array>
<key>ExcludeSimpleHostnames</key>
<false />
<key>AutoDiscoveryEnabled</key>
<false />
<key>AutoConfigurationEnabled</key>
<false />
<key>AutoConfigurationJavaScript</key>
<string />
<key>AutoConfigurationURL</key>
<string />
<key>FTPPassive</key>
<true />
<key>HTTPEnable</key>
<false />
<key>HTTPPort</key>
<integer>80</integer>
<key>HTTPProxy</key>
<string />
<key>HTTPRequiresPassword</key>
<false />
<key>HTTPUsername</key>
<string />
<key>HTTPPassword</key>
<string />
<key>HTTPSEnable</key>
<false />
<key>HTTPSPort</key>
<integer>443</integer>
<key>HTTPSProxy</key>
<string />
<key>HTTPSRequiresPassword</key>
<false />
<key>HTTPSUsername</key>
<string />
<key>HTTPSPassword</key>
<string />
<key>FTPEnable</key>
<false />
<key>FTPPort</key>
<integer>21</integer>
<key>FTPProxy</key>
<string />
<key>FTPRequiresPassword</key>
<false />
<key>FTPUsername</key>
<string />
<key>FTPPassword</key>
<string />
<key>SOCKSEnable</key>
<false />
<key>SOCKSPort</key>
<integer>1080</integer>
<key>SOCKSProxy</key>
<string />
<key>SOCKSRequiresPassword</key>
<false />
<key>SOCKSUsername</key>
<string />
<key>SOCKSPassword</key>
<string />
<key>RTSPEnable</key>
<false />
<key>RTSPPort</key>
<integer>554</integer>
<key>RTSPProxy</key>
<string />
<key>RTSPRequiresPassword</key>
<false />
<key>RTSPUsername</key>
<string />
<key>RTSPPassword</key>
<string />
</dict>
<key>sebServicePolicy</key>
<integer>1</integer>
<key>allowVirtualMachine</key>
<true />
<key>allowScreenSharing</key>
<false />
<key>enablePrivateClipboard</key>
<true />
<key>createNewDesktop</key>
<true />
<key>killExplorerShell</key>
<false />
<key>enableLogging</key>
<true />
<key>logDirectoryOSX</key>
<string>~/Documents</string>
<key>logDirectoryWin</key>
<string />
<key>allowWlan</key>
<false />
<key>lockOnMessageSocketClose</key>
<true />
<key>minMacOSVersion</key>
<integer>4</integer>
<key>enableAppSwitcherCheck</key>
<true />
<key>forceAppFolderInstall</key>
<true />
<key>allowUserAppFolderInstall</key>
<false />
<key>allowSiri</key>
<false />
<key>allowDictation</key>
<false />
<key>detectStoppedProcess</key>
<true />
<key>allowDisplayMirroring</key>
<false />
<key>allowedDisplaysMaxNumber</key>
<integer>1</integer>
<key>allowedDisplayBuiltin</key>
<true />
<key>insideSebEnableSwitchUser</key>
<false />
<key>insideSebEnableLockThisComputer</key>
<false />
<key>insideSebEnableChangeAPassword</key>
<false />
<key>insideSebEnableStartTaskManager</key>
<false />
<key>insideSebEnableLogOff</key>
<false />
<key>insideSebEnableShutDown</key>
<false />
<key>insideSebEnableEaseOfAccess</key>
<false />
<key>insideSebEnableVmWareClientShade</key>
<false />
<key>insideSebEnableEnableNetworkConnectionSelector</key>
<false />
<key>hookKeys</key>
<true />
<key>enableEsc</key>
<true />
<key>enableCtrlEsc</key>
<false />
<key>enableAltEsc</key>
<false />
<key>enableAltTab</key>
<true />
<key>enableAltF4</key>
<false />
<key>enableStartMenu</key>
<false />
<key>enableRightMouse</key>
<true />
<key>enablePrintScreen</key>
<false />
<key>enableAltMouseWheel</key>
<false />
<key>enableF1</key>
<true />
<key>enableF2</key>
<true />
<key>enableF3</key>
<true />
<key>enableF4</key>
<true />
<key>enableF5</key>
<true />
<key>enableF6</key>
<true />
<key>enableF7</key>
<true />
<key>enableF8</key>
<true />
<key>enableF9</key>
<true />
<key>enableF10</key>
<true />
<key>enableF11</key>
<true />
<key>enableF12</key>
<true />
</dict>
</plist>
@@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Behat data generator the quizaccess_seb plugin.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Behat data generator for quizaccess_seb.
*
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_quizaccess_seb_generator extends behat_generator_base {
/**
* Get a list of the entities that can be created.
* @return array entity name => information about how to generate.
*/
protected function get_creatable_entities(): array {
return [
'seb templates' => [
'singular' => 'seb template',
'datagenerator' => 'template',
'required' => ['name'],
],
];
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Data generator the quizaccess_seb plugin.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Data generator the quizaccess_seb plugin.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quizaccess_seb_generator extends component_generator_base {
/**
* Create SEB template.
*
* @param array $data Template data.
* @return \quizaccess_seb\template
*/
public function create_template(array $data) {
global $CFG;
if (!isset($data['name'])) {
$data['name'] = 'test';
}
if (!isset($data['content'])) {
$data['content'] = file_get_contents(
$CFG->dirroot . '/mod/quiz/accessrule/seb/tests/fixtures/unencrypted.seb'
);
}
if (!isset($data['enabled'])) {
$data['enabled'] = 1;
}
$template = new \quizaccess_seb\template();
$template->set('content', $data['content']);
$template->set('name', $data['name']);
$template->set('enabled', $data['enabled']);
$template->save();
return $template;
}
}
@@ -0,0 +1,188 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/test_helper_trait.php');
/**
* PHPUnit tests for helper class.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class helper_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/**
* Test that we can check valid seb string.
*/
public function test_is_valid_seb_config(): void {
$validseb = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array>"
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$invalidseb = 'Invalid seb';
$emptyseb = '';
$this->assertTrue(\quizaccess_seb\helper::is_valid_seb_config($validseb));
$this->assertFalse(\quizaccess_seb\helper::is_valid_seb_config($invalidseb));
$this->assertFalse(\quizaccess_seb\helper::is_valid_seb_config($emptyseb));
}
/**
* Test that we can get seb file headers.
*/
public function test_get_seb_file_headers(): void {
$expiretime = 1582767914;
$headers = \quizaccess_seb\helper::get_seb_file_headers($expiretime);
$this->assertCount(5, $headers);
$this->assertEquals('Cache-Control: private, max-age=1, no-transform', $headers[0]);
$this->assertEquals('Expires: Thu, 27 Feb 2020 01:45:14 GMT', $headers[1]);
$this->assertEquals('Pragma: no-cache', $headers[2]);
$this->assertEquals('Content-Disposition: attachment; filename=config.seb', $headers[3]);
$this->assertEquals('Content-Type: application/seb', $headers[4]);
}
/**
* Test that the course module must exist to get a seb config file content.
*/
public function test_can_not_get_config_content_with_invalid_cmid(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->setUser($user); // Log user in.
$this->expectException(\dml_exception::class);
$this->expectExceptionMessage("Can't find data record in database. (SELECT cm.*, m.name, md.name AS modname \n"
. " FROM {course_modules} cm\n"
. " JOIN {modules} md ON md.id = cm.module\n"
. " JOIN {quiz} m ON m.id = cm.instance\n"
. " \n"
. " WHERE cm.id = :cmid AND md.name = :modulename\n"
. " \n"
. "[array (\n"
. " 'cmid' => '999',\n"
. " 'modulename' => 'quiz',\n"
.')])');
\quizaccess_seb\helper::get_seb_config_content('999');
}
/**
* Test that the user must be enrolled to get seb config content.
*/
public function test_can_not_get_config_content_when_user_not_enrolled_in_course(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->create_test_quiz($course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$user = $this->getDataGenerator()->create_user();
$this->setUser($user); // Log user in.
$this->expectException(\moodle_exception::class);
$this->expectExceptionMessage('Unsupported redirect detected, script execution terminated');
\quizaccess_seb\helper::get_seb_config_content($quiz->cmid);
}
/**
* Test that if SEB quiz settings can't be found, a seb config content won't be provided.
*/
public function test_can_not_get_config_content_if_config_not_found_for_cmid(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->create_test_quiz($course);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->setUser($user); // Log user in.
$this->expectException(\moodle_exception::class);
$this->expectExceptionMessage("No SEB config could be found for quiz with cmid: $quiz->cmid");
\quizaccess_seb\helper::get_seb_config_content($quiz->cmid);
}
/**
* That that if config is empty for a quiz, a seb config content won't be provided.
*/
public function test_can_not_get_config_content_if_config_empty(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->create_test_quiz($course, \quizaccess_seb\settings_provider::USE_SEB_NO);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->setUser($user); // Log user in.
$this->expectException(\moodle_exception::class);
$this->expectExceptionMessage("No SEB config could be found for quiz with cmid: $quiz->cmid");
\quizaccess_seb\helper::get_seb_config_content($quiz->cmid);
}
/**
* Test config content is provided successfully.
*/
public function test_config_provided(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$quiz = $this->create_test_quiz($course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$this->setUser($user); // Log user in.
$config = \quizaccess_seb\helper::get_seb_config_content($quiz->cmid);
$url = new \moodle_url("/mod/quiz/view.php", ['id' => $quiz->cmid]);
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key>"
. "<false/><key>showReloadButton</key><true/><key>showTime</key><true/><key>showInputLanguage</key>"
. "<true/><key>allowQuit</key><true/><key>quitURLConfirm</key><true/><key>audioControlEnabled</key>"
. "<false/><key>audioMute</key><false/><key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key>"
. "<true/><key>URLFilterEnable</key><false/><key>URLFilterEnableContentFilter</key><false/>"
. "<key>URLFilterRules</key><array/><key>startURL</key><string>$url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
}
}
@@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
/**
* PHPUnit tests for hideif_rule.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class hideif_rule_test extends \advanced_testcase {
/**
* Test that can get rule data.
*/
public function test_can_get_what_set_in_constructor(): void {
$rule = new hideif_rule('Element', 'Dependant', 'eq', 'Value');
$this->assertEquals('Element', $rule->get_element());
$this->assertEquals('Dependant', $rule->get_dependantname());
$this->assertEquals('eq', $rule->get_condition());
$this->assertEquals('Value', $rule->get_dependantvalue());
}
}
@@ -0,0 +1,93 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
/**
* PHPUnit tests for link_generator.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class link_generator_test extends \advanced_testcase {
/**
* Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
}
/**
* Test that a http link is generated correctly.
*/
public function test_http_link_generated(): void {
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
$this->assertEquals(
"http://www.example.com/moodle/mod/quiz/accessrule/seb/config.php?cmid=$quiz->cmid",
link_generator::get_link($quiz->cmid, false, false));
}
/**
* Test that a http link is generated correctly.
*/
public function test_https_link_generated(): void {
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
$this->assertEquals(
"https://www.example.com/moodle/mod/quiz/accessrule/seb/config.php?cmid=$quiz->cmid",
link_generator::get_link($quiz->cmid, false));
}
/**
* Test that a seb link is generated correctly.
*/
public function test_seb_link_generated(): void {
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
$this->assertEquals(
"seb://www.example.com/moodle/mod/quiz/accessrule/seb/config.php?cmid=$quiz->cmid",
link_generator::get_link($quiz->cmid, true, false));
}
/**
* Test that a sebs link is generated correctly.
*/
public function test_sebs_link_generated(): void {
$course = $this->getDataGenerator()->create_course();
$quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
$this->assertEquals(
"sebs://www.example.com/moodle/mod/quiz/accessrule/seb/config.php?cmid=$quiz->cmid",
link_generator::get_link($quiz->cmid, true));
}
/**
* Test that link_generator can't not be instantiated with fake course module.
*/
public function test_course_module_does_not_exist(): void {
$this->expectException(\dml_exception::class);
$this->expectExceptionMessageMatches("/^Can't find data record in database.*/");
$generator = link_generator::get_link(123456, false);
}
}
@@ -0,0 +1,239 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* PHPUnit tests for privacy provider.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace quizaccess_seb\privacy;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
use core_privacy\tests\request\approved_contextlist;
use core_privacy\tests\provider_testcase;
use quizaccess_seb\privacy\provider;
use quizaccess_seb\seb_quiz_settings;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../test_helper_trait.php');
/**
* PHPUnit tests for privacy provider.
*
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
use \quizaccess_seb_test_helper_trait;
/**
* Setup the user, the quiz and ensure that the user is the last user to modify the SEB quiz settings.
*/
public function setup_test_data() {
$this->resetAfterTest();
$this->setAdminUser();
$this->course = $this->getDataGenerator()->create_course();
$this->quiz = $this->create_test_quiz($this->course, \quizaccess_seb\settings_provider::USE_SEB_CONFIG_MANUALLY);
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$template = $this->create_template();
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Modify settings so usermodified is updated. This is the user data we are testing for.
$quizsettings->set('requiresafeexambrowser', \quizaccess_seb\settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$quizsettings->save();
}
/**
* Test that the module context for a user who last modified the module is retrieved.
*/
public function test_get_contexts_for_userid(): void {
$this->setup_test_data();
$contexts = provider::get_contexts_for_userid($this->user->id);
$contextids = $contexts->get_contextids();
$this->assertEquals(\context_module::instance($this->quiz->cmid)->id, reset($contextids));
}
/**
* That that no module context is found for a user who has not modified any quiz settings.
*/
public function test_get_no_contexts_for_userid(): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$contexts = provider::get_contexts_for_userid($user->id);
$contextids = $contexts->get_contextids();
$this->assertEmpty($contextids);
}
/**
* Test that user data is exported in format expected.
*/
public function test_export_user_data(): void {
$this->setup_test_data();
$context = \context_module::instance($this->quiz->cmid);
// Add another course_module of a differenty type - doing this lets us
// test that the data exporter is correctly limiting its selection to
// the quiz and not anything with the same instance id.
// (note this is only effective with databases not using fed (+1000) sequences
// per table, like postgres and mysql do, rendering this useless. In any
// case better to have the situation covered by some DBs,
// like sqlsrv or oracle than by none).
$this->getDataGenerator()->create_module('label', ['course' => $this->course->id]);
$contextlist = provider::get_contexts_for_userid($this->user->id);
$approvedcontextlist = new approved_contextlist(
$this->user,
'quizaccess_seb',
$contextlist->get_contextids()
);
writer::reset();
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = writer::with_context($context);
$this->assertFalse($writer->has_any_data());
provider::export_user_data($approvedcontextlist);
$index = '1'; // Get first data returned from the quizsettings table metadata.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
seb_quiz_settings::TABLE,
$index,
]);
$this->assertNotEmpty($data);
$index = '1'; // Get first data returned from the template table metadata.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
\quizaccess_seb\template::TABLE,
$index,
]);
$this->assertNotEmpty($data);
$index = '2'; // There should not be more than one instance with data.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
seb_quiz_settings::TABLE,
$index,
]);
$this->assertEmpty($data);
$index = '2'; // There should not be more than one instance with data.
$data = $writer->get_data([
get_string('pluginname', 'quizaccess_seb'),
\quizaccess_seb\template::TABLE,
$index,
]);
$this->assertEmpty($data);
}
/**
* Test that a userlist with module context is populated by usermodified user.
*/
public function test_get_users_in_context(): void {
$this->setup_test_data();
// Create empty userlist with quiz module context.
$userlist = new userlist(\context_module::instance($this->quiz->cmid), 'quizaccess_seb');
// Test that the userlist is populated with expected user/s.
provider::get_users_in_context($userlist);
$this->assertEquals($this->user->id, $userlist->get_userids()[0]);
}
/**
* Test that data is deleted for a list of users.
*/
public function test_delete_data_for_users(): void {
$this->setup_test_data();
$approveduserlist = new approved_userlist(\context_module::instance($this->quiz->cmid),
'quizaccess_seb', [$this->user->id]);
// Test data exists.
$this->assertNotEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
// Test data is deleted.
provider::delete_data_for_users($approveduserlist);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEmpty($record->get('usermodified'));
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertEmpty($template->get('usermodified'));
}
/**
* Test that data is deleted for a list of contexts.
*/
public function test_delete_data_for_user(): void {
$this->setup_test_data();
$context = \context_module::instance($this->quiz->cmid);
$approvedcontextlist = new approved_contextlist($this->user,
'quizaccess_seb', [$context->id]);
// Test data exists.
$this->assertNotEmpty(seb_quiz_settings::get_record(['quizid' => $this->quiz->id]));
// Test data is deleted.
provider::delete_data_for_user($approvedcontextlist);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEmpty($record->get('usermodified'));
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertEmpty($template->get('usermodified'));
}
/**
* Test that data is deleted for a single context.
*/
public function test_delete_data_for_all_users_in_context(): void {
$this->setup_test_data();
$context = \context_module::instance($this->quiz->cmid);
// Test data exists.
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertNotEmpty($record->get('usermodified'));
$this->assertNotEmpty($template->get('usermodified'));
// Test data is deleted.
provider::delete_data_for_all_users_in_context($context);
$record = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$template = \quizaccess_seb\template::get_record(['id' => $record->get('templateid')]);
$this->assertEmpty($record->get('usermodified'));
$this->assertEmpty($template->get('usermodified'));
}
}
@@ -0,0 +1,423 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
/**
* PHPUnit for property_list class.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class property_list_test extends \advanced_testcase {
/**
* Test that an empty PList with a root dictionary is created.
*/
public function test_create_empty_plist(): void {
$emptyplist = new property_list();
$xml = trim($emptyplist->to_xml());
$this->assertEquals('<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict/></plist>', $xml);
}
/**
* Test that a Plist is constructed from an XML string.
*/
public function test_construct_plist_from_xml(): void {
$xml = $this->get_plist_xml_header()
. "<key>testKey</key>"
. "<string>testValue</string>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$generatedxml = trim($plist->to_xml());
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
}
/**
* Test that an element can be added to the root dictionary.
*/
public function test_add_element_to_root(): void {
$plist = new property_list();
$newelement = new \CFPropertyList\CFString('testValue');
$plist->add_element_to_root('testKey', $newelement);
$generatedxml = trim($plist->to_xml());
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
}
/**
* Test that an element's value can be retrieved.
*/
public function test_get_element_value(): void {
$xml = $this->get_plist_xml_header()
. "<key>testKey</key>"
. "<string>testValue</string>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$this->assertEquals('testValue', $plist->get_element_value('testKey'));
}
/**
* Test that an element's value can be retrieved.
*/
public function test_get_element_value_if_not_exists(): void {
$plist = new property_list();
$this->assertEmpty($plist->get_element_value('testKey'));
}
/**
* Test an element's value can be retrieved if it is an array.
*/
public function test_get_element_value_if_array(): void {
$xml = $this->get_plist_xml_header()
. "<key>testDict</key>"
. "<dict>"
. "<key>testKey</key>"
. "<string>testValue</string>"
. "</dict>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$this->assertEquals(['testKey' => 'testValue'], $plist->get_element_value('testDict'));
}
/**
* Test that a element's value can be updated that is not an array or dictionary.
*
* @param string $xml XML to create PList.
* @param string $key Key of element to try and update.
* @param mixed $value Value to try to update with.
*
* @dataProvider good_update_data_provider
*/
public function test_updating_element_value($xml, $key, $value): void {
$xml = $this->get_plist_xml_header()
. $xml
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$plist->update_element_value($key, $value);
$this->assertEquals($value, $plist->get_element_value($key));
}
/**
* Test that a element's value can be updated that is not an array or dictionary.
*
* @param string $xml XML to create PList.
* @param string $key Key of element to try and update.
* @param mixed $value Bad value to try to update with.
* @param mixed $expected Expected value of element after update is called.
* @param string $exceptionmessage Message of exception expected to be thrown.
* @dataProvider bad_update_data_provider
*/
public function test_updating_element_value_with_bad_data(string $xml, string $key, $value, $expected, $exceptionmessage): void {
$xml = $this->get_plist_xml_header()
. $xml
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessage($exceptionmessage);
$plist->update_element_value($key, $value);
$plistarray = json_decode($plist->to_json()); // Export elements.
$this->assertEquals($expected, $plistarray->$key);
}
/**
* Test that a dictionary can have it's value (array) updated.
*/
public function test_updating_element_array_if_dictionary(): void {
$xml = $this->get_plist_xml_header()
. "<key>testDict</key>"
. "<dict>"
. "<key>testKey</key>"
. "<string>testValue</string>"
. "</dict>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$plist->update_element_array('testDict', ['newKey' => new \CFPropertyList\CFString('newValue')]);
$this->assertEquals(['newKey' => 'newValue'], $plist->get_element_value('testDict'));
}
/**
* Test that a dictionary can have it's value (array) updated.
*/
public function test_updating_element_array_if_dictionary_with_bad_data(): void {
$xml = $this->get_plist_xml_header()
. "<key>testDict</key>"
. "<dict>"
. "<key>testKey</key>"
. "<string>testValue</string>"
. "</dict>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$this->expectException(\invalid_parameter_exception::class);
$this->expectExceptionMessage('New array must only contain CFType objects.');
$plist->update_element_array('testDict', [false]);
$this->assertEquals(['testKey' => 'testValue'], $plist->get_element_value('testDict'));
$this->assertDebuggingCalled('property_list: If updating an array in PList, it must only contain CFType objects.',
DEBUG_DEVELOPER);
}
/**
* Test that an element can be deleted.
*/
public function test_delete_element(): void {
$xml = $this->get_plist_xml_header()
. "<key>testKey</key>"
. "<string>testValue</string>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$plist->delete_element('testKey');
$generatedxml = trim($plist->to_xml());
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict/></plist>", $generatedxml);
}
/**
* Test that an element can be deleted.
*/
public function test_delete_element_if_not_exists(): void {
$xml = $this->get_plist_xml_header()
. "<key>testKey</key>"
. "<string>testValue</string>"
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$plist->delete_element('nonExistentKey');
$generatedxml = trim($plist->to_xml());
// The xml should be unaltered.
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
}
/**
* Test that json is exported correctly according to SEB Config Key requirements.
*
* @param string $xml PList XML used to generate CFPropertyList.
* @param string $expectedjson Expected JSON output.
*
* @dataProvider json_data_provider
*/
public function test_export_to_json($xml, $expectedjson): void {
$xml = $this->get_plist_xml_header()
. $xml
. $this->get_plist_xml_footer();
$plist = new property_list($xml);
$generatedjson = $plist->to_json();
$this->assertEquals($expectedjson, $generatedjson);
}
/**
* Test that the xml is exported to JSON from a real SEB config file. Expected JSON extracted from SEB logs.
*/
public function test_export_to_json_full_file(): void {
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted_mac_001.seb');
$plist = new property_list($xml);
$plist->delete_element('originatorVersion'); // JSON should not contain originatorVersion key.
$generatedjson = $plist->to_json();
$json = trim(file_get_contents(__DIR__ . '/fixtures/JSON_unencrypted_mac_001.txt'));
$this->assertEquals($json, $generatedjson);
}
/**
* Test the set_or_update_value function.
*/
public function test_set_or_update_value(): void {
$plist = new property_list();
$this->assertEmpty($plist->get_element_value('string'));
$this->assertEmpty($plist->get_element_value('bool'));
$this->assertEmpty($plist->get_element_value('number'));
// Setting values.
$plist->set_or_update_value('string', new \CFPropertyList\CFString('initial string'));
$plist->set_or_update_value('bool', new \CFPropertyList\CFBoolean(true));
$plist->set_or_update_value('number', new \CFPropertyList\CFNumber('10'));
$this->assertEquals('initial string', $plist->get_element_value('string'));
$this->assertEquals(true, $plist->get_element_value('bool'));
$this->assertEquals(10, $plist->get_element_value('number'));
// Updating values.
$plist->set_or_update_value('string', new \CFPropertyList\CFString('new string'));
$plist->set_or_update_value('bool', new \CFPropertyList\CFBoolean(false));
$plist->set_or_update_value('number', new \CFPropertyList\CFNumber('42'));
$this->assertEquals('new string', $plist->get_element_value('string'));
$this->assertEquals(false, $plist->get_element_value('bool'));
$this->assertEquals(42, $plist->get_element_value('number'));
// Type exception.
$this->expectException(\TypeError::class);
$plist->set_or_update_value('someKey', 'We really need to pass in CFTypes here');
}
/**
* Get a valid PList header. Must also use footer.
*
* @return string
*/
private function get_plist_xml_header(): string {
return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
. "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\">\n"
. " <dict>";
}
/**
* Get a valid PList footer. Must also use header.
*
* @return string
*/
private function get_plist_xml_footer(): string {
return " </dict>\n"
. "</plist>";
}
/**
* Data provider for good data on update.
*
* @return array Array with test data.
*/
public function good_update_data_provider(): array {
return [
'Update string' => ['<key>testKey</key><string>testValue</string>', 'testKey', 'newValue'],
'Update bool' => ['<key>testKey</key><true/>', 'testKey', false],
'Update number' => ['<key>testKey</key><real>888</real>', 'testKey', 123.4],
];
}
/**
* Data provider for bad data on update.
*
* @return array Array with test data.
*/
public function bad_update_data_provider(): array {
return [
'Update string with bool' => ['<key>testKey</key><string>testValue</string>', 'testKey', true, 'testValue',
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFString'],
'Update string with number' => ['<key>testKey</key><string>testValue</string>', 'testKey', 999, 'testValue',
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFString'],
'Update string with null' => ['<key>testKey</key><string>testValue</string>', 'testKey', null, 'testValue',
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFString'],
'Update string with array' => ['<key>testKey</key><string>testValue</string>', 'testKey', ['arrayValue'], 'testValue',
'Use update_element_array to update a collection.'],
'Update bool with string' => ['<key>testKey</key><true/>', 'testKey', 'testValue', true,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFBool'],
'Update bool with number' => ['<key>testKey</key><true/>', 'testKey', 999, true,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFBool'],
'Update bool with null' => ['<key>testKey</key><true/>', 'testKey', null, true,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFBool'],
'Update bool with array' => ['<key>testKey</key><true/>', 'testKey', ['testValue'], true,
'Use update_element_array to update a collection.'],
'Update number with string' => ['<key>testKey</key><real>888</real>', 'testKey', 'string', 888,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFNumber'],
'Update number with bool' => ['<key>testKey</key><real>888</real>', 'testKey', true, 888,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFNumber'],
'Update number with null' => ['<key>testKey</key><real>888</real>', 'testKey', null, 888,
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFNumber'],
'Update number with array' => ['<key>testKey</key><real>888</real>', 'testKey', ['testValue'], 888,
'Use update_element_array to update a collection.'],
'Update date with string' => ['<key>testKey</key><date>1940-10-09T22:13:56Z</date>', 'testKey', 'string',
'1940-10-10T06:13:56+08:00',
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFDate'],
'Update data with number' => ['<key>testKey</key><data>testData</data>', 'testKey', 789, 'testData',
'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
. 'or value type does not match element type: CFPropertyList\CFData'],
];
}
/**
* Data provider for expected JSON from PList.
*
* Examples extracted from requirements listed in SEB Config Key documents.
* https://safeexambrowser.org/developer/seb-config-key.html
*
* 1. Date should be in ISO 8601 format.
* 2. Data should be base 64 encoded.
* 3. String should be UTF-8 encoded.
* 4, 5, 6, 7. No requirements for bools, arrays or dicts.
* 8. Empty dicts should not be included.
* 9. JSON key ordering should be case insensitive, and use string ordering.
* 10. URL forward slashes should not be escaped.
*
* @return array
*/
public function json_data_provider(): array {
$data = "blahblah";
$base64data = base64_encode($data);
return [
'date' => ["<key>date</key><date>1940-10-09T22:13:56Z</date>", "{\"date\":\"1940-10-09T22:13:56+00:00\"}"],
'data' => ["<key>data</key><data>$base64data</data>", "{\"data\":\"$base64data\"}"],
'string' => ["<key>string</key><string>hello wörld</string>", "{\"string\":\"hello wörld\"}"],
'string with 1 backslash' => ["<key>string</key><string>ws:\localhost</string>", "{\"string\":\"ws:\localhost\"}"],
'string with 2 backslashes' => ["<key>string</key><string>ws:\\localhost</string>",
'{"string":"ws:\\localhost"}'],
'string with 3 backslashes' => ["<key>string</key><string>ws:\\\localhost</string>",
'{"string":"ws:\\\localhost"}'],
'string with 4 backslashes' => ["<key>string</key><string>ws:\\\\localhost</string>",
'{"string":"ws:\\\\localhost"}'],
'string with 5 backslashes' => ["<key>string</key><string>ws:\\\\\localhost</string>",
'{"string":"ws:\\\\\localhost"}'],
'bool' => ["<key>bool</key><true/>", "{\"bool\":true}"],
'array' => ["<key>array</key><array><key>arraybool</key><false/><key>arraybool2</key><true/></array>"
, "{\"array\":[false,true]}"],
'empty array' => ["<key>bool</key><true/><key>array</key><array/>"
, "{\"array\":[],\"bool\":true}"],
'dict' => ["<key>dict</key><dict><key>dictbool</key><false/><key>dictbool2</key><true/></dict>"
, "{\"dict\":{\"dictbool\":false,\"dictbool2\":true}}"],
'empty dict' => ["<key>bool</key><true/><key>emptydict</key><dict/>", "{\"bool\":true}"],
'unordered elements' => ["<key>testKey</key>"
. "<string>testValue</string>"
. "<key>allowWLAN</key>"
. "<string>testValue2</string>"
. "<key>allowWlan</key>"
. "<string>testValue3</string>"
, "{\"allowWlan\":\"testValue3\",\"allowWLAN\":\"testValue2\",\"testKey\":\"testValue\"}"],
'url' => ["<key>url</key><string>http://test.com</string>", "{\"url\":\"http://test.com\"}"],
'assoc dict' => ["<key>dict</key><dict><key>banana</key><false/><key>apple</key><true/></dict>",
"{\"dict\":{\"apple\":true,\"banana\":false}}"],
'seq array' => ["<key>array</key><array><key>1</key><false/><key>2</key><true/>
<key>3</key><true/><key>4</key><true/><key>5</key><true/><key>6</key><true/>
<key>7</key><true/><key>8</key><true/><key>9</key><true/><key>10</key><true/></array>",
"{\"array\":[false,true,true,true,true,true,true,true,true,true]}"],
];
}
}
@@ -0,0 +1,853 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
use context_module;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/test_helper_trait.php');
/**
* PHPUnit tests for seb_quiz_settings class.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_settings_test extends \advanced_testcase {
use \quizaccess_seb_test_helper_trait;
/** @var context_module $context Test context. */
protected $context;
/** @var moodle_url $url Test quiz URL. */
protected $url;
/**
* Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
$this->course = $this->getDataGenerator()->create_course();
$this->quiz = $this->getDataGenerator()->create_module('quiz', [
'course' => $this->course->id,
'seb_requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
]);
$this->context = \context_module::instance($this->quiz->cmid);
$this->url = new \moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
}
/**
* Test that config is generated immediately prior to saving quiz settings.
*/
public function test_config_is_created_from_quiz_settings(): void {
// Test settings to populate the in the object.
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
]);
// Obtain the existing record that is created when using a generator.
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Update the settings with values from the test function.
$quizsettings->from_record($settings);
$quizsettings->save();
$config = $quizsettings->get_config();
$this->assertEquals(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/><key>allowPreferencesWindow</key><false/></dict></plist>\n",
$config);
}
/**
* Test that config string gets updated from quiz settings.
*/
public function test_config_is_updated_from_quiz_settings(): void {
// Test settings to populate the in the object.
$settings = $this->get_test_settings([
'quizid' => $this->quiz->id,
'cmid' => $this->quiz->cmid,
]);
// Obtain the existing record that is created when using a generator.
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
// Update the settings with values from the test function.
$quizsettings->from_record($settings);
$quizsettings->save();
$config = $quizsettings->get_config();
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
$quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
$quizsettings->save();
$config = $quizsettings->get_config();
$this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><true/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
}
/**
* Test that config key is generated immediately prior to saving quiz settings.
*/
public function test_config_key_is_created_from_quiz_settings(): void {
$settings = $this->get_test_settings();
$quizsettings = new seb_quiz_settings(0, $settings);
$configkey = $quizsettings->get_config_key();
$this->assertEquals("65ff7a3b8aec80e58fbe2e7968826c33cbf0ac444a748055ebe665829cbf4201",
$configkey
);
}
/**
* Test that config key is generated immediately prior to saving quiz settings.
*/
public function test_config_key_is_updated_from_quiz_settings(): void {
$settings = $this->get_test_settings();
$quizsettings = new seb_quiz_settings(0, $settings);
$configkey = $quizsettings->get_config_key();
$this->assertEquals("65ff7a3b8aec80e58fbe2e7968826c33cbf0ac444a748055ebe665829cbf4201",
$configkey);
$quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
$configkey = $quizsettings->get_config_key();
$this->assertEquals("d975b8a2ec4472495a8be7c64d7c8cc960dbb62472d5e88a8847ac0e5d77e533",
$configkey);
}
/**
* Test that different URL filter expressions are turned into config XML.
*
* @param \stdClass $settings Quiz settings
* @param string $expectedxml SEB Config XML.
*
* @dataProvider filter_rules_provider
*/
public function test_filter_rules_added_to_config(\stdClass $settings, string $expectedxml): void {
$quizsettings = new seb_quiz_settings(0, $settings);
$config = $quizsettings->get_config();
$this->assertEquals($expectedxml, $config);
}
/**
* Test that browser keys are validated and retrieved as an array instead of string.
*/
public function test_browser_exam_keys_are_retrieved_as_array(): void {
$quizsettings = new seb_quiz_settings();
$quizsettings->set('allowedbrowserexamkeys', "one two,three\nfour");
$retrievedkeys = $quizsettings->get('allowedbrowserexamkeys');
$this->assertEquals(['one', 'two', 'three', 'four'], $retrievedkeys);
}
/**
* Test validation of Browser Exam Keys.
*
* @param string $bek Browser Exam Key.
* @param string $expectederrorstring Expected error.
*
* @dataProvider bad_browser_exam_key_provider
*/
public function test_browser_exam_keys_validation_errors($bek, $expectederrorstring): void {
$quizsettings = new seb_quiz_settings();
$quizsettings->set('allowedbrowserexamkeys', $bek);
$quizsettings->validate();
$errors = $quizsettings->get_errors();
$this->assertContainsEquals($expectederrorstring, $errors);
}
/**
* Test that uploaded seb file gets converted to config string.
*/
public function test_config_file_uploaded_converted_to_config(): void {
$url = new \moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
. "<key>allowWlan</key><false/><key>startURL</key><string>$url</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer></dict></plist>\n";
$itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->save();
$config = $quizsettings->get_config();
$this->assertEquals($xml, $config);
}
/**
* Test test_no_config_file_uploaded
*/
public function test_no_config_file_uploaded(): void {
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$cmid = $quizsettings->get('cmid');
$this->expectException(\moodle_exception::class);
$this->expectExceptionMessage("No uploaded SEB config file could be found for quiz with cmid: {$cmid}");
$quizsettings->get_config();
}
/**
* A helper function to build a config file.
*
* @param mixed $allowuserquitseb Required allowQuit setting.
* @param mixed $quitpassword Required hashedQuitPassword setting.
*
* @return string
*/
protected function get_config_xml($allowuserquitseb = null, $quitpassword = null) {
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>allowWlan</key><false/><key>startURL</key>"
. "<string>https://safeexambrowser.org/start</string>"
. "<key>sendBrowserExamKey</key><true/>";
if (!is_null($allowuserquitseb)) {
$allowuserquitseb = empty($allowuserquitseb) ? 'false' : 'true';
$xml .= "<key>allowQuit</key><{$allowuserquitseb}/>";
}
if (!is_null($quitpassword)) {
$xml .= "<key>hashedQuitPassword</key><string>{$quitpassword}</string>";
}
$xml .= "</dict></plist>\n";
return $xml;
}
/**
* Test using USE_SEB_TEMPLATE and have it override settings from the template when they are set.
*/
public function test_using_seb_template_override_settings_when_they_set_in_template(): void {
$xml = $this->get_config_xml(true, 'password');
$template = $this->create_template($xml);
$this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
$this->assertStringContainsString("<key>allowQuit</key><true/>", $template->get('content'));
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$quizsettings->set('allowuserquitseb', 1);
$quizsettings->save();
$this->assertStringContainsString(
"<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
$quizsettings->get_config()
);
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
$quizsettings->set('quitpassword', 'new password');
$quizsettings->save();
$hashedpassword = hash('SHA256', 'new password');
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
}
/**
* Test using USE_SEB_TEMPLATE and have it override settings from the template when they are not set.
*/
public function test_using_seb_template_override_settings_when_not_set_in_template(): void {
$xml = $this->get_config_xml();
$template = $this->create_template($xml);
$this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
$this->assertStringNotContainsString("<key>allowQuit</key><true/>", $template->get('content'));
$this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$quizsettings->set('allowuserquitseb', 1);
$quizsettings->save();
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
$quizsettings->set('quitpassword', 'new password');
$quizsettings->save();
$hashedpassword = hash('SHA256', 'new password');
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
}
/**
* Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are set.
*/
public function test_using_own_config_settings_are_not_overridden_if_set(): void {
$xml = $this->get_config_xml(true, 'password');
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringContainsString(
"<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
$quizsettings->get_config()
);
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
$quizsettings->set('quitpassword', 'new password');
$quizsettings->save();
$hashedpassword = hash('SHA256', 'new password');
$this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
$this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
}
/**
* Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are not set.
*/
public function test_using_own_config_settings_are_not_overridden_if_not_set(): void {
$xml = $this->get_config_xml();
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings->set('allowuserquitseb', 1);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringContainsString(
"<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
$quizsettings->get_config()
);
$this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
$quizsettings->set('quitpassword', 'new password');
$quizsettings->save();
$this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
$quizsettings->set('allowuserquitseb', 0);
$quizsettings->set('quitpassword', '');
$quizsettings->save();
$this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
$this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
}
/**
* Test using USE_SEB_TEMPLATE populates the linkquitseb setting if a quitURL is found.
*/
public function test_template_has_quit_url_set(): void {
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
. "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$template = $this->create_template($xml);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
$quizsettings->set('templateid', $template->get('id'));
$this->assertEmpty($quizsettings->get('linkquitseb'));
$quizsettings->save();
$this->assertNotEmpty($quizsettings->get('linkquitseb'));
$this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
}
/**
* Test using USE_SEB_UPLOAD_CONFIG populates the linkquitseb setting if a quitURL is found.
*/
public function test_config_file_uploaded_has_quit_url_set(): void {
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
. "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$this->assertEmpty($quizsettings->get('linkquitseb'));
$quizsettings->save();
$this->assertNotEmpty($quizsettings->get('linkquitseb'));
$this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
}
/**
* Test template id set correctly.
*/
public function test_templateid_set_correctly_when_save_settings(): void {
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
$template = $this->create_template();
$templateid = $template->get('id');
// Initially set to USE_SEB_TEMPLATE with a template id.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($templateid, $quizsettings->get('templateid'));
// Case for USE_SEB_NO, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_NO);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
// Case for USE_SEB_CONFIG_MANUALLY, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CONFIG_MANUALLY);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
// Case for USE_SEB_CLIENT_CONFIG, ensure template id reverts to 0.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CLIENT_CONFIG);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Reverting back to USE_SEB_TEMPLATE.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
// Case for USE_SEB_UPLOAD_CONFIG, ensure template id reverts to 0.
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_UPLOAD_CONFIG);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals(0, $quizsettings->get('templateid'));
// Case for USE_SEB_TEMPLATE, ensure template id is correct.
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($templateid, $quizsettings->get('templateid'));
}
/**
* Helper function in tests to set USE_SEB_TEMPLATE and a template id on the quiz settings.
*
* @param seb_quiz_settings $quizsettings Given quiz settings instance.
* @param int $savetype Type of SEB usage.
* @param int $templateid Template ID.
*/
public function save_settings_with_optional_template($quizsettings, $savetype, $templateid = 0) {
$quizsettings->set('requiresafeexambrowser', $savetype);
if (!empty($templateid)) {
$quizsettings->set('templateid', $templateid);
}
$quizsettings->save();
}
/**
* Bad browser exam key data provider.
*
* @return array
*/
public function bad_browser_exam_key_provider(): array {
return [
'Short string' => ['fdsf434r',
'A key should be a 64-character hex string.'],
'Non hex string' => ['aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf678!',
'A key should be a 64-character hex string.'],
'Non unique' => ["aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789"
. "\naadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789", 'The keys must all be different.'],
];
}
/**
* Provide settings for different filter rules.
*
* @return array Test data.
*/
public function filter_rules_provider(): array {
return [
'enabled simple expessions' => [
(object) [
'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
'quizid' => 1,
'cmid' => 1,
'expressionsallowed' => "test.com\r\nsecond.hello",
'regexallowed' => '',
'expressionsblocked' => '',
'regexblocked' => '',
],
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
. "<key>allowWlan</key><false/><key>showReloadButton</key>"
. "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
. "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
. "<key>expression</key><string>test.com</string>"
. "<key>regex</key><false/></dict><dict><key>action</key><integer>1</integer>"
. "<key>active</key><true/><key>expression</key>"
. "<string>second.hello</string><key>regex</key><false/></dict></array>"
. "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
],
'blocked simple expessions' => [
(object) [
'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
'quizid' => 1,
'cmid' => 1,
'expressionsallowed' => '',
'regexallowed' => '',
'expressionsblocked' => "test.com\r\nsecond.hello",
'regexblocked' => '',
],
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
. "<key>allowWlan</key><false/><key>showReloadButton</key>"
. "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
. "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
. "<key>expression</key><string>test.com</string>"
. "<key>regex</key><false/></dict><dict><key>action</key><integer>0</integer>"
. "<key>active</key><true/><key>expression</key>"
. "<string>second.hello</string><key>regex</key><false/></dict></array>"
. "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
],
'enabled regex expessions' => [
(object) [
'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
'quizid' => 1,
'cmid' => 1,
'expressionsallowed' => '',
'regexallowed' => "test.com\r\nsecond.hello",
'expressionsblocked' => '',
'regexblocked' => '',
],
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
. "<key>allowWlan</key><false/><key>showReloadButton</key>"
. "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
. "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
. "<key>expression</key><string>test.com</string>"
. "<key>regex</key><true/></dict><dict><key>action</key><integer>1</integer>"
. "<key>active</key><true/><key>expression</key>"
. "<string>second.hello</string><key>regex</key><true/></dict></array>"
. "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
],
'blocked regex expessions' => [
(object) [
'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
'quizid' => 1,
'cmid' => 1,
'expressionsallowed' => '',
'regexallowed' => '',
'expressionsblocked' => '',
'regexblocked' => "test.com\r\nsecond.hello",
],
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
. "<key>allowWlan</key><false/><key>showReloadButton</key>"
. "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
. "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
. "<key>expression</key><string>test.com</string>"
. "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
. "<key>active</key><true/><key>expression</key>"
. "<string>second.hello</string><key>regex</key><true/></dict></array>"
. "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
],
'multiple simple expessions' => [
(object) [
'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
'quizid' => 1,
'cmid' => 1,
'expressionsallowed' => "*",
'regexallowed' => '',
'expressionsblocked' => '',
'regexblocked' => "test.com\r\nsecond.hello",
],
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
. "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
. "<key>allowWlan</key><false/><key>showReloadButton</key>"
. "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array><dict><key>action</key>"
. "<integer>1</integer><key>active</key><true/><key>expression</key><string>*</string>"
. "<key>regex</key><false/></dict>"
. "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
. "<key>expression</key><string>test.com</string>"
. "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
. "<key>active</key><true/><key>expression</key>"
. "<string>second.hello</string><key>regex</key><true/></dict></array>"
. "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
. "<key>sendBrowserExamKey</key><true/><key>browserWindowWebView</key><integer>3</integer>"
. "<key>examSessionClearCookiesOnStart</key><false/>"
. "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
],
];
}
/**
* Test that config and config key are null when expected.
*/
public function test_generates_config_values_as_null_when_expected(): void {
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO);
$quizsettings->save();
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNull($quizsettings->get_config());
$this->assertNull($quizsettings->get_config());
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
$xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
$this->create_module_test_file($xml, $this->quiz->cmid);
$quizsettings->save();
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
$quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG);
$quizsettings->save();
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNull($quizsettings->get_config());
$this->assertNull($quizsettings->get_config_key());
$template = $this->create_template();
$templateid = $template->get('id');
$this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertNotNull($quizsettings->get_config());
$this->assertNotNull($quizsettings->get_config_key());
}
/**
* Test that quizsettings cache exists after creation.
*/
public function test_quizsettings_cache_exists_after_creation(): void {
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->to_record(), \cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
}
/**
* Test that quizsettings cache gets deleted after deletion.
*/
public function test_quizsettings_cache_purged_after_deletion(): void {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
}
/**
* Test that we can get seb_quiz_settings by quiz id.
*/
public function test_get_quiz_settings_by_quiz_id(): void {
$expected = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$this->assertEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Check that data is getting from cache.
$expected->set('showsebtaskbar', 0);
$this->assertNotEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Now save and check that cached as been updated.
$expected->save();
$this->assertEquals($expected->to_record(), seb_quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
// Returns false for non existing quiz.
$this->assertFalse(seb_quiz_settings::get_by_quiz_id(7777777));
}
/**
* Test that SEB config cache exists after creation of the quiz.
*/
public function test_config_cache_exists_after_creation(): void {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
}
/**
* Test that SEB config cache gets deleted after deletion.
*/
public function test_config_cache_purged_after_deletion(): void {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
}
/**
* Test that we can get SEB config by quiz id.
*/
public function test_get_config_by_quiz_id(): void {
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = $quizsettings->get_config();
$this->assertEquals($expected, seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Check that data is getting from cache.
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config(), seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Now save and check that cached as been updated.
$quizsettings->save();
$this->assertEquals($quizsettings->get_config(), seb_quiz_settings::get_config_by_quiz_id($this->quiz->id));
// Returns null for non existing quiz.
$this->assertNull(seb_quiz_settings::get_config_by_quiz_id(7777777));
}
/**
* Test that SEB config key cache exists after creation of the quiz.
*/
public function test_config_key_cache_exists_after_creation(): void {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
}
/**
* Test that SEB config key cache gets deleted after deletion.
*/
public function test_config_key_cache_purged_after_deletion(): void {
$this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$quizsettings->delete();
$this->assertFalse(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
}
/**
* Test that we can get SEB config key by quiz id.
*/
public function test_get_config_key_by_quiz_id(): void {
$quizsettings = seb_quiz_settings::get_record(['quizid' => $this->quiz->id]);
$expected = $quizsettings->get_config_key();
$this->assertEquals($expected, seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Check that data is getting from cache.
$quizsettings->set('showsebtaskbar', 0);
$this->assertNotEquals($quizsettings->get_config_key(), seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Now save and check that cached as been updated.
$quizsettings->save();
$this->assertEquals($quizsettings->get_config_key(), seb_quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
// Returns null for non existing quiz.
$this->assertNull(seb_quiz_settings::get_config_key_by_quiz_id(7777777));
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace quizaccess_seb;
/**
* PHPUnit tests for template class.
*
* @package quizaccess_seb
* @author Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class template_test extends \advanced_testcase {
/**
* Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
}
/**
* Test that template saved with valid content.
*/
public function test_template_is_saved(): void {
global $DB;
$data = new \stdClass();
$data->name = 'Test name';
$data->description = 'Test description';
$data->enabled = 1;
$data->content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array>"
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$template = new template(0, $data);
$template->save();
$actual = $DB->get_record(template::TABLE, ['id' => $template->get('id')]);
$this->assertEquals($data->name, $actual->name);
$this->assertEquals($data->description, $actual->description);
$this->assertEquals($data->enabled, $actual->enabled);
$this->assertEquals($data->content, $actual->content);
$this->assertTrue($template->can_delete());
}
/**
* Test that template is not saved with invalid content.
*/
public function test_template_is_not_saved_with_invalid_content(): void {
$this->expectException(\core\invalid_persistent_exception::class);
$this->expectExceptionMessage('Invalid SEB config template');
$data = new \stdClass();
$data->name = 'Test name';
$data->description = 'Test description';
$data->enabled = 1;
$data->content = "Invalid content";
$template = new template(0, $data);
$template->save();
}
/**
* Test that a template cannot be deleted when assigned to a quiz.
*/
public function test_cannot_delete_template_when_assigned_to_quiz(): void {
global $DB;
$data = new \stdClass();
$data->name = 'Test name';
$data->description = 'Test description';
$data->enabled = 1;
$data->content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
. "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
. "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
. "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
. "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
. "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
. "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
. "<string>test.com</string><key>regex</key><false/></dict></array>"
. "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
$template = new template(0, $data);
$template->save();
$this->assertTrue($template->can_delete());
$DB->insert_record(seb_quiz_settings::TABLE, (object) [
'quizid' => 1,
'cmid' => 1,
'templateid' => $template->get('id'),
'requiresafeexambrowser' => '1',
'sebconfigfile' => '373552893',
'showsebtaskbar' => '1',
'showwificontrol' => '0',
'showreloadbutton' => '1',
'showtime' => '0',
'showkeyboardlayout' => '1',
'allowuserquitseb' => '1',
'quitpassword' => 'test',
'linkquitseb' => '',
'userconfirmquit' => '1',
'enableaudiocontrol' => '1',
'muteonstartup' => '0',
'allowspellchecking' => '0',
'allowreloadinexam' => '1',
'activateurlfiltering' => '1',
'filterembeddedcontent' => '0',
'expressionsallowed' => 'test.com',
'regexallowed' => '',
'expressionsblocked' => '',
'regexblocked' => '',
'showsebdownloadlink' => '1',
'config' => '',
]);
$this->assertFalse($template->can_delete());
}
}
@@ -0,0 +1,311 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A test helper trait.
*
* @package quizaccess_seb
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2019 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use mod_quiz\local\access_rule_base;
use mod_quiz\quiz_attempt;
use quizaccess_seb\seb_access_manager;
use quizaccess_seb\settings_provider;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . "/mod/quiz/accessrule/seb/rule.php"); // Include plugin rule class.
require_once($CFG->dirroot . "/mod/quiz/mod_form.php"); // Include plugin rule class.
/**
* A test helper trait. It has some common helper methods.
*
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait quizaccess_seb_test_helper_trait {
/** @var \stdClass $course Test course to contain quiz. */
protected $course;
/** @var \stdClass $quiz A test quiz. */
protected $quiz;
/** @var \stdClass $user A test logged-in user. */
protected $user;
/**
* Assign a capability to $USER
* The function creates a student $USER if $USER->id is empty
*
* @param string $capability Capability name.
* @param int $contextid Context ID.
* @param int $roleid Role ID.
* @return int The role id - mainly returned for creation, so calling function can reuse it.
*/
protected function assign_user_capability($capability, $contextid, $roleid = null) {
global $USER;
// Create a new student $USER if $USER doesn't exist.
if (empty($USER->id)) {
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
}
if (empty($roleid)) {
$roleid = \create_role('Dummy role', 'dummyrole', 'dummy role description');
}
\assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
\role_assign($roleid, $USER->id, $contextid);
\accesslib_clear_all_caches_for_unit_testing();
return $roleid;
}
/**
* Strip the seb_ prefix from each setting key.
*
* @param \stdClass $settings Object containing settings.
* @return \stdClass The modified settings object.
*/
protected function strip_all_prefixes(\stdClass $settings): \stdClass {
$newsettings = new \stdClass();
foreach ($settings as $name => $setting) {
$newname = preg_replace("/^seb_/", "", $name);
$newsettings->$newname = $setting; // Add new key.
}
return $newsettings;
}
/**
* Creates a file in the user draft area.
*
* @param string $xml
* @return int The user draftarea id
*/
protected function create_test_draftarea_file(string $xml): int {
global $USER;
$itemid = 0;
$usercontext = \context_user::instance($USER->id);
$filerecord = [
'contextid' => \context_user::instance($USER->id)->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $itemid,
'filepath' => '/',
'filename' => 'test.xml'
];
$fs = get_file_storage();
$fs->create_file_from_string($filerecord, $xml);
$draftitemid = 0;
file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'draft', 0);
return $draftitemid;
}
/**
* Create a file in a modules filearea.
*
* @param string $xml XML content of the file.
* @param string $cmid Course module id.
* @return int Item ID of file.
*/
protected function create_module_test_file(string $xml, string $cmid): int {
$itemid = 0;
$fs = get_file_storage();
$filerecord = [
'contextid' => \context_module::instance($cmid)->id,
'component' => 'quizaccess_seb',
'filearea' => 'filemanager_sebconfigfile',
'itemid' => $itemid,
'filepath' => '/',
'filename' => 'test.xml'
];
$fs->create_file_from_string($filerecord, $xml);
return $itemid;
}
/**
* Create a test quiz for the specified course.
*
* @param \stdClass $course
* @param int $requiresafeexambrowser How to use SEB for this quiz?
* @return array
*/
protected function create_test_quiz($course, $requiresafeexambrowser = settings_provider::USE_SEB_NO) {
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance([
'course' => $course->id,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => 2,
'seb_requiresafeexambrowser' => $requiresafeexambrowser,
]);
$quiz->seb_showsebdownloadlink = 1;
$quiz->coursemodule = $quiz->cmid;
// Create a couple of questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
quiz_add_quiz_question($saq->id, $quiz);
$numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
quiz_add_quiz_question($numq->id, $quiz);
return $quiz;
}
/**
* Answer questions for a quiz + user.
*
* @param \stdClass $quiz Quiz to attempt.
* @param \stdClass $user A user to attempt the quiz.
* @return array
*/
protected function attempt_quiz($quiz, $user) {
$this->setUser($user);
$starttime = time();
$quizobj = mod_quiz\quiz_settings::create($quiz->id, $user->id);
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
// Start the attempt.
$attempt = quiz_create_attempt($quizobj, 1, false, $starttime, false, $user->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $starttime);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Answer the questions.
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = [
1 => ['answer' => 'frog'],
2 => ['answer' => '3.14'],
];
$attemptobj->process_submitted_actions($starttime, false, $tosubmit);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish($starttime, false);
$this->setUser();
return [$quizobj, $quba, $attemptobj];
}
/**
* Create test template.
*
* @param string|null $xml Template content.
* @return \quizaccess_seb\template Just created template.
*/
public function create_template(string $xml = null) {
$data = [];
if (!is_null($xml)) {
$data['content'] = $xml;
}
return $this->getDataGenerator()->get_plugin_generator('quizaccess_seb')->create_template($data);
}
/**
* Get access manager for testing.
*
* @return \quizaccess_seb\seb_access_manager
*/
protected function get_access_manager() {
return new seb_access_manager(new mod_quiz\quiz_settings($this->quiz,
get_coursemodule_from_id('quiz', $this->quiz->cmid), $this->course));
}
/**
* A helper method to make the rule form the currently created quiz and course.
*
* @return access_rule_base|null
*/
protected function make_rule() {
return \quizaccess_seb::make(
new mod_quiz\quiz_settings($this->quiz, get_coursemodule_from_id('quiz', $this->quiz->cmid), $this->course),
0,
true
);
}
/**
* A helper method to set up quiz view page.
*/
protected function set_up_quiz_view_page() {
global $PAGE;
$page = new \moodle_page();
$page->set_context(\context_module::instance($this->quiz->cmid));
$page->set_course($this->course);
$page->set_pagelayout('standard');
$page->set_pagetype("mod-quiz-view");
$page->set_url('/mod/quiz/view.php?id=' . $this->quiz->cmid);
$PAGE = $page;
}
/**
* Get a test object containing mock test settings.
*
* @return \stdClass Settings.
*/
protected function get_test_settings(array $settings = []): \stdClass {
return (object) array_merge([
'quizid' => 1,
'cmid' => 1,
'requiresafeexambrowser' => '1',
'showsebtaskbar' => '1',
'showwificontrol' => '0',
'showreloadbutton' => '1',
'showtime' => '0',
'showkeyboardlayout' => '1',
'allowuserquitseb' => '1',
'quitpassword' => 'test',
'linkquitseb' => '',
'userconfirmquit' => '1',
'enableaudiocontrol' => '1',
'muteonstartup' => '0',
'allowspellchecking' => '0',
'allowreloadinexam' => '1',
'activateurlfiltering' => '1',
'filterembeddedcontent' => '0',
'expressionsallowed' => 'test.com',
'regexallowed' => '',
'expressionsblocked' => '',
'regexblocked' => '',
'showsebdownloadlink' => '1',
], $settings);
}
}