diff --git a/lang/en/moodle.php b/lang/en/moodle.php index f508a19b37b..40e83ca4b03 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -746,6 +746,7 @@ $string['eventusercreated'] = 'User created'; $string['eventuserdeleted'] = 'User deleted'; $string['eventuserlistviewed'] = 'User list viewed'; $string['eventuserloggedout'] = 'User logged out'; +$string['eventuserpasswordupdated'] = 'User password updated'; $string['eventuserprofileviewed'] = 'User profile viewed'; $string['eventuserupdated'] = 'User updated'; $string['everybody'] = 'Everybody'; diff --git a/lib/classes/event/user_password_updated.php b/lib/classes/event/user_password_updated.php new file mode 100644 index 00000000000..c0915ca56ec --- /dev/null +++ b/lib/classes/event/user_password_updated.php @@ -0,0 +1,132 @@ +. + +/** + * User password updated event. + * + * @package core + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\event; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Event when user password is changed or reset. + * + * @property-read array $other { + * Extra information about event. + * + * - bool forgottenreset: true means reset via token. + * } + * + * @package core + * @since Moodle 2.7 + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_password_updated extends base { + /** + * Create event for user password changing and resetting. + * + * @param \stdClass $user + * @param bool $forgottenreset true if reset via recovery link + * @return user_password_updated + */ + public static function create_from_user(\stdClass $user, $forgottenreset = false) { + $data = array( + 'context' => \context_user::instance($user->id), + 'relateduserid' => $user->id, + 'other' => array('forgottenreset' => $forgottenreset), + ); + $event = self::create($data); + $event->add_record_snapshot('user', $user); + return $event; + } + + /** + * Initialise required event data properties. + */ + protected function init() { + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_OTHER; + } + + /** + * Returns localised event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventuserpasswordupdated'); + } + + /** + * Returns non-localised event description with id's for admin use only. + * + * @return string + */ + public function get_description() { + if ($this->userid == $this->relateduserid) { + if ($this->other['forgottenreset']) { + return "User $this->userid reset their password"; + } + return "User $this->userid changed their password"; + } else { + return "User $this->userid changed password of user $this->relateduserid"; + } + } + + /** + * Returns relevant URL. + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url('/user/profile.php', array('id' => $this->relateduserid)); + } + + /** + * Returns array of parameters to be passed to legacy logging. + * + * @return array|null + */ + protected function get_legacy_logdata() { + if (!$this->other['forgottenreset']) { + // We did not log password changes in earlier versions. + return null; + } + return array(SITEID, 'user', 'set password', 'profile.php?id='.$this->userid, $this->relateduserid); + } + + /** + * Custom validation. + * + * @throws \coding_exception + */ + protected function validate_data() { + parent::validate_data(); + + if (!$this->relateduserid) { + throw new \coding_exception('relateduserid needs to be set.'); + } + + if (!isset($this->other['forgottenreset'])) { + throw new \coding_exception('forgottenreset needs to be set in $other.'); + } + } +} diff --git a/lib/tests/event_user_password_updated_test.php b/lib/tests/event_user_password_updated_test.php new file mode 100644 index 00000000000..96b2c0b298c --- /dev/null +++ b/lib/tests/event_user_password_updated_test.php @@ -0,0 +1,77 @@ +. + +/** + * Tests for password changes event. + * + * @package core + * @category phpunit + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tests for event \core\event\user_password_updated + * + * @package core + * @category phpunit + * @copyright 2014 Petr Skoda + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_event_user_password_updated_testcase extends advanced_testcase { + /** + * Test the event. + */ + public function test_event() { + $this->resetAfterTest(); + + $user1 = $this->getDataGenerator()->create_user(); + $context1 = context_user::instance($user1->id); + $user2 = $this->getDataGenerator()->create_user(); + $context2 = context_user::instance($user2->id); + + $this->setUser($user1); + + // Changing own password. + $event = \core\event\user_password_updated::create_from_user($user1); + $this->assertEventContextNotUsed($event); + $this->assertEquals($user1->id, $event->relateduserid); + $this->assertSame($context1, $event->get_context()); + $this->assertEventLegacyLogData(null, $event); + $this->assertFalse($event->other['forgottenreset']); + $event->trigger(); + + // Changing password of other user. + $event = \core\event\user_password_updated::create_from_user($user2); + $this->assertEventContextNotUsed($event); + $this->assertEquals($user2->id, $event->relateduserid); + $this->assertSame($context2, $event->get_context()); + $this->assertEventLegacyLogData(null, $event); + $this->assertFalse($event->other['forgottenreset']); + $event->trigger(); + + // Password reset. + $event = \core\event\user_password_updated::create_from_user($user1, true); + $this->assertEventContextNotUsed($event); + $this->assertEquals($user1->id, $event->relateduserid); + $this->assertSame($context1, $event->get_context()); + $this->assertEventLegacyLogData(array(SITEID, 'user', 'set password', 'profile.php?id='.$user1->id, $user1->id), $event); + $this->assertTrue($event->other['forgottenreset']); + $event->trigger(); + } +} diff --git a/login/change_password.php b/login/change_password.php index cb433d5c5c4..ba8e8db1d0c 100644 --- a/login/change_password.php +++ b/login/change_password.php @@ -122,6 +122,9 @@ if ($mform->is_cancelled()) { unset_user_preference('auth_forcepasswordchange', $USER); unset_user_preference('create_password', $USER); + $user = $DB->get_record('user', array('id' => $USER->id), '*', MUST_EXIST); + \core\event\user_password_updated::create_from_user($user)->trigger(); + $strpasswordchanged = get_string('passwordchanged'); $fullname = fullname($USER, true); diff --git a/login/lib.php b/login/lib.php index ab1ef1f83c2..d0b8f5d2c34 100644 --- a/login/lib.php +++ b/login/lib.php @@ -169,10 +169,11 @@ function core_login_process_password_reset_request() { echo $OUTPUT->footer(); } -/** This function processes a user's submitted token to validate the request to set a new password. - * If the user's token is validated, they are prompted to set a new password. +/** + * This function processes a user's submitted token to validate the request to set a new password. + * If the user's token is validated, they are prompted to set a new password. * @param string $token the one-use identifier which should verify the password reset request as being valid. - * @return null + * @return void */ function core_login_process_password_set($token) { global $DB, $CFG, $OUTPUT, $PAGE, $SESSION; @@ -238,7 +239,6 @@ function core_login_process_password_set($token) { if (!$userauth->user_update_password($user, $data->password)) { print_error('errorpasswordupdate', 'auth'); } - add_to_log(SITEID, 'user', 'set password', "view.php?id=$user->id&course=" . SITEID, $user->id); // Reset login lockout (if present) before a new password is set. login_unlock_account($user); // Clear any requirement to change passwords. @@ -249,8 +249,11 @@ function core_login_process_password_set($token) { // Unset previous session language - use user preference instead. unset($SESSION->lang); } - add_to_log(SITEID, 'user', 'login', "view.php?id=$user->id&course=".SITEID, $user->id, 0, $user->id); - complete_user_login($user); + complete_user_login($user); // Triggers the login event. + + $user = $DB->get_record('user', array('id' => $user->id), '*', MUST_EXIST); + \core\event\user_password_updated::create_from_user($user, true)->trigger(); + $urltogo = core_login_get_return_url(); unset($SESSION->wantsurl); redirect($urltogo, get_string('passwordset'), 1); diff --git a/user/editadvanced.php b/user/editadvanced.php index a70c1106fbe..047868e62fb 100644 --- a/user/editadvanced.php +++ b/user/editadvanced.php @@ -166,6 +166,7 @@ if ($usernew = $userform->get_data()) { $usernew->timemodified = time(); $createpassword = false; + $passwordupdated = false; if ($usernew->id == -1) { unset($usernew->id); @@ -190,6 +191,8 @@ if ($usernew = $userform->get_data()) { if (!$authplugin->user_update_password($usernew, $usernew->newpassword)) { // Do not stop here, we need to finish user creation. debugging(get_string('cannotupdatepasswordonextauth', '', '', $usernew->auth), DEBUG_NONE); + } else { + $passwordupdated = true; } } @@ -207,6 +210,8 @@ if ($usernew = $userform->get_data()) { if ($authplugin->can_change_password()) { if (!$authplugin->user_update_password($usernew, $usernew->newpassword)) { print_error('cannotupdatepasswordonextauth', '', '', $usernew->auth); + } else { + $passwordupdated = true; } unset_user_preference('create_password', $usernew); // Prevent cron from generating the password. } @@ -245,6 +250,10 @@ if ($usernew = $userform->get_data()) { // Reload from db. $usernew = $DB->get_record('user', array('id' => $usernew->id)); + if ($passwordupdated) { + \core\event\user_password_updated::create_from_user($usernew)->trigger(); + } + if ($createpassword) { setnew_password_and_mail($usernew); unset_user_preference('create_password', $usernew);