From fcb7f74af76c0e4d11fe0180c2901f21c3ba0bb6 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Sun, 27 Apr 2014 11:40:06 +0200 Subject: [PATCH] Allow exception bubbling in RememberMeListener --- .../Security/Factory/RememberMeFactory.php | 2 + .../Resources/config/security_rememberme.xml | 3 +- .../CompleteConfigurationTest.php | 15 +++ .../Fixtures/php/container1.php | 1 + .../Fixtures/php/remember_me_options.php | 17 +++ .../Fixtures/xml/container1.xml | 1 + .../Fixtures/xml/remember_me_options.xml | 18 +++ .../Fixtures/yml/container1.yml | 2 + .../Fixtures/yml/remember_me_options.yml | 12 ++ .../Http/Firewall/RememberMeListener.php | 9 +- .../Tests/Firewall/RememberMeListenerTest.php | 111 ++++++++++++++++-- 11 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index 288ea1e824..679b1db96f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -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) { diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml index eec67aa868..15131e96c7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_rememberme.xml @@ -24,13 +24,14 @@ + - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index 3165b2f34b..cf79e9bfd9 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -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(); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index 9a15ea5fd7..1a24528b77 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -70,6 +70,7 @@ $container->loadFromExtension('security', array( 'switch_user' => true, 'x509' => true, 'logout' => true, + 'remember_me' => array('key' => 'TheKey') ), 'host' => array( 'pattern' => '/test', diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php new file mode 100644 index 0000000000..ef4a7887d1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/remember_me_options.php @@ -0,0 +1,17 @@ +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', + ) + ) + ), +)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index 2368aa312d..e520c1120f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -55,6 +55,7 @@ + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml new file mode 100644 index 0000000000..1674756891 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/remember_me_options.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 9bfe29230b..a17b3c810d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -53,6 +53,8 @@ security: switch_user: true x509: true logout: true + remember_me: + key: TheKey host: pattern: /test host: foo\.example\.org diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml new file mode 100644 index 0000000000..3a38b33c52 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/remember_me_options.yml @@ -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 diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index 6ca384296c..44000d364d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -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; + } } } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index 950669282c..68dfc1462f 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -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'); } }