From 9a9083c59e83a32244da262478d48aacf0e89ff1 Mon Sep 17 00:00:00 2001 From: Thomas Durand Date: Thu, 12 Mar 2026 11:52:29 +0100 Subject: [PATCH] feat(api): expose ProviderRegistry and ConditionRegistry with an interface Motivation: allow override, and decoration of those pillars of the decision for when a 2fa triggers, and which one triggers. --- src/bundle/Controller/FormController.php | 4 ++-- .../EventListener/CheckTwoFactorCodeListener.php | 4 ++-- .../Condition/TwoFactorConditionRegistry.php | 2 +- .../TwoFactorConditionRegistryInterface.php | 12 ++++++++++++ .../Event/AuthenticationTokenListener.php | 4 ++-- .../Provider/TwoFactorProviderInitiator.php | 2 +- .../TwoFactorProviderPreparationListener.php | 2 +- .../Provider/TwoFactorProviderRegistry.php | 4 ++-- .../TwoFactorProviderRegistryInterface.php | 15 +++++++++++++++ tests/Controller/FormControllerTest.php | 6 +++--- .../CheckTwoFactorCodeListenerTest.php | 6 +++--- .../Event/AuthenticationTokenListenerTest.php | 6 +++--- .../Provider/TwoFactorProviderInitiatorTest.php | 4 ++-- .../TwoFactorProviderPreparationListenerTest.php | 6 +++--- 14 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistryInterface.php create mode 100644 src/bundle/Security/TwoFactor/Provider/TwoFactorProviderRegistryInterface.php diff --git a/src/bundle/Controller/FormController.php b/src/bundle/Controller/FormController.php index 07848741..d695649d 100644 --- a/src/bundle/Controller/FormController.php +++ b/src/bundle/Controller/FormController.php @@ -6,7 +6,7 @@ use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Exception\UnknownTwoFactorProviderException; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceManagerInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\TwoFactorFirewallConfig; use Scheb\TwoFactorBundle\Security\TwoFactor\TwoFactorFirewallContext; @@ -28,7 +28,7 @@ class FormController { public function __construct( private readonly TokenStorageInterface $tokenStorage, - private readonly TwoFactorProviderRegistry $providerRegistry, + private readonly TwoFactorProviderRegistryInterface $providerRegistry, private readonly TwoFactorFirewallContext $twoFactorFirewallContext, private readonly LogoutUrlGenerator $logoutUrlGenerator, private readonly TrustedDeviceManagerInterface|null $trustedDeviceManager, diff --git a/src/bundle/Security/Http/EventListener/CheckTwoFactorCodeListener.php b/src/bundle/Security/Http/EventListener/CheckTwoFactorCodeListener.php index 3838f8c5..22257eac 100644 --- a/src/bundle/Security/Http/EventListener/CheckTwoFactorCodeListener.php +++ b/src/bundle/Security/Http/EventListener/CheckTwoFactorCodeListener.php @@ -10,7 +10,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents; use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorCodeEvent; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\PreparationRecorderInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -23,7 +23,7 @@ class CheckTwoFactorCodeListener extends AbstractCheckCodeListener public function __construct( PreparationRecorderInterface $preparationRecorder, - private readonly TwoFactorProviderRegistry $providerRegistry, + private readonly TwoFactorProviderRegistryInterface $providerRegistry, private readonly EventDispatcherInterface $eventDispatcher, ) { parent::__construct($preparationRecorder); diff --git a/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistry.php b/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistry.php index f0d858f9..defc9326 100644 --- a/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistry.php +++ b/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistry.php @@ -9,7 +9,7 @@ /** * @final */ -class TwoFactorConditionRegistry +class TwoFactorConditionRegistry implements TwoFactorConditionRegistryInterface { /** * @param TwoFactorConditionInterface[] $conditions diff --git a/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistryInterface.php b/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistryInterface.php new file mode 100644 index 00000000..da3c7438 --- /dev/null +++ b/src/bundle/Security/TwoFactor/Condition/TwoFactorConditionRegistryInterface.php @@ -0,0 +1,12 @@ + $providers @@ -20,7 +20,7 @@ public function __construct(private readonly iterable $providers) } /** - * @return iterable + * {@inheritDoc} */ public function getAllProviders(): iterable { diff --git a/src/bundle/Security/TwoFactor/Provider/TwoFactorProviderRegistryInterface.php b/src/bundle/Security/TwoFactor/Provider/TwoFactorProviderRegistryInterface.php new file mode 100644 index 00000000..42bea7fa --- /dev/null +++ b/src/bundle/Security/TwoFactor/Provider/TwoFactorProviderRegistryInterface.php @@ -0,0 +1,15 @@ + + */ + public function getAllProviders(): iterable; + + public function getProvider(string $providerName): TwoFactorProviderInterface; +} diff --git a/tests/Controller/FormControllerTest.php b/tests/Controller/FormControllerTest.php index 38d19492..c09a955e 100644 --- a/tests/Controller/FormControllerTest.php +++ b/tests/Controller/FormControllerTest.php @@ -12,7 +12,7 @@ use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorFormRendererInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Trusted\TrustedDeviceManagerInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\TwoFactorFirewallConfig; use Scheb\TwoFactorBundle\Security\TwoFactor\TwoFactorFirewallContext; @@ -38,7 +38,7 @@ class FormControllerTest extends TestCase private const string LOGOUT_PATH = '/logout'; private MockObject&TokenStorageInterface $tokenStorage; - private MockObject&TwoFactorProviderRegistry $providerRegistry; + private MockObject&TwoFactorProviderRegistryInterface $providerRegistry; private MockObject&SessionInterface $session; private MockObject&Request $request; private MockObject&TwoFactorFormRendererInterface $formRenderer; @@ -68,7 +68,7 @@ protected function setUp(): void $this->tokenStorage = $this->createMock(TokenStorageInterface::class); - $this->providerRegistry = $this->createMock(TwoFactorProviderRegistry::class); + $this->providerRegistry = $this->createMock(TwoFactorProviderRegistryInterface::class); $this->providerRegistry ->expects($this->any()) ->method('getProvider') diff --git a/tests/Security/Http/EventListener/CheckTwoFactorCodeListenerTest.php b/tests/Security/Http/EventListener/CheckTwoFactorCodeListenerTest.php index 82aeab95..28534d14 100644 --- a/tests/Security/Http/EventListener/CheckTwoFactorCodeListenerTest.php +++ b/tests/Security/Http/EventListener/CheckTwoFactorCodeListenerTest.php @@ -13,7 +13,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents; use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorCodeEvent; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -21,14 +21,14 @@ */ class CheckTwoFactorCodeListenerTest extends AbstractCheckCodeListenerTestSetup { - private MockObject&TwoFactorProviderRegistry $providerRegistry; + private MockObject&TwoFactorProviderRegistryInterface $providerRegistry; private MockObject&EventDispatcherInterface $eventDispatcher; protected function setUp(): void { parent::setUp(); - $this->providerRegistry = $this->createMock(TwoFactorProviderRegistry::class); + $this->providerRegistry = $this->createMock(TwoFactorProviderRegistryInterface::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->listener = new CheckTwoFactorCodeListener( $this->preparationRecorder, diff --git a/tests/Security/TwoFactor/Event/AuthenticationTokenListenerTest.php b/tests/Security/TwoFactor/Event/AuthenticationTokenListenerTest.php index 21e09845..828d3b52 100644 --- a/tests/Security/TwoFactor/Event/AuthenticationTokenListenerTest.php +++ b/tests/Security/TwoFactor/Event/AuthenticationTokenListenerTest.php @@ -10,7 +10,7 @@ use Scheb\TwoFactorBundle\Security\Http\Authenticator\TwoFactorAuthenticator; use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextFactoryInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Condition\TwoFactorConditionRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Condition\TwoFactorConditionRegistryInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Event\AuthenticationTokenListener; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInitiator; use Scheb\TwoFactorBundle\Tests\TestCase; @@ -24,14 +24,14 @@ class AuthenticationTokenListenerTest extends TestCase { private const string FIREWALL_NAME = 'firewallName'; - private MockObject&TwoFactorConditionRegistry $twoFactorConditionRegistry; + private MockObject&TwoFactorConditionRegistryInterface $twoFactorConditionRegistry; private MockObject&TwoFactorProviderInitiator $twoFactorProviderInitiator; private MockObject&AuthenticationContextFactoryInterface $authenticationContextFactory; private AuthenticationTokenListener $listener; protected function setUp(): void { - $this->twoFactorConditionRegistry = $this->createMock(TwoFactorConditionRegistry::class); + $this->twoFactorConditionRegistry = $this->createMock(TwoFactorConditionRegistryInterface::class); $this->twoFactorProviderInitiator = $this->createMock(TwoFactorProviderInitiator::class); $this->authenticationContextFactory = $this->createMock(AuthenticationContextFactoryInterface::class); diff --git a/tests/Security/TwoFactor/Provider/TwoFactorProviderInitiatorTest.php b/tests/Security/TwoFactor/Provider/TwoFactorProviderInitiatorTest.php index dd218c66..20ccdee2 100644 --- a/tests/Security/TwoFactor/Provider/TwoFactorProviderInitiatorTest.php +++ b/tests/Security/TwoFactor/Provider/TwoFactorProviderInitiatorTest.php @@ -12,7 +12,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderDeciderInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInitiator; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Scheb\TwoFactorBundle\Tests\Security\TwoFactor\Condition\AbstractAuthenticationContextTestCase; class TwoFactorProviderInitiatorTest extends AbstractAuthenticationContextTestCase @@ -40,7 +40,7 @@ protected function setUp(): void $this->withoutNeedsPreparationProvider = $this->createMock(TwoFactorProviderInterface::class); - $providerRegistry = $this->createMock(TwoFactorProviderRegistry::class); + $providerRegistry = $this->createMock(TwoFactorProviderRegistryInterface::class); $providerRegistry ->expects($this->any()) ->method('getAllProviders') diff --git a/tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationListenerTest.php b/tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationListenerTest.php index b440fdab..543bd5ba 100644 --- a/tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationListenerTest.php +++ b/tests/Security/TwoFactor/Provider/TwoFactorProviderPreparationListenerTest.php @@ -13,7 +13,7 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\PreparationRecorderInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderPreparationListener; -use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistry; +use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\TwoFactorProviderRegistryInterface; use Scheb\TwoFactorBundle\Tests\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -27,7 +27,7 @@ class TwoFactorProviderPreparationListenerTest extends TestCase private const string FIREWALL_NAME = 'firewallName'; private const string CURRENT_PROVIDER_NAME = 'currentProviderName'; - private MockObject&TwoFactorProviderRegistry $providerRegistry; + private MockObject&TwoFactorProviderRegistryInterface $providerRegistry; private MockObject&Request $request; private MockObject&PreparationRecorderInterface $preparationRecorder; private MockObject&TwoFactorToken $token; @@ -54,7 +54,7 @@ protected function setUp(): void $this->preparationRecorder = $this->createMock(PreparationRecorderInterface::class); - $this->providerRegistry = $this->createMock(TwoFactorProviderRegistry::class); + $this->providerRegistry = $this->createMock(TwoFactorProviderRegistryInterface::class); } private function initTwoFactorProviderPreparationListener(bool $prepareOnLogin, bool $prepareOnAccessDenied): void