src/Platform/SecurityBundle/Security/PlatformVoter.php line 140

Open in your IDE?
  1. <?php
  2. namespace Platform\SecurityBundle\Security;
  3. use App\Model\Cacher;
  4. use App\Security\Core\Authorization\Voter\TesterVoterInterface;
  5. use Cms\FrontendBundle\Service\ResolverManager;
  6. use Cms\ModuleBundle\Service\ModuleManager;
  7. use Doctrine\Common\Util\ClassUtils;
  8. use Platform\SecurityBundle\Entity\Identity\Account;
  9. use Platform\SecurityBundle\Model\PlatformSubject;
  10. use Platform\SecurityBundle\Service\Sentry;
  11. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  12. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  13. use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
  14. /**
  15.  * Class PlatformVoter
  16.  * @package Platform\SecurityBundle\Security
  17.  */
  18. abstract class PlatformVoter implements VoterInterfaceTesterVoterInterface
  19. {
  20.     /**
  21.      * @var array
  22.      */
  23.     public static array $votes = [];
  24.     /**
  25.      * @var array
  26.      */
  27.     public static array $polls = [];
  28.     /**
  29.      * @var Sentry
  30.      */
  31.     protected Sentry $sentry;
  32.     /**
  33.      * @var Cacher|null
  34.      */
  35.     protected static ?Cacher $cache null;
  36.     /**
  37.      * @var ResolverManager
  38.      */
  39.     protected ResolverManager $rm;
  40.     /**
  41.      * @var ParameterBagInterface
  42.      */
  43.     protected ParameterBagInterface $params;
  44.     /**
  45.      * @var ModuleManager
  46.      */
  47.     protected ModuleManager $moduleManager;
  48.     /**
  49.      * @param Sentry $sentry
  50.      * @param ResolverManager $rm
  51.      * @param ParameterBagInterface $params
  52.      * @param ModuleManager $moduleManager
  53.      */
  54.     public function __construct(
  55.         Sentry $sentry,
  56.         ResolverManager $rm,
  57.         ParameterBagInterface $params,
  58.         ModuleManager $moduleManager
  59.     )
  60.     {
  61.         if ( ! self::$cache) {
  62.             self::$cache = new Cacher();
  63.         }
  64.         $this->sentry $sentry;
  65.         $this->rm $rm;
  66.         $this->params $params;
  67.         $this->moduleManager $moduleManager;
  68.     }
  69.     /**
  70.      * {@inheritdoc}
  71.      */
  72.     public function vote(TokenInterface $token$subject, array $attributes): int
  73.     {
  74.         // we are only interested if the user object is an account type
  75.         $account $token->getUser();
  76.         if ( ! $account instanceof Account) {
  77.             return VoterInterface::ACCESS_ABSTAIN;
  78.         }
  79.         // normalize the subject
  80.         $subject PlatformSubject::factory($subject);
  81.         // clean up the atttributes
  82.         $attrs $this->attributes($attributes$account$subject);
  83.         // DEBUGGING
  84.         $timestamp microtime(true);
  85.         // determine if we actually checked anything
  86.         // may affect the final result
  87.         $checking 0;
  88.         // loop over the attributes that we have to check
  89.         foreach ($attrs as $attr) {
  90.             // generate a cache key
  91.             $lookup implode('|', [
  92.                 static::class,
  93.                 $account->getId(),
  94.                 $attr,
  95.                 $subject->getContext() ? $subject->getContext()->getId() : null,
  96.             ]);
  97.             // attempt to grab a cached value
  98.             $cached self::$cache->get(__FUNCTION__$lookup);
  99.             // a false return means the support check failed last time through
  100.             // continue on in that case as we don't have anything to do
  101.             // also, see if we need to skip the check
  102.             // flag in the cache if we do and haven't cached yet
  103.             if ($cached === false || ! $this->isSupported($account$attr$subject)) {
  104.                 if ($cached !== false) {
  105.                     self::$cache->set(__FUNCTION__$lookupfalse);
  106.                 }
  107.                 continue;
  108.             }
  109.             // we are running some kind of check
  110.             $checking++;
  111.             // DEBUGGING
  112.             $polled microtime(true);
  113.             // if a miss, try to grab it
  114.             $result $cached;
  115.             if ($result === null) {
  116.                 // try checking
  117.                 $result $this->poll($account$attr$subject);
  118.                 // set in the cache
  119.                 self::$cache->set(__FUNCTION__$lookup$result);
  120.             }
  121.             // DEBUGGING
  122.             self::$polls[] = [
  123.                 'time' => $polled,
  124.                 'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::poll',
  125.                 'cached' => is_int($cached),
  126.                 'account' => $account->getEmail(),
  127.                 'permission' => $attr,
  128.                 'context' => $subject->getContext() ? [
  129.                     ClassUtils::getClass($subject->getContext()),
  130.                     $subject->getContext()->getId(),
  131.                     $subject->getContext()->getName(),
  132.                 ] : null,
  133.                 'thing' => $subject->getThing() ? [
  134.                     ClassUtils::getClass($subject->getThing()),
  135.                     $subject->getThing()->getId(),
  136.                 ] : null,
  137.                 'access' => $result,
  138.             ];
  139.             // if we're not abstaining, we want to return immediately
  140.             if ($result !== VoterInterface::ACCESS_ABSTAIN) {
  141.                 // DEBUGGING
  142.                 self::$votes[] = [
  143.                     'time' => $timestamp,
  144.                     'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::' __FUNCTION__,
  145.                     'cached' => false,
  146.                     'account' => $account->getEmail(),
  147.                     'permission' => $attrs,
  148.                     'context' => $subject->getContext() ? [
  149.                         ClassUtils::getClass($subject->getContext()),
  150.                         $subject->getContext()->getId(),
  151.                         $subject->getContext()->getName(),
  152.                     ] : null,
  153.                     'thing' => $subject->getThing() ? [
  154.                         ClassUtils::getClass($subject->getThing()),
  155.                         $subject->getThing()->getId(),
  156.                     ] : null,
  157.                     'access' => $result,
  158.                 ];
  159.                 return $result;
  160.             }
  161.         }
  162.         // if all polls didn't abstain by this point, we should deny access as no other voters should need to trigger (all perms covered by this voter)
  163.         $result = ($checking === count($attrs))
  164.             ? VoterInterface::ACCESS_DENIED
  165.             VoterInterface::ACCESS_ABSTAIN;
  166.         // DEBUGGING
  167.         if ($checking) {
  168.             self::$votes[] = [
  169.                 'time' => $timestamp,
  170.                 'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::' __FUNCTION__,
  171.                 'cached' => false,
  172.                 'account' => $account->getEmail(),
  173.                 'permission' => $attrs,
  174.                 'context' => $subject->getContext() ? [
  175.                     ClassUtils::getClass($subject->getContext()),
  176.                     $subject->getContext()->getId(),
  177.                     $subject->getContext()->getName(),
  178.                 ] : null,
  179.                 'thing' => $subject->getThing() ? [
  180.                     ClassUtils::getClass($subject->getThing()),
  181.                     $subject->getThing()->getId(),
  182.                 ] : null,
  183.                 'access' => $result,
  184.             ];
  185.         }
  186.         // abstain by default
  187.         return $result;
  188.     }
  189.     /**
  190.      * {@inheritdoc}
  191.      */
  192.     public function test(TokenInterface $token, array $attributes): int
  193.     {
  194.         // we are only interested if the user object is an account type
  195.         $account $token->getUser();
  196.         if ( ! $account instanceof Account) {
  197.             return VoterInterface::ACCESS_ABSTAIN;
  198.         }
  199.         // clean up the atttributes
  200.         $attrs $this->attributes($attributes$account);
  201.         // DEBUGGING
  202.         $timestamp microtime(true);
  203.         // determine if we actually checked anything
  204.         // may affect the final result
  205.         $checking 0;
  206.         // loop over the attributes that we have to check
  207.         foreach ($attrs as $attr) {
  208.             // if we have an alias still, that may be a problem
  209.             if ($this->sentry->isAlias($attr)) {
  210.                 // if we are debugging, this is expected, so just skip it
  211.                 if ($this->params->get('kernel.debug')) {
  212.                     continue;
  213.                 }
  214.                 // not debugging, so unexpected...
  215.                 throw new \LogicException();
  216.             }
  217.             // generate a cache key
  218.             $lookup implode('|', [
  219.                 static::class,
  220.                 $account->getId(),
  221.                 $attr,
  222.             ]);
  223.             // attempt to grab a cached value
  224.             $cached self::$cache->get(__FUNCTION__$lookup);
  225.             // a false return means the support check failed last time through
  226.             // continue on in that case as we don't have anything to do
  227.             // also, see if we need to skip the check
  228.             // flag in the cache if we do and haven't cached yet
  229.             if ($cached === false || ! $this->isSupported($account$attr)) {
  230.                 if ($cached !== false) {
  231.                     self::$cache->set(__FUNCTION__$lookupfalse);
  232.                 }
  233.                 continue;
  234.             }
  235.             // we are running some kind of check
  236.             $checking++;
  237.             // DEBUGGING
  238.             $polled microtime(true);
  239.             // if a miss, try to grab it
  240.             $result $cached;
  241.             if ($result === null) {
  242.                 // try checking
  243.                 $result $this->try($account$attr);
  244.                 // set in the cache
  245.                 self::$cache->set(__FUNCTION__$lookup$result);
  246.             }
  247.             // DEBUGGING
  248.             self::$polls[] = [
  249.                 'time' => $polled,
  250.                 'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::try',
  251.                 'cached' => is_int($cached),
  252.                 'account' => $account->getEmail(),
  253.                 'permission' => $attr,
  254.                 'context' => null,
  255.                 'thing' => null,
  256.                 'access' => $result,
  257.             ];
  258.             // if we're not abstaining, we want to return immediately
  259.             if ($result !== VoterInterface::ACCESS_ABSTAIN) {
  260.                 // DEBUGGING
  261.                 self::$votes[] = [
  262.                     'time' => $timestamp,
  263.                     'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::' __FUNCTION__,
  264.                     'cached' => false,
  265.                     'account' => $account->getEmail(),
  266.                     'permission' => $attrs,
  267.                     'context' => null,
  268.                     'thing' => null,
  269.                     'access' => $result,
  270.                 ];
  271.                 return $result;
  272.             }
  273.         }
  274.         // if all polls didn't abstain by this point, we should deny access as no other voters should need to trigger (all perms covered by this voter)
  275.         $result = ($checking === count($attrs))
  276.             ? VoterInterface::ACCESS_DENIED
  277.             VoterInterface::ACCESS_ABSTAIN;
  278.         // DEBUGGING
  279.         if ($checking) {
  280.             self::$votes[] = [
  281.                 'time' => $timestamp,
  282.                 'op' => str_replace(__NAMESPACE__.'\\Voter\\''', static::class) . '::' __FUNCTION__,
  283.                 'cached' => false,
  284.                 'account' => $account->getEmail(),
  285.                 'permission' => $attrs,
  286.                 'context' => null,
  287.                 'thing' => null,
  288.                 'access' => $result,
  289.             ];
  290.         }
  291.         // abstain by default
  292.         return $result;
  293.     }
  294.     /**
  295.      * @param Account $account
  296.      * @param string $permission
  297.      * @param PlatformSubject|null $subject
  298.      * @return int
  299.      */
  300.     abstract protected function poll(
  301.         Account $account,
  302.         string $permission,
  303.         ?PlatformSubject $subject
  304.     ): int;
  305.     /**
  306.      * @param Account $account
  307.      * @param string $permission
  308.      * @return int
  309.      */
  310.     abstract protected function try(
  311.         Account $account,
  312.         string $permission
  313.     ): int;
  314.     /**
  315.      * @param array<string> $attributes
  316.      * @param Account $account
  317.      * @param PlatformSubject|null $subject
  318.      * @return array<string>
  319.      */
  320.     protected function attributes(
  321.         array $attributes,
  322.         Account $account,
  323.         ?PlatformSubject $subject null
  324.     ): array
  325.     {
  326.         return array_unique($attributes);
  327.     }
  328.     /**
  329.      * @param Account $account
  330.      * @param string $attribute
  331.      * @param PlatformSubject|null $subject
  332.      * @return bool
  333.      */
  334.     abstract protected function supports(
  335.         Account $account,
  336.         string $attribute,
  337.         ?PlatformSubject $subject null
  338.     ): bool;
  339.     /**
  340.      * @param Account $account
  341.      * @param string $attribute
  342.      * @param PlatformSubject|null $subject
  343.      * @return bool
  344.      */
  345.     protected function isSupported(
  346.         Account $account,
  347.         string $attribute,
  348.         ?PlatformSubject $subject null
  349.     ): bool
  350.     {
  351.         return (
  352.                 $this->sentry->isPermission($attribute)
  353.                 || $this->sentry->isSpecialPermission($attribute)
  354.                 || $this->sentry->isInternalPermission($attribute)
  355.             )
  356.             && $this->supports($account$attribute$subject)
  357.         ;
  358.     }
  359. }