Default to user provider, if available, in password upgrader

This commit is contained in:
Wouter de Jong 2020-11-18 18:08:02 +01:00
parent 0302332238
commit e39e844606
5 changed files with 50 additions and 16 deletions

View File

@ -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
-----

View File

@ -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 @@ class PasswordUpgradeBadge implements BadgeInterface
return $password;
}
public function getPasswordUpgrader(): PasswordUpgraderInterface
public function getPasswordUpgrader(): ?PasswordUpgraderInterface
{
return $this->passwordUpgrader;
}

View File

@ -13,7 +13,9 @@ namespace Symfony\Component\Security\Http\EventListener;
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 @@ class PasswordMigratingListener implements EventSubscriberInterface
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

View File

@ -16,6 +16,9 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
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 <wouter@wouterj.nl>
*
* @final

View File

@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
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->encoderFactory = $this->createMock(EncoderFactoryInterface::class);
$this->listener = new PasswordMigratingListener($this->encoderFactory);
$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);
}
/**
@ -61,16 +67,8 @@ class PasswordMigratingListenerTest extends TestCase
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 @@ class PasswordMigratingListenerTest extends TestCase
$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 @@ class PasswordMigratingListenerTest extends TestCase
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main');
}
}
abstract class MigratingUserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
}