src/OnlineConsultation/Application/Security/OnlineConsultationVoter.php line 37

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\OnlineConsultation\Application\Security;
  12. use App\Common\Model\Calendar\CalendarInterface;
  13. use App\Common\Model\Core\AccountInterface;
  14. use App\Common\Model\Core\UserInterface;
  15. use App\Common\Model\OnlineConsultation\OnlineConsultationInterface;
  16. use App\Common\Model\Payment\FreePaymentTokenInterface;
  17. use App\Common\Model\Service\ServiceInstanceInterface;
  18. use App\Common\Repository\OnlineConsultation\OnlineConsultationRepositoryInterface;
  19. use App\Common\Repository\Payment\FreePaymentTokenRepositoryInterface;
  20. use App\Common\Security\AccountRole;
  21. use Carbon\Carbon;
  22. use LogicException;
  23. use Symfony\Component\HttpFoundation\Request;
  24. use Symfony\Component\HttpFoundation\RequestStack;
  25. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  26. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  27. use Symfony\Component\Security\Core\Security;
  28. /**
  29.  * Class OnlineConsultationVoter
  30.  *
  31.  * @author  Mateusz Korkosz <korkosz.mateusz@gmail.com>
  32.  */
  33. class OnlineConsultationVoter extends Voter
  34. {
  35.     public const REGISTER_ONLINE_CONSULTATION 'register_online_consultation';
  36.     public const GET_CONSULTATION_DATA 'get_consultation_data';
  37.     public const CANCEL_ONLINE_CONSULTATION 'cancel_online_consultation';
  38.     public const START_ONLINE_CONSULTATION 'start_online_consultation';
  39.     public const FINISH_ONLINE_CONSULTATION 'finish_online_consultation';
  40.     public const RESCHEDULE_ONLINE_CONSULTATION 'reschedule_online_consultation';
  41.     public const UPDATE_ONLINE_CONSULTATION 'update_online_consultation';
  42.     public const ASSIGN_ONLINE_CONSULTATION 'assign_online_consultation';
  43.     public const VIEW_ONLINE_CONSULTATION 'view_online_consultation';
  44.     public const SEND_ONLINE_CONSULTATION_NOTIFICATION 'send_online_consultation_notification';
  45.     public const REGISTER_FREE_ONLINE_CONSULTATION 'register_free_online_consultation';
  46.     /** @var Security */
  47.     private Security $security;
  48.     /** @var RequestStack */
  49.     private RequestStack $requestStack;
  50.     /** @var FreePaymentTokenRepositoryInterface */
  51.     private FreePaymentTokenRepositoryInterface $freePaymentTokenRepository;
  52.     /**
  53.      * OnlineConsultationVoter constructor.
  54.      *
  55.      * @param Security                            $security
  56.      * @param RequestStack                        $requestStack
  57.      * @param FreePaymentTokenRepositoryInterface $freePaymentTokenRepository
  58.      */
  59.     public function __construct(Security $securityRequestStack $requestStackFreePaymentTokenRepositoryInterface $freePaymentTokenRepository)
  60.     {
  61.         $this->security $security;
  62.         $this->requestStack $requestStack;
  63.         $this->freePaymentTokenRepository $freePaymentTokenRepository;
  64.     }
  65.     /**
  66.      * @param string $attribute
  67.      * @param mixed  $subject
  68.      *
  69.      * @return bool
  70.      */
  71.     protected function supports($attribute$subject): bool
  72.     {
  73.         if (!$subject && (self::REGISTER_ONLINE_CONSULTATION === $attribute)) {
  74.             return true;
  75.         }
  76.         if (!in_array(
  77.             $attribute,
  78.             [
  79.                 self::REGISTER_ONLINE_CONSULTATION,
  80.                 self::GET_CONSULTATION_DATA,
  81.                 self::REGISTER_FREE_ONLINE_CONSULTATION,
  82.                 self::CANCEL_ONLINE_CONSULTATION,
  83.                 self::START_ONLINE_CONSULTATION,
  84.                 self::FINISH_ONLINE_CONSULTATION,
  85.                 self::RESCHEDULE_ONLINE_CONSULTATION,
  86.                 self::UPDATE_ONLINE_CONSULTATION,
  87.                 self::VIEW_ONLINE_CONSULTATION,
  88.                 self::ASSIGN_ONLINE_CONSULTATION,
  89.             ],
  90.         )) {
  91.             return false;
  92.         }
  93.         return $subject instanceof OnlineConsultationInterface || $subject instanceof ServiceInstanceInterface;
  94.     }
  95.     /**
  96.      * @inheritDoc
  97.      */
  98.     protected function voteOnAttribute(mixed $attributemixed $subjectTokenInterface $token): bool
  99.     {
  100.         $user   $token->getUser();
  101.         $isAnon false;
  102.         if (!$user instanceof UserInterface) {
  103.             $isAnon true;
  104.             $user   null;
  105.         }
  106.         switch ($attribute) {
  107.             case self::REGISTER_ONLINE_CONSULTATION:
  108.                 return $this->canRegisterOnlineConsultation($subject$user);
  109.             case self::REGISTER_FREE_ONLINE_CONSULTATION:
  110.                 return $this->canRegisterFreeOnlineConsultation($subject);
  111.             case self::GET_CONSULTATION_DATA:
  112.                 return $this->canGetConsultationData($subject$user);
  113.             case self::CANCEL_ONLINE_CONSULTATION:
  114.                 return $this->canCancelOnlineConsultation($subject$user);
  115.             case self::START_ONLINE_CONSULTATION:
  116.                 return $this->canStartOnlineConsultation($subject$user);
  117.             case self::FINISH_ONLINE_CONSULTATION:
  118.                 return $this->canFinishOnlineConsultation($subject$isAnon);
  119.             case self::RESCHEDULE_ONLINE_CONSULTATION:
  120.                 return $this->canRescheduleOnlineConsultation($subject$user);
  121.             case self::UPDATE_ONLINE_CONSULTATION:
  122.                 return $this->canUpdateOnlineConsultation($subject$user$isAnon);
  123.             case self::ASSIGN_ONLINE_CONSULTATION:
  124.                 return $this->canAssignOnlineConsultation($subject);
  125.             case self::VIEW_ONLINE_CONSULTATION:
  126.                 return $this->canViewOnlineConsultation($subject$user);
  127.             default:
  128.                 throw new LogicException('This code should not be reached!');
  129.         }
  130.     }
  131.     /**
  132.      * @param OnlineConsultationInterface $onlineConsultation
  133.      *
  134.      * @return bool
  135.      */
  136.     private function canRegisterFreeOnlineConsultation(OnlineConsultationInterface $onlineConsultation): bool
  137.     {
  138.         if ($this->security->isGranted(AccountRole::ADMIN)) {
  139.             return true;
  140.         }
  141.         $calendar $onlineConsultation->getCalendar();
  142.         if (!$calendar instanceof CalendarInterface) {
  143.             return false;
  144.         }
  145.         $requestStack $this->requestStack->getMainRequest();
  146.         if (!$requestStack instanceof Request) {
  147.             return false;
  148.         }
  149.         $tokenId $requestStack->headers->get('X-Payment-Token');
  150.         if (!$tokenId) {
  151.             return false;
  152.         }
  153.         $token $this->freePaymentTokenRepository->find($tokenId);
  154.         if (!$token instanceof FreePaymentTokenInterface) {
  155.             return false;
  156.         }
  157.         if ($calendar->getId()->equals($token->getSubjectId())) {
  158.             return true;
  159.         }
  160.         return false;
  161.     }
  162.     /**
  163.      * @param ServiceInstanceInterface $serviceInstance
  164.      * @param UserInterface            $user
  165.      *
  166.      * @return bool
  167.      */
  168.     private function canRegisterOnlineConsultation(ServiceInstanceInterface $serviceInstanceUserInterface $user): bool
  169.     {
  170.         $serviceDefinitions $serviceInstance->getServiceDefinition();
  171.         if ($serviceDefinitions) {
  172.             if (!$serviceDefinitions->getConsultationCount()) {
  173.                 return false;
  174.             }
  175.         }
  176.         if ($serviceInstance->isDraft() || $serviceInstance->isUsed()) {
  177.             return false;
  178.         }
  179.         if ($this->security->isGranted(AccountRole::ADMIN)) {
  180.             return true;
  181.         }
  182.         if ($serviceInstance->accountIsOwner($user->getAccount())) {
  183.             return true;
  184.         }
  185.         return false;
  186.     }
  187.     /**
  188.      * @param OnlineConsultationInterface $onlineConsultation
  189.      * @param UserInterface|null          $user
  190.      * @param bool                        $isAnon
  191.      *
  192.      * @return bool
  193.      */
  194.     private function canUpdateOnlineConsultation(
  195.         OnlineConsultationInterface $onlineConsultation,
  196.         ?UserInterface              $user,
  197.         bool                        $isAnon
  198.     ): bool {
  199.         if ($onlineConsultation->getPaidAt()) {
  200.             return false;
  201.         }
  202.         if ($isAnon) {
  203.             return $this->hasAccessAsAnonymousUser($onlineConsultation);
  204.         }
  205.         $account $user->getAccount();
  206.         return $onlineConsultation->isOwner($account) || $this->security->isGranted(AccountRole::ADMIN);
  207.     }
  208.     /**
  209.      * @param OnlineConsultationInterface $consultation
  210.      *
  211.      * @return bool
  212.      */
  213.     private function canAssignOnlineConsultation(OnlineConsultationInterface $consultation): bool
  214.     {
  215.         if ($this->hasAccessAsAnonymousUser($consultation)) {
  216.             return true;
  217.         }
  218.         return $this->security->isGranted(AccountRole::ADMIN);
  219.     }
  220.     /**
  221.      * @param OnlineConsultationInterface $onlineConsultation
  222.      * @param UserInterface|null          $user
  223.      *
  224.      * @return bool
  225.      */
  226.     private function canGetConsultationData(OnlineConsultationInterface $onlineConsultation, ?UserInterface $user): bool
  227.     {
  228.         if ($user && $onlineConsultation->isOwner($user->getAccount())) {
  229.             return true;
  230.         }
  231.         return $this->isAdminOrParticipant($onlineConsultation$user);
  232.     }
  233.     /**
  234.      * @param OnlineConsultationInterface $onlineConsultation
  235.      * @param UserInterface|null          $user
  236.      *
  237.      * @return bool
  238.      */
  239.     private function canCancelOnlineConsultation(
  240.         OnlineConsultationInterface $onlineConsultation,
  241.         ?UserInterface              $user
  242.     ): bool {
  243.         if (!$user) {
  244.             return false;
  245.         }
  246.         if ($this->security->isGranted(AccountRole::ADMIN)) {
  247.             return true;
  248.         }
  249.         if ($onlineConsultation->getCancelledBy()) {
  250.             return false;
  251.         }
  252.         $now Carbon::now();
  253.         if ($now->greaterThan($onlineConsultation->getStartsAt())) {
  254.             return false;
  255.         }
  256.         return $onlineConsultation->isParticipant($user->getAccount());
  257.     }
  258.     /**
  259.      * @param OnlineConsultationInterface $onlineConsultation
  260.      * @param bool                        $isAnon
  261.      *
  262.      * @return bool
  263.      */
  264.     private function canFinishOnlineConsultation(OnlineConsultationInterface $onlineConsultationbool $isAnon): bool
  265.     {
  266.         return true;
  267.         if (!$isAnon && $this->security->isGranted(AccountRole::ADMIN)) {// @phpstan-ignore-line
  268.             return true;
  269.         }
  270.         return $this->checkSecret($onlineConsultation);
  271.     }
  272.     /**
  273.      * @param OnlineConsultationInterface $onlineConsultation
  274.      * @param UserInterface|null          $user
  275.      *
  276.      * @return bool
  277.      */
  278.     private function canRescheduleOnlineConsultation(
  279.         OnlineConsultationInterface $onlineConsultation,
  280.         ?UserInterface              $user
  281.     ): bool {
  282.         $dayBefore             Carbon::now();
  283.         $consultationStartDate $onlineConsultation->getStartsAt();
  284.         if ($dayBefore->greaterThan($consultationStartDate->subDay())) {
  285.             return false;
  286.         }
  287.         if ($user && $onlineConsultation->isOwner($user->getAccount())) {
  288.             return true;
  289.         }
  290.         return $this->isAdminOrParticipant($onlineConsultation$user);
  291.     }
  292.     /**
  293.      * @param OnlineConsultationInterface $onlineConsultation
  294.      * @param UserInterface|null          $user
  295.      *
  296.      * @return bool
  297.      */
  298.     private function canViewOnlineConsultation(
  299.         OnlineConsultationInterface $onlineConsultation,
  300.         ?UserInterface              $user
  301.     ): bool {
  302.         if (!$user) {
  303.             return $this->hasAccessAsAnonymousUser($onlineConsultation);
  304.         }
  305.         if ($onlineConsultation->isOwner($user->getAccount())) {
  306.             return true;
  307.         }
  308.         if ($user->getAccount() === $onlineConsultation->getCreatedBy()) {
  309.             return true;
  310.         }
  311.         return $this->isAdminOrParticipant($onlineConsultation$user);
  312.     }
  313.     /**
  314.      * @param OnlineConsultationInterface $subject
  315.      * @param UserInterface|null          $user
  316.      *
  317.      * @return bool
  318.      */
  319.     private function canStartOnlineConsultation(OnlineConsultationInterface $subject, ?UserInterface $user): bool
  320.     {
  321.         return true;
  322.         return $this->isAdminOrParticipant($subject$user); // @phpstan-ignore-line
  323.     }
  324.     /**
  325.      * @param OnlineConsultationInterface $onlineConsultation
  326.      * @param UserInterface|null          $user
  327.      *
  328.      * @return bool
  329.      */
  330.     private function isAdminOrParticipant(OnlineConsultationInterface $onlineConsultation, ?UserInterface $user): bool
  331.     {
  332.         if (!$user) {
  333.             return false;
  334.         }
  335.         if ($this->security->isGranted(AccountRole::ADMIN)) {
  336.             return true;
  337.         }
  338.         return $onlineConsultation->isParticipant($user->getAccount());
  339.     }
  340.     /**
  341.      * @param OnlineConsultationInterface $consultation
  342.      *
  343.      * @return bool
  344.      */
  345.     private function hasAccessAsAnonymousUser(OnlineConsultationInterface $consultation): bool
  346.     {
  347.         $request $this->requestStack->getMasterRequest();
  348.         $secret  $request->headers->get('X-Online-Consultation-Secret');
  349.         if (!$secret) {
  350.             return false;
  351.         }
  352.         if ($consultation->getSecret() && $secret === $consultation->getSecret()) {
  353.             return true;
  354.         }
  355.         foreach ($consultation->getParticipants() as $participant) {
  356.             if ($participant->getAccessSecret() === $secret) {
  357.                 return true;
  358.             }
  359.         }
  360.         return false;
  361.     }
  362.     /**
  363.      * @param OnlineConsultationInterface $consultation
  364.      *
  365.      * @return bool
  366.      */
  367.     private function checkSecret(OnlineConsultationInterface $consultation): bool// @phpstan-ignore-line
  368.     {
  369.         $request $this->requestStack->getMasterRequest();
  370.         $secret  $request->headers->get('X-Online-Consultation-Secret');
  371.         if (!$secret) {
  372.             return false;
  373.         }
  374.         foreach ($consultation->getParticipants() as $participant) {
  375.             $account $participant->getAccount();
  376.             if (!$account instanceof AccountInterface) {
  377.                 continue;
  378.             }
  379.             if ($participant->getAccessSecret() === $secret) {
  380.                 return true;
  381.             }
  382.         }
  383.         return false;
  384.     }
  385. }