mirror of
https://github.com/moodle/moodle.git
synced 2025-02-25 12:33:18 +01:00
MDL-80599 enrol_self: Support password update in csv course upload
This commit is contained in:
parent
4b16c37b96
commit
a45b5583a5
@ -247,10 +247,10 @@ class tool_uploadcourse_course {
|
||||
* Log an error
|
||||
*
|
||||
* @param string $code error code.
|
||||
* @param lang_string $message error message.
|
||||
* @param string $message error message.
|
||||
* @return void
|
||||
*/
|
||||
protected function error($code, lang_string $message) {
|
||||
protected function error($code, string $message) {
|
||||
if (array_key_exists($code, $this->errors)) {
|
||||
throw new coding_exception('Error code already defined');
|
||||
}
|
||||
|
122
admin/tool/uploadcourse/tests/behat/self.feature
Normal file
122
admin/tool/uploadcourse/tests/behat/self.feature
Normal file
@ -0,0 +1,122 @@
|
||||
@tool @tool_uploadcourse @_file_upload
|
||||
Feature: An admin can create courses with self enrolments using a CSV file
|
||||
In order to create courses using a CSV file with self enrolment
|
||||
As an admin
|
||||
I need to be able to upload a CSV file and navigate through the import process
|
||||
|
||||
Background:
|
||||
Given the following "categories" exist:
|
||||
| name | category | idnumber |
|
||||
| Cat 0 | 0 | CAT0 |
|
||||
| Cat 1 | CAT0 | CAT1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | CAT1 |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber | enrolmentkey |
|
||||
| group1 | C1 | G1 | test |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
|
||||
@javascript
|
||||
Scenario: Validation of password for uploaded courses with self enrolments
|
||||
# usepasswordpolicy is not set.
|
||||
Given I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 2"
|
||||
And I should see "Courses updated: 1"
|
||||
And I should see "Courses errors: 0"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "test"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used, but password not required so it will not be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| usepasswordpolicy | 1 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Self enrolment (Student)" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value ""
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
And I press "Cancel"
|
||||
And I click on "Delete" "link" in the "Self enrolment" "table_row"
|
||||
And I press "Continue"
|
||||
|
||||
# Policy is used and password not required so it will be generated if omitted.
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| requirepassword | 1 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "Passwords must be at least 8 characters long."
|
||||
And I should see "Passwords must have at least 1 digit(s)."
|
||||
And I should see "Passwords must have at least 1 upper case letter(s)."
|
||||
And I should see "The password must have at least 1 special character(s) such as *, -, or #."
|
||||
And I click on "Upload courses" "button"
|
||||
And I should see "Courses created: 0"
|
||||
And I should see "Courses updated: 2"
|
||||
And I should see "Courses errors: 1"
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And "Self enrolment (Student)" "table_row" should not exist
|
||||
And I am on the "Course 2" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" does not match value ""
|
||||
And I am on the "Course 3" "enrolment methods" page
|
||||
And I click on "Edit" "link" in the "Self enrolment" "table_row"
|
||||
And the field "Enrolment key" matches value "Test123@"
|
||||
|
||||
# Use group enrolment keys is set to yes, so the password must be different from group enrolment key.
|
||||
And I am on the "Course 1" "enrolment methods" page
|
||||
And I add "Self enrolment" enrolment method in "Course 1" with:
|
||||
| Enrolment key | Abcejfd12@ |
|
||||
| Use group enrolment keys | Yes |
|
||||
And the following config values are set as admin:
|
||||
| config | value | plugin |
|
||||
| usepasswordpolicy | 0 | enrol_self |
|
||||
And I navigate to "Courses > Upload courses" in site administration
|
||||
And I set the field "Upload mode" to "Create new courses, or update existing ones"
|
||||
And I set the field "Update mode" to "Update with CSV data only"
|
||||
And I upload "admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv" file to "File" filemanager
|
||||
And I click on "Preview" "button"
|
||||
And I should see "This enrolment key is already used as a group enrolment key."
|
4
admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv
vendored
Normal file
4
admin/tool/uploadcourse/tests/fixtures/enrolment_self.csv
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
shortname,fullname,category_idnumber,enrolment_1,enrolment_1_role,enrolment_1_password
|
||||
C1,Course 1,CAT1,self,student,test
|
||||
C2,Course 2,CAT1,self,student,
|
||||
C3,Course 3,CAT1,self,student,Test123@
|
|
@ -1205,6 +1205,59 @@ class enrol_self_plugin extends enrol_plugin {
|
||||
public function fill_enrol_custom_fields(array $enrolmentdata, int $courseid): array {
|
||||
return $enrolmentdata + ['password' => ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates enrol plugin instance with provided data.
|
||||
* @param int $courseid Course ID.
|
||||
* @param array $enrolmentdata enrolment data.
|
||||
* @param stdClass $instance Instance to update.
|
||||
*
|
||||
* @return stdClass updated instance
|
||||
*/
|
||||
public function update_enrol_plugin_data(int $courseid, array $enrolmentdata, stdClass $instance): stdClass {
|
||||
if (!empty($enrolmentdata['password'])) {
|
||||
$instance->password = $enrolmentdata['password'];
|
||||
}
|
||||
return parent::update_enrol_plugin_data($courseid, $enrolmentdata, $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data is valid for a given enrolment plugin
|
||||
*
|
||||
* @param array $enrolmentdata enrolment data to validate.
|
||||
* @param int|null $courseid Course ID.
|
||||
* @return array Errors
|
||||
*/
|
||||
public function validate_enrol_plugin_data(array $enrolmentdata, ?int $courseid = null): array {
|
||||
global $CFG, $DB;
|
||||
|
||||
require_once($CFG->dirroot . "/enrol/self/locallib.php");
|
||||
|
||||
// If password is omitted or empty in csv it will be generated automatically if it is a required policy.
|
||||
|
||||
$errors = parent::validate_enrol_plugin_data($enrolmentdata, $courseid);
|
||||
$policy = $this->get_config('usepasswordpolicy');
|
||||
if (!empty($enrolmentdata['password'])) {
|
||||
if ($policy) {
|
||||
$errarray = get_password_policy_errors($enrolmentdata['password']);
|
||||
foreach ($errarray as $i => $err) {
|
||||
$errors['enrol_self' . $i] = $err;
|
||||
}
|
||||
}
|
||||
|
||||
if ($courseid) {
|
||||
// This is bad - no way to identify which instance it is.
|
||||
// So if any instance in course uses group key we should error.
|
||||
$usegroupenrolmentkeys =
|
||||
$DB->count_records('enrol', ['courseid' => $courseid, 'enrol' => 'self', 'customint1' => 1]);
|
||||
if ($usegroupenrolmentkeys && enrol_self_check_group_enrolment_key($courseid, $enrolmentdata['password'])) {
|
||||
$errors['errorpasswordmatchesgroupkey'] =
|
||||
new lang_string('passwordmatchesgroupkey', 'enrol_self', $enrolmentdata['password']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1143,4 +1143,86 @@ class self_test extends \advanced_testcase {
|
||||
$this->assertEquals($instanceid1->id, $actual->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of validate_enrol_plugin_data().
|
||||
*
|
||||
* @covers ::validate_enrol_plugin_data
|
||||
*/
|
||||
public function test_validate_enrol_plugin_data(): void {
|
||||
global $CFG;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Test in course with groups.
|
||||
$course = self::getDataGenerator()->create_course(['groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1]);
|
||||
|
||||
$selfplugin = enrol_get_plugin('self');
|
||||
|
||||
$selfplugin->set_config('usepasswordpolicy', false);
|
||||
$enrolmentdata = [];
|
||||
$errors = $selfplugin->validate_enrol_plugin_data($enrolmentdata);
|
||||
$this->assertEmpty($errors);
|
||||
|
||||
// Now enable some controls, and check that the policy responds with policy text.
|
||||
$selfplugin->set_config('usepasswordpolicy', true);
|
||||
$CFG->minpasswordlength = 8;
|
||||
$CFG->minpassworddigits = 1;
|
||||
$CFG->minpasswordlower = 1;
|
||||
$CFG->minpasswordupper = 1;
|
||||
$CFG->minpasswordnonalphanum = 1;
|
||||
$CFG->maxconsecutiveidentchars = 1;
|
||||
$errors = $selfplugin->validate_enrol_plugin_data($enrolmentdata);
|
||||
// If password is omitted it will be autocreated so nothing to validate.
|
||||
$this->assertEmpty($errors);
|
||||
|
||||
$enrolmentdata = ['password' => 'test'];
|
||||
$errors = $selfplugin->validate_enrol_plugin_data($enrolmentdata);
|
||||
$this->assertCount(4, $errors);
|
||||
$this->assertEquals(get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength), $errors['enrol_self0']);
|
||||
$this->assertEquals(get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits), $errors['enrol_self1']);
|
||||
$this->assertEquals(get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper), $errors['enrol_self2']);
|
||||
$this->assertEquals(get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum), $errors['enrol_self3']);
|
||||
|
||||
$enrolmentdata = ['password' => 'Testingtest123@'];
|
||||
$errors = $selfplugin->validate_enrol_plugin_data($enrolmentdata);
|
||||
$this->assertEmpty($errors);
|
||||
|
||||
$this->getDataGenerator()->create_group(['courseid' => $course->id, 'enrolmentkey' => 'Abirvalg123@']);
|
||||
$instance = $selfplugin->find_instance([], $course->id);
|
||||
$instance->customint1 = 1;
|
||||
$selfplugin->update_instance($instance, $instance);
|
||||
$enrolmentdata = ['password' => 'Abirvalg123@'];
|
||||
$errors = $selfplugin->validate_enrol_plugin_data($enrolmentdata, $course->id);
|
||||
$this->assertArrayHasKey('errorpasswordmatchesgroupkey', $errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of update_enrol_plugin_data().
|
||||
*
|
||||
* @covers ::update_enrol_plugin_data
|
||||
*/
|
||||
public function test_update_enrol_plugin_data(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$manualplugin = enrol_get_plugin('self');
|
||||
|
||||
$admin = get_admin();
|
||||
$this->setUser($admin);
|
||||
|
||||
$enrolmentdata = [];
|
||||
|
||||
$cat = $this->getDataGenerator()->create_category();
|
||||
$course = $this->getDataGenerator()->create_course(['category' => $cat->id, 'shortname' => 'ANON']);
|
||||
$instance = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => 'self'], '*', MUST_EXIST);
|
||||
|
||||
$expectedinstance = $instance;
|
||||
$modifiedinstance = $manualplugin->update_enrol_plugin_data($course->id, $enrolmentdata, $instance);
|
||||
$this->assertEquals($expectedinstance, $modifiedinstance);
|
||||
|
||||
$enrolmentdata['password'] = 'test';
|
||||
$expectedinstance->password = 'test';
|
||||
$modifiedinstance = $manualplugin->update_enrol_plugin_data($course->id, $enrolmentdata, $instance);
|
||||
$this->assertEquals($expectedinstance, $modifiedinstance);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4549,63 +4549,72 @@ function get_complete_user_data($field, $value, $mnethostid = null, $throwexcept
|
||||
* Validate a password against the configured password policy
|
||||
*
|
||||
* @param string $password the password to be checked against the password policy
|
||||
* @param string $errmsg the error message to display when the password doesn't comply with the policy.
|
||||
* @param stdClass $user the user object to perform password validation against. Defaults to null if not provided.
|
||||
* @param ?array $errorsarray array of errors with string identifiers.
|
||||
* @param string|null $errmsg the error message to display when the password doesn't comply with the policy.
|
||||
* @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
|
||||
*
|
||||
* @return bool true if the password is valid according to the policy. false otherwise.
|
||||
*/
|
||||
function check_password_policy($password, &$errmsg, $user = null, ?array &$errorsarray = null) {
|
||||
function check_password_policy(string $password, ?string &$errmsg, ?stdClass $user = null) {
|
||||
global $CFG;
|
||||
if (!empty($CFG->passwordpolicy) && !isguestuser($user)) {
|
||||
$errors = get_password_policy_errors($password, $user);
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$errmsg .= '<div>' . $error . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
return $errmsg == '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a password against the configured password policy.
|
||||
* Note: This function is unaffected by whether the password policy is enabled or not.
|
||||
*
|
||||
* @param string $password the password to be checked against the password policy
|
||||
* @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
|
||||
*
|
||||
* @return string[] Array of error messages.
|
||||
*/
|
||||
function get_password_policy_errors(string $password, ?stdClass $user = null) : array {
|
||||
global $CFG;
|
||||
|
||||
if (!empty($CFG->passwordpolicy) && !isguestuser($user)) {
|
||||
$errmsg = '';
|
||||
if (core_text::strlen($password) < $CFG->minpasswordlength) {
|
||||
$errmsg .= '<div>'. get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errorminpasswordlength'] : null;
|
||||
}
|
||||
if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
|
||||
$errmsg .= '<div>'. get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errorminpassworddigits'] : null;
|
||||
}
|
||||
if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
|
||||
$errmsg .= '<div>'. get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errorminpasswordlower'] : null;
|
||||
}
|
||||
if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
|
||||
$errmsg .= '<div>'. get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errorminpasswordupper'] : null;
|
||||
}
|
||||
if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
|
||||
$errmsg .= '<div>'. get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errorminpasswordnonalphanum'] : null;
|
||||
}
|
||||
if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
|
||||
$errmsg .= '<div>'. get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars) .'</div>';
|
||||
$errorsarray = ($errorsarray !== null) ? [...$errorsarray, 'errormaxconsecutiveidentchars'] : null;
|
||||
}
|
||||
$errors = [];
|
||||
|
||||
// Fire any additional password policy functions from plugins.
|
||||
// Plugin functions should output an error message string or empty string for success.
|
||||
$pluginsfunction = get_plugins_with_function('check_password_policy');
|
||||
foreach ($pluginsfunction as $plugintype => $plugins) {
|
||||
foreach ($plugins as $pluginfunction) {
|
||||
$pluginerr = $pluginfunction($password, $user);
|
||||
if ($pluginerr) {
|
||||
$errmsg .= '<div>'. $pluginerr .'</div>';
|
||||
}
|
||||
if (core_text::strlen($password) < $CFG->minpasswordlength) {
|
||||
$errors[] = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength);
|
||||
}
|
||||
if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
|
||||
$errors[] = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits);
|
||||
}
|
||||
if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
|
||||
$errors[] = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower);
|
||||
}
|
||||
if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
|
||||
$errors[] = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper);
|
||||
}
|
||||
if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
|
||||
$errors[] = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
|
||||
}
|
||||
if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
|
||||
$errors[] = get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars);
|
||||
}
|
||||
|
||||
// Fire any additional password policy functions from plugins.
|
||||
// Plugin functions should output an error message string or empty string for success.
|
||||
$pluginsfunction = get_plugins_with_function('check_password_policy');
|
||||
foreach ($pluginsfunction as $plugintype => $plugins) {
|
||||
foreach ($plugins as $pluginfunction) {
|
||||
$pluginerr = $pluginfunction($password, $user);
|
||||
if ($pluginerr) {
|
||||
$errors[] = $pluginerr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errmsg == '') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When logging in, this function is run to set certain preferences for the current SESSION.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user