Skip to content

Commit

Permalink
feature #19034 [Security] make it possible to configure a custom acce…
Browse files Browse the repository at this point in the history
…ss decision manager service (xabbuh)

This PR was merged into the 3.4 branch.

Discussion
----------

[Security] make it possible to configure a custom access decision manager service

| Q | A |
| --- | --- |
| Branch? | 3.4 |
| Bug fix? | no |
| New feature? | yes |
| BC breaks? | no |
| Deprecations? | no |
| Tests pass? | yes |
| Fixed tickets | #942, #14049, #15295, #16828, #16843, |
| License | MIT |
| Doc PR | TODO |

These changes will make it possible to let users define their own voting strategies without the need for custom compiler passes that replace the built-in `AccessDecisionManager` (see linked issues in the PR table for some use cases).

Commits
-------

e0913a2 add option to define the access decision manager
  • Loading branch information
nicolas-grekas committed Jul 12, 2017
2 parents 954e9f1 + e0913a2 commit afaf299
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 7 deletions.
Expand Up @@ -59,6 +59,26 @@ public function getConfigTreeBuilder()
$rootNode = $tb->root('security');

$rootNode
->beforeNormalization()
->ifTrue(function ($v) {
if (!isset($v['access_decision_manager'])) {
return true;
}

if (!isset($v['access_decision_manager']['strategy']) && !isset($v['access_decision_manager']['service'])) {
return true;
}

return false;
})
->then(function ($v) {
$v['access_decision_manager'] = array(
'strategy' => AccessDecisionManager::STRATEGY_AFFIRMATIVE,
);

return $v;
})
->end()
->children()
->scalarNode('access_denied_url')->defaultNull()->example('/foo/error403')->end()
->enumNode('session_fixation_strategy')
Expand All @@ -73,11 +93,15 @@ public function getConfigTreeBuilder()
->children()
->enumNode('strategy')
->values(array(AccessDecisionManager::STRATEGY_AFFIRMATIVE, AccessDecisionManager::STRATEGY_CONSENSUS, AccessDecisionManager::STRATEGY_UNANIMOUS))
->defaultValue(AccessDecisionManager::STRATEGY_AFFIRMATIVE)
->end()
->scalarNode('service')->end()
->booleanNode('allow_if_all_abstain')->defaultFalse()->end()
->booleanNode('allow_if_equal_granted_denied')->defaultTrue()->end()
->end()
->validate()
->ifTrue(function ($v) { return isset($v['strategy']) && isset($v['service']); })
->thenInvalid('"strategy" and "service" cannot be used together.')
->end()
->end()
->end()
;
Expand Down
Expand Up @@ -83,12 +83,17 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
$container->setParameter('security.authentication.manager.erase_credentials', $config['erase_credentials']);
$container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_strategy']);
$container
->getDefinition('security.access.decision_manager')
->addArgument($config['access_decision_manager']['strategy'])
->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied'])
;

if (isset($config['access_decision_manager']['service'])) {
$container->setAlias('security.access.decision_manager', $config['access_decision_manager']['service']);
} else {
$container
->getDefinition('security.access.decision_manager')
->addArgument($config['access_decision_manager']['strategy'])
->addArgument($config['access_decision_manager']['allow_if_all_abstain'])
->addArgument($config['access_decision_manager']['allow_if_equal_granted_denied']);
}

$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);
$container->setParameter('security.authentication.hide_user_not_found', $config['hide_user_not_found']);

Expand Down
Expand Up @@ -17,6 +17,7 @@
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;

abstract class CompleteConfigurationTest extends TestCase
{
Expand Down Expand Up @@ -357,6 +358,29 @@ public function testUserPasswordEncoderCommandIsRegistered()
$this->assertTrue($this->getContainer('remember_me_options')->has('security.console.user_password_encoder_command'));
}

public function testDefaultAccessDecisionManagerStrategyIsAffirmative()
{
$container = $this->getContainer('access_decision_manager_default_strategy');

$this->assertSame(AccessDecisionManager::STRATEGY_AFFIRMATIVE, $container->getDefinition('security.access.decision_manager')->getArgument(1), 'Default vote strategy is affirmative');
}

public function testCustomAccessDecisionManagerService()
{
$container = $this->getContainer('access_decision_manager_service');

$this->assertSame('app.access_decision_manager', (string) $container->getAlias('security.access.decision_manager'), 'The custom access decision manager service is aliased');
}

/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage "strategy" and "service" cannot be used together.
*/
public function testAccessDecisionManagerServiceAndStrategyCannotBeUsedAtTheSameTime()
{
$container = $this->getContainer('access_decision_manager_service_and_strategy');
}

protected function getContainer($file)
{
$file = $file.'.'.$this->getFileExtension();
Expand Down
@@ -0,0 +1,16 @@
<?php

$container->loadFromExtension('security', array(
'providers' => array(
'default' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
),
),
),
),
'firewalls' => array(
'simple' => array('pattern' => '/login', 'security' => false),
),
));
@@ -0,0 +1,19 @@
<?php

$container->loadFromExtension('security', array(
'access_decision_manager' => array(
'service' => 'app.access_decision_manager',
),
'providers' => array(
'default' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
),
),
),
),
'firewalls' => array(
'simple' => array('pattern' => '/login', 'security' => false),
),
));
@@ -0,0 +1,20 @@
<?php

$container->loadFromExtension('security', array(
'access_decision_manager' => array(
'service' => 'app.access_decision_manager',
'strategy' => 'affirmative',
),
'providers' => array(
'default' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'foo', 'roles' => 'ROLE_USER'),
),
),
),
),
'firewalls' => array(
'simple' => array('pattern' => '/login', 'security' => false),
),
));
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<config>
<provider name="default">
<memory>
<user name="foo" password="foo" roles="ROLE_USER" />
</memory>
</provider>

<firewall name="simple" pattern="/login" security="false" />
</config>
</srv:container>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<config>
<access-decision-manager service="app.access_decision_manager" />

<provider name="default">
<memory>
<user name="foo" password="foo" roles="ROLE_USER" />
</memory>
</provider>

<firewall name="simple" pattern="/login" security="false" />
</config>
</srv:container>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<config>
<access-decision-manager service="app.access_decision_manager" strategy="affirmative" />

<provider name="default">
<memory>
<user name="foo" password="foo" roles="ROLE_USER" />
</memory>
</provider>

<firewall name="simple" pattern="/login" security="false" />
</config>
</srv:container>
@@ -0,0 +1,8 @@
security:
providers:
default:
memory:
users:
foo: { password: foo, roles: ROLE_USER }
firewalls:
simple: { pattern: /login, security: false }
@@ -0,0 +1,10 @@
security:
access_decision_manager:
service: app.access_decision_manager
providers:
default:
memory:
users:
foo: { password: foo, roles: ROLE_USER }
firewalls:
simple: { pattern: /login, security: false }
@@ -0,0 +1,11 @@
security:
access_decision_manager:
service: app.access_decision_manager
strategy: affirmative
providers:
default:
memory:
users:
foo: { password: foo, roles: ROLE_USER }
firewalls:
simple: { pattern: /login, security: false }

0 comments on commit afaf299

Please sign in to comment.