From 3ae1b3cbe274f94adb6ab60dd47c4385f55c8264 Mon Sep 17 00:00:00 2001 From: chaours Date: Sun, 6 Oct 2024 16:04:40 +0200 Subject: [PATCH] Creation of BackupCodeUsedEvent --- doc/events.rst | 7 +++++++ .../EventListener/CheckBackupCodeListener.php | 19 +++++++++++++++++++ src/bundle/Resources/config/backup_codes.php | 3 +++ .../Event/TwoFactorAuthenticationEvents.php | 5 +++++ .../CheckBackupCodeListenerTest.php | 11 ++++++++++- 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/doc/events.rst b/doc/events.rst index 4d54fab0..3ec4c9a2 100644 --- a/doc/events.rst +++ b/doc/events.rst @@ -28,6 +28,13 @@ Constant: ``Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticati Is dispatched when two-factor authentication is attempted, right before checking the code. +``scheb_two_factor.authentication.backup_code_used`` +------------------------------------------- + +Constant: ``Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents::BACKUP_CODE_USED`` + +Is dispatched when two-factor authentication was successful with a backup-code. + ``scheb_two_factor.authentication.success`` ------------------------------------------- diff --git a/src/backup-code/Security/Http/EventListener/CheckBackupCodeListener.php b/src/backup-code/Security/Http/EventListener/CheckBackupCodeListener.php index 200fd830..8427efa1 100644 --- a/src/backup-code/Security/Http/EventListener/CheckBackupCodeListener.php +++ b/src/backup-code/Security/Http/EventListener/CheckBackupCodeListener.php @@ -4,9 +4,15 @@ namespace Scheb\TwoFactorBundle\Security\Http\EventListener; +use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface; use Scheb\TwoFactorBundle\Security\TwoFactor\Backup\BackupCodeManagerInterface; +use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent; +use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents; use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\PreparationRecorderInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @final @@ -20,6 +26,9 @@ class CheckBackupCodeListener extends AbstractCheckCodeListener public function __construct( PreparationRecorderInterface $preparationRecorder, private readonly BackupCodeManagerInterface $backupCodeManager, + private readonly TokenStorageInterface $tokenStorage, + private readonly RequestStack $requestStack, + private readonly EventDispatcherInterface $eventDispatcher, ) { parent::__construct($preparationRecorder); } @@ -28,6 +37,16 @@ protected function isValidCode(string $providerName, object $user, string $code) { if ($this->backupCodeManager->isBackupCode($user, $code)) { $this->backupCodeManager->invalidateBackupCode($user, $code); + $token = $this->tokenStorage->getToken(); + if (!($token instanceof TwoFactorTokenInterface)) { + return false; + } + + $request = $this->requestStack->getCurrentRequest(); + if ($request) { + $event = new TwoFactorAuthenticationEvent($request, $token); + $this->eventDispatcher->dispatch($event, TwoFactorAuthenticationEvents::BACKUP_CODE_USED); + } return true; } diff --git a/src/bundle/Resources/config/backup_codes.php b/src/bundle/Resources/config/backup_codes.php index b08e2749..c0ae7799 100644 --- a/src/bundle/Resources/config/backup_codes.php +++ b/src/bundle/Resources/config/backup_codes.php @@ -20,5 +20,8 @@ ->args([ service('scheb_two_factor.provider_preparation_recorder'), service('scheb_two_factor.backup_code_manager'), + service('security.token_storage'), + service('request_stack'), + service('event_dispatcher'), ]); }; diff --git a/src/bundle/Security/TwoFactor/Event/TwoFactorAuthenticationEvents.php b/src/bundle/Security/TwoFactor/Event/TwoFactorAuthenticationEvents.php index 0f1df6c1..8667aec8 100644 --- a/src/bundle/Security/TwoFactor/Event/TwoFactorAuthenticationEvents.php +++ b/src/bundle/Security/TwoFactor/Event/TwoFactorAuthenticationEvents.php @@ -25,6 +25,11 @@ class TwoFactorAuthenticationEvents */ public const ATTEMPT = 'scheb_two_factor.authentication.attempt'; + /** + * When a backup-code is used. + */ + public const BACKUP_CODE_USED = 'scheb_two_factor.authentication.backup_code_used'; + /** * When two-factor authentication was successful (code was valid) for a single provider. */ diff --git a/tests/Security/Http/EventListener/CheckBackupCodeListenerTest.php b/tests/Security/Http/EventListener/CheckBackupCodeListenerTest.php index 66fda4c4..0fa13997 100644 --- a/tests/Security/Http/EventListener/CheckBackupCodeListenerTest.php +++ b/tests/Security/Http/EventListener/CheckBackupCodeListenerTest.php @@ -7,6 +7,9 @@ use PHPUnit\Framework\MockObject\MockObject; use Scheb\TwoFactorBundle\Security\Http\EventListener\CheckBackupCodeListener; use Scheb\TwoFactorBundle\Security\TwoFactor\Backup\BackupCodeManagerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @property CheckBackupCodeListener $listener @@ -14,13 +17,19 @@ class CheckBackupCodeListenerTest extends AbstractCheckCodeListenerTestSetup { private MockObject|BackupCodeManagerInterface $backupCodeManager; + private MockObject|TokenStorageInterface $tokenStorage; + private MockObject|RequestStack $requestStack; + private MockObject|EventDispatcherInterface $eventDispatcher; protected function setUp(): void { parent::setUp(); $this->backupCodeManager = $this->createMock(BackupCodeManagerInterface::class); - $this->listener = new CheckBackupCodeListener($this->preparationRecorder, $this->backupCodeManager); + $this->tokenStorage = $this->createMock(TokenStorageInterface::class); + $this->requestStack = $this->createMock(RequestStack::class); + $this->eventDispatecher = $this->createMock(EventDispatcherInterface::class); + $this->listener = new CheckBackupCodeListener($this->preparationRecorder, $this->backupCodeManager, $this->tokenStorage, $this->requestStack, $this->eventDispatecher); } protected function expectDoNothing(): void