vendor/sensio/framework-extra-bundle/src/EventListener/SecurityListener.php line 54

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
  11. use Psr\Log\LoggerInterface;
  12. use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
  13. use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
  14. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  15. use Symfony\Component\HttpKernel\Event\KernelEvent;
  16. use Symfony\Component\HttpKernel\Exception\HttpException;
  17. use Symfony\Component\HttpKernel\KernelEvents;
  18. use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
  19. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  20. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  21. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  22. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  23. use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
  24. /**
  25. * SecurityListener handles security restrictions on controllers.
  26. *
  27. * @author Fabien Potencier <fabien@symfony.com>
  28. */
  29. class SecurityListener implements EventSubscriberInterface
  30. {
  31. private $argumentNameConverter;
  32. private $tokenStorage;
  33. private $authChecker;
  34. private $language;
  35. private $trustResolver;
  36. private $roleHierarchy;
  37. private $logger;
  38. public function __construct(ArgumentNameConverter $argumentNameConverter, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null, LoggerInterface $logger = null)
  39. {
  40. $this->argumentNameConverter = $argumentNameConverter;
  41. $this->tokenStorage = $tokenStorage;
  42. $this->authChecker = $authChecker;
  43. $this->language = $language;
  44. $this->trustResolver = $trustResolver;
  45. $this->roleHierarchy = $roleHierarchy;
  46. $this->logger = $logger;
  47. }
  48. public function onKernelControllerArguments(KernelEvent $event)
  49. {
  50. $request = $event->getRequest();
  51. if (!$configurations = $request->attributes->get('_security')) {
  52. return;
  53. }
  54. if (null === $this->tokenStorage || null === $this->trustResolver) {
  55. throw new \LogicException('To use the @Security tag, you need to install the Symfony Security bundle.');
  56. }
  57. if (null === $this->tokenStorage->getToken()) {
  58. throw new AccessDeniedException('No user token or you forgot to put your controller behind a firewall while using a @Security tag.');
  59. }
  60. if (null === $this->language) {
  61. throw new \LogicException('To use the @Security tag, you need to use the Security component 2.4 or newer and install the ExpressionLanguage component.');
  62. }
  63. foreach ($configurations as $configuration) {
  64. if (!$this->language->evaluate($configuration->getExpression(), $this->getVariables($event))) {
  65. if ($statusCode = $configuration->getStatusCode()) {
  66. throw new HttpException($statusCode, $configuration->getMessage());
  67. }
  68. throw new AccessDeniedException($configuration->getMessage() ?: sprintf('Expression "%s" denied access.', $configuration->getExpression()));
  69. }
  70. }
  71. }
  72. // code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
  73. private function getVariables(KernelEvent $event)
  74. {
  75. $request = $event->getRequest();
  76. $token = $this->tokenStorage->getToken();
  77. $variables = [
  78. 'token' => $token,
  79. 'user' => $token->getUser(),
  80. 'object' => $request,
  81. 'subject' => $request,
  82. 'request' => $request,
  83. 'roles' => $this->getRoles($token),
  84. 'trust_resolver' => $this->trustResolver,
  85. // needed for the is_granted expression function
  86. 'auth_checker' => $this->authChecker,
  87. ];
  88. $controllerArguments = $this->argumentNameConverter->getControllerArguments($event);
  89. if ($diff = array_intersect(array_keys($variables), array_keys($controllerArguments))) {
  90. foreach ($diff as $key => $variableName) {
  91. if ($variables[$variableName] === $controllerArguments[$variableName]) {
  92. unset($diff[$key]);
  93. }
  94. }
  95. if ($diff) {
  96. $singular = 1 === \count($diff);
  97. if (null !== $this->logger) {
  98. $this->logger->warning(sprintf('Controller argument%s "%s" collided with the built-in security expression variables. The built-in value%s are being used for the @Security expression.', $singular ? '' : 's', implode('", "', $diff), $singular ? 's' : ''));
  99. }
  100. }
  101. }
  102. // controller variables should also be accessible
  103. return array_merge($controllerArguments, $variables);
  104. }
  105. private function getRoles(TokenInterface $token): array
  106. {
  107. if (method_exists($this->roleHierarchy, 'getReachableRoleNames')) {
  108. if (null !== $this->roleHierarchy) {
  109. $roles = $this->roleHierarchy->getReachableRoleNames($token->getRoleNames());
  110. } else {
  111. $roles = $token->getRoleNames();
  112. }
  113. } else {
  114. if (null !== $this->roleHierarchy) {
  115. $roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
  116. } else {
  117. $roles = $token->getRoles();
  118. }
  119. $roles = array_map(function ($role) {
  120. return $role->getRole();
  121. }, $roles);
  122. }
  123. return $roles;
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public static function getSubscribedEvents()
  129. {
  130. return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
  131. }
  132. }