feature #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
This commit is contained in:
commit
fb9dc6adc3
@ -102,6 +102,7 @@ class RememberMeFactory implements SecurityFactoryInterface
|
||||
$listenerId = 'security.authentication.listener.rememberme.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.rememberme'));
|
||||
$listener->replaceArgument(1, new Reference($rememberMeServicesId));
|
||||
$listener->replaceArgument(4, $config['catch_exceptions']);
|
||||
|
||||
return array($authProviderId, $listenerId, $defaultEntryPoint);
|
||||
}
|
||||
@ -130,6 +131,7 @@ class RememberMeFactory implements SecurityFactoryInterface
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->scalarNode('catch_exceptions')->defaultTrue()->end()
|
||||
;
|
||||
|
||||
foreach ($this->options as $name => $value) {
|
||||
|
@ -24,13 +24,14 @@
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="service" id="event_dispatcher" on-invalid="null"/>
|
||||
<argument /> <!-- Catch exception flag set in RememberMeFactory -->
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.provider.rememberme" class="%security.authentication.provider.rememberme.class%" abstract="true" public="false">
|
||||
<argument type="service" id="security.user_checker" />
|
||||
</service>
|
||||
|
||||
<service id="security.rememberme.token.provider.in_memory" class="%security.rememberme.token.provider.in_memory.class%" public="false"></service>
|
||||
<service id="security.rememberme.token.provider.in_memory" class="%security.rememberme.token.provider.in_memory.class%" public="false"/>
|
||||
|
||||
<service id="security.authentication.rememberme.services.abstract" abstract="true" public="false">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
|
@ -82,6 +82,7 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
'security.authentication.listener.form.secure',
|
||||
'security.authentication.listener.basic.secure',
|
||||
'security.authentication.listener.digest.secure',
|
||||
'security.authentication.listener.rememberme.secure',
|
||||
'security.authentication.listener.anonymous.secure',
|
||||
'security.access_listener',
|
||||
'security.authentication.switchuser_listener.secure',
|
||||
@ -219,6 +220,20 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo', (string) $container->getAlias('security.acl.provider'));
|
||||
}
|
||||
|
||||
public function testRememberMeThrowExceptionsDefault()
|
||||
{
|
||||
$container = $this->getContainer('container1');
|
||||
$this->assertTrue($container->getDefinition('security.authentication.listener.rememberme.secure')->getArgument(4));
|
||||
}
|
||||
|
||||
public function testRememberMeThrowExceptions()
|
||||
{
|
||||
$container = $this->getContainer('remember_me_options');
|
||||
$service = $container->getDefinition('security.authentication.listener.rememberme.main');
|
||||
$this->assertEquals('security.authentication.rememberme.services.persistent.main', $service->getArgument(1));
|
||||
$this->assertFalse($service->getArgument(4));
|
||||
}
|
||||
|
||||
protected function getContainer($file)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -70,6 +70,7 @@ $container->loadFromExtension('security', array(
|
||||
'switch_user' => true,
|
||||
'x509' => true,
|
||||
'logout' => true,
|
||||
'remember_me' => array('key' => 'TheKey')
|
||||
),
|
||||
'host' => array(
|
||||
'pattern' => '/test',
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
$container->loadFromExtension('security', array(
|
||||
'providers' => array(
|
||||
'default' => array('id' => 'foo'),
|
||||
),
|
||||
|
||||
'firewalls' => array(
|
||||
'main' => array(
|
||||
'form_login' => true,
|
||||
'remember_me' => array(
|
||||
'key' => 'TheyKey',
|
||||
'catch_exceptions' => false,
|
||||
'token_provider' => 'token_provider_id',
|
||||
)
|
||||
)
|
||||
),
|
||||
));
|
@ -55,6 +55,7 @@
|
||||
<switch-user />
|
||||
<x509 />
|
||||
<logout />
|
||||
<remember-me key="TheyKey"/>
|
||||
</firewall>
|
||||
|
||||
<firewall name="host" pattern="/test" host="foo\.example\.org" methods="GET,POST">
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sec="http://symfony.com/schema/dic/security"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<sec:config>
|
||||
<sec:providers>
|
||||
<sec:default id="foo"/>
|
||||
</sec:providers>
|
||||
<sec:firewall name="main">
|
||||
<sec:form-login/>
|
||||
<sec:remember-me key="TheKey" catch-exceptions="false" token-provider="token_provider_id" />
|
||||
</sec:firewall>
|
||||
</sec:config>
|
||||
|
||||
</container>
|
@ -53,6 +53,8 @@ security:
|
||||
switch_user: true
|
||||
x509: true
|
||||
logout: true
|
||||
remember_me:
|
||||
key: TheKey
|
||||
host:
|
||||
pattern: /test
|
||||
host: foo\.example\.org
|
||||
|
@ -0,0 +1,12 @@
|
||||
security:
|
||||
providers:
|
||||
default:
|
||||
id: foo
|
||||
|
||||
firewalls:
|
||||
main:
|
||||
form_login: true
|
||||
remember_me:
|
||||
key: TheKey
|
||||
catch_exceptions: false
|
||||
token_provider: token_provider_id
|
@ -33,6 +33,7 @@ class RememberMeListener implements ListenerInterface
|
||||
private $authenticationManager;
|
||||
private $logger;
|
||||
private $dispatcher;
|
||||
private $catchExceptions = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -42,14 +43,16 @@ class RememberMeListener implements ListenerInterface
|
||||
* @param AuthenticationManagerInterface $authenticationManager
|
||||
* @param LoggerInterface $logger
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @param bool $catchExceptions
|
||||
*/
|
||||
public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
|
||||
public function __construct(SecurityContextInterface $securityContext, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $catchExceptions = true)
|
||||
{
|
||||
$this->securityContext = $securityContext;
|
||||
$this->rememberMeServices = $rememberMeServices;
|
||||
$this->authenticationManager = $authenticationManager;
|
||||
$this->logger = $logger;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->catchExceptions = $catchExceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,6 +93,10 @@ class RememberMeListener implements ListenerInterface
|
||||
}
|
||||
|
||||
$this->rememberMeServices->loginFail($request);
|
||||
|
||||
if (!$this->catchExceptions) {
|
||||
throw $failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,13 @@ namespace Symfony\Component\Security\Http\Tests\Firewall;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Firewall\RememberMeListener;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
|
||||
class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testOnCoreSecurityDoesNotTryToPopulateNonEmptySecurityContext()
|
||||
{
|
||||
list($listener, $context, $service,,) = $this->getListener();
|
||||
list($listener, $context,,,,) = $this->getListener();
|
||||
|
||||
$context
|
||||
->expects($this->once())
|
||||
@ -99,6 +100,48 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\Security\Core\Exception\AuthenticationException
|
||||
* @expectedExceptionMessage Authentication failed.
|
||||
*/
|
||||
public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExceptionThrownAuthenticationManagerImplementation()
|
||||
{
|
||||
list($listener, $context, $service, $manager,) = $this->getListener(false, false);
|
||||
|
||||
$context
|
||||
->expects($this->once())
|
||||
->method('getToken')
|
||||
->will($this->returnValue(null))
|
||||
;
|
||||
|
||||
$service
|
||||
->expects($this->once())
|
||||
->method('autoLogin')
|
||||
->will($this->returnValue($this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')))
|
||||
;
|
||||
|
||||
$service
|
||||
->expects($this->once())
|
||||
->method('loginFail')
|
||||
;
|
||||
|
||||
$exception = new AuthenticationException('Authentication failed.');
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('authenticate')
|
||||
->will($this->throwException($exception))
|
||||
;
|
||||
|
||||
$event = $this->getGetResponseEvent();
|
||||
$event
|
||||
->expects($this->once())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue(new Request()))
|
||||
;
|
||||
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
public function testOnCoreSecurity()
|
||||
{
|
||||
list($listener, $context, $service, $manager,) = $this->getListener();
|
||||
@ -138,6 +181,55 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherIsPresent()
|
||||
{
|
||||
list($listener, $context, $service, $manager,, $dispatcher) = $this->getListener(true);
|
||||
|
||||
$context
|
||||
->expects($this->once())
|
||||
->method('getToken')
|
||||
->will($this->returnValue(null))
|
||||
;
|
||||
|
||||
$token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
|
||||
$service
|
||||
->expects($this->once())
|
||||
->method('autoLogin')
|
||||
->will($this->returnValue($token))
|
||||
;
|
||||
|
||||
$context
|
||||
->expects($this->once())
|
||||
->method('setToken')
|
||||
->with($this->equalTo($token))
|
||||
;
|
||||
|
||||
$manager
|
||||
->expects($this->once())
|
||||
->method('authenticate')
|
||||
->will($this->returnValue($token))
|
||||
;
|
||||
|
||||
$event = $this->getGetResponseEvent();
|
||||
$request = new Request();
|
||||
$event
|
||||
->expects($this->once())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue($request))
|
||||
;
|
||||
|
||||
$dispatcher
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with(
|
||||
SecurityEvents::INTERACTIVE_LOGIN,
|
||||
$this->isInstanceOf('Symfony\Component\Security\Http\Event\InteractiveLoginEvent')
|
||||
)
|
||||
;
|
||||
|
||||
$listener->handle($event);
|
||||
}
|
||||
|
||||
protected function getGetResponseEvent()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
|
||||
@ -148,16 +240,18 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
|
||||
return $this->getMock('Symfony\Component\HttpKernel\Event\FilterResponseEvent', array(), array(), '', false);
|
||||
}
|
||||
|
||||
protected function getListener()
|
||||
protected function getListener($withDispatcher = false, $catchExceptions = true)
|
||||
{
|
||||
$listener = new RememberMeListener(
|
||||
$context = $this->getContext(),
|
||||
$service = $this->getService(),
|
||||
$manager = $this->getManager(),
|
||||
$logger = $this->getLogger()
|
||||
$logger = $this->getLogger(),
|
||||
$dispatcher = ($withDispatcher ? $this->getDispatcher() : null),
|
||||
$catchExceptions
|
||||
);
|
||||
|
||||
return array($listener, $context, $service, $manager, $logger);
|
||||
return array($listener, $context, $service, $manager, $logger, $dispatcher);
|
||||
}
|
||||
|
||||
protected function getLogger()
|
||||
@ -177,8 +271,11 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected function getContext()
|
||||
{
|
||||
return $this->getMockBuilder('Symfony\Component\Security\Core\SecurityContext')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
return $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
|
||||
}
|
||||
|
||||
protected function getDispatcher()
|
||||
{
|
||||
return $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user