diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index f7b868fcbadea..654e8c5cbc3f1 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -23,6 +23,7 @@ CHANGELOG * Dispatch `SwitchUserEvent` on `security.switch_user` * Deprecated `Argon2iPasswordEncoder`, use `SodiumPasswordEncoder` instead * Deprecated `BCryptPasswordEncoder`, use `NativePasswordEncoder` instead + * Added `DeauthenticatedEvent` dispatched in case the user has changed when trying to refresh it 4.2.0 ----- diff --git a/src/Symfony/Component/Security/Http/Event/DeauthenticatedEvent.php b/src/Symfony/Component/Security/Http/Event/DeauthenticatedEvent.php new file mode 100644 index 0000000000000..2c1653b3c04ef --- /dev/null +++ b/src/Symfony/Component/Security/Http/Event/DeauthenticatedEvent.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Event; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Deauthentication happens in case the user has changed when trying to refresh it. + * + * @author Hamza Amrouche + */ +class DeauthenticatedEvent extends Event +{ + private $originalToken; + private $refreshedToken; + + public function __construct(TokenInterface $originalToken, TokenInterface $refreshedToken) + { + $this->originalToken = $originalToken; + $this->refreshedToken = $refreshedToken; + } + + public function getRefreshedToken(): TokenInterface + { + return $this->refreshedToken; + } + + public function getOriginalToken(): TokenInterface + { + return $this->originalToken; + } +} diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index 2947105fc29a2..16cdc8f9e23f8 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -28,6 +28,7 @@ use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; /** * ContextListener manages the SecurityContext persistence through a session. @@ -225,6 +226,10 @@ protected function refreshUser(TokenInterface $token) $this->logger->debug('Token was deauthenticated after trying to refresh it.'); } + if (null !== $this->dispatcher) { + $this->dispatcher->dispatch(new DeauthenticatedEvent($token, $newToken), DeauthenticatedEvent::class); + } + return null; } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index 644ba93ec57ec..b0792bb279bde 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -31,6 +31,7 @@ use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; use Symfony\Component\Security\Http\Firewall\ContextListener; class ContextListenerTest extends TestCase @@ -313,6 +314,33 @@ public function testAcceptsProvidersAsTraversable() $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } + public function testDeauthenticatedEvent() + { + $tokenStorage = new TokenStorage(); + $refreshedUser = new User('foobar', 'baz'); + + $user = new User('foo', 'bar'); + $session = new Session(new MockArraySessionStorage()); + $session->set('_security_context_key', serialize(new UsernamePasswordToken($user, '', 'context_key', ['ROLE_USER']))); + + $request = new Request(); + $request->setSession($session); + $request->cookies->set('MOCKSESSID', true); + + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addListener(DeauthenticatedEvent::class, function (DeauthenticatedEvent $event) use ($user) { + $this->assertTrue($event->getOriginalToken()->isAuthenticated()); + $this->assertEquals($event->getOriginalToken()->getUser(), $user); + $this->assertFalse($event->getRefreshedToken()->isAuthenticated()); + $this->assertNotEquals($event->getRefreshedToken()->getUser(), $user); + }); + + $listener = new ContextListener($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], 'context_key', null, $eventDispatcher); + $listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST)); + + $this->assertNull($tokenStorage->getToken()); + } + protected function runSessionOnKernelResponse($newToken, $original = null) { $session = new Session(new MockArraySessionStorage());