Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

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