Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace quizaccess_seb\external;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  global $CFG;
  22  
  23  use quizaccess_seb\quiz_settings;
  24  
  25  require_once($CFG->libdir . '/externallib.php');
  26  
  27  /**
  28   * PHPUnit tests for external function.
  29   *
  30   * @package    quizaccess_seb
  31   * @author     Andrew Madden <andrewmadden@catalyst-au.net>
  32   * @copyright  2021 Catalyst IT
  33   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  34   * @covers \quizaccess_seb\external\validate_quiz_access
  35   */
  36  class validate_quiz_access_test extends \advanced_testcase {
  37      use \quizaccess_seb_test_helper_trait;
  38  
  39      /**
  40       * This method runs before every test.
  41       */
  42      public function setUp(): void {
  43          parent::setUp();
  44          $this->resetAfterTest();
  45  
  46          // Generate data objects.
  47          $this->course = $this->getDataGenerator()->create_course();
  48          $this->quiz = $this->create_test_quiz($this->course);
  49          $this->user = $this->getDataGenerator()->create_user();
  50          $this->getDataGenerator()->enrol_user($this->user->id, $this->course->id, 'student');
  51          $this->setUser($this->user);
  52      }
  53  
  54      /**
  55       * Bad parameter provider.
  56       *
  57       * @return array
  58       */
  59      public function bad_parameters_provider(): array {
  60          return [
  61              'no params' => [
  62                  'cmid' => null,
  63                  'url' => null,
  64                  'configkey' => null,
  65                  '/Invalid parameter value detected \(Missing required key in single structure: cmid\)/'
  66              ],
  67              'no course module id' => [
  68                  'cmid' => null,
  69                  'url' => 'https://www.example.com/moodle',
  70                  'configkey' => hash('sha256', 'configkey'),
  71                  '/Invalid parameter value detected \(Missing required key in single structure: cmid\)/'
  72              ],
  73              'no url' => [
  74                  'cmid' => 123,
  75                  'url' => null,
  76                  'configkey' => hash('sha256', 'configkey'),
  77                  '/Invalid parameter value detected \(Missing required key in single structure: url\)/'
  78              ],
  79              'cmid is not an int' => [
  80                  'cmid' => 'test',
  81                  'url' => 'https://www.example.com/moodle',
  82                  'configkey' => null,
  83                  '/Invalid external api parameter: the value is "test", the server was expecting "int" type/'
  84              ],
  85              'url is not a url' => [
  86                  'cmid' => 123,
  87                  'url' => 123,
  88                  'configkey' => hash('sha256', 'configkey'),
  89                  '/Invalid external api parameter: the value is "123", the server was expecting "url" type/'
  90              ],
  91          ];
  92      }
  93  
  94      /**
  95       * Test exception thrown for bad parameters.
  96       *
  97       * @param mixed $cmid Course module id.
  98       * @param mixed $url Page URL.
  99       * @param mixed $configkey SEB config key.
 100       * @param mixed $messageregex Error message regex to check.
 101       *
 102       * @dataProvider bad_parameters_provider
 103       */
 104      public function test_invalid_parameters($cmid, $url, $configkey, $messageregex) {
 105          $params = [];
 106          if (!empty($cmid)) {
 107              $params['cmid'] = $cmid;
 108          }
 109          if (!empty($url)) {
 110              $params['url'] = $url;
 111          }
 112          if (!empty($configkey)) {
 113              $params['configkey'] = $configkey;
 114          }
 115  
 116          $this->expectException(\invalid_parameter_exception::class);
 117          $this->expectExceptionMessageMatches($messageregex);
 118          \external_api::validate_parameters(validate_quiz_keys::execute_parameters(), $params);
 119      }
 120  
 121      /**
 122       * Test that the user has permissions to access context.
 123       */
 124      public function test_context_is_not_valid_for_user() {
 125          // Set user as user not enrolled in course and quiz.
 126          $this->user = $this->getDataGenerator()->create_user();
 127          $this->setUser($this->user);
 128  
 129          $this->expectException(\require_login_exception::class);
 130          $this->expectExceptionMessage('Course or activity not accessible. (Not enrolled)');
 131          validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', 'configkey');
 132      }
 133  
 134      /**
 135       * Test exception thrown when no key provided.
 136       */
 137      public function test_no_keys_provided() {
 138          $this->expectException(\invalid_parameter_exception::class);
 139          $this->expectExceptionMessage('At least one Safe Exam Browser key must be provided.');
 140          validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle');
 141      }
 142  
 143      /**
 144       * Test exception thrown if cmid doesn't match a quiz.
 145       */
 146      public function test_quiz_does_not_exist() {
 147          $this->setAdminUser();
 148          $forum = $this->getDataGenerator()->create_module('forum', ['course' => $this->course->id]);
 149          $this->expectException(\invalid_parameter_exception::class);
 150          $this->expectExceptionMessage('Quiz not found matching course module ID: ' . $forum->cmid);
 151          validate_quiz_keys::execute($forum->cmid, 'https://www.example.com/moodle', 'configkey');
 152      }
 153  
 154      /**
 155       * Test config key is valid.
 156       */
 157      public function test_config_key_valid() {
 158          $sink = $this->redirectEvents();
 159          // Test settings to populate the quiz.
 160          $settings = $this->get_test_settings([
 161              'quizid' => $this->quiz->id,
 162              'cmid' => $this->quiz->cmid,
 163          ]);
 164          $url = 'https://www.example.com/moodle';
 165  
 166          // Create the quiz settings.
 167          $quizsettings = new quiz_settings(0, $settings);
 168          $quizsettings->save();
 169  
 170          $fullconfigkey = hash('sha256', $url . $quizsettings->get_config_key());
 171          $result = validate_quiz_keys::execute($this->quiz->cmid, $url, $fullconfigkey);
 172          $this->assertTrue($result['configkey']);
 173          $this->assertTrue($result['browserexamkey']);
 174  
 175          $events = $sink->get_events();
 176          $this->assertCount(0, $events);
 177      }
 178  
 179      /**
 180       * Test config key is not valid.
 181       */
 182      public function test_config_key_not_valid() {
 183          $sink = $this->redirectEvents();
 184          // Test settings to populate the quiz.
 185          $settings = $this->get_test_settings([
 186              'quizid' => $this->quiz->id,
 187              'cmid' => $this->quiz->cmid,
 188          ]);
 189  
 190          // Create the quiz settings.
 191          $quizsettings = new quiz_settings(0, $settings);
 192          $quizsettings->save();
 193  
 194          $result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', 'badconfigkey');
 195          $this->assertFalse($result['configkey']);
 196          $this->assertTrue($result['browserexamkey']);
 197          $events = $sink->get_events();
 198          $this->assertCount(1, $events);
 199          $event = reset($events);
 200          $this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
 201          $this->assertStringContainsString('Invalid SEB config key', $event->get_description());
 202      }
 203  
 204      /**
 205       * Test browser exam key is valid.
 206       */
 207      public function test_browser_exam_key_valid() {
 208          $sink = $this->redirectEvents();
 209          // Test settings to populate the quiz.
 210          $url = 'https://www.example.com/moodle';
 211          $validbrowserexamkey = hash('sha256', 'validbrowserexamkey');
 212          $settings = $this->get_test_settings([
 213              'quizid' => $this->quiz->id,
 214              'cmid' => $this->quiz->cmid,
 215              'requiresafeexambrowser' => \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG,
 216              'allowedbrowserexamkeys' => $validbrowserexamkey,
 217          ]);
 218  
 219          // Create the quiz settings.
 220          $quizsettings = new quiz_settings(0, $settings);
 221          $quizsettings->save();
 222  
 223          $fullbrowserexamkey = hash('sha256', $url . $validbrowserexamkey);
 224          $result = validate_quiz_keys::execute($this->quiz->cmid, $url, null, $fullbrowserexamkey);
 225          $this->assertTrue($result['configkey']);
 226          $this->assertTrue($result['browserexamkey']);
 227          $events = $sink->get_events();
 228          $this->assertCount(0, $events);
 229      }
 230  
 231      /**
 232       * Test browser exam key is not valid.
 233       */
 234      public function test_browser_exam_key_not_valid() {
 235          $sink = $this->redirectEvents();
 236          // Test settings to populate the quiz.
 237          $validbrowserexamkey = hash('sha256', 'validbrowserexamkey');
 238          $settings = $this->get_test_settings([
 239              'quizid' => $this->quiz->id,
 240              'cmid' => $this->quiz->cmid,
 241              'requiresafeexambrowser' => \quizaccess_seb\settings_provider::USE_SEB_CLIENT_CONFIG,
 242              'allowedbrowserexamkeys' => $validbrowserexamkey,
 243          ]);
 244  
 245          // Create the quiz settings.
 246          $quizsettings = new quiz_settings(0, $settings);
 247          $quizsettings->save();
 248  
 249          $result = validate_quiz_keys::execute($this->quiz->cmid, 'https://www.example.com/moodle', null,
 250                  hash('sha256', 'badbrowserexamkey'));
 251          $this->assertTrue($result['configkey']);
 252          $this->assertFalse($result['browserexamkey']);
 253          $events = $sink->get_events();
 254          $this->assertCount(1, $events);
 255          $event = reset($events);
 256          $this->assertInstanceOf('\quizaccess_seb\event\access_prevented', $event);
 257          $this->assertStringContainsString('Invalid SEB browser key', $event->get_description());
 258      }
 259  
 260  }