Differences Between: [Versions 310 and 400] [Versions 310 and 401]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Manage the access to the quiz. 19 * 20 * @package quizaccess_seb 21 * @author Tim Hunt 22 * @author Luca Bösch <luca.boesch@bfh.ch> 23 * @author Andrew Madden <andrewmadden@catalyst-au.net> 24 * @author Dmitrii Metelkin <dmitriim@catalyst-au.net> 25 * @copyright 2019 Catalyst IT 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 namespace quizaccess_seb; 30 31 use context_module; 32 use quiz; 33 34 defined('MOODLE_INTERNAL') || die(); 35 36 /** 37 * Manage the access to the quiz. 38 * 39 * @copyright 2020 Catalyst IT 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class access_manager { 43 44 /** Header sent by Safe Exam Browser containing the Config Key hash. */ 45 private const CONFIG_KEY_HEADER = 'HTTP_X_SAFEEXAMBROWSER_CONFIGKEYHASH'; 46 47 /** Header sent by Safe Exam Browser containing the Browser Exam Key hash. */ 48 private const BROWSER_EXAM_KEY_HEADER = 'HTTP_X_SAFEEXAMBROWSER_REQUESTHASH'; 49 50 /** @var quiz $quiz A quiz object containing all information pertaining to current quiz. */ 51 private $quiz; 52 53 /** @var quiz_settings $quizsettings A quiz settings persistent object containing plugin settings */ 54 private $quizsettings; 55 56 /** @var context_module $context Context of this quiz activity. */ 57 private $context; 58 59 /** @var string|null $validconfigkey Expected valid SEB config key. 60 */ 61 private $validconfigkey = null; 62 63 /** 64 * The access_manager constructor. 65 * 66 * @param quiz $quiz The details of the quiz. 67 */ 68 public function __construct(quiz $quiz) { 69 $this->quiz = $quiz; 70 $this->context = context_module::instance($quiz->get_cmid()); 71 $this->quizsettings = quiz_settings::get_by_quiz_id($quiz->get_quizid()); 72 $this->validconfigkey = quiz_settings::get_config_key_by_quiz_id($quiz->get_quizid()); 73 } 74 75 /** 76 * Check if the browser exam key hash in header matches one of the listed browser exam keys from quiz settings. 77 * 78 * @return bool True if header key matches one of the saved keys. 79 */ 80 public function validate_browser_exam_keys() : bool { 81 // If browser exam keys are entered in settings, check they match the header. 82 $browserexamkeys = $this->quizsettings->get('allowedbrowserexamkeys'); 83 if (empty($browserexamkeys)) { 84 return true; // If no browser exam keys, no check required. 85 } 86 87 // If the Browser Exam Key header isn't present, prevent access. 88 if (is_null($this->get_received_browser_exam_key())) { 89 return false; 90 } 91 92 return $this->check_browser_exam_keys($browserexamkeys, $this->get_received_browser_exam_key()); 93 } 94 95 /** 96 * Check if the config key hash in header matches quiz settings. 97 * 98 * @return bool True if header key matches saved key. 99 */ 100 public function validate_config_key() : bool { 101 // If using client config, or with no requirement, then no check required. 102 $requiredtype = $this->get_seb_use_type(); 103 if ($requiredtype == settings_provider::USE_SEB_NO 104 || $requiredtype == settings_provider::USE_SEB_CLIENT_CONFIG) { 105 return true; 106 } 107 108 if (empty($this->validconfigkey)) { 109 return false; // No config key has been saved. 110 } 111 112 // If the Config Key header isn't present, prevent access. 113 if (is_null($this->get_received_config_key())) { 114 return false; 115 } 116 117 return $this->check_key($this->validconfigkey, $this->get_received_config_key()); 118 } 119 120 /** 121 * Check if Safe Exam Browser is required to access quiz. 122 * If quizsettings do not exist, then there is no requirement for using SEB. 123 * 124 * @return bool If required. 125 */ 126 public function seb_required() : bool { 127 if (!$this->quizsettings) { 128 return false; 129 } else { 130 return $this->get_seb_use_type() != settings_provider::USE_SEB_NO; 131 } 132 } 133 134 /** 135 * This is the basic check for the Safe Exam Browser previously used in the quizaccess_safebrowser plugin that 136 * managed basic Moodle interactions with SEB. 137 * 138 * @return bool 139 */ 140 public function validate_basic_header() : bool { 141 if ($this->get_seb_use_type() == settings_provider::USE_SEB_CLIENT_CONFIG) { 142 return $this->is_using_seb(); 143 } 144 return true; 145 } 146 147 /** 148 * Check if using Safe Exam Browser. 149 * 150 * @return bool 151 */ 152 public function is_using_seb() : bool { 153 if (isset($_SERVER['HTTP_USER_AGENT'])) { 154 return strpos($_SERVER['HTTP_USER_AGENT'], 'SEB') !== false; 155 } 156 157 return false; 158 } 159 160 /** 161 * Check if user has any capability to bypass the Safe Exam Browser requirement. 162 * 163 * @return bool True if user can bypass check. 164 */ 165 public function can_bypass_seb() : bool { 166 return has_capability('quizaccess/seb:bypassseb', $this->context); 167 } 168 169 /** 170 * Return the full URL that was used to request the current page, which is 171 * what we need for verifying the X-SafeExamBrowser-RequestHash header. 172 */ 173 private function get_this_page_url() : string { 174 global $CFG, $FULLME; 175 // If $FULLME not set fall back to wwwroot. 176 if ($FULLME == null) { 177 return $CFG->wwwroot; 178 } 179 return $FULLME; 180 } 181 182 /** 183 * Return expected SEB config key. 184 * 185 * @return string|null 186 */ 187 public function get_valid_config_key() : ?string { 188 return $this->validconfigkey; 189 } 190 191 /** 192 * Getter for the quiz object. 193 * 194 * @return quiz 195 */ 196 public function get_quiz() : quiz { 197 return $this->quiz; 198 } 199 200 /** 201 * Check the hash from the request header against the permitted browser exam keys. 202 * 203 * @param array $keys Allowed browser exam keys. 204 * @param string $header The value of the X-SafeExamBrowser-RequestHash to check. 205 * @return bool True if the hash matches. 206 */ 207 private function check_browser_exam_keys(array $keys, string $header) : bool { 208 foreach ($keys as $key) { 209 if ($this->check_key($key, $header)) { 210 return true; 211 } 212 } 213 return false; 214 } 215 216 /** 217 * Check the hash from the request header against a single permitted key. 218 * 219 * @param string $key an allowed key. 220 * @param string $header the value of the X-SafeExamBrowser-RequestHash or X-SafeExamBrowser-ConfigKeyHash to check. 221 * @return bool true if the hash matches. 222 */ 223 private function check_key($key, $header) : bool { 224 return hash('sha256', $this->get_this_page_url() . $key) === $header; 225 } 226 227 /** 228 * Returns Safe Exam Browser Config Key hash. 229 * 230 * @return string|null 231 */ 232 public function get_received_config_key() { 233 if (isset($_SERVER[self::CONFIG_KEY_HEADER])) { 234 return trim($_SERVER[self::CONFIG_KEY_HEADER]); 235 } 236 237 return null; 238 } 239 240 /** 241 * Returns the Browser Exam Key hash. 242 * 243 * @return string|null 244 */ 245 public function get_received_browser_exam_key() { 246 if (isset($_SERVER[self::BROWSER_EXAM_KEY_HEADER])) { 247 return trim($_SERVER[self::BROWSER_EXAM_KEY_HEADER]); 248 } 249 250 return null; 251 } 252 253 /** 254 * Get type of SEB usage for the quiz. 255 * 256 * @return int 257 */ 258 public function get_seb_use_type() : int { 259 if (empty($this->quizsettings)) { 260 return settings_provider::USE_SEB_NO; 261 } else { 262 return $this->quizsettings->get('requiresafeexambrowser'); 263 } 264 } 265 266 /** 267 * Should validate basic header? 268 * 269 * @return bool 270 */ 271 public function should_validate_basic_header() : bool { 272 return in_array($this->get_seb_use_type(), [ 273 settings_provider::USE_SEB_CLIENT_CONFIG, 274 ]); 275 } 276 277 /** 278 * Should validate SEB config key? 279 * @return bool 280 */ 281 public function should_validate_config_key() : bool { 282 return in_array($this->get_seb_use_type(), [ 283 settings_provider::USE_SEB_CONFIG_MANUALLY, 284 settings_provider::USE_SEB_TEMPLATE, 285 settings_provider::USE_SEB_UPLOAD_CONFIG, 286 ]); 287 } 288 289 /** 290 * Should validate browser exam key? 291 * 292 * @return bool 293 */ 294 public function should_validate_browser_exam_key() : bool { 295 return in_array($this->get_seb_use_type(), [ 296 settings_provider::USE_SEB_UPLOAD_CONFIG, 297 settings_provider::USE_SEB_CLIENT_CONFIG, 298 ]); 299 } 300 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body