diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index b6e7ca04dcd68..566625a7c1f7c 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -15,6 +15,7 @@ CHANGELOG * Added `LoginThrottlingListener`. * Added `LoginLinkAuthenticator`. * Moved methods `supports()` and `authenticate()` from `AbstractListener` to `FirewallListenerInterface`. + * [BC break] `PasswordUpgradeBadge::getPasswordUpgrader()` changed its return type to return null or a `PasswordUpgraderInterface` implementation. 5.1.0 ----- diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php index 5d88513af7ca0..cfffe9d307b78 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/PasswordUpgradeBadge.php @@ -30,10 +30,10 @@ class PasswordUpgradeBadge implements BadgeInterface private $passwordUpgrader; /** - * @param string $plaintextPassword The presented password, used in the rehash - * @param PasswordUpgraderInterface $passwordUpgrader The password upgrader, usually the UserProvider + * @param string $plaintextPassword The presented password, used in the rehash + * @param PasswordUpgraderInterface|null $passwordUpgrader The password upgrader, defaults to the UserProvider if null */ - public function __construct(string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader) + public function __construct(string $plaintextPassword, ?PasswordUpgraderInterface $passwordUpgrader = null) { $this->plaintextPassword = $plaintextPassword; $this->passwordUpgrader = $passwordUpgrader; @@ -51,7 +51,7 @@ public function getAndErasePlaintextPassword(): string return $password; } - public function getPasswordUpgrader(): PasswordUpgraderInterface + public function getPasswordUpgrader(): ?PasswordUpgraderInterface { return $this->passwordUpgrader; } diff --git a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php index 140552b3f3097..d667b5826eaca 100644 --- a/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php @@ -13,7 +13,9 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; @@ -53,7 +55,19 @@ public function onLoginSuccess(LoginSuccessEvent $event): void return; } - $badge->getPasswordUpgrader()->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt())); + $passwordUpgrader = $badge->getPasswordUpgrader(); + if (null === $passwordUpgrader) { + /** @var UserBadge $userBadge */ + $userBadge = $passport->getBadge(UserBadge::class); + $userLoader = $userBadge->getUserLoader(); + if (\is_array($userLoader) && $userLoader[0] instanceof PasswordUpgraderInterface) { + $passwordUpgrader = $userLoader[0]; + } else { + return; + } + } + + $passwordUpgrader->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt())); } public static function getSubscribedEvents(): array diff --git a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php index cb0d8fcdae114..5b862e6c0a755 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Http\Event\CheckPassportEvent; /** + * Configures the user provider as user loader, if no user load + * has been explicitly set. + * * @author Wouter de Jong * * @final diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php index bf90aa3be6d5e..42607ca853328 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; @@ -34,9 +35,14 @@ class PasswordMigratingListenerTest extends TestCase protected function setUp(): void { + $this->user = $this->createMock(UserInterface::class); + $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password'); + $encoder = $this->createMock(PasswordEncoderInterface::class); + $encoder->expects($this->any())->method('needsRehash')->willReturn(true); + $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password'); $this->encoderFactory = $this->createMock(EncoderFactoryInterface::class); + $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder); $this->listener = new PasswordMigratingListener($this->encoderFactory); - $this->user = $this->createMock(UserInterface::class); } /** @@ -61,16 +67,8 @@ public function provideUnsupportedEvents() yield [$this->createEvent($this->createMock(PassportInterface::class))]; } - public function testUpgrade() + public function testUpgradeWithUpgrader() { - $encoder = $this->createMock(PasswordEncoderInterface::class); - $encoder->expects($this->any())->method('needsRehash')->willReturn(true); - $encoder->expects($this->any())->method('encodePassword')->with('pa$$word', null)->willReturn('new-encoded-password'); - - $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->user)->willReturn($encoder); - - $this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password'); - $passwordUpgrader = $this->createPasswordUpgrader(); $passwordUpgrader->expects($this->once()) ->method('upgradePassword') @@ -81,6 +79,20 @@ public function testUpgrade() $this->listener->onLoginSuccess($event); } + public function testUpgradeWithoutUpgrader() + { + $userLoader = $this->createMock(MigratingUserProvider::class); + $userLoader->expects($this->any())->method('loadUserByUsername')->willReturn($this->user); + + $userLoader->expects($this->once()) + ->method('upgradePassword') + ->with($this->user, 'new-encoded-password') + ; + + $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', [$userLoader, 'loadUserByUsername']), [new PasswordUpgradeBadge('pa$$word')])); + $this->listener->onLoginSuccess($event); + } + private function createPasswordUpgrader() { return $this->createMock(PasswordUpgraderInterface::class); @@ -91,3 +103,7 @@ private function createEvent(PassportInterface $passport) return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main'); } } + +abstract class MigratingUserProvider implements UserProviderInterface, PasswordUpgraderInterface +{ +}