diff --git a/core/lib/Drupal/Core/Password/PhpPassword.php b/core/lib/Drupal/Core/Password/PhpPassword.php index 3e02b8f..7b0968f 100644 --- a/core/lib/Drupal/Core/Password/PhpPassword.php +++ b/core/lib/Drupal/Core/Password/PhpPassword.php @@ -34,7 +34,7 @@ class PhpPassword implements PasswordInterface { /** * The Drupal 7 password hashing service. * - * @var \Drupal\Core\Password\PasswordInterface + * @var \Drupal\Core\Password\PhpassHashedPassword */ protected $drupal7Password; @@ -70,9 +70,10 @@ public function check($password, UserInterface $account) { $stored_hash = $account->getPassword(); // Password migrated from Drupal 7. - if (substr($stored_hash, 0, 3) == 'D7$') { - $stored_hash = substr($stored_hash, 2); - $password = $this->drupal7Password->hash($password); + if (substr($stored_hash, 0, 5) == 'D7$S$') { + $salt = substr($stored_hash, 2, 12); + $stored_hash = substr($stored_hash, 14); + $password = $this->drupal7Password->crypt('sha512', $password, $salt); } // MD5 migrated password (Drupal 6). elseif (substr($stored_hash, 0, 2) == 'U$') { diff --git a/core/lib/Drupal/Core/Password/PhpassHashedPassword.php b/core/lib/Drupal/Core/Password/PhpassHashedPassword.php index 17cafdd..e8cd3e9 100644 --- a/core/lib/Drupal/Core/Password/PhpassHashedPassword.php +++ b/core/lib/Drupal/Core/Password/PhpassHashedPassword.php @@ -158,7 +158,7 @@ protected function enforceLog2Boundaries($count_log2) { * 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) { + public function crypt($algo, $password, $setting) { // Prevent DoS attacks by refusing to hash large passwords. if (strlen($password) > 512) { return FALSE; diff --git a/core/modules/migrate/src/MigratePassword.php b/core/modules/migrate/src/MigratePassword.php index 2d45f41..b412357 100644 --- a/core/modules/migrate/src/MigratePassword.php +++ b/core/modules/migrate/src/MigratePassword.php @@ -77,11 +77,10 @@ public function hash($password) { $hash = 'U' . $hash; } // Prefix user hashed passwords coming from Drupal 7. - // @todo This needs testing when D7->D8 migration will be merged into main - // repository. $source = $this->migration->getSourcePlugin()->getPluginId(); if (substr($password, 0, 3) == '$S$' && $source == 'd7_user') { - $hash = 'D7' . $hash; + $salt = substr($password, 0, 12); + $hash = 'D7' . $salt . $hash; } } diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index d608d05..2237499 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -345,8 +345,9 @@ public function containerBuild(ContainerBuilder $container) { $definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound'); } - if ($container->hasDefinition('password')) { - $container->getDefinition('password')->setArguments([4, new PhpassHashedPassword(1)]); + if ($container->hasDefinition('password') && $container->hasDefinition('drupal7_password')) { + $container->getDefinition('drupal7_password')->setArguments([1]); + $container->getDefinition('password')->setArguments([4, $container->get('drupal7_password')]); } // Register the stream wrapper manager. diff --git a/core/modules/user/src/Tests/UserLoginTest.php b/core/modules/user/src/Tests/UserLoginTest.php index 08b89b5..2b75204 100644 --- a/core/modules/user/src/Tests/UserLoginTest.php +++ b/core/modules/user/src/Tests/UserLoginTest.php @@ -8,7 +8,8 @@ namespace Drupal\user\Tests; use Drupal\simpletest\WebTestBase; -use Drupal\Core\Password\PhpassHashedPassword; +use Drupal\user\Entity\User; +use Drupal\user\UserInterface; /** * Ensure that login works as expected. @@ -141,6 +142,48 @@ function testPasswordRehashOnLogin() { } /** + * Test MD5 (Drupal 6) and Drupal 7 passwords rehashing. + */ + public function testMigratedPasswordRehashing() { + /** @var \Drupal\Core\Password\PasswordInterface $d7_hasher */ + $d7_hasher = $this->container->get('drupal7_password'); + /** @var \Drupal\Core\Password\PasswordInterface $hasher */ + $hasher = $this->container->get('password'); + + // Drupal 6 migrated password. + + $account = $this->drupalCreateUser(); + $plain = $account->pass_raw; + + // We pretend that the user was migrated from Drupal 6. + $md5_pass = md5($plain); + $migrated_pass = 'U' . $hasher->hash($md5_pass); + $this->storeHashedPassword($account, $migrated_pass); + + $this->drupalLogin($account); + $this->drupalLogout(); + + // After logging is the user password has been rehashed and is valid. + $this->assertTrue($hasher->check($plain, $account)); + + // Drupal 7 migrated password. + + $account2 = $this->drupalCreateUser(); + $plain = $account2->pass_raw; + + // We pretend that the user was migrated from Drupal 7. + $d7_pass = $d7_hasher->hash($plain); + $salt = substr($d7_pass, 0, 12); + $migrated_pass = 'D7' . $salt . $hasher->hash($d7_pass); + $this->storeHashedPassword($account2, $migrated_pass); + $this->drupalLogin($account2); + $this->drupalLogout(); + + // After logging is the user password has been rehashed and is valid. + $this->assertTrue($hasher->check($plain, $account2)); + } + + /** * Make an unsuccessful login attempt. * * @param $account @@ -169,4 +212,22 @@ function assertFailedLogin($account, $flood_trigger = NULL) { $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?')); } } + + /** + * Updates the hashed user password bypassing the API. We want to set an + * already hashed password. + * + * @param \Drupal\user\UserInterface $account + * The user account. + * @param string $hashed_password + * An already hashed password. + */ + protected function storeHashedPassword(UserInterface $account, $hashed_password) { + $account->setPassword($hashed_password); + db_update('users_field_data') + ->fields(['pass' => $hashed_password]) + ->condition('uid', $account->id()) + ->execute(); + } + }