Skip to content

Commit e824ed6

Browse files
committed
[Security] Add a method in the security helper to ease programmatic logout (#40663)
1 parent 8bdbd34 commit e824ed6

File tree

7 files changed

+136
-1
lines changed

7 files changed

+136
-1
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,12 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
353353
$container->register($firewallEventDispatcherId, EventDispatcher::class)
354354
->addTag('event_dispatcher.dispatcher', ['name' => $firewallEventDispatcherId]);
355355

356+
$container
357+
->getDefinition('security.firewall.event_dispatcher_map')
358+
->setLazy(true)
359+
->addMethodCall('addEventDispatcher', [$id, new Reference($firewallEventDispatcherId)])
360+
;
361+
356362
// Register listeners
357363
$listeners = [];
358364
$listenerKeys = [];

src/Symfony/Bundle/SecurityBundle/Resources/config/security.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
->args([service_locator([
8080
'security.token_storage' => service('security.token_storage'),
8181
'security.authorization_checker' => service('security.authorization_checker'),
82+
'request_stack' => service('request_stack'),
83+
'security.firewall.map' => service('security.firewall.map'),
84+
'security.firewall.event_dispatcher_map' => service('security.firewall.event_dispatcher_map'),
8285
])])
8386
->alias(Security::class, 'security.helper')
8487

src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
2626
use Symfony\Component\Security\Http\Firewall\LogoutListener;
2727
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
28+
use Symfony\Component\Security\Http\FirewallEventDispatcherMap;
2829

2930
return static function (ContainerConfigurator $container) {
3031
$container->services()
@@ -159,5 +160,7 @@
159160
service('security.access_map'),
160161
])
161162
->tag('monolog.logger', ['channel' => 'security'])
163+
164+
->set('security.firewall.event_dispatcher_map', FirewallEventDispatcherMap::class)
162165
;
163166
};

src/Symfony/Component/Security/Core/Security.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1616
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
1717
use Symfony\Component\Security\Core\User\UserInterface;
18+
use Symfony\Component\Security\Http\Event\LogoutEvent;
1819

1920
/**
2021
* Helper class for commonly-needed security tasks.
@@ -57,4 +58,23 @@ public function getToken(): ?TokenInterface
5758
{
5859
return $this->container->get('security.token_storage')->getToken();
5960
}
61+
62+
/**
63+
* Logout the current user automatically. Dispatch the logout event.
64+
*/
65+
public function logout(): void
66+
{
67+
$request = $this->container->get('request_stack')->getCurrentRequest();
68+
$logoutEvent = new LogoutEvent($request, $this->container->get('security.token_storage')->getToken());
69+
70+
$firewallName = $this
71+
->container
72+
->get('security.firewall.map')
73+
->getFirewallConfig($request)
74+
->getName()
75+
;
76+
77+
$this->container->get('security.firewall.event_dispatcher_map')->getEventDispatcher($firewallName)->dispatch($logoutEvent);
78+
$this->container->get('security.token_storage')->setToken();
79+
}
6080
}

src/Symfony/Component/Security/Core/Tests/SecurityTest.php

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,22 @@
1111

1212
namespace Symfony\Component\Security\Core\Tests;
1313

14+
use PHPUnit\Framework\MockObject\MockObject;
1415
use PHPUnit\Framework\TestCase;
1516
use Psr\Container\ContainerInterface;
17+
use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
18+
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
19+
use Symfony\Component\HttpFoundation\Request;
20+
use Symfony\Component\HttpFoundation\RequestStack;
1621
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1722
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1823
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
1924
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2025
use Symfony\Component\Security\Core\Security;
2126
use Symfony\Component\Security\Core\User\InMemoryUser;
27+
use Symfony\Component\Security\Http\Event\LogoutEvent;
28+
use Symfony\Component\Security\Http\FirewallEventDispatcherMapInterface;
29+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2230

2331
class SecurityTest extends TestCase
2432
{
@@ -81,7 +89,67 @@ public function testIsGranted()
8189
$this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT'));
8290
}
8391

84-
private function createContainer($serviceId, $serviceObject)
92+
public function testAutoLogout()
93+
{
94+
$request = new Request();
95+
$requestStack = $this->createMock(RequestStack::class);
96+
$requestStack
97+
->expects($this->once())
98+
->method('getCurrentRequest')
99+
->willReturn($request)
100+
;
101+
102+
$token = $this->createMock(TokenInterface::class);
103+
$tokenStorage = $this->createMock(TokenStorageInterface::class);
104+
$tokenStorage
105+
->expects($this->once())
106+
->method('getToken')
107+
->willReturn($token)
108+
;
109+
$tokenStorage
110+
->expects($this->once())
111+
->method('setToken')
112+
;
113+
114+
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
115+
$eventDispatcher
116+
->expects($this->once())
117+
->method('dispatch')
118+
->with(new LogoutEvent($request, $token))
119+
;
120+
121+
$firewallMap = $this->createMock(FirewallMap::class);
122+
$firewallConfig = new FirewallConfig('my_firewall', 'user_checker');
123+
$firewallMap
124+
->expects($this->once())
125+
->method('getFirewallConfig')
126+
->willReturn($firewallConfig)
127+
;
128+
129+
$eventDispatcherMap = $this->createMock(FirewallEventDispatcherMapInterface::class);
130+
$eventDispatcherMap
131+
->expects($this->once())
132+
->method('getEventDispatcher')
133+
->with('my_firewall')
134+
->willReturn($eventDispatcher)
135+
;
136+
137+
$container = $this->createMock(ContainerInterface::class);
138+
$container
139+
->expects($this->atLeastOnce())
140+
->method('get')
141+
->willReturnMap([
142+
['request_stack', $requestStack],
143+
['security.token_storage', $tokenStorage],
144+
['security.firewall.map', $firewallMap],
145+
['security.firewall.event_dispatcher_map', $eventDispatcherMap],
146+
])
147+
;
148+
$security = new Security($container);
149+
$security->logout();
150+
}
151+
152+
private function createContainer($serviceId, $serviceObject): MockObject
85153
{
86154
$container = $this->createMock(ContainerInterface::class);
87155

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Http;
4+
5+
use Symfony\Component\Security\Core\Exception\LogicException;
6+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
7+
8+
class FirewallEventDispatcherMap implements FirewallEventDispatcherMapInterface
9+
{
10+
private array $map = [];
11+
12+
public function addEventDispatcher(string $firewallName, EventDispatcherInterface $eventDispatcher): void
13+
{
14+
$this->map[$firewallName] = $eventDispatcher;
15+
}
16+
17+
public function getEventDispatcher(string $firewallName): ?EventDispatcherInterface
18+
{
19+
if (array_key_exists($firewallName, $this->map)) {
20+
return $this->map[$firewallName];
21+
}
22+
23+
throw new LogicException(sprintf('The firewall "%s" does not have an event dispatcher', $firewallName));
24+
}
25+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Http;
4+
5+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
6+
7+
interface FirewallEventDispatcherMapInterface
8+
{
9+
public function getEventDispatcher(string $firewallName): ?EventDispatcherInterface;
10+
}

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