src/Core/Application/Security/AccountVoter.php line 33

Open in your IDE?
  1. <?php
  2. /**
  3.  * This file is part of the educat package.
  4.  *
  5.  * (c) Solvee
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace App\Core\Application\Security;
  12. use App\Common\Model\Core\AccountInterface;
  13. use App\Common\Model\Core\UserInterface;
  14. use App\Common\Security\AccountRole;
  15. use App\Common\Security\ObjectOwnerVoterInterface;
  16. use App\Common\Security\ObjectOwnerVoterTrait;
  17. use App\Core\Domain\Entity\Account;
  18. use App\Core\Domain\Entity\User;
  19. use LogicException;
  20. use Symfony\Component\HttpFoundation\RequestStack;
  21. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  22. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  23. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  24. use Symfony\Component\Security\Core\Security;
  25. /**
  26.  * Class AccountVoter
  27.  *
  28.  * @author  Mateusz Korkosz <korkosz.mateusz@gmail.com>
  29.  */
  30. class AccountVoter extends Voter implements ObjectOwnerVoterInterface
  31. {
  32.     use ObjectOwnerVoterTrait;
  33.     public const EDIT                     'update_account';
  34.     public const DELETE                   'delete_account';
  35.     public const TOGGLE_NOTIFICATIONS     'toggle_notifications';
  36.     public const GENERATE_ONBOARDING_LINK 'generate_onboarding_link';
  37.     private Security     $security;
  38.     private RequestStack $requestStack;
  39.     /**
  40.      * AccountVoter constructor.
  41.      *
  42.      * @param Security     $security
  43.      * @param RequestStack $requestStack
  44.      */
  45.     public function __construct(Security $securityRequestStack $requestStack)
  46.     {
  47.         $this->security     $security;
  48.         $this->requestStack $requestStack;
  49.     }
  50.     /**
  51.      * @param string $attribute
  52.      * @param mixed  $subject
  53.      *
  54.      * @return bool
  55.      */
  56.     protected function supports(mixed $attributemixed $subject)
  57.     {
  58.         if (!in_array(
  59.             $attribute,
  60.             [
  61.                 self::EDIT,
  62.                 self::DELETE,
  63.                 self::TOGGLE_NOTIFICATIONS,
  64.                 self::GENERATE_ONBOARDING_LINK,
  65.                 self::OBJECT_OWNER,
  66.             ],
  67.             true
  68.         )) {
  69.             return false;
  70.         }
  71.         return $subject instanceof Account;
  72.     }
  73.     /**
  74.      * @param string         $attribute
  75.      * @param Account        $subject
  76.      * @param TokenInterface $token
  77.      *
  78.      * @return bool
  79.      */
  80.     protected function voteOnAttribute(mixed $attributemixed $subjectTokenInterface $token)
  81.     {
  82.         $user $token->getUser();
  83.         if (self::TOGGLE_NOTIFICATIONS === $attribute) {
  84.             $nullableUser $user instanceof UserInterface ?
  85.                 $user :
  86.                 null;
  87.             return $this->canToggleNotifications($subject$nullableUser);
  88.         }
  89.         if (!$user instanceof User) {
  90.             return false;
  91.         }
  92.         if (self::GENERATE_ONBOARDING_LINK === $attribute) {
  93.             return $this->canGenerateOnboardingLink($subject$user);
  94.         }
  95.         if (self::EDIT === $attribute) {
  96.             return $this->canEdit($subject$user);
  97.         }
  98.         if (self::DELETE === $attribute) {
  99.             return $this->canDelete($subject$user);
  100.         }
  101.         if (self::OBJECT_OWNER === $attribute) {
  102.             return $this->isObjectOwner($subject$user->getAccount());
  103.         }
  104.         throw new LogicException('This code should not be reached!');
  105.     }
  106.     /**
  107.      * @param AccountInterface $subject
  108.      * @param UserInterface    $user
  109.      *
  110.      * @return bool
  111.      */
  112.     private function canGenerateOnboardingLink(AccountInterface $subjectUserInterface $user): bool
  113.     {
  114.         return $this->isOwner($subject$user);
  115.     }
  116.     /**
  117.      * @param AccountInterface   $subject
  118.      * @param UserInterface|null $user
  119.      *
  120.      * @return bool
  121.      */
  122.     private function canToggleNotifications(AccountInterface $subject, ?UserInterface $user null): bool
  123.     {
  124.         if ($this->security->isGranted(AccountRole::ADMIN)) {
  125.             return true;
  126.         }
  127.         if ($user instanceof UserInterface && $user->getAccount() === $subject) {
  128.             return true;
  129.         }
  130.         $request $this->requestStack->getMasterRequest();
  131.         if (!$request) {
  132.             return false;
  133.         }
  134.         $token $request->headers->get('X-Toggle-Notifications-Token');
  135.         $this->throwOnInvalidToken($token$subject);
  136.         return true;
  137.     }
  138.     /**
  139.      * @param Account $account
  140.      * @param User    $user
  141.      *
  142.      * @return bool
  143.      */
  144.     private function canEdit(Account $accountUser $user): bool
  145.     {
  146.         return $this->isOwner($account$user);
  147.     }
  148.     /**
  149.      * @param Account $account
  150.      * @param User    $user
  151.      *
  152.      * @return bool
  153.      */
  154.     private function canDelete(Account $accountUser $user): bool
  155.     {
  156.         return $this->isOwner($account$user);
  157.     }
  158.     /**
  159.      * @param AccountInterface $account
  160.      * @param UserInterface    $user
  161.      *
  162.      * @return bool
  163.      */
  164.     private function isOwner(AccountInterface $accountUserInterface $user): bool
  165.     {
  166.         $accountUser $account->getUser();
  167.         if (!$accountUser instanceof UserInterface) {
  168.             return false;
  169.         }
  170.         return $user->getId()->equals($accountUser->getId()) || $this->security->isGranted(AccountRole::ADMIN);
  171.     }
  172.     /**
  173.      * @param string|null      $token
  174.      * @param AccountInterface $subject
  175.      */
  176.     private function throwOnInvalidToken(?string $tokenAccountInterface $subject): void
  177.     {
  178.         if (!$token || $subject->getNotificationsToggleToken() !== $token) {
  179.             throw new BadRequestHttpException('Given token is invalid');
  180.         }
  181.     }
  182. }