Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1635a6a

Browse files
committed
feature#20516 [Security][SecurityBundle] Enhance automatic logout url generation (ogizanagi)
This PR was merged into the 3.3-dev branch.Discussion----------[Security][SecurityBundle] Enhance automatic logout url generation| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | yes| Tests pass? | yes| Fixed tickets | N/A| License | MIT| Doc PR | N/AThis should help whenever:- [the token does not implement the `getProviderKey` method](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php#L89-L99)- you've got multiple firewalls sharing a same context but a logout listener only define on one of them.##### Behavior:> When not providing the firewall key:>>- Try to find the key from the token (unless it's an anonymous token)>- If found, try to get the listener from the key. If the listener is found, stop there.>- Try from the injected firewall key. If the listener is found, stop there.>- Try from the injected firewall context. If the listener is found, stop there.>>The behavior remains unchanged when providing explicitly the firewall key. No fallback.Commits-------5b7fe85 [Security][SecurityBundle] Enhance automatic logout url generation
2 parentsc73009a +5b7fe85 commit1635a6a

File tree

7 files changed

+265
-27
lines changed

7 files changed

+265
-27
lines changed

‎UPGRADE-3.3.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@ Security
246246
* The`RoleInterface` has been deprecated. Extend the`Symfony\Component\Security\Core\Role\Role`
247247
class in your custom role implementations instead.
248248

249+
* The`LogoutUrlGenerator::registerListener()` method will expect a 6th`$context = null` argument in 4.0.
250+
Define the argument when overriding this method.
251+
249252
SecurityBundle
250253
--------------
251254

‎UPGRADE-4.0.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,8 @@ Security
373373

374374
* The`RoleInterface` has been removed. Extend the`Symfony\Component\Security\Core\Role\Role`
375375
class instead.
376+
377+
* The`LogoutUrlGenerator::registerListener()` method expects a 6th`$context = null` argument.
376378

377379
SecurityBundle
378380
--------------

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
389389
$firewall['logout']['csrf_token_id'],
390390
$firewall['logout']['csrf_parameter'],
391391
isset($firewall['logout']['csrf_token_generator']) ?newReference($firewall['logout']['csrf_token_generator']) :null,
392+
false ===$firewall['stateless'] &&isset($firewall['context']) ?$firewall['context'] :null,
392393
))
393394
;
394395
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Bundle\SecurityBundle\EventListener;
13+
14+
useSymfony\Bundle\SecurityBundle\Security\FirewallMap;
15+
useSymfony\Component\EventDispatcher\EventDispatcherInterface;
16+
useSymfony\Component\HttpKernel\Event\FinishRequestEvent;
17+
useSymfony\Component\HttpKernel\Event\GetResponseEvent;
18+
useSymfony\Component\Security\Http\Firewall;
19+
useSymfony\Component\Security\Http\FirewallMapInterface;
20+
useSymfony\Component\Security\Http\Logout\LogoutUrlGenerator;
21+
22+
/**
23+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
24+
*/
25+
class FirewallListenerextends Firewall
26+
{
27+
private$map;
28+
private$logoutUrlGenerator;
29+
30+
publicfunction__construct(FirewallMapInterface$map,EventDispatcherInterface$dispatcher,LogoutUrlGenerator$logoutUrlGenerator)
31+
{
32+
$this->map =$map;
33+
$this->logoutUrlGenerator =$logoutUrlGenerator;
34+
35+
parent::__construct($map,$dispatcher);
36+
}
37+
38+
publicfunctiononKernelRequest(GetResponseEvent$event)
39+
{
40+
if (!$event->isMasterRequest()) {
41+
return;
42+
}
43+
44+
if ($this->mapinstanceof FirewallMap &&$config =$this->map->getFirewallConfig($event->getRequest())) {
45+
$this->logoutUrlGenerator->setCurrentFirewall($config->getName(),$config->getContext());
46+
}
47+
48+
parent::onKernelRequest($event);
49+
}
50+
51+
publicfunctiononKernelFinishRequest(FinishRequestEvent$event)
52+
{
53+
if ($event->isMasterRequest()) {
54+
$this->logoutUrlGenerator->setCurrentFirewall(null);
55+
}
56+
57+
parent::onKernelFinishRequest($event);
58+
}
59+
}

