<?php
namespace App\Security\Voter;
use App\AclPermission\AclPermissionManager;
use App\AclPermission\AclPermissionProvider;
use App\Entity\Hotel;
use App\Entity\User;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use function Symfony\Component\String\u;
class AclVoter extends Voter
{
public const ANYONE_PERMISSION = 'ACL_ANYONE';
private EntityManagerInterface $em;
private Security $security;
private AclPermissionProvider $aclPermissionProvider;
private AclPermissionManager $aclPermissionManager;
public function __construct(EntityManagerInterface $em, Security $security, AclPermissionProvider $aclPermissionProvider, AclPermissionManager $aclPermissionManager)
{
$this->em = $em;
$this->security = $security;
$this->aclPermissionProvider = $aclPermissionProvider;
$this->aclPermissionManager = $aclPermissionManager;
}
protected function supports(string $attribute, $subject): bool
{
$subjectClassFcqn = $this->getSubjectClassFcqn($subject);
if (!$subjectClassFcqn) {
return false;
}
$aclConfig = $this->aclPermissionProvider->getConfigForDomainFcqn($subjectClassFcqn);
return !empty($aclConfig) && (in_array($attribute, $aclConfig['permissions'], true) || $attribute === self::ANYONE_PERMISSION);
}
/**
* @param string $attribute
* @param $subject
* @param TokenInterface $token
* @return bool
*/
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
/** @var User $user */
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof User || !$user->isActive()) {
return false;
}
if ($this->security->isGranted('ROLE_SUPER_ADMIN')) {
return true;
}
// Assigned users can do anything to hotels
if ($subject instanceof Hotel && $user->getAssignedHotels()->contains($subject)) {
return true;
}
$subjectClassFcqn = $this->getSubjectClassFcqn($subject);
if (!$subjectClassFcqn) {
return false;
}
$scope = is_object($subject) ? 'object' : 'class';
$domainId = is_object($subject) ? $subject->getId() : 'class';
foreach ($user->getGroups() as $group) {
$permissions = $this->aclPermissionManager->getStructuredIdentityAclPermissions($group);
if ($this->hasPermission($permissions, $subjectClassFcqn, $scope, $domainId, $attribute)) {
return true;
}
}
$permissions = $this->aclPermissionManager->getStructuredIdentityAclPermissions($user);
return $this->hasPermission($permissions, $subjectClassFcqn, $scope, $domainId, $attribute);
}
private function hasPermission(array $permissions, string $subjectClassFcqn, string $scope, string $domainId, string $attribute): bool
{
if ($attribute === self::ANYONE_PERMISSION) {
return array_key_exists($subjectClassFcqn, $permissions) && !empty($permissions[$subjectClassFcqn]);
}
if ($scope !== 'class') {
$classScope = 'class';
$classDomainId = 'class';
if (array_key_exists($subjectClassFcqn, $permissions) &&
array_key_exists($classScope, $permissions[$subjectClassFcqn]) &&
array_key_exists($classDomainId, $permissions[$subjectClassFcqn][$classScope]) &&
in_array($attribute, $permissions[$subjectClassFcqn][$classScope][$classDomainId]['permissions'], true)
) {
return true;
}
}
return array_key_exists($subjectClassFcqn, $permissions) &&
array_key_exists($scope, $permissions[$subjectClassFcqn]) &&
array_key_exists($domainId, $permissions[$subjectClassFcqn][$scope]) &&
in_array($attribute, $permissions[$subjectClassFcqn][$scope][$domainId]['permissions'], true);
}
private function getSubjectClassFcqn($subject): ?string
{
if (is_object($subject)) {
// Sometimes we have proxy classes, thus we need to guess real class name.
return ClassUtils::getClass($subject);
}
if (u($subject)->containsAny('\\')) {
return $subject;
}
if (u($subject)->endsWith('::class')) {
$subject = u($subject)->before('::class');
}
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
if ($metadata->getReflectionClass()->getShortName() === $subject) {
return $metadata->getName();
}
}
return null;
}
}