Introduce Passport & Badges to extend authenticators
This commit is contained in:
parent
9ea32c4ed3
commit
50224aa285
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -30,6 +31,7 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
|
||||
$this->addOption('password_parameter', '_password');
|
||||
$this->addOption('csrf_parameter', '_csrf_token');
|
||||
$this->addOption('csrf_token_id', 'authenticate');
|
||||
$this->addOption('enable_csrf', false);
|
||||
$this->addOption('post_only', true);
|
||||
}
|
||||
|
||||
@ -61,6 +63,10 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
|
||||
|
||||
protected function createAuthProvider(ContainerBuilder $container, string $id, array $config, string $userProviderId)
|
||||
{
|
||||
if ($config['enable_csrf'] ?? false) {
|
||||
throw new InvalidConfigurationException('The "enable_csrf" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "true", use "csrf_token_generator" instead.');
|
||||
}
|
||||
|
||||
$provider = 'security.authentication.provider.dao.'.$id;
|
||||
$container
|
||||
->setDefinition($provider, new ChildDefinition('security.authentication.provider.dao'))
|
||||
@ -99,6 +105,10 @@ class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryIn
|
||||
|
||||
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId): string
|
||||
{
|
||||
if (isset($config['csrf_token_generator'])) {
|
||||
throw new InvalidConfigurationException('The "csrf_token_generator" option of "form_login" is only available when "security.enable_authenticator_manager" is set to "false", use "enable_csrf" instead.');
|
||||
}
|
||||
|
||||
$authenticatorId = 'security.authenticator.form_login.'.$id;
|
||||
$options = array_intersect_key($config, $this->options);
|
||||
$container
|
||||
|
@ -9,6 +9,7 @@ CHANGELOG
|
||||
* Hash the persistent RememberMe token value in database.
|
||||
* Added `LogoutEvent` to allow custom logout listeners.
|
||||
* Deprecated `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface` in favor of listening on the `LogoutEvent`.
|
||||
* Added experimental new security using `Http\Authenticator\AuthenticatorInterface`, `Http\Authentication\AuthenticatorManager` and `Http\Firewall\AuthenticatorManagerListener`.
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
@ -19,11 +19,13 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\AuthenticationEvents;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
@ -60,13 +62,16 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
||||
$this->eraseCredentials = $eraseCredentials;
|
||||
}
|
||||
|
||||
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request): ?Response
|
||||
/**
|
||||
* @param BadgeInterface[] $badges Optionally, pass some Passport badges to use for the manual login
|
||||
*/
|
||||
public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response
|
||||
{
|
||||
// create an authenticated token for the User
|
||||
$token = $authenticator->createAuthenticatedToken($user, $this->providerKey);
|
||||
$token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport($user, $badges), $this->providerKey);
|
||||
|
||||
// authenticate this in the system
|
||||
return $this->handleAuthenticationSuccess($token, $request, $authenticator);
|
||||
return $this->handleAuthenticationSuccess($token, $passport, $request, $authenticator);
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
@ -133,7 +138,7 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = $this->executeAuthenticator($key, $authenticator, $request);
|
||||
$response = $this->executeAuthenticator($authenticator, $request);
|
||||
if (null !== $response) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($authenticator)]);
|
||||
@ -146,29 +151,35 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
||||
return null;
|
||||
}
|
||||
|
||||
private function executeAuthenticator(string $uniqueAuthenticatorKey, AuthenticatorInterface $authenticator, Request $request): ?Response
|
||||
private function executeAuthenticator(AuthenticatorInterface $authenticator, Request $request): ?Response
|
||||
{
|
||||
try {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Calling getCredentials() on authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($authenticator)]);
|
||||
// get the passport from the Authenticator
|
||||
$passport = $authenticator->authenticate($request);
|
||||
|
||||
// check the passport (e.g. password checking)
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $passport);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
// check if all badges are resolved
|
||||
$passport->checkIfCompletelyResolved();
|
||||
|
||||
// create the authenticated token
|
||||
$authenticatedToken = $authenticator->createAuthenticatedToken($passport, $this->providerKey);
|
||||
if (true === $this->eraseCredentials) {
|
||||
$authenticatedToken->eraseCredentials();
|
||||
}
|
||||
|
||||
// allow the authenticator to fetch authentication info from the request
|
||||
$credentials = $authenticator->getCredentials($request);
|
||||
|
||||
if (null === $credentials) {
|
||||
throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', \get_class($authenticator)));
|
||||
if (null !== $this->eventDispatcher) {
|
||||
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS);
|
||||
}
|
||||
|
||||
// authenticate the credentials (e.g. check password)
|
||||
$token = $this->authenticateViaAuthenticator($authenticator, $credentials);
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Authenticator successful!', ['token' => $token, 'authenticator' => \get_class($authenticator)]);
|
||||
$this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]);
|
||||
}
|
||||
|
||||
// success! (sets the token on the token storage, etc)
|
||||
$response = $this->handleAuthenticationSuccess($token, $request, $authenticator);
|
||||
$response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator);
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
@ -189,35 +200,7 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
||||
}
|
||||
}
|
||||
|
||||
private function authenticateViaAuthenticator(AuthenticatorInterface $authenticator, $credentials): TokenInterface
|
||||
{
|
||||
// get the user from the Authenticator
|
||||
$user = $authenticator->getUser($credentials);
|
||||
if (null === $user) {
|
||||
throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', \get_class($authenticator)));
|
||||
}
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, $credentials, $user);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
if (true !== $event->areCredentialsValid()) {
|
||||
throw new BadCredentialsException(sprintf('Authentication failed because "%s" did not approve the credentials.', \get_class($authenticator)));
|
||||
}
|
||||
|
||||
// turn the UserInterface into a TokenInterface
|
||||
$authenticatedToken = $authenticator->createAuthenticatedToken($user, $this->providerKey);
|
||||
|
||||
if (true === $this->eraseCredentials) {
|
||||
$authenticatedToken->eraseCredentials();
|
||||
}
|
||||
|
||||
if (null !== $this->eventDispatcher) {
|
||||
$this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($authenticatedToken), AuthenticationEvents::AUTHENTICATION_SUCCESS);
|
||||
}
|
||||
|
||||
return $authenticatedToken;
|
||||
}
|
||||
|
||||
private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, Request $request, AuthenticatorInterface $authenticator): ?Response
|
||||
private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response
|
||||
{
|
||||
$this->tokenStorage->setToken($authenticatedToken);
|
||||
|
||||
@ -227,7 +210,11 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent
|
||||
$this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $authenticatedToken, $request, $response, $this->providerKey));
|
||||
if ($passport instanceof AnonymousPassport) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $passport, $authenticatedToken, $request, $response, $this->firewallName));
|
||||
|
||||
return $loginSuccessEvent->getResponse();
|
||||
}
|
||||
|
@ -12,7 +12,9 @@
|
||||
namespace Symfony\Component\Security\Http\Authenticator;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken;
|
||||
|
||||
/**
|
||||
@ -30,8 +32,12 @@ abstract class AbstractAuthenticator implements AuthenticatorInterface
|
||||
*
|
||||
* @return PostAuthenticationToken
|
||||
*/
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
|
||||
{
|
||||
return new PostAuthenticationToken($user, $providerKey, $user->getRoles());
|
||||
if (!$passport instanceof UserPassportInterface) {
|
||||
throw new LogicException(sprintf('Passport does not contain a user, overwrite "createAuthenticatedToken()" in "%s" to create a custom authenticated token.', \get_class($this)));
|
||||
}
|
||||
|
||||
return new PostAuthenticationToken($passport->getUser(), $providerKey, $passport->getUser()->getRoles());
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, RememberMeAuthenticatorInterface, InteractiveAuthenticatorInterface
|
||||
abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface, InteractiveAuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* Return the URL to the login page.
|
||||
@ -57,11 +57,6 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
public function supportsRememberMe(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isInteractive(): bool
|
||||
{
|
||||
return true;
|
||||
|
@ -19,8 +19,10 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
|
||||
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\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
|
||||
/**
|
||||
* The base authenticator for authenticators to use pre-authenticated
|
||||
@ -32,7 +34,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
* @internal
|
||||
* @experimental in Symfony 5.1
|
||||
*/
|
||||
abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface, CustomAuthenticatedInterface
|
||||
abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface
|
||||
{
|
||||
private $userProvider;
|
||||
private $tokenStorage;
|
||||
@ -63,7 +65,7 @@ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthe
|
||||
$this->clearToken($e);
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => \get_class($this)]);
|
||||
$this->logger->debug('Skipping pre-authenticated authenticator as a BadCredentialsException is thrown.', ['exception' => $e, 'authenticator' => static::class]);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -71,7 +73,7 @@ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthe
|
||||
|
||||
if (null === $username) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => \get_class($this)]);
|
||||
$this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => static::class]);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -82,27 +84,17 @@ abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthe
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
return [
|
||||
'username' => $request->attributes->get('_pre_authenticated_username'),
|
||||
];
|
||||
$username = $request->attributes->get('_pre_authenticated_username');
|
||||
$user = $this->userProvider->loadUserByUsername($username);
|
||||
|
||||
return new SelfValidatingPassport($user, [new PreAuthenticatedUserBadge()]);
|
||||
}
|
||||
|
||||
public function getUser($credentials): ?UserInterface
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
|
||||
{
|
||||
return $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
}
|
||||
|
||||
public function checkCredentials($credentials, UserInterface $user): bool
|
||||
{
|
||||
// the user is already authenticated before it entered Symfony
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
|
||||
{
|
||||
return new PreAuthenticatedToken($user, null, $providerKey);
|
||||
return new PreAuthenticatedToken($passport->getUser(), null, $providerKey, $passport->getUser()->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
|
@ -17,8 +17,8 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
|
||||
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\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
@ -27,7 +27,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthenticatedInterface
|
||||
class AnonymousAuthenticator implements AuthenticatorInterface
|
||||
{
|
||||
private $secret;
|
||||
private $tokenStorage;
|
||||
@ -45,23 +45,12 @@ class AnonymousAuthenticator implements AuthenticatorInterface, CustomAuthentica
|
||||
return null === $this->tokenStorage->getToken() ? null : false;
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
return [];
|
||||
return new AnonymousPassport();
|
||||
}
|
||||
|
||||
public function checkCredentials($credentials, UserInterface $user): bool
|
||||
{
|
||||
// anonymous users do not have credentials
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUser($credentials): ?UserInterface
|
||||
{
|
||||
return new User('anon.', null);
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
|
||||
{
|
||||
return new AnonymousToken($this->secret, 'anon.', []);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
|
||||
/**
|
||||
* The interface for all authenticators.
|
||||
@ -38,39 +38,19 @@ interface AuthenticatorInterface
|
||||
public function supports(Request $request): ?bool;
|
||||
|
||||
/**
|
||||
* Get the authentication credentials from the request and return them
|
||||
* as any type (e.g. an associate array).
|
||||
* Create a passport for the current request.
|
||||
*
|
||||
* Whatever value you return here will be passed to getUser() and checkCredentials()
|
||||
* The passport contains the user, credentials and any additional information
|
||||
* that has to be checked by the Symfony Security system. For example, a login
|
||||
* form authenticator will probably return a passport containing the user, the
|
||||
* presented password and the CSRF token value.
|
||||
*
|
||||
* For example, for a form login, you might:
|
||||
*
|
||||
* return [
|
||||
* 'username' => $request->request->get('_username'),
|
||||
* 'password' => $request->request->get('_password'),
|
||||
* ];
|
||||
*
|
||||
* Or for an API token that's on a header, you might use:
|
||||
*
|
||||
* return ['api_key' => $request->headers->get('X-API-TOKEN')];
|
||||
*
|
||||
* @return mixed Any non-null value
|
||||
*
|
||||
* @throws \UnexpectedValueException If null is returned
|
||||
*/
|
||||
public function getCredentials(Request $request);
|
||||
|
||||
/**
|
||||
* Return a UserInterface object based on the credentials.
|
||||
*
|
||||
* You may throw an AuthenticationException if you wish. If you return
|
||||
* null, then a UsernameNotFoundException is thrown for you.
|
||||
*
|
||||
* @param mixed $credentials the value returned from getCredentials()
|
||||
* You may throw any AuthenticationException in this method in case of error (e.g.
|
||||
* a UsernameNotFoundException when the user cannot be found).
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function getUser($credentials): ?UserInterface;
|
||||
public function authenticate(Request $request): PassportInterface;
|
||||
|
||||
/**
|
||||
* Create an authenticated token for the given user.
|
||||
@ -80,19 +60,10 @@ interface AuthenticatorInterface
|
||||
* the AbstractAuthenticator class from your authenticator.
|
||||
*
|
||||
* @see AbstractAuthenticator
|
||||
*/
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface;
|
||||
|
||||
/**
|
||||
* Called when authentication executed, but failed (e.g. wrong username password).
|
||||
*
|
||||
* This should return the Response sent back to the user, like a
|
||||
* RedirectResponse to the login page or a 403 response.
|
||||
*
|
||||
* If you return null, the request will continue, but the user will
|
||||
* not be authenticated. This is probably not what you want to do.
|
||||
* @param PassportInterface $passport The passport returned from authenticate()
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response;
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface;
|
||||
|
||||
/**
|
||||
* Called when authentication executed and was successful!
|
||||
@ -104,4 +75,15 @@ interface AuthenticatorInterface
|
||||
* will be authenticated. This makes sense, for example, with an API.
|
||||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response;
|
||||
|
||||
/**
|
||||
* Called when authentication executed, but failed (e.g. wrong username password).
|
||||
*
|
||||
* This should return the Response sent back to the user, like a
|
||||
* RedirectResponse to the login page or a 403 response.
|
||||
*
|
||||
* If you return null, the request will continue, but the user will
|
||||
* not be authenticated. This is probably not what you want to do.
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response;
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?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\Authenticator;
|
||||
|
||||
/**
|
||||
* This interface can be implemented to automatically add CSF
|
||||
* protection to the authenticator.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface CsrfProtectedAuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* An arbitrary string used to generate the value of the CSRF token.
|
||||
* Using a different string for each authenticator improves its security.
|
||||
*/
|
||||
public function getCsrfTokenId(): string;
|
||||
|
||||
/**
|
||||
* Returns the CSRF token contained in credentials if any.
|
||||
*
|
||||
* @param mixed $credentials the credentials returned by getCredentials()
|
||||
*/
|
||||
public function getCsrfToken($credentials): ?string;
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?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\Authenticator;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* This interface should be implemented by authenticators that
|
||||
* require custom (not password related) authentication.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface CustomAuthenticatedInterface
|
||||
{
|
||||
/**
|
||||
* Returns true if the credentials are valid.
|
||||
*
|
||||
* If false is returned, authentication will fail. You may also throw
|
||||
* an AuthenticationException if you wish to cause authentication to fail.
|
||||
*
|
||||
* @param mixed $credentials the value returned from getCredentials()
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function checkCredentials($credentials, UserInterface $user): bool;
|
||||
}
|
@ -17,12 +17,20 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
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\AuthenticationServiceException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
use Symfony\Component\Security\Http\ParameterBagUtils;
|
||||
|
||||
@ -33,7 +41,7 @@ use Symfony\Component\Security\Http\ParameterBagUtils;
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements PasswordAuthenticatedInterface, CsrfProtectedAuthenticatorInterface
|
||||
class FormLoginAuthenticator extends AbstractLoginFormAuthenticator
|
||||
{
|
||||
private $httpUtils;
|
||||
private $userProvider;
|
||||
@ -52,7 +60,7 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
|
||||
'password_parameter' => '_password',
|
||||
'check_path' => '/login_check',
|
||||
'post_only' => true,
|
||||
|
||||
'enable_csrf' => false,
|
||||
'csrf_parameter' => '_csrf_token',
|
||||
'csrf_token_id' => 'authenticate',
|
||||
], $options);
|
||||
@ -69,17 +77,55 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
|
||||
&& $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request): array
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
$credentials = $this->getCredentials($request);
|
||||
$user = $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
|
||||
}
|
||||
|
||||
$passport = new Passport($user, new PasswordCredentials($credentials['password']), [new RememberMeBadge()]);
|
||||
if ($this->options['enable_csrf']) {
|
||||
$passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token']));
|
||||
}
|
||||
|
||||
if ($this->userProvider instanceof PasswordUpgraderInterface) {
|
||||
$passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
|
||||
}
|
||||
|
||||
return $passport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Passport $passport
|
||||
*/
|
||||
public function createAuthenticatedToken(PassportInterface $passport, $providerKey): TokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($passport->getUser(), null, $providerKey, $passport->getUser()->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
{
|
||||
return $this->successHandler->onAuthenticationSuccess($request, $token);
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
||||
{
|
||||
return $this->failureHandler->onAuthenticationFailure($request, $exception);
|
||||
}
|
||||
|
||||
private function getCredentials(Request $request): array
|
||||
{
|
||||
$credentials = [];
|
||||
$credentials['csrf_token'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
|
||||
|
||||
if ($this->options['post_only']) {
|
||||
$credentials['username'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
|
||||
$credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
|
||||
$credentials['password'] = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']) ?? '';
|
||||
} else {
|
||||
$credentials['username'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']);
|
||||
$credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
|
||||
$credentials['password'] = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']) ?? '';
|
||||
}
|
||||
|
||||
if (!\is_string($credentials['username']) && (!\is_object($credentials['username']) || !method_exists($credentials['username'], '__toString'))) {
|
||||
@ -96,39 +142,4 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
public function getPassword($credentials): ?string
|
||||
{
|
||||
return $credentials['password'];
|
||||
}
|
||||
|
||||
public function getUser($credentials): ?UserInterface
|
||||
{
|
||||
return $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
}
|
||||
|
||||
public function getCsrfTokenId(): string
|
||||
{
|
||||
return $this->options['csrf_token_id'];
|
||||
}
|
||||
|
||||
public function getCsrfToken($credentials): ?string
|
||||
{
|
||||
return $credentials['csrf_token'];
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
{
|
||||
return $this->successHandler->onAuthenticationSuccess($request, $token);
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
|
||||
{
|
||||
return $this->failureHandler->onAuthenticationFailure($request, $exception);
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,14 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
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\AuthenticationServiceException;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
||||
/**
|
||||
@ -28,7 +34,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface, PasswordAuthenticatedInterface
|
||||
class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEntryPointInterface
|
||||
{
|
||||
private $realmName;
|
||||
private $userProvider;
|
||||
@ -55,27 +61,30 @@ class HttpBasicAuthenticator implements AuthenticatorInterface, AuthenticationEn
|
||||
return $request->headers->has('PHP_AUTH_USER');
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
return [
|
||||
'username' => $request->headers->get('PHP_AUTH_USER'),
|
||||
'password' => $request->headers->get('PHP_AUTH_PW', ''),
|
||||
];
|
||||
$username = $request->headers->get('PHP_AUTH_USER');
|
||||
$password = $request->headers->get('PHP_AUTH_PW', '');
|
||||
|
||||
$user = $this->userProvider->loadUserByUsername($username);
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
|
||||
}
|
||||
|
||||
$passport = new Passport($user, new PasswordCredentials($password));
|
||||
if ($this->userProvider instanceof PasswordUpgraderInterface) {
|
||||
$passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider));
|
||||
}
|
||||
|
||||
return $passport;
|
||||
}
|
||||
|
||||
public function getPassword($credentials): ?string
|
||||
/**
|
||||
* @param Passport $passport
|
||||
*/
|
||||
public function createAuthenticatedToken(PassportInterface $passport, $providerKey): TokenInterface
|
||||
{
|
||||
return $credentials['password'];
|
||||
}
|
||||
|
||||
public function getUser($credentials): ?UserInterface
|
||||
{
|
||||
return $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
|
||||
return new UsernamePasswordToken($passport->getUser(), null, $providerKey, $passport->getUser()->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response
|
||||
|
@ -11,10 +11,6 @@
|
||||
|
||||
namespace Symfony\Component\Security\Http\Authenticator;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
|
||||
/**
|
||||
* This is an extension of the authenticator interface that must
|
||||
* be used by interactive authenticators.
|
||||
|
@ -21,12 +21,18 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
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\AuthenticationServiceException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
/**
|
||||
@ -39,7 +45,7 @@ use Symfony\Component\Security\Http\HttpUtils;
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface, PasswordAuthenticatedInterface
|
||||
class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface
|
||||
{
|
||||
private $options;
|
||||
private $httpUtils;
|
||||
@ -71,7 +77,51 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface, Passw
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
$credentials = $this->getCredentials($request);
|
||||
$user = $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
|
||||
}
|
||||
|
||||
$passport = new Passport($user, new PasswordCredentials($credentials['password']));
|
||||
if ($this->userProvider instanceof PasswordUpgraderInterface) {
|
||||
$passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
|
||||
}
|
||||
|
||||
return $passport;
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($passport->getUser(), null, $providerKey, $passport->getUser()->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
{
|
||||
if (null === $this->successHandler) {
|
||||
return null; // let the original request continue
|
||||
}
|
||||
|
||||
return $this->successHandler->onAuthenticationSuccess($request, $token);
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
if (null === $this->failureHandler) {
|
||||
return new JsonResponse(['error' => $exception->getMessageKey()], JsonResponse::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return $this->failureHandler->onAuthenticationFailure($request, $exception);
|
||||
}
|
||||
|
||||
public function isInteractive(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getCredentials(Request $request)
|
||||
{
|
||||
$data = json_decode($request->getContent());
|
||||
if (!$data instanceof \stdClass) {
|
||||
@ -105,42 +155,4 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface, Passw
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
public function getUser($credentials): ?UserInterface
|
||||
{
|
||||
return $this->userProvider->loadUserByUsername($credentials['username']);
|
||||
}
|
||||
|
||||
public function getPassword($credentials): ?string
|
||||
{
|
||||
return $credentials['password'];
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
|
||||
{
|
||||
return new UsernamePasswordToken($user, null, $providerKey, $user->getRoles());
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
{
|
||||
if (null === $this->successHandler) {
|
||||
return null; // let the original request continue
|
||||
}
|
||||
|
||||
return $this->successHandler->onAuthenticationSuccess($request, $token);
|
||||
}
|
||||
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||
{
|
||||
if (null === $this->failureHandler) {
|
||||
return new JsonResponse(['error' => $exception->getMessageKey()], JsonResponse::HTTP_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return $this->failureHandler->onAuthenticationFailure($request, $exception);
|
||||
}
|
||||
|
||||
public function isInteractive(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
<?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\Authenticator\Passport;
|
||||
|
||||
/**
|
||||
* A passport used during anonymous authentication.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @internal
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class AnonymousPassport implements PassportInterface
|
||||
{
|
||||
use PassportTrait;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?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\Authenticator\Passport\Badge;
|
||||
|
||||
/**
|
||||
* Passport badges allow to add more information to a passport (e.g. a CSRF token).
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
interface BadgeInterface
|
||||
{
|
||||
/**
|
||||
* Checks if this badge is resolved by the security system.
|
||||
*
|
||||
* After authentication, all badges must return `true` in this method in order
|
||||
* for the authentication to succeed.
|
||||
*/
|
||||
public function isResolved(): bool;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?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\Authenticator\Passport\Badge;
|
||||
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
|
||||
|
||||
/**
|
||||
* Adds automatic CSRF tokens checking capabilities to this authenticator.
|
||||
*
|
||||
* @see CsrfProtectionListener
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in5.1
|
||||
*/
|
||||
class CsrfTokenBadge implements BadgeInterface
|
||||
{
|
||||
private $resolved = false;
|
||||
private $csrfTokenId;
|
||||
private $csrfToken;
|
||||
|
||||
/**
|
||||
* @param string $csrfTokenId An arbitrary string used to generate the value of the CSRF token.
|
||||
* Using a different string for each authenticator improves its security.
|
||||
* @param string|null $csrfToken The CSRF token presented in the request, if any
|
||||
*/
|
||||
public function __construct(string $csrfTokenId, ?string $csrfToken)
|
||||
{
|
||||
$this->csrfTokenId = $csrfTokenId;
|
||||
$this->csrfToken = $csrfToken;
|
||||
}
|
||||
|
||||
public function getCsrfTokenId(): string
|
||||
{
|
||||
return $this->csrfTokenId;
|
||||
}
|
||||
|
||||
public function getCsrfToken(): string
|
||||
{
|
||||
return $this->csrfToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function markResolved(): void
|
||||
{
|
||||
$this->resolved = true;
|
||||
}
|
||||
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return $this->resolved;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?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\Authenticator\Passport\Badge;
|
||||
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
|
||||
/**
|
||||
* Adds automatic password migration, if enabled and required in the password encoder.
|
||||
*
|
||||
* @see PasswordUpgraderInterface
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class PasswordUpgradeBadge implements BadgeInterface
|
||||
{
|
||||
private $plaintextPassword;
|
||||
private $passwordUpgrader;
|
||||
|
||||
/**
|
||||
* @param string $plaintextPassword The presented password, used in the rehash
|
||||
* @param PasswordUpgraderInterface $passwordUpgrader The password upgrader, usually the UserProvider
|
||||
*/
|
||||
public function __construct(string $plaintextPassword, PasswordUpgraderInterface $passwordUpgrader)
|
||||
{
|
||||
$this->plaintextPassword = $plaintextPassword;
|
||||
$this->passwordUpgrader = $passwordUpgrader;
|
||||
}
|
||||
|
||||
public function getPlaintextPassword(): string
|
||||
{
|
||||
return $this->plaintextPassword;
|
||||
}
|
||||
|
||||
public function getPasswordUpgrader(): PasswordUpgraderInterface
|
||||
{
|
||||
return $this->passwordUpgrader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function eraseCredentials()
|
||||
{
|
||||
$this->plaintextPassword = null;
|
||||
}
|
||||
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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\Authenticator\Passport\Badge;
|
||||
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractPreAuthenticatedAuthenticator;
|
||||
|
||||
/**
|
||||
* Marks the authentication as being pre-authenticated.
|
||||
*
|
||||
* This disables pre-authentication user checkers.
|
||||
*
|
||||
* @see AbstractPreAuthenticatedAuthenticator
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class PreAuthenticatedUserBadge implements BadgeInterface
|
||||
{
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@ -9,23 +9,29 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Http\Authenticator;
|
||||
namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge;
|
||||
|
||||
/**
|
||||
* This interface must be extended if the authenticator supports remember me functionality.
|
||||
* Adds support for remember me to this authenticator.
|
||||
*
|
||||
* Remember me cookie will be set if *all* of the following are met:
|
||||
* A) SupportsRememberMe() returns true in the successful authenticator
|
||||
* A) This badge is present in the Passport
|
||||
* B) The remember_me key under your firewall is configured
|
||||
* C) The "remember me" functionality is activated. This is usually
|
||||
* done by having a _remember_me checkbox in your form, but
|
||||
* can be configured by the "always_remember_me" and "remember_me_parameter"
|
||||
* parameters under the "remember_me" firewall key
|
||||
* D) The onAuthenticationSuccess method returns a Response object
|
||||
* D) The authentication process returns a success Response object
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
interface RememberMeAuthenticatorInterface
|
||||
class RememberMeBadge implements BadgeInterface
|
||||
{
|
||||
public function supportsRememberMe(): bool;
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return true; // remember me does not need to be explicitly resolved
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?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\Authenticator\Passport\Credentials;
|
||||
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
|
||||
/**
|
||||
* Credentials are a special badge used to explicitly mark the
|
||||
* credential check of an authenticator.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
interface CredentialsInterface extends BadgeInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?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\Authenticator\Passport\Credentials;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Implements credentials checking using a custom checker function.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class CustomCredentials implements CredentialsInterface
|
||||
{
|
||||
private $customCredentialsChecker;
|
||||
private $credentials;
|
||||
private $resolved = false;
|
||||
|
||||
/**
|
||||
* @param callable $customCredentialsChecker the check function. If this function does not return `true`, a
|
||||
* BadCredentialsException is thrown. You may also throw a more
|
||||
* specific exception in the function.
|
||||
* @param $credentials
|
||||
*/
|
||||
public function __construct(callable $customCredentialsChecker, $credentials)
|
||||
{
|
||||
$this->customCredentialsChecker = $customCredentialsChecker;
|
||||
$this->credentials = $credentials;
|
||||
}
|
||||
|
||||
public function executeCustomChecker(UserInterface $user): void
|
||||
{
|
||||
$checker = $this->customCredentialsChecker;
|
||||
|
||||
if (true !== $checker($this->credentials, $user)) {
|
||||
throw new BadCredentialsException('Credentials check failed as the callable passed to CustomCredentials did not return "true".');
|
||||
}
|
||||
|
||||
$this->resolved = true;
|
||||
}
|
||||
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return $this->resolved;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?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\Authenticator\Passport\Credentials;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Implements password credentials.
|
||||
*
|
||||
* These plaintext passwords are checked by the UserPasswordEncoder during
|
||||
* authentication.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class PasswordCredentials implements CredentialsInterface
|
||||
{
|
||||
private $password;
|
||||
private $resolved = false;
|
||||
|
||||
public function __construct(string $password)
|
||||
{
|
||||
$this->password = $password;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
if (null === $this->password) {
|
||||
throw new LogicException('The credentials are erased as another listener already verified these credentials.');
|
||||
}
|
||||
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function markResolved(): void
|
||||
{
|
||||
$this->resolved = true;
|
||||
$this->password = null;
|
||||
}
|
||||
|
||||
public function isResolved(): bool
|
||||
{
|
||||
return $this->resolved;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?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\Authenticator\Passport;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CredentialsInterface;
|
||||
|
||||
/**
|
||||
* The default implementation for passports.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class Passport implements UserPassportInterface
|
||||
{
|
||||
use PassportTrait;
|
||||
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @param CredentialsInterface $credentials the credentials to check for this authentication, use
|
||||
* SelfValidatingPassport if no credentials should be checked.
|
||||
* @param BadgeInterface[] $badges
|
||||
*/
|
||||
public function __construct(UserInterface $user, CredentialsInterface $credentials, array $badges = [])
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
$this->addBadge($credentials);
|
||||
foreach ($badges as $badge) {
|
||||
$this->addBadge($badge);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUser(): UserInterface
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?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\Authenticator\Passport;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
|
||||
/**
|
||||
* A Passport contains all security-related information that needs to be
|
||||
* validated during authentication.
|
||||
*
|
||||
* A passport badge can be used to add any additional information to the
|
||||
* passport.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
interface PassportInterface
|
||||
{
|
||||
/**
|
||||
* Adds a new security badge.
|
||||
*
|
||||
* A passport can hold only one instance of the same security badge.
|
||||
* This method replaces the current badge if it is already set on this
|
||||
* passport.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addBadge(BadgeInterface $badge): self;
|
||||
|
||||
public function hasBadge(string $badgeFqcn): bool;
|
||||
|
||||
public function getBadge(string $badgeFqcn): ?BadgeInterface;
|
||||
|
||||
/**
|
||||
* Checks if all badges are marked as resolved.
|
||||
*
|
||||
* @throws BadCredentialsException when a badge is not marked as resolved
|
||||
*/
|
||||
public function checkIfCompletelyResolved(): void;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?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\Authenticator\Passport;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
trait PassportTrait
|
||||
{
|
||||
/**
|
||||
* @var BadgeInterface[]
|
||||
*/
|
||||
private $badges = [];
|
||||
|
||||
public function addBadge(BadgeInterface $badge): PassportInterface
|
||||
{
|
||||
$this->badges[\get_class($badge)] = $badge;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasBadge(string $badgeFqcn): bool
|
||||
{
|
||||
return isset($this->badges[$badgeFqcn]);
|
||||
}
|
||||
|
||||
public function getBadge(string $badgeFqcn): ?BadgeInterface
|
||||
{
|
||||
return $this->badges[$badgeFqcn] ?? null;
|
||||
}
|
||||
|
||||
public function checkIfCompletelyResolved(): void
|
||||
{
|
||||
foreach ($this->badges as $badge) {
|
||||
if (!$badge->isResolved()) {
|
||||
throw new BadCredentialsException(sprintf('Authentication failed security badge "%s" is not resolved, did you forget to register the correct listeners?', \get_class($badge)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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\Authenticator\Passport;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* An implementation used when there are no credentials to be checked (e.g.
|
||||
* API token authentication).
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class SelfValidatingPassport extends Passport
|
||||
{
|
||||
public function __construct(UserInterface $user, array $badges = [])
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
foreach ($badges as $badge) {
|
||||
$this->addBadge($badge);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?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\Authenticator\Passport;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Represents a passport for a Security User.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
interface UserPassportInterface extends PassportInterface
|
||||
{
|
||||
public function getUser(): UserInterface;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?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\Authenticator;
|
||||
|
||||
/**
|
||||
* This interface should be implemented when the authenticator
|
||||
* uses a password to authenticate.
|
||||
*
|
||||
* The EncoderFactory will be used to automatically validate
|
||||
* the password.
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface PasswordAuthenticatedInterface
|
||||
{
|
||||
/**
|
||||
* Returns the clear-text password contained in credentials if any.
|
||||
*
|
||||
* @param mixed $credentials The user credentials
|
||||
*/
|
||||
public function getPassword($credentials): ?string;
|
||||
}
|
@ -17,8 +17,10 @@ use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
|
||||
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\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
|
||||
|
||||
/**
|
||||
* The RememberMe *Authenticator* performs remember me authentication.
|
||||
@ -33,22 +35,19 @@ use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class RememberMeAuthenticator implements InteractiveAuthenticatorInterface, CustomAuthenticatedInterface
|
||||
class RememberMeAuthenticator implements InteractiveAuthenticatorInterface
|
||||
{
|
||||
private $rememberMeServices;
|
||||
private $secret;
|
||||
private $tokenStorage;
|
||||
private $options = [
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
];
|
||||
private $options = [];
|
||||
|
||||
public function __construct(AbstractRememberMeServices $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options)
|
||||
public function __construct(RememberMeServicesInterface $rememberMeServices, string $secret, TokenStorageInterface $tokenStorage, array $options)
|
||||
{
|
||||
$this->rememberMeServices = $rememberMeServices;
|
||||
$this->secret = $secret;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->options = array_merge($this->options, $options);
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function supports(Request $request): ?bool
|
||||
@ -62,7 +61,7 @@ class RememberMeAuthenticator implements InteractiveAuthenticatorInterface, Cust
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$request->cookies->has($this->options['name'])) {
|
||||
if (isset($this->options['name']) && !$request->cookies->has($this->options['name'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -70,31 +69,16 @@ class RememberMeAuthenticator implements InteractiveAuthenticatorInterface, Cust
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
public function authenticate(Request $request): PassportInterface
|
||||
{
|
||||
return [
|
||||
'cookie_parts' => explode(AbstractRememberMeServices::COOKIE_DELIMITER, base64_decode($request->cookies->get($this->options['name']))),
|
||||
'request' => $request,
|
||||
];
|
||||
$token = $this->rememberMeServices->autoLogin($request);
|
||||
|
||||
return new SelfValidatingPassport($token->getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $credentials
|
||||
*/
|
||||
public function getUser($credentials): ?UserInterface
|
||||
public function createAuthenticatedToken(PassportInterface $passport, string $providerKey): TokenInterface
|
||||
{
|
||||
return $this->rememberMeServices->performLogin($credentials['cookie_parts'], $credentials['request']);
|
||||
}
|
||||
|
||||
public function checkCredentials($credentials, UserInterface $user): bool
|
||||
{
|
||||
// remember me always is valid (if a user could be found)
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createAuthenticatedToken(UserInterface $user, string $providerKey): TokenInterface
|
||||
{
|
||||
return new RememberMeToken($user, $providerKey, $this->secret);
|
||||
return new RememberMeToken($passport->getUser(), $providerKey, $this->secret);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||
|
@ -24,6 +24,8 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*
|
||||
* @internal in Symfony 5.1
|
||||
*/
|
||||
class RemoteUserAuthenticator extends AbstractPreAuthenticatedAuthenticator
|
||||
|
@ -1,33 +0,0 @@
|
||||
<?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\Authenticator;
|
||||
|
||||
/**
|
||||
* This interface should be implemented when the authenticator
|
||||
* doesn't need to check credentials (e.g. when using API tokens)
|
||||
*
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*/
|
||||
interface TokenAuthenticatedInterface
|
||||
{
|
||||
/**
|
||||
* Extracts the token from the credentials.
|
||||
*
|
||||
* If you return null, the credentials will not be marked as
|
||||
* valid and a BadCredentialsException is thrown.
|
||||
*
|
||||
* @param mixed $credentials The user credentials
|
||||
*
|
||||
* @return mixed|null the token - if any - or null otherwise
|
||||
*/
|
||||
public function getToken($credentials);
|
||||
}
|
@ -24,7 +24,7 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @internal
|
||||
* @final
|
||||
* @experimental in Symfony 5.1
|
||||
*/
|
||||
class X509Authenticator extends AbstractPreAuthenticatedAuthenticator
|
||||
|
@ -5,7 +5,11 @@ namespace Symfony\Component\Security\Http\Event;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
@ -21,14 +25,16 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
class LoginSuccessEvent extends Event
|
||||
{
|
||||
private $authenticator;
|
||||
private $passport;
|
||||
private $authenticatedToken;
|
||||
private $request;
|
||||
private $response;
|
||||
private $providerKey;
|
||||
|
||||
public function __construct(AuthenticatorInterface $authenticator, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $providerKey)
|
||||
public function __construct(AuthenticatorInterface $authenticator, PassportInterface $passport, TokenInterface $authenticatedToken, Request $request, ?Response $response, string $providerKey)
|
||||
{
|
||||
$this->authenticator = $authenticator;
|
||||
$this->passport = $passport;
|
||||
$this->authenticatedToken = $authenticatedToken;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
@ -40,6 +46,20 @@ class LoginSuccessEvent extends Event
|
||||
return $this->authenticator;
|
||||
}
|
||||
|
||||
public function getPassport(): PassportInterface
|
||||
{
|
||||
return $this->passport;
|
||||
}
|
||||
|
||||
public function getUser(): UserInterface
|
||||
{
|
||||
if (!$this->passport instanceof UserPassportInterface) {
|
||||
throw new LogicException(sprintf('Cannot call "%s" as the authenticator ("%s") did not set a user.', __METHOD__, \get_class($this->authenticator)));
|
||||
}
|
||||
|
||||
return $this->passport->getUser();
|
||||
}
|
||||
|
||||
public function getAuthenticatedToken(): TokenInterface
|
||||
{
|
||||
return $this->authenticatedToken;
|
||||
|
@ -5,6 +5,7 @@ namespace Symfony\Component\Security\Http\Event;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
@ -19,15 +20,12 @@ use Symfony\Contracts\EventDispatcher\Event;
|
||||
class VerifyAuthenticatorCredentialsEvent extends Event
|
||||
{
|
||||
private $authenticator;
|
||||
private $user;
|
||||
private $credentials;
|
||||
private $credentialsValid = false;
|
||||
private $passport;
|
||||
|
||||
public function __construct(AuthenticatorInterface $authenticator, $credentials, ?UserInterface $user)
|
||||
public function __construct(AuthenticatorInterface $authenticator, PassportInterface $passport)
|
||||
{
|
||||
$this->authenticator = $authenticator;
|
||||
$this->credentials = $credentials;
|
||||
$this->user = $user;
|
||||
$this->passport = $passport;
|
||||
}
|
||||
|
||||
public function getAuthenticator(): AuthenticatorInterface
|
||||
@ -35,23 +33,8 @@ class VerifyAuthenticatorCredentialsEvent extends Event
|
||||
return $this->authenticator;
|
||||
}
|
||||
|
||||
public function getCredentials()
|
||||
public function getPassport(): PassportInterface
|
||||
{
|
||||
return $this->credentials;
|
||||
}
|
||||
|
||||
public function getUser(): ?UserInterface
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setCredentialsValid(bool $validated = true): void
|
||||
{
|
||||
$this->credentialsValid = $validated;
|
||||
}
|
||||
|
||||
public function areCredentialsValid(): bool
|
||||
{
|
||||
return $this->credentialsValid;
|
||||
return $this->passport;
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,15 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\CsrfProtectedAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
*
|
||||
* @final
|
||||
* @experimental in 5.1
|
||||
*/
|
||||
class CsrfProtectionListener implements EventSubscriberInterface
|
||||
{
|
||||
private $csrfTokenManager;
|
||||
@ -29,20 +35,24 @@ class CsrfProtectionListener implements EventSubscriberInterface
|
||||
|
||||
public function verifyCredentials(VerifyAuthenticatorCredentialsEvent $event): void
|
||||
{
|
||||
$authenticator = $event->getAuthenticator();
|
||||
if (!$authenticator instanceof CsrfProtectedAuthenticatorInterface) {
|
||||
$passport = $event->getPassport();
|
||||
if (!$passport->hasBadge(CsrfTokenBadge::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$csrfTokenValue = $authenticator->getCsrfToken($event->getCredentials());
|
||||
if (null === $csrfTokenValue) {
|
||||
/** @var CsrfTokenBadge $badge */
|
||||
$badge = $passport->getBadge(CsrfTokenBadge::class);
|
||||
if ($badge->isResolved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$csrfToken = new CsrfToken($authenticator->getCsrfTokenId(), $csrfTokenValue);
|
||||
$csrfToken = new CsrfToken($badge->getCsrfTokenId(), $badge->getCsrfToken());
|
||||
|
||||
if (false === $this->csrfTokenManager->isTokenValid($csrfToken)) {
|
||||
throw new InvalidCsrfTokenException('Invalid CSRF token.');
|
||||
}
|
||||
|
||||
$badge->markResolved();
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
|
@ -4,10 +4,9 @@ namespace Symfony\Component\Security\Http\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
|
||||
/**
|
||||
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||
@ -24,37 +23,33 @@ class PasswordMigratingListener implements EventSubscriberInterface
|
||||
$this->encoderFactory = $encoderFactory;
|
||||
}
|
||||
|
||||
public function onCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
|
||||
public function onLoginSuccess(LoginSuccessEvent $event): void
|
||||
{
|
||||
if (!$event->areCredentialsValid()) {
|
||||
// Do not migrate password that are not validated
|
||||
$passport = $event->getPassport();
|
||||
if (!$passport instanceof UserPassportInterface || !$passport->hasBadge(PasswordUpgradeBadge::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authenticator = $event->getAuthenticator();
|
||||
if (!$authenticator instanceof PasswordAuthenticatedInterface || !$authenticator instanceof PasswordUpgraderInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $password = $authenticator->getPassword($event->getCredentials())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $event->getUser();
|
||||
if (!$user instanceof UserInterface) {
|
||||
/** @var PasswordUpgradeBadge $badge */
|
||||
$badge = $passport->getBadge(PasswordUpgradeBadge::class);
|
||||
$plaintextPassword = $badge->getPlaintextPassword();
|
||||
$badge->eraseCredentials();
|
||||
|
||||
if ('' === $plaintextPassword) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $passport->getUser();
|
||||
$passwordEncoder = $this->encoderFactory->getEncoder($user);
|
||||
if (!$passwordEncoder->needsRehash($user->getPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authenticator->upgradePassword($user, $passwordEncoder->encodePassword($password, $user->getSalt()));
|
||||
$badge->getPasswordUpgrader()->upgradePassword($user, $passwordEncoder->encodePassword($plaintextPassword, $user->getSalt()));
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [VerifyAuthenticatorCredentialsEvent::class => ['onCredentialsVerification', -128]];
|
||||
return [LoginSuccessEvent::class => 'onLoginSuccess'];
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ namespace Symfony\Component\Security\Http\EventListener;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
||||
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
|
||||
@ -34,13 +33,12 @@ class RememberMeListener implements EventSubscriberInterface
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
||||
public function onSuccessfulLogin(LoginSuccessEvent $event): void
|
||||
{
|
||||
$authenticator = $event->getAuthenticator();
|
||||
if (!$authenticator instanceof RememberMeAuthenticatorInterface || !$authenticator->supportsRememberMe()) {
|
||||
$passport = $event->getPassport();
|
||||
if (!$passport->hasBadge(RememberMeBadge::class)) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($authenticator)]);
|
||||
$this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($event->getAuthenticator())]);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -48,7 +46,7 @@ class RememberMeListener implements EventSubscriberInterface
|
||||
|
||||
if (null === $event->getResponse()) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Remember me skipped: the authenticator did not set a success response.', ['authenticator' => \get_class($authenticator)]);
|
||||
$this->logger->debug('Remember me skipped: the authenticator did not set a success response.', ['authenticator' => \get_class($event->getAuthenticator())]);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -4,7 +4,9 @@ namespace Symfony\Component\Security\Http\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AbstractPreAuthenticatedAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
|
||||
/**
|
||||
@ -24,29 +26,29 @@ class UserCheckerListener implements EventSubscriberInterface
|
||||
|
||||
public function preCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
|
||||
{
|
||||
if (null === $event->getUser() || $event->getAuthenticator() instanceof AbstractPreAuthenticatedAuthenticator) {
|
||||
$passport = $event->getPassport();
|
||||
if (!$passport instanceof UserPassportInterface || $passport->hasBadge(PreAuthenticatedUserBadge::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userChecker->checkPreAuth($event->getUser());
|
||||
$this->userChecker->checkPreAuth($passport->getUser());
|
||||
}
|
||||
|
||||
public function postCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
|
||||
public function postCredentialsVerification(LoginSuccessEvent $event): void
|
||||
{
|
||||
if (null === $event->getUser() || !$event->areCredentialsValid()) {
|
||||
$passport = $event->getPassport();
|
||||
if (!$passport instanceof UserPassportInterface || null === $passport->getUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->userChecker->checkPostAuth($event->getUser());
|
||||
$this->userChecker->checkPostAuth($passport->getUser());
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
VerifyAuthenticatorCredentialsEvent::class => [
|
||||
['preCredentialsVerification', 256],
|
||||
['preCredentialsVerification', 32]
|
||||
],
|
||||
VerifyAuthenticatorCredentialsEvent::class => [['preCredentialsVerification', 256]],
|
||||
LoginSuccessEvent::class => ['postCredentialsVerification', 256],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,9 @@ namespace Symfony\Component\Security\Http\EventListener;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Http\Authenticator\CustomAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\TokenAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
|
||||
/**
|
||||
@ -31,44 +30,46 @@ class VerifyAuthenticatorCredentialsListener implements EventSubscriberInterface
|
||||
|
||||
public function onAuthenticating(VerifyAuthenticatorCredentialsEvent $event): void
|
||||
{
|
||||
if ($event->areCredentialsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$authenticator = $event->getAuthenticator();
|
||||
if ($authenticator instanceof PasswordAuthenticatedInterface) {
|
||||
$passport = $event->getPassport();
|
||||
if ($passport instanceof UserPassportInterface && $passport->hasBadge(PasswordCredentials::class)) {
|
||||
// Use the password encoder to validate the credentials
|
||||
$user = $event->getUser();
|
||||
$presentedPassword = $authenticator->getPassword($event->getCredentials());
|
||||
$user = $passport->getUser();
|
||||
/** @var PasswordCredentials $badge */
|
||||
$badge = $passport->getBadge(PasswordCredentials::class);
|
||||
|
||||
if ($badge->isResolved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$presentedPassword = $badge->getPassword();
|
||||
if ('' === $presentedPassword) {
|
||||
throw new BadCredentialsException('The presented password cannot be empty.');
|
||||
}
|
||||
|
||||
if (null === $user->getPassword()) {
|
||||
throw new BadCredentialsException('The presented password is invalid.');
|
||||
}
|
||||
|
||||
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
|
||||
throw new BadCredentialsException('The presented password is invalid.');
|
||||
}
|
||||
|
||||
$badge->markResolved();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($passport->hasBadge(CustomCredentials::class)) {
|
||||
/** @var CustomCredentials $badge */
|
||||
$badge = $passport->getBadge(CustomCredentials::class);
|
||||
if ($badge->isResolved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setCredentialsValid($this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt()));
|
||||
$badge->executeCustomChecker($passport->getUser());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($authenticator instanceof TokenAuthenticatedInterface) {
|
||||
if (null !== $authenticator->getToken($event->getCredentials())) {
|
||||
// Token based authenticators do not have a credential validation step
|
||||
$event->setCredentialsValid();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($authenticator instanceof CustomAuthenticatedInterface) {
|
||||
$event->setCredentialsValid($authenticator->checkCredentials($event->getCredentials(), $event->getUser()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new LogicException(sprintf('Authenticator %s does not have valid credentials. Authenticators must implement one of the authenticated interfaces (%s, %s or %s).', \get_class($authenticator), PasswordAuthenticatedInterface::class, TokenAuthenticatedInterface::class, CustomAuthenticatedInterface::class));
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
|
@ -89,11 +89,6 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function performLogin(array $cookieParts, Request $request): UserInterface
|
||||
{
|
||||
return $this->processAutoLoginCookie($cookieParts, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of RememberMeServicesInterface. Detects whether a remember-me
|
||||
* cookie was set, decodes it, and hands it to subclasses for further processing.
|
||||
|
@ -18,10 +18,12 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
|
||||
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
|
||||
class AuthenticatorManagerTest extends TestCase
|
||||
@ -38,7 +40,7 @@ class AuthenticatorManagerTest extends TestCase
|
||||
$this->tokenStorage = $this->createMock(TokenStorageInterface::class);
|
||||
$this->eventDispatcher = new EventDispatcher();
|
||||
$this->request = new Request();
|
||||
$this->user = $this->createMock(UserInterface::class);
|
||||
$this->user = new User('wouter', null);
|
||||
$this->token = $this->createMock(TokenInterface::class);
|
||||
$this->response = $this->createMock(Response::class);
|
||||
}
|
||||
@ -73,7 +75,7 @@ class AuthenticatorManagerTest extends TestCase
|
||||
$authenticator = $this->createAuthenticator(false);
|
||||
$this->request->attributes->set('_guard_authenticators', [$authenticator]);
|
||||
|
||||
$authenticator->expects($this->never())->method('getCredentials');
|
||||
$authenticator->expects($this->never())->method('authenticate');
|
||||
|
||||
$manager = $this->createManager([$authenticator]);
|
||||
$manager->authenticateRequest($this->request);
|
||||
@ -88,17 +90,14 @@ class AuthenticatorManagerTest extends TestCase
|
||||
$this->request->attributes->set('_guard_authenticators', $authenticators);
|
||||
$matchingAuthenticator = $authenticators[$matchingAuthenticatorIndex];
|
||||
|
||||
$authenticators[($matchingAuthenticatorIndex + 1) % 2]->expects($this->never())->method('getCredentials');
|
||||
$authenticators[($matchingAuthenticatorIndex + 1) % 2]->expects($this->never())->method('authenticate');
|
||||
|
||||
$matchingAuthenticator->expects($this->any())->method('getCredentials')->willReturn(['password' => 'pa$$']);
|
||||
$matchingAuthenticator->expects($this->any())->method('getUser')->willReturn($this->user);
|
||||
$matchingAuthenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
|
||||
|
||||
$listenerCalled = false;
|
||||
$this->eventDispatcher->addListener(VerifyAuthenticatorCredentialsEvent::class, function (VerifyAuthenticatorCredentialsEvent $event) use (&$listenerCalled, $matchingAuthenticator) {
|
||||
if ($event->getAuthenticator() === $matchingAuthenticator && $event->getCredentials() === ['password' => 'pa$$'] && $event->getUser() === $this->user) {
|
||||
if ($event->getAuthenticator() === $matchingAuthenticator && $event->getPassport()->getUser() === $this->user) {
|
||||
$listenerCalled = true;
|
||||
|
||||
$event->setCredentialsValid(true);
|
||||
}
|
||||
});
|
||||
$matchingAuthenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
|
||||
@ -116,29 +115,12 @@ class AuthenticatorManagerTest extends TestCase
|
||||
yield [1];
|
||||
}
|
||||
|
||||
public function testUserNotFound()
|
||||
{
|
||||
$authenticator = $this->createAuthenticator();
|
||||
$this->request->attributes->set('_guard_authenticators', [$authenticator]);
|
||||
|
||||
$authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
|
||||
$authenticator->expects($this->any())->method('getUser')->with(['username' => 'john'])->willReturn(null);
|
||||
|
||||
$authenticator->expects($this->once())
|
||||
->method('onAuthenticationFailure')
|
||||
->with($this->request, $this->isInstanceOf(UsernameNotFoundException::class));
|
||||
|
||||
$manager = $this->createManager([$authenticator]);
|
||||
$manager->authenticateRequest($this->request);
|
||||
}
|
||||
|
||||
public function testNoCredentialsValidated()
|
||||
{
|
||||
$authenticator = $this->createAuthenticator();
|
||||
$this->request->attributes->set('_guard_authenticators', [$authenticator]);
|
||||
|
||||
$authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
|
||||
$authenticator->expects($this->any())->method('getUser')->willReturn($this->user);
|
||||
$authenticator->expects($this->any())->method('authenticate')->willReturn(new Passport($this->user, new PasswordCredentials('pass')));
|
||||
|
||||
$authenticator->expects($this->once())
|
||||
->method('onAuthenticationFailure')
|
||||
@ -156,11 +138,7 @@ class AuthenticatorManagerTest extends TestCase
|
||||
$authenticator = $this->createAuthenticator();
|
||||
$this->request->attributes->set('_guard_authenticators', [$authenticator]);
|
||||
|
||||
$authenticator->expects($this->any())->method('getCredentials')->willReturn(['username' => 'john']);
|
||||
$authenticator->expects($this->any())->method('getUser')->willReturn($this->user);
|
||||
$this->eventDispatcher->addListener(VerifyAuthenticatorCredentialsEvent::class, function (VerifyAuthenticatorCredentialsEvent $event) {
|
||||
$event->setCredentialsValid(true);
|
||||
});
|
||||
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
|
||||
|
||||
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
|
||||
|
||||
@ -194,12 +172,7 @@ class AuthenticatorManagerTest extends TestCase
|
||||
$authenticator->expects($this->any())->method('isInteractive')->willReturn(true);
|
||||
$this->request->attributes->set('_guard_authenticators', [$authenticator]);
|
||||
|
||||
$authenticator->expects($this->any())->method('getCredentials')->willReturn(['password' => 'pa$$']);
|
||||
$authenticator->expects($this->any())->method('getUser')->willReturn($this->user);
|
||||
|
||||
$this->eventDispatcher->addListener(VerifyAuthenticatorCredentialsEvent::class, function (VerifyAuthenticatorCredentialsEvent $event) {
|
||||
$event->setCredentialsValid(true);
|
||||
});
|
||||
$authenticator->expects($this->any())->method('authenticate')->willReturn(new SelfValidatingPassport($this->user));
|
||||
$authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token);
|
||||
|
||||
$this->tokenStorage->expects($this->once())->method('setToken')->with($this->token);
|
||||
|
@ -14,7 +14,6 @@ namespace Symfony\Component\Security\Http\Tests\Authenticator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AnonymousAuthenticator;
|
||||
|
||||
class AnonymousAuthenticatorTest extends TestCase
|
||||
@ -46,14 +45,9 @@ class AnonymousAuthenticatorTest extends TestCase
|
||||
yield [false, false];
|
||||
}
|
||||
|
||||
public function testAlwaysValidCredentials()
|
||||
{
|
||||
$this->assertTrue($this->authenticator->checkCredentials([], $this->createMock(UserInterface::class)));
|
||||
}
|
||||
|
||||
public function testAuthenticatedToken()
|
||||
{
|
||||
$token = $this->authenticator->createAuthenticatedToken($this->authenticator->getUser([]), 'main');
|
||||
$token = $this->authenticator->createAuthenticatedToken($this->authenticator->authenticate($this->request), 'main');
|
||||
|
||||
$this->assertTrue($token->isAuthenticated());
|
||||
$this->assertEquals('anon.', $token->getUser());
|
||||
|
@ -16,10 +16,14 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
class FormLoginAuthenticatorTest extends TestCase
|
||||
@ -27,11 +31,13 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
private $userProvider;
|
||||
private $successHandler;
|
||||
private $failureHandler;
|
||||
/** @var FormLoginAuthenticator */
|
||||
private $authenticator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->userProvider = $this->createMock(UserProviderInterface::class);
|
||||
$this->userProvider->expects($this->any())->method('loadUserByUsername')->willReturn(new User('test', 's$cr$t'));
|
||||
$this->successHandler = $this->createMock(AuthenticationSuccessHandlerInterface::class);
|
||||
$this->failureHandler = $this->createMock(AuthenticationFailureHandlerInterface::class);
|
||||
}
|
||||
@ -48,11 +54,11 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
$this->expectExceptionMessage('Invalid username.');
|
||||
}
|
||||
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => $username]);
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => $username, '_password' => 's$cr$t']);
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator();
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public function provideUsernamesForLength()
|
||||
@ -73,7 +79,7 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator(['post_only' => $postOnly]);
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +94,7 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator(['post_only' => $postOnly]);
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,22 +109,22 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator(['post_only' => $postOnly]);
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider postOnlyDataProvider
|
||||
*/
|
||||
public function testHandleNonStringUsernameWith__toString($postOnly)
|
||||
public function testHandleNonStringUsernameWithToString($postOnly)
|
||||
{
|
||||
$usernameObject = $this->getMockBuilder(DummyUserClass::class)->getMock();
|
||||
$usernameObject->expects($this->once())->method('__toString')->willReturn('someUsername');
|
||||
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => $usernameObject]);
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => $usernameObject, '_password' => 's$cr$t']);
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator(['post_only' => $postOnly]);
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public function postOnlyDataProvider()
|
||||
@ -127,6 +133,31 @@ class FormLoginAuthenticatorTest extends TestCase
|
||||
yield [false];
|
||||
}
|
||||
|
||||
public function testCsrfProtection()
|
||||
{
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => 'wouter', '_password' => 's$cr$t']);
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->setUpAuthenticator(['enable_csrf' => true]);
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertTrue($passport->hasBadge(CsrfTokenBadge::class));
|
||||
}
|
||||
|
||||
public function testUpgradePassword()
|
||||
{
|
||||
$request = Request::create('/login_check', 'POST', ['_username' => 'wouter', '_password' => 's$cr$t']);
|
||||
$request->setSession($this->createSession());
|
||||
|
||||
$this->userProvider = $this->createMock([UserProviderInterface::class, PasswordUpgraderInterface::class]);
|
||||
$this->userProvider->expects($this->any())->method('loadUserByUsername')->willReturn(new User('test', 's$cr$t'));
|
||||
|
||||
$this->setUpAuthenticator();
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertTrue($passport->hasBadge(PasswordUpgradeBadge::class));
|
||||
$badge = $passport->getBadge(PasswordUpgradeBadge::class);
|
||||
$this->assertEquals('s$cr$t', $badge->getPlaintextPassword());
|
||||
}
|
||||
|
||||
private function setUpAuthenticator(array $options = [])
|
||||
{
|
||||
$this->authenticator = new FormLoginAuthenticator(new HttpUtils(), $this->userProvider, $this->successHandler, $this->failureHandler, $options);
|
||||
|
@ -2,15 +2,16 @@
|
||||
|
||||
namespace Symfony\Component\Security\Http\Tests\Authenticator;
|
||||
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
|
||||
class HttpBasicAuthenticatorTest extends TestCase
|
||||
{
|
||||
@ -39,25 +40,16 @@ class HttpBasicAuthenticatorTest extends TestCase
|
||||
'PHP_AUTH_PW' => 'ThePassword',
|
||||
]);
|
||||
|
||||
$credentials = $this->authenticator->getCredentials($request);
|
||||
$this->assertEquals([
|
||||
'username' => 'TheUsername',
|
||||
'password' => 'ThePassword',
|
||||
], $credentials);
|
||||
|
||||
$mockedUser = $this->getMockBuilder(UserInterface::class)->getMock();
|
||||
$mockedUser->expects($this->any())->method('getPassword')->willReturn('ThePassword');
|
||||
|
||||
$this->userProvider
|
||||
->expects($this->any())
|
||||
->method('loadUserByUsername')
|
||||
->with('TheUsername')
|
||||
->willReturn($mockedUser);
|
||||
->willReturn($user = new User('TheUsername', 'ThePassword'));
|
||||
|
||||
$user = $this->authenticator->getUser($credentials);
|
||||
$this->assertSame($mockedUser, $user);
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertEquals('ThePassword', $passport->getBadge(PasswordCredentials::class)->getPassword());
|
||||
|
||||
$this->assertEquals('ThePassword', $this->authenticator->getPassword($credentials));
|
||||
$this->assertSame($user, $passport->getUser());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,4 +69,21 @@ class HttpBasicAuthenticatorTest extends TestCase
|
||||
[['PHP_AUTH_PW' => 'ThePassword']],
|
||||
];
|
||||
}
|
||||
|
||||
public function testUpgradePassword()
|
||||
{
|
||||
$request = new Request([], [], [], [], [], [
|
||||
'PHP_AUTH_USER' => 'TheUsername',
|
||||
'PHP_AUTH_PW' => 'ThePassword',
|
||||
]);
|
||||
|
||||
$this->userProvider = $this->createMock([UserProviderInterface::class, PasswordUpgraderInterface::class]);
|
||||
$this->userProvider->expects($this->any())->method('loadUserByUsername')->willReturn(new User('test', 's$cr$t'));
|
||||
$authenticator = new HttpBasicAuthenticator('test', $this->userProvider);
|
||||
|
||||
$passport = $authenticator->authenticate($request);
|
||||
$this->assertTrue($passport->hasBadge(PasswordUpgradeBadge::class));
|
||||
$badge = $passport->getBadge(PasswordUpgradeBadge::class);
|
||||
$this->assertEquals('ThePassword', $badge->getPlaintextPassword());
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
class JsonLoginAuthenticatorTest extends TestCase
|
||||
@ -66,39 +68,45 @@ class JsonLoginAuthenticatorTest extends TestCase
|
||||
yield [Request::create('/login', 'GET', [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json']), false];
|
||||
}
|
||||
|
||||
public function testGetCredentials()
|
||||
public function testAuthenticate()
|
||||
{
|
||||
$this->setUpAuthenticator();
|
||||
|
||||
$this->userProvider->expects($this->once())->method('loadUserByUsername')->with('dunglas')->willReturn(new User('dunglas', 'pa$$'));
|
||||
|
||||
$request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"username": "dunglas", "password": "foo"}');
|
||||
$this->assertEquals(['username' => 'dunglas', 'password' => 'foo'], $this->authenticator->getCredentials($request));
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertEquals('foo', $passport->getBadge(PasswordCredentials::class)->getPassword());
|
||||
}
|
||||
|
||||
public function testGetCredentialsCustomPath()
|
||||
public function testAuthenticateWithCustomPath()
|
||||
{
|
||||
$this->setUpAuthenticator([
|
||||
'username_path' => 'authentication.username',
|
||||
'password_path' => 'authentication.password',
|
||||
]);
|
||||
|
||||
$this->userProvider->expects($this->once())->method('loadUserByUsername')->with('dunglas')->willReturn(new User('dunglas', 'pa$$'));
|
||||
|
||||
$request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"authentication": {"username": "dunglas", "password": "foo"}}');
|
||||
$this->assertEquals(['username' => 'dunglas', 'password' => 'foo'], $this->authenticator->getCredentials($request));
|
||||
$passport = $this->authenticator->authenticate($request);
|
||||
$this->assertEquals('foo', $passport->getBadge(PasswordCredentials::class)->getPassword());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidGetCredentialsData
|
||||
* @dataProvider provideInvalidAuthenticateData
|
||||
*/
|
||||
public function testGetCredentialsInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
|
||||
public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class)
|
||||
{
|
||||
$this->expectException($exceptionType);
|
||||
$this->expectExceptionMessage($errorMessage);
|
||||
|
||||
$this->setUpAuthenticator();
|
||||
|
||||
$this->authenticator->getCredentials($request);
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public function provideInvalidGetCredentialsData()
|
||||
public function provideInvalidAuthenticateData()
|
||||
{
|
||||
$request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json']);
|
||||
yield [$request, 'Invalid JSON.'];
|
||||
|
@ -14,11 +14,13 @@ namespace Symfony\Component\Security\Http\Tests\Authenticator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticator;
|
||||
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
|
||||
|
||||
class RememberMeAuthenticatorTest extends TestCase
|
||||
{
|
||||
@ -29,7 +31,7 @@ class RememberMeAuthenticatorTest extends TestCase
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->rememberMeServices = $this->createMock(AbstractRememberMeServices::class);
|
||||
$this->rememberMeServices = $this->createMock(RememberMeServicesInterface::class);
|
||||
$this->tokenStorage = $this->createMock(TokenStorage::class);
|
||||
$this->authenticator = new RememberMeAuthenticator($this->rememberMeServices, 's3cr3t', $this->tokenStorage, [
|
||||
'name' => '_remember_me_cookie',
|
||||
@ -67,22 +69,14 @@ class RememberMeAuthenticatorTest extends TestCase
|
||||
|
||||
public function testAuthenticate()
|
||||
{
|
||||
$credentials = $this->authenticator->getCredentials($this->request);
|
||||
$this->assertEquals(['part1', 'part2'], $credentials['cookie_parts']);
|
||||
$this->assertSame($this->request, $credentials['request']);
|
||||
$this->rememberMeServices->expects($this->once())
|
||||
->method('autoLogin')
|
||||
->with($this->request)
|
||||
->willReturn(new RememberMeToken($user = new User('wouter', 'test'), 'main', 'secret'));
|
||||
|
||||
$user = $this->createMock(UserInterface::class);
|
||||
$this->rememberMeServices->expects($this->any())
|
||||
->method('performLogin')
|
||||
->with($credentials['cookie_parts'], $credentials['request'])
|
||||
->willReturn($user);
|
||||
$passport = $this->authenticator->authenticate($this->request);
|
||||
|
||||
$this->assertSame($user, $this->authenticator->getUser($credentials));
|
||||
}
|
||||
|
||||
public function testCredentialsAlwaysValid()
|
||||
{
|
||||
$this->assertTrue($this->authenticator->checkCredentials([], $this->createMock(UserInterface::class)));
|
||||
$this->assertSame($user, $passport->getUser());
|
||||
}
|
||||
|
||||
private function generateCookieValue()
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\Authenticator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
|
||||
|
||||
@ -22,7 +23,7 @@ class RemoteUserAuthenticatorTest extends TestCase
|
||||
/**
|
||||
* @dataProvider provideAuthenticators
|
||||
*/
|
||||
public function testSupport(RemoteUserAuthenticator $authenticator, $parameterName)
|
||||
public function testSupport(UserProviderInterface $userProvider, RemoteUserAuthenticator $authenticator, $parameterName)
|
||||
{
|
||||
$request = $this->createRequest([$parameterName => 'TheUsername']);
|
||||
|
||||
@ -39,20 +40,28 @@ class RemoteUserAuthenticatorTest extends TestCase
|
||||
/**
|
||||
* @dataProvider provideAuthenticators
|
||||
*/
|
||||
public function testGetCredentials(RemoteUserAuthenticator $authenticator, $parameterName)
|
||||
public function testAuthenticate(UserProviderInterface $userProvider, RemoteUserAuthenticator $authenticator, $parameterName)
|
||||
{
|
||||
$request = $this->createRequest([$parameterName => 'TheUsername']);
|
||||
|
||||
$authenticator->supports($request);
|
||||
$this->assertEquals(['username' => 'TheUsername'], $authenticator->getCredentials($request));
|
||||
|
||||
$userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->with('TheUsername')
|
||||
->willReturn($user = new User('TheUsername', null));
|
||||
|
||||
$passport = $authenticator->authenticate($request);
|
||||
$this->assertEquals($user, $passport->getUser());
|
||||
}
|
||||
|
||||
public function provideAuthenticators()
|
||||
{
|
||||
$userProvider = $this->createMock(UserProviderInterface::class);
|
||||
yield [$userProvider, new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main'), 'REMOTE_USER'];
|
||||
|
||||
yield [new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main'), 'REMOTE_USER'];
|
||||
yield [new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main', 'CUSTOM_USER_PARAMETER'), 'CUSTOM_USER_PARAMETER'];
|
||||
$userProvider = $this->createMock(UserProviderInterface::class);
|
||||
yield [$userProvider, new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main', 'CUSTOM_USER_PARAMETER'), 'CUSTOM_USER_PARAMETER'];
|
||||
}
|
||||
|
||||
private function createRequest(array $server)
|
||||
|
@ -14,6 +14,7 @@ namespace Symfony\Component\Security\Http\Tests\Authenticator;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
|
||||
|
||||
@ -43,7 +44,13 @@ class X509AuthenticatorTest extends TestCase
|
||||
|
||||
$request = $this->createRequest($serverVars);
|
||||
$this->assertTrue($this->authenticator->supports($request));
|
||||
$this->assertEquals(['username' => $user], $this->authenticator->getCredentials($request));
|
||||
|
||||
$this->userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->with($user)
|
||||
->willReturn(new User($user, null));
|
||||
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public static function provideServerVars()
|
||||
@ -60,7 +67,13 @@ class X509AuthenticatorTest extends TestCase
|
||||
$request = $this->createRequest(['SSL_CLIENT_S_DN' => $credentials]);
|
||||
|
||||
$this->assertTrue($this->authenticator->supports($request));
|
||||
$this->assertEquals(['username' => $emailAddress], $this->authenticator->getCredentials($request));
|
||||
|
||||
$this->userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->with($emailAddress)
|
||||
->willReturn(new User($emailAddress, null));
|
||||
|
||||
$this->authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public static function provideServerVarsNoUser()
|
||||
@ -89,7 +102,13 @@ class X509AuthenticatorTest extends TestCase
|
||||
'TheUserKey' => 'TheUser',
|
||||
]);
|
||||
$this->assertTrue($authenticator->supports($request));
|
||||
$this->assertEquals(['username' => 'TheUser'], $authenticator->getCredentials($request));
|
||||
|
||||
$this->userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->with('TheUser')
|
||||
->willReturn(new User('TheUser', null));
|
||||
|
||||
$authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
public function testAuthenticationCustomCredentialsKey()
|
||||
@ -100,7 +119,13 @@ class X509AuthenticatorTest extends TestCase
|
||||
'TheCertKey' => 'CN=Sample certificate DN/emailAddress=cert@example.com',
|
||||
]);
|
||||
$this->assertTrue($authenticator->supports($request));
|
||||
$this->assertEquals(['username' => 'cert@example.com'], $authenticator->getCredentials($request));
|
||||
|
||||
$this->userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')
|
||||
->with('cert@example.com')
|
||||
->willReturn(new User('cert@example.com', null));
|
||||
|
||||
$authenticator->authenticate($request);
|
||||
}
|
||||
|
||||
private function createRequest(array $server)
|
||||
|
@ -13,10 +13,12 @@ namespace Symfony\Component\Security\Http\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\CsrfProtectedAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\CsrfProtectionListener;
|
||||
|
||||
@ -31,11 +33,11 @@ class CsrfProtectionListenerTest extends TestCase
|
||||
$this->listener = new CsrfProtectionListener($this->csrfTokenManager);
|
||||
}
|
||||
|
||||
public function testNonCsrfProtectedAuthenticator()
|
||||
public function testNoCsrfTokenBadge()
|
||||
{
|
||||
$this->csrfTokenManager->expects($this->never())->method('isTokenValid');
|
||||
|
||||
$event = $this->createEvent($this->createAuthenticator(false));
|
||||
$event = $this->createEvent($this->createPassport(null));
|
||||
$this->listener->verifyCredentials($event);
|
||||
}
|
||||
|
||||
@ -46,7 +48,7 @@ class CsrfProtectionListenerTest extends TestCase
|
||||
->with(new CsrfToken('authenticator_token_id', 'abc123'))
|
||||
->willReturn(true);
|
||||
|
||||
$event = $this->createEvent($this->createAuthenticator(true), ['_csrf' => 'abc123']);
|
||||
$event = $this->createEvent($this->createPassport(new CsrfTokenBadge('authenticator_token_id', 'abc123')));
|
||||
$this->listener->verifyCredentials($event);
|
||||
|
||||
$this->expectNotToPerformAssertions();
|
||||
@ -62,28 +64,22 @@ class CsrfProtectionListenerTest extends TestCase
|
||||
->with(new CsrfToken('authenticator_token_id', 'abc123'))
|
||||
->willReturn(false);
|
||||
|
||||
$event = $this->createEvent($this->createAuthenticator(true), ['_csrf' => 'abc123']);
|
||||
$event = $this->createEvent($this->createPassport(new CsrfTokenBadge('authenticator_token_id', 'abc123')));
|
||||
$this->listener->verifyCredentials($event);
|
||||
}
|
||||
|
||||
private function createEvent($authenticator, $credentials = null)
|
||||
private function createEvent($passport)
|
||||
{
|
||||
return new VerifyAuthenticatorCredentialsEvent($authenticator, $credentials, null);
|
||||
return new VerifyAuthenticatorCredentialsEvent($this->createMock(AuthenticatorInterface::class), $passport);
|
||||
}
|
||||
|
||||
private function createAuthenticator($supportsCsrf)
|
||||
private function createPassport(?CsrfTokenBadge $badge)
|
||||
{
|
||||
if (!$supportsCsrf) {
|
||||
return $this->createMock(AuthenticatorInterface::class);
|
||||
$passport = new SelfValidatingPassport(new User('wouter', 'pass'));
|
||||
if ($badge) {
|
||||
$passport->addBadge($badge);
|
||||
}
|
||||
|
||||
$authenticator = $this->createMock([AuthenticatorInterface::class, CsrfProtectedAuthenticatorInterface::class]);
|
||||
$authenticator->expects($this->any())->method('getCsrfTokenId')->willReturn('authenticator_token_id');
|
||||
$authenticator->expects($this->any())
|
||||
->method('getCsrfToken')
|
||||
->with(['_csrf' => 'abc123'])
|
||||
->willReturn('abc123');
|
||||
|
||||
return $authenticator;
|
||||
return $passport;
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,17 @@
|
||||
namespace Symfony\Component\Security\Http\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\PasswordMigratingListener;
|
||||
|
||||
class PasswordMigratingListenerTest extends TestCase
|
||||
@ -41,23 +45,19 @@ class PasswordMigratingListenerTest extends TestCase
|
||||
{
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$this->listener->onCredentialsVerification($event);
|
||||
$this->listener->onLoginSuccess($event);
|
||||
}
|
||||
|
||||
public function provideUnsupportedEvents()
|
||||
{
|
||||
// unsupported authenticators
|
||||
yield [$this->createEvent($this->createMock(AuthenticatorInterface::class), $this->user)];
|
||||
yield [$this->createEvent($this->createMock([AuthenticatorInterface::class, PasswordAuthenticatedInterface::class]), $this->user)];
|
||||
// no password upgrade badge
|
||||
yield [$this->createEvent(new SelfValidatingPassport($this->createMock(UserInterface::class)))];
|
||||
|
||||
// null password
|
||||
yield [$this->createEvent($this->createAuthenticator(null), $this->user)];
|
||||
// blank password
|
||||
yield [$this->createEvent(new SelfValidatingPassport($this->createMock(UserInterface::class), [new PasswordUpgradeBadge('', $this->createPasswordUpgrader())]))];
|
||||
|
||||
// no user
|
||||
yield [$this->createEvent($this->createAuthenticator('pa$$word'), null)];
|
||||
|
||||
// invalid password
|
||||
yield [$this->createEvent($this->createAuthenticator('pa$$word'), $this->user, false)];
|
||||
yield [$this->createEvent($this->createMock(PassportInterface::class))];
|
||||
}
|
||||
|
||||
public function testUpgrade()
|
||||
@ -70,32 +70,23 @@ class PasswordMigratingListenerTest extends TestCase
|
||||
|
||||
$this->user->expects($this->any())->method('getPassword')->willReturn('old-encoded-password');
|
||||
|
||||
$authenticator = $this->createAuthenticator('pa$$word');
|
||||
$authenticator->expects($this->once())
|
||||
$passwordUpgrader = $this->createPasswordUpgrader();
|
||||
$passwordUpgrader->expects($this->once())
|
||||
->method('upgradePassword')
|
||||
->with($this->user, 'new-encoded-password')
|
||||
;
|
||||
|
||||
$event = $this->createEvent($authenticator, $this->user);
|
||||
$this->listener->onCredentialsVerification($event);
|
||||
$event = $this->createEvent(new SelfValidatingPassport($this->user, [new PasswordUpgradeBadge('pa$$word', $passwordUpgrader)]));
|
||||
$this->listener->onLoginSuccess($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuthenticatorInterface
|
||||
*/
|
||||
private function createAuthenticator($password)
|
||||
private function createPasswordUpgrader()
|
||||
{
|
||||
$authenticator = $this->createMock([AuthenticatorInterface::class, PasswordAuthenticatedInterface::class, PasswordUpgraderInterface::class]);
|
||||
$authenticator->expects($this->any())->method('getPassword')->willReturn($password);
|
||||
|
||||
return $authenticator;
|
||||
return $this->createMock(PasswordUpgraderInterface::class);
|
||||
}
|
||||
|
||||
private function createEvent($authenticator, $user, $credentialsValid = true)
|
||||
private function createEvent(PassportInterface $passport)
|
||||
{
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, [], $user);
|
||||
$event->setCredentialsValid($credentialsValid);
|
||||
|
||||
return $event;
|
||||
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main');
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,11 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\RememberMeAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\LoginFailureEvent;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\RememberMeListener;
|
||||
@ -40,26 +43,14 @@ class RememberMeListenerTest extends TestCase
|
||||
$this->token = $this->createMock(TokenInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideUnsupportingAuthenticators
|
||||
*/
|
||||
public function testSuccessfulLoginWithoutSupportingAuthenticator($authenticator)
|
||||
public function testSuccessfulLoginWithoutSupportingAuthenticator()
|
||||
{
|
||||
$this->rememberMeServices->expects($this->never())->method('loginSuccess');
|
||||
|
||||
$event = $this->createLoginSuccessfulEvent('main_firewall', $this->response, $authenticator);
|
||||
$event = $this->createLoginSuccessfulEvent('main_firewall', $this->response, new SelfValidatingPassport(new User('wouter', null)));
|
||||
$this->listener->onSuccessfulLogin($event);
|
||||
}
|
||||
|
||||
public function provideUnsupportingAuthenticators()
|
||||
{
|
||||
yield [$this->createMock(AuthenticatorInterface::class)];
|
||||
|
||||
$authenticator = $this->createMock([AuthenticatorInterface::class, RememberMeAuthenticatorInterface::class]);
|
||||
$authenticator->expects($this->any())->method('supportsRememberMe')->willReturn(false);
|
||||
yield [$authenticator];
|
||||
}
|
||||
|
||||
public function testSuccessfulLoginWithoutSuccessResponse()
|
||||
{
|
||||
$this->rememberMeServices->expects($this->never())->method('loginSuccess');
|
||||
@ -84,14 +75,13 @@ class RememberMeListenerTest extends TestCase
|
||||
$this->listener->onFailedLogin($event);
|
||||
}
|
||||
|
||||
private function createLoginSuccessfulEvent($providerKey, $response, $authenticator = null)
|
||||
private function createLoginSuccessfulEvent($providerKey, $response, PassportInterface $passport = null)
|
||||
{
|
||||
if (null === $authenticator) {
|
||||
$authenticator = $this->createMock([AuthenticatorInterface::class, RememberMeAuthenticatorInterface::class]);
|
||||
$authenticator->expects($this->any())->method('supportsRememberMe')->willReturn(true);
|
||||
if (null === $passport) {
|
||||
$passport = new SelfValidatingPassport(new User('test', null), [new RememberMeBadge()]);
|
||||
}
|
||||
|
||||
return new LoginSuccessEvent($authenticator, $this->token, $this->request, $response, $providerKey);
|
||||
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->token, $this->request, $response, $providerKey);
|
||||
}
|
||||
|
||||
private function createLoginFailureEvent($providerKey)
|
||||
|
@ -14,12 +14,14 @@ namespace Symfony\Component\Security\Http\Tests\EventListener;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\SessionStrategyListener;
|
||||
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
|
||||
|
||||
class SessionListenerTest extends TestCase
|
||||
class SessionStrategyListenerTest extends TestCase
|
||||
{
|
||||
private $sessionAuthenticationStrategy;
|
||||
private $listener;
|
||||
@ -60,7 +62,7 @@ class SessionListenerTest extends TestCase
|
||||
|
||||
private function createEvent($providerKey)
|
||||
{
|
||||
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $this->token, $this->request, null, $providerKey);
|
||||
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), new SelfValidatingPassport(new User('test', null)), $this->token, $this->request, null, $providerKey);
|
||||
}
|
||||
|
||||
private function configurePreviousSession()
|
@ -12,9 +12,15 @@
|
||||
namespace Symfony\Component\Security\Http\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\UserCheckerListener;
|
||||
|
||||
@ -28,51 +34,59 @@ class UserCheckerListenerTest extends TestCase
|
||||
{
|
||||
$this->userChecker = $this->createMock(UserCheckerInterface::class);
|
||||
$this->listener = new UserCheckerListener($this->userChecker);
|
||||
$this->user = $this->createMock(UserInterface::class);
|
||||
$this->user = new User('test', null);
|
||||
}
|
||||
|
||||
public function testPreAuth()
|
||||
{
|
||||
$this->userChecker->expects($this->once())->method('checkPreAuth')->with($this->user);
|
||||
|
||||
$this->listener->preCredentialsVerification($this->createEvent());
|
||||
$this->listener->preCredentialsVerification($this->createVerifyAuthenticatorCredentialsEvent());
|
||||
}
|
||||
|
||||
public function testPreAuthNoUser()
|
||||
{
|
||||
$this->userChecker->expects($this->never())->method('checkPreAuth');
|
||||
|
||||
$this->listener->preCredentialsVerification($this->createEvent(true, null));
|
||||
$this->listener->preCredentialsVerification($this->createVerifyAuthenticatorCredentialsEvent($this->createMock(PassportInterface::class)));
|
||||
}
|
||||
|
||||
public function testPreAuthenticatedBadge()
|
||||
{
|
||||
$this->userChecker->expects($this->never())->method('checkPreAuth');
|
||||
|
||||
$this->listener->preCredentialsVerification($this->createVerifyAuthenticatorCredentialsEvent(new SelfValidatingPassport($this->user, [new PreAuthenticatedUserBadge()])));
|
||||
}
|
||||
|
||||
public function testPostAuthValidCredentials()
|
||||
{
|
||||
$this->userChecker->expects($this->once())->method('checkPostAuth')->with($this->user);
|
||||
|
||||
$this->listener->postCredentialsVerification($this->createEvent(true));
|
||||
}
|
||||
|
||||
public function testPostAuthInvalidCredentials()
|
||||
{
|
||||
$this->userChecker->expects($this->never())->method('checkPostAuth')->with($this->user);
|
||||
|
||||
$this->listener->postCredentialsVerification($this->createEvent());
|
||||
$this->listener->postCredentialsVerification($this->createLoginSuccessEvent());
|
||||
}
|
||||
|
||||
public function testPostAuthNoUser()
|
||||
{
|
||||
$this->userChecker->expects($this->never())->method('checkPostAuth');
|
||||
|
||||
$this->listener->postCredentialsVerification($this->createEvent(true, null));
|
||||
$this->listener->postCredentialsVerification($this->createLoginSuccessEvent($this->createMock(PassportInterface::class)));
|
||||
}
|
||||
|
||||
private function createEvent($credentialsValid = false, $customUser = false)
|
||||
private function createVerifyAuthenticatorCredentialsEvent($passport = null)
|
||||
{
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createMock(AuthenticatorInterface::class), [], false === $customUser ? $this->user : $customUser);
|
||||
if ($credentialsValid) {
|
||||
$event->setCredentialsValid(true);
|
||||
if (null === $passport) {
|
||||
$passport = new SelfValidatingPassport($this->user);
|
||||
}
|
||||
|
||||
return $event;
|
||||
return new VerifyAuthenticatorCredentialsEvent($this->createMock(AuthenticatorInterface::class), $passport);
|
||||
}
|
||||
|
||||
private function createLoginSuccessEvent($passport = null)
|
||||
{
|
||||
if (null === $passport) {
|
||||
$passport = new SelfValidatingPassport($this->user);
|
||||
}
|
||||
|
||||
return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main');
|
||||
}
|
||||
}
|
||||
|
@ -15,12 +15,12 @@ use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\LogicException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\User;
|
||||
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\CustomAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\PasswordAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\TokenAuthenticatedInterface;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
|
||||
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
|
||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||
use Symfony\Component\Security\Http\EventListener\VerifyAuthenticatorCredentialsListener;
|
||||
|
||||
@ -34,7 +34,7 @@ class VerifyAuthenticatorCredentialsListenerTest extends TestCase
|
||||
{
|
||||
$this->encoderFactory = $this->createMock(EncoderFactoryInterface::class);
|
||||
$this->listener = new VerifyAuthenticatorCredentialsListener($this->encoderFactory);
|
||||
$this->user = $this->createMock(UserInterface::class);
|
||||
$this->user = new User('wouter', 'encoded-password');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,16 +42,22 @@ class VerifyAuthenticatorCredentialsListenerTest extends TestCase
|
||||
*/
|
||||
public function testPasswordAuthenticated($password, $passwordValid, $result)
|
||||
{
|
||||
$this->user->expects($this->any())->method('getPassword')->willReturn('encoded-password');
|
||||
|
||||
$encoder = $this->createMock(PasswordEncoderInterface::class);
|
||||
$encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', $password)->willReturn($passwordValid);
|
||||
|
||||
$this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder);
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('password', $password), ['password' => $password], $this->user);
|
||||
$this->listener->onAuthenticating($event);
|
||||
$this->assertEquals($result, $event->areCredentialsValid());
|
||||
if (false === $result) {
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
$this->expectExceptionMessage('The presented password is invalid.');
|
||||
}
|
||||
|
||||
$credentials = new PasswordCredentials($password);
|
||||
$this->listener->onAuthenticating($this->createEvent(new Passport($this->user, $credentials)));
|
||||
|
||||
if (true === $result) {
|
||||
$this->assertTrue($credentials->isResolved());
|
||||
}
|
||||
}
|
||||
|
||||
public function providePasswords()
|
||||
@ -67,30 +73,10 @@ class VerifyAuthenticatorCredentialsListenerTest extends TestCase
|
||||
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('password', ''), ['password' => ''], $this->user);
|
||||
$event = $this->createEvent(new Passport($this->user, new PasswordCredentials('')));
|
||||
$this->listener->onAuthenticating($event);
|
||||
}
|
||||
|
||||
public function testTokenAuthenticated()
|
||||
{
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('token', 'some_token'), ['token' => 'abc'], $this->user);
|
||||
$this->listener->onAuthenticating($event);
|
||||
|
||||
$this->assertTrue($event->areCredentialsValid());
|
||||
}
|
||||
|
||||
public function testTokenAuthenticatedReturningNull()
|
||||
{
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('token', null), ['token' => 'abc'], $this->user);
|
||||
$this->listener->onAuthenticating($event);
|
||||
|
||||
$this->assertFalse($event->areCredentialsValid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideCustomAuthenticatedResults
|
||||
*/
|
||||
@ -98,10 +84,18 @@ class VerifyAuthenticatorCredentialsListenerTest extends TestCase
|
||||
{
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator('custom', $result), [], $this->user);
|
||||
$this->listener->onAuthenticating($event);
|
||||
if (false === $result) {
|
||||
$this->expectException(BadCredentialsException::class);
|
||||
}
|
||||
|
||||
$this->assertEquals($result, $event->areCredentialsValid());
|
||||
$credentials = new CustomCredentials(function () use ($result) {
|
||||
return $result;
|
||||
}, ['password' => 'foo']);
|
||||
$this->listener->onAuthenticating($this->createEvent(new Passport($this->user, $credentials)));
|
||||
|
||||
if (true === $result) {
|
||||
$this->assertTrue($credentials->isResolved());
|
||||
}
|
||||
}
|
||||
|
||||
public function provideCustomAuthenticatedResults()
|
||||
@ -110,58 +104,16 @@ class VerifyAuthenticatorCredentialsListenerTest extends TestCase
|
||||
yield [false];
|
||||
}
|
||||
|
||||
public function testAlreadyAuthenticated()
|
||||
public function testNoCredentialsBadgeProvided()
|
||||
{
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($this->createAuthenticator(), [], $this->user);
|
||||
$event->setCredentialsValid(true);
|
||||
$this->listener->onAuthenticating($event);
|
||||
|
||||
$this->assertTrue($event->areCredentialsValid());
|
||||
}
|
||||
|
||||
public function testNoAuthenticatedInterfaceImplemented()
|
||||
{
|
||||
$authenticator = $this->createAuthenticator();
|
||||
$this->expectException(LogicException::class);
|
||||
$this->expectExceptionMessage(sprintf('Authenticator %s does not have valid credentials. Authenticators must implement one of the authenticated interfaces (%s, %s or %s).', \get_class($authenticator), PasswordAuthenticatedInterface::class, TokenAuthenticatedInterface::class, CustomAuthenticatedInterface::class));
|
||||
|
||||
$this->encoderFactory->expects($this->never())->method('getEncoder');
|
||||
|
||||
$event = new VerifyAuthenticatorCredentialsEvent($authenticator, [], $this->user);
|
||||
$event = $this->createEvent(new SelfValidatingPassport($this->user));
|
||||
$this->listener->onAuthenticating($event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AuthenticatorInterface
|
||||
*/
|
||||
private function createAuthenticator(?string $type = null, $result = null)
|
||||
private function createEvent($passport)
|
||||
{
|
||||
$interfaces = [AuthenticatorInterface::class];
|
||||
switch ($type) {
|
||||
case 'password':
|
||||
$interfaces[] = PasswordAuthenticatedInterface::class;
|
||||
break;
|
||||
case 'token':
|
||||
$interfaces[] = TokenAuthenticatedInterface::class;
|
||||
break;
|
||||
case 'custom':
|
||||
$interfaces[] = CustomAuthenticatedInterface::class;
|
||||
break;
|
||||
}
|
||||
|
||||
$authenticator = $this->createMock(1 === \count($interfaces) ? $interfaces[0] : $interfaces);
|
||||
switch ($type) {
|
||||
case 'password':
|
||||
$authenticator->expects($this->any())->method('getPassword')->willReturn($result);
|
||||
break;
|
||||
case 'token':
|
||||
$authenticator->expects($this->any())->method('getToken')->willReturn($result);
|
||||
break;
|
||||
case 'custom':
|
||||
$authenticator->expects($this->any())->method('checkCredentials')->willReturn($result);
|
||||
break;
|
||||
}
|
||||
|
||||
return $authenticator;
|
||||
return new VerifyAuthenticatorCredentialsEvent($this->createMock(AuthenticatorInterface::class), $passport);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user