<?php
namespace App\Security\Api\Authorization\Voter;
use App\Entity\Api\CoreModule\User;
use App\Helper\Api\Entity\RequestDataContainer;
use App\Helper\Api\Translator\ApiTranslator;
use App\Security\Api\Authorization\RoleConfiguration\RoleConfigurationServiceSubscriber;
use App\Security\Api\Authorization\VoterAttribute\AbstractVoterAttribute;
use LogicException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* API catch all role voter.
*
* Denies access unless role configuration allows access.
*
* @package API
* @internal
*/
class RoleVoter extends AbstractApiBaseVoter
{
/**
* Role configuration service subscriber.
*
* @var RoleConfigurationServiceSubscriber
*/
protected RoleConfigurationServiceSubscriber $roleConfigurationServiceSubscriber;
/**
* Translator for all API controllers.
*
* @var ApiTranslator
*/
protected ApiTranslator $translator;
/**
* Constructor.
*
* @param ApiTranslator $translator API translator.
* @param RoleConfigurationServiceSubscriber $roleConfigurationServiceSubscriber Role configuration service
* subscriber.
*/
public function __construct(
ApiTranslator $translator,
RoleConfigurationServiceSubscriber $roleConfigurationServiceSubscriber
) {
$this->translator = $translator;
$this->roleConfigurationServiceSubscriber = $roleConfigurationServiceSubscriber;
}
/**
* Determines if the attribute and subject are supported by this voter.
*
* @param string $attribute An attribute.
* @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type.
*
* @return bool
*/
protected function supports(string $attribute, $subject): bool
{
// requires data container
if (!$subject instanceof RequestDataContainer) {
return false;
}
// votes then on every API call when attribute format is valid
return AbstractVoterAttribute::isValidAttribute($attribute);
}
/**
* Perform a single access check operation on a given attribute, subject and token.
* It is safe to assume that $attribute and $subject already passed the "supports()" method check.
*
* @param string $attribute An attribute.
* @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type.
* @param TokenInterface $token The token interface.
* @return bool
*/
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
// the user must be logged in, otherwise deny access
return false;
}
if (!$this->supports($attribute, $subject)) {
throw new LogicException('This code should not be reached!');
}
if (!$subject instanceof RequestDataContainer) {
throw new LogicException('This voter requires a RequestDataContainer object passed as subject!');
}
try {
$roles = $this->roleConfigurationServiceSubscriber->getRoles($attribute);
$allowed = !empty(array_intersect($user->getRoles(), $roles));
if (!$allowed) {
$subject->setException(
new AccessDeniedHttpException($this->translator->t('authorization.access_denied'))
);
}
return $allowed;
} catch (NotFoundExceptionInterface|ContainerExceptionInterface $exception) {
$subject->setException(
new AccessDeniedHttpException($this->translator->t('authorization.access_denied'), $exception)
);
return false;
}
}
}