<?php
/**
* This file is part of the educat package.
*
* (c) Solvee
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\OnlineConsultation\Application\Security;
use App\Common\Model\Calendar\CalendarInterface;
use App\Common\Model\Core\AccountInterface;
use App\Common\Model\Core\UserInterface;
use App\Common\Model\OnlineConsultation\OnlineConsultationInterface;
use App\Common\Model\Payment\FreePaymentTokenInterface;
use App\Common\Model\Service\ServiceInstanceInterface;
use App\Common\Repository\OnlineConsultation\OnlineConsultationRepositoryInterface;
use App\Common\Repository\Payment\FreePaymentTokenRepositoryInterface;
use App\Common\Security\AccountRole;
use Carbon\Carbon;
use LogicException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
/**
* Class OnlineConsultationVoter
*
* @author Mateusz Korkosz <korkosz.mateusz@gmail.com>
*/
class OnlineConsultationVoter extends Voter
{
public const REGISTER_ONLINE_CONSULTATION = 'register_online_consultation';
public const GET_CONSULTATION_DATA = 'get_consultation_data';
public const CANCEL_ONLINE_CONSULTATION = 'cancel_online_consultation';
public const START_ONLINE_CONSULTATION = 'start_online_consultation';
public const FINISH_ONLINE_CONSULTATION = 'finish_online_consultation';
public const RESCHEDULE_ONLINE_CONSULTATION = 'reschedule_online_consultation';
public const UPDATE_ONLINE_CONSULTATION = 'update_online_consultation';
public const ASSIGN_ONLINE_CONSULTATION = 'assign_online_consultation';
public const VIEW_ONLINE_CONSULTATION = 'view_online_consultation';
public const SEND_ONLINE_CONSULTATION_NOTIFICATION = 'send_online_consultation_notification';
public const REGISTER_FREE_ONLINE_CONSULTATION = 'register_free_online_consultation';
/** @var Security */
private Security $security;
/** @var RequestStack */
private RequestStack $requestStack;
/** @var FreePaymentTokenRepositoryInterface */
private FreePaymentTokenRepositoryInterface $freePaymentTokenRepository;
/**
* OnlineConsultationVoter constructor.
*
* @param Security $security
* @param RequestStack $requestStack
* @param FreePaymentTokenRepositoryInterface $freePaymentTokenRepository
*/
public function __construct(Security $security, RequestStack $requestStack, FreePaymentTokenRepositoryInterface $freePaymentTokenRepository)
{
$this->security = $security;
$this->requestStack = $requestStack;
$this->freePaymentTokenRepository = $freePaymentTokenRepository;
}
/**
* @param string $attribute
* @param mixed $subject
*
* @return bool
*/
protected function supports($attribute, $subject): bool
{
if (!$subject && (self::REGISTER_ONLINE_CONSULTATION === $attribute)) {
return true;
}
if (!in_array(
$attribute,
[
self::REGISTER_ONLINE_CONSULTATION,
self::GET_CONSULTATION_DATA,
self::REGISTER_FREE_ONLINE_CONSULTATION,
self::CANCEL_ONLINE_CONSULTATION,
self::START_ONLINE_CONSULTATION,
self::FINISH_ONLINE_CONSULTATION,
self::RESCHEDULE_ONLINE_CONSULTATION,
self::UPDATE_ONLINE_CONSULTATION,
self::VIEW_ONLINE_CONSULTATION,
self::ASSIGN_ONLINE_CONSULTATION,
],
)) {
return false;
}
return $subject instanceof OnlineConsultationInterface || $subject instanceof ServiceInstanceInterface;
}
/**
* @inheritDoc
*/
protected function voteOnAttribute(mixed $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
$isAnon = false;
if (!$user instanceof UserInterface) {
$isAnon = true;
$user = null;
}
switch ($attribute) {
case self::REGISTER_ONLINE_CONSULTATION:
return $this->canRegisterOnlineConsultation($subject, $user);
case self::REGISTER_FREE_ONLINE_CONSULTATION:
return $this->canRegisterFreeOnlineConsultation($subject);
case self::GET_CONSULTATION_DATA:
return $this->canGetConsultationData($subject, $user);
case self::CANCEL_ONLINE_CONSULTATION:
return $this->canCancelOnlineConsultation($subject, $user);
case self::START_ONLINE_CONSULTATION:
return $this->canStartOnlineConsultation($subject, $user);
case self::FINISH_ONLINE_CONSULTATION:
return $this->canFinishOnlineConsultation($subject, $isAnon);
case self::RESCHEDULE_ONLINE_CONSULTATION:
return $this->canRescheduleOnlineConsultation($subject, $user);
case self::UPDATE_ONLINE_CONSULTATION:
return $this->canUpdateOnlineConsultation($subject, $user, $isAnon);
case self::ASSIGN_ONLINE_CONSULTATION:
return $this->canAssignOnlineConsultation($subject);
case self::VIEW_ONLINE_CONSULTATION:
return $this->canViewOnlineConsultation($subject, $user);
default:
throw new LogicException('This code should not be reached!');
}
}
/**
* @param OnlineConsultationInterface $onlineConsultation
*
* @return bool
*/
private function canRegisterFreeOnlineConsultation(OnlineConsultationInterface $onlineConsultation): bool
{
if ($this->security->isGranted(AccountRole::ADMIN)) {
return true;
}
$calendar = $onlineConsultation->getCalendar();
if (!$calendar instanceof CalendarInterface) {
return false;
}
$requestStack = $this->requestStack->getMainRequest();
if (!$requestStack instanceof Request) {
return false;
}
$tokenId = $requestStack->headers->get('X-Payment-Token');
if (!$tokenId) {
return false;
}
$token = $this->freePaymentTokenRepository->find($tokenId);
if (!$token instanceof FreePaymentTokenInterface) {
return false;
}
if ($calendar->getId()->equals($token->getSubjectId())) {
return true;
}
return false;
}
/**
* @param ServiceInstanceInterface $serviceInstance
* @param UserInterface $user
*
* @return bool
*/
private function canRegisterOnlineConsultation(ServiceInstanceInterface $serviceInstance, UserInterface $user): bool
{
$serviceDefinitions = $serviceInstance->getServiceDefinition();
if ($serviceDefinitions) {
if (!$serviceDefinitions->getConsultationCount()) {
return false;
}
}
if ($serviceInstance->isDraft() || $serviceInstance->isUsed()) {
return false;
}
if ($this->security->isGranted(AccountRole::ADMIN)) {
return true;
}
if ($serviceInstance->accountIsOwner($user->getAccount())) {
return true;
}
return false;
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
* @param bool $isAnon
*
* @return bool
*/
private function canUpdateOnlineConsultation(
OnlineConsultationInterface $onlineConsultation,
?UserInterface $user,
bool $isAnon
): bool {
if ($onlineConsultation->getPaidAt()) {
return false;
}
if ($isAnon) {
return $this->hasAccessAsAnonymousUser($onlineConsultation);
}
$account = $user->getAccount();
return $onlineConsultation->isOwner($account) || $this->security->isGranted(AccountRole::ADMIN);
}
/**
* @param OnlineConsultationInterface $consultation
*
* @return bool
*/
private function canAssignOnlineConsultation(OnlineConsultationInterface $consultation): bool
{
if ($this->hasAccessAsAnonymousUser($consultation)) {
return true;
}
return $this->security->isGranted(AccountRole::ADMIN);
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
*
* @return bool
*/
private function canGetConsultationData(OnlineConsultationInterface $onlineConsultation, ?UserInterface $user): bool
{
if ($user && $onlineConsultation->isOwner($user->getAccount())) {
return true;
}
return $this->isAdminOrParticipant($onlineConsultation, $user);
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
*
* @return bool
*/
private function canCancelOnlineConsultation(
OnlineConsultationInterface $onlineConsultation,
?UserInterface $user
): bool {
if (!$user) {
return false;
}
if ($this->security->isGranted(AccountRole::ADMIN)) {
return true;
}
if ($onlineConsultation->getCancelledBy()) {
return false;
}
$now = Carbon::now();
if ($now->greaterThan($onlineConsultation->getStartsAt())) {
return false;
}
return $onlineConsultation->isParticipant($user->getAccount());
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param bool $isAnon
*
* @return bool
*/
private function canFinishOnlineConsultation(OnlineConsultationInterface $onlineConsultation, bool $isAnon): bool
{
return true;
if (!$isAnon && $this->security->isGranted(AccountRole::ADMIN)) {// @phpstan-ignore-line
return true;
}
return $this->checkSecret($onlineConsultation);
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
*
* @return bool
*/
private function canRescheduleOnlineConsultation(
OnlineConsultationInterface $onlineConsultation,
?UserInterface $user
): bool {
$dayBefore = Carbon::now();
$consultationStartDate = $onlineConsultation->getStartsAt();
if ($dayBefore->greaterThan($consultationStartDate->subDay())) {
return false;
}
if ($user && $onlineConsultation->isOwner($user->getAccount())) {
return true;
}
return $this->isAdminOrParticipant($onlineConsultation, $user);
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
*
* @return bool
*/
private function canViewOnlineConsultation(
OnlineConsultationInterface $onlineConsultation,
?UserInterface $user
): bool {
if (!$user) {
return $this->hasAccessAsAnonymousUser($onlineConsultation);
}
if ($onlineConsultation->isOwner($user->getAccount())) {
return true;
}
if ($user->getAccount() === $onlineConsultation->getCreatedBy()) {
return true;
}
return $this->isAdminOrParticipant($onlineConsultation, $user);
}
/**
* @param OnlineConsultationInterface $subject
* @param UserInterface|null $user
*
* @return bool
*/
private function canStartOnlineConsultation(OnlineConsultationInterface $subject, ?UserInterface $user): bool
{
return true;
return $this->isAdminOrParticipant($subject, $user); // @phpstan-ignore-line
}
/**
* @param OnlineConsultationInterface $onlineConsultation
* @param UserInterface|null $user
*
* @return bool
*/
private function isAdminOrParticipant(OnlineConsultationInterface $onlineConsultation, ?UserInterface $user): bool
{
if (!$user) {
return false;
}
if ($this->security->isGranted(AccountRole::ADMIN)) {
return true;
}
return $onlineConsultation->isParticipant($user->getAccount());
}
/**
* @param OnlineConsultationInterface $consultation
*
* @return bool
*/
private function hasAccessAsAnonymousUser(OnlineConsultationInterface $consultation): bool
{
$request = $this->requestStack->getMasterRequest();
$secret = $request->headers->get('X-Online-Consultation-Secret');
if (!$secret) {
return false;
}
if ($consultation->getSecret() && $secret === $consultation->getSecret()) {
return true;
}
foreach ($consultation->getParticipants() as $participant) {
if ($participant->getAccessSecret() === $secret) {
return true;
}
}
return false;
}
/**
* @param OnlineConsultationInterface $consultation
*
* @return bool
*/
private function checkSecret(OnlineConsultationInterface $consultation): bool// @phpstan-ignore-line
{
$request = $this->requestStack->getMasterRequest();
$secret = $request->headers->get('X-Online-Consultation-Secret');
if (!$secret) {
return false;
}
foreach ($consultation->getParticipants() as $participant) {
$account = $participant->getAccount();
if (!$account instanceof AccountInterface) {
continue;
}
if ($participant->getAccessSecret() === $secret) {
return true;
}
}
return false;
}
}