<?php
namespace MedBrief\MSR\Security\Voter;
use MedBrief\MSR\Entity\Account;
use MedBrief\MSR\Entity\Project;
use MedBrief\MSR\Entity\User;
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;
class InsightsVoter extends Voter
{
public const VIEW_RECORDS_INSIGHTS = 'VIEW_RECORDS_INSIGHTS';
public function __construct(private readonly AuthorizationCheckerInterface $auth)
{
}
#[Override]
protected function supports(string $attribute, mixed $subject): bool
{
return $attribute === self::VIEW_RECORDS_INSIGHTS && $subject instanceof Project;
}
#[Override]
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
/** @var Project $project */
$project = $subject;
return match ($attribute) {
self::VIEW_RECORDS_INSIGHTS => $this->canViewRecordsInsights($user, $project),
default => false,
};
}
/**
* Determines if the user can view the insights button and panel.
* User roles allowed:
* - MB super admins (ROLE_SUPER_ADMIN)
* - MB admins (ROLE_ADMIN)
* - Client super admins (ROLE_ACCOUNT_{accountId}_SUPERADMINISTRATOR)
* - Client admins (ROLE_ACCOUNT_{accountId}_ADMINISTRATOR)
* - Matter/Project managers:
* - Role-based: ROLE_PROJECT_{projectId}_PROJECTMANAGER
* - Entity-based: $project->getManager() === $user
*
* @param UserInterface $user
* @param Project $project
*
* @return bool Returns true if the user can view records insights, false otherwise
*/
private function canViewRecordsInsights(UserInterface $user, Project $project): bool
{
$account = $project->getAccount();
// Only consider Client matters (no Disclosures or other non-matter projects)
if ($project->getType() !== Project::TYPE_MATTER_FIRM) {
return false;
}
// MB admins always have access to insights, regardless of account settings or user opt-in
if ($this->auth->isGranted('ROLE_SUPER_ADMIN') || $this->auth->isGranted('ROLE_ADMIN')) {
return true;
}
// If insights is not enabled at the account level, then only MB admins can access insights
if (!$account instanceof Account || !$account->isInsightsEnabled()) {
return false;
}
// For client users, check if they have insights access based on account settings
if (!$this->hasUserInsightsAccess($account, $user)) {
return false;
}
// For client users, they must have an eligible role and insights must be enabled for the account, then we check opt-in settings
if (!$this->hasEligibleInsightsRole($user, $project, $account)) {
return false;
}
// At this point, the project is eligible for Insights and the client user has
// both an allowed role and the required opt-in access, so access is granted.
return true;
}
/**
* Checks if the user has an eligible role for insights access based on their relationship to the project and account.
*
* @param UserInterface $user The user for whom we are checking roles
* @param Project $project The project associated with the insights access
* @param Account $account The account associated with the project
*
* @return bool Returns true if the user has an eligible role for insights access, false otherwise
*/
private function hasEligibleInsightsRole(UserInterface $user, Project $project, Account $account): bool
{
// Client-level admins
if ($this->auth->isGranted('ROLE_ACCOUNT_' . $account->getId() . '_SUPERADMINISTRATOR')) {
return true;
}
if ($this->auth->isGranted('ROLE_ACCOUNT_' . $account->getId() . '_ADMINISTRATOR')) {
return true;
}
// Matter/Project manager (role-based)
if ($this->auth->isGranted('ROLE_PROJECT_' . $project->getId() . '_PROJECTMANAGER')) {
return true;
}
// Matter/Project manager (entity relationship fallback)
if ($project->getManager() instanceof User && $project->getManager() === $user) {
return true;
}
return false;
}
/**
* Checks if the user has access to insights based on account level and user level opt-in settings.
*
* @param UserInterface $user The user for whom we are checking access
* @param Account $account The account associated with the project for which we are checking access
*
* @return bool Returns true if the user has access to insights, false otherwise
*/
private function hasUserInsightsAccess(Account $account, UserInterface $user): bool
{
// If insights is enabled for all client users, then any user with an eligible role
// can access,otherwise we check the user's individual opt-in setting
if ($account->isInsightsOptInAllClientUsers()) {
return true;
}
// If insights is only enabled for specific users, then we check the user's opt-in setting
if ($account->isInsightsOptInSpecificUsers() && $user instanceof User) {
return $user->getInsightsOptIn();
}
return false;
}
}