<?php
namespace MedBrief\MSR\Security\Voter;
use MedBrief\MSR\Entity\AiFeedbackMatch;
use MedBrief\MSR\Entity\User;
use MedBrief\MSR\Repository\ProjectRepository;
use Override;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Voter for AiFeedbackMatch entity CRUD permissions.
*
* - CREATE: requires MATCH_VIEW_FIRM access on the related project
* - READ: requires MATCH_VIEW_FIRM access on the related project
* - UPDATE: only the user who created the feedback may update it
* - DELETE: requires ROLE_ADMIN
*/
class AiFeedbackMatchVoter extends Voter
{
public const CREATE = 'AI_FEEDBACK_MATCH_CREATE';
public const READ = 'AI_FEEDBACK_MATCH_READ';
public const UPDATE = 'AI_FEEDBACK_MATCH_UPDATE';
public const DELETE = 'AI_FEEDBACK_MATCH_DELETE';
public function __construct(
private readonly AuthorizationCheckerInterface $auth,
private readonly ProjectRepository $projectRepository,
) {
}
#[Override]
protected function supports(string $attribute, mixed $subject): bool
{
return \in_array($attribute, [self::CREATE, self::READ, self::UPDATE, self::DELETE], true)
&& $subject instanceof AiFeedbackMatch;
}
#[Override]
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var AiFeedbackMatch $feedback */
$feedback = $subject;
return match ($attribute) {
self::CREATE => $this->canCreate($feedback),
self::READ => $this->canRead($feedback),
self::UPDATE => $this->canUpdate($feedback, $user),
self::DELETE => $this->canDelete(),
default => false,
};
}
/**
* CREATE: user must have MATCH_VIEW_FIRM access on the related project.
*
* @param AiFeedbackMatch $feedback
*/
private function canCreate(AiFeedbackMatch $feedback): bool
{
$project = $this->projectRepository->find($feedback->getMatterId());
if ($project === null) {
return false;
}
return $this->auth->isGranted(MatchVoter::VIEW_FIRM, $project);
}
/**
* READ: user must have MATCH_VIEW_FIRM access on the related project.
*
* @param AiFeedbackMatch $feedback
*/
private function canRead(AiFeedbackMatch $feedback): bool
{
$project = $this->projectRepository->find($feedback->getMatterId());
if ($project === null) {
return false;
}
return $this->auth->isGranted(MatchVoter::VIEW_FIRM, $project);
}
/**
* UPDATE: only the user who created the feedback may update it.
*
* @param AiFeedbackMatch $feedback
* @param UserInterface $user
*/
private function canUpdate(AiFeedbackMatch $feedback, UserInterface $user): bool
{
if (!$user instanceof User) {
return false;
}
return $feedback->getUserId() !== null && $feedback->getUserId() === $user->getId();
}
/**
* DELETE: only MB admins.
*/
private function canDelete(): bool
{
return $this->auth->isGranted('ROLE_ADMIN');
}
}