login consent app sql

This commit is contained in:
2022-05-03 08:54:45 +02:00
parent e7253acfd8
commit f9a6535906
1652 changed files with 187600 additions and 45 deletions

View File

@ -0,0 +1,229 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\SessionUnavailableException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AbstractAuthenticationListener::class);
/**
* The AbstractAuthenticationListener is the preferred base class for all
* browser-/HTTP-based authentication requests.
*
* Subclasses likely have to implement the following:
* - an TokenInterface to hold authentication related data
* - an AuthenticationProvider to perform the actual authentication of the
* token, retrieve the UserInterface implementation from a database, and
* perform the specific account checks using the UserChecker
*
* By default, this listener only is active for a specific path, e.g.
* /login_check. If you want to change this behavior, you can overwrite the
* requiresAuthentication() method.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
abstract class AbstractAuthenticationListener extends AbstractListener
{
protected $options;
protected $logger;
protected $authenticationManager;
protected $providerKey;
protected $httpUtils;
private $tokenStorage;
private $sessionStrategy;
private $dispatcher;
private $successHandler;
private $failureHandler;
private $rememberMeServices;
/**
* @throws \InvalidArgumentException
*/
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->sessionStrategy = $sessionStrategy;
$this->providerKey = $providerKey;
$this->successHandler = $successHandler;
$this->failureHandler = $failureHandler;
$this->options = array_merge([
'check_path' => '/login_check',
'login_path' => '/login',
'always_use_default_target_path' => false,
'default_target_path' => '/',
'target_path_parameter' => '_target_path',
'use_referer' => false,
'failure_path' => null,
'failure_forward' => false,
'require_previous_session' => true,
], $options);
$this->logger = $logger;
$this->dispatcher = $dispatcher;
$this->httpUtils = $httpUtils;
}
/**
* Sets the RememberMeServices implementation to use.
*/
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
{
$this->rememberMeServices = $rememberMeServices;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return $this->requiresAuthentication($request);
}
/**
* Handles form based authentication.
*
* @throws \RuntimeException
* @throws SessionUnavailableException
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
if (!$request->hasSession()) {
throw new \RuntimeException('This authentication method requires a session.');
}
try {
if ($this->options['require_previous_session'] && !$request->hasPreviousSession()) {
throw new SessionUnavailableException('Your session has timed out, or you have disabled cookies.');
}
if (null === $returnValue = $this->attemptAuthentication($request)) {
return;
}
if ($returnValue instanceof TokenInterface) {
$this->sessionStrategy->onAuthentication($request, $returnValue);
$response = $this->onSuccess($request, $returnValue);
} elseif ($returnValue instanceof Response) {
$response = $returnValue;
} else {
throw new \RuntimeException('attemptAuthentication() must either return a Response, an implementation of TokenInterface, or null.');
}
} catch (AuthenticationException $e) {
$response = $this->onFailure($request, $e);
}
$event->setResponse($response);
}
/**
* Whether this request requires authentication.
*
* The default implementation only processes requests to a specific path,
* but a subclass could change this to only authenticate requests where a
* certain parameters is present.
*
* @return bool
*/
protected function requiresAuthentication(Request $request)
{
return $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
}
/**
* Performs authentication.
*
* @return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response
*
* @throws AuthenticationException if the authentication fails
*/
abstract protected function attemptAuthentication(Request $request);
private function onFailure(Request $request, AuthenticationException $failed): Response
{
if (null !== $this->logger) {
$this->logger->info('Authentication request failed.', ['exception' => $failed]);
}
$token = $this->tokenStorage->getToken();
if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) {
$this->tokenStorage->setToken(null);
}
$response = $this->failureHandler->onAuthenticationFailure($request, $failed);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
}
return $response;
}
private function onSuccess(Request $request, TokenInterface $token): Response
{
if (null !== $this->logger) {
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
$this->logger->info('User has been authenticated successfully.', ['username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]);
}
$this->tokenStorage->setToken($token);
$session = $request->getSession();
$session->remove(Security::AUTHENTICATION_ERROR);
$session->remove(Security::LAST_USERNAME);
if (null !== $this->dispatcher) {
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
}
$response = $this->successHandler->onAuthenticationSuccess($request, $token);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Success Handler did not return a Response.');
}
if (null !== $this->rememberMeServices) {
$this->rememberMeServices->loginSuccess($request, $response, $token);
}
return $response;
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpKernel\Event\RequestEvent;
/**
* A base class for listeners that can tell whether they should authenticate incoming requests.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractListener implements FirewallListenerInterface
{
final public function __invoke(RequestEvent $event)
{
if (false !== $this->supports($event->getRequest())) {
$this->authenticate($event);
}
}
public static function getPriority(): int
{
return 0; // Default
}
}

View File

@ -0,0 +1,160 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AbstractPreAuthenticatedListener::class);
/**
* AbstractPreAuthenticatedListener is the base class for all listener that
* authenticates users based on a pre-authenticated request (like a certificate
* for instance).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
abstract class AbstractPreAuthenticatedListener extends AbstractListener
{
protected $logger;
private $tokenStorage;
private $authenticationManager;
private $providerKey;
private $dispatcher;
private $sessionStrategy;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
{
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->providerKey = $providerKey;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
try {
$request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request));
} catch (BadCredentialsException $e) {
$this->clearToken($e);
return false;
}
return true;
}
/**
* Handles pre-authentication.
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
[$user, $credentials] = $request->attributes->get('_pre_authenticated_data');
$request->attributes->remove('_pre_authenticated_data');
if (null !== $this->logger) {
$this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]);
}
if (null !== $token = $this->tokenStorage->getToken()) {
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getFirewallName() && $token->isAuthenticated() && (method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $user) {
return;
}
}
if (null !== $this->logger) {
$this->logger->debug('Trying to pre-authenticate user.', ['username' => (string) $user]);
}
try {
$token = $this->authenticationManager->authenticate(new PreAuthenticatedToken($user, $credentials, $this->providerKey));
if (null !== $this->logger) {
$this->logger->info('Pre-authentication successful.', ['token' => (string) $token]);
}
$this->migrateSession($request, $token);
$this->tokenStorage->setToken($token);
if (null !== $this->dispatcher) {
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
}
} catch (AuthenticationException $e) {
$this->clearToken($e);
}
}
/**
* Call this method if your authentication token is stored to a session.
*
* @final
*/
public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy)
{
$this->sessionStrategy = $sessionStrategy;
}
/**
* Clears a PreAuthenticatedToken for this provider (if present).
*/
private function clearToken(AuthenticationException $exception)
{
$token = $this->tokenStorage->getToken();
if ($token instanceof PreAuthenticatedToken && $this->providerKey === $token->getFirewallName()) {
$this->tokenStorage->setToken(null);
if (null !== $this->logger) {
$this->logger->info('Cleared security token due to an exception.', ['exception' => $exception]);
}
}
}
/**
* Gets the user and credentials from the Request.
*
* @return array An array composed of the user and the credentials
*/
abstract protected function getPreAuthenticatedData(Request $request);
private function migrateSession(Request $request, TokenInterface $token)
{
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {
return;
}
$this->sessionStrategy->onAuthentication($request, $token);
}
}

View File

@ -0,0 +1,143 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\Authentication\NoopAuthenticationManager;
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
/**
* AccessListener enforces access control rules.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class AccessListener extends AbstractListener
{
private $tokenStorage;
private $accessDecisionManager;
private $map;
private $authManager;
private $exceptionOnNoToken;
public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionManagerInterface $accessDecisionManager, AccessMapInterface $map, /*bool*/ $exceptionOnNoToken = true)
{
if ($exceptionOnNoToken instanceof AuthenticationManagerInterface) {
trigger_deprecation('symfony/security-http', '5.4', 'The $authManager argument of "%s" is deprecated.', __METHOD__);
$authManager = $exceptionOnNoToken;
$exceptionOnNoToken = \func_num_args() > 4 ? func_get_arg(4) : true;
}
if (false !== $exceptionOnNoToken) {
trigger_deprecation('symfony/security-http', '5.4', 'Not setting the $exceptionOnNoToken argument of "%s" to "false" is deprecated.', __METHOD__);
}
$this->tokenStorage = $tokenStorage;
$this->accessDecisionManager = $accessDecisionManager;
$this->map = $map;
$this->authManager = $authManager ?? (class_exists(AuthenticationManagerInterface::class) ? new NoopAuthenticationManager() : null);
$this->exceptionOnNoToken = $exceptionOnNoToken;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
[$attributes] = $this->map->getPatterns($request);
$request->attributes->set('_access_control_attributes', $attributes);
if ($attributes && (
(\defined(AuthenticatedVoter::class.'::IS_AUTHENTICATED_ANONYMOUSLY') ? [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes : true)
&& [AuthenticatedVoter::PUBLIC_ACCESS] !== $attributes
)) {
return true;
}
return null;
}
/**
* Handles access authorization.
*
* @throws AccessDeniedException
* @throws AuthenticationCredentialsNotFoundException when the token storage has no authentication token and $exceptionOnNoToken is set to true
*/
public function authenticate(RequestEvent $event)
{
if (!$event instanceof LazyResponseEvent && null === ($token = $this->tokenStorage->getToken()) && $this->exceptionOnNoToken) {
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
}
$request = $event->getRequest();
$attributes = $request->attributes->get('_access_control_attributes');
$request->attributes->remove('_access_control_attributes');
if (!$attributes || ((
(\defined(AuthenticatedVoter::class.'::IS_AUTHENTICATED_ANONYMOUSLY') ? [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes : false)
|| [AuthenticatedVoter::PUBLIC_ACCESS] === $attributes
) && $event instanceof LazyResponseEvent)) {
return;
}
if ($event instanceof LazyResponseEvent) {
$token = $this->tokenStorage->getToken();
}
if (null === $token) {
if ($this->exceptionOnNoToken) {
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
}
$token = new NullToken();
}
// @deprecated since Symfony 5.4
if (method_exists($token, 'isAuthenticated') && !$token->isAuthenticated(false)) {
trigger_deprecation('symfony/core', '5.4', 'Returning false from "%s()" is deprecated, return null from "getUser()" instead.');
if ($this->authManager) {
$token = $this->authManager->authenticate($token);
$this->tokenStorage->setToken($token);
}
}
if (!$this->accessDecisionManager->decide($token, $attributes, $request, true)) {
throw $this->createAccessDeniedException($request, $attributes);
}
}
private function createAccessDeniedException(Request $request, array $attributes)
{
$exception = new AccessDeniedException();
$exception->setAttributes($attributes);
$exception->setSubject($request);
return $exception;
}
public static function getPriority(): int
{
return -255;
}
}

View File

@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationListener::class);
// Help opcache.preload discover always-needed symbols
class_exists(AnonymousToken::class);
/**
* AnonymousAuthenticationListener automatically adds a Token if none is
* already present.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class AnonymousAuthenticationListener extends AbstractListener
{
private $tokenStorage;
private $secret;
private $authenticationManager;
private $logger;
public function __construct(TokenStorageInterface $tokenStorage, string $secret, LoggerInterface $logger = null, AuthenticationManagerInterface $authenticationManager = null)
{
$this->tokenStorage = $tokenStorage;
$this->secret = $secret;
$this->authenticationManager = $authenticationManager;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/**
* Handles anonymous authentication.
*/
public function authenticate(RequestEvent $event)
{
if (null !== $this->tokenStorage->getToken()) {
return;
}
try {
$token = new AnonymousToken($this->secret, 'anon.', []);
if (null !== $this->authenticationManager) {
$token = $this->authenticationManager->authenticate($token);
}
$this->tokenStorage->setToken($token);
if (null !== $this->logger) {
$this->logger->info('Populated the TokenStorage with an anonymous Token.');
}
} catch (AuthenticationException $failed) {
if (null !== $this->logger) {
$this->logger->info('Anonymous authentication failed.', ['exception' => $failed]);
}
}
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\Authentication\AuthenticatorManagerInterface;
/**
* Firewall authentication listener that delegates to the authenticator system.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class AuthenticatorManagerListener extends AbstractListener
{
private $authenticatorManager;
public function __construct(AuthenticatorManagerInterface $authenticationManager)
{
$this->authenticatorManager = $authenticationManager;
}
public function supports(Request $request): ?bool
{
return $this->authenticatorManager->supports($request);
}
public function authenticate(RequestEvent $event): void
{
$request = $event->getRequest();
$response = $this->authenticatorManager->authenticateRequest($request);
if (null === $response) {
return;
}
$event->setResponse($response);
}
}

View File

@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', AnonymousAuthenticationListener::class);
/**
* BasicAuthenticationListener implements Basic HTTP authentication.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class BasicAuthenticationListener extends AbstractListener
{
private $tokenStorage;
private $authenticationManager;
private $providerKey;
private $authenticationEntryPoint;
private $logger;
private $ignoreFailure;
private $sessionStrategy;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, AuthenticationEntryPointInterface $authenticationEntryPoint, LoggerInterface $logger = null)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->providerKey = $providerKey;
$this->authenticationEntryPoint = $authenticationEntryPoint;
$this->logger = $logger;
$this->ignoreFailure = false;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null !== $request->headers->get('PHP_AUTH_USER');
}
/**
* Handles basic authentication.
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
if (null === $username = $request->headers->get('PHP_AUTH_USER')) {
return;
}
if (null !== $token = $this->tokenStorage->getToken()) {
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
if ($token instanceof UsernamePasswordToken && $token->isAuthenticated(false) && (method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) {
return;
}
}
if (null !== $this->logger) {
$this->logger->info('Basic authentication Authorization header found for user.', ['username' => $username]);
}
try {
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
$this->migrateSession($request, $token);
$this->tokenStorage->setToken($token);
} catch (AuthenticationException $e) {
$token = $this->tokenStorage->getToken();
if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) {
$this->tokenStorage->setToken(null);
}
if (null !== $this->logger) {
$this->logger->info('Basic authentication failed for user.', ['username' => $username, 'exception' => $e]);
}
if ($this->ignoreFailure) {
return;
}
$event->setResponse($this->authenticationEntryPoint->start($request, $e));
}
}
/**
* Call this method if your authentication token is stored to a session.
*
* @final
*/
public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy)
{
$this->sessionStrategy = $sessionStrategy;
}
private function migrateSession(Request $request, TokenInterface $token)
{
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {
return;
}
$this->sessionStrategy->onAuthentication($request, $token);
}
}

View File

@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Http\AccessMapInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
/**
* ChannelListener switches the HTTP protocol based on the access control
* configuration.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ChannelListener extends AbstractListener
{
private $map;
private $authenticationEntryPoint = null;
private $logger;
private $httpPort;
private $httpsPort;
public function __construct(AccessMapInterface $map, /*LoggerInterface*/ $logger = null, /*int*/ $httpPort = 80, /*int*/ $httpsPort = 443)
{
if ($logger instanceof AuthenticationEntryPointInterface) {
trigger_deprecation('symfony/security-http', '5.4', 'The "$authenticationEntryPoint" argument of "%s()" is deprecated.', __METHOD__);
$this->authenticationEntryPoint = $logger;
$nrOfArgs = \func_num_args();
$logger = $nrOfArgs > 2 ? func_get_arg(2) : null;
$httpPort = $nrOfArgs > 3 ? func_get_arg(3) : 80;
$httpsPort = $nrOfArgs > 4 ? func_get_arg(4) : 443;
}
if (null !== $logger && !$logger instanceof LoggerInterface) {
throw new \TypeError(sprintf('Argument "$logger" of "%s()" must be instance of "%s", "%s" given.', __METHOD__, LoggerInterface::class, get_debug_type($logger)));
}
$this->map = $map;
$this->logger = $logger;
$this->httpPort = $httpPort;
$this->httpsPort = $httpsPort;
}
/**
* Handles channel management.
*/
public function supports(Request $request): ?bool
{
[, $channel] = $this->map->getPatterns($request);
if ('https' === $channel && !$request->isSecure()) {
if (null !== $this->logger) {
if ('https' === $request->headers->get('X-Forwarded-Proto')) {
$this->logger->info('Redirecting to HTTPS. ("X-Forwarded-Proto" header is set to "https" - did you set "trusted_proxies" correctly?)');
} elseif (str_contains($request->headers->get('Forwarded', ''), 'proto=https')) {
$this->logger->info('Redirecting to HTTPS. ("Forwarded" header is set to "proto=https" - did you set "trusted_proxies" correctly?)');
} else {
$this->logger->info('Redirecting to HTTPS.');
}
}
return true;
}
if ('http' === $channel && $request->isSecure()) {
if (null !== $this->logger) {
$this->logger->info('Redirecting to HTTP.');
}
return true;
}
return false;
}
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$event->setResponse($this->createRedirectResponse($request));
}
private function createRedirectResponse(Request $request): RedirectResponse
{
if (null !== $this->authenticationEntryPoint) {
return $this->authenticationEntryPoint->start($request);
}
$scheme = $request->isSecure() ? 'http' : 'https';
if ('http' === $scheme && 80 != $this->httpPort) {
$port = ':'.$this->httpPort;
} elseif ('https' === $scheme && 443 != $this->httpsPort) {
$port = ':'.$this->httpsPort;
} else {
$port = '';
}
$qs = $request->getQueryString();
if (null !== $qs) {
$qs = '?'.$qs;
}
$url = $scheme.'://'.$request->getHost().$port.$request->getBaseUrl().$request->getPathInfo().$qs;
return new RedirectResponse($url, 301);
}
}

View File

@ -0,0 +1,402 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
use Symfony\Component\Security\Http\Event\TokenDeauthenticatedEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* ContextListener manages the SecurityContext persistence through a session.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final
*/
class ContextListener extends AbstractListener
{
private $tokenStorage;
private $sessionKey;
private $logger;
private $userProviders;
private $dispatcher;
private $registered;
private $trustResolver;
private $rememberMeServices;
private $sessionTrackerEnabler;
/**
* @param iterable<mixed, UserProviderInterface> $userProviders
*/
public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null, callable $sessionTrackerEnabler = null)
{
if (empty($contextKey)) {
throw new \InvalidArgumentException('$contextKey must not be empty.');
}
$this->tokenStorage = $tokenStorage;
$this->userProviders = $userProviders;
$this->sessionKey = '_security_'.$contextKey;
$this->logger = $logger;
$this->dispatcher = class_exists(Event::class) ? LegacyEventDispatcherProxy::decorate($dispatcher) : $dispatcher;
$this->trustResolver = $trustResolver ?? new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
$this->sessionTrackerEnabler = $sessionTrackerEnabler;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/**
* Reads the Security Token from the session.
*/
public function authenticate(RequestEvent $event)
{
if (!$this->registered && null !== $this->dispatcher && $event->isMainRequest()) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
$this->registered = true;
}
$request = $event->getRequest();
$session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
$request->attributes->set('_security_firewall_run', $this->sessionKey);
if (null !== $session) {
$usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : 0;
$usageIndexReference = \PHP_INT_MIN;
$sessionId = $request->cookies->all()[$session->getName()] ?? null;
$token = $session->get($this->sessionKey);
// sessionId = true is used in the tests
if ($this->sessionTrackerEnabler && \in_array($sessionId, [true, $session->getId()], true)) {
$usageIndexReference = $usageIndexValue;
} else {
$usageIndexReference = $usageIndexReference - \PHP_INT_MIN + $usageIndexValue;
}
}
if (null === $session || null === $token) {
if ($this->sessionTrackerEnabler) {
($this->sessionTrackerEnabler)();
}
$this->tokenStorage->setToken(null);
return;
}
$token = $this->safelyUnserialize($token);
if (null !== $this->logger) {
$this->logger->debug('Read existing security token from the session.', [
'key' => $this->sessionKey,
'token_class' => \is_object($token) ? \get_class($token) : null,
]);
}
if ($token instanceof TokenInterface) {
$originalToken = $token;
$token = $this->refreshUser($token);
if (!$token) {
if ($this->logger) {
$this->logger->debug('Token was deauthenticated after trying to refresh it.');
}
if ($this->dispatcher) {
$this->dispatcher->dispatch(new TokenDeauthenticatedEvent($originalToken, $request));
}
if ($this->rememberMeServices) {
$this->rememberMeServices->loginFail($request);
}
}
} elseif (null !== $token) {
if (null !== $this->logger) {
$this->logger->warning('Expected a security token from the session, got something else.', ['key' => $this->sessionKey, 'received' => $token]);
}
$token = null;
}
if ($this->sessionTrackerEnabler) {
($this->sessionTrackerEnabler)();
}
$this->tokenStorage->setToken($token);
}
/**
* Writes the security token into the session.
*/
public function onKernelResponse(ResponseEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if (!$request->hasSession() || $request->attributes->get('_security_firewall_run') !== $this->sessionKey) {
return;
}
if ($this->dispatcher) {
$this->dispatcher->removeListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
}
$this->registered = false;
$session = $request->getSession();
$sessionId = $session->getId();
$usageIndexValue = $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : null;
$token = $this->tokenStorage->getToken();
// @deprecated always use isAuthenticated() in 6.0
$notAuthenticated = method_exists($this->trustResolver, 'isAuthenticated') ? !$this->trustResolver->isAuthenticated($token) : (null === $token || $this->trustResolver->isAnonymous($token));
if ($notAuthenticated) {
if ($request->hasPreviousSession()) {
$session->remove($this->sessionKey);
}
} else {
$session->set($this->sessionKey, serialize($token));
if (null !== $this->logger) {
$this->logger->debug('Stored the security token in the session.', ['key' => $this->sessionKey]);
}
}
if ($this->sessionTrackerEnabler && $session->getId() === $sessionId) {
$usageIndexReference = $usageIndexValue;
}
}
/**
* Refreshes the user by reloading it from the user provider.
*
* @throws \RuntimeException
*/
protected function refreshUser(TokenInterface $token): ?TokenInterface
{
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return $token;
}
$userNotFoundByProvider = false;
$userDeauthenticated = false;
$userClass = \get_class($user);
foreach ($this->userProviders as $provider) {
if (!$provider instanceof UserProviderInterface) {
throw new \InvalidArgumentException(sprintf('User provider "%s" must implement "%s".', get_debug_type($provider), UserProviderInterface::class));
}
if (!$provider->supportsClass($userClass)) {
continue;
}
try {
$refreshedUser = $provider->refreshUser($user);
$newToken = clone $token;
$newToken->setUser($refreshedUser, false);
// tokens can be deauthenticated if the user has been changed.
if ($token instanceof AbstractToken && $this->hasUserChanged($user, $newToken)) {
$userDeauthenticated = true;
// @deprecated since Symfony 5.4
if (method_exists($newToken, 'setAuthenticated')) {
$newToken->setAuthenticated(false, false);
}
if (null !== $this->logger) {
// @deprecated since Symfony 5.3, change to $refreshedUser->getUserIdentifier() in 6.0
$this->logger->debug('Cannot refresh token because user has changed.', ['username' => method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername(), 'provider' => \get_class($provider)]);
}
continue;
}
$token->setUser($refreshedUser);
if (null !== $this->logger) {
// @deprecated since Symfony 5.3, change to $refreshedUser->getUserIdentifier() in 6.0
$context = ['provider' => \get_class($provider), 'username' => method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername()];
if ($token instanceof SwitchUserToken) {
$originalToken = $token->getOriginalToken();
// @deprecated since Symfony 5.3, change to $originalToken->getUserIdentifier() in 6.0
$context['impersonator_username'] = method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername();
}
$this->logger->debug('User was reloaded from a user provider.', $context);
}
return $token;
} catch (UnsupportedUserException $e) {
// let's try the next user provider
} catch (UserNotFoundException $e) {
if (null !== $this->logger) {
$this->logger->warning('Username could not be found in the selected user provider.', ['username' => method_exists($e, 'getUserIdentifier') ? $e->getUserIdentifier() : $e->getUsername(), 'provider' => \get_class($provider)]);
}
$userNotFoundByProvider = true;
}
}
if ($userDeauthenticated) {
// @deprecated since Symfony 5.4
if ($this->dispatcher) {
$this->dispatcher->dispatch(new DeauthenticatedEvent($token, $newToken, false), DeauthenticatedEvent::class);
}
return null;
}
if ($userNotFoundByProvider) {
return null;
}
throw new \RuntimeException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', $userClass));
}
private function safelyUnserialize(string $serializedToken)
{
$token = null;
$prevUnserializeHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler) {
if (__FILE__ === $file) {
throw new \ErrorException($msg, 0x37313BC, $type, $file, $line);
}
return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
});
try {
$token = unserialize($serializedToken);
} catch (\ErrorException $e) {
if (0x37313BC !== $e->getCode()) {
throw $e;
}
if ($this->logger) {
$this->logger->warning('Failed to unserialize the security token from the session.', ['key' => $this->sessionKey, 'received' => $serializedToken, 'exception' => $e]);
}
} finally {
restore_error_handler();
ini_set('unserialize_callback_func', $prevUnserializeHandler);
}
return $token;
}
/**
* @param string|\Stringable|UserInterface $originalUser
*/
private static function hasUserChanged($originalUser, TokenInterface $refreshedToken): bool
{
$refreshedUser = $refreshedToken->getUser();
if ($originalUser instanceof UserInterface) {
if (!$refreshedUser instanceof UserInterface) {
return true;
} else {
// noop
}
} elseif ($refreshedUser instanceof UserInterface) {
return true;
} else {
return (string) $originalUser !== (string) $refreshedUser;
}
if ($originalUser instanceof EquatableInterface) {
return !(bool) $originalUser->isEqualTo($refreshedUser);
}
// @deprecated since Symfony 5.3, check for PasswordAuthenticatedUserInterface on both user objects before comparing passwords
if ($originalUser->getPassword() !== $refreshedUser->getPassword()) {
return true;
}
// @deprecated since Symfony 5.3, check for LegacyPasswordAuthenticatedUserInterface on both user objects before comparing salts
if ($originalUser->getSalt() !== $refreshedUser->getSalt()) {
return true;
}
$userRoles = array_map('strval', (array) $refreshedUser->getRoles());
if ($refreshedToken instanceof SwitchUserToken) {
$userRoles[] = 'ROLE_PREVIOUS_ADMIN';
}
if (
\count($userRoles) !== \count($refreshedToken->getRoleNames()) ||
\count($userRoles) !== \count(array_intersect($userRoles, $refreshedToken->getRoleNames()))
) {
return true;
}
// @deprecated since Symfony 5.3, drop getUsername() in 6.0
$userIdentifier = function ($refreshedUser) {
return method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername();
};
if ($userIdentifier($originalUser) !== $userIdentifier($refreshedUser)) {
return true;
}
return false;
}
/**
* @internal
*/
public static function handleUnserializeCallback(string $class)
{
throw new \ErrorException('Class not found: '.$class, 0x37313BC);
}
/**
* @deprecated since Symfony 5.4
*/
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
{
trigger_deprecation('symfony/security-http', '5.4', 'Method "%s()" is deprecated, use the new remember me handlers instead.', __METHOD__);
$this->rememberMeServices = $rememberMeServices;
}
}

View File

@ -0,0 +1,250 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
/**
* ExceptionListener catches authentication exception and converts them to
* Response instances.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ExceptionListener
{
use TargetPathTrait;
private $tokenStorage;
private $firewallName;
private $accessDeniedHandler;
private $authenticationEntryPoint;
private $authenticationTrustResolver;
private $errorPage;
private $logger;
private $httpUtils;
private $stateless;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationTrustResolverInterface $trustResolver, HttpUtils $httpUtils, string $firewallName, AuthenticationEntryPointInterface $authenticationEntryPoint = null, string $errorPage = null, AccessDeniedHandlerInterface $accessDeniedHandler = null, LoggerInterface $logger = null, bool $stateless = false)
{
$this->tokenStorage = $tokenStorage;
$this->accessDeniedHandler = $accessDeniedHandler;
$this->httpUtils = $httpUtils;
$this->firewallName = $firewallName;
$this->authenticationEntryPoint = $authenticationEntryPoint;
$this->authenticationTrustResolver = $trustResolver;
$this->errorPage = $errorPage;
$this->logger = $logger;
$this->stateless = $stateless;
}
/**
* Registers a onKernelException listener to take care of security exceptions.
*/
public function register(EventDispatcherInterface $dispatcher)
{
$dispatcher->addListener(KernelEvents::EXCEPTION, [$this, 'onKernelException'], 1);
}
/**
* Unregisters the dispatcher.
*/
public function unregister(EventDispatcherInterface $dispatcher)
{
$dispatcher->removeListener(KernelEvents::EXCEPTION, [$this, 'onKernelException']);
}
/**
* Handles security related exceptions.
*/
public function onKernelException(ExceptionEvent $event)
{
$exception = $event->getThrowable();
do {
if ($exception instanceof AuthenticationException) {
$this->handleAuthenticationException($event, $exception);
return;
}
if ($exception instanceof AccessDeniedException) {
$this->handleAccessDeniedException($event, $exception);
return;
}
if ($exception instanceof LazyResponseException) {
$event->setResponse($exception->getResponse());
return;
}
if ($exception instanceof LogoutException) {
$this->handleLogoutException($event, $exception);
return;
}
} while (null !== $exception = $exception->getPrevious());
}
private function handleAuthenticationException(ExceptionEvent $event, AuthenticationException $exception): void
{
if (null !== $this->logger) {
$this->logger->info('An AuthenticationException was thrown; redirecting to authentication entry point.', ['exception' => $exception]);
}
try {
$event->setResponse($this->startAuthentication($event->getRequest(), $exception));
$event->allowCustomResponseCode();
} catch (\Exception $e) {
$event->setThrowable($e);
}
}
private function handleAccessDeniedException(ExceptionEvent $event, AccessDeniedException $exception)
{
$event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception));
$token = $this->tokenStorage->getToken();
if (!$this->authenticationTrustResolver->isFullFledged($token)) {
if (null !== $this->logger) {
$this->logger->debug('Access denied, the user is not fully authenticated; redirecting to authentication entry point.', ['exception' => $exception]);
}
try {
$insufficientAuthenticationException = new InsufficientAuthenticationException('Full authentication is required to access this resource.', 0, $exception);
if (null !== $token) {
$insufficientAuthenticationException->setToken($token);
}
$event->setResponse($this->startAuthentication($event->getRequest(), $insufficientAuthenticationException));
} catch (\Exception $e) {
$event->setThrowable($e);
}
return;
}
if (null !== $this->logger) {
$this->logger->debug('Access denied, the user is neither anonymous, nor remember-me.', ['exception' => $exception]);
}
try {
if (null !== $this->accessDeniedHandler) {
$response = $this->accessDeniedHandler->handle($event->getRequest(), $exception);
if ($response instanceof Response) {
$event->setResponse($response);
}
} elseif (null !== $this->errorPage) {
$subRequest = $this->httpUtils->createRequest($event->getRequest(), $this->errorPage);
$subRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $exception);
$event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true));
$event->allowCustomResponseCode();
}
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->error('An exception was thrown when handling an AccessDeniedException.', ['exception' => $e]);
}
$event->setThrowable(new \RuntimeException('Exception thrown when handling an exception.', 0, $e));
}
}
private function handleLogoutException(ExceptionEvent $event, LogoutException $exception): void
{
$event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception));
if (null !== $this->logger) {
$this->logger->info('A LogoutException was thrown; wrapping with AccessDeniedHttpException', ['exception' => $exception]);
}
}
private function startAuthentication(Request $request, AuthenticationException $authException): Response
{
if (null === $this->authenticationEntryPoint) {
$this->throwUnauthorizedException($authException);
}
if (null !== $this->logger) {
$this->logger->debug('Calling Authentication entry point.');
}
if (!$this->stateless) {
$this->setTargetPath($request);
}
if ($authException instanceof AccountStatusException) {
// remove the security token to prevent infinite redirect loops
$this->tokenStorage->setToken(null);
if (null !== $this->logger) {
$this->logger->info('The security token was removed due to an AccountStatusException.', ['exception' => $authException]);
}
}
try {
$response = $this->authenticationEntryPoint->start($request, $authException);
} catch (NotAnEntryPointException $e) {
$this->throwUnauthorizedException($authException);
}
if (!$response instanceof Response) {
$given = get_debug_type($response);
throw new \LogicException(sprintf('The "%s::start()" method must return a Response object ("%s" returned).', get_debug_type($this->authenticationEntryPoint), $given));
}
return $response;
}
protected function setTargetPath(Request $request)
{
// session isn't required when using HTTP basic authentication mechanism for example
if ($request->hasSession() && $request->isMethodSafe() && !$request->isXmlHttpRequest()) {
$this->saveTargetPath($request->getSession(), $this->firewallName, $request->getUri());
}
}
private function throwUnauthorizedException(AuthenticationException $authException)
{
if (null !== $this->logger) {
$this->logger->notice(sprintf('No Authentication entry point configured, returning a %s HTTP response. Configure "entry_point" on the firewall "%s" if you want to modify the response.', Response::HTTP_UNAUTHORIZED, $this->firewallName));
}
throw new HttpException(Response::HTTP_UNAUTHORIZED, $authException->getMessage(), $authException, [], $authException->getCode());
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
/**
* Can be implemented by firewall listeners.
*
* @author Christian Scheb <me@christianscheb.de>
* @author Nicolas Grekas <p@tchwork.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface FirewallListenerInterface
{
/**
* Tells whether the authenticate() method should be called or not depending on the incoming request.
*
* Returning null means authenticate() can be called lazily when accessing the token storage.
*/
public function supports(Request $request): ?bool;
/**
* Does whatever is required to authenticate the request, typically calling $event->setResponse() internally.
*/
public function authenticate(RequestEvent $event);
/**
* Defines the priority of the listener.
* The higher the number, the earlier a listener is executed.
*/
public static function getPriority(): int;
}

View File

@ -0,0 +1,150 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\LogicException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* LogoutListener logout users.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class LogoutListener extends AbstractListener
{
private $tokenStorage;
private $options;
private $httpUtils;
private $csrfTokenManager;
private $eventDispatcher;
/**
* @param EventDispatcherInterface $eventDispatcher
* @param array $options An array of options to process a logout attempt
*/
public function __construct(TokenStorageInterface $tokenStorage, HttpUtils $httpUtils, $eventDispatcher, array $options = [], CsrfTokenManagerInterface $csrfTokenManager = null)
{
if (!$eventDispatcher instanceof EventDispatcherInterface) {
trigger_deprecation('symfony/security-http', '5.1', 'Passing a logout success handler to "%s" is deprecated, pass an instance of "%s" instead.', __METHOD__, EventDispatcherInterface::class);
if (!$eventDispatcher instanceof LogoutSuccessHandlerInterface) {
throw new \TypeError(sprintf('Argument 3 of "%s" must be instance of "%s" or "%s", "%s" given.', __METHOD__, EventDispatcherInterface::class, LogoutSuccessHandlerInterface::class, get_debug_type($eventDispatcher)));
}
$successHandler = $eventDispatcher;
$eventDispatcher = new EventDispatcher();
$eventDispatcher->addListener(LogoutEvent::class, function (LogoutEvent $event) use ($successHandler) {
$event->setResponse($r = $successHandler->onLogoutSuccess($event->getRequest()));
});
}
$this->tokenStorage = $tokenStorage;
$this->httpUtils = $httpUtils;
$this->options = array_merge([
'csrf_parameter' => '_csrf_token',
'csrf_token_id' => 'logout',
'logout_path' => '/logout',
], $options);
$this->csrfTokenManager = $csrfTokenManager;
$this->eventDispatcher = $eventDispatcher;
}
/**
* @deprecated since Symfony 5.1
*/
public function addHandler(LogoutHandlerInterface $handler)
{
trigger_deprecation('symfony/security-http', '5.1', 'Calling "%s" is deprecated, register a listener on the "%s" event instead.', __METHOD__, LogoutEvent::class);
$this->eventDispatcher->addListener(LogoutEvent::class, function (LogoutEvent $event) use ($handler) {
if (null === $event->getResponse()) {
throw new LogicException(sprintf('No response was set for this logout action. Make sure the DefaultLogoutListener or another listener has set the response before "%s" is called.', __CLASS__));
}
$handler->logout($event->getRequest(), $event->getResponse(), $event->getToken());
});
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return $this->requiresLogout($request);
}
/**
* Performs the logout if requested.
*
* If a CsrfTokenManagerInterface instance is available, it will be used to
* validate the request.
*
* @throws LogoutException if the CSRF token is invalid
* @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
if (null !== $this->csrfTokenManager) {
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) {
throw new LogoutException('Invalid CSRF token.');
}
}
$logoutEvent = new LogoutEvent($request, $this->tokenStorage->getToken());
$this->eventDispatcher->dispatch($logoutEvent);
$response = $logoutEvent->getResponse();
if (!$response instanceof Response) {
throw new \RuntimeException('No logout listener set the Response, make sure at least the DefaultLogoutListener is registered.');
}
$this->tokenStorage->setToken(null);
$event->setResponse($response);
}
/**
* Whether this request is asking for logout.
*
* The default implementation only processed requests to a specific path,
* but a subclass could change this to logout requests where
* certain parameters is present.
*/
protected function requiresLogout(Request $request): bool
{
return isset($this->options['logout_path']) && $this->httpUtils->checkRequestPath($request, $this->options['logout_path']);
}
public static function getPriority(): int
{
return -127;
}
}

View File

@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RememberMeListener::class);
/**
* RememberMeListener implements authentication capabilities via a cookie.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class RememberMeListener extends AbstractListener
{
private $tokenStorage;
private $rememberMeServices;
private $authenticationManager;
private $logger;
private $dispatcher;
private $catchExceptions = true;
private $sessionStrategy;
public function __construct(TokenStorageInterface $tokenStorage, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, bool $catchExceptions = true, SessionAuthenticationStrategyInterface $sessionStrategy = null)
{
$this->tokenStorage = $tokenStorage;
$this->rememberMeServices = $rememberMeServices;
$this->authenticationManager = $authenticationManager;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
$this->catchExceptions = $catchExceptions;
$this->sessionStrategy = $sessionStrategy ?? new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE);
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
return null; // always run authenticate() lazily with lazy firewalls
}
/**
* Handles remember-me cookie based authentication.
*/
public function authenticate(RequestEvent $event)
{
if (null !== $this->tokenStorage->getToken()) {
return;
}
$request = $event->getRequest();
try {
if (null === $token = $this->rememberMeServices->autoLogin($request)) {
return;
}
} catch (AuthenticationException $e) {
if (null !== $this->logger) {
$this->logger->warning(
'The token storage was not populated with remember-me token as the'
.' RememberMeServices was not able to create a token from the remember'
.' me information.', ['exception' => $e]
);
}
$this->rememberMeServices->loginFail($request);
if (!$this->catchExceptions) {
throw $e;
}
return;
}
try {
$token = $this->authenticationManager->authenticate($token);
if ($request->hasSession() && $request->getSession()->isStarted()) {
$this->sessionStrategy->onAuthentication($request, $token);
}
$this->tokenStorage->setToken($token);
if (null !== $this->dispatcher) {
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->dispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
}
if (null !== $this->logger) {
$this->logger->debug('Populated the token storage with a remember-me token.');
}
} catch (AuthenticationException $e) {
if (null !== $this->logger) {
$this->logger->warning(
'The token storage was not populated with remember-me token as the'
.' AuthenticationManager rejected the AuthenticationToken returned'
.' by the RememberMeServices.', ['exception' => $e]
);
}
$this->rememberMeServices->loginFail($request, $e);
if (!$this->catchExceptions) {
throw $e;
}
}
}
}

View File

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', RemoteUserAuthenticationListener::class);
/**
* REMOTE_USER authentication listener.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Maxime Douailin <maxime.douailin@gmail.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class RemoteUserAuthenticationListener extends AbstractPreAuthenticatedListener
{
private $userKey;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, string $userKey = 'REMOTE_USER', LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
{
parent::__construct($tokenStorage, $authenticationManager, $providerKey, $logger, $dispatcher);
$this->userKey = $userKey;
}
/**
* {@inheritdoc}
*/
protected function getPreAuthenticatedData(Request $request)
{
if (!$request->server->has($this->userKey)) {
throw new BadCredentialsException(sprintf('User key was not found: "%s".', $this->userKey));
}
return [$request->server->get($this->userKey), null];
}
}

View File

@ -0,0 +1,235 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* SwitchUserListener allows a user to impersonate another one temporarily
* (like the Unix su command).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class SwitchUserListener extends AbstractListener
{
public const EXIT_VALUE = '_exit';
private $tokenStorage;
private $provider;
private $userChecker;
private $firewallName;
private $accessDecisionManager;
private $usernameParameter;
private $role;
private $logger;
private $dispatcher;
private $stateless;
public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, string $firewallName, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, string $usernameParameter = '_switch_user', string $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, bool $stateless = false)
{
if ('' === $firewallName) {
throw new \InvalidArgumentException('$firewallName must not be empty.');
}
$this->tokenStorage = $tokenStorage;
$this->provider = $provider;
$this->userChecker = $userChecker;
$this->firewallName = $firewallName;
$this->accessDecisionManager = $accessDecisionManager;
$this->usernameParameter = $usernameParameter;
$this->role = $role;
$this->logger = $logger;
$this->dispatcher = $dispatcher;
$this->stateless = $stateless;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request): ?bool
{
// usernames can be falsy
$username = $request->get($this->usernameParameter);
if (null === $username || '' === $username) {
$username = $request->headers->get($this->usernameParameter);
}
// if it's still "empty", nothing to do.
if (null === $username || '' === $username) {
return false;
}
$request->attributes->set('_switch_user_username', $username);
return true;
}
/**
* Handles the switch to another user.
*
* @throws \LogicException if switching to a user failed
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$username = $request->attributes->get('_switch_user_username');
$request->attributes->remove('_switch_user_username');
if (null === $this->tokenStorage->getToken()) {
throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.');
}
if (self::EXIT_VALUE === $username) {
$this->tokenStorage->setToken($this->attemptExitUser($request));
} else {
try {
$this->tokenStorage->setToken($this->attemptSwitchUser($request, $username));
} catch (AuthenticationException $e) {
// Generate 403 in any conditions to prevent user enumeration vulnerabilities
throw new AccessDeniedException('Switch User failed: '.$e->getMessage(), $e);
}
}
if (!$this->stateless) {
$request->query->remove($this->usernameParameter);
$request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&'));
$response = new RedirectResponse($request->getUri(), 302);
$event->setResponse($response);
}
}
/**
* Attempts to switch to another user and returns the new token if successfully switched.
*
* @throws \LogicException
* @throws AccessDeniedException
*/
private function attemptSwitchUser(Request $request, string $username): ?TokenInterface
{
$token = $this->tokenStorage->getToken();
$originalToken = $this->getOriginalToken($token);
if (null !== $originalToken) {
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
if ((method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) {
return $token;
}
// User already switched, exit before seamlessly switching to another user
$token = $this->attemptExitUser($request);
}
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
$currentUsername = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername();
$nonExistentUsername = '_'.md5(random_bytes(8).$username);
// To protect against user enumeration via timing measurements
// we always load both successfully and unsuccessfully
$methodName = 'loadUserByIdentifier';
if (!method_exists($this->provider, $methodName)) {
trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->provider));
$methodName = 'loadUserByUsername';
}
try {
$user = $this->provider->$methodName($username);
try {
$this->provider->$methodName($nonExistentUsername);
} catch (\Exception $e) {
}
} catch (AuthenticationException $e) {
$this->provider->$methodName($currentUsername);
throw $e;
}
if (false === $this->accessDecisionManager->decide($token, [$this->role], $user)) {
$exception = new AccessDeniedException();
$exception->setAttributes($this->role);
throw $exception;
}
if (null !== $this->logger) {
$this->logger->info('Attempting to switch to user.', ['username' => $username]);
}
$this->userChecker->checkPostAuth($user);
$roles = $user->getRoles();
$roles[] = 'ROLE_PREVIOUS_ADMIN';
$originatedFromUri = str_replace('/&', '/?', preg_replace('#[&?]'.$this->usernameParameter.'=[^&]*#', '', $request->getRequestUri()));
$token = new SwitchUserToken($user, $this->firewallName, $roles, $token, $originatedFromUri);
if (null !== $this->dispatcher) {
$switchEvent = new SwitchUserEvent($request, $token->getUser(), $token);
$this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER);
// use the token from the event in case any listeners have replaced it.
$token = $switchEvent->getToken();
}
return $token;
}
/**
* Attempts to exit from an already switched user and returns the original token.
*
* @throws AuthenticationCredentialsNotFoundException
*/
private function attemptExitUser(Request $request): TokenInterface
{
if (null === ($currentToken = $this->tokenStorage->getToken()) || null === $original = $this->getOriginalToken($currentToken)) {
throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.');
}
if (null !== $this->dispatcher && $original->getUser() instanceof UserInterface) {
$user = $this->provider->refreshUser($original->getUser());
$original->setUser($user);
$switchEvent = new SwitchUserEvent($request, $user, $original);
$this->dispatcher->dispatch($switchEvent, SecurityEvents::SWITCH_USER);
$original = $switchEvent->getToken();
}
return $original;
}
private function getOriginalToken(TokenInterface $token): ?TokenInterface
{
if ($token instanceof SwitchUserToken) {
return $token->getOriginalToken();
}
return null;
}
}

View File

@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\ParameterBagUtils;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UsernamePasswordFormAuthenticationListener::class);
/**
* UsernamePasswordFormAuthenticationListener is the default implementation of
* an authentication via a simple form composed of a username and a password.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener
{
private $csrfTokenManager;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null)
{
parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge([
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_token_id' => 'authenticate',
'post_only' => true,
], $options), $logger, $dispatcher);
$this->csrfTokenManager = $csrfTokenManager;
}
/**
* {@inheritdoc}
*/
protected function requiresAuthentication(Request $request)
{
if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
return parent::requiresAuthentication($request);
}
/**
* {@inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if (null !== $this->csrfTokenManager) {
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
if ($this->options['post_only']) {
$username = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
$password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
} else {
$username = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']);
$password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
}
if (!\is_string($username) && (!\is_object($username) || !method_exists($username, '__toString'))) {
throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], get_debug_type($username)));
}
$username = trim($username);
if (\strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
if (null === $password) {
throw new \LogicException(sprintf('The key "%s" cannot be null; check that the password field name of the form matches.', $this->options['password_parameter']));
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey));
}
}

View File

@ -0,0 +1,235 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\PropertyAccess\Exception\AccessException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', UsernamePasswordJsonAuthenticationListener::class);
/**
* UsernamePasswordJsonAuthenticationListener is a stateless implementation of
* an authentication via a JSON document composed of a username and a password.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class UsernamePasswordJsonAuthenticationListener extends AbstractListener
{
private $tokenStorage;
private $authenticationManager;
private $httpUtils;
private $providerKey;
private $successHandler;
private $failureHandler;
private $options;
private $logger;
private $eventDispatcher;
private $propertyAccessor;
private $sessionStrategy;
/**
* @var TranslatorInterface|null
*/
private $translator;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null)
{
$this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager;
$this->httpUtils = $httpUtils;
$this->providerKey = $providerKey;
$this->successHandler = $successHandler;
$this->failureHandler = $failureHandler;
$this->logger = $logger;
$this->eventDispatcher = $eventDispatcher;
$this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
public function supports(Request $request): ?bool
{
if (!str_contains($request->getRequestFormat() ?? '', 'json')
&& !str_contains($request->getContentType() ?? '', 'json')
) {
return false;
}
if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function authenticate(RequestEvent $event)
{
$request = $event->getRequest();
$data = json_decode($request->getContent());
try {
if (!$data instanceof \stdClass) {
throw new BadRequestHttpException('Invalid JSON.');
}
try {
$username = $this->propertyAccessor->getValue($data, $this->options['username_path']);
} catch (AccessException $e) {
throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['username_path']), $e);
}
try {
$password = $this->propertyAccessor->getValue($data, $this->options['password_path']);
} catch (AccessException $e) {
throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['password_path']), $e);
}
if (!\is_string($username)) {
throw new BadRequestHttpException(sprintf('The key "%s" must be a string.', $this->options['username_path']));
}
if (\strlen($username) > Security::MAX_USERNAME_LENGTH) {
throw new BadCredentialsException('Invalid username.');
}
if (!\is_string($password)) {
throw new BadRequestHttpException(sprintf('The key "%s" must be a string.', $this->options['password_path']));
}
$token = new UsernamePasswordToken($username, $password, $this->providerKey);
$authenticatedToken = $this->authenticationManager->authenticate($token);
$response = $this->onSuccess($request, $authenticatedToken);
} catch (AuthenticationException $e) {
$response = $this->onFailure($request, $e);
} catch (BadRequestHttpException $e) {
$request->setRequestFormat('json');
throw $e;
}
if (null === $response) {
return;
}
$event->setResponse($response);
}
private function onSuccess(Request $request, TokenInterface $token): ?Response
{
if (null !== $this->logger) {
// @deprecated since Symfony 5.3, change to $token->getUserIdentifier() in 6.0
$this->logger->info('User has been authenticated successfully.', ['username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]);
}
$this->migrateSession($request, $token);
$this->tokenStorage->setToken($token);
if (null !== $this->eventDispatcher) {
$loginEvent = new InteractiveLoginEvent($request, $token);
$this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
}
if (!$this->successHandler) {
return null; // let the original request succeeds
}
$response = $this->successHandler->onAuthenticationSuccess($request, $token);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Success Handler did not return a Response.');
}
return $response;
}
private function onFailure(Request $request, AuthenticationException $failed): Response
{
if (null !== $this->logger) {
$this->logger->info('Authentication request failed.', ['exception' => $failed]);
}
$token = $this->tokenStorage->getToken();
if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getFirewallName()) {
$this->tokenStorage->setToken(null);
}
if (!$this->failureHandler) {
if (null !== $this->translator) {
$errorMessage = $this->translator->trans($failed->getMessageKey(), $failed->getMessageData(), 'security');
} else {
$errorMessage = strtr($failed->getMessageKey(), $failed->getMessageData());
}
return new JsonResponse(['error' => $errorMessage], 401);
}
$response = $this->failureHandler->onAuthenticationFailure($request, $failed);
if (!$response instanceof Response) {
throw new \RuntimeException('Authentication Failure Handler did not return a Response.');
}
return $response;
}
/**
* Call this method if your authentication token is stored to a session.
*
* @final
*/
public function setSessionAuthenticationStrategy(SessionAuthenticationStrategyInterface $sessionStrategy)
{
$this->sessionStrategy = $sessionStrategy;
}
public function setTranslator(TranslatorInterface $translator)
{
$this->translator = $translator;
}
private function migrateSession(Request $request, TokenInterface $token)
{
if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {
return;
}
$this->sessionStrategy->onAuthentication($request, $token);
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Http\Firewall;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
trigger_deprecation('symfony/security-http', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', X509AuthenticationListener::class);
/**
* X509 authentication listener.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 5.3, use the new authenticator system instead
*/
class X509AuthenticationListener extends AbstractPreAuthenticatedListener
{
private $userKey;
private $credentialKey;
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, string $providerKey, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialKey = 'SSL_CLIENT_S_DN', LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
{
parent::__construct($tokenStorage, $authenticationManager, $providerKey, $logger, $dispatcher);
$this->userKey = $userKey;
$this->credentialKey = $credentialKey;
}
/**
* {@inheritdoc}
*/
protected function getPreAuthenticatedData(Request $request)
{
$user = null;
if ($request->server->has($this->userKey)) {
$user = $request->server->get($this->userKey);
} elseif (
$request->server->has($this->credentialKey)
&& preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialKey), $matches)
) {
$user = $matches[1];
}
if (null === $user) {
throw new BadCredentialsException(sprintf('SSL credentials not found: "%s", "%s".', $this->userKey, $this->credentialKey));
}
return [$user, $request->server->get($this->credentialKey, '')];
}
}