<?php
declare(strict_types=1);
namespace App\Api\Security\Voter;
use App\Api\Entity\UserAbstract;
use Doctrine\Common\Collections\Collection;
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\User\UserInterface;
/**
* Action and entityVoterName set in attribute to create permission type and check it in user permissions.
* Example: attributes="create, dues" => convert to 'can_create_dues' in permission type.
*/
class UserPermissionVoter extends Voter
{
// actions
const VIEW = 'view';
const CREATE = 'create';
const UPDATE = 'update';
const EDIT = 'edit';
const MAKE = 'make';
// entityVoterName
private const DUE = 'due';
private const DUES = 'dues';
private const ENTERPRISE = 'enterprise';
private const EXEMPTIONS = 'exemptions';
private const ANALYTICS = 'analytics';
private const DELIMITER = ', ';
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
protected function supports($attribute, $subject): bool
{
if (!$this->resolveRoute($this->requestStack->getCurrentRequest())) {
return false;
}
if (!$this->isValidAttribute($attribute)) {
return false;
}
list($action, $entityVoterName) = explode(', ', $attribute);
if ($this->isValidAction($action) || $this->isValidEntityVoterName($entityVoterName)) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if ($this->resolveTdc1CompanyDueRoute($user)) {
return true;
}
if (!$user instanceof UserAbstract || !$permissions = $user->getPermissions()) {
return false;
}
list($action, $entityVoterName) = explode(', ', $attribute);
switch ($action) {
case self::VIEW:
case self::CREATE:
case self::EDIT:
case self::UPDATE:
return $this->canAction($permissions, $action, $entityVoterName);
case self::MAKE:
return $this->canAction($permissions, $action, $entityVoterName, true);
default:
return false;
}
}
private function resolveRoute(Request $request): bool
{
return strpos($request->getRequestUri(), 'admin') === false;
}
private function isValidAttribute($attribute): bool
{
if (!is_string($attribute)) {
return false;
}
$position = strpos($attribute, self::DELIMITER);
return !($position === false || $position === 0);
}
private function isValidAction(string $action): bool
{
return !in_array($action, [self::VIEW, self::CREATE, self::UPDATE, self::EDIT, self::MAKE]);
}
private function isValidEntityVoterName(string $entityVoterName): bool
{
return !in_array($entityVoterName, [self::DUE, self::DUES, self::ENTERPRISE, self::EXEMPTIONS, self::ANALYTICS]);
}
private function resolveTdc1CompanyDueRoute(UserInterface $user): bool
{
$request = $this->requestStack->getCurrentRequest();
return $user->isTdc1Company()
&& str_contains($request->getRequestUri(), '/api/v1/due')
&& $request->isMethod(Request::METHOD_GET)
;
}
private function canAction(
Collection $permissions,
string $action,
string $entityVoterName,
bool $isMake = false
): bool {
$permissionType = $this->createPermissionType($action, $entityVoterName, $isMake);
$userPermissionValues = $permissions->map(
function ($permission) {
return $permission->getType();
})
;
return in_array($permissionType, $userPermissionValues->toArray());
}
private function createPermissionType(string $action, string $entityVoterName, bool $isMake = false): string
{
return $isMake
? sprintf('can_%s_%s_effective', $action, $entityVoterName)
: sprintf('can_%s_%s', $action, $entityVoterName);
}
}