src/Security/Voter/InsightsVoter.php line 14

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\Security\Voter;
  3. use MedBrief\MSR\Entity\Account;
  4. use MedBrief\MSR\Entity\Project;
  5. use MedBrief\MSR\Entity\User;
  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. class InsightsVoter extends Voter
  12. {
  13. public const VIEW_RECORDS_INSIGHTS = 'VIEW_RECORDS_INSIGHTS';
  14. public const EDIT_RECORDS_INSIGHTS = 'EDIT_RECORDS_INSIGHTS';
  15. public function __construct(private readonly AuthorizationCheckerInterface $auth)
  16. {
  17. }
  18. #[Override]
  19. protected function supports(string $attribute, mixed $subject): bool
  20. {
  21. return in_array($attribute, [self::VIEW_RECORDS_INSIGHTS, self::EDIT_RECORDS_INSIGHTS], true)
  22. && $subject instanceof Project;
  23. }
  24. #[Override]
  25. protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
  26. {
  27. $user = $token->getUser();
  28. if (!$user instanceof UserInterface) {
  29. return false;
  30. }
  31. /** @var Project $project */
  32. $project = $subject;
  33. return match ($attribute) {
  34. self::VIEW_RECORDS_INSIGHTS => $this->canViewRecordsInsights($user, $project),
  35. self::EDIT_RECORDS_INSIGHTS => $this->canViewRecordsInsights($user, $project),
  36. default => false,
  37. };
  38. }
  39. /**
  40. * Determines if the user can view the insights button and panel.
  41. * User roles allowed:
  42. * - MB super admins (ROLE_SUPER_ADMIN)
  43. * - MB admins (ROLE_ADMIN)
  44. * - Client super admins (ROLE_ACCOUNT_{accountId}_SUPERADMINISTRATOR)
  45. * - Client admins (ROLE_ACCOUNT_{accountId}_ADMINISTRATOR)
  46. * - Client project managers (ROLE_ACCOUNT_{accountId}_PROJECTMANAGER)
  47. * - Matter experts (ROLE_PROJECT_{projectId}_EXPERT)
  48. * - Matter expert viewers (ROLE_PROJECT_{projectId}_EXPERTVIEWER)
  49. * - Matter/Project managers:
  50. * - Role-based: ROLE_PROJECT_{projectId}_PROJECTMANAGER
  51. * - Entity-based: $project->getManager() === $user
  52. *
  53. * @param UserInterface $user
  54. * @param Project $project
  55. *
  56. * @return bool Returns true if the user can view records insights, false otherwise
  57. */
  58. private function canViewRecordsInsights(UserInterface $user, Project $project): bool
  59. {
  60. $account = $project->getAccount();
  61. // Only consider Client matters (no Disclosures or other non-matter projects)
  62. if ($project->getType() !== Project::TYPE_MATTER_FIRM) {
  63. return false;
  64. }
  65. // MB admins always have access to insights, regardless of account settings or user opt-in
  66. if ($this->auth->isGranted('ROLE_SUPER_ADMIN') || $this->auth->isGranted('ROLE_ADMIN')) {
  67. return true;
  68. }
  69. // If insights is not enabled at the account level, then only MB admins can access insights
  70. if (!$account instanceof Account || !$account->isInsightsEnabled()) {
  71. return false;
  72. }
  73. // For client users, check if they have insights access based on account settings
  74. if (!$this->hasUserInsightsAccess($account, $user)) {
  75. return false;
  76. }
  77. // For client users, they must have an eligible role and insights must be enabled for the account, then we check opt-in settings
  78. if (!$this->hasEligibleInsightsRole($user, $project, $account)) {
  79. return false;
  80. }
  81. // At this point, the project is eligible for Insights and the client user has
  82. // both an allowed role and the required opt-in access, so access is granted.
  83. return true;
  84. }
  85. /**
  86. * Checks if the user has an eligible role for insights access based on their relationship to the project and account.
  87. *
  88. * @param UserInterface $user The user for whom we are checking roles
  89. * @param Project $project The project associated with the insights access
  90. * @param Account $account The account associated with the project
  91. *
  92. * @return bool Returns true if the user has an eligible role for insights access, false otherwise
  93. */
  94. private function hasEligibleInsightsRole(UserInterface $user, Project $project, Account $account): bool
  95. {
  96. // Client-level admins
  97. if ($this->auth->isGranted('ROLE_ACCOUNT_' . $account->getId() . '_SUPERADMINISTRATOR')) {
  98. return true;
  99. }
  100. if ($this->auth->isGranted('ROLE_ACCOUNT_' . $account->getId() . '_ADMINISTRATOR')) {
  101. return true;
  102. }
  103. if ($this->auth->isGranted('ROLE_ACCOUNT_' . $account->getId() . '_PROJECTMANAGER')) {
  104. return true;
  105. }
  106. // Project-level experts and expert viewers
  107. if ($this->auth->isGranted('ROLE_PROJECT_' . $project->getId() . '_EXPERT')) {
  108. return true;
  109. }
  110. if ($this->auth->isGranted('ROLE_PROJECT_' . $project->getId() . '_EXPERTVIEWER')) {
  111. return true;
  112. }
  113. // Matter/Project manager (role-based)
  114. if ($this->auth->isGranted('ROLE_PROJECT_' . $project->getId() . '_PROJECTMANAGER')) {
  115. return true;
  116. }
  117. // Matter/Project manager (entity relationship fallback)
  118. if ($project->getManager() instanceof User && $project->getManager() === $user) {
  119. return true;
  120. }
  121. return false;
  122. }
  123. /**
  124. * Checks if the user has access to insights based on account level and user level opt-in settings.
  125. *
  126. * @param Account $account The account associated with the project for which we are checking access
  127. * @param UserInterface $user The user for whom we are checking access
  128. *
  129. * @return bool Returns true if the user has access to insights, false otherwise
  130. */
  131. private function hasUserInsightsAccess(Account $account, UserInterface $user): bool
  132. {
  133. // If insights is enabled for all client users, then any user with an eligible role
  134. // can access,otherwise we check the user's individual opt-in setting
  135. if ($account->isInsightsOptInAllClientUsers()) {
  136. return true;
  137. }
  138. // If insights is only enabled for specific users, then we check the user's opt-in setting
  139. if ($account->isInsightsOptInSpecificUsers() && $user instanceof User) {
  140. return $user->getInsightsOptIn();
  141. }
  142. return false;
  143. }
  144. }