diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index fb307f0..2797b6c 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -40789,7 +40789,7 @@ ->values(array( 'uid' => '2', 'name' => 'Odo', - 'pass' => '$S$DZ4P7zZOh92vgrgZDBbv8Pu6lQB337OJ1wsOy21602G4A5F7.M9K', + 'pass' => '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ', 'mail' => 'odo@local.host', 'theme' => '', 'signature' => '', diff --git a/core/modules/user/src/MigratePassword.php b/core/modules/user/src/MigratePassword.php index 4116c16..c541d5f 100644 --- a/core/modules/user/src/MigratePassword.php +++ b/core/modules/user/src/MigratePassword.php @@ -21,7 +21,15 @@ class MigratePassword implements PasswordInterface { /** * Indicates if MD5 password prefixing is enabled. */ - protected $enabled = FALSE; + protected $md5Prefixing = FALSE; + + /** + * Indicates if password needs hashing. + * + * Setting to false allows passwords that have already been hashed to be + * migrated. + */ + protected $needsHashing = TRUE; /** * Builds the replacement password service class. @@ -51,11 +59,16 @@ public function needsRehash($hash) { * {@inheritdoc} */ public function hash($password) { - $hash = $this->originalPassword->hash($password); + if ($this->needsHashing) { + $hash = $this->originalPassword->hash($password); + } + else { + $hash = $password; + } // Allow prefixing only if the service was asked to prefix. Check also if // the $password pattern is conforming to a MD5 result. - if ($this->enabled && preg_match('/^[0-9a-f]{32}$/', $password)) { + if ($this->md5Prefixing && preg_match('/^[0-9a-f]{32}$/', $password)) { $hash = 'U' . $hash; } @@ -66,14 +79,40 @@ public function hash($password) { * Enables the MD5 password prefixing. */ public function enableMd5Prefixing() { - $this->enabled = TRUE; + $this->md5Prefixing = TRUE; } /** * Disables the MD5 password prefixing. */ public function disableMd5Prefixing() { - $this->enabled = FALSE; + $this->md5Prefixing = FALSE; + } + + /** + * Enables hashing of passwords. + * + * @return $this + * + * @see \Drupal\user\MigratePassword::disableHashing() + */ + public function enableHashing() { + $this->needsHashing = TRUE; + return $this; + } + + /** + * Disables hashing of passwords. + * + * This is useful if passwords have already been hashed and don't require + * hashing on save. For example, Drupal 7 passwords have already been hashed + * and do not need to be hashed again during user migration. + * + * @return $this + */ + public function disableHashing() { + $this->needsHashing = FALSE; + return $this; } /** diff --git a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php index 4187121..e7405da 100644 --- a/core/modules/user/src/Plugin/migrate/destination/EntityUser.php +++ b/core/modules/user/src/Plugin/migrate/destination/EntityUser.php @@ -25,7 +25,7 @@ class EntityUser extends EntityContentBase { /** * The password service class. * - * @var \Drupal\Core\Password\PasswordInterface + * @var \Drupal\user\MigratePassword */ protected $password; @@ -55,8 +55,9 @@ class EntityUser extends EntityContentBase { */ public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, PasswordInterface $password) { parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager); - if (isset($configuration['md5_passwords'])) { - $this->password = $password; + $this->password = $password; + if (!($this->password instanceof MigratePassword)) { + throw new MigrateException('Password service has been altered by another module, aborting.'); } } @@ -83,22 +84,25 @@ public static function create(ContainerInterface $container, array $configuratio * @throws \Drupal\migrate\MigrateException */ public function import(Row $row, array $old_destination_id_values = array()) { - if ($this->password) { - if ($this->password instanceof MigratePassword) { - $this->password->enableMd5Prefixing(); - } - else { - throw new MigrateException('Password service has been altered by another module, aborting.'); - } + if (isset($this->configuration['md5_passwords'])) { + $this->password->enableMd5Prefixing(); } + else { + $this->password->disableHashing(); + } + // Do not overwrite the root account password. if ($row->getDestinationProperty('uid') == 1) { $row->removeDestinationProperty('pass'); } $ids = parent::import($row, $old_destination_id_values); - if ($this->password) { + + if (isset($this->configuration['md5_passwords'])) { $this->password->disableMd5Prefixing(); } + else { + $this->password->enableHashing(); + } return $ids; } diff --git a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php index 61ff800..2e0965d 100644 --- a/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php +++ b/core/modules/user/tests/src/Kernel/Migrate/d7/MigrateUserTest.php @@ -44,6 +44,8 @@ protected function setUp() { * The username. * @param string $mail * The user's email address. + * @param string $password + * The password for this user. * @param int $access * The last access time. * @param int $login @@ -59,7 +61,7 @@ protected function setUp() { * @param bool $has_picture * Whether the user is expected to have a picture attached. */ - protected function assertEntity($id, $label, $mail, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE) { + protected function assertEntity($id, $label, $mail, $password, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID], $has_picture = FALSE) { /** @var \Drupal\user\UserInterface $user */ $user = User::load($id); $this->assertTrue($user instanceof UserInterface); @@ -77,13 +79,29 @@ protected function assertEntity($id, $label, $mail, $access, $login, $blocked, $ $this->assertIdentical($init, $user->getInitialEmail()); $this->assertIdentical($roles, $user->getRoles()); $this->assertIdentical($has_picture, !$user->user_picture->isEmpty()); + $this->assertIdentical($password, $user->getPassword()); } /** * Tests the Drupal 7 user to Drupal 8 migration. */ public function testUser() { - $this->assertEntity(2, 'Odo', 'odo@local.host', '0', '0', FALSE, '', 'odo@local.host'); + $password = '$S$DGFZUE.FhrXbe4y52eC7p0ZVRGD/gOPtVctDlmC89qkujnBokAlJ'; + $this->assertEntity(2, 'Odo', 'odo@local.host', $password, '0', '0', FALSE, '', 'odo@local.host'); + + // Ensure that the user can authenticate. + $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); + // After authenticating the password will be rehashed because the password + // stretching iteration count has changed from 15 in Drupal 7 to 16 in + // Drupal 8. + $user = User::load(2); + $rehash = $user->getPassword(); + $this->assertNotEquals($password, $rehash); + + // Authenticate again and there should be no re-hash. + $this->assertEquals(2, \Drupal::service('user.auth')->authenticate('Odo', 'a password')); + $user = User::load(2); + $this->assertEquals($rehash, $user->getPassword()); } }