vendor/symfony/security-core/Authorization/AccessDecisionManager.php line 113

Open in your IDE?
  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\Strategy\AccessDecisionStrategyInterface;
  13. use Symfony\Component\Security\Core\Authorization\Strategy\AffirmativeStrategy;
  14. use Symfony\Component\Security\Core\Authorization\Strategy\ConsensusStrategy;
  15. use Symfony\Component\Security\Core\Authorization\Strategy\PriorityStrategy;
  16. use Symfony\Component\Security\Core\Authorization\Strategy\UnanimousStrategy;
  17. use Symfony\Component\Security\Core\Authorization\Voter\CacheableVoterInterface;
  18. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  19. use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
  20. /**
  21.  * AccessDecisionManager is the base class for all access decision managers
  22.  * that use decision voters.
  23.  *
  24.  * @author Fabien Potencier <fabien@symfony.com>
  25.  *
  26.  * @final since Symfony 5.4
  27.  */
  28. class AccessDecisionManager implements AccessDecisionManagerInterface
  29. {
  30.     /**
  31.      * @deprecated use {@see AffirmativeStrategy} instead
  32.      */
  33.     public const STRATEGY_AFFIRMATIVE 'affirmative';
  34.     /**
  35.      * @deprecated use {@see ConsensusStrategy} instead
  36.      */
  37.     public const STRATEGY_CONSENSUS 'consensus';
  38.     /**
  39.      * @deprecated use {@see UnanimousStrategy} instead
  40.      */
  41.     public const STRATEGY_UNANIMOUS 'unanimous';
  42.     /**
  43.      * @deprecated use {@see PriorityStrategy} instead
  44.      */
  45.     public const STRATEGY_PRIORITY 'priority';
  46.     private const VALID_VOTES = [
  47.         VoterInterface::ACCESS_GRANTED => true,
  48.         VoterInterface::ACCESS_DENIED => true,
  49.         VoterInterface::ACCESS_ABSTAIN => true,
  50.     ];
  51.     private $voters;
  52.     private $votersCacheAttributes;
  53.     private $votersCacheObject;
  54.     private $strategy;
  55.     /**
  56.      * @param iterable<mixed, VoterInterface>      $voters   An array or an iterator of VoterInterface instances
  57.      * @param AccessDecisionStrategyInterface|null $strategy The vote strategy
  58.      *
  59.      * @throws \InvalidArgumentException
  60.      */
  61.     public function __construct(iterable $voters = [], /* ?AccessDecisionStrategyInterface */ $strategy null)
  62.     {
  63.         $this->voters $voters;
  64.         if (\is_string($strategy)) {
  65.             trigger_deprecation('symfony/security-core''5.4''Passing the access decision strategy as a string is deprecated, pass an instance of "%s" instead.'AccessDecisionStrategyInterface::class);
  66.             $allowIfAllAbstainDecisions <= \func_num_args() && func_get_arg(2);
  67.             $allowIfEqualGrantedDeniedDecisions \func_num_args() || func_get_arg(3);
  68.             $strategy $this->createStrategy($strategy$allowIfAllAbstainDecisions$allowIfEqualGrantedDeniedDecisions);
  69.         } elseif (null !== $strategy && !$strategy instanceof AccessDecisionStrategyInterface) {
  70.             throw new \TypeError(sprintf('"%s": Parameter #2 ($strategy) is expected to be an instance of "%s" or null, "%s" given.'__METHOD__AccessDecisionStrategyInterface::class, get_debug_type($strategy)));
  71.         }
  72.         $this->strategy $strategy ?? new AffirmativeStrategy();
  73.     }
  74.     /**
  75.      * @param bool $allowMultipleAttributes Whether to allow passing multiple values to the $attributes array
  76.      *
  77.      * {@inheritdoc}
  78.      */
  79.     public function decide(TokenInterface $token, array $attributes$object null/* , bool $allowMultipleAttributes = false */)
  80.     {
  81.         $allowMultipleAttributes \func_num_args() && func_get_arg(3);
  82.         // Special case for AccessListener, do not remove the right side of the condition before 6.0
  83.         if (\count($attributes) > && !$allowMultipleAttributes) {
  84.             throw new InvalidArgumentException(sprintf('Passing more than one Security attribute to "%s()" is not supported.'__METHOD__));
  85.         }
  86.         return $this->strategy->decide(
  87.             $this->collectResults($token$attributes$object)
  88.         );
  89.     }
  90.     /**
  91.      * @param mixed $object
  92.      *
  93.      * @return \Traversable<int, int>
  94.      */
  95.     private function collectResults(TokenInterface $token, array $attributes$object): \Traversable
  96.     {
  97.         foreach ($this->getVoters($attributes$object) as $voter) {
  98.             $result $voter->vote($token$object$attributes);
  99.             if (!\is_int($result) || !(self::VALID_VOTES[$result] ?? false)) {
  100.                 trigger_deprecation('symfony/security-core''5.3''Returning "%s" in "%s::vote()" is deprecated, return one of "%s" constants: "ACCESS_GRANTED", "ACCESS_DENIED" or "ACCESS_ABSTAIN".'var_export($resulttrue), get_debug_type($voter), VoterInterface::class);
  101.             }
  102.             yield $result;
  103.         }
  104.     }
  105.     /**
  106.      * @throws \InvalidArgumentException if the $strategy is invalid
  107.      */
  108.     private function createStrategy(string $strategybool $allowIfAllAbstainDecisionsbool $allowIfEqualGrantedDeniedDecisions): AccessDecisionStrategyInterface
  109.     {
  110.         switch ($strategy) {
  111.             case self::STRATEGY_AFFIRMATIVE:
  112.                 return new AffirmativeStrategy($allowIfAllAbstainDecisions);
  113.             case self::STRATEGY_CONSENSUS:
  114.                 return new ConsensusStrategy($allowIfAllAbstainDecisions$allowIfEqualGrantedDeniedDecisions);
  115.             case self::STRATEGY_UNANIMOUS:
  116.                 return new UnanimousStrategy($allowIfAllAbstainDecisions);
  117.             case self::STRATEGY_PRIORITY:
  118.                 return new PriorityStrategy($allowIfAllAbstainDecisions);
  119.         }
  120.         throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.'$strategy));
  121.     }
  122.     /**
  123.      * @return iterable<mixed, VoterInterface>
  124.      */
  125.     private function getVoters(array $attributes$object null): iterable
  126.     {
  127.         $keyAttributes = [];
  128.         foreach ($attributes as $attribute) {
  129.             $keyAttributes[] = \is_string($attribute) ? $attribute null;
  130.         }
  131.         // use `get_class` to handle anonymous classes
  132.         $keyObject \is_object($object) ? \get_class($object) : get_debug_type($object);
  133.         foreach ($this->voters as $key => $voter) {
  134.             if (!$voter instanceof CacheableVoterInterface) {
  135.                 yield $voter;
  136.                 continue;
  137.             }
  138.             $supports true;
  139.             // The voter supports the attributes if it supports at least one attribute of the list
  140.             foreach ($keyAttributes as $keyAttribute) {
  141.                 if (null === $keyAttribute) {
  142.                     $supports true;
  143.                 } elseif (!isset($this->votersCacheAttributes[$keyAttribute][$key])) {
  144.                     $this->votersCacheAttributes[$keyAttribute][$key] = $supports $voter->supportsAttribute($keyAttribute);
  145.                 } else {
  146.                     $supports $this->votersCacheAttributes[$keyAttribute][$key];
  147.                 }
  148.                 if ($supports) {
  149.                     break;
  150.                 }
  151.             }
  152.             if (!$supports) {
  153.                 continue;
  154.             }
  155.             if (!isset($this->votersCacheObject[$keyObject][$key])) {
  156.                 $this->votersCacheObject[$keyObject][$key] = $supports $voter->supportsType($keyObject);
  157.             } else {
  158.                 $supports $this->votersCacheObject[$keyObject][$key];
  159.             }
  160.             if (!$supports) {
  161.                 continue;
  162.             }
  163.             yield $voter;
  164.         }
  165.     }
  166. }