‎src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@
102102

103103

104104
<!-- Firewall related services-->
105-
<serviceid="security.firewall"class="Symfony\Component\Security\Http\Firewall">
105+
<serviceid="security.firewall"class="Symfony\Bundle\SecurityBundle\EventListener\FirewallListener">
106106
<tagname="kernel.event_subscriber" />
107107
<argumenttype="service"id="security.firewall.map" />
108108
<argumenttype="service"id="event_dispatcher" />
109+
<argumenttype="service"id="security.logout_url_generator" />
109110
</service>
110111
<serviceid="Symfony\Component\Security\Http\Firewall"alias="security.firewall"public="false" />
111112

‎src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php‎

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
useSymfony\Component\HttpFoundation\RequestStack;
1515
useSymfony\Component\Routing\Generator\UrlGeneratorInterface;
16+
useSymfony\Component\Security\Core\Authentication\Token\AnonymousToken;
1617
useSymfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1718
useSymfony\Component\Security\Csrf\CsrfTokenManagerInterface;
1819

@@ -28,6 +29,7 @@ class LogoutUrlGenerator
2829
private$router;
2930
private$tokenStorage;
3031
private$listeners =array();
32+
private$currentFirewall;
3133

3234
publicfunction__construct(RequestStack$requestStack =null,UrlGeneratorInterface$router =null,TokenStorageInterface$tokenStorage =null)
3335
{
@@ -39,15 +41,29 @@ public function __construct(RequestStack $requestStack = null, UrlGeneratorInter
3941
/**
4042
* Registers a firewall's LogoutListener, allowing its URL to be generated.
4143
*
42-
* @param string $key The firewall key
43-
* @param string $logoutPath The path that starts the logout process
44-
* @param string $csrfTokenId The ID of the CSRF token
45-
* @param string $csrfParameter The CSRF token parameter name
46-
* @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance
44+
* @param string $key The firewall key
45+
* @param string $logoutPath The path that starts the logout process
46+
* @param string $csrfTokenId The ID of the CSRF token
47+
* @param string $csrfParameter The CSRF token parameter name
48+
* @param CsrfTokenManagerInterface|null $csrfTokenManager A CsrfTokenManagerInterface instance
49+
* @param string|null $context The listener context
4750
*/
48-
publicfunctionregisterListener($key,$logoutPath,$csrfTokenId,$csrfParameter,CsrfTokenManagerInterface$csrfTokenManager =null)
51+
publicfunctionregisterListener($key,$logoutPath,$csrfTokenId,$csrfParameter,CsrfTokenManagerInterface$csrfTokenManager =null/*, $context = null*/)
4952
{
50-
$this->listeners[$key] =array($logoutPath,$csrfTokenId,$csrfParameter,$csrfTokenManager);
53+
if (func_num_args() >=6) {
54+
$context =func_get_arg(5);
55+
}else {
56+
if (__CLASS__ !==get_class($this)) {
57+
$r =new \ReflectionMethod($this,__FUNCTION__);
58+
if (__CLASS__ !==$r->getDeclaringClass()->getName()) {
59+
@trigger_error(sprintf('Method %s() will have a sixth `$context = null` argument in version 4.0. Not defining it is deprecated since 3.3.',get_class($this),__FUNCTION__),E_USER_DEPRECATED);
60+
}
61+
}
62+
63+
$context =null;
64+
}
65+
66+
$this->listeners[$key] =array($logoutPath,$csrfTokenId,$csrfParameter,$csrfTokenManager,$context);
5167
}
5268

5369
/**
@@ -74,35 +90,26 @@ public function getLogoutUrl($key = null)
7490
return$this->generateLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL);
7591
}
7692

93+
/**
94+
* @param string|null $key The current firewall key
95+
* @param string|null $context The current firewall context
96+
*/
97+
publicfunctionsetCurrentFirewall($key,$context =null)
98+
{
99+
$this->currentFirewall =array($key,$context);
100+
}
101+
77102
/**
78103
* Generates the logout URL for the firewall.
79104
*
80105
* @param string|null $key The firewall key or null to use the current firewall key
81106
* @param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface)
82107
*
83108
* @return string The logout URL
84-
*
85-
* @throws \InvalidArgumentException if no LogoutListener is registered for the key or the key could not be found automatically.
86109
*/
87110
privatefunctiongenerateLogoutUrl($key,$referenceType)
88111
{
89-
// Fetch the current provider key from token, if possible
90-
if (null ===$key &&null !==$this->tokenStorage) {
91-
$token =$this->tokenStorage->getToken();
92-
if (null !==$token &&method_exists($token,'getProviderKey')) {
93-
$key =$token->getProviderKey();
94-
}
95-
}
96-
97-
if (null ===$key) {
98-
thrownew \InvalidArgumentException('Unable to find the current firewall LogoutListener, please provide the provider key manually.');
99-
}
100-
101-
if (!array_key_exists($key,$this->listeners)) {
102-
thrownew \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".',$key));
103-
}
104-
105-
list($logoutPath,$csrfTokenId,$csrfParameter,$csrfTokenManager) =$this->listeners[$key];
112+
list($logoutPath,$csrfTokenId,$csrfParameter,$csrfTokenManager) =$this->getListener($key);
106113

107114
$parameters =null !==$csrfTokenManager ?array($csrfParameter => (string)$csrfTokenManager->getToken($csrfTokenId)) :array();
108115

@@ -128,4 +135,54 @@ private function generateLogoutUrl($key, $referenceType)
128135

129136
return$url;
130137
}
138+
139+
/**
140+
* @param string|null $key The firewall key or null use the current firewall key
141+
*
142+
* @return array The logout listener found
143+
*
144+
* @throws \InvalidArgumentException if no LogoutListener is registered for the key or could not be found automatically.
145+
*/
146+
privatefunctiongetListener($key)
147+
{
148+
if (null !==$key) {
149+
if (isset($this->listeners[$key])) {
150+
return$this->listeners[$key];
151+
}
152+
153+
thrownew \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".',$key));
154+
}
155+
156+
// Fetch the current provider key from token, if possible
157+
if (null !==$this->tokenStorage) {
158+
$token =$this->tokenStorage->getToken();
159+
160+
if ($tokeninstanceof AnonymousToken) {
161+
thrownew \InvalidArgumentException('Unable to generate a logout url for an anonymous token.');
162+
}
163+
164+
if (null !==$token &&method_exists($token,'getProviderKey')) {
165+
$key =$token->getProviderKey();
166+
167+
if (isset($this->listeners[$key])) {
168+
return$this->listeners[$key];
169+
}
170+
}
171+
}
172+
173+
// Fetch from injected current firewall information, if possible
174+
list($key,$context) =$this->currentFirewall;
175+
176+
if (isset($this->listeners[$key])) {
177+
return$this->listeners[$key];
178+
}
179+
180+
foreach ($this->listenersas$listener) {
181+
if (isset($listener[4]) &&$context ===$listener[4]) {
182+
return$listener;
183+
}
184+
}
185+
186+
thrownew \InvalidArgumentException('Unable to find the current firewall LogoutListener, please provide the provider key manually.');
187+
}
131188
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\Security\Http\Tests\Logout;
13+
14+
usePHPUnit\Framework\TestCase;
15+
useSymfony\Component\HttpFoundation\Request;
16+
useSymfony\Component\HttpFoundation\RequestStack;
17+
useSymfony\Component\Security\Core\Authentication\Token\AnonymousToken;
18+
useSymfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
19+
useSymfony\Component\Security\Core\Authentication\Token\TokenInterface;
20+
useSymfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
21+
useSymfony\Component\Security\Http\Logout\LogoutUrlGenerator;
22+
23+
/**
24+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
25+
*/
26+
class LogoutUrlGeneratorTestextends TestCase
27+
{
28+
/** @var TokenStorage */
29+
private$tokenStorage;
30+
/** @var LogoutUrlGenerator */
31+
private$generator;
32+
33+
protectedfunctionsetUp()
34+
{
35+
$requestStack =$this->getMockBuilder(RequestStack::class)->getMock();
36+
$request =$this->getMockBuilder(Request::class)->getMock();
37+
$requestStack->method('getCurrentRequest')->willReturn($request);
38+
39+
$this->tokenStorage =newTokenStorage();
40+
$this->generator =newLogoutUrlGenerator($requestStack,null,$this->tokenStorage);
41+
}
42+
43+
publicfunctiontestGetLogoutPath()
44+
{
45+
$this->generator->registerListener('secured_area','/logout',null,null);
46+
47+
$this->assertSame('/logout',$this->generator->getLogoutPath('secured_area'));
48+
}
49+
50+
/**
51+
* @expectedException \InvalidArgumentException
52+
* @expectedExceptionMessage No LogoutListener found for firewall key "unregistered_key".
53+
*/
54+
publicfunctiontestGetLogoutPathWithoutLogoutListenerRegisteredForKeyThrowsException()
55+
{
56+
$this->generator->registerListener('secured_area','/logout',null,null,null);
57+
58+
$this->generator->getLogoutPath('unregistered_key');
59+
}
60+
61+
publicfunctiontestGuessFromToken()
62+
{
63+
$this->tokenStorage->setToken(newUsernamePasswordToken('user','password','secured_area'));
64+
$this->generator->registerListener('secured_area','/logout',null,null);
65+
66+
$this->assertSame('/logout',$this->generator->getLogoutPath());
67+
}
68+
69+
/**
70+
* @expectedException \InvalidArgumentException
71+
* @expectedExceptionMessage Unable to generate a logout url for an anonymous token.
72+
*/
73+
publicfunctiontestGuessFromAnonymousTokenThrowsException()
74+
{
75+
$this->tokenStorage->setToken(newAnonymousToken('default','anon.'));
76+
77+
$this->generator->getLogoutPath();
78+
}
79+
80+
publicfunctiontestGuessFromCurrentFirewallKey()
81+
{
82+
$this->generator->registerListener('secured_area','/logout',null,null);
83+
$this->generator->setCurrentFirewall('secured_area');
84+
85+
$this->assertSame('/logout',$this->generator->getLogoutPath());
86+
}
87+
88+
publicfunctiontestGuessFromCurrentFirewallContext()
89+
{
90+
$this->generator->registerListener('secured_area','/logout',null,null,null,'secured');
91+
$this->generator->setCurrentFirewall('admin','secured');
92+
93+
$this->assertSame('/logout',$this->generator->getLogoutPath());
94+
}
95+
96+
publicfunctiontestGuessFromTokenWithoutProviderKeyFallbacksToCurrentFirewall()
97+
{
98+
$this->tokenStorage->setToken($this->getMockBuilder(TokenInterface::class)->getMock());
99+
$this->generator->registerListener('secured_area','/logout',null,null);
100+
$this->generator->setCurrentFirewall('secured_area');
101+
102+
$this->assertSame('/logout',$this->generator->getLogoutPath());
103+
}
104+
105+
/**
106+
* @expectedException \InvalidArgumentException
107+
* @expectedExceptionMessage Unable to find the current firewall LogoutListener, please provide the provider key manually
108+
*/
109+
publicfunctiontestUnableToGuessThrowsException()
110+
{
111+
$this->generator->registerListener('secured_area','/logout',null,null);
112+
113+
$this->generator->getLogoutPath();
114+
}
115+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp