<?php
namespace App\Permission\Listener;
use App\Permission\Annotation\PermissionCheck;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Contracts\Translation\TranslatorInterface;
class PermissionCheckListener
{
/**
* @param Reader $annotationReader
* @param AuthorizationCheckerInterface $authorizationChecker
* @param TranslatorInterface $translator
*/
public function __construct(
protected Reader $annotationReader,
protected AuthorizationCheckerInterface $authorizationChecker,
protected TranslatorInterface $translator,
)
{
}
/**
* @param ControllerEvent $event
* @return void
* @throws ReflectionException
*/
public function onKernelController(ControllerEvent $event): void
{
if (!$event->getRequest()) {
return;
}
$controllers = $event->getController();
if (!is_array($controllers)) {
return;
}
$this->handleAnnotation($controllers);
}
/**
* @param iterable $controllers
* @return void
* @throws ReflectionException
*/
private function handleAnnotation(iterable $controllers): void
{
list($controller, $method) = $controllers;
try {
$controller = new ReflectionClass($controller);
} catch (ReflectionException) {
throw new RuntimeException($this->translator->trans('api.failedToReadAnnotation'));
}
$this->handleClassAnnotation($controller);
$this->handleMethodAnnotation($controller, $method);
}
/**
* @param ReflectionClass $controller
* @return void
*/
private function handleClassAnnotation(ReflectionClass $controller): void
{
$annotation = $this->annotationReader->getClassAnnotation($controller, PermissionCheck::class);
$this->extracted($annotation);
}
/**
* @param ReflectionClass $controller
* @param string $method
* @return void
* @throws ReflectionException
*/
private function handleMethodAnnotation(ReflectionClass $controller, string $method): void
{
$method = $controller->getMethod($method);
$annotation = $this->annotationReader->getMethodAnnotation($method, PermissionCheck::class);
$this->extracted($annotation);
}
/**
* @param PermissionCheck|null $annotation
* @return void
*/
private function extracted(?PermissionCheck $annotation): void
{
if ($annotation instanceof PermissionCheck) {
$isGranted = $this->authorizationChecker->isGranted($annotation->permission, $annotation->role);
if (!$isGranted) {
$message = sprintf($this->translator->trans('api.rolePermissionNotFound'), $annotation->role, implode(' ' . $this->translator->trans('api.or') . ' ', $annotation->permission));
throw new AccessDeniedException($message);
}
}
}
}