-
-
Save vjandrea/f4fdb7d187ba8016a664f77997b2aeeb to your computer and use it in GitHub Desktop.
Symfony 4: Check if a route is accessible to a User based on their roles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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