8000 feature #11993 [Security] make it possible to override the default su… · symfony/symfony@af0aa50 · GitHub
[go: up one dir, main page]

Skip to content

Commit af0aa50

Browse files
committed
feature #11993 [Security] make it possible to override the default success/failure handler (fabpot)
This PR was merged into the 2.6-dev branch. Discussion ---------- [Security] make it possible to override the default success/failure handler | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #5432, #9272, #10417, #11926 | License | MIT | Doc PR | symfony/symfony-docs#4258 Overriding the default success/failure handler of the security firewalls is possible via the `success_handler` and `failure_handler` setting but this approach is not flexible as it does not allow you to get the options/provider key. To sum up the problem: * Overriding the default success/failure handler is possible via a service; * When not overridden, the default success/failure handler gets options and the provider key; * Those options and the provider key are injected by the factory as they are dynamic (they depend on the firewall and the provider key), so getting those options/provider key is not possible for a custom service that is only configured via the container configuration; * Extending the default handler does not help as the injection mechanism is only triggered when no custom provider is set; * Wrapping the default handler is not possible as the service id is dynamic. ... and of course we need to keep BC and make it work for people extending the default handler but also for people just using the interface. Instead of the current PR, I propose this slightly different approach. It's not perfect, but given the above constraint, I think this is an acceptable trade-of. So, several use cases: * Using the default handler (no change); * Using a custom handler that implements `AuthenticationSuccessHandlerInterface` directly and does not need any options (no change); * Using a custom handler that needs the options/provider key (that's the new use case this PR supports). This PR introduces 2 new classes that wrap custom handlers. If those classes define the `setOptions()` and/or `setProviderKey()` methods, they are automatically called with the correct arguments. Yours handler does not need to extend the default handler `DefaultAuthentication*Handler`, but doing so helps as the setters are already defined there. Commits ------- 810eeaf [Security] made it possible to override the default success/failure handler (take 2) 36116fc [Security] made it possible to override the default success/failure handler
2 parents b4dcd50 + 810eeaf commit af0aa50

File tree

8 files changed

+273
-64
lines changed

8 files changed

+273
-64
lines changed

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
2.6.0
55
-----
66

7+
* Added the possibility to override the default success/failure handler
8+
to get the provider key and the options injected
79
* Deprecated the `security.context` service for the `security.token_storage` and
810
`security.authorization_checker` services.
911

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
1313

1414
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
15-
1615
use Symfony\Component\DependencyInjection\DefinitionDecorator;
1716
use Symfony\Component\DependencyInjection\ContainerBuilder;
1817
use Symfony\Component\DependencyInjection\Reference;
@@ -21,15 +20,16 @@
2120
* AbstractFactory is the base class for all classes inheriting from
2221
* AbstractAuthenticationListener
2322
*
23+
* @author Fabien Potencier <fabien@symfony.com>
2424
* @author Lukas Kahwe Smith <smith@pooteeweet.org>
2525
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
2626
*/
2727
abstract class AbstractFactory implements SecurityFactoryInterface
2828
{
2929
protected $options = array(
30-
'check_path' => '/login_check',
31-
'use_forward' => false,
32-
'require_previous_session' => true,
30+
'check_path' => '/login_check',
31+
'use_forward' => false,
32+
'require_previous_session' => true,
3333
);
3434

3535
protected $defaultSuccessHandlerOptions = array(
@@ -41,10 +41,10 @@ abstract class AbstractFactory implements SecurityFactoryInterface
4141
);
4242

4343
protected $defaultFailureHandlerOptions = array(
44-
'failure_path' => null,
45-
'failure_forward' => false,
46-
'login_path' => '/login',
47-
'failure_path_parameter' => '_failure_path',
44+
'failure_path' => null,
45+
'failure_forward' => false,
46+
'login_path' => '/login',
47+
'failure_path_parameter' => '_failure_path',
4848
);
4949

5050
public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId)
@@ -170,29 +170,36 @@ protected function createListener($container, $id, $config, $userProvider)
170170

171171
protected function createAuthenticationSuccessHandler($container, $id, $config)
172172
{
173-
if (isset($config['success_handler'])) {
174-
return $config['success_handler'];
175-
}
176-
177173
$successHandlerId = $this->getSuccessHandlerId($id);
174+
$options = array_intersect_key($config, $this->defaultSuccessHandlerOptions);
178175

179-
$successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler'));
180-
$successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions));
181-
$successHandler->addMethodCall('setProviderKey', array($id));
176+
if (isset($config['success_handler'])) {
177+
$successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.custom_success_handler'));
178+
$successHandler->replaceArgument(0, new Reference($config['success_handler']));
179+
$successHandler->replaceArgument(1, $options);
180+
$successHandler->replaceArgument(2, $id);
181+
} else {
182+
$successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler'));
183+
$successHandler->addMethodCall('setOptions', array($options));
184+
$successHandler->addMethodCall('setProviderKey', array($id));
185+
}
182186

183187
return $successHandlerId;
184188
}
185189

