diff --git a/admin/tool/uploaduser/index.php b/admin/tool/uploaduser/index.php index b07f4dc0d61..cb95ff6f9f8 100644 --- a/admin/tool/uploaduser/index.php +++ b/admin/tool/uploaduser/index.php @@ -669,7 +669,7 @@ if ($formdata = $mform2->is_cancelled()) { if ($doupdate or $existinguser->password !== $oldpw) { // We want only users that were really updated. - user_update_user($existinguser, false); + user_update_user($existinguser, false, false); $upt->track('status', $struserupdated); $usersupdated++; @@ -687,6 +687,9 @@ if ($formdata = $mform2->is_cancelled()) { } } + // Trigger event. + \core\event\user_updated::create_from_userid($existinguser->id)->trigger(); + } else { // no user information changed $upt->track('status', $struseruptodate); @@ -800,7 +803,7 @@ if ($formdata = $mform2->is_cancelled()) { $upt->track('password', '-', 'normal', false); } - $user->id = user_create_user($user, false); + $user->id = user_create_user($user, false, false); $upt->track('username', html_writer::link(new moodle_url('/user/profile.php', array('id'=>$user->id)), s($user->username)), 'normal', false); // pre-process custom profile menu fields data from csv file @@ -815,6 +818,9 @@ if ($formdata = $mform2->is_cancelled()) { set_user_preference('create_password', 1, $user); } + // Trigger event. + \core\event\user_created::create_from_userid($user->id)->trigger(); + $upt->track('status', $struseradded); $upt->track('id', $user->id, 'normal', false); $usersnew++; diff --git a/auth/email/auth.php b/auth/email/auth.php index b071e3252d9..2c36eabcb33 100644 --- a/auth/email/auth.php +++ b/auth/email/auth.php @@ -94,11 +94,14 @@ class auth_plugin_email extends auth_plugin_base { $user->calendartype = $CFG->calendartype; } - $user->id = user_create_user($user, false); + $user->id = user_create_user($user, false, false); // Save any custom profile field information. profile_save_data($user); + // Trigger event. + \core\event\user_created::create_from_userid($user->id)->trigger(); + if (! send_confirmation_email($user)) { print_error('auth_emailnoemail','auth_email'); } diff --git a/auth/ldap/auth.php b/auth/ldap/auth.php index 87274c908a2..1c8b1c95f99 100644 --- a/auth/ldap/auth.php +++ b/auth/ldap/auth.php @@ -551,7 +551,7 @@ class auth_plugin_ldap extends auth_plugin_base { print_error('auth_ldap_create_error', 'auth_ldap'); } - $user->id = user_create_user($user, false); + $user->id = user_create_user($user, false, false); // Save any custom profile field information profile_save_data($user); @@ -564,6 +564,8 @@ class auth_plugin_ldap extends auth_plugin_base { $user = $DB->get_record('user', array('id'=>$user->id)); + \core\event\user_created::create_from_userid($user->id)->trigger(); + if (! send_confirmation_email($user)) { print_error('noemail', 'auth_ldap'); } @@ -1027,7 +1029,7 @@ class auth_plugin_ldap extends auth_plugin_base { } } } - user_update_user($newuser, false); + user_update_user($newuser, false, false); } } else { return false; diff --git a/auth/ldap/tests/plugin_test.php b/auth/ldap/tests/plugin_test.php index 13cdcc083b9..ddd3d44dcf2 100644 --- a/auth/ldap/tests/plugin_test.php +++ b/auth/ldap/tests/plugin_test.php @@ -279,6 +279,142 @@ class auth_ldap_plugin_testcase extends advanced_testcase { $this->assertEventLegacyLogData($expectedlog, $event); } + /** + * Test logging in via LDAP calls a user_loggedin event. + */ + public function test_ldap_user_signup() { + global $CFG, $DB; + + // User to create. + $user = array( + 'username' => 'usersignuptest1', + 'password' => 'Moodle2014!', + 'idnumber' => 'idsignuptest1', + 'firstname' => 'First Name User Test 1', + 'lastname' => 'Last Name User Test 1', + 'middlename' => 'Middle Name User Test 1', + 'lastnamephonetic' => '最後のお名前のテスト一号', + 'firstnamephonetic' => 'お名前のテスト一号', + 'alternatename' => 'Alternate Name User Test 1', + 'email' => 'usersignuptest1@email.com', + 'description' => 'This is a description for user 1', + 'city' => 'Perth', + 'country' => 'au', + 'mnethostid' => $CFG->mnet_localhost_id, + 'auth' => 'ldap' + ); + + if (!extension_loaded('ldap')) { + $this->markTestSkipped('LDAP extension is not loaded.'); + } + + $this->resetAfterTest(); + + require_once($CFG->dirroot.'/auth/ldap/auth.php'); + require_once($CFG->libdir.'/ldaplib.php'); + + if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) { + $this->markTestSkipped('External LDAP test server not configured.'); + } + + // Make sure we can connect the server. + $debuginfo = ''; + if (!$connection = ldap_connect_moodle(TEST_AUTH_LDAP_HOST_URL, 3, 'rfc2307', TEST_AUTH_LDAP_BIND_DN, TEST_AUTH_LDAP_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) { + $this->markTestSkipped('Can not connect to LDAP test server: '.$debuginfo); + } + + $this->enable_plugin(); + + // Create new empty test container. + $topdn = 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN; + + $this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest'); + + $o = array(); + $o['objectClass'] = array('dcObject', 'organizationalUnit'); + $o['dc'] = 'moodletest'; + $o['ou'] = 'MOODLETEST'; + if (!ldap_add($connection, 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN, $o)) { + $this->markTestSkipped('Can not create test LDAP container.'); + } + + // Create a few users. + $o = array(); + $o['objectClass'] = array('organizationalUnit'); + $o['ou'] = 'users'; + ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o); + + // Configure the plugin a bit. + set_config('host_url', TEST_AUTH_LDAP_HOST_URL, 'auth/ldap'); + set_config('start_tls', 0, 'auth/ldap'); + set_config('ldap_version', 3, 'auth/ldap'); + set_config('ldapencoding', 'utf-8', 'auth/ldap'); + set_config('pagesize', '2', 'auth/ldap'); + set_config('bind_dn', TEST_AUTH_LDAP_BIND_DN, 'auth/ldap'); + set_config('bind_pw', TEST_AUTH_LDAP_BIND_PW, 'auth/ldap'); + set_config('user_type', 'rfc2307', 'auth/ldap'); + set_config('contexts', 'ou=users,'.$topdn, 'auth/ldap'); + set_config('search_sub', 0, 'auth/ldap'); + set_config('opt_deref', LDAP_DEREF_NEVER, 'auth/ldap'); + set_config('user_attribute', 'cn', 'auth/ldap'); + set_config('memberattribute', 'memberuid', 'auth/ldap'); + set_config('memberattribute_isdn', 0, 'auth/ldap'); + set_config('creators', 'cn=creators,'.$topdn, 'auth/ldap'); + set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth/ldap'); + + set_config('field_map_email', 'mail', 'auth/ldap'); + set_config('field_updatelocal_email', 'oncreate', 'auth/ldap'); + set_config('field_updateremote_email', '0', 'auth/ldap'); + set_config('field_lock_email', 'unlocked', 'auth/ldap'); + + set_config('field_map_firstname', 'givenName', 'auth/ldap'); + set_config('field_updatelocal_firstname', 'oncreate', 'auth/ldap'); + set_config('field_updateremote_firstname', '0', 'auth/ldap'); + set_config('field_lock_firstname', 'unlocked', 'auth/ldap'); + + set_config('field_map_lastname', 'sn', 'auth/ldap'); + set_config('field_updatelocal_lastname', 'oncreate', 'auth/ldap'); + set_config('field_updateremote_lastname', '0', 'auth/ldap'); + set_config('field_lock_lastname', 'unlocked', 'auth/ldap'); + set_config('passtype', 'md5', 'auth/ldap'); + set_config('create_context', 'ou=users,'.$topdn, 'auth/ldap'); + + $this->assertEquals(2, $DB->count_records('user')); + $this->assertEquals(0, $DB->count_records('role_assignments')); + + /** @var auth_plugin_ldap $auth */ + $auth = get_auth_plugin('ldap'); + + $sink = $this->redirectEvents(); + $auth->user_signup((object)$user, false); + $this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting'); + $events = $sink->get_events(); + $sink->close(); + + // Verify 2 events get generated. + $this->assertCount(2, $events); + + // Get record from db. + $dbuser = $DB->get_record('user', array('username' => $user['username'])); + $user['id'] = $dbuser->id; + + // Last event is user_created. + $event = array_pop($events); + $this->assertInstanceOf('\core\event\user_created', $event); + $this->assertEquals($user['id'], $event->objectid); + $this->assertEquals('user_created', $event->get_legacy_eventname()); + $this->assertEquals(context_user::instance($user['id']), $event->get_context()); + $expectedlogdata = array(SITEID, 'user', 'add', '/view.php?id='.$event->objectid, fullname($dbuser)); + $this->assertEventLegacyLogData($expectedlogdata, $event); + + // First event is user_password_updated. + $event = array_pop($events); + $this->assertInstanceOf('\core\event\user_updated', $event); + + // Delete user which we just created. + ldap_delete($connection, 'cn='.$user['username'].',ou=users,'.$topdn); + } + protected function create_ldap_user($connection, $topdn, $i) { $o = array(); $o['objectClass'] = array('inetOrgPerson', 'organizationalPerson', 'person', 'posixAccount'); diff --git a/lib/classes/event/user_created.php b/lib/classes/event/user_created.php index 265eff22b8c..bd991deefd5 100644 --- a/lib/classes/event/user_created.php +++ b/lib/classes/event/user_created.php @@ -97,4 +97,24 @@ class user_created extends base { protected function get_legacy_logdata() { return array(SITEID, 'user', 'add', '/view.php?id='.$this->objectid, fullname($this->get_legacy_eventdata())); } + + /** + * Create instance of event. + * + * @since Moodle 2.6.4, 2.7.1 + * + * @param int $userid id of user + * @return user_created + */ + public static function create_from_userid($userid) { + $data = array( + 'objectid' => $userid, + 'relateduserid' => $userid, + 'context' => \context_user::instance($userid) + ); + + // Create user_created event. + $event = self::create($data); + return $event; + } } diff --git a/lib/classes/event/user_updated.php b/lib/classes/event/user_updated.php index ad2940b6cda..59b9f2c8346 100644 --- a/lib/classes/event/user_updated.php +++ b/lib/classes/event/user_updated.php @@ -97,4 +97,24 @@ class user_updated extends base { protected function get_legacy_logdata() { return array(SITEID, 'user', 'update', 'view.php?id='.$this->objectid, ''); } + + /** + * Create instance of event. + * + * @since Moodle 2.6.4, 2.7.1 + * + * @param int $userid id of user + * @return user_updated + */ + public static function create_from_userid($userid) { + $data = array( + 'objectid' => $userid, + 'relateduserid' => $userid, + 'context' => \context_user::instance($userid) + ); + + // Create user_updated event. + $event = self::create($data); + return $event; + } } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 929c1bcdcda..41778f7d2a7 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -4026,7 +4026,7 @@ function create_user_record($username, $password, $auth = 'manual') { $newuser->timemodified = $newuser->timecreated; $newuser->mnethostid = $CFG->mnet_localhost_id; - $newuser->id = user_create_user($newuser, false); + $newuser->id = user_create_user($newuser, false, false); // Save user profile data. profile_save_data($newuser); @@ -4038,6 +4038,9 @@ function create_user_record($username, $password, $auth = 'manual') { // Set the password. update_internal_user_password($user, $password); + // Trigger event. + \core\event\user_created::create_from_userid($newuser->id)->trigger(); + return $user; } @@ -4093,10 +4096,13 @@ function update_user_record($username) { if ($newuser) { $newuser['id'] = $oldinfo->id; $newuser['timemodified'] = time(); - user_update_user((object) $newuser, false); + user_update_user((object) $newuser, false, false); // Save user profile data. profile_save_data((object) $newuser); + + // Trigger event. + \core\event\user_updated::create_from_userid($newuser['id'])->trigger(); } } @@ -4255,7 +4261,8 @@ function delete_user(stdClass $user) { $updateuser->picture = 0; $updateuser->timemodified = time(); - user_update_user($updateuser, false); + // Don't trigger update event, as user is being deleted. + user_update_user($updateuser, false, false); // Now do a final accesslib cleanup - removes all role assignments in user context and context itself. context_helper::delete_instance(CONTEXT_USER, $user->id); @@ -4697,9 +4704,13 @@ function update_internal_user_password($user, $password) { $passwordchanged = ($user->password !== $hashedpassword); $algorithmchanged = false; } else { - // If verification fails then it means the password has changed. - $passwordchanged = !password_verify($password, $user->password); - $algorithmchanged = password_needs_rehash($user->password, PASSWORD_DEFAULT); + if (isset($user->password)) { + // If verification fails then it means the password has changed. + $passwordchanged = !password_verify($password, $user->password); + $algorithmchanged = password_needs_rehash($user->password, PASSWORD_DEFAULT); + } else { + $passwordchanged = true; + } } if ($passwordchanged || $algorithmchanged) { diff --git a/lib/upgrade.txt b/lib/upgrade.txt index d7caeca1337..012a4bb3908 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -1,6 +1,13 @@ This files describes API changes in core libraries and APIs, information provided here is intended especially for developers. +=== 2.6.4 / 2.7.1 === + +* update_internal_user_password() accepts optional boolean $fasthash for fast + hashing. +* user_update_user() and user_create_user() api's accept optional param + $triggerevent to avoid respective events to be triggred from the api's. + === 2.5.5 / 2.6.2 / 2.7 === * New "Time spent waiting for the database" performance metric displayed along with the diff --git a/user/edit.php b/user/edit.php index b17eb989a98..7b2dc73c32f 100644 --- a/user/edit.php +++ b/user/edit.php @@ -221,7 +221,7 @@ if ($usernew = $userform->get_data()) { } // Update user with new profile data. - user_update_user($usernew, false); + user_update_user($usernew, false, false); //update preferences useredit_update_user_preference($usernew); @@ -245,6 +245,9 @@ if ($usernew = $userform->get_data()) { // save custom profile fields data profile_save_data($usernew); + // Trigger event. + \core\event\user_updated::create_from_userid($user->id)->trigger(); + // If email was changed and confirmation is required, send confirmation email now to the new address. if ($email_changed && $CFG->emailchangeconfirmation) { $temp_user = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST); diff --git a/user/editadvanced.php b/user/editadvanced.php index 5c808731e5d..c5d77d838de 100644 --- a/user/editadvanced.php +++ b/user/editadvanced.php @@ -156,6 +156,7 @@ $userform = new user_editadvanced_form(null, array( $userform->set_data($user); if ($usernew = $userform->get_data()) { + $usercreated = false; if (empty($usernew->auth)) { //user editing self @@ -182,7 +183,7 @@ if ($usernew = $userform->get_data()) { } else { $usernew->password = hash_internal_user_password($usernew->newpassword); } - $usernew->id = user_create_user($usernew, false); + $usernew->id = user_create_user($usernew, false, false); } else { $usernew = file_postupdate_standard_editor($usernew, 'description', $editoroptions, $usercontext, 'user', 'profile', 0); // Pass a true old $user here. @@ -190,7 +191,7 @@ if ($usernew = $userform->get_data()) { // Auth update failed. print_error('cannotupdateuseronexauth', '', '', $user->auth); } - user_update_user($usernew, false); + user_update_user($usernew, false, false); //set new password if specified if (!empty($usernew->newpassword)) { diff --git a/user/externallib.php b/user/externallib.php index 6cc2e04a6dd..0de43b32d12 100644 --- a/user/externallib.php +++ b/user/externallib.php @@ -158,8 +158,8 @@ class core_user_external extends external_api { } // End of user info validation. - // create the user data now! - $user['id'] = user_create_user($user); + // Create the user data now! + $user['id'] = user_create_user($user, true, false); // custom fields if(!empty($user['customfields'])) { @@ -171,6 +171,9 @@ class core_user_external extends external_api { profile_save_data((object) $user); } + // Trigger event. + \core\event\user_created::create_from_userid($user['id'])->trigger(); + //preferences if (!empty($user['preferences'])) { foreach($user['preferences'] as $preference) { @@ -341,10 +344,9 @@ class core_user_external extends external_api { $transaction = $DB->start_delegated_transaction(); foreach ($params['users'] as $user) { - user_update_user($user); + user_update_user($user, true, false); //update user custom fields if(!empty($user['customfields'])) { - foreach($user['customfields'] as $customfield) { $user["profile_field_".$customfield['type']] = $customfield['value']; //profile_save_data() saves profile file //it's expecting a user with the correct id, @@ -352,6 +354,9 @@ class core_user_external extends external_api { } profile_save_data((object) $user); } + + // Trigger event. + \core\event\user_updated::create_from_userid($user['id'])->trigger(); //preferences if (!empty($user['preferences'])) { diff --git a/user/lib.php b/user/lib.php index 62d21452e14..4d1359901f0 100644 --- a/user/lib.php +++ b/user/lib.php @@ -30,9 +30,10 @@ * * @param stdClass $user user to create * @param bool $updatepassword if true, authentication plugin will update password. + * @param bool $triggerevent set false if user_created event should not be triggred. * @return int id of the newly created user */ -function user_create_user($user, $updatepassword = true) { +function user_create_user($user, $updatepassword = true, $triggerevent = true) { global $CFG, $DB; // Set the timecreate field to the current time. @@ -88,14 +89,10 @@ function user_create_user($user, $updatepassword = true) { $authplugin->user_update_password($newuser, $userpassword); } - // Trigger event. - $event = \core\event\user_created::create( - array( - 'objectid' => $newuserid, - 'context' => $usercontext - ) - ); - $event->trigger(); + // Trigger event If required. + if ($triggerevent) { + \core\event\user_created::create_from_userid($newuserid)->trigger(); + } return $newuserid; } @@ -105,8 +102,9 @@ function user_create_user($user, $updatepassword = true) { * * @param stdClass $user the user to update * @param bool $updatepassword if true, authentication plugin will update password. + * @param bool $triggerevent set false if user_updated event should not be triggred. */ -function user_update_user($user, $updatepassword = true) { +function user_update_user($user, $updatepassword = true, $triggerevent = true) { global $DB; // set the timecreate field to the current time @@ -164,15 +162,10 @@ function user_update_user($user, $updatepassword = true) { } } } - - // Trigger event. - $event = \core\event\user_updated::create( - array( - 'objectid' => $user->id, - 'context' => context_user::instance($user->id) - ) - ); - $event->trigger(); + // Trigger event if required. + if ($triggerevent) { + \core\event\user_updated::create_from_userid($user->id)->trigger(); + } } /** diff --git a/user/tests/userlib_test.php b/user/tests/userlib_test.php index 9959d0fe49d..204428d5e79 100644 --- a/user/tests/userlib_test.php +++ b/user/tests/userlib_test.php @@ -76,6 +76,22 @@ class core_userliblib_testcase extends advanced_testcase { user_update_user($user, false); $dbuser = $DB->get_record('user', array('id' => $user->id)); $this->assertSame($password, $dbuser->password); + + // Verify event is not triggred by user_update_user when needed. + $sink = $this->redirectEvents(); + user_update_user($user, false, false); + $events = $sink->get_events(); + $sink->close(); + $this->assertCount(0, $events); + + // With password, there should be 1 event. + $sink = $this->redirectEvents(); + user_update_user($user, true, false); + $events = $sink->get_events(); + $sink->close(); + $this->assertCount(1, $events); + $event = array_pop($events); + $this->assertInstanceOf('\core\event\user_updated', $event); } /** @@ -128,5 +144,13 @@ class core_userliblib_testcase extends advanced_testcase { $this->assertEventLegacyData($dbuser, $event); $expectedlogdata = array(SITEID, 'user', 'add', '/view.php?id='.$event->objectid, fullname($dbuser)); $this->assertEventLegacyLogData($expectedlogdata, $event); + + // Verify event is not triggred by user_create_user when needed. + $user = array('username' => 'usernametest2'); // Create another user. + $sink = $this->redirectEvents(); + user_create_user($user, true, false); + $events = $sink->get_events(); + $sink->close(); + $this->assertCount(0, $events); } }