Skip to content

Commit 194ad4c

Browse files
committed
feature #14721 [Security] Configuring a user checker per firewall (iltar)
This PR was squashed before being merged into the 2.8 branch (closes #14721). Discussion ---------- [Security] Configuring a user checker per firewall _Changed my base branch to avoid issues, closed old PR_ | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed ticket | #11090 and helps #14673 | License | MIT | Doc PR | symfony/symfony-docs/pull/5530 This pull request adds support for a configurable user checker per firewall. An example could be: ```yml services: app.user_checker: class: App\Security\UserChecker arguments: - "@request_stack" security: firewalls: secured_area: pattern: ^/ anonymous: ~ basic_auth: ~ user_checker: app.user_checker ``` The above example will use the `UserChecker` defined as `app.user_checker`. If the `user_checker` option is left empty, `security.user_checker` will be used. If the `user_checkers` option is not defined, it will fall back to the original behavior to not break backwards compatibility and will validate using the existing `UserChecker`: `security.user_checker`. I left the default argument in the service definitions to be `security.user_checker` to include backwards compatibility for people who for some reason don't have the extension executed. You can obtain the checker for a specific firewall by appending the firewall name to it. For the firewall `secured_area`, this would be `security.user_checker.secured_area`. Commits ------- 76bc662 [Security] Configuring a user checker per firewall
2 parents fcf65f2 + 05be5da commit 194ad4c

19 files changed

+106
-19
lines changed

DependencyInjection/MainConfiguration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
216216
->prototype('scalar')->end()
217217
->end()
218218
->booleanNode('security')->defaultTrue()->end()
219+
->scalarNode('user_checker')
220+
->defaultValue('security.user_checker')
221+
->treatNullLike('security.user_checker')
222+
->info('The UserChecker to use when authenticating users in this firewall.')
223+
->end()
219224
->scalarNode('request_matcher')->end()
220225
->scalarNode('access_denied_url')->end()
221226
->scalarNode('access_denied_handler')->end()

DependencyInjection/Security/Factory/FormLoginFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config,
6565
$container
6666
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao'))
6767
->replaceArgument(0, new Reference($userProviderId))
68+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
6869
->replaceArgument(2, $id)
6970
;
7071

DependencyInjection/Security/Factory/FormLoginLdapFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config,
3030
$container
3131
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind'))
3232
->replaceArgument(0, new Reference($userProviderId))
33+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
3334
->replaceArgument(2, $id)
3435
->replaceArgument(3, new Reference($config['service']))
3536
->replaceArgument(4, $config['dn_string'])

DependencyInjection/Security/Factory/GuardAuthenticationFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
6969
->replaceArgument(0, $authenticatorReferences)
7070
->replaceArgument(1, new Reference($userProvider))
7171
->replaceArgument(2, $id)
72+
->replaceArgument(3, new Reference('security.user_checker.'.$id))
7273
;
7374

7475
// listener

DependencyInjection/Security/Factory/HttpBasicFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
2929
$container
3030
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.dao'))
3131
->replaceArgument(0, new Reference($userProvider))
32+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
3233
->replaceArgument(2, $id)
3334
;
3435

DependencyInjection/Security/Factory/HttpBasicLdapFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
3131
$container
3232
->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.ldap_bind'))
3333
->replaceArgument(0, new Reference($userProvider))
34+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
3435
->replaceArgument(2, $id)
3536
->replaceArgument(3, new Reference($config['service']))
3637
->replaceArgument(4, $config['dn_string'])

DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
3535
$authProviderId = 'security.authentication.provider.rememberme.'.$id;
3636
$container
3737
->setDefinition($authProviderId, new DefinitionDecorator('security.authentication.provider.rememberme'))
38+
->replaceArgument(0, new Reference('security.user_checker.'.$id))
3839
->addArgument($config['secret'])
3940
->addArgument($id)
4041
;

DependencyInjection/Security/Factory/RemoteUserFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
3030
$container
3131
->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.pre_authenticated'))
3232
->replaceArgument(0, new Reference($userProvider))
33+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
3334
->addArgument($id)
3435
;
3536

DependencyInjection/Security/Factory/X509Factory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider,
2929
$container
3030
->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.pre_authenticated'))
3131
->replaceArgument(0, new Reference($userProvider))
32+
->replaceArgument(1, new Reference('security.user_checker.'.$id))
3233
->addArgument($id)
3334
;
3435

DependencyInjection/SecurityExtension.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
1515
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
1616
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
17+
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\DefinitionDecorator;
1819
use Symfony\Component\DependencyInjection\Alias;
1920
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
@@ -100,16 +101,16 @@ public function load(array $configs, ContainerBuilder $container)
100101

101102
// add some required classes for compilation
102103
$this->addClassesToCompile(array(
103-
'Symfony\\Component\\Security\\Http\\Firewall',
104-
'Symfony\\Component\\Security\\Core\\User\\UserProviderInterface',
105-
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager',
106-
'Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorage',
107-
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager',
108-
'Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker',
109-
'Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface',
110-
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap',
111-
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext',
112-
'Symfony\\Component\\HttpFoundation\\RequestMatcher',
104+
'Symfony\Component\Security\Http\Firewall',
105+
'Symfony\Component\Security\Core\User\UserProviderInterface',
106+
'Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager',
107+
'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage',
108+
'Symfony\Component\Security\Core\Authorization\AccessDecisionManager',
109+
'Symfony\Component\Security\Core\Authorization\AuthorizationChecker',
110+
'Symfony\Component\Security\Core\Authorization\Voter\VoterInterface',
111+
'Symfony\Bundle\SecurityBundle\Security\FirewallMap',
112+
'Symfony\Bundle\SecurityBundle\Security\FirewallContext',
113+
'Symfony\Component\HttpFoundation\RequestMatcher',
113114
));
114115
}
115116

@@ -369,6 +370,8 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
369370
// Exception listener
370371
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
371372

373+
$container->setAlias(new Alias('security.user_checker.'.$id, false), $firewall['user_checker']);
374+
372375
return array($matcher, $listeners, $exceptionListener);
373376
}
374377

@@ -577,6 +580,7 @@ private function createSwitchUserListener($container, $id, $config, $defaultProv
577580
$switchUserListenerId = 'security.authentication.switchuser_listener.'.$id;
578581
$listener = $container->setDefinition($switchUserListenerId, new DefinitionDecorator('security.authentication.switchuser_listener'));
579582
$listener->replaceArgument(1, new Reference($userProvider));
583+
$listener->replaceArgument(2, new Reference('security.user_checker.'.$id));
580584
$listener->replaceArgument(3, $id);
581585
$listener->replaceArgument(6, $config['parameter']);
582586
$listener->replaceArgument(7, $config['role']);

Resources/config/guard.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<argument /> <!-- Simple Authenticator -->
2222
<argument /> <!-- User Provider -->
2323
<argument /> <!-- Provider-shared Key -->
24-
<argument type="service" id="security.user_checker" />
24+
<argument /> <!-- User Checker -->
2525
</service>
2626

2727
<service id="security.authentication.listener.guard"

Resources/config/security_listeners.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,15 @@
217217

218218
<service id="security.authentication.provider.dao" class="%security.authentication.provider.dao.class%" abstract="true" public="false">
219219
<argument /> <!-- User Provider -->
220-
<argument type="service" id="security.user_checker" />
220+
<argument /> <!-- User Checker -->
221221
<argument /> <!-- Provider-shared Key -->
222222
<argument type="service" id="security.encoder_factory" />
223223
<argument>%security.authentication.hide_user_not_found%</argument>
224224
</service>
225225

226226
<service id="security.authentication.provider.ldap_bind" class="Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider" public="false" abstract="true">
227227
<argument /> <!-- User Provider -->
228-
<argument type="service" id="security.user_checker" />
228+
<argument /> <!-- UserChecker -->
229229
<argument /> <!-- Provider-shared Key -->
230230
<argument /> <!-- LDAP -->
231231
<argument /> <!-- Base DN -->
@@ -240,7 +240,7 @@
240240

241241
<service id="security.authentication.provider.pre_authenticated" class="%security.authentication.provider.pre_authenticated.class%" abstract="true" public="false">
242242
<argument /> <!-- User Provider -->
243-
<argument type="service" id="security.user_checker" />
243+
<argument /> <!-- User Checker -->
244244
</service>
245245

246246
<service id="security.exception_listener" class="%security.exception_listener.class%" public="false" abstract="true">
@@ -260,7 +260,7 @@
260260
<tag name="monolog.logger" channel="security" />
261261
<argument type="service" id="security.token_storage" />
262262
<argument /> <!-- User Provider -->
263-
<argument type="service" id="security.user_checker" />
263+
<argument /> <!-- User Checker -->
264264
<argument /> <!-- Provider Key -->
265265
<argument type="service" id="security.access.decision_manager" />
266266
<argument type="service" id="logger" on-invalid="null" />

Resources/config/security_rememberme.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</service>
2929

3030
<service id="security.authentication.provider.rememberme" class="%security.authentication.provider.rememberme.class%" abstract="true" public="false">
31-
<argument type="service" id="security.user_checker" />
31+
<argument /> <!-- User Checker -->
3232
</service>
3333

3434
<service id="security.rememberme.token.provider.in_memory" class="%security.rememberme.token.provider.in_memory.class%" public="false" />

Tests/DependencyInjection/CompleteConfigurationTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ public function testFirewalls()
9393
'security.authentication.listener.anonymous.host',
9494
'security.access_listener',
9595
),
96+
array(
97+
'security.channel_listener',
98+
'security.context_listener.1',
99+
'security.authentication.listener.basic.with_user_checker',
100+
'security.authentication.listener.anonymous.with_user_checker',
101+
'security.access_listener',
102+
),
96103
), $listeners);
97104
}
98105

@@ -233,6 +240,21 @@ public function testRememberMeThrowExceptions()
233240
$this->assertFalse($service->getArgument(5));
234241
}
235242

243+
public function testUserCheckerConfig()
244+
{
245+
$this->assertEquals('app.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.with_user_checker'));
246+
}
247+
248+
public function testUserCheckerConfigWithDefaultChecker()
249+
{
250+
$this->assertEquals('security.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.host'));
251+
}
252+
253+
public function testUserCheckerConfigWithNoCheckers()
254+
{
255+
$this->assertEquals('security.user_checker', $this->getContainer('container1')->getAlias('security.user_checker.secure'));
256+
}
257+
236258
protected function getContainer($file)
237259
{
238260
$container = new ContainerBuilder();

Tests/DependencyInjection/Fixtures/php/container1.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
'remote_user' => true,
7373
'logout' => true,
7474
'remember_me' => array('secret' => 'TheSecret'),
75+
'user_checker' => null,
7576
),
7677
'host' => array(
7778
'pattern' => '/test',
@@ -80,6 +81,11 @@
8081
'anonymous' => true,
8182
'http_basic' => true,
8283
),
84+
'with_user_checker' => array(
85+
'user_checker' => 'app.user_checker',
86+
'anonymous' => true,
87+
'http_basic' => true,
88+
),
8389
),
8490

8591
'access_control' => array(

Tests/DependencyInjection/Fixtures/xml/container1.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<switch-user />
5656
<x509 />
5757
<remote-user />
58+
<user-checker />
5859
<logout />
5960
<remember-me secret="TheSecret"/>
6061
</firewall>
@@ -64,6 +65,12 @@
6465
<http-basic />
6566
</firewall>
6667

68+
<firewall name="with_user_checker">
69+
<anonymous />
70+
<http-basic />
71+
<user-checker>app.user_checker</user-checker>
72+
</firewall>
73+
6774
<role id="ROLE_ADMIN">ROLE_USER</role>
6875
<role id="ROLE_SUPER_ADMIN">ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH</role>
6976
<role id="ROLE_REMOTE">ROLE_USER,ROLE_ADMIN</role>

Tests/DependencyInjection/Fixtures/yml/container1.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,20 @@ security:
5656
logout: true
5757
remember_me:
5858
secret: TheSecret
59+
user_checker: ~
60+
5961
host:
6062
pattern: /test
6163
host: foo\.example\.org
6264
methods: [GET,POST]
6365
anonymous: true
6466
http_basic: true
6567

68+
with_user_checker:
69+
anonymous: ~
70+
http_basic: ~
71+
user_checker: app.user_checker
72+
6673
role_hierarchy:
6774
ROLE_ADMIN: ROLE_USER
6875
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

Tests/DependencyInjection/MainConfigurationTest.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function testNoConfigForProvider()
4646

4747
$processor = new Processor();
4848
$configuration = new MainConfiguration(array(), array());
49-
$config = $processor->processConfiguration($configuration, array($config));
49+
$processor->processConfiguration($configuration, array($config));
5050
}
5151

5252
/**
@@ -65,7 +65,7 @@ public function testManyConfigForProvider()
6565

6666
$processor = new Processor();
6767
$configuration = new MainConfiguration(array(), array());
68-
$config = $processor->processConfiguration($configuration, array($config));
68+
$processor->processConfiguration($configuration, array($config));
6969
}
7070

7171
public function testCsrfAliases()
@@ -108,8 +108,35 @@ public function testCsrfOriginalAndAliasValueCausesException()
108108
);
109109
$config = array_merge(static::$minimalConfig, $config);
110110

111+
$processor = new Processor();
112+
$configuration = new MainConfiguration(array(), array());
113+
$processor->processConfiguration($configuration, array($config));
114+
}
115+
116+
public function testDefaultUserCheckers()
117+
{
118+
$processor = new Processor();
119+
$configuration = new MainConfiguration(array(), array());
120+
$processedConfig = $processor->processConfiguration($configuration, array(static::$minimalConfig));
121+
122+
$this->assertEquals('security.user_checker', $processedConfig['firewalls']['stub']['user_checker']);
123+
}
124+
125+
public function testUserCheckers()
126+
{
127+
$config = array(
128+
'firewalls' => array(
129+
'stub' => array(
130+
'user_checker' => 'app.henk_checker',
131+
),
132+
),
133+
);
134+
$config = array_merge(static::$minimalConfig, $config);
135+
111136
$processor = new Processor();
112137
$configuration = new MainConfiguration(array(), array());
113138
$processedConfig = $processor->processConfiguration($configuration, array($config));
139+
140+
$this->assertEquals('app.henk_checker', $processedConfig['firewalls']['stub']['user_checker']);
114141
}
115142
}

Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public function testBasicCreate()
109109
'index_0' => array(new Reference('authenticator123')),
110110
'index_1' => new Reference('my_user_provider'),
111111
'index_2' => 'my_firewall',
112+
'index_3' => new Reference('security.user_checker.my_firewall'),
112113
), $providerDefinition->getArguments());
113114

114115
$listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall');
@@ -123,7 +124,7 @@ public function testExistingDefaultEntryPointUsed()
123124
'authenticators' => array('authenticator123'),
124125
'entry_point' => null,
125126
);
126-
list($container, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point');
127+
list(, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point');
127128
$this->assertEquals('some_default_entry_point', $entryPointId);
128129
}
129130

0 commit comments

Comments
 (0)