Thanks to visit codestin.com
Credit goes to github.com

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

7 files changed

Lines changed: 136 additions & 1 deletion

File tree

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)