From fcadbf018a1d26d0fd3d4a8dea7bcf8484188584 Mon Sep 17 00:00:00 2001 From: matlec Date: Mon, 23 Jun 2025 18:59:48 +0200 Subject: [PATCH] Remove callable firewall listeners support --- UPGRADE-8.0.md | 4 ++ .../Bundle/SecurityBundle/CHANGELOG.md | 5 ++ .../Debug/TraceableFirewallListener.php | 22 +++----- .../Debug/TraceableListenerTrait.php | 51 ------------------- .../Debug/WrappedLazyListener.php | 25 ++++++++- .../SecurityBundle/Debug/WrappedListener.php | 42 --------------- .../Security/FirewallContext.php | 4 +- .../Security/LazyFirewallContext.php | 22 ++------ .../Debug/TraceableFirewallListenerTest.php | 4 +- .../Bundle/SecurityBundle/composer.json | 1 - .../Component/Security/Http/CHANGELOG.md | 6 +++ .../Component/Security/Http/Firewall.php | 23 +++------ .../Http/Firewall/AbstractListener.php | 12 ----- .../Component/Security/Http/FirewallMap.php | 5 +- .../Security/Http/FirewallMapInterface.php | 3 +- .../Security/Http/Tests/FirewallTest.php | 29 ----------- 16 files changed, 65 insertions(+), 193 deletions(-) delete mode 100644 src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php delete mode 100644 src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php diff --git a/UPGRADE-8.0.md b/UPGRADE-8.0.md index 644b68ea82b87..80948b9422e55 100644 --- a/UPGRADE-8.0.md +++ b/UPGRADE-8.0.md @@ -326,6 +326,10 @@ Security +} ``` + * Remove callable firewall listeners support, extend `AbstractListener` or implement `FirewallListenerInterface` instead + * Remove `AbstractListener::__invoke` + * Remove `LazyFirewallContext::__invoke()` + Serializer ---------- diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 73754eddb83a5..6bc26a1312870 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +8.0 +--- + + * Remove `LazyFirewallContext::__invoke()` + 7.4 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php index f3a8ca22b46ff..74d26a1ea298f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php @@ -16,8 +16,6 @@ use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; -use Symfony\Component\Security\Http\Firewall\AbstractListener; -use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Contracts\Service\ResetInterface; /** @@ -33,7 +31,7 @@ final class TraceableFirewallListener extends FirewallListener implements ResetI public function getWrappedListeners(): array { return array_map( - static fn (WrappedListener|WrappedLazyListener $listener) => $listener->getInfo(), + static fn (WrappedLazyListener $listener) => $listener->getInfo(), $this->wrappedListeners ); } @@ -62,10 +60,7 @@ protected function callListeners(RequestEvent $event, iterable $listeners): void if ($listener instanceof TraceableAuthenticatorManagerListener) { $contextAuthenticatorManagerListener ??= $listener; } - $contextWrappedListeners[] = $listener instanceof FirewallListenerInterface - ? new WrappedLazyListener($listener) - : new WrappedListener($listener) - ; + $contextWrappedListeners[] = new WrappedLazyListener($listener); } $this->listeners = $contextWrappedListeners; }, $listener, FirewallContext::class)(); @@ -78,10 +73,7 @@ protected function callListeners(RequestEvent $event, iterable $listeners): void if ($listener instanceof TraceableAuthenticatorManagerListener) { $this->authenticatorManagerListener ??= $listener; } - $wrappedListener = $listener instanceof FirewallListenerInterface - ? new WrappedLazyListener($listener) - : new WrappedListener($listener) - ; + $wrappedListener = new WrappedLazyListener($listener); $this->wrappedListeners[] = $wrappedListener; $requestListeners[] = $wrappedListener; @@ -89,12 +81,12 @@ protected function callListeners(RequestEvent $event, iterable $listeners): void } foreach ($requestListeners as $listener) { - if (!$listener instanceof FirewallListenerInterface) { - $listener($event); - } elseif (false !== $listener->supports($event->getRequest())) { - $listener->authenticate($event); + if (false === $listener->supports($event->getRequest())) { + continue; } + $listener->authenticate($event); + if ($event->hasResponse()) { break; } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php b/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php deleted file mode 100644 index 0c4ff9e5cfb90..0000000000000 --- a/src/Symfony/Bundle/SecurityBundle/Debug/TraceableListenerTrait.php +++ /dev/null @@ -1,51 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\SecurityBundle\Debug; - -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; -use Symfony\Component\VarDumper\Caster\ClassStub; - -/** - * @author Robin Chalas - * - * @internal - */ -trait TraceableListenerTrait -{ - private ?Response $response = null; - private mixed $listener; - private ?float $time = null; - private object $stub; - - /** - * Proxies all method calls to the original listener. - */ - public function __call(string $method, array $arguments): mixed - { - return $this->listener->{$method}(...$arguments); - } - - public function getWrappedListener(): mixed - { - return $this->listener; - } - - public function getInfo(): array - { - return [ - 'response' => $this->response, - 'time' => $this->time, - 'stub' => $this->stub ??= ClassStub::wrapCallable($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener() : $this->listener), - ]; - } -} diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php index 55c70ec5780d6..98c160cd8fa77 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedLazyListener.php @@ -12,10 +12,13 @@ namespace Symfony\Bundle\SecurityBundle\Debug; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Exception\LazyResponseException; +use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener; use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; +use Symfony\Component\VarDumper\Caster\ClassStub; /** * Wraps a lazy security listener. @@ -26,7 +29,10 @@ */ final class WrappedLazyListener extends AbstractListener { - use TraceableListenerTrait; + private ?Response $response = null; + private FirewallListenerInterface $listener; + private ?float $time = null; + private ClassStub $stub; public function __construct(FirewallListenerInterface $listener) { @@ -54,4 +60,21 @@ public function authenticate(RequestEvent $event): void $this->response = $event->getResponse(); } + + public function getInfo(): array + { + return [ + 'response' => $this->response, + 'time' => $this->time, + 'stub' => $this->stub ??= new ClassStub($this->listener instanceof TraceableAuthenticatorManagerListener ? $this->listener->getAuthenticatorManagerListener()::class : $this->listener::class), + ]; + } + + /** + * Proxies all method calls to the original listener. + */ + public function __call(string $method, array $arguments): mixed + { + return $this->listener->{$method}(...$arguments); + } } diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php deleted file mode 100644 index 7a3941952fe10..0000000000000 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\SecurityBundle\Debug; - -use Symfony\Component\HttpKernel\Event\RequestEvent; - -/** - * Wraps a security listener for calls record. - * - * @author Robin Chalas - * - * @internal - */ -final class WrappedListener -{ - use TraceableListenerTrait; - - /** - * @param callable(RequestEvent):void $listener - */ - public function __construct(callable $listener) - { - $this->listener = $listener; - } - - public function __invoke(RequestEvent $event): void - { - $startTime = microtime(true); - ($this->listener)($event); - $this->time = microtime(true) - $startTime; - $this->response = $event->getResponse(); - } -} diff --git a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php index 1da8913906f01..c4896f456c699 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php @@ -24,7 +24,7 @@ class FirewallContext { /** - * @param iterable $listeners + * @param iterable $listeners */ public function __construct( private iterable $listeners, @@ -40,7 +40,7 @@ public function getConfig(): ?FirewallConfig } /** - * @return iterable + * @return iterable */ public function getListeners(): iterable { diff --git a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php index 09526fde6c5cd..447002f973877 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php @@ -15,7 +15,6 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Http\Event\LazyResponseEvent; -use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener; use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Component\Security\Http\Firewall\LogoutListener; @@ -54,20 +53,15 @@ public function authenticate(RequestEvent $event): void $lazy = $request->isMethodCacheable(); foreach (parent::getListeners() as $listener) { - if (!$listener instanceof FirewallListenerInterface) { - trigger_deprecation('symfony/security-http', '7.4', 'Using a callable as firewall listener is deprecated, extend "%s" or implement "%s" instead.', AbstractListener::class, FirewallListenerInterface::class); - + if (false !== $supports = $listener->supports($request)) { $listeners[] = $listener; - $lazy = false; - } elseif (false !== $supports = $listener->supports($request)) { - $listeners[] = [$listener, 'authenticate']; $lazy = $lazy && null === $supports; } } if (!$lazy) { foreach ($listeners as $listener) { - $listener($event); + $listener->authenticate($event); if ($event->hasResponse()) { return; @@ -80,7 +74,7 @@ public function authenticate(RequestEvent $event): void $this->tokenStorage->setInitializer(function () use ($event, $listeners) { $event = new LazyResponseEvent($event); foreach ($listeners as $listener) { - $listener($event); + $listener->authenticate($event); } }); } @@ -89,14 +83,4 @@ public static function getPriority(): int { return 0; } - - /** - * @deprecated since Symfony 7.4, to be removed in 8.0 - */ - public function __invoke(RequestEvent $event): void - { - trigger_deprecation('symfony/security-bundle', '7.4', 'The "%s()" method is deprecated since Symfony 7.4 and will be removed in 8.0.', __METHOD__); - - $this->authenticate($event); - } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php index db6e8a0e548c8..376cb194a109b 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php @@ -33,6 +33,7 @@ use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener; use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Symfony\Component\VarDumper\Caster\ClassStub; /** * @group time-sensitive @@ -75,7 +76,8 @@ public function authenticate(RequestEvent $event): void $listeners = $firewall->getWrappedListeners(); $this->assertCount(1, $listeners); - $this->assertSame($listener, $listeners[0]['stub']); + $this->assertInstanceOf(ClassStub::class, $listeners[0]['stub']); + $this->assertSame((string) new ClassStub($listener::class), (string) $listeners[0]['stub']); } public function testOnKernelRequestRecordsAuthenticatorsInfo() diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 497d4d2f5465b..614769d2dabdb 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -22,7 +22,6 @@ "symfony/clock": "^7.4|^8.0", "symfony/config": "^7.4|^8.0", "symfony/dependency-injection": "^7.4|^8.0", - "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", diff --git a/src/Symfony/Component/Security/Http/CHANGELOG.md b/src/Symfony/Component/Security/Http/CHANGELOG.md index 6c485dc6e5450..9df71105c01ee 100644 --- a/src/Symfony/Component/Security/Http/CHANGELOG.md +++ b/src/Symfony/Component/Security/Http/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +8.0 +--- + + * Remove callable firewall listeners support, extend `AbstractListener` or implement `FirewallListenerInterface` instead + * Remove `AbstractListener::__invoke` + 7.4 --- diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index dd858d2af7c42..eb18f3deab1ad 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -16,9 +16,7 @@ use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\ExceptionListener; -use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -66,14 +64,12 @@ public function onKernelRequest(RequestEvent $event): void // Authentication listeners are pre-sorted by SortFirewallListenersPass $authenticationListeners = function () use ($authenticationListeners, $logoutListener) { if (null !== $logoutListener) { - $logoutListenerPriority = $this->getListenerPriority($logoutListener); + $logoutListenerPriority = $logoutListener::getPriority(); } foreach ($authenticationListeners as $listener) { - $listenerPriority = $this->getListenerPriority($listener); - // Yielding the LogoutListener at the correct position - if (null !== $logoutListener && $listenerPriority < $logoutListenerPriority) { + if (null !== $logoutListener && $listener::getPriority() < $logoutListenerPriority) { yield $logoutListener; $logoutListener = null; } @@ -111,22 +107,15 @@ public static function getSubscribedEvents(): array protected function callListeners(RequestEvent $event, iterable $listeners): void { foreach ($listeners as $listener) { - if (!$listener instanceof FirewallListenerInterface) { - trigger_deprecation('symfony/security-http', '7.4', 'Using a callable as firewall listener is deprecated, extend "%s" or implement "%s" instead.', AbstractListener::class, FirewallListenerInterface::class); - - $listener($event); - } elseif (false !== $listener->supports($event->getRequest())) { - $listener->authenticate($event); + if (false === $listener->supports($event->getRequest())) { + continue; } + $listener->authenticate($event); + if ($event->hasResponse()) { break; } } } - - private function getListenerPriority(object $listener): int - { - return $listener instanceof FirewallListenerInterface ? $listener->getPriority() : 0; - } } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php index b30614defd215..00a8373cec8c6 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractListener.php @@ -20,18 +20,6 @@ */ abstract class AbstractListener implements FirewallListenerInterface { - /** - * @deprecated since Symfony 7.4, to be removed in 8.0 - */ - final public function __invoke(RequestEvent $event): void - { - trigger_deprecation('symfony/security-http', '7.4', 'The "%s()" method is deprecated since Symfony 7.4 and will be removed in 8.0.', __METHOD__); - - if (false !== $this->supports($event->getRequest())) { - $this->authenticate($event); - } - } - public static function getPriority(): int { return 0; // Default diff --git a/src/Symfony/Component/Security/Http/FirewallMap.php b/src/Symfony/Component/Security/Http/FirewallMap.php index 3b01cbdc161a6..366222c706c5f 100644 --- a/src/Symfony/Component/Security/Http/FirewallMap.php +++ b/src/Symfony/Component/Security/Http/FirewallMap.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Component\Security\Http\Firewall\LogoutListener; /** @@ -25,12 +26,12 @@ class FirewallMap implements FirewallMapInterface { /** - * @var list, ExceptionListener|null, LogoutListener|null}> + * @var list, ExceptionListener|null, LogoutListener|null}> */ private array $map = []; /** - * @param list $listeners + * @param list $listeners */ public function add(?RequestMatcherInterface $requestMatcher = null, array $listeners = [], ?ExceptionListener $exceptionListener = null, ?LogoutListener $logoutListener = null): void { diff --git a/src/Symfony/Component/Security/Http/FirewallMapInterface.php b/src/Symfony/Component/Security/Http/FirewallMapInterface.php index fa43d6a6e9ba3..ba4349dcea98b 100644 --- a/src/Symfony/Component/Security/Http/FirewallMapInterface.php +++ b/src/Symfony/Component/Security/Http/FirewallMapInterface.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Firewall\ExceptionListener; +use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface; use Symfony\Component\Security\Http\Firewall\LogoutListener; /** @@ -35,7 +36,7 @@ interface FirewallMapInterface * If there is no logout listener, the third element of the outer array * must be null. * - * @return array{iterable, ExceptionListener, LogoutListener} + * @return array{iterable, ExceptionListener, LogoutListener} */ public function getListeners(Request $request): array; } diff --git a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php index bfa9bebdd0b32..403560517cbd6 100644 --- a/src/Symfony/Component/Security/Http/Tests/FirewallTest.php +++ b/src/Symfony/Component/Security/Http/Tests/FirewallTest.php @@ -160,33 +160,4 @@ public function authenticate(RequestEvent $event): void $this->assertSame(['firewallListener', 'callableFirewallListener'], $calledListeners); } - - /** - * @group legacy - */ - public function testCallableListenersAreCalled() - { - $calledListeners = []; - - $callableListener = static function() use(&$calledListeners) { $calledListeners[] = 'callableListener'; }; - - $request = $this->createMock(Request::class); - - $map = $this->createMock(FirewallMapInterface::class); - $map - ->expects($this->once()) - ->method('getListeners') - ->with($this->equalTo($request)) - ->willReturn([[$callableListener], null, null]) - ; - - $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST); - - $firewall = new Firewall($map, $this->createMock(EventDispatcherInterface::class)); - - $this->expectUserDeprecationMessage('Since symfony/security-http 7.4: Using a callable as firewall listener is deprecated, extend "Symfony\Component\Security\Http\Firewall\AbstractListener" or implement "Symfony\Component\Security\Http\Firewall\FirewallListenerInterface" instead.'); - $firewall->onKernelRequest($event); - - $this->assertSame(['callableListener'], $calledListeners); - } }