src/Security/Voter/AiFeedbackMatchVoter.php line 22

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\Security\Voter;
  3. use MedBrief\MSR\Entity\AiFeedbackMatch;
  4. use MedBrief\MSR\Entity\User;
  5. use MedBrief\MSR\Repository\ProjectRepository;
  6. use Override;
  7. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  8. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  9. use Symfony\Component\Security\Core\Authorization\Voter\Voter;
  10. use Symfony\Component\Security\Core\User\UserInterface;
  11. /**
  12. * Voter for AiFeedbackMatch entity CRUD permissions.
  13. *
  14. * - CREATE: requires MATCH_VIEW_FIRM access on the related project
  15. * - READ: requires MATCH_VIEW_FIRM access on the related project
  16. * - UPDATE: only the user who created the feedback may update it
  17. * - DELETE: requires ROLE_ADMIN
  18. */
  19. class AiFeedbackMatchVoter extends Voter
  20. {
  21. public const CREATE = 'AI_FEEDBACK_MATCH_CREATE';
  22. public const READ = 'AI_FEEDBACK_MATCH_READ';
  23. public const UPDATE = 'AI_FEEDBACK_MATCH_UPDATE';
  24. public const DELETE = 'AI_FEEDBACK_MATCH_DELETE';
  25. public function __construct(
  26. private readonly AuthorizationCheckerInterface $auth,
  27. private readonly ProjectRepository $projectRepository,
  28. ) {
  29. }
  30. #[Override]
  31. protected function supports(string $attribute, mixed $subject): bool
  32. {
  33. return \in_array($attribute, [self::CREATE, self::READ, self::UPDATE, self::DELETE], true)
  34. && $subject instanceof AiFeedbackMatch;
  35. }
  36. #[Override]
  37. protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
  38. {
  39. $user = $token->getUser();
  40. if (!$user instanceof UserInterface) {
  41. return false;
  42. }
  43. /** @var AiFeedbackMatch $feedback */
  44. $feedback = $subject;
  45. return match ($attribute) {
  46. self::CREATE => $this->canCreate($feedback),
  47. self::READ => $this->canRead($feedback),
  48. self::UPDATE => $this->canUpdate($feedback, $user),
  49. self::DELETE => $this->canDelete(),
  50. default => false,
  51. };
  52. }
  53. /**
  54. * CREATE: user must have MATCH_VIEW_FIRM access on the related project.
  55. *
  56. * @param AiFeedbackMatch $feedback
  57. */
  58. private function canCreate(AiFeedbackMatch $feedback): bool
  59. {
  60. $project = $this->projectRepository->find($feedback->getMatterId());
  61. if ($project === null) {
  62. return false;
  63. }
  64. return $this->auth->isGranted(MatchVoter::VIEW_FIRM, $project);
  65. }
  66. /**
  67. * READ: user must have MATCH_VIEW_FIRM access on the related project.
  68. *
  69. * @param AiFeedbackMatch $feedback
  70. */
  71. private function canRead(AiFeedbackMatch $feedback): bool
  72. {
  73. $project = $this->projectRepository->find($feedback->getMatterId());
  74. if ($project === null) {
  75. return false;
  76. }
  77. return $this->auth->isGranted(MatchVoter::VIEW_FIRM, $project);
  78. }
  79. /**
  80. * UPDATE: only the user who created the feedback may update it.
  81. *
  82. * @param AiFeedbackMatch $feedback
  83. * @param UserInterface $user
  84. */
  85. private function canUpdate(AiFeedbackMatch $feedback, UserInterface $user): bool
  86. {
  87. if (!$user instanceof User) {
  88. return false;
  89. }
  90. return $feedback->getUserId() !== null && $feedback->getUserId() === $user->getId();
  91. }
  92. /**
  93. * DELETE: only MB admins.
  94. */
  95. private function canDelete(): bool
  96. {
  97. return $this->auth->isGranted('ROLE_ADMIN');
  98. }
  99. }