diff --git a/core/includes/password.inc b/core/includes/password.inc deleted file mode 100644 index b052a4a..0000000 --- a/core/includes/password.inc +++ /dev/null @@ -1,288 +0,0 @@ -> 6) & 0x3f]; - if ($i++ >= $count) { - break; - } - if ($i < $count) { - $value |= ord($input[$i]) << 16; - } - $output .= $itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) { - break; - } - $output .= $itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; -} - -/** - * Generates a random base 64-encoded salt prefixed with settings for the hash. - * - * Proper use of salts may defeat a number of attacks, including: - * - The ability to try candidate passwords against multiple hashes at once. - * - The ability to use pre-hashed lists of candidate passwords. - * - The ability to determine whether two users have the same (or different) - * password without actually having to guess one of the passwords. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * A 12 character string containing the iteration count and a random salt. - */ -function _password_generate_salt($count_log2) { - $output = '$S$'; - // Ensure that $count_log2 is within set bounds. - $count_log2 = _password_enforce_log2_boundaries($count_log2); - // We encode the final log2 iteration count in base 64. - $itoa64 = _password_itoa64(); - $output .= $itoa64[$count_log2]; - // 6 bytes is the standard salt for a portable phpass hash. - $output .= _password_base64_encode(drupal_random_bytes(6), 6); - return $output; -} - -/** - * Ensures that $count_log2 is within set bounds. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * Integer within set bounds that is closest to $count_log2. - */ -function _password_enforce_log2_boundaries($count_log2) { - if ($count_log2 < DRUPAL_MIN_HASH_COUNT) { - return DRUPAL_MIN_HASH_COUNT; - } - elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) { - return DRUPAL_MAX_HASH_COUNT; - } - - return (int) $count_log2; -} - -/** - * Hash a password using a secure stretched hash. - * - * By using a salt and repeated hashing the password is "stretched". Its - * security is increased because it becomes much more computationally costly - * for an attacker to try to break the hash by brute-force computation of the - * hashes of a large number of plain-text words or strings to find a match. - * - * @param $algo - * The string name of a hashing algorithm usable by hash(), like 'sha256'. - * @param $password - * The plain-text password to hash. - * @param $setting - * An existing hash or the output of _password_generate_salt(). Must be - * at least 12 characters (the settings and salt). - * - * @return - * A string containing the hashed password (and salt) or FALSE on failure. - * The return string will be truncated at DRUPAL_HASH_LENGTH characters max. - */ -function _password_crypt($algo, $password, $setting) { - // The first 12 characters of an existing hash are its setting string. - $setting = substr($setting, 0, 12); - - if ($setting[0] != '$' || $setting[2] != '$') { - return FALSE; - } - $count_log2 = _password_get_count_log2($setting); - // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT - if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) { - return FALSE; - } - $salt = substr($setting, 4, 8); - // Hashes must have an 8 character salt. - if (strlen($salt) != 8) { - return FALSE; - } - - // Convert the base 2 logarithm into an integer. - $count = 1 << $count_log2; - - // We rely on the hash() function being available in PHP 5.2+. - $hash = hash($algo, $salt . $password, TRUE); - do { - $hash = hash($algo, $hash . $password, TRUE); - } while (--$count); - - $len = strlen($hash); - $output = $setting . _password_base64_encode($hash, $len); - // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. - // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. - $expected = 12 + ceil((8 * $len) / 6); - return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE; -} - -/** - * Parse the log2 iteration count from a stored hash or setting string. - */ -function _password_get_count_log2($setting) { - $itoa64 = _password_itoa64(); - return strpos($itoa64, $setting[3]); -} - -/** - * Hash a password using a secure hash. - * - * @param $password - * A plain-text password. - * @param $count_log2 - * Optional integer to specify the iteration count. Generally used only during - * mass operations where a value less than the default is needed for speed. - * - * @return - * A string containing the hashed password (and a salt), or FALSE on failure. - */ -function user_hash_password($password, $count_log2 = 0) { - if (empty($count_log2)) { - // Use the standard iteration count. - $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); - } - return _password_crypt('sha512', $password, _password_generate_salt($count_log2)); -} - -/** - * Check whether a plain text password matches a stored hashed password. - * - * Alternative implementations of this function may use other data in the - * $account object, for example the uid to look up the hash in a custom table - * or remote database. - * - * @param $password - * A plain-text password - * @param $account - * A user object with at least the fields from the {users} table. - * - * @return - * TRUE or FALSE. - */ -function user_check_password($password, $account) { - if (substr($account->pass, 0, 2) == 'U$') { - // This may be an updated password from user_update_7000(). Such hashes - // have 'U' added as the first character and need an extra md5() (see the - // Drupal 7 documentation). - $stored_hash = substr($account->pass, 1); - $password = md5($password); - } - else { - $stored_hash = $account->pass; - } - - $type = substr($stored_hash, 0, 3); - switch ($type) { - case '$S$': - // A normal Drupal 7 password using sha512. - $hash = _password_crypt('sha512', $password, $stored_hash); - break; - case '$H$': - // phpBB3 uses "$H$" for the same thing as "$P$". - case '$P$': - // A phpass password generated using md5. This is an - // imported password or from an earlier Drupal version. - $hash = _password_crypt('md5', $password, $stored_hash); - break; - default: - return FALSE; - } - return ($hash && $stored_hash == $hash); -} - -/** - * Check whether a user's hashed password needs to be replaced with a new hash. - * - * This is typically called during the login process when the plain text - * password is available. A new hash is needed when the desired iteration count - * has changed through a change in the variable password_count_log2 or - * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update - * like user_update_7000() (see the Drupal 7 documentation). - * - * Alternative implementations of this function might use other criteria based - * on the fields in $account. - * - * @param $account - * A user object with at least the fields from the {users} table. - * - * @return - * TRUE or FALSE. - */ -function user_needs_new_hash($account) { - // Check whether this was an updated password. - if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) { - return TRUE; - } - // Ensure that $count_log2 is within set bounds. - $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT)); - // Check whether the iteration count used differs from the standard number. - return (_password_get_count_log2($account->pass) !== $count_log2); -} diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index bbc4e2e..bf9e7cb 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -80,6 +80,15 @@ public function build(ContainerBuilder $container) { $container->register('nested_matcher', 'Drupal\Core\Routing\NestedMatcher') ->addTag('chained_matcher', array('priority' => 5)); + // Add password hashing service. The argument to PhpassHashedPassword + // constructor is the log2 number of iterations for password stretching. + // This should increase by 1 every Drupal version in order to counteract + // increases in the speed and power of computers available to crack the + // hashes. The current password hashing method was introduced in Drupal 7 + // with a log2 count of 15. + $container->register('password', 'Drupal\Core\Password\PhpassHashedPassword') + ->addArgument(16); + // The following services are tagged as 'nested_matcher' services and are // processed in the RegisterNestedMatchersPass compiler pass. Each one // needs to be set on the matcher using a different method, so we use a diff --git a/core/lib/Drupal/Core/Password/PasswordInterface.php b/core/lib/Drupal/Core/Password/PasswordInterface.php new file mode 100644 index 0000000..e14a6d8 --- /dev/null +++ b/core/lib/Drupal/Core/Password/PasswordInterface.php @@ -0,0 +1,64 @@ +countLog2 = $this->enforceLog2Boundaries($countLog2); + } + + /** + * Encode bytes into printable base 64 using the *nix standard from crypt(). + * + * @param String $input + * The string containing bytes to encode. + * @param Integer $count + * The number of characters (bytes) to encode. + * + * @return String + * Encoded string + */ + protected function base64Encode($input, $count) { + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= static::$ITOA64[$value & 0x3f]; + if ($i < $count) { + $value |= ord($input[$i]) << 8; + } + $output .= static::$ITOA64[($value >> 6) & 0x3f]; + if ($i++ >= $count) { + break; + } + if ($i < $count) { + $value |= ord($input[$i]) << 16; + } + $output .= static::$ITOA64[($value >> 12) & 0x3f]; + if ($i++ >= $count) { + break; + } + $output .= static::$ITOA64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + /** + * Generates a random base 64-encoded salt prefixed with settings for the hash. + * + * Proper use of salts may defeat a number of attacks, including: + * - The ability to try candidate passwords against multiple hashes at once. + * - The ability to use pre-hashed lists of candidate passwords. + * - The ability to determine whether two users have the same (or different) + * password without actually having to guess one of the passwords. + * + * @return String + * A 12 character string containing the iteration count and a random salt. + */ + protected function generateSalt() { + $output = '$S$'; + // We encode the final log2 iteration count in base 64. + $output .= static::$ITOA64[$this->countLog2]; + // 6 bytes is the standard salt for a portable phpass hash. + $output .= $this->base64Encode(drupal_random_bytes(6), 6); + return $output; + } + + /** + * Ensures that $count_log2 is within set bounds. + * + * @param Integer $count_log2 + * Integer that determines the number of iterations used in the hashing + * process. A larger value is more secure, but takes more time to complete. + * + * @return Integer + * Integer within set bounds that is closest to $count_log2. + */ + protected function enforceLog2Boundaries($count_log2) { + if ($count_log2 < static::MIN_HASH_COUNT) { + return static::MIN_HASH_COUNT; + } + elseif ($count_log2 > static::MAX_HASH_COUNT) { + return static::MAX_HASH_COUNT; + } + + return (int) $count_log2; + } + + /** + * Hash a password using a secure stretched hash. + * + * By using a salt and repeated hashing the password is "stretched". Its + * security is increased because it becomes much more computationally costly + * for an attacker to try to break the hash by brute-force computation of the + * hashes of a large number of plain-text words or strings to find a match. + * + * @param String $algo + * The string name of a hashing algorithm usable by hash(), like 'sha256'. + * @param String $password + * The plain-text password to hash. + * @param String $setting + * An existing hash or the output of $this->generateSalt(). Must be + * at least 12 characters (the settings and salt). + * + * @return String + * A string containing the hashed password (and salt) or FALSE on failure. + * The return string will be truncated at HASH_LENGTH characters max. + */ + protected function crypt($algo, $password, $setting) { + // The first 12 characters of an existing hash are its setting string. + $setting = substr($setting, 0, 12); + + if ($setting[0] != '$' || $setting[2] != '$') { + return FALSE; + } + $count_log2 = $this->getCountLog2($setting); + // Stored hashes may have been crypted with any iteration count. However we + // do not allow applying the algorithm for unreasonable low and heigh + // values respectively. + if ($count_log2 != $this->enforceLog2Boundaries($count_log2)) { + return FALSE; + } + $salt = substr($setting, 4, 8); + // Hashes must have an 8 character salt. + if (strlen($salt) != 8) { + return FALSE; + } + + // Convert the base 2 logarithm into an integer. + $count = 1 << $count_log2; + + // We rely on the hash() function being available in PHP 5.2+. + $hash = hash($algo, $salt . $password, TRUE); + do { + $hash = hash($algo, $hash . $password, TRUE); + } while (--$count); + + $len = strlen($hash); + $output = $setting . $this->base64Encode($hash, $len); + // $this->base64Encode() of a 16 byte MD5 will always be 22 characters. + // $this->base64Encode() of a 64 byte sha512 will always be 86 characters. + $expected = 12 + ceil((8 * $len) / 6); + return (strlen($output) == $expected) ? substr($output, 0, static::HASH_LENGTH) : FALSE; + } + + /** + * Parse the log2 iteration count from a stored hash or setting string. + * + * @param String $setting + * An existing hash or the output of $this->generateSalt(). Must be + * at least 12 characters (the settings and salt). + */ + public function getCountLog2($setting) { + return strpos(static::$ITOA64, $setting[3]); + } + + /** + * Implements Drupal\Core\Password\PasswordInterface::hash(). + */ + public function hash($password) { + return $this->crypt('sha512', $password, $this->generateSalt()); + } + + /** + * Implements Drupal\Core\Password\PasswordInterface::checkPassword(). + */ + public function check($password, $account) { + if (substr($account->pass, 0, 2) == 'U$') { + // This may be an updated password from user_update_7000(). Such hashes + // have 'U' added as the first character and need an extra md5() (see the + // Drupal 7 documentation). + $stored_hash = substr($account->pass, 1); + $password = md5($password); + } + else { + $stored_hash = $account->pass; + } + + $type = substr($stored_hash, 0, 3); + switch ($type) { + case '$S$': + // A normal Drupal 7 password using sha512. + $hash = $this->crypt('sha512', $password, $stored_hash); + break; + case '$H$': + // phpBB3 uses "$H$" for the same thing as "$P$". + case '$P$': + // A phpass password generated using md5. This is an + // imported password or from an earlier Drupal version. + $hash = $this->crypt('md5', $password, $stored_hash); + break; + default: + return FALSE; + } + return ($hash && $stored_hash == $hash); + } + + /** + * Implements Drupal\Core\Password\PasswordInterface::userNeedsNewHash(). + */ + public function userNeedsNewHash($account) { + // Check whether this was an updated password. + if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != static::HASH_LENGTH)) { + return TRUE; + } + // Ensure that $count_log2 is within set bounds. + $count_log2 = $this->enforceLog2Boundaries($this->countLog2); + // Check whether the iteration count used differs from the standard number. + return ($this->getCountLog2($account->pass) !== $count_log2); + } +} diff --git a/core/modules/system/lib/Drupal/system/Tests/System/PasswordHashingTest.php b/core/modules/system/lib/Drupal/system/Tests/System/PasswordHashingTest.php index eb9c634..92e37be 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/PasswordHashingTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/PasswordHashingTest.php @@ -7,12 +7,13 @@ namespace Drupal\system\Tests\System; -use Drupal\simpletest\WebTestBase; +use Drupal\simpletest\UnitTestBase; +use Drupal\Core\Password\PhpassHashedPassword; /** * Unit tests for password hashing API. */ -class PasswordHashingTest extends WebTestBase { +class PasswordHashingTest extends UnitTestBase { public static function getInfo() { return array( 'name' => 'Password hashing', @@ -21,42 +22,38 @@ public static function getInfo() { ); } - function setUp() { - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - parent::setUp(); - } - /** * Test password hashing. */ function testPasswordHashing() { // Set a log2 iteration count that is deliberately out of bounds to test // that it is corrected to be within bounds. - variable_set('password_count_log2', 1); + $password_hasher = new PhpassHashedPassword(1); // Set up a fake $account with a password 'baz', hashed with md5. $password = 'baz'; $account = (object) array('name' => 'foo', 'pass' => md5($password)); // The md5 password should be flagged as needing an update. - $this->assertTrue(user_needs_new_hash($account), 'User with md5 password needs a new hash.'); + $this->assertTrue($password_hasher->userNeedsNewHash($account), 'User with md5 password needs a new hash.'); // Re-hash the password. $old_hash = $account->pass; - $account->pass = user_hash_password($password); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_MIN_HASH_COUNT, 'Re-hashed password has the minimum number of log2 iterations.'); + $account->pass = $password_hasher->hash($password); + $this->assertIdentical($password_hasher->getCountLog2($account->pass), $password_hasher::MIN_HASH_COUNT, 'Re-hashed password has the minimum number of log2 iterations.'); $this->assertTrue($account->pass != $old_hash, 'Password hash changed.'); - $this->assertTrue(user_check_password($password, $account), 'Password check succeeds.'); + $this->assertTrue($password_hasher->check($password, $account), 'Password check succeeds.'); // Since the log2 setting hasn't changed and the user has a valid password, - // user_needs_new_hash() should return FALSE. - $this->assertFalse(user_needs_new_hash($account), 'User does not need a new hash.'); + // $password_hasher->userNeedsNewHash() should return FALSE. + $this->assertFalse($password_hasher->userNeedsNewHash($account), 'User does not need a new hash.'); + // Increment the log2 iteration to MIN + 1. - variable_set('password_count_log2', DRUPAL_MIN_HASH_COUNT + 1); - $this->assertTrue(user_needs_new_hash($account), 'User needs a new hash after incrementing the log2 count.'); + $password_hasher = new PhpassHashedPassword($password_hasher::MIN_HASH_COUNT + 1); + $this->assertTrue($password_hasher->userNeedsNewHash($account), 'User needs a new hash after incrementing the log2 count.'); // Re-hash the password. $old_hash = $account->pass; - $account->pass = user_hash_password($password); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_MIN_HASH_COUNT + 1, 'Re-hashed password has the correct number of log2 iterations.'); + $account->pass = $password_hasher->hash($password); + $this->assertIdentical($password_hasher->getCountLog2($account->pass), $password_hasher::MIN_HASH_COUNT + 1, 'Re-hashed password has the correct number of log2 iterations.'); $this->assertTrue($account->pass != $old_hash, 'Password hash changed again.'); // Now the hash should be OK. - $this->assertFalse(user_needs_new_hash($account), 'Re-hashed password does not need a new hash.'); - $this->assertTrue(user_check_password($password, $account), 'Password check succeeds with re-hashed password.'); + $this->assertFalse($password_hasher->userNeedsNewHash($account), 'Re-hashed password does not need a new hash.'); + $this->assertTrue($password_hasher->check($password, $account), 'Password check succeeds with re-hashed password.'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php index d1e8aef..2bb96b9 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Update/UpdateScriptTest.php @@ -61,8 +61,7 @@ function testUpdateAccess() { // Access the update page as user 1. $user1 = user_load(1); $user1->pass_raw = user_password(); - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $user1->pass = user_hash_password(trim($user1->pass_raw)); + $user1->pass = drupal_container()->get('password')->hash(trim($user1->pass_raw)); db_query("UPDATE {users} SET pass = :pass WHERE uid = :uid", array(':pass' => $user1->pass, ':uid' => $user1->uid)); $this->drupalLogin($user1); $this->drupalGet($this->update_url, array('external' => TRUE)); diff --git a/core/modules/system/system.install b/core/modules/system/system.install index e29674e..451d221 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -2224,6 +2224,15 @@ function system_update_8035() { } /** + * Remove the 'password_count_log2' variable. + * + * @ingroup config_upgrade + */ +function system_update_8036() { + update_variable_del('password_count_log2'); +} + +/** * @} End of "defgroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/system/tests/upgrade/drupal-7.system.database.php b/core/modules/system/tests/upgrade/drupal-7.system.database.php index ca85f43..d3a6e9d 100644 --- a/core/modules/system/tests/upgrade/drupal-7.system.database.php +++ b/core/modules/system/tests/upgrade/drupal-7.system.database.php @@ -98,6 +98,10 @@ ->values(array( 'name' => 'filter_allowed_protocols', 'value' => 'a:4:{i:0;s:4:"http";i:1;s:5:"https";i:2;s:3:"ftp";i:3;s:6:"mailto";}', + )) +->values(array( + 'name' => 'password_count_log2', + 'value' => 'i:42;', )) ->execute(); diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php index 8b5db87..5a393fa 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php @@ -71,10 +71,9 @@ function testUserCancelWithoutPermission() { function testUserCancelUid1() { // Update uid 1's name and password to we know it. $password = user_password(); - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); $account = array( 'name' => 'user1', - 'pass' => user_hash_password(trim($password)), + 'pass' => drupal_container()->get('password')->hash(trim($password)), ); // We cannot use $account->save() here, because this would result in the // password being hashed again. diff --git a/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php b/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php index 0b6eeba..45a3bdf 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php @@ -8,6 +8,7 @@ namespace Drupal\user\Tests; use Drupal\simpletest\WebTestBase; +use Drupal\Core\Password\PhpassHashedPassword; /** * Functional tests for user logins, including rate limiting of login attempts. @@ -102,25 +103,32 @@ function testPerUserLoginFloodControl() { * Test that user password is re-hashed upon login after changing $count_log2. */ function testPasswordRehashOnLogin() { - // Load password hashing API. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - // Set initial $count_log2 to the default, DRUPAL_HASH_COUNT. - variable_set('password_count_log2', DRUPAL_HASH_COUNT); + // Determine default log2 for phpass hashing algoritm + $default_count_log2 = 16; + + // Retrieve instance of password hashing algorithm + $password_hasher = drupal_container()->get('password'); + // Create a new user and authenticate. $account = $this->drupalCreateUser(array()); $password = $account->pass_raw; $this->drupalLogin($account); $this->drupalLogout(); - // Load the stored user. The password hash should reflect $count_log2. + // Load the stored user. The password hash should reflect $default_count_log2. $account = user_load($account->uid); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT); - // Change $count_log2 and log in again. - variable_set('password_count_log2', DRUPAL_HASH_COUNT + 1); + $this->assertIdentical($password_hasher->getCountLog2($account->pass), $default_count_log2); + + // Change the required number of iterations by loading a test-module + // containing the necessary container builder code and then verify that the + // users password gets rehashed during the login. + $overridden_count_log2 = 19; + module_enable(array('user_custom_phpass_params_test')); + $account->pass_raw = $password; $this->drupalLogin($account); // Load the stored user, which should have a different password hash now. $account = user_load($account->uid, TRUE); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT + 1); + $this->assertIdentical($password_hasher->getCountLog2($account->pass), $overridden_count_log2); } /** diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index 5f340e4..ca47505 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -90,8 +90,7 @@ protected function preSave(EntityInterface $entity) { // Update the user password if it has changed. if ($entity->isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) { // Allow alternate password hashing schemes. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $entity->pass = user_hash_password(trim($entity->pass)); + $entity->pass = drupal_container()->get('password')->hash(trim($entity->pass)); // Abort if the hashing failed and returned FALSE. if (!$entity->pass) { throw new EntityMalformedException('The entity does not have a password.'); diff --git a/core/modules/user/tests/user_custom_phpass_params_test/lib/Drupal/user_custom_phpass_params_test/UserCustomPhpassParamsTestBundle.php b/core/modules/user/tests/user_custom_phpass_params_test/lib/Drupal/user_custom_phpass_params_test/UserCustomPhpassParamsTestBundle.php new file mode 100644 index 0000000..ab68d2e --- /dev/null +++ b/core/modules/user/tests/user_custom_phpass_params_test/lib/Drupal/user_custom_phpass_params_test/UserCustomPhpassParamsTestBundle.php @@ -0,0 +1,20 @@ +register('password', 'Drupal\Core\Password\PhpassHashedPassword') + ->addArgument(19); + } +} diff --git a/core/modules/user/tests/user_custom_phpass_params_test/user_custom_phpass_params_test.info b/core/modules/user/tests/user_custom_phpass_params_test/user_custom_phpass_params_test.info new file mode 100644 index 0000000..be72cf8 --- /dev/null +++ b/core/modules/user/tests/user_custom_phpass_params_test/user_custom_phpass_params_test.info @@ -0,0 +1,6 @@ +name = "User custom phpass params test" +description = "Support module for testing custom phpass password algorithm parameters." +package = Testing +version = VERSION +core = 8.x +;hidden = TRUE diff --git a/core/modules/user/tests/user_custom_phpass_params_test/user_custom_phpass_params_test.module b/core/modules/user/tests/user_custom_phpass_params_test/user_custom_phpass_params_test.module new file mode 100644 index 0000000..e69de29 diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 68b45ea..9ac0316 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -655,8 +655,7 @@ function user_validate_current_pass(&$form, &$form_state) { // form values like password_confirm that have their own validation // that prevent them from being empty if they are changed. if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) { - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account); + $current_pass_failed = empty($form_state['values']['current_pass']) || !drupal_container()->get('password')->check($form_state['values']['current_pass'], $account); if ($current_pass_failed) { form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name))); form_set_error($key); @@ -1706,14 +1705,13 @@ function user_authenticate($name, $password) { if (!empty($name) && !empty($password)) { $account = user_load_by_name($name); if ($account) { - // Allow alternate password hashing schemes. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - if (user_check_password($password, $account)) { + $password_hasher = drupal_container()->get('password'); + if ($password_hasher->check($password, $account)) { // Successful authentication. $uid = $account->uid; // Update user to new password scheme if needed. - if (user_needs_new_hash($account)) { + if ($password_hasher->userNeedsNewHash($account)) { $account->pass = $password; $account->save(); } diff --git a/core/scripts/generate-d7-content.sh b/core/scripts/generate-d7-content.sh index acfc1a3..3705fe6 100644 --- a/core/scripts/generate-d7-content.sh +++ b/core/scripts/generate-d7-content.sh @@ -30,7 +30,6 @@ drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); // Enable requested modules -require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc'); include_once './modules/system/system.admin.inc'; $form = system_modules(); foreach ($modules_to_enable as $module) { @@ -45,12 +44,13 @@ // Create six users $query = db_insert('users')->fields(array('uid', 'name', 'pass', 'mail', 'status', 'created', 'access')); +$password_hasher = drupal_container()->get('password'); for ($i = 0; $i < 6; $i++) { $name = "test user $i"; $pass = md5("test PassW0rd $i !(.)"); $mail = "test$i@example.com"; $now = mktime(0, 0, 0, 1, $i + 1, 2010); - $query->values(array(db_next_id(), $name, user_hash_password($pass), $mail, 1, $now, $now)); + $query->values(array(db_next_id(), $name, $password_hasher->hash($pass), $mail, 1, $now, $now)); } $query->execute(); diff --git a/core/scripts/password-hash.sh b/core/scripts/password-hash.sh index 8109af4..b91cd41 100755 --- a/core/scripts/password-hash.sh +++ b/core/scripts/password-hash.sh @@ -81,11 +81,12 @@ chdir('..'); define('DRUPAL_ROOT', getcwd()); -include_once DRUPAL_ROOT . '/core/includes/password.inc'; include_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; +$password_hasher = drupal_container()->get('password'); + foreach ($passwords as $password) { - print("\npassword: $password \t\thash: ". user_hash_password($password) ."\n"); + print("\npassword: $password \t\thash: ". $password_hasher->hash($password) ."\n"); } print("\n");