10000 [Security] Fixed #14049 · solilokiam/symfony@38b8cf5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 38b8cf5

Browse files
committed
[Security] Fixed symfony#14049
HighestNotAbstainedVoterStrategy has been added. Also this commit makes access decision strategy extensible.
1 parent 2655072 commit 38b8cf5

File tree

10 files changed

+417
-136
lines changed

10 files changed

+417
-136
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
4+
5+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
8+
class AddAccessDecisionStrategyPass implements CompilerPassInterface
9+
{
10+
/**
11+
* You can modify the container here before it is dumped to PHP code.
12+
*
13+
* @param ContainerBuilder $container
14+
*
15+
* @api
16+
*/
17+
public function process(ContainerBuilder $container)
18+
{
19+
if (!$container->hasDefinition('security.access.decision_manager')) {
20+
return;
21+
}
22+
23+
$strategies = array();
24+
foreach ($container->findTaggedServiceIds('security.access_strategy') as $id => $attributes) {
25+
$strategyName = isset($attributes[0]['strategy']) ? $attributes[0]['strategy'] : 0;
26+
$strategies[$strategyName] = new Reference($id);
27+
}
28+
29+
if (!$strategies) {
30+
throw new LogicException('No access decision strategies found. You need to tag at least one with "security.access_strategy"');
31+
}
32+
33+
$container->getDefinition('security.access.decision_manager')->addMethodCall('setStrategies', $strategies);
34+
}
35+
36+
}

src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@
2929

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

32+
<parameter key="security.decide_affirmative_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\DecideAffirmativeStrategy</parameter>
33+
<parameter key="security.decide_consensus_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\DecideConsensusStrategy</parameter>
34+
<parameter key="security.decide_unanimous_strategy.class">Symfony\Component\Security\Core\Authorization\Strategy\DecideUnanimousStrategy</parameter>
35+
<parameter key="security.decide_highest_not_abstained_voter_strategy.class">
36+
Symfony\Component\Security\Core\Authorization\Strategy\DecideHighestNotAbstainedVoterStrategy
37+
</parameter>
38+
3239
<parameter key="security.access.simple_role_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleVoter</parameter>
3340
<parameter key="security.access.authenticated_voter.class">Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter</parameter>
3441
<parameter key="security.access.role_hierarchy_voter.class">Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter</parameter>
@@ -111,6 +118,24 @@
111118
<argument>%security.role_hierarchy.roles%</argument>
112119
</service>
113120

121+
<!-- Access Strategies -->
122+
<service id="security.access.decide_affirmative_strategy" class="%security.decide_affirmative_strategy.class%" public="false">
123+
<tag name="security.access_strategy" strategy="affirmative"></tag>
124+
</service>
125+
126+
<service id="security.access.decide_consensus_strategy" class="%security.decide_consensus_strategy.class%" public="false">
127+
<tag name="security.access_strategy" strategy="consensus"></tag>
128+
</service>
129+
130+
<service id="security.access.decide_unanimous_strategy" class="%security.decide_unanimous_strategy.class%" public="false">
131+
<tag name="security.access_strategy" strategy="unanimous"></tag>
132+
</service>
133+
134+
<service id="security.access.decide_highest_not_abstained_boter_strategy"
135+
class="%security.decide_highest_not_abstained_voter_strategy.class%" public="false">
136+
<tag name="security.access_strategy" strategy="highest"></tag>
137+
</service>
138+
114139

115140
<!-- Security Voters -->
116141
<service id="security.access.simple_role_voter" class="%security.access.simple_role_voter.class%" public="false">

src/Symfony/Component/Security/Core/Authorization/AccessDecisionManager.php

Lines changed: 44 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
namespace Symfony\Component\Security\Core\Authorization;
1313

14+
use Symfony\Component\Security\Core\Authorization\Strategy\DecideAffirmativeStrategy;
15+
use Symfony\Component\Security\Core\Authorization\Strategy\DecideConsensusStrategy;
16+
use Symfony\Component\Security\Core\Authorization\Strategy\DecideHighestNotAbstainedVoterStrategy;
17+
use Symfony\Component\Security\Core\Authorization\Strategy\DecideUnanimousStrategy;
1418
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
1519
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
1620

@@ -25,8 +29,10 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
2529
const STRATEGY_AFFIRMATIVE = 'affirmative';
2630
const STRATEGY_CONSENSUS = 'consensus';
2731
const STRATEGY_UNANIMOUS = 'unanimous';
32+
const STRATEGY_HIGHEST_NOT_ABSTAINED = 'highest';
2833

2934
private $voters;
35+
private $strategies;
3036
private $strategy;
3137
private $allowIfAllAbstainDecisions;
3238
private $allowIfEqualGrantedDeniedDecisions;
@@ -43,13 +49,9 @@ class AccessDecisionManager implements AccessDecisionManagerInterface
4349
*/
4450
public function __construct(array $voters = array(), $strategy = self::STRATEGY_AFFIRMATIVE, $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true)
4551
{
46-
$strategyMethod = 'decide'.ucfirst($strategy);
47-
if (!is_callable(array($this, $strategyMethod))) {
48-
throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy));
49-
}
50-
52+
$this->strategies = array();
5153
$this->voters = $voters;
52-
$this->strategy = $strategyMethod;
54+
$this->strategy = $strategy;
5355
$this->allowIfAllAbstainDecisions = (bool) $allowIfAllAbstainDecisions;
5456
$this->allowIfEqualGrantedDeniedDecisions = (bool) $allowIfEqualGrantedDeniedDecisions;
5557
}
@@ -65,155 +67,74 @@ public function setVoters(array $voters)
6567
}
6668

6769
/**
68-
* {@inheritdoc}
70+
* @param mixed $strategies
6971
*/
70-
public function decide(TokenInterface $token, array $attributes, $object = null)
72+
public function addStrategy($name,$strategy)
7173
{
72-
return $this->{$this->strategy}($token, $attributes, $object);
74+
$this->strategies[$name] = $strategy;
7375
}
7476

75-
/**
76-
* {@inheritdoc}
77-
*/
78-
public function supportsAttribute($attribute)
77+
private function getStrategy($strategyName)
7978
{
80-
foreach ($this->voters as $voter) {
81-
if ($voter->supportsAttribute($attribute)) {
82-
return true;
79+
if(!array_key_exists($strategyName,$this->strategies))
80+
{
81+
switch($strategyName){
82+
case self::STRATEGY_UNANIMOUS:
83+
return new DecideUnanimousStrategy();
84+
case self::STRATEGY_CONSENSUS:
85+
return new DecideConsensusStrategy();
86+
case self::STRATEGY_AFFIRMATIVE:
87+
return new DecideAffirmativeStrategy();
88+
case self::STRATEGY_HIGHEST_NOT_ABSTAINED:
89+
return new DecideHighestNotAbstainedVoterStrategy();
90+
default:
91+
break;
8392
}
93+
} elseif($this->strategies[$strategyName] instanceof AccessDecisionStrategyInterface) {
94+
return $this->strategies[$strategyName];
8495
}
8596

86-
return false;
97+
throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategyName));
8798
}
8899

89100
/**
90101
* {@inheritdoc}
91102
*/
92-
public function supportsClass($class)
103+
public function decide(TokenInterface $token, array $attributes, $object = null)
93104
{
94-
foreach ($this->voters as $voter) {
95-
if ($voter->supportsClass($class)) {
96-
return true;
97-
}
98-
}
105+
$strategy = $this->getStrategy($this->strategy);
106+
$strategy->setVoters($this->voters);
107+
$strategy->setAllowIfAllAbstainDecisions($this->allowIfAllAbstainDecisions);
108+
$strategy->setAllowIfEqualGrantedDeniedDecisions($this->allowIfEqualGrantedDeniedDecisions);
99109

100-
return false;
110+
return $strategy->decide($token, $attributes, $object);
101111
}
102112

103113
/**
104-
* Grants access if any voter returns an affirmative response.
105-
*
106-
* If all voters abstained from voting, the decision will be based on the
107-
* allowIfAllAbstainDecisions property value (defaults to false).
114+
* {@inheritdoc}
108115
*/
109-
private function decideAffirmative(TokenInterface $token, array $attributes, $object = null)
116+
public function supportsAttribute($attribute)
110117
{
111-
$deny = 0;
112118
foreach ($this->voters as $voter) {
113-
$result = $voter->vote($token, $object, $attributes);
114-
switch ($result) {
115-
case VoterInterface::ACCESS_GRANTED:
116-
return true;
117-
118-
case VoterInterface::ACCESS_DENIED:
119-
++$deny;
120-
121-
break;
122-
123-
default:
124-
break;
119+
if ($voter->supportsAttribute($attribute)) {
120+
return true;
125121
}
126122
}
127123

128-
if ($deny > 0) {
129-
return false;
130-
}
131-
132-
return $this->allowIfAllAbstainDecisions;
124+
return false;
133125
}
134126

135127
/**
136-
* Grants access if there is consensus of granted against denied responses.
137-
*
138-
* Consensus means majority-rule (ignoring abstains) rather than unanimous
139-
* agreement (ignoring abstains). If you require unanimity, see
140-
* UnanimousBased.
141-
*
142-
* If there were an equal number of grant and deny votes, the decision will
143-
* be based on the allowIfEqualGrantedDeniedDecisions property value
144-
* (defaults to true).
145-
*
146-
* If all voters abstained from voting, the decision will be based on the
147-
* allowIfAllAbstainDecisions property value (defaults to false).
128+
* {@inheritdoc}
148129
*/
149-
private function decideConsensus(TokenInterface $token, array $attributes, $object = null)
130+
public function supportsClass($class)
150131
{
151-
$grant = 0;
152-
$deny = 0;
153132
foreach ($this->voters as $voter) {
154-
$result = $voter->vote($token, $object, $attributes);
155-
156-
switch ($result) {
157-
case VoterInterface::ACCESS_GRANTED:
158-
++$grant;
159-
160-
break;
161-
162-
case VoterInterface::ACCESS_DENIED:
163-
++$deny;
164-
165-
break;
166-
}
167-
}
168-
169-
if ($grant > $deny) {
170-
return true;
171-
}
172-
173-
if ($deny > $grant) {
174-
return false;
175-
}
176-
177-
if ($grant > 0) {
178-
return $this->allowIfEqualGrantedDeniedDecisions;
179-
}
180-
181-
return $this->allowIfAllAbstainDecisions;
182-
}
183-
184-
/**
185-
* Grants access if only grant (or abstain) votes were received.
186-
*
187-
* If all voters abstained from voting, the decision will be based on the
188-
* allowIfAllAbstainDecisions property value (defaults to false).
189-
*/
190-
private function decideUnanimous(TokenInterface $token, array $attributes, $object = null)
191-
{
192-
$grant = 0;
193-
foreach ($attributes as $attribute) {
194-
foreach ($this->voters as $voter) {
195-
$result = $voter->vote($token, $object, array($attribute));
196-
197-
switch ($result) {
198-
case VoterInterface::ACCESS_GRANTED:
199-
++$grant;
200-
201-
break;
202-
203-
case VoterInterface::ACCESS_DENIED:
204-
return false;
205-
206-
default:
207-
break;
208-
}
133+
if ($voter->supportsClass($class)) {
134+
return true;
209135
}
210136
}
211137

212-
// no deny votes
213-
if ($grant > 0) {
214-
return true;
215-
}
216-
217-
return $this->allowIfAllAbstainDecisions;
138+
return false;
218139
}
219140
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Core\Authorization;
4+
5+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
6+
7+
/**
8+
* AccessDecisionStrategyInterface contains the strategy to make access decissions.
9+
*
10+
* @author Fabien Potencier <fabien@symfony.com>
11+
*/
12+
interface AccessDecisionStrategyInterface
13+
{
14+
/**
15+
* Configures the voters.
16+
*
17+
* @param VoterInterface[] $voters An array of VoterInterface instances
18+
*/
19+
public function setVoters(array $voters);
20+
21+
/**
22+
* Set whether to grant access if all voters abstained or not.
23+
*
24+
* @param bool $allowIfAllAbstainDecisions
25+
*/
26+
public function setAllowIfAllAbstainDecisions($allowIfAllAbstainDecisions);
27+
28+
/**
29+
* Set whether to grant access if result are equals.
30+
*
31+
* @param bool $allowIfEqualGrantedDeniedDecisions
32+
*/
33+
public function setAllowIfEqualGrantedDeniedDecisions($allowIfEqualGrantedDeniedDecisions);
34+
35+
/**
36+
* Decides whether the access is possible or not.
37+
*
38+
* @param TokenInterface $token
39+
* @param array $attributes
40+
* @param null $object
41+
*
42+
* @return true if this decision strategy decides that the access can be made
43+
*/
44+
public function decide(TokenInterface $token, array $attributes, $object = null);
45+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Core\Authorization\Strategy;
4+
5+
abstract class AbstractDecideStrategy
6+
{
7+
protected $voters;
8+
9+
protected $allowIfAllAbstainDecisions;
10+
11+
protected $allowIfEqualGrantedDeniedDecisions;
12+
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function setVoters(array $voters)
17+
{
18+
$this->voters = $voters;
19+
}
20+
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public function setAllowIfAllAbstainDecisions($allowIfAllAbstainDecisions)
25+
{
26+
$this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions;
27+
}
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function setAllowIfEqualGrantedDeniedDecisions($allowIfEqualGrantedDeniedDecisions)
33+
{
34+
$this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions;
35+
}
36+
}

0 commit comments

Comments
 (0)
0