Skip to content

Commit abeed67

Browse files
TristanPouliquenderrabus
authored andcommitted
[SecurityBundle] Allow to specify a RequestMatcher directly in an ACL definition
1 parent 536d4fa commit abeed67

File tree

4 files changed

+98
-8
lines changed

4 files changed

+98
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
CHANGELOG
22
=========
33

4+
6.1
5+
---
6+
* The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration
7+
48
6.0
59
---
610

DependencyInjection/MainConfiguration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode)
134134
->fixXmlConfig('ip')
135135
->fixXmlConfig('method')
136136
->children()
137+
->scalarNode('request_matcher')->defaultNull()->end()
137138
->scalarNode('requires_channel')->defaultNull()->end()
138139
->scalarNode('path')
139140
->defaultNull()

DependencyInjection/SecurityExtension.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,24 @@ private function createRoleHierarchy(array $config, ContainerBuilder $container)
195195
private function createAuthorization(array $config, ContainerBuilder $container)
196196
{
197197
foreach ($config['access_control'] as $access) {
198-
$matcher = $this->createRequestMatcher(
199-
$container,
200-
$access['path'],
201-
$access['host'],
202-
$access['port'],
203-
$access['methods'],
204-
$access['ips']
205-
);
198+
if (isset($access['request_matcher'])) {
199+
if (
200+
isset($access['path']) || isset($access['host']) || isset($access['port'])
201+
|| [] !== $access['ips'] || [] !== $access['methods']
202+
) {
203+
throw new InvalidConfigurationException('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.');
204+
}
205+
$matcher = new Reference($access['request_matcher']);
206+
} else {
207+
$matcher = $this->createRequestMatcher(
208+
$container,
209+
$access['path'],
210+
$access['host'],
211+
$access['port'],
212+
$access['methods'],
213+
$access['ips']
214+
);
215+
}
206216

207217
$attributes = $access['roles'];
208218
if ($access['allow_if']) {

Tests/DependencyInjection/SecurityExtensionTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\DependencyInjection\Reference;
2727
use Symfony\Component\ExpressionLanguage\Expression;
2828
use Symfony\Component\HttpFoundation\Request;
29+
use Symfony\Component\HttpFoundation\RequestMatcher;
2930
use Symfony\Component\HttpFoundation\Response;
3031
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
3132
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -250,6 +251,80 @@ public function testRegisterRequestMatchersWithAllowIfExpression()
250251
);
251252
}
252253

254+
public function testRegisterAccessControlWithSpecifiedRequestMatcherService()
255+
{
256+
$container = $this->getRawContainer();
257+
258+
$requestMatcherId = 'My\Test\RequestMatcher';
259+
$requestMatcher = new RequestMatcher('/');
260+
$container->set($requestMatcherId, $requestMatcher);
261+
262+
$container->loadFromExtension('security', [
263+
'enable_authenticator_manager' => true,
264+
'providers' => [
265+
'default' => ['id' => 'foo'],
266+
],
267+
'firewalls' => [
268+
'some_firewall' => [
269+
'pattern' => '/.*',
270+
'http_basic' => [],
271+
],
272+
],
273+
'access_control' => [
274+
['request_matcher' => $requestMatcherId],
275+
],
276+
]);
277+
278+
$container->compile();
279+
$accessMap = $container->getDefinition('security.access_map');
280+
$this->assertCount(1, $accessMap->getMethodCalls());
281+
$call = $accessMap->getMethodCalls()[0];
282+
$this->assertSame('add', $call[0]);
283+
$args = $call[1];
284+
$this->assertCount(3, $args);
285+
$this->assertSame($requestMatcherId, (string) $args[0]);
286+
}
287+
288+
/** @dataProvider provideAdditionalRequestMatcherConstraints */
289+
public function testRegisterAccessControlWithRequestMatcherAndAdditionalOptionsThrowsInvalidException(array $additionalConstraints)
290+
{
291+
$container = $this->getRawContainer();
292+
293+
$requestMatcherId = 'My\Test\RequestMatcher';
294+
$requestMatcher = new RequestMatcher('/');
295+
$container->set($requestMatcherId, $requestMatcher);
296+
297+
$container->loadFromExtension('security', [
298+
'enable_authenticator_manager' => true,
299+
'providers' => [
300+
'default' => ['id' => 'foo'],
301+
],
302+
'firewalls' => [
303+
'some_firewall' => [
304+
'pattern' => '/.*',
305+
'http_basic' => [],
306+
],
307+
],
308+
'access_control' => [
309+
array_merge(['request_matcher' => $requestMatcherId], $additionalConstraints),
310+
],
311+
]);
312+
313+
$this->expectException(InvalidConfigurationException::class);
314+
$this->expectExceptionMessage('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.');
315+
316+
$container->compile();
317+
}
318+
319+
public function provideAdditionalRequestMatcherConstraints()
320+
{
321+
yield 'Invalid configuration with path' => [['path' => '^/url']];
322+
yield 'Invalid configuration with host' => [['host' => 'example.com']];
323+
yield 'Invalid configuration with port' => [['port' => 80]];
324+
yield 'Invalid configuration with methods' => [['methods' => ['POST']]];
325+
yield 'Invalid configuration with ips' => [['ips' => ['0.0.0.0']]];
326+
}
327+
253328
public function testRemovesExpressionCacheWarmerDefinitionIfNoExpressions()
254329
{
255330
$container = $this->getRawContainer();

0 commit comments

Comments
 (0)