diff --git a/src/Eccube/Entity/Customer.php b/src/Eccube/Entity/Customer.php index 75e4361dc84..9f7d4ae3f97 100644 --- a/src/Eccube/Entity/Customer.php +++ b/src/Eccube/Entity/Customer.php @@ -15,6 +15,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; @@ -30,7 +31,7 @@ * @ORM\HasLifecycleCallbacks() * @ORM\Entity(repositoryClass="Eccube\Repository\CustomerRepository") */ - class Customer extends \Eccube\Entity\AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface, \Serializable + class Customer extends \Eccube\Entity\AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface, LegacyPasswordAuthenticatedUserInterface, \Serializable { /** * @var int @@ -680,7 +681,7 @@ public function setSalt($salt = null) * * @return string|null */ - public function getSalt() + public function getSalt(): ?string { return $this->salt; } diff --git a/src/Eccube/Entity/Member.php b/src/Eccube/Entity/Member.php index 3cd2ed0a5e6..14b91b68a90 100644 --- a/src/Eccube/Entity/Member.php +++ b/src/Eccube/Entity/Member.php @@ -15,6 +15,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; @@ -30,7 +31,7 @@ * @ORM\HasLifecycleCallbacks() * @ORM\Entity(repositoryClass="Eccube\Repository\MemberRepository") */ - class Member extends \Eccube\Entity\AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface, \Serializable + class Member extends \Eccube\Entity\AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface, LegacyPasswordAuthenticatedUserInterface, \Serializable { public static function loadValidatorMetadata(ClassMetadata $metadata) { @@ -338,7 +339,7 @@ public function setSalt($salt) * * @return string */ - public function getSalt() + public function getSalt(): ?string { return $this->salt; } diff --git a/src/Eccube/Security/Core/User/CustomerProvider.php b/src/Eccube/Security/Core/User/CustomerProvider.php index de809cf53be..f5fcf54776f 100644 --- a/src/Eccube/Security/Core/User/CustomerProvider.php +++ b/src/Eccube/Security/Core/User/CustomerProvider.php @@ -13,24 +13,33 @@ namespace Eccube\Security\Core\User; +use Doctrine\ORM\EntityManagerInterface; use Eccube\Entity\Customer; use Eccube\Entity\Master\CustomerStatus; use Eccube\Repository\CustomerRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; -class CustomerProvider implements UserProviderInterface +class CustomerProvider implements UserProviderInterface, PasswordUpgraderInterface { /** * @var CustomerRepository */ protected $customerRepository; - public function __construct(CustomerRepository $customerRepository) + /** + * @var EntityManagerInterface + */ + private $entityManager; + + public function __construct(CustomerRepository $customerRepository, EntityManagerInterface $entityManager) { $this->customerRepository = $customerRepository; + $this->entityManager = $entityManager; } /** @@ -92,4 +101,10 @@ public function loadUserByIdentifier(string $identifier): UserInterface // FIXME deprecated return $this->loadUserByUsername($identifier); } + + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + $user->setPassword($newHashedPassword); + $this->entityManager->flush(); + } } diff --git a/src/Eccube/Security/Core/User/MemberProvider.php b/src/Eccube/Security/Core/User/MemberProvider.php index e38e298cd73..d6671982a0f 100644 --- a/src/Eccube/Security/Core/User/MemberProvider.php +++ b/src/Eccube/Security/Core/User/MemberProvider.php @@ -13,24 +13,33 @@ namespace Eccube\Security\Core\User; +use Doctrine\ORM\EntityManagerInterface; use Eccube\Entity\Master\Work; use Eccube\Entity\Member; use Eccube\Repository\MemberRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; -class MemberProvider implements UserProviderInterface +class MemberProvider implements UserProviderInterface, PasswordUpgraderInterface { /** * @var MemberRepository */ protected $memberRepository; - public function __construct(MemberRepository $memberRepository) + /** + * @var EntityManagerInterface + */ + private $entityManager; + + public function __construct(MemberRepository $memberRepository, EntityManagerInterface $entityManager) { $this->memberRepository = $memberRepository; + $this->entityManager = $entityManager; } /** @@ -89,4 +98,10 @@ public function loadUserByIdentifier(string $identifier): UserInterface // FIXME deprecated return $this->loadUserByUsername($identifier); } + + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + $user->setPassword($newHashedPassword); + $this->entityManager->flush(); + } } diff --git a/tests/Eccube/Tests/Security/PasswordHasher/PasswordMigrationTest.php b/tests/Eccube/Tests/Security/PasswordHasher/PasswordMigrationTest.php new file mode 100644 index 00000000000..f4cb57bf19b --- /dev/null +++ b/tests/Eccube/Tests/Security/PasswordHasher/PasswordMigrationTest.php @@ -0,0 +1,70 @@ +legacyPasswordHasher = self::getContainer()->get(PasswordHasher::class); + } + + /** + * EC-CUBEのハッシュアルゴリズムからSymfony標準のハッシュアルゴリズムへマイグレーションできることを確認する + */ + public function testPasswordMigration() + { + // 旧アルゴリズムでパスワードをハッシュ化 + $username = 'migration-test-uesr'; + $password = 'password'; + $salt = StringUtil::random(); + $hash = $this->legacyPasswordHasher->hash($password, $salt); + + $Member = $this->createMember(); + $Member->setLoginId($username) + ->setPassword($hash) + ->setSalt($salt); + $this->entityManager->flush(); + + // ログイン + $crawler = $this->client->request('GET', '/admin/login'); + self::assertTrue($this->client->getResponse()->isSuccessful()); + + $form = $crawler->selectButton('ログイン')->form(); + $form['login_id'] = $username; + $form['password'] = $password; + $this->client->submit($form); + + self::assertTrue($this->client->getResponse()->isRedirection()); + + // ログイン後、パスワードがマイグレーションされていることを確認. + $this->entityManager->clear(); + $Member = $this->entityManager->find(Member::class, $Member->getId()); + + self::assertNotSame($hash, $Member->getPassword(), $hash.':'.$Member->getPassword()); + self::assertStringStartsWith('$', $Member->getPassword(), $Member->getPassword()); + } +}