Skip to content

Commit 3b041a7

Browse files
committed
[Security] Deprecate callable firewall listeners
1 parent d69f345 commit 3b041a7

File tree

10 files changed

+132
-27
lines changed

10 files changed

+132
-27
lines changed

UPGRADE-7.4.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,8 @@ HttpClient
2222
----------
2323

2424
* Deprecate using amphp/http-client < 5
25+
26+
Security
27+
--------
28+
29+
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead

src/Symfony/Bundle/SecurityBundle/Debug/TraceableFirewallListener.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext;
1717
use Symfony\Component\HttpKernel\Event\RequestEvent;
1818
use Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticatorManagerListener;
19+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
1920
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
2021
use Symfony\Contracts\Service\ResetInterface;
2122

src/Symfony/Bundle/SecurityBundle/Security/FirewallContext.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\SecurityBundle\Security;
1313

1414
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
15+
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
1516
use Symfony\Component\Security\Http\Firewall\LogoutListener;
1617

1718
/**
@@ -39,7 +40,7 @@ public function getConfig(): ?FirewallConfig
3940
}
4041

4142
/**
42-
* @return iterable<mixed, callable>
43+
* @return iterable<mixed, FirewallListenerInterface|callable>
4344
*/
4445
public function getListeners(): iterable
4546
{

src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Security;
1313

14+
use Symfony\Component\HttpFoundation\Request;
1415
use Symfony\Component\HttpKernel\Event\RequestEvent;
1516
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
1617
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
18+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
1719
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
1820
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
1921
use Symfony\Component\Security\Http\Firewall\LogoutListener;
@@ -23,7 +25,7 @@
2325
*
2426
* @author Nicolas Grekas <p@tchwork.com>
2527
*/
26-
class LazyFirewallContext extends FirewallContext
28+
class LazyFirewallContext extends FirewallContext implements FirewallListenerInterface
2729
{
2830
public function __construct(
2931
iterable $listeners,
@@ -40,19 +42,26 @@ public function getListeners(): iterable
4042
return [$this];
4143
}
4244

43-
public function __invoke(RequestEvent $event): void
45+
public function supports(Request $request): ?bool
46+
{
47+
return true;
48+
}
49+
50+
public function authenticate(RequestEvent $event): void
4451
{
4552
$listeners = [];
4653
$request = $event->getRequest();
4754
$lazy = $request->isMethodCacheable();
4855

4956
foreach (parent::getListeners() as $listener) {
50-
if (!$lazy || !$listener instanceof FirewallListenerInterface) {
57+
if (!$listener instanceof FirewallListenerInterface) {
58+
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);
59+
5160
$listeners[] = $listener;
52-
$lazy = $lazy && $listener instanceof FirewallListenerInterface;
61+
$lazy = false;
5362
} elseif (false !== $supports = $listener->supports($request)) {
5463
$listeners[] = [$listener, 'authenticate'];
55-
$lazy = null === $supports;
64+
$lazy = $lazy && null === $supports;
5665
}
5766
}
5867

@@ -75,4 +84,14 @@ public function __invoke(RequestEvent $event): void
7584
}
7685
});
7786
}
87+
88+
public static function getPriority(): int
89+
{
90+
return 0;
91+
}
92+
93+
public function __invoke(RequestEvent $event): void
94+
{
95+
$this->authenticate($event);
96+
}
7897
}

src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
3333
use Symfony\Component\Security\Core\Role\RoleHierarchy;
3434
use Symfony\Component\Security\Core\User\InMemoryUser;
35+
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
3536
use Symfony\Component\Security\Http\FirewallMapInterface;
3637
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
3738
use Symfony\Component\VarDumper\Caster\ClassStub;
@@ -193,8 +194,24 @@ public function testGetListeners()
193194
$request = new Request();
194195
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);
195196
$event->setResponse($response = new Response());
196-
$listener = function ($e) use ($event, &$listenerCalled) {
197-
$listenerCalled += $e === $event;
197+
$listener = new class implements FirewallListenerInterface
198+
{
199+
public int $callCount = 0;
200+
201+
public function supports(Request $request): ?bool
202+
{
203+
return true;
204+
}
205+
206+
public function authenticate(RequestEvent $event): void
207+
{
208+
++$this->callCount;
209+
}
210+
211+
public static function getPriority(): int
212+
{
213+
return 0;
214+
}
198215
};
199216
$firewallMap = $this
200217
->getMockBuilder(FirewallMap::class)
@@ -217,9 +234,9 @@ public function testGetListeners()
217234
$collector = new SecurityDataCollector(null, null, null, null, $firewallMap, $firewall, true);
218235
$collector->collect($request, $response);
219236

220-
$this->assertNotEmpty($collected = $collector->getListeners()[0]);
237+
$this->assertCount(1, $collector->getListeners());
221238
$collector->lateCollect();
222-
$this->assertSame(1, $listenerCalled);
239+
$this->assertSame(1, $listener->callCount);
223240
}
224241

225242
public function testCollectCollectsDecisionLogWhenStrategyIsAffirmative()

src/Symfony/Bundle/SecurityBundle/Tests/Debug/TraceableFirewallListenerTest.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
3131
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
3232
use Symfony\Component\Security\Http\Firewall\AuthenticatorManagerListener;
33+
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
3334
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
3435

3536
/**
@@ -41,9 +42,25 @@ public function testOnKernelRequestRecordsListeners()
4142
{
4243
$request = new Request();
4344
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);
44-
$event->setResponse($response = new Response());
45-
$listener = function ($e) use ($event, &$listenerCalled) {
46-
$listenerCalled += $e === $event;
45+
$event->setResponse(new Response());
46+
$listener = new class implements FirewallListenerInterface
47+
{
48+
public int $callCount = 0;
49+
50+
public function supports(Request $request): ?bool
51+
{
52+
return true;
53+
}
54+
55+
public function authenticate(RequestEvent $event): void
56+
{
57+
++$this->callCount;
58+
}
59+
60+
public static function getPriority(): int
61+
{
62+
return 0;
63+
}
4764
};
4865
$firewallMap = $this->createMock(FirewallMap::class);
4966
$firewallMap

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"symfony/clock": "^6.4|^7.0|^8.0",
2323
"symfony/config": "^7.3|^8.0",
2424
"symfony/dependency-injection": "^6.4.11|^7.1.4|^8.0",
25+
"symfony/deprecation-contracts": "^2.5|^3",
2526
"symfony/event-dispatcher": "^6.4|^7.0|^8.0",
2627
"symfony/http-kernel": "^6.4|^7.0|^8.0",
2728
"symfony/http-foundation": "^6.4|^7.0|^8.0",

src/Symfony/Component/Security/Http/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.4
5+
---
6+
7+
* Deprecate callable firewall listeners, extend `AbstractListener` or implement `FirewallListenerInterface` instead
8+
49
7.3
510
---
611

src/Symfony/Component/Security/Http/Firewall.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
1717
use Symfony\Component\HttpKernel\Event\RequestEvent;
1818
use Symfony\Component\HttpKernel\KernelEvents;
19+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
1920
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
2021
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
2122
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
@@ -123,6 +124,8 @@ protected function callListeners(RequestEvent $event, iterable $listeners)
123124
{
124125
foreach ($listeners as $listener) {
125126
if (!$listener instanceof FirewallListenerInterface) {
127+
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);
128+
126129
$listener($event);
127130
} elseif (false !== $listener->supports($event->getRequest())) {
128131
$listener->authenticate($event);
@@ -134,8 +137,8 @@ protected function callListeners(RequestEvent $event, iterable $listeners)
134137
}
135138
}
136139

137-
private function getListenerPriority(object $logoutListener): int
140+
private function getListenerPriority(object $listener): int
138141
{
139-
return $logoutListener instanceof FirewallListenerInterface ? $logoutListener->getPriority() : 0;
142+
return $listener instanceof FirewallListenerInterface ? $listener->getPriority() : 0;
140143
}
141144
}

src/Symfony/Component/Security/Http/Tests/FirewallTest.php

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Security\Http\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait;
1516
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1617
use Symfony\Component\HttpFoundation\Request;
1718
use Symfony\Component\HttpFoundation\Response;
@@ -25,6 +26,8 @@
2526

2627
class FirewallTest extends TestCase
2728
{
29+
use ExpectUserDeprecationMessageTrait;
30+
2831
public function testOnKernelRequestRegistersExceptionListener()
2932
{
3033
$dispatcher = $this->createMock(EventDispatcherInterface::class);
@@ -54,21 +57,26 @@ public function testOnKernelRequestRegistersExceptionListener()
5457

5558
public function testOnKernelRequestStopsWhenThereIsAResponse()
5659
{
57-
$called = [];
60+
$listener = new class extends AbstractListener
61+
{
62+
public int $callCount = 0;
5863

59-
$first = function () use (&$called) {
60-
$called[] = 1;
61-
};
64+
public function supports(Request $request): ?bool
65+
{
66+
return true;
67+
}
6268

63-
$second = function () use (&$called) {
64-
$called[] = 2;
69+
public function authenticate(RequestEvent $event): void
70+
{
71+
++$this->callCount;
72+
}
6573
};
6674

6775
$map = $this->createMock(FirewallMapInterface::class);
6876
$map
6977
->expects($this->once())
7078
->method('getListeners')
71-
->willReturn([[$first, $second], null, null])
79+
->willReturn([[$listener, $listener], null, null])
7280
;
7381

7482
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), new Request(), HttpKernelInterface::MAIN_REQUEST);
@@ -77,7 +85,7 @@ public function testOnKernelRequestStopsWhenThereIsAResponse()
7785
$firewall = new Firewall($map, $this->createMock(EventDispatcherInterface::class));
7886
$firewall->onKernelRequest($event);
7987

80-
$this->assertSame([1], $called);
88+
$this->assertSame(1, $listener->callCount);
8189
}
8290

8391
public function testOnKernelRequestWithSubRequest()
@@ -100,11 +108,10 @@ public function testOnKernelRequestWithSubRequest()
100108
$this->assertFalse($event->hasResponse());
101109
}
102110

103-
public function testListenersAreCalled()
111+
public function testFirewallListenersAreCalled()
104112
{
105113
$calledListeners = [];
106114

107-
$callableListener = static function() use(&$calledListeners) { $calledListeners[] = 'callableListener'; };
108115
$firewallListener = new class($calledListeners) implements FirewallListenerInterface {
109116
public function __construct(private array &$calledListeners) {}
110117

@@ -144,14 +151,43 @@ public function authenticate(RequestEvent $event): void
144151
->expects($this->once())
145152
->method('getListeners')
146153
->with($this->equalTo($request))
147-
->willReturn([[$callableListener, $firewallListener, $callableFirewallListener], null, null])
154+
->willReturn([[$firewallListener, $callableFirewallListener], null, null])
155+
;
156+
157+
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);
158+
159+
$firewall = new Firewall($map, $this->createMock(EventDispatcherInterface::class));
160+
$firewall->onKernelRequest($event);
161+
162+
$this->assertSame(['firewallListener', 'callableFirewallListener'], $calledListeners);
163+
}
164+
165+
/**
166+
* @group legacy
167+
*/
168+
public function testCallableListenersAreCalled()
169+
{
170+
$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.');
171+
172+
$calledListeners = [];
173+
174+
$callableListener = static function() use(&$calledListeners) { $calledListeners[] = 'callableListener'; };
175+
176+
$request = $this->createMock(Request::class);
177+
178+
$map = $this->createMock(FirewallMapInterface::class);
179+
$map
180+
->expects($this->once())
181+
->method('getListeners')
182+
->with($this->equalTo($request))
183+
->willReturn([[$callableListener], null, null])
148184
;
149185

150186
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);
151187

152188
$firewall = new Firewall($map, $this->createMock(EventDispatcherInterface::class));
153189
$firewall->onKernelRequest($event);
154190

155-
$this->assertSame(['callableListener', 'firewallListener', 'callableFirewallListener'], $calledListeners);
191+
$this->assertSame(['callableListener'], $calledListeners);
156192
}
157193
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy