From f74ce531ba3801910e9d10300539ee3ab33a3436 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Fri, 28 Apr 2023 18:52:12 +0200 Subject: [PATCH] MDL-74263 enrol_guest: Add WebService support for mobile app --- .../classes/external/validate_password.php | 127 ++++++++++++++++++ enrol/guest/db/services.php | 7 + enrol/guest/lib.php | 11 ++ .../tests/external/validate_password_test.php | 81 +++++++++++ enrol/guest/version.php | 2 +- 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 enrol/guest/classes/external/validate_password.php create mode 100644 enrol/guest/tests/external/validate_password_test.php diff --git a/enrol/guest/classes/external/validate_password.php b/enrol/guest/classes/external/validate_password.php new file mode 100644 index 00000000000..6d945b96c72 --- /dev/null +++ b/enrol/guest/classes/external/validate_password.php @@ -0,0 +1,127 @@ +. + +namespace enrol_guest\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_single_structure; +use core_external\external_value; +use core_external\external_warnings; +use context_system; +use moodle_exception; +use core_text; +use stdClass; + +/** + * This is the external method validating a guest password. + * + * @package enrol_guest + * @since Moodle 4.3 + * @copyright 2023 Juan Leyva + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class validate_password extends external_api { + + /** + * Webservice parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'instanceid' => new external_value(PARAM_INT, 'instance id of guest enrolment plugin'), + 'password' => new external_value(PARAM_RAW, 'the course password'), + ] + ); + } + + /** + * Perform password validation. + * + * If password is correct: keep it as user preference. + * If password is not correct: remove existing user preference (if any) + * + * @throws moodle_exception + * @param int $instanceid instance id of guest enrolment plugin + * @param string $password the course password + * @return stdClass validation result info + */ + public static function execute(int $instanceid, string $password): stdClass { + global $CFG, $DB; + require_once($CFG->libdir . '/enrollib.php'); + + $params = external_api::validate_parameters(self::execute_parameters(), [ + 'instanceid' => $instanceid, + 'password' => $password, + ]); + $warnings = []; + $validated = false; + $hint = ''; + + // Retrieve guest enrolment plugin. + $enrolplugin = enrol_get_plugin('guest'); + if (empty($enrolplugin)) { + throw new moodle_exception('invaliddata', 'error'); + } + + self::validate_context(context_system::instance()); + $enrolinstance = $DB->get_record('enrol', + ['id' => $params['instanceid'], 'status' => ENROL_INSTANCE_ENABLED], '*', MUST_EXIST); + $course = $DB->get_record('course', ['id' => $enrolinstance->courseid], '*', MUST_EXIST); + + if (!\core_course_category::can_view_course_info($course) && !can_access_course($course)) { + throw new moodle_exception('coursehidden'); + } + + if ($enrolinstance->password) { + if ($params['password'] === $enrolinstance->password) { + $validated = true; + set_user_preference('enrol_guest_ws_password_' . $enrolinstance->id, $params['password']); + } else { + // Always unset in case there was something stored. + unset_user_preference('enrol_guest_ws_password_' . $enrolinstance->id); + + if ($enrolplugin->get_config('showhint')) { + $hint = core_text::substr($enrolinstance->password, 0, 1); + $hint = get_string('passwordinvalidhint', 'enrol_guest', $hint); + } + } + } + + $result = (object)[ + 'validated' => $validated, + 'hint' => $hint, + 'warnings' => $warnings, + ]; + + return $result; + } + + /** + * Describes the return information. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'validated' => new external_value(PARAM_BOOL, 'Whether the password was successfully validated'), + 'hint' => new external_value(PARAM_RAW, 'Password hint (if enabled)', VALUE_OPTIONAL), + 'warnings' => new external_warnings(), + ]); + } +} diff --git a/enrol/guest/db/services.php b/enrol/guest/db/services.php index dc23ea65733..de9d3577b9c 100644 --- a/enrol/guest/db/services.php +++ b/enrol/guest/db/services.php @@ -33,4 +33,11 @@ $functions = array( 'type' => 'read', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE), ), + + 'enrol_guest_validate_password' => [ + 'classname' => 'enrol_guest\external\validate_password', + 'description' => 'Perform password validation.', + 'type' => 'write', + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], ); diff --git a/enrol/guest/lib.php b/enrol/guest/lib.php index fd8ffbda59b..aba166b9dfb 100644 --- a/enrol/guest/lib.php +++ b/enrol/guest/lib.php @@ -103,6 +103,17 @@ class enrol_guest_plugin extends enrol_plugin { if ($USER->enrol_guest_passwords[$instance->id] === $instance->password) { $allow = true; } + } else if (WS_SERVER) { // Mobile app mostly. + $storedpass = get_user_preferences('enrol_guest_ws_password_'. $instance->id); + // We check first if there is a supplied password. + if (!is_null($storedpass)) { + $allow = $storedpass === $instance->password; + + if (!$allow) { + // Reset, probably the course password was changed. + unset_user_preference('enrol_guest_ws_password_' . $instance->id); + } + } } if ($allow) { diff --git a/enrol/guest/tests/external/validate_password_test.php b/enrol/guest/tests/external/validate_password_test.php new file mode 100644 index 00000000000..4fb1264417c --- /dev/null +++ b/enrol/guest/tests/external/validate_password_test.php @@ -0,0 +1,81 @@ +. + +namespace enrol_guest\external; + +use core_external\external_api; + +/** + * Tests for validate_password class. + * + * @package enrol_guest + * @covers \enrol_guest\external\validate_password + */ +class validate_password_test extends \advanced_testcase { + + public function test_execute(): void { + global $DB; + + $this->resetAfterTest(); + + $course = self::getDataGenerator()->create_course(); + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + $student = self::getDataGenerator()->create_user(); + $pass = 'abc'; + + // Add enrolment methods for course. + $guestplugin = enrol_get_plugin('guest'); + $instanceid = $guestplugin->add_instance($course, [ + 'status' => ENROL_INSTANCE_ENABLED, + 'name' => 'Test instance', + 'customint6' => 1, + 'password' => $pass, + 'roleid' => $studentrole->id, + ]); + + $this->setUser($student); + + // Invalid password. + $result = validate_password::execute($instanceid, 'z'); + $result = external_api::clean_returnvalue(validate_password::execute_returns(), $result); + $this->assertFalse($result['validated']); + $this->assertEmpty($result['hint']); + + // Set invalid password preference. + set_user_preference('enrol_guest_ws_password_' . $instanceid, 'y'); + + // Enable hint for invalid password. + set_config('showhint', 1, 'enrol_guest'); + $result = validate_password::execute($instanceid, 'z'); + $result = external_api::clean_returnvalue(validate_password::execute_returns(), $result); + $this->assertFalse($result['validated']); + $this->assertNotEmpty($result['hint']); // Check hint. + $this->assertNull(get_user_preferences('enrol_guest_ws_password_'. $instanceid)); // Check preference was reset. + + // Try valid password. + $result = validate_password::execute($instanceid, $pass); + $result = external_api::clean_returnvalue(validate_password::execute_returns(), $result); + $this->assertTrue($result['validated']); + + // Check correct user preference. + $this->assertEquals($pass, get_user_preferences('enrol_guest_ws_password_'. $instanceid)); + + // Course hidden, expect exception. + $DB->set_field('course', 'visible', 0, ['id' => $course->id]); + $this->expectException(\moodle_exception::class); + $result = validate_password::execute($instanceid, ''); + } +} diff --git a/enrol/guest/version.php b/enrol/guest/version.php index b34a9ce390f..608e05c2f21 100644 --- a/enrol/guest/version.php +++ b/enrol/guest/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2023042400; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2023042401; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2023041800; // Requires this Moodle version. $plugin->component = 'enrol_guest'; // Full name of the plugin (used for diagnostics)