. */ declare(strict_types=1); namespace App\Services\UserSystem; use App\Entity\UserSystem\User; use App\Repository\UserRepository; use App\Security\ApiTokenAuthenticatedToken; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Vote; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\UserSystem\VoterHelperTest */ final class VoterHelper { private readonly UserRepository $userRepository; private readonly array $permissionStructure; public function __construct(private readonly PermissionManager $permissionManager, private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager) { $this->userRepository = $this->entityManager->getRepository(User::class); $this->permissionStructure = $this->permissionManager->getPermissionStructure(); } /** * Checks if the operation on the given permission is granted for the given token. * Similar to isGrantedTrinary, but returns false if the permission is not granted. * @param TokenInterface $token The token to check * @param string $permission The permission to check * @param string $operation The operation to check * @param Vote|null $vote The vote object to add reasons to (optional). If null, no reasons are added. * @return bool */ public function isGranted(TokenInterface $token, string $permission, string $operation, ?Vote $vote = null): bool { $tmp = $this->isGrantedTrinary($token, $permission, $operation) ?? false; if ($tmp === false) { $this->addReason($vote, $permission, $operation); } return $tmp; } /** * Checks if the operation on the given permission is granted for the given token. * The result is returned in trinary value, where null means inherted from the parent. * @param TokenInterface $token The token to check * @param string $permission The permission to check * @param string $operation The operation to check * @return bool|null The result of the check. Null means inherted from the parent. */ public function isGrantedTrinary(TokenInterface $token, string $permission, string $operation): ?bool { $user = $token->getUser(); if ($user instanceof User) { //A disallowed user is not allowed to do anything... if ($user->isDisabled()) { return false; } } else { //Try to resolve the user from the token $user = $this->resolveUser($token); } //If the token is a APITokenAuthenticated if ($token instanceof ApiTokenAuthenticatedToken) { //Use the special API token checker return $this->permissionManager->inheritWithAPILevel($user, $token->getRoleNames(), $permission, $operation); } //Otherwise use the normal permission checker return $this->permissionManager->inherit($user, $permission, $operation); } /** * Resolves the user from the given token. If the token is anonymous, the anonymous user is returned. * @return User */ public function resolveUser(TokenInterface $token): User { $user = $token->getUser(); //If the user is a User entity, just return it if ($user instanceof User) { return $user; } //If the user is null, return the anonymous user if ($user === null) { $user = $this->userRepository->getAnonymousUser(); if (!$user instanceof User) { throw new \RuntimeException('The anonymous user could not be resolved.'); } return $user; } //Otherwise throw an exception throw new \RuntimeException('The user could not be resolved.'); } /** * Checks if the permission operation combination with the given names is existing. * Just a proxy to the permission manager. * * @param string $permission the name of the permission which should be checked * @param string $operation the name of the operation which should be checked * * @return bool true if the given permission operation combination is existing */ public function isValidOperation(string $permission, string $operation): bool { return $this->permissionManager->isValidOperation($permission, $operation); } public function addReason(?Vote $voter, string $permission, $operation): void { if ($voter !== null) { $voter->addReason(sprintf("User does not have permission %s -> %s -> %s (%s.%s).", $this->translator->trans('perm.group.'.($this->permissionStructure['perms'][$permission]['group'] ?? 'unknown') ), $this->translator->trans($this->permissionStructure['perms'][$permission]['label'] ?? $permission), $this->translator->trans($this->permissionStructure['perms'][$permission]['operations'][$operation]['label'] ?? $operation), $permission, $operation )); } } }