Skip to content

[Security] AccessDecisionManager refactoring #942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ public function load(array $configs, ContainerBuilder $container)

// set some global scalars
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
$container->setParameter('security.access.allow_if_all_abstain', $config['access_decision_manager']['allow_if_all_abstain']);
$container->setParameter('security.access.allow_if_equal_granted_denied', $config['access_decision_manager']['allow_if_equal_granted_denied']);

$strategy = sprintf('security.access.%s_access_strategy', $config['access_decision_manager']['strategy']);
$container->setAlias('security.access.strategy', $strategy);

$container->getDefinition('security.authentication.session_strategy')->replaceArgument(0, $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'])
;
$container->setParameter('security.access.always_authenticate_before_granting', $config['always_authenticate_before_granting']);

$this->createFirewalls($config, $container);
Expand All @@ -86,6 +86,9 @@ public function load(array $configs, ContainerBuilder $container)

// add some required classes for compilation
$this->addClassesToCompile(array(
'Symfony\\Component\\HttpFoundation\\RequestMatcher',
'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface',

'Symfony\\Component\\Security\\Http\\Firewall',
'Symfony\\Component\\Security\\Http\\FirewallMapInterface',
'Symfony\\Component\\Security\\Core\\SecurityContext',
Expand All @@ -95,13 +98,14 @@ public function load(array $configs, ContainerBuilder $container)
'Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface',
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager',
'Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface',
'Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AccessStrategy',
'Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AccessStrategyInterface',
'Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\AffirmativeAccessStrategy',
'Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\ConsensusAccessStrategy',
'Symfony\\Component\\Security\\Core\\Authorization\\Strategy\\UnanimousAccessStrategy',
'Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface',

'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap',
'Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext',

'Symfony\\Component\\HttpFoundation\\RequestMatcher',
'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface',
));
}

Expand Down
26 changes: 25 additions & 1 deletion src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@

<parameter key="security.access.decision_manager.class">Symfony\Component\Security\Core\Authorization\AccessDecisionManager</parameter>

<parameter key="security.access.affirmative_access_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeAccessStrategy</parameter>
<parameter key="security.access.consensus_access_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\ConsensusAccessStrategy</parameter>
<parameter key="security.access.unanimous_access_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\UnanimousAccessStrategy</parameter>
<parameter key="security.access.allow_if_all_abstain">false</parameter>
<parameter key="security.access.allow_if_equal_granted_denied">true</parameter>

<parameter key="security.access.simple_role_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleVoter</parameter>
<parameter key="security.access.authenticated_voter.class">Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter</parameter>
<parameter key="security.access.role_hierarchy_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter</parameter>
Expand Down Expand Up @@ -66,13 +72,31 @@
</service>

<service id="security.encoder_factory" alias="security.encoder_factory.generic"></service>

<service id="security.user_checker" class="%security.user_checker.class%" public="false" />


<!-- Authorization related services -->
<service id="security.access.decision_manager" class="%security.access.decision_manager.class%" public="false">
<argument type="service" id="security.access.strategy"/>
</service>


<!-- Access strategies -->
<service id="security.access.affirmative_access_strategy" class="%security.access.affirmative_access_strategy.class%" public="false">
<argument type="collection"></argument>
<argument>%security.access.allow_if_all_abstain%</argument>
</service>

<service id="security.access.consensus_access_strategy" class="%security.access.consensus_access_strategy.class%" public="false">
<argument type="collection"></argument>
<argument>%security.access.allow_if_all_abstain%</argument>
<argument>%security.access.allow_if_equal_granted_denied%</argument>
</service>

<service id="security.access.unanimous_access_strategy" class="%security.access.unanimous_access_strategy.class%" public="false">
<argument type="collection"></argument>
<argument>%security.access.allow_if_all_abstain%</argument>
</service>

<service id="security.role_hierarchy" class="%security.role_hierarchy.class%" public="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Security\Core\Authorization;

use Symfony\Component\Security\Core\Authorization\Strategy\AccessStrategyInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

Expand All @@ -22,45 +23,32 @@
*/
class AccessDecisionManager implements AccessDecisionManagerInterface
{
private $voters;
private $strategy;
private $allowIfAllAbstainDecisions;
private $allowIfEqualGrantedDeniedDecisions;

/**
* Constructor.
*
* @param VoterInterface[] $voters An array of VoterInterface instances
* @param string $strategy The vote strategy
* @param Boolean $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not
* @param Boolean $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals
* @param AccessStrategyInterface $strategy The access decision strategy
*/
public function __construct(array $voters, $strategy = 'affirmative', $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true)
public function __construct(AccessStrategyInterface $strategy)
{
if (!$voters) {
throw new \InvalidArgumentException('You must at least add one voter.');
}

$this->voters = $voters;
$this->strategy = 'decide'.ucfirst($strategy);
$this->allowIfAllAbstainDecisions = (Boolean) $allowIfAllAbstainDecisions;
$this->allowIfEqualGrantedDeniedDecisions = (Boolean) $allowIfEqualGrantedDeniedDecisions;
$this->strategy = $strategy;
}

/**
* {@inheritdoc}
*/
public function decide(TokenInterface $token, array $attributes, $object = null)
{
return $this->{$this->strategy}($token, $attributes, $object);
return $this->strategy->decide($token, $attributes, $object);
}

/**
* {@inheritdoc}
*/
public function supportsAttribute($attribute)
{
foreach ($this->voters as $voter) {
foreach ($this->strategy->getVoters() as $voter) {
if ($voter->supportsAttribute($attribute)) {
return true;
}
Expand All @@ -74,135 +62,12 @@ public function supportsAttribute($attribute)
*/
public function supportsClass($class)
{
foreach ($this->voters as $voter) {
foreach ($this->strategy->getVoters() as $voter) {
if ($voter->supportsClass($class)) {
return true;
}
}

return false;
}

/**
* Grants access if any voter returns an affirmative response.
*
* If all voters abstained from voting, the decision will be based on the
* allowIfAllAbstainDecisions property value (defaults to false).
*/
private function decideAffirmative(TokenInterface $token, array $attributes, $object = null)
{
$deny = 0;
foreach ($this->voters as $voter) {
$result = $voter->vote($token, $object, $attributes);
switch ($result) {
case VoterInterface::ACCESS_GRANTED:
return true;

case VoterInterface::ACCESS_DENIED:
++$deny;

break;

default:
break;
}
}

if ($deny > 0) {
return false;
}

return $this->allowIfAllAbstainDecisions;
}

/**
* Grants access if there is consensus of granted against denied responses.
*
* Consensus means majority-rule (ignoring abstains) rather than unanimous
* agreement (ignoring abstains). If you require unanimity, see
* UnanimousBased.
*
* If there were an equal number of grant and deny votes, the decision will
* be based on the allowIfEqualGrantedDeniedDecisions property value
* (defaults to true).
*
* If all voters abstained from voting, the decision will be based on the
* allowIfAllAbstainDecisions property value (defaults to false).
*/
private function decideConsensus(TokenInterface $token, array $attributes, $object = null)
{
$grant = 0;
$deny = 0;
$abstain = 0;
foreach ($this->voters as $voter) {
$result = $voter->vote($token, $object, $attributes);

switch ($result) {
case VoterInterface::ACCESS_GRANTED:
++$grant;

break;

case VoterInterface::ACCESS_DENIED:
++$deny;

break;

default:
++$abstain;

break;
}
}

if ($grant > $deny) {
return true;
}

if ($deny > $grant) {
return false;
}

if ($grant == $deny && $grant != 0) {
return $this->allowIfEqualGrantedDeniedDecisions;
}

return $this->allowIfAllAbstainDecisions;
}

/**
* Grants access if only grant (or abstain) votes were received.
*
* If all voters abstained from voting, the decision will be based on the
* allowIfAllAbstainDecisions property value (defaults to false).
*/
private function decideUnanimous(TokenInterface $token, array $attributes, $object = null)
{
$grant = 0;
foreach ($attributes as $attribute) {
foreach ($this->voters as $voter) {
$result = $voter->vote($token, $object, array($attribute));

switch ($result) {
case VoterInterface::ACCESS_GRANTED:
++$grant;

break;

case VoterInterface::ACCESS_DENIED:
return false;

default:
break;
}
}
}

// no deny votes
if ($grant > 0) {
return true;
}

return $this->allowIfAllAbstainDecisions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Security\Core\Authorization\Strategy;

use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* AccessStrategy is the base class for all access strategy classes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Hugo Hamon <hugo.hamon@sensio.com>
*/
abstract class AccessStrategy implements AccessStrategyInterface
{
protected $voters;
protected $allowIfAllAbstainDecisions;

/**
* Constructor.
*
* @param VoterInterface[] An array of VoterInterface objects.
* @param Boolean $allowIfAllAbstainDecisions Allow access if all voters abstain decisions.
*/
public function __construct(array $voters, $allowIfAllAbstainDecisions = false)
{
if (!$voters) {
throw new \InvalidArgumentException('You must at least add one voter.');
}

$this->voters = $voters;
$this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions;
}

/**
* {@inheritdoc}
*/
public function getVoters()
{
return $this->voters;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Security\Core\Authorization\Strategy;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* AccessStrategyInterface is the base interface to define behaviors for access
* decision strategies. Access decision strategies are used by the
* AccessDecisionManager service.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Hugo Hamon <hugo.hamon@sensio.com>
*/
interface AccessStrategyInterface
{
/**
* Returns an array of VoterInterface objects.
*
* @return VoterInterface[] An array of VoterInterface objects.
*/
public function getVoters();

/**
* Decides which access must be applied for a given TokenInterface object.
*
* @return int One of the constant of the VoterInterface interface.
*/
public function decide(TokenInterface $token, array $attributes, $object = null);
}
Loading