vendor/symfony/security-bundle/Security.php line 60

  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 Symfony\Bundle\SecurityBundle;
  11. use Psr\Container\ContainerInterface;
  12. use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  16. use Symfony\Component\Security\Core\Exception\LogicException;
  17. use Symfony\Component\Security\Core\Exception\LogoutException;
  18. use Symfony\Component\Security\Core\Security as LegacySecurity;
  19. use Symfony\Component\Security\Core\User\UserInterface;
  20. use Symfony\Component\Security\Csrf\CsrfToken;
  21. use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
  22. use Symfony\Component\Security\Http\Event\LogoutEvent;
  23. use Symfony\Component\Security\Http\ParameterBagUtils;
  24. use Symfony\Component\Security\Http\SecurityRequestAttributes;
  25. use Symfony\Contracts\Service\ServiceProviderInterface;
  26. /**
  27.  * Helper class for commonly-needed security tasks.
  28.  *
  29.  * @author Ryan Weaver <ryan@symfonycasts.com>
  30.  * @author Robin Chalas <robin.chalas@gmail.com>
  31.  * @author Arnaud Frézet <arnaud@larriereguichet.fr>
  32.  *
  33.  * @final
  34.  */
  35. class Security extends LegacySecurity
  36. {
  37.     public const ACCESS_DENIED_ERROR SecurityRequestAttributes::ACCESS_DENIED_ERROR;
  38.     public const AUTHENTICATION_ERROR SecurityRequestAttributes::AUTHENTICATION_ERROR;
  39.     public const LAST_USERNAME SecurityRequestAttributes::LAST_USERNAME;
  40.     public function __construct(private readonly ContainerInterface $container, private readonly array $authenticators = [])
  41.     {
  42.         parent::__construct($containerfalse);
  43.     }
  44.     public function getFirewallConfig(Request $request): ?FirewallConfig
  45.     {
  46.         return $this->container->get('security.firewall.map')->getFirewallConfig($request);
  47.     }
  48.     /**
  49.      * @param UserInterface $user              The user to authenticate
  50.      * @param string|null   $authenticatorName The authenticator name (e.g. "form_login") or service id (e.g. SomeApiKeyAuthenticator::class) - required only if multiple authenticators are configured
  51.      * @param string|null   $firewallName      The firewall name - required only if multiple firewalls are configured
  52.      */
  53.     public function login(UserInterface $userstring $authenticatorName nullstring $firewallName null): void
  54.     {
  55.         $request $this->container->get('request_stack')->getCurrentRequest();
  56.         $firewallName ??= $this->getFirewallConfig($request)?->getName();
  57.         if (!$firewallName) {
  58.             throw new LogicException('Unable to login as the current route is not covered by any firewall.');
  59.         }
  60.         $authenticator $this->getAuthenticator($authenticatorName$firewallName);
  61.         $this->container->get('security.user_checker')->checkPreAuth($user);
  62.         $this->container->get('security.authenticator.managers_locator')->get($firewallName)->authenticateUser($user$authenticator$request);
  63.     }
  64.     /**
  65.      * Logout the current user by dispatching the LogoutEvent.
  66.      *
  67.      * @param bool $validateCsrfToken Whether to look for a valid CSRF token based on the `logout` listener configuration
  68.      *
  69.      * @return Response|null The LogoutEvent's Response if any
  70.      *
  71.      * @throws LogoutException When $validateCsrfToken is true and the CSRF token is not found or invalid
  72.      */
  73.     public function logout(bool $validateCsrfToken true): ?Response
  74.     {
  75.         /** @var TokenStorageInterface $tokenStorage */
  76.         $tokenStorage $this->container->get('security.token_storage');
  77.         if (!($token $tokenStorage->getToken()) || !$token->getUser()) {
  78.             throw new LogicException('Unable to logout as there is no logged-in user.');
  79.         }
  80.         $request $this->container->get('request_stack')->getMainRequest();
  81.         if (!$firewallConfig $this->container->get('security.firewall.map')->getFirewallConfig($request)) {
  82.             throw new LogicException('Unable to logout as the request is not behind a firewall.');
  83.         }
  84.         if ($validateCsrfToken) {
  85.             if (!$this->container->has('security.csrf.token_manager') || !$logoutConfig $firewallConfig->getLogout()) {
  86.                 throw new LogicException(sprintf('Unable to logout with CSRF token validation. Either make sure that CSRF protection is enabled and "logout" is configured on the "%s" firewall, or bypass CSRF token validation explicitly by passing false to the $validateCsrfToken argument of this method.'$firewallConfig->getName()));
  87.             }
  88.             $csrfToken ParameterBagUtils::getRequestParameterValue($request$logoutConfig['csrf_parameter']);
  89.             if (!\is_string($csrfToken) || !$this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($logoutConfig['csrf_token_id'], $csrfToken))) {
  90.                 throw new LogoutException('Invalid CSRF token.');
  91.             }
  92.         }
  93.         $logoutEvent = new LogoutEvent($request$token);
  94.         $this->container->get('security.firewall.event_dispatcher_locator')->get($firewallConfig->getName())->dispatch($logoutEvent);
  95.         $tokenStorage->setToken(null);
  96.         return $logoutEvent->getResponse();
  97.     }
  98.     private function getAuthenticator(?string $authenticatorNamestring $firewallName): AuthenticatorInterface
  99.     {
  100.         if (!isset($this->authenticators[$firewallName])) {
  101.             throw new LogicException(sprintf('No authenticators found for firewall "%s".'$firewallName));
  102.         }
  103.         /** @var ServiceProviderInterface $firewallAuthenticatorLocator */
  104.         $firewallAuthenticatorLocator $this->authenticators[$firewallName];
  105.         if (!$authenticatorName) {
  106.             $authenticatorIds array_keys($firewallAuthenticatorLocator->getProvidedServices());
  107.             if (!$authenticatorIds) {
  108.                 throw new LogicException(sprintf('No authenticator was found for the firewall "%s".'$firewallName));
  109.             }
  110.             if (\count($authenticatorIds)) {
  111.                 throw new LogicException(sprintf('Too many authenticators were found for the current firewall "%s". You must provide an instance of "%s" to login programmatically. The available authenticators for the firewall "%s" are "%s".'$firewallNameAuthenticatorInterface::class, $firewallNameimplode('" ,"'$authenticatorIds)));
  112.             }
  113.             return $firewallAuthenticatorLocator->get($authenticatorIds[0]);
  114.         }
  115.         if ($firewallAuthenticatorLocator->has($authenticatorName)) {
  116.             return $firewallAuthenticatorLocator->get($authenticatorName);
  117.         }
  118.         $authenticatorId 'security.authenticator.'.$authenticatorName.'.'.$firewallName;
  119.         if (!$firewallAuthenticatorLocator->has($authenticatorId)) {
  120.             throw new LogicException(sprintf('Unable to find an authenticator named "%s" for the firewall "%s". Available authenticators: "%s".'$authenticatorName$firewallNameimplode('", "'array_keys($firewallAuthenticatorLocator->getProvidedServices()))));
  121.         }
  122.         return $firewallAuthenticatorLocator->get($authenticatorId);
  123.     }
  124. }