Added pre-authenticated authenticators (X.509 & REMOTE_USER)
This commit is contained in:
parent
f5e11e5f32
commit
95edc806a1
@ -22,7 +22,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
|||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||||
*/
|
*/
|
||||||
class RemoteUserFactory implements SecurityFactoryInterface
|
class RemoteUserFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
|
||||||
{
|
{
|
||||||
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
|
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
|
||||||
{
|
{
|
||||||
@ -43,6 +43,19 @@ class RemoteUserFactory implements SecurityFactoryInterface
|
|||||||
return [$providerId, $listenerId, $defaultEntryPoint];
|
return [$providerId, $listenerId, $defaultEntryPoint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId)
|
||||||
|
{
|
||||||
|
$authenticatorId = 'security.authenticator.remote_user.'.$id;
|
||||||
|
$container
|
||||||
|
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.remote_user'))
|
||||||
|
->replaceArgument(0, new Reference($userProviderId))
|
||||||
|
->replaceArgument(2, $firewallName)
|
||||||
|
->replaceArgument(3, $config['user'])
|
||||||
|
;
|
||||||
|
|
||||||
|
return $authenticatorId;
|
||||||
|
}
|
||||||
|
|
||||||
public function getPosition()
|
public function getPosition()
|
||||||
{
|
{
|
||||||
return 'pre_auth';
|
return 'pre_auth';
|
||||||
|
@ -21,7 +21,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
|||||||
*
|
*
|
||||||
* @author Fabien Potencier <fabien@symfony.com>
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
*/
|
*/
|
||||||
class X509Factory implements SecurityFactoryInterface
|
class X509Factory implements SecurityFactoryInterface, AuthenticatorFactoryInterface
|
||||||
{
|
{
|
||||||
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
|
public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint)
|
||||||
{
|
{
|
||||||
@ -44,6 +44,20 @@ class X509Factory implements SecurityFactoryInterface
|
|||||||
return [$providerId, $listenerId, $defaultEntryPoint];
|
return [$providerId, $listenerId, $defaultEntryPoint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId)
|
||||||
|
{
|
||||||
|
$authenticatorId = 'security.authenticator.x509.'.$id;
|
||||||
|
$container
|
||||||
|
->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.x509'))
|
||||||
|
->replaceArgument(0, new Reference($userProviderId))
|
||||||
|
->replaceArgument(2, $firewallName)
|
||||||
|
->replaceArgument(3, $config['user'])
|
||||||
|
->replaceArgument(4, $config['credentials'])
|
||||||
|
;
|
||||||
|
|
||||||
|
return $authenticatorId;
|
||||||
|
}
|
||||||
|
|
||||||
public function getPosition()
|
public function getPosition()
|
||||||
{
|
{
|
||||||
return 'pre_auth';
|
return 'pre_auth';
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
class="Symfony\Component\Security\Http\Authentication\AuthenticatorManager"
|
class="Symfony\Component\Security\Http\Authentication\AuthenticatorManager"
|
||||||
abstract="true"
|
abstract="true"
|
||||||
>
|
>
|
||||||
|
<tag name="monolog.logger" channel="security" />
|
||||||
<argument type="abstract">authenticators</argument>
|
<argument type="abstract">authenticators</argument>
|
||||||
<argument type="service" id="security.token_storage" />
|
<argument type="service" id="security.token_storage" />
|
||||||
<argument type="service" id="event_dispatcher" />
|
<argument type="service" id="event_dispatcher" />
|
||||||
@ -82,6 +83,7 @@
|
|||||||
<service id="security.authenticator.http_basic"
|
<service id="security.authenticator.http_basic"
|
||||||
class="Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator"
|
class="Symfony\Component\Security\Http\Authenticator\HttpBasicAuthenticator"
|
||||||
abstract="true">
|
abstract="true">
|
||||||
|
<tag name="monolog.logger" channel="security" />
|
||||||
<argument type="abstract">realm name</argument>
|
<argument type="abstract">realm name</argument>
|
||||||
<argument type="abstract">user provider</argument>
|
<argument type="abstract">user provider</argument>
|
||||||
<argument type="service" id="logger" on-invalid="null" />
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
@ -111,5 +113,28 @@
|
|||||||
<argument type="abstract">options</argument>
|
<argument type="abstract">options</argument>
|
||||||
<argument type="service" id="security.authentication.session_strategy" />
|
<argument type="service" id="security.authentication.session_strategy" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="security.authenticator.x509"
|
||||||
|
class="Symfony\Component\Security\Http\Authenticator\X509Authenticator"
|
||||||
|
abstract="true">
|
||||||
|
<tag name="monolog.logger" channel="security" />
|
||||||
|
<argument type="abstract">user provider</argument>
|
||||||
|
<argument type="service" id="security.token_storage"/>
|
||||||
|
<argument type="abstract">firewall name</argument>
|
||||||
|
<argument type="abstract">user key</argument>
|
||||||
|
<argument type="abstract">credentials key</argument>
|
||||||
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service id="security.authenticator.remote_user"
|
||||||
|
class="Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator"
|
||||||
|
abstract="true">
|
||||||
|
<tag name="monolog.logger" channel="security" />
|
||||||
|
<argument type="abstract">user provider</argument>
|
||||||
|
<argument type="service" id="security.token_storage"/>
|
||||||
|
<argument type="abstract">firewall name</argument>
|
||||||
|
<argument type="abstract">user key</argument>
|
||||||
|
<argument type="service" id="logger" on-invalid="null" />
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
<?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 Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base authenticator for authenticators to use pre-authenticated
|
||||||
|
* requests (e.g. using certificates).
|
||||||
|
*
|
||||||
|
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @experimental in Symfony 5.1
|
||||||
|
*/
|
||||||
|
abstract class AbstractPreAuthenticatedAuthenticator implements InteractiveAuthenticatorInterface, CustomAuthenticatedInterface
|
||||||
|
{
|
||||||
|
private $userProvider;
|
||||||
|
private $tokenStorage;
|
||||||
|
private $firewallName;
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, ?LoggerInterface $logger = null)
|
||||||
|
{
|
||||||
|
$this->userProvider = $userProvider;
|
||||||
|
$this->tokenStorage = $tokenStorage;
|
||||||
|
$this->firewallName = $firewallName;
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username of the pre-authenticated user.
|
||||||
|
*
|
||||||
|
* This authenticator is skipped if null is returned or a custom
|
||||||
|
* BadCredentialsException is thrown.
|
||||||
|
*/
|
||||||
|
abstract protected function extractUsername(Request $request): ?string;
|
||||||
|
|
||||||
|
public function supports(Request $request): ?bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$username = $this->extractUsername($request);
|
||||||
|
} catch (BadCredentialsException $e) {
|
||||||
|
$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)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $username) {
|
||||||
|
if (null !== $this->logger) {
|
||||||
|
$this->logger->debug('Skipping pre-authenticated authenticator no username could be extracted.', ['authenticator' => \get_class($this)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->attributes->set('_pre_authenticated_username', $username);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCredentials(Request $request)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username' => $request->attributes->get('_pre_authenticated_username'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser($credentials): ?UserInterface
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||||
|
{
|
||||||
|
return null; // let the original request continue
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||||
|
{
|
||||||
|
$this->clearToken($exception);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isInteractive(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clearToken(AuthenticationException $exception): void
|
||||||
|
{
|
||||||
|
$token = $this->tokenStorage->getToken();
|
||||||
|
if ($token instanceof PreAuthenticatedToken && $this->firewallName === $token->getProviderKey()) {
|
||||||
|
$this->tokenStorage->setToken(null);
|
||||||
|
|
||||||
|
if (null !== $this->logger) {
|
||||||
|
$this->logger->info('Cleared pre-authenticated token due to an exception.', ['exception' => $exception]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
<?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 Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authenticator authenticates a remote user.
|
||||||
|
*
|
||||||
|
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||||
|
*
|
||||||
|
* @internal in Symfony 5.1
|
||||||
|
*/
|
||||||
|
class RemoteUserAuthenticator extends AbstractPreAuthenticatedAuthenticator
|
||||||
|
{
|
||||||
|
private $userKey;
|
||||||
|
|
||||||
|
public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'REMOTE_USER', ?LoggerInterface $logger = null)
|
||||||
|
{
|
||||||
|
parent::__construct($userProvider, $tokenStorage, $firewallName, $logger);
|
||||||
|
|
||||||
|
$this->userKey = $userKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractUsername(Request $request): ?string
|
||||||
|
{
|
||||||
|
if (!$request->server->has($this->userKey)) {
|
||||||
|
throw new BadCredentialsException(sprintf('User key was not found: "%s".', $this->userKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->server->get($this->userKey);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
<?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 Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This authenticator authenticates pre-authenticated (by the
|
||||||
|
* webserver) X.509 certificates.
|
||||||
|
*
|
||||||
|
* @author Wouter de Jong <wouter@wouterj.nl>
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
* @experimental in Symfony 5.1
|
||||||
|
*/
|
||||||
|
class X509Authenticator extends AbstractPreAuthenticatedAuthenticator
|
||||||
|
{
|
||||||
|
private $userKey;
|
||||||
|
private $credentialsKey;
|
||||||
|
|
||||||
|
public function __construct(UserProviderInterface $userProvider, TokenStorageInterface $tokenStorage, string $firewallName, string $userKey = 'SSL_CLIENT_S_DN_Email', string $credentialsKey = 'SSL_CLIENT_S_DN', ?LoggerInterface $logger = null)
|
||||||
|
{
|
||||||
|
parent::__construct($userProvider, $tokenStorage, $firewallName, $logger);
|
||||||
|
|
||||||
|
$this->userKey = $userKey;
|
||||||
|
$this->credentialsKey = $credentialsKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractUsername(Request $request): string
|
||||||
|
{
|
||||||
|
$username = null;
|
||||||
|
if ($request->server->has($this->userKey)) {
|
||||||
|
$username = $request->server->get($this->userKey);
|
||||||
|
} elseif (
|
||||||
|
$request->server->has($this->credentialsKey)
|
||||||
|
&& preg_match('#emailAddress=([^,/@]++@[^,/]++)#', $request->server->get($this->credentialsKey), $matches)
|
||||||
|
) {
|
||||||
|
$username = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $username) {
|
||||||
|
throw new BadCredentialsException(sprintf('SSL credentials not found: %s, %s', $this->userKey, $this->credentialsKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ namespace Symfony\Component\Security\Http\EventListener;
|
|||||||
|
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\AbstractPreAuthenticatedAuthenticator;
|
||||||
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +24,7 @@ class UserCheckerListener implements EventSubscriberInterface
|
|||||||
|
|
||||||
public function preCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
|
public function preCredentialsVerification(VerifyAuthenticatorCredentialsEvent $event): void
|
||||||
{
|
{
|
||||||
if (null === $event->getUser()) {
|
if (null === $event->getUser() || $event->getAuthenticator() instanceof AbstractPreAuthenticatedAuthenticator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
<?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\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\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\RemoteUserAuthenticator;
|
||||||
|
|
||||||
|
class RemoteUserAuthenticatorTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideAuthenticators
|
||||||
|
*/
|
||||||
|
public function testSupport(RemoteUserAuthenticator $authenticator, $parameterName)
|
||||||
|
{
|
||||||
|
$request = $this->createRequest([$parameterName => 'TheUsername']);
|
||||||
|
|
||||||
|
$this->assertTrue($authenticator->supports($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportNoUser()
|
||||||
|
{
|
||||||
|
$authenticator = new RemoteUserAuthenticator($this->createMock(UserProviderInterface::class), new TokenStorage(), 'main');
|
||||||
|
|
||||||
|
$this->assertFalse($authenticator->supports($this->createRequest([])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideAuthenticators
|
||||||
|
*/
|
||||||
|
public function testGetCredentials(RemoteUserAuthenticator $authenticator, $parameterName)
|
||||||
|
{
|
||||||
|
$request = $this->createRequest([$parameterName => 'TheUsername']);
|
||||||
|
|
||||||
|
$authenticator->supports($request);
|
||||||
|
$this->assertEquals(['username' => 'TheUsername'], $authenticator->getCredentials($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideAuthenticators()
|
||||||
|
{
|
||||||
|
$userProvider = $this->createMock(UserProviderInterface::class);
|
||||||
|
|
||||||
|
yield [new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main'), 'REMOTE_USER'];
|
||||||
|
yield [new RemoteUserAuthenticator($userProvider, new TokenStorage(), 'main', 'CUSTOM_USER_PARAMETER'), 'CUSTOM_USER_PARAMETER'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createRequest(array $server)
|
||||||
|
{
|
||||||
|
return new Request([], [], [], [], [], $server);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Security\Http\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\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authenticator\X509Authenticator;
|
||||||
|
|
||||||
|
class X509AuthenticatorTest extends TestCase
|
||||||
|
{
|
||||||
|
private $userProvider;
|
||||||
|
private $authenticator;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->userProvider = $this->createMock(UserProviderInterface::class);
|
||||||
|
$this->authenticator = new X509Authenticator($this->userProvider, new TokenStorage(), 'main');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideServerVars
|
||||||
|
*/
|
||||||
|
public function testAuthentication($user, $credentials)
|
||||||
|
{
|
||||||
|
$serverVars = [];
|
||||||
|
if ('' !== $user) {
|
||||||
|
$serverVars['SSL_CLIENT_S_DN_Email'] = $user;
|
||||||
|
}
|
||||||
|
if ('' !== $credentials) {
|
||||||
|
$serverVars['SSL_CLIENT_S_DN'] = $credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $this->createRequest($serverVars);
|
||||||
|
$this->assertTrue($this->authenticator->supports($request));
|
||||||
|
$this->assertEquals(['username' => $user], $this->authenticator->getCredentials($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function provideServerVars()
|
||||||
|
{
|
||||||
|
yield ['TheUser', 'TheCredentials'];
|
||||||
|
yield ['TheUser', ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideServerVarsNoUser
|
||||||
|
*/
|
||||||
|
public function testAuthenticationNoUser($emailAddress, $credentials)
|
||||||
|
{
|
||||||
|
$request = $this->createRequest(['SSL_CLIENT_S_DN' => $credentials]);
|
||||||
|
|
||||||
|
$this->assertTrue($this->authenticator->supports($request));
|
||||||
|
$this->assertEquals(['username' => $emailAddress], $this->authenticator->getCredentials($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function provideServerVarsNoUser()
|
||||||
|
{
|
||||||
|
yield ['cert@example.com', 'CN=Sample certificate DN/emailAddress=cert@example.com'];
|
||||||
|
yield ['cert+something@example.com', 'CN=Sample certificate DN/emailAddress=cert+something@example.com'];
|
||||||
|
yield ['cert@example.com', 'CN=Sample certificate DN,emailAddress=cert@example.com'];
|
||||||
|
yield ['cert+something@example.com', 'CN=Sample certificate DN,emailAddress=cert+something@example.com'];
|
||||||
|
yield ['cert+something@example.com', 'emailAddress=cert+something@example.com,CN=Sample certificate DN'];
|
||||||
|
yield ['cert+something@example.com', 'emailAddress=cert+something@example.com'];
|
||||||
|
yield ['firstname.lastname@mycompany.co.uk', 'emailAddress=firstname.lastname@mycompany.co.uk,CN=Firstname.Lastname,OU=london,OU=company design and engineering,OU=Issuer London,OU=Roaming,OU=Interactive,OU=Users,OU=Standard,OU=Business,DC=england,DC=core,DC=company,DC=co,DC=uk'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportNoData()
|
||||||
|
{
|
||||||
|
$request = $this->createRequest([]);
|
||||||
|
|
||||||
|
$this->assertFalse($this->authenticator->supports($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAuthenticationCustomUserKey()
|
||||||
|
{
|
||||||
|
$authenticator = new X509Authenticator($this->userProvider, new TokenStorage(), 'main', 'TheUserKey');
|
||||||
|
|
||||||
|
$request = $this->createRequest([
|
||||||
|
'TheUserKey' => 'TheUser',
|
||||||
|
]);
|
||||||
|
$this->assertTrue($authenticator->supports($request));
|
||||||
|
$this->assertEquals(['username' => 'TheUser'], $authenticator->getCredentials($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAuthenticationCustomCredentialsKey()
|
||||||
|
{
|
||||||
|
$authenticator = new X509Authenticator($this->userProvider, new TokenStorage(), 'main', 'SSL_CLIENT_S_DN_Email', 'TheCertKey');
|
||||||
|
|
||||||
|
$request = $this->createRequest([
|
||||||
|
'TheCertKey' => 'CN=Sample certificate DN/emailAddress=cert@example.com',
|
||||||
|
]);
|
||||||
|
$this->assertTrue($authenticator->supports($request));
|
||||||
|
$this->assertEquals(['username' => 'cert@example.com'], $authenticator->getCredentials($request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createRequest(array $server)
|
||||||
|
{
|
||||||
|
return new Request([], [], [], [], [], $server);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user