186190
protected function createAuthenticationFailureHandler($container, $id, $config)
187191
{
188-
if (isset($config['failure_handler'])) {
189-
return $config['failure_handler'];
190-
}
191-
192192
$id = $this->getFailureHandlerId($id);
193+
$options = array_intersect_key($config, $this->defaultFailureHandlerOptions);
193194

194-
$failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler'));
195-
$failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions));
195+
if (isset($config['failure_handler'])) {
196+
$failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.custom_failure_handler'));
197+
$failureHandler->replaceArgument(0, new Reference($config['failure_handler']));
198+
$failureHandler->replaceArgument(1, $options);
199+
} else {
200+
$failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler'));
201+
$failureHandler->addMethodCall('setOptions', array($options));
202+
}
196203

197204
return $id;
198205
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848

4949
<parameter key="security.authentication.success_handler.class">Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler</parameter>
5050
<parameter key="security.authentication.failure_handler.class">Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler</parameter>
51+
<parameter key="security.authentication.custom_success_handler.class">Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler</parameter>
52+
<parameter key="security.authentication.custom_failure_handler.class">Symfony\Component\Security\Http\Authentication\CustomAuthenticationFailureHandler</parameter>
5153
<parameter key="security.authentication.simple_success_failure_handler.class">Symfony\Component\Security\Http\Authentication\SimpleAuthenticationHandler</parameter>
5254
</parameters>
5355

@@ -123,11 +125,22 @@
123125
<argument type="service" id="event_dispatcher" on-invalid="null" />
124126
</service>
125127

128+
<service id="security.authentication.custom_success_handler" class="%security.authentication.custom_success_handler.class%" abstract="true" public="false">
129+
<argument /> <!-- The custom success handler service id -->
130+
<argument type="collection" /> <!-- Options -->
131+
<argument /> <!-- Provider-shared Key -->
132+
</service>
133+
126134
<service id="security.authentication.success_handler" class="%security.authentication.success_handler.class%" abstract="true" public="false">
127135
<argument type="service" id="security.http_utils" />
128136
<argument type="collection" /> <!-- Options -->
129137
</service>
130138

139+
<service id="security.authentication.custom_failure_handler" class="%security.authentication.custom_failure_handler.class%" abstract="true" public="false">
140+
<argument /> <!-- The custom failure handler service id -->
141+
<argument type="collection" /> <!-- Options -->
142+
</service>
143+
131144
<service id="security.authentication.failure_handler" class="%security.authentication.failure_handler.class%" abstract="true" public="false">
132145
<tag name="monolog.logger" channel="security" />
133146
<argument type="service" id="http_kernel" />

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
1818
{
1919
public function testCreate()
2020
{
21-
list($container,
22-
$authProviderId,
23-
$listenerId,
24-
$entryPointId
25-
) = $this->callFactory('foo', array('use_forward' => true, 'failure_path' => '/foo', 'success_handler' => 'qux', 'failure_handler' => 'bar', 'remember_me' => true), 'user_provider', 'entry_point');
21+
list($container, $authProviderId, $listenerId, $entryPointId) = $this->callFactory('foo', array(
22+
'use_forward' => true,
23+
'failure_path' => '/foo',
24+
'success_handler' => 'custom_success_handler',
25+
'failure_handler' => 'custom_failure_handler',
26+
'remember_me' => true,
27+
), 'user_provider', 'entry_point');
2628

2729
// auth provider
2830
$this->assertEquals('auth_provider', $authProviderId);
@@ -33,41 +35,93 @@ public function testCreate()
3335
$definition = $container->getDefinition('abstract_listener.foo');
3436
$this->assertEquals(array(
3537
'index_4' => 'foo',
36-
'index_5' => new Reference('qux'),
37-
'index_6' => new Reference('bar'),
38+
'index_5' => new Reference('security.authentication.success_handler.foo.abstract_factory'),
39+
'index_6' => new Reference('security.authentication.failure_handler.foo.abstract_factory'),
3840
'index_7' => array(
39-
'use_forward' => true,
41+
'use_forward' => true,
4042
),
4143
), $definition->getArguments());
4244

4345
// entry point
4446
$this->assertEquals('entry_point', $entryPointId, '->create() does not change the default entry point.');
4547
}
4648

47-
public function testDefaultFailureHandler()
49+
/**
50+
* @dataProvider getFailureHandlers
51+
*/
52+
public function testDefaultFailureHandler($serviceId, $defaultHandlerInjection)
4853
{
49-
list($container,
50-
$authProviderId,
51-
$listenerId,
52-
$entryPointId
53-
) = $this->callFactory('foo', array('remember_me' => true), 'user_provider', 'entry_point');
54+
$options = array(
55+
'remember_me' => true,
56+
'login_path' => '/bar',
57+
);
58+
59+
if ($serviceId) {
60+
$options['failure_handler'] = $serviceId;
61+
}
62+
63+
list($container, $authProviderId, $listenerId, $entryPointId) = $this->callFactory('foo', $options, 'user_provider', 'entry_point');
5464

5565
$definition = $container->getDefinition('abstract_listener.foo');
5666
$arguments = $definition->getArguments();
5767
$this->assertEquals(new Reference('security.authentication.failure_handler.foo.abstract_factory'), $arguments['index_6']);
68+
$failureHandler = $container->findDefinition((string) $arguments['index_6']);
69+
70+
$methodCalls = $failureHandler->getMethodCalls();
71+
if ($defaultHandlerInjection) {
72+
$this->assertEquals('setOptions', $methodCalls[0][0]);
73+
$this->assertEquals(array('login_path' => '/bar'), $methodCalls[0][1][0]);
74+
} else {
75+
$this->assertCount(0, $methodCalls);
< 10000 /td>76+
}
77+
}
78+
79+
public function getFailureHandlers()
80+
{
81+
return array(
82+
array(null, true),
83+
array('custom_failure_handler', false),
84+
);
5885
}
5986

60-
public function testDefaultSuccessHandler()
87+
/**
88+
* @dataProvider getSuccessHandlers
89+
*/
90+
public function testDefaultSuccessHandler($serviceId, $defaultHandlerInjection)
6191
{
62-
list($container,
63-
$authProviderId,
64-
$listenerId,
65-
$entryPointId
66-
) = $this->callFactory('foo', array('remember_me' => true), 'user_provider', 'entry_point');
92+
$options = array(
93+
'remember_me' => true,
94+
'default_target_path' => '/bar',
95+
);
96+
97+
if ($serviceId) {
98+
$options['success_handler'] = $serviceId;
99+
}
100+
101+
list($container, $authProviderId, $listenerId, $entryPointId) = $this->callFactory('foo', $options, 'user_provider', 'entry_point');
67102

68103
$definition = $container->getDefinition('abstract_listener.foo');
69104
$arguments = $definition->getArguments();
70105
$this->assertEquals(new Reference('security.authentication.success_handler.foo.abstract_factory'), $arguments['index_5']);
106+
$successHandler = $container->findDefinition((string) $arguments['index_5']);
107+
$methodCalls = $successHandler->getMethodCalls();
108+
109+
if ($defaultHandlerInjection) {
110+
$this->assertEquals('setOptions', $methodCalls[0][0]);
111+
$this->assertEquals(array('default_target_path' => '/bar'), $methodCalls[0][1][0]);
112+
$this->assertEquals('setProviderKey', $methodCalls[1][0]);
113+
$this->assertEquals(array('foo'), $methodCalls[1][1]);
114+
} else {
115+
$this->assertCount(0, $methodCalls);
116+
}
117+
}
118+
119+
public function getSuccessHandlers()
120+
{
121+
return array(
122+
array(null, true),
123+
array('custom_success_handler', false),
124+
);
71125
}
72126

73127
protected function callFactory($id, $config, $userProviderId, $defaultEntryPointId)
@@ -92,11 +146,10 @@ protected function callFactory($id, $config, $userProviderId, $defaultEntryPoint
92146

93147
$container = new ContainerBuilder();
94148
$container->register('auth_provider');
149+
$container->register('custom_success_handler');
150+
$container->register('custom_failure_handler');
95151

96-
list($authProviderId,
97-
$listenerId,
98-
$entryPointId
99-
) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId);
152+
list($authProviderId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId);
100153

101154
return array($container, $authProviderId, $listenerId, $entryPointId);
102155
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
namespace Symfony\Component\Security\Http\Authentication;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
16+
17+
/**
18+
* @author Fabien Potencier <fabien@symfony.com>
19+
*/
20+
class CustomAuthenticationFailureHandler implements AuthenticationFailureHandlerInterface
21+
{
22+
private $handler;
23+
24+
/**
25+
* Constructor.
26+
*
27+
* @param AuthenticationFailureHandlerInterface $handler An AuthenticationFailureHandlerInterface instance
28+
* @param array $options Options for processing a successful authentication attempt
29+
*/
30+
public function __construct(AuthenticationFailureHandlerInterface $handler, array $options)
31+
{
32+
$this->handler = $handler;
33+
if (method_exists($handler, 'setOptions')) {
34+
$this->handler->setOptions($options);
35+
}
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
42+
{
43+
return $this->handler->onAuthenticationFailure($request, $exception);
44+
}
45+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
namespace Symfony\Component\Security\Http\Authentication;
13+
14+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
17+
/**
18+
* @author Fabien Potencier <fabien@symfony.com>
19+
*/
20+
class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface
21+
{
22+
private $handler;
23+
24+
/**
25+
* Constructor.
26+
*
27+
* @param AuthenticationSuccessHandlerInterface $handler An AuthenticationFailureHandlerInterface instance
28+
* @param array $options Options for processing a successful authentication attempt
29+
* @param string $providerKey The provider key
30+
*/
31+
public function __construct(AuthenticationSuccessHandlerInterface $handler, array $options, $providerKey)
32+
{
33+
$this->handler = $handler;
34+
if (method_exists($handler, 'setOptions')) {
35+
$this->handler->setOptions($options);
36+
}
37+
if (method_exists($providerKey, 'setProviderKey')) {
38+
$this->handler->setProviderKey($providerKey);
39+
}
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
46+
{
47+
return $this->handler->onAuthenticationSuccess($request, $token);
48+
}
49+
}

0 commit comments

Comments
 (0)
0