Skip to content

Commit 710fe58

Browse files
committed
feature #45907 [SecurityBundle] Allow specifying attributes for RequestMatcher (freiondrej-lmc)
This PR was squashed before being merged into the 6.2 branch. Discussion ---------- [SecurityBundle] Allow specifying attributes for `RequestMatcher` | Q | A | ------------- | --- | Branch? | 6.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #45901 | License | MIT The \Symfony\Component\HttpFoundation\RequestMatcher supports array $attributes, which makes it possible to specify a _route (useful e.g. in a multilingual project where $path is translated). However, its current configuration does not offer the possibility to specify the attributes. This PR adds this possibility, so this already existing feature can be leveraged. I also added a shortcut to just specify "route": "xxx", which translates to "attributes": ["_route": "xxx"]. Commits ------- c19d1cb3ef [SecurityBundle] Allow specifying attributes for `RequestMatcher`
2 parents 8c853ab + 857a7ac commit 710fe58

File tree

4 files changed

+120
-5
lines changed

4 files changed

+120
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ CHANGELOG
1818
---
1919

2020
* The `security.access_control` now accepts a `RequestMatcherInterface` under the `request_matcher` option as scope configuration
21+
* The `security.access_control` now accepts an `attributes` array to match request attributes in the `RequestMatcher`
22+
* The `security.access_control` now accepts a `route` option to match request route in the `RequestMatcher`
2123
* Display the inherited roles of the logged-in user in the Web Debug Toolbar
2224

2325
6.0

DependencyInjection/MainConfiguration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ private function addAccessControlSection(ArrayNodeDefinition $rootNode)
147147
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
148148
->prototype('scalar')->end()
149149
->end()
150+
->arrayNode('attributes')
151+
->prototype('scalar')->end()
152+
->end()
153+
->scalarNode('route')->defaultNull()->end()
150154
->arrayNode('methods')
151155
->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end()
152156
->prototype('scalar')->end()

DependencyInjection/SecurityExtension.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,24 +204,34 @@ private function createAuthorization(array $config, ContainerBuilder $container)
204204
{
205205
foreach ($config['access_control'] as $access) {
206206
if (isset($access['request_matcher'])) {
207-
if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods']) {
207+
if ($access['path'] || $access['host'] || $access['port'] || $access['ips'] || $access['methods'] || $access['attributes'] || $access['route']) {
208208
throw new InvalidConfigurationException('The "request_matcher" option should not be specified alongside other options. Consider integrating your constraints inside your RequestMatcher directly.');
209209
}
210210
$matcher = new Reference($access['request_matcher']);
211211
} else {
212+
$attributes = $access['attributes'];
213+
214+
if ($access['route']) {
215+
if (\array_key_exists('_route', $attributes)) {
216+
throw new InvalidConfigurationException('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.');
217+
}
218+
$attributes['_route'] = $access['route'];
219+
}
220+
212221
$matcher = $this->createRequestMatcher(
213222
$container,
214223
$access['path'],
215224
$access['host'],
216225
$access['port'],
217226
$access['methods'],
218-
$access['ips']
227+
$access['ips'],
228+
$attributes
219229
);
220230
}
221231

222-
$attributes = $access['roles'];
232+
$roles = $access['roles'];
223233
if ($access['allow_if']) {
224-
$attributes[] = $this->createExpression($container, $access['allow_if']);
234+
$roles[] = $this->createExpression($container, $access['allow_if']);
225235
}
226236

227237
$emptyAccess = 0 === \count(array_filter($access));
@@ -231,7 +241,7 @@ private function createAuthorization(array $config, ContainerBuilder $container)
231241
}
232242

233243
$container->getDefinition('security.access_map')
234-
->addMethodCall('add', [$matcher, $attributes, $access['requires_channel']]);
244+
->addMethodCall('add', [$matcher, $roles, $access['requires_channel']]);
235245
}
236246

237247
// allow cache warm-up for expressions

Tests/DependencyInjection/SecurityExtensionTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,105 @@ public function provideAdditionalRequestMatcherConstraints()
313313
yield 'Invalid configuration with port' => [['port' => 80]];
314314
yield 'Invalid configuration with methods' => [['methods' => ['POST']]];
315315
yield 'Invalid configuration with ips' => [['ips' => ['0.0.0.0']]];
316+
yield 'Invalid configuration with attributes' => [['attributes' => ['_route' => 'foo_route']]];
317+
yield 'Invalid configuration with route' => [['route' => 'foo_route']];
318+
}
319+
320+
public function testRegisterAccessControlWithSpecifiedAttributes()
321+
{
322+
$container = $this->getRawContainer();
323+
$container->loadFromExtension('security', [
324+
'enable_authenticator_manager' => true,
325+
'providers' => [
326+
'default' => ['id' => 'foo'],
327+
],
328+
'firewalls' => [
329+
'some_firewall' => [
330+
'pattern' => '/.*',
331+
'http_basic' => [],
332+
],
333+
],
334+
'access_control' => [
335+
['attributes' => ['_route' => 'foo_route']],
336+
],
337+
]);
338+
339+
$container->compile();
340+
341+
$accessMap = $container->getDefinition('security.access_map');
342+
$this->assertCount(1, $accessMap->getMethodCalls());
343+
$call = $accessMap->getMethodCalls()[0];
344+
$this->assertSame('add', $call[0]);
345+
$args = $call[1];
346+
$requestMatcherId = (string) $args[0];
347+
348+
$requestMatcherDefinition = $container->getDefinition($requestMatcherId);
349+
$requestMatcherConstructorArguments = $requestMatcherDefinition->getArguments();
350+
$this->assertArrayHasKey(4, $requestMatcherConstructorArguments);
351+
$attributes = $requestMatcherConstructorArguments[4];
352+
$this->assertArrayHasKey('_route', $attributes);
353+
$this->assertSame('foo_route', $attributes['_route']);
354+
}
355+
356+
public function testRegisterAccessControlWithSpecifiedRoute()
357+
{
358+
$container = $this->getRawContainer();
359+
$container->loadFromExtension('security', [
360+
'enable_authenticator_manager' => true,
361+
'providers' => [
362+
'default' => ['id' => 'foo'],
363+
],
364+
'firewalls' => [
365+
'some_firewall' => [
366+
'pattern' => '/.*',
367+
'http_basic' => [],
368+
],
369+
],
370+
'access_control' => [
371+
['route' => 'foo_route'],
372+
],
373+
]);
374+
375+
$container->compile();
376+
377+
$accessMap = $container->getDefinition('security.access_map');
378+
$this->assertCount(1, $accessMap->getMethodCalls());
379+
$call = $accessMap->getMethodCalls()[0];
380+
$this->assertSame('add', $call[0]);
381+
$args = $call[1];
382+
$requestMatcherId = (string) $args[0];
383+
384+
$requestMatcherDefinition = $container->getDefinition($requestMatcherId);
385+
$requestMatcherConstructorArguments = $requestMatcherDefinition->getArguments();
386+
$this->assertArrayHasKey(4, $requestMatcherConstructorArguments);
387+
$attributes = $requestMatcherConstructorArguments[4];
388+
$this->assertArrayHasKey('_route', $attributes);
389+
$this->assertSame('foo_route', $attributes['_route']);
390+
}
391+
392+
public function testRegisterAccessControlWithSpecifiedAttributesThrowsException()
393+
{
394+
$container = $this->getRawContainer();
395+
$container->loadFromExtension('security', [
396+
'enable_authenticator_manager' => true,
397+
'providers' => [
398+
'default' => ['id' => 'foo'],
399+
],
400+
'firewalls' => [
401+
'some_firewall' => [
402+
'pattern' => '/.*',
403+
'http_basic' => [],
404+
],
405+
],
406+
'access_control' => [
407+
['route' => 'anything', 'attributes' => ['_route' => 'foo_route']],
408+
],
409+
]);
410+
411+
$this->expectException(InvalidConfigurationException::class);
412+
$this->expectExceptionMessage('The "route" option should not be specified alongside "attributes._route" option. Use just one of the options.');
413+
414+
$container->compile();
316415
}
317416

318417
public function testRemovesExpressionCacheWarmerDefinitionIfNoExpressions()

0 commit comments

Comments
 (0)