Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Symfony 4: Check if a route is accessible to a User based on their roles
<?php
/**
* @Author Andrea Bergamasco <andrea@bergamasco.me>
* Inspired by @leotiger: https://gist.github.com/leotiger/ca496d1b16c30afea060904bd5e78714
*
* Usage:
* $token = $this->security->getToken();
* $this->accessChecker->userTokenCanAccessRoute($token, 'admin.index');
*/
namespace App\Service;
use Doctrine\Common\Annotations\Annotation;
use Doctrine\Common\Annotations\Reader;
use Exception;
use ReflectionClass;
use ReflectionException;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
/**
* Class AccessChecker
* @package GroupeComplus\Bundle\VisioBundle\Service
* @todo Should check also routes protected in firewall configuration: security.access_control.*
*/
class AccessChecker
{
private Reader $annotationReader;
private RouterInterface $router;
private AccessDecisionManagerInterface $accessDecisionManager;
public function __construct(Reader $annotationReader, RouterInterface $router, AccessDecisionManagerInterface $accessDecisionManager)
{
$this->annotationReader = $annotationReader;
$this->router = $router;
$this->accessDecisionManager = $accessDecisionManager;
}
/**
* @param TokenInterface $token
* @param string $routeName
*
* @return bool
*/
public function userCanAccessRoute(TokenInterface $userToken, string $routeName): bool
{
$securityExpressions = array_merge(
$this->getRouteControllerSecurityExpressions($routeName),
$this->getRouteMethodSecurityExpressions($routeName)
);
$url = $this->router->generate($routeName, [], RouterInterface::ABSOLUTE_PATH);
$request = Request::create($url);
return $this->accessDecisionManager->decide($userToken, $securityExpressions, $request);
}
/**
* @param string $routeName
*
* @return ReflectionClass
* @throws ReflectionException
*/
private function getRouteControllerClass(string $routeName): ReflectionClass
{
$className = $this->getRouteControllerClassName($routeName);
return new ReflectionClass($className);
}
/**
* @param string $routeName
*
* @return array|Annotation[]
*/
private function getRouteControllerMethodAnnotations(string $routeName): array
{
try {
$reflectedClass = $this->getRouteControllerClass($routeName);
} catch (Exception $exception) {
return [];
}
$controllerMethodName = $this->getRouteControllerMethodName($routeName);
try {
$reflectedMethod = $reflectedClass->getMethod($controllerMethodName);
} catch (Exception $exception) {
return [];
}
return $this->annotationReader->getMethodAnnotations($reflectedMethod);
}
/**
* @param string $routeName
*
* @return string
*/
private function getRouteControllerMethodName(string $routeName): string
{
$route = $this->router->getRouteCollection()->get($routeName);
return preg_replace('/.*::/i', '', $route->getDefault('_controller'));
}
/**
* @param string $routeName
*
* @return string
*/
private function getRouteControllerClassName(string $routeName): string
{
$route = $this->router->getRouteCollection()->get($routeName);
return preg_replace('/::.*/i', '', $route->getDefault('_controller'));
}
/**
* @param string $routeName
*
* @return array|Expression[]
*/
private function getRouteControllerSecurityExpressions(string $routeName): array
{
try {
$controllerClass = $this->getRouteControllerClass($routeName);
} catch (Exception $exception) {
return [];
}
$controllerSecurityAnnotations = $this->annotationReader->getClassAnnotations($controllerClass);
return $this->getSecurityAnnotationsExpressions($controllerSecurityAnnotations);
}
/**
* @param string $routeName
*
* @return array|Expression[]
*/
private function getRouteMethodSecurityExpressions(string $routeName): array
{
$methodSecurityAnnotations = $this->getRouteControllerMethodAnnotations($routeName);
return $this->getSecurityAnnotationsExpressions($methodSecurityAnnotations);
}
/**
* @param array $securityAnnotations
*
* @return array
*/
private function getSecurityAnnotationsExpressions(array $securityAnnotations): array
{
$expressions = [];
foreach ($securityAnnotations as $annotation) {
if ('Sensio\Bundle\FrameworkExtraBundle\Configuration\Security' === get_class($annotation)) {
$expressions[] = new Expression($annotation->getExpression());
}
}
return $expressions;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment