Skip to content

Commit fb9dc6a

Browse files
committed
feature symfony#10793 [Security] Allow exception bubbling in RememberMeListener (lstrojny)
This PR was merged into the 2.6-dev branch. Discussion ---------- [Security] Allow exception bubbling in RememberMeListener - Allow optional exception bubbling so that the exception listener has a chance to handle those exceptions #### While at it - Test for dispatching the InteractiveLogin event - Smaller cleanups in the test | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | ye | Fixed tickets | n.A. | License | MIT | Doc PR | n.A. Commits ------- fcb7f74 Allow exception bubbling in RememberMeListener
2 parents 21e7ad7 + fcb7f74 commit fb9dc6a

File tree

11 files changed

+182
-9
lines changed

11 files changed

+182
-9
lines changed

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
102102
$listenerId = 'security.authentication.listener.rememberme.'.$id;
103103
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.rememberme'));
104104
$listener->replaceArgument(1, new Reference($rememberMeServicesId));
105+
$listener->replaceArgument(4, $config['catch_exceptions']);
105106

106107
return array($authProviderId, $listenerId, $defaultEntryPoint);
107108
}
@@ -130,6 +131,7 @@ public function addConfiguration(NodeDefinition $node)
130131
->end()
131132
->prototype('scalar')->end()
132133
->end()
134+
->scalarNode('catch_exceptions')->defaultTrue()->end()
133135
;
134136

135137
foreach ($this->options as $name => $value) {

src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
<argument type="service" id="security.authentication.manager" />
2525
<argument type="service" id="logger" on-invalid="null" />
2626
<argument type="service" id="event_dispatcher" on-invalid="null"/>
27+
<argument /> <!-- Catch exception flag set in RememberMeFactory -->
2728
</service>
2829

2930
<service id="security.authentication.provider.rememberme" class="%security.authentication.provider.rememberme.class%" abstract="true" public="false">
3031
<argument type="service" id="security.user_checker" />
3132
</service>
3233

33-
<service id="security.rememberme.token.provider.in_memory" class="%security.rememberme.token.provider.in_memory.class%" public="false"></service>
34+
<service id="security.rememberme.token.provider.in_memory" class="%security.rememberme.token.provider.in_memory.class%" public="false"/>
3435

3536
<service id="security.authentication.rememberme.services.abstract" abstract="true" public="false">
3637
<tag name="monolog.logger" channel="security" />

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public function testFirewalls()
8282
'security.authentication.listener.form.secure',
8383
'security.authentication.listener.basic.secure',
8484
'security.authentication.listener.digest.secure',
85+
'security.authentication.listener.rememberme.secure',
8586
'security.authentication.listener.anonymous.secure',
8687
'security.access_listener',
8788
'security.authentication.switchuser_listener.secure',
@@ -219,6 +220,20 @@ public function testCustomAclProvider()
219220
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
220221
}
221222

223+
public function testRememberMeThrowExceptionsDefault()
224+
{
225+
$container = $this->getContainer('container1');
226+
$this->assertTrue($container->getDefinition('security.authentication.listener.rememberme.secure')->getArgument(4));
227+
}
228+
229+
public function testRememberMeThrowExceptions()
230+
{
231+
$container = $this->getContainer('remember_me_options');
232+
$service = $container->getDefinition('security.authentication.listener.rememberme.main');
233+
$this->assertEquals('security.authentication.rememberme.services.persistent.main', $service->getArgument(1));
234+
$this->assertFalse($service->getArgument(4));
235+
}
236+
222237
protected function getContainer($file)
223238
{
224239
$container = new ContainerBuilder();

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
'switch_user' => true,
7171
'x509' => true,
7272
'logout' => true,
73+
'remember_me' => array('key' => 'TheKey')
7374
),
7475
'host' => array(
7576
'pattern' => '/test',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
$container->loadFromExtension('security', array(
3+
'providers' => array(
4+
'default' => array('id' => 'foo'),
5+
),
6+
7+
'firewalls' => array(
8+
'main' => array(
9+
'form_login' => true,
10+
'remember_me' => array(
11+
'key' => 'TheyKey',
12+
'catch_exceptions' => false,
13+
'token_provider' => 'token_provider_id',
14+
)
15+
)
16+
),
17+
));

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<switch-user />
5656
<x509 />
5757
<logout />
58+
<remember-me key="TheyKey"/>
5859
</firewall>
5960

6061
<firewall name="host" pattern="/test" host="foo\.example\.org" methods="GET,POST">
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:sec="http://symfony.com/schema/dic/security"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
7+
8+
<sec:config>
9+
<sec:providers>
10+
<sec:default id="foo"/>
11+
</sec:providers>
12+
<sec:firewall name="main">
13+
<sec:form-login/>
14+
<sec:remember-me key="TheKey" catch-exceptions="false" token-provider="token_provider_id" />
15+
</sec:firewall>
16+
</sec:config>
17+
18+
</container>

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ security:
5353
switch_user: true
5454
x509: true
5555
logout: true
56+
remember_me:
57+
key: TheKey
5658
host:
5759
pattern: /test
5860
host: foo\.example\.org
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
security:
2+
providers:
3+
default:
4+
id: foo
5+
6+
firewalls:
7+
main:
8+
form_login: true
9+
remember_me:
10+
key: TheKey
11+
catch_exceptions: false
12+
token_provider: token_provider_id

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class RememberMeListener implements ListenerInterface
3333
private $authenticationManager;
3434
private $logger;
3535
private $dispatcher;
36+
private $catchExceptions = true;
3637

3738
/**
3839
* Constructor.
@@ -42,14 +43,16 @@ class RememberMeListener implements ListenerInterface
4243
* @param AuthenticationManagerInterface $authenticationManager
4344
* @param LoggerInterface $logger
4445
* @param EventDispatcherInterface $dispatcher
46+
* @param bool $catchExceptions
4547
*/
46-
public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
48+
public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $catchExceptions = true)
4749
{
4850
$this->securityContext = $securityContext;
4951
$this->rememberMeServices = $rememberMeServices;
5052
$this->authenticationManager = $authenticationManager;
5153
$this->logger = $logger;
5254
$this->dispatcher = $dispatcher;
55+
$this->catchExceptions = $catchExceptions;
5356
}
5457

5558
/**
@@ -90,6 +93,10 @@ public function handle(GetResponseEvent $event)
9093
}
9194

9295
$this->rememberMeServices->loginFail($request);
96+
97+
if (!$this->catchExceptions) {
98+
throw $failed;
99+
}
93100
}
94101
}
95102
}

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

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
use Symfony\Component\Security\Core\Exception\AuthenticationException;
1515
use Symfony\Component\Security\Http\Firewall\RememberMeListener;
1616
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\Security\Http\SecurityEvents;
1718

1819
class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
1920
{
2021
public function testOnCoreSecurityDoesNotTryToPopulateNonEmptySecurityContext()
2122
{
22-
list($listener, $context, $service,,) = $this->getListener();
23+
list($listener, $context,,,,) = $this->getListener();
2324

2425
$context
2526
->expects($this->once())
@@ -99,6 +100,48 @@ public function testOnCoreSecurityIgnoresAuthenticationExceptionThrownByAuthenti
99100
$listener->handle($event);
100101
}
101102

103+
/**
104+
* @expectedException Symfony\Component\Security\Core\Exception\AuthenticationException
105+
* @expectedExceptionMessage Authentication failed.
106+
*/
107+
public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExceptionThrownAuthenticationManagerImplementation()
108+
{
109+
list($listener, $context, $service, $manager,) = $this->getListener(false, false);
110+
111+
$context
112+
->expects($this->once())
113+
->method('getToken')
114+
->will($this->returnValue(null))
115+
;
116+
117+
$service
118+
->expects($this->once())
119+
->method('autoLogin')
120+
->will($this->returnValue($this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')))
121+
;
122+
123+
$service
124+
->expects($this->once())
125+
->method('loginFail')
126+
;
127+
128+
$exception = new AuthenticationException('Authentication failed.');
129+
$manager
130+
->expects($this->once())
131+
->method('authenticate')
132+
->will($this->throwException($exception))
133+
;
134+
135+
$event = $this->getGetResponseEvent();
136+
$event
137+
->expects($this->once())
138+
->method('getRequest')
139+
->will($this->returnValue(new Request()))
140+
;
141+
142+
$listener->handle($event);
143+
}
144+
102145
public function testOnCoreSecurity()
103146
{
104147
list($listener, $context, $service, $manager,) = $this->getListener();
@@ -138,6 +181,55 @@ public function testOnCoreSecurity()
138181
$listener->handle($event);
139182
}
140183

184+
public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherIsPresent()
185+
{
186+
list($listener, $context, $service, $manager,, $dispatcher) = $this->getListener(true);
187+
188+
$context
189+
->expects($this->once())
190+
->method('getToken')
191+
->will($this->returnValue(null))
192+
;
193+
194+
$token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
195+
$service
196+
->expects($this->once())
197+
->method('autoLogin')
198+
->will($this->returnValue($token))
199+
;
200+
201+
$context
202+
->expects($this->once())
203+
->method('setToken')
204+
->with($this->equalTo($token))
205+
;
206+
207+
$manager
208+
->expects($this->once())
209+
->method('authenticate')
210+
->will($this->returnValue($token))
211+
;
212+
213+
$event = $this->getGetResponseEvent();
214+
$request = new Request();
215+
$event
216+
->expects($this->once())
217+
->method('getRequest')
218+
->will($this->returnValue($request))
219+
;
220+
221+
$dispatcher
222+
->expects($this->once())
223+
->method('dispatch')
224+
->with(
225+
SecurityEvents::INTERACTIVE_LOGIN,
226+
$this->isInstanceOf('Symfony\Component\Security\Http\Event\InteractiveLoginEvent')
227+
)
228+
;
229+
230+
$listener->handle($event);
231+
}
232+
141233
protected function getGetResponseEvent()
142234
{
143235
return $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
@@ -148,16 +240,18 @@ protected function getFilterResponseEvent()
148240
return $this->getMock('Symfony\Component\HttpKernel\Event\FilterResponseEvent', array(), array(), '', false);
149241
}
150242

151-
protected function getListener()
243+
protected function getListener($withDispatcher = false, $catchExceptions = true)
152244
{
153245
$listener = new RememberMeListener(
154246
$context = $this->getContext(),
155247
$service = $this->getService(),
156248
$manager = $this->getManager(),
157-
$logger = $this->getLogger()
249+
$logger = $this->getLogger(),
250+
$dispatcher = ($withDispatcher ? $this->getDispatcher() : null),
251+
$catchExceptions
158252
);
159253

160-
return array($listener, $context, $service, $manager, $logger);
254+
return array($listener, $context, $service, $manager, $logger, $dispatcher);
161255
}
162256

163257
protected function getLogger()
@@ -177,8 +271,11 @@ protected function getService()
177271

178272
protected function getContext()
179273
{
180-
return $this->getMockBuilder('Symfony\Component\Security\Core\SecurityContext')
181-
->disableOriginalConstructor()
182-
->getMock();
274+
return $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
275+
}
276+
277+
protected function getDispatcher()
278+
{
279+
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
183280
}
184281
}

0 commit comments

Comments
 (0)