/** * Compare password against hash stored in user object to determine if it is valid. * * If necessary it also updates the stored hash to the current format. * * @param stdClass $user (Password property may be updated). * @param string $password Plain text password. * @return bool True if password is valid. */ function validate_internal_user_password($user, $password) { global $CFG; if ($user->password === AUTH_PASSWORD_NOT_CACHED) { // Internal password is not used at all, it can not validate. return false; } // If hash isn't a legacy (md5) hash, validate using the library function. if (!password_is_legacy_hash($user->password)) { return password_verify($password, $user->password); } // Otherwise we need to check for a legacy (md5) hash instead. If the hash // is valid we can then update it to the new algorithm. $sitesalt = isset($CFG->passwordsaltmain) ? $CFG->passwordsaltmain : ''; $validated = false; if ($user->password === md5($password . $sitesalt) or $user->password === md5($password) or $user->password === md5(addslashes($password) . $sitesalt) or $user->password === md5(addslashes($password))) { // Note: we are intentionally using the addslashes() here because we // need to accept old password hashes of passwords with magic quotes. $validated = true; } else { for ($i = 1; $i <= 20; $i++) { // 20 alternative salts should be enough, right? $alt = 'passwordsaltalt' . $i; if (!empty($CFG->{$alt})) { if ($user->password === md5($password . $CFG->{$alt}) or $user->password === md5(addslashes($password) . $CFG->{$alt})) { $validated = true; break; } } } } if ($validated) { // If the password matches the existing md5 hash, update to the // current hash algorithm while we have access to the user's password. update_internal_user_password($user, $password); } return $validated; }
/** * Test function update_internal_user_password(). */ public function test_update_internal_user_password() { global $DB; $this->resetAfterTest(); $passwords = array('password', '1234', 'changeme', '****'); foreach ($passwords as $password) { $user = $this->getDataGenerator()->create_user(array('auth' => 'manual')); update_internal_user_password($user, $password); // The user object should have been updated. $this->assertTrue(validate_internal_user_password($user, $password)); // The database field for the user should also have been updated to the // same value. $this->assertSame($user->password, $DB->get_field('user', 'password', array('id' => $user->id))); } $user = $this->getDataGenerator()->create_user(array('auth' => 'manual')); // Manually set the user's password to the md5 of the string 'password'. $DB->set_field('user', 'password', '5f4dcc3b5aa765d61d8327deb882cf99', array('id' => $user->id)); // Update the password. update_internal_user_password($user, 'password'); if (password_compat_not_supported()) { // If bcrypt not properly supported the password should remain as an md5 hash. $expected_hash = hash_internal_user_password('password', true); $this->assertSame($user->password, $expected_hash); $this->assertTrue(password_is_legacy_hash($user->password)); } else { // Otherwise password should have been updated to a bcrypt hash. $this->assertFalse(password_is_legacy_hash($user->password)); } }
/** * Test function update_internal_user_password(). */ public function test_update_internal_user_password() { global $DB; $this->resetAfterTest(); $passwords = array('password', '1234', 'changeme', '****'); foreach ($passwords as $password) { $user = $this->getDataGenerator()->create_user(array('auth' => 'manual')); update_internal_user_password($user, $password); // The user object should have been updated. $this->assertTrue(validate_internal_user_password($user, $password)); // The database field for the user should also have been updated to the // same value. $this->assertSame($user->password, $DB->get_field('user', 'password', array('id' => $user->id))); } $user = $this->getDataGenerator()->create_user(array('auth' => 'manual')); // Manually set the user's password to the md5 of the string 'password'. $DB->set_field('user', 'password', '5f4dcc3b5aa765d61d8327deb882cf99', array('id' => $user->id)); $sink = $this->redirectEvents(); // Update the password. update_internal_user_password($user, 'password'); $events = $sink->get_events(); $sink->close(); $event = array_pop($events); // Password should have been updated to a bcrypt hash. $this->assertFalse(password_is_legacy_hash($user->password)); // Verify event information. $this->assertInstanceOf('\\core\\event\\user_password_updated', $event); $this->assertSame($user->id, $event->relateduserid); $this->assertEquals(context_user::instance($user->id), $event->get_context()); $this->assertEventContextNotUsed($event); // Verify recovery of property 'auth'. unset($user->auth); update_internal_user_password($user, 'newpassword'); $this->assertDebuggingCalled('User record in update_internal_user_password() must include field auth', DEBUG_DEVELOPER); $this->assertEquals('manual', $user->auth); }