AccessDecisionManager.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Core\Authorization;
  11. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  12. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  13. use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
  14. /**
  15. * AccessDecisionManager is the base class for all access decision managers
  16. * that use decision voters.
  17. *
  18. * @author Fabien Potencier <fabien@symfony.com>
  19. */
  20. class AccessDecisionManager implements AccessDecisionManagerInterface
  21. {
  22. public const STRATEGY_AFFIRMATIVE = 'affirmative';
  23. public const STRATEGY_CONSENSUS = 'consensus';
  24. public const STRATEGY_UNANIMOUS = 'unanimous';
  25. public const STRATEGY_PRIORITY = 'priority';
  26. private $voters;
  27. private $strategy;
  28. private $allowIfAllAbstainDecisions;
  29. private $allowIfEqualGrantedDeniedDecisions;
  30. /**
  31. * @param iterable|VoterInterface[] $voters An array or an iterator of VoterInterface instances
  32. * @param string $strategy The vote strategy
  33. * @param bool $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not
  34. * @param bool $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals
  35. *
  36. * @throws \InvalidArgumentException
  37. */
  38. public function __construct(iterable $voters = [], string $strategy = self::STRATEGY_AFFIRMATIVE, bool $allowIfAllAbstainDecisions = false, bool $allowIfEqualGrantedDeniedDecisions = true)
  39. {
  40. $strategyMethod = 'decide'.ucfirst($strategy);
  41. if ('' === $strategy || !\is_callable([$this, $strategyMethod])) {
  42. throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy));
  43. }
  44. $this->voters = $voters;
  45. $this->strategy = $strategyMethod;
  46. $this->allowIfAllAbstainDecisions = $allowIfAllAbstainDecisions;
  47. $this->allowIfEqualGrantedDeniedDecisions = $allowIfEqualGrantedDeniedDecisions;
  48. }
  49. /**
  50. * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array
  51. *
  52. * {@inheritdoc}
  53. */
  54. public function decide(TokenInterface $token, array $attributes, $object = null/*, bool $allowMultipleAttributes = false*/)
  55. {
  56. $allowMultipleAttributes = 3 < \func_num_args() && func_get_arg(3);
  57. // Special case for AccessListener, do not remove the right side of the condition before 6.0
  58. if (\count($attributes) > 1 && !$allowMultipleAttributes) {
  59. throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.', __METHOD__));
  60. }
  61. return $this->{$this->strategy}($token, $attributes, $object);
  62. }
  63. /**
  64. * Grants access if any voter returns an affirmative response.
  65. *
  66. * If all voters abstained from voting, the decision will be based on the
  67. * allowIfAllAbstainDecisions property value (defaults to false).
  68. */
  69. private function decideAffirmative(TokenInterface $token, array $attributes, $object = null): bool
  70. {
  71. $deny = 0;
  72. foreach ($this->voters as $voter) {
  73. $result = $voter->vote($token, $object, $attributes);
  74. if (VoterInterface::ACCESS_GRANTED === $result) {
  75. return true;
  76. }
  77. if (VoterInterface::ACCESS_DENIED === $result) {
  78. ++$deny;
  79. }
  80. }
  81. if ($deny > 0) {
  82. return false;
  83. }
  84. return $this->allowIfAllAbstainDecisions;
  85. }
  86. /**
  87. * Grants access if there is consensus of granted against denied responses.
  88. *
  89. * Consensus means majority-rule (ignoring abstains) rather than unanimous
  90. * agreement (ignoring abstains). If you require unanimity, see
  91. * UnanimousBased.
  92. *
  93. * If there were an equal number of grant and deny votes, the decision will
  94. * be based on the allowIfEqualGrantedDeniedDecisions property value
  95. * (defaults to true).
  96. *
  97. * If all voters abstained from voting, the decision will be based on the
  98. * allowIfAllAbstainDecisions property value (defaults to false).
  99. */
  100. private function decideConsensus(TokenInterface $token, array $attributes, $object = null): bool
  101. {
  102. $grant = 0;
  103. $deny = 0;
  104. foreach ($this->voters as $voter) {
  105. $result = $voter->vote($token, $object, $attributes);
  106. if (VoterInterface::ACCESS_GRANTED === $result) {
  107. ++$grant;
  108. } elseif (VoterInterface::ACCESS_DENIED === $result) {
  109. ++$deny;
  110. }
  111. }
  112. if ($grant > $deny) {
  113. return true;
  114. }
  115. if ($deny > $grant) {
  116. return false;
  117. }
  118. if ($grant > 0) {
  119. return $this->allowIfEqualGrantedDeniedDecisions;
  120. }
  121. return $this->allowIfAllAbstainDecisions;
  122. }
  123. /**
  124. * Grants access if only grant (or abstain) votes were received.
  125. *
  126. * If all voters abstained from voting, the decision will be based on the
  127. * allowIfAllAbstainDecisions property value (defaults to false).
  128. */
  129. private function decideUnanimous(TokenInterface $token, array $attributes, $object = null): bool
  130. {
  131. $grant = 0;
  132. foreach ($this->voters as $voter) {
  133. foreach ($attributes as $attribute) {
  134. $result = $voter->vote($token, $object, [$attribute]);
  135. if (VoterInterface::ACCESS_DENIED === $result) {
  136. return false;
  137. }
  138. if (VoterInterface::ACCESS_GRANTED === $result) {
  139. ++$grant;
  140. }
  141. }
  142. }
  143. // no deny votes
  144. if ($grant > 0) {
  145. return true;
  146. }
  147. return $this->allowIfAllAbstainDecisions;
  148. }
  149. /**
  150. * Grant or deny access depending on the first voter that does not abstain.
  151. * The priority of voters can be used to overrule a decision.
  152. *
  153. * If all voters abstained from voting, the decision will be based on the
  154. * allowIfAllAbstainDecisions property value (defaults to false).
  155. */
  156. private function decidePriority(TokenInterface $token, array $attributes, $object = null)
  157. {
  158. foreach ($this->voters as $voter) {
  159. $result = $voter->vote($token, $object, $attributes);
  160. if (VoterInterface::ACCESS_GRANTED === $result) {
  161. return true;
  162. }
  163. if (VoterInterface::ACCESS_DENIED === $result) {
  164. return false;
  165. }
  166. }
  167. return $this->allowIfAllAbstainDecisions;
  168. }
  169. }