[Security] Make stateful firewalls turn responses private only when needed
This commit is contained in:
parent
ba313d3d25
commit
20df3a125c
@ -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\Bundle\SecurityBundle\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Bridge\Monolog\Processor\ProcessorInterface;
|
||||
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Injects the session tracker enabler in "security.context_listener" + binds "security.untracked_token_storage" to ProcessorInterface instances.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class RegisterTokenUsageTrackingPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->has('security.untracked_token_storage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class);
|
||||
$processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + [
|
||||
TokenStorageInterface::class => new BoundArgument(new Reference('security.untracked_token_storage'), false),
|
||||
]);
|
||||
|
||||
if (!$container->has('session')) {
|
||||
$container->setAlias('security.token_storage', 'security.untracked_token_storage')->setPublic(true);
|
||||
} elseif ($container->hasDefinition('security.context_listener')) {
|
||||
$container->getDefinition('security.context_listener')
|
||||
->setArgument(6, [new Reference('security.token_storage'), 'enableUsageTracking']);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
<service id="data_collector.security" class="Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector">
|
||||
<tag name="data_collector" template="@Security/Collector/security.html.twig" id="security" priority="270" />
|
||||
<argument type="service" id="security.token_storage" on-invalid="ignore" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument type="service" id="security.role_hierarchy" />
|
||||
<argument type="service" id="security.logout_url_generator" />
|
||||
<argument type="service" id="security.access.decision_manager" />
|
||||
|
@ -21,11 +21,18 @@
|
||||
</service>
|
||||
<service id="Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface" alias="security.authorization_checker" />
|
||||
|
||||
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" public="true">
|
||||
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage" public="true">
|
||||
<tag name="kernel.reset" method="disableUsageTracking" />
|
||||
<tag name="kernel.reset" method="setToken" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument type="service_locator">
|
||||
<argument key="session" type="service" id="session" />
|
||||
</argument>
|
||||
</service>
|
||||
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />
|
||||
|
||||
<service id="security.untracked_token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" />
|
||||
|
||||
<service id="security.helper" class="Symfony\Component\Security\Core\Security">
|
||||
<argument type="service_locator">
|
||||
<argument key="security.token_storage" type="service" id="security.token_storage" />
|
||||
@ -162,7 +169,7 @@
|
||||
<service id="security.logout_url_generator" class="Symfony\Component\Security\Http\Logout\LogoutUrlGenerator">
|
||||
<argument type="service" id="request_stack" on-invalid="null" />
|
||||
<argument type="service" id="router" on-invalid="null" />
|
||||
<argument type="service" id="security.token_storage" on-invalid="null" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
</service>
|
||||
|
||||
<!-- Provisioning -->
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<service id="security.authentication.listener.anonymous" class="Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument /> <!-- Key -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
<service id="security.context_listener" class="Symfony\Component\Security\Http\Firewall\ContextListener">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument type="collection" />
|
||||
<argument /> <!-- Provider Key -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
@ -128,7 +128,7 @@
|
||||
|
||||
<service id="security.authentication.listener.simple_preauth" class="Symfony\Component\Security\Http\Firewall\SimplePreAuthenticationListener" abstract="true">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
<argument /> <!-- Provider-shared Key -->
|
||||
<argument /> <!-- Authenticator -->
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
<service id="security.authentication.listener.rememberme" class="Symfony\Component\Security\Http\Firewall\RememberMeListener" abstract="true">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="security.untracked_token_storage" />
|
||||
<argument type="service" id="security.authentication.rememberme" />
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLang
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AnonymousFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
|
||||
@ -66,5 +67,6 @@ class SecurityBundle extends Bundle
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
|
||||
$container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass());
|
||||
$container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
|
||||
}
|
||||
}
|
||||
|
@ -136,10 +136,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
|
||||
return \count($this->getAttributeBag()->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getUsageIndex(): int
|
||||
public function &getUsageIndex(): int
|
||||
{
|
||||
return $this->usageIndex;
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
<?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\Core\Authentication\Token\Storage;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
|
||||
/**
|
||||
* A token storage that increments the session usage index when the token is accessed.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceSubscriberInterface
|
||||
{
|
||||
private $storage;
|
||||
private $sessionLocator;
|
||||
private $enableUsageTracking = false;
|
||||
|
||||
public function __construct(TokenStorageInterface $storage, ContainerInterface $sessionLocator)
|
||||
{
|
||||
$this->storage = $storage;
|
||||
$this->sessionLocator = $sessionLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getToken(): ?TokenInterface
|
||||
{
|
||||
if ($this->enableUsageTracking) {
|
||||
// increments the internal session usage index
|
||||
$this->sessionLocator->get('session')->getMetadataBag();
|
||||
}
|
||||
|
||||
return $this->storage->getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setToken(TokenInterface $token = null): void
|
||||
{
|
||||
$this->storage->setToken($token);
|
||||
}
|
||||
|
||||
public function enableUsageTracking(): void
|
||||
{
|
||||
$this->enableUsageTracking = true;
|
||||
}
|
||||
|
||||
public function disableUsageTracking(): void
|
||||
{
|
||||
$this->enableUsageTracking = false;
|
||||
}
|
||||
|
||||
public static function getSubscribedServices(): array
|
||||
{
|
||||
return [
|
||||
'session' => SessionInterface::class,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?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\Core\Tests\Authentication\Token\Storage;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Contracts\Service\ServiceLocatorTrait;
|
||||
|
||||
class UsageTrackingTokenStorageTest extends TestCase
|
||||
{
|
||||
public function testGetSetToken()
|
||||
{
|
||||
$sessionAccess = 0;
|
||||
$sessionLocator = new class(['session' => function () use (&$sessionAccess) {
|
||||
++$sessionAccess;
|
||||
|
||||
$session = $this->createMock(SessionInterface::class);
|
||||
$session->expects($this->once())
|
||||
->method('getMetadataBag');
|
||||
|
||||
return $session;
|
||||
}]) implements ContainerInterface {
|
||||
use ServiceLocatorTrait;
|
||||
};
|
||||
$tokenStorage = new TokenStorage();
|
||||
$trackingStorage = new UsageTrackingTokenStorage($tokenStorage, $sessionLocator);
|
||||
|
||||
$this->assertNull($trackingStorage->getToken());
|
||||
$token = $this->getMockBuilder(TokenInterface::class)->getMock();
|
||||
|
||||
$trackingStorage->setToken($token);
|
||||
$this->assertSame($token, $trackingStorage->getToken());
|
||||
$this->assertSame($token, $tokenStorage->getToken());
|
||||
$this->assertSame(0, $sessionAccess);
|
||||
|
||||
$trackingStorage->enableUsageTracking();
|
||||
$this->assertSame($token, $trackingStorage->getToken());
|
||||
$this->assertSame(1, $sessionAccess);
|
||||
|
||||
$trackingStorage->disableUsageTracking();
|
||||
$this->assertSame($token, $trackingStorage->getToken());
|
||||
$this->assertSame(1, $sessionAccess);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"symfony/event-dispatcher-contracts": "^1.1|^2",
|
||||
"symfony/service-contracts": "^1.1|^2"
|
||||
"symfony/service-contracts": "^1.1.6|^2"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/container": "^1.0",
|
||||
|
@ -51,18 +51,18 @@ class AccessListener implements ListenerInterface
|
||||
*/
|
||||
public function __invoke(RequestEvent $event)
|
||||
{
|
||||
if (null === $token = $this->tokenStorage->getToken()) {
|
||||
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
|
||||
}
|
||||
|
||||
$request = $event->getRequest();
|
||||
|
||||
list($attributes) = $this->map->getPatterns($request);
|
||||
|
||||
if (null === $attributes) {
|
||||
if (!$attributes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $token = $this->tokenStorage->getToken()) {
|
||||
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
|
||||
}
|
||||
|
||||
if (!$token->isAuthenticated()) {
|
||||
$token = $this->authManager->authenticate($token);
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
@ -13,6 +13,8 @@ namespace Symfony\Component\Security\Http\Firewall;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
@ -49,11 +51,12 @@ class ContextListener implements ListenerInterface
|
||||
private $dispatcher;
|
||||
private $registered;
|
||||
private $trustResolver;
|
||||
private $sessionTrackerEnabler;
|
||||
|
||||
/**
|
||||
* @param iterable|UserProviderInterface[] $userProviders
|
||||
*/
|
||||
public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null)
|
||||
public function __construct(TokenStorageInterface $tokenStorage, iterable $userProviders, string $contextKey, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, AuthenticationTrustResolverInterface $trustResolver = null, callable $sessionTrackerEnabler = null)
|
||||
{
|
||||
if (empty($contextKey)) {
|
||||
throw new \InvalidArgumentException('$contextKey must not be empty.');
|
||||
@ -65,6 +68,7 @@ class ContextListener implements ListenerInterface
|
||||
$this->logger = $logger;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->trustResolver = $trustResolver ?: new AuthenticationTrustResolver(AnonymousToken::class, RememberMeToken::class);
|
||||
$this->sessionTrackerEnabler = $sessionTrackerEnabler;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +96,21 @@ class ContextListener implements ListenerInterface
|
||||
$request = $event->getRequest();
|
||||
$session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null;
|
||||
|
||||
if (null === $session || null === $token = $session->get($this->sessionKey)) {
|
||||
if (null !== $session) {
|
||||
$usageIndexValue = method_exists(Request::class, 'getPreferredFormat') && $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : 0;
|
||||
$sessionId = $session->getId();
|
||||
$token = $session->get($this->sessionKey);
|
||||
|
||||
if ($this->sessionTrackerEnabler && $session->getId() === $sessionId) {
|
||||
$usageIndexReference = $usageIndexValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $session || null === $token) {
|
||||
if ($this->sessionTrackerEnabler) {
|
||||
($this->sessionTrackerEnabler)();
|
||||
}
|
||||
|
||||
$this->tokenStorage->setToken(null);
|
||||
|
||||
return;
|
||||
@ -117,6 +135,10 @@ class ContextListener implements ListenerInterface
|
||||
$token = null;
|
||||
}
|
||||
|
||||
if ($this->sessionTrackerEnabler) {
|
||||
($this->sessionTrackerEnabler)();
|
||||
}
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
}
|
||||
|
||||
@ -137,19 +159,26 @@ class ContextListener implements ListenerInterface
|
||||
|
||||
$this->dispatcher->removeListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
|
||||
$this->registered = false;
|
||||
$session = $request->getSession();
|
||||
$sessionId = $session->getId();
|
||||
$usageIndexValue = method_exists(Request::class, 'getPreferredFormat') && $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : null;
|
||||
$token = $this->tokenStorage->getToken();
|
||||
|
||||
if (null === $token || $this->trustResolver->isAnonymous($token)) {
|
||||
if ($request->hasPreviousSession() && $request->hasSession()) {
|
||||
$request->getSession()->remove($this->sessionKey);
|
||||
if ($request->hasPreviousSession()) {
|
||||
$session->remove($this->sessionKey);
|
||||
}
|
||||
} else {
|
||||
$request->getSession()->set($this->sessionKey, serialize($token));
|
||||
$session->set($this->sessionKey, serialize($token));
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->debug('Stored the security token in the session.', ['key' => $this->sessionKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->sessionTrackerEnabler && $session->getId() === $sessionId) {
|
||||
$usageIndexReference = $usageIndexValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,12 @@
|
||||
namespace Symfony\Component\Security\Http\Tests\Firewall;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Symfony\Component\Security\Http\AccessMapInterface;
|
||||
use Symfony\Component\Security\Http\Firewall\AccessListener;
|
||||
|
||||
class AccessListenerTest extends TestCase
|
||||
@ -182,6 +187,41 @@ class AccessListenerTest extends TestCase
|
||||
$listener($event);
|
||||
}
|
||||
|
||||
public function testHandleWhenAccessMapReturnsEmptyAttributes()
|
||||
{
|
||||
$request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
|
||||
$accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock();
|
||||
$accessMap
|
||||
->expects($this->any())
|
||||
->method('getPatterns')
|
||||
->with($this->equalTo($request))
|
||||
->willReturn([[], null])
|
||||
;
|
||||
|
||||
$tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
|
||||
$tokenStorage
|
||||
->expects($this->never())
|
||||
->method('getToken')
|
||||
;
|
||||
|
||||
$listener = new AccessListener(
|
||||
$tokenStorage,
|
||||
$this->getMockBuilder(AccessDecisionManagerInterface::class)->getMock(),
|
||||
$accessMap,
|
||||
$this->getMockBuilder(AuthenticationManagerInterface::class)->getMock()
|
||||
);
|
||||
|
||||
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock();
|
||||
$event
|
||||
->expects($this->any())
|
||||
->method('getRequest')
|
||||
->willReturn($request)
|
||||
;
|
||||
|
||||
$listener($event);
|
||||
}
|
||||
|
||||
public function testHandleWhenTheSecurityTokenStorageHasNoToken()
|
||||
{
|
||||
$this->expectException('Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException');
|
||||
@ -192,14 +232,29 @@ class AccessListenerTest extends TestCase
|
||||
->willReturn(null)
|
||||
;
|
||||
|
||||
$request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->disableOriginalClone()->getMock();
|
||||
|
||||
$accessMap = $this->getMockBuilder(AccessMapInterface::class)->getMock();
|
||||
$accessMap
|
||||
->expects($this->any())
|
||||
->method('getPatterns')
|
||||
->with($this->equalTo($request))
|
||||
->willReturn([['foo' => 'bar'], null])
|
||||
;
|
||||
|
||||
$listener = new AccessListener(
|
||||
$tokenStorage,
|
||||
$this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(),
|
||||
$this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(),
|
||||
$accessMap,
|
||||
$this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock()
|
||||
);
|
||||
|
||||
$event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock();
|
||||
$event
|
||||
->expects($this->any())
|
||||
->method('getRequest')
|
||||
->willReturn($request)
|
||||
;
|
||||
|
||||
$listener($event);
|
||||
}
|
||||
|
@ -12,11 +12,13 @@
|
||||
namespace Symfony\Component\Security\Http\Tests\Firewall;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
@ -25,6 +27,7 @@ use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
@ -33,6 +36,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Http\Event\DeauthenticatedEvent;
|
||||
use Symfony\Component\Security\Http\Firewall\ContextListener;
|
||||
use Symfony\Contracts\Service\ServiceLocatorTrait;
|
||||
|
||||
class ContextListenerTest extends TestCase
|
||||
{
|
||||
@ -51,7 +55,7 @@ class ContextListenerTest extends TestCase
|
||||
{
|
||||
$this->expectException('InvalidArgumentException');
|
||||
$this->expectExceptionMessage('User provider "stdClass" must implement "Symfony\Component\Security\Core\User\UserProviderInterface');
|
||||
$this->handleEventWithPreviousSession(new TokenStorage(), [new \stdClass()]);
|
||||
$this->handleEventWithPreviousSession([new \stdClass()]);
|
||||
}
|
||||
|
||||
public function testOnKernelResponseWillAddSession()
|
||||
@ -205,6 +209,7 @@ class ContextListenerTest extends TestCase
|
||||
|
||||
public function testOnKernelResponseListenerRemovesItself()
|
||||
{
|
||||
$session = $this->getMockBuilder(SessionInterface::class)->getMock();
|
||||
$tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock();
|
||||
$dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock();
|
||||
|
||||
@ -214,6 +219,9 @@ class ContextListenerTest extends TestCase
|
||||
$request->expects($this->any())
|
||||
->method('hasSession')
|
||||
->willReturn(true);
|
||||
$request->expects($this->any())
|
||||
->method('getSession')
|
||||
->will($this->returnValue($session));
|
||||
|
||||
$event = new ResponseEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST, new Response());
|
||||
|
||||
@ -243,9 +251,8 @@ class ContextListenerTest extends TestCase
|
||||
|
||||
public function testIfTokenIsDeauthenticated()
|
||||
{
|
||||
$tokenStorage = new TokenStorage();
|
||||
$refreshedUser = new User('foobar', 'baz');
|
||||
$this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]);
|
||||
|
||||
$this->assertNull($tokenStorage->getToken());
|
||||
}
|
||||
@ -255,32 +262,29 @@ class ContextListenerTest extends TestCase
|
||||
$tokenStorage = new TokenStorage();
|
||||
$badRefreshedUser = new User('foobar', 'baz');
|
||||
$goodRefreshedUser = new User('foobar', 'bar');
|
||||
$this->handleEventWithPreviousSession($tokenStorage, [new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser, true);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession([new SupportingUserProvider($badRefreshedUser), new SupportingUserProvider($goodRefreshedUser)], $goodRefreshedUser, true);
|
||||
$this->assertSame($goodRefreshedUser, $tokenStorage->getToken()->getUser());
|
||||
}
|
||||
|
||||
public function testTryAllUserProvidersUntilASupportingUserProviderIsFound()
|
||||
{
|
||||
$tokenStorage = new TokenStorage();
|
||||
$refreshedUser = new User('foobar', 'baz');
|
||||
$this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser);
|
||||
|
||||
$this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
|
||||
}
|
||||
|
||||
public function testNextSupportingUserProviderIsTriedIfPreviousSupportingUserProviderDidNotLoadTheUser()
|
||||
{
|
||||
$tokenStorage = new TokenStorage();
|
||||
$refreshedUser = new User('foobar', 'baz');
|
||||
$this->handleEventWithPreviousSession($tokenStorage, [new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession([new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)], $refreshedUser);
|
||||
|
||||
$this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
|
||||
}
|
||||
|
||||
public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders()
|
||||
{
|
||||
$tokenStorage = new TokenStorage();
|
||||
$this->handleEventWithPreviousSession($tokenStorage, [new NotSupportingUserProvider(), new SupportingUserProvider()]);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new SupportingUserProvider()]);
|
||||
|
||||
$this->assertNull($tokenStorage->getToken());
|
||||
}
|
||||
@ -288,14 +292,13 @@ class ContextListenerTest extends TestCase
|
||||
public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered()
|
||||
{
|
||||
$this->expectException('RuntimeException');
|
||||
$this->handleEventWithPreviousSession(new TokenStorage(), [new NotSupportingUserProvider(), new NotSupportingUserProvider()]);
|
||||
$this->handleEventWithPreviousSession([new NotSupportingUserProvider(), new NotSupportingUserProvider()]);
|
||||
}
|
||||
|
||||
public function testAcceptsProvidersAsTraversable()
|
||||
{
|
||||
$tokenStorage = new TokenStorage();
|
||||
$refreshedUser = new User('foobar', 'baz');
|
||||
$this->handleEventWithPreviousSession($tokenStorage, new \ArrayObject([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]), $refreshedUser);
|
||||
$tokenStorage = $this->handleEventWithPreviousSession(new \ArrayObject([new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)]), $refreshedUser);
|
||||
|
||||
$this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser());
|
||||
}
|
||||
@ -335,13 +338,21 @@ class ContextListenerTest extends TestCase
|
||||
$session->set('_security_session', $original);
|
||||
}
|
||||
|
||||
$tokenStorage = new TokenStorage();
|
||||
$tokenStorage = new UsageTrackingTokenStorage(new TokenStorage(), new class([
|
||||
'session' => function () use ($session) { return $session; }
|
||||
]) implements ContainerInterface {
|
||||
use ServiceLocatorTrait;
|
||||
});
|
||||
|
||||
$tokenStorage->setToken($newToken);
|
||||
|
||||
$request = new Request();
|
||||
$request->setSession($session);
|
||||
$request->cookies->set('MOCKSESSID', true);
|
||||
|
||||
$sessionId = $session->getId();
|
||||
$usageIndex = \method_exists(Request::class, 'getPreferredFormat') ? $session->getUsageIndex() : null;
|
||||
|
||||
$event = new ResponseEvent(
|
||||
$this->getMockBuilder(HttpKernelInterface::class)->getMock(),
|
||||
$request,
|
||||
@ -349,13 +360,21 @@ class ContextListenerTest extends TestCase
|
||||
new Response()
|
||||
);
|
||||
|
||||
$listener = new ContextListener($tokenStorage, [], 'session', null, new EventDispatcher());
|
||||
$listener = new ContextListener($tokenStorage, [], 'session', null, new EventDispatcher(), null, [$tokenStorage, 'enableUsageTracking']);
|
||||
$listener->onKernelResponse($event);
|
||||
|
||||
if (null !== $usageIndex) {
|
||||
if ($session->getId() === $sessionId) {
|
||||
$this->assertSame($usageIndex, $session->getUsageIndex());
|
||||
} else {
|
||||
$this->assertNotSame($usageIndex, $session->getUsageIndex());
|
||||
}
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
private function handleEventWithPreviousSession(TokenStorageInterface $tokenStorage, $userProviders, UserInterface $user = null)
|
||||
private function handleEventWithPreviousSession($userProviders, UserInterface $user = null)
|
||||
{
|
||||
$user = $user ?: new User('foo', 'bar');
|
||||
$session = new Session(new MockArraySessionStorage());
|
||||
@ -365,8 +384,30 @@ class ContextListenerTest extends TestCase
|
||||
$request->setSession($session);
|
||||
$request->cookies->set('MOCKSESSID', true);
|
||||
|
||||
$listener = new ContextListener($tokenStorage, $userProviders, 'context_key');
|
||||
$tokenStorage = new TokenStorage();
|
||||
$usageIndex = null;
|
||||
$sessionTrackerEnabler = null;
|
||||
|
||||
if (\method_exists(Request::class, 'getPreferredFormat')) {
|
||||
$usageIndex = $session->getUsageIndex();
|
||||
$tokenStorage = new UsageTrackingTokenStorage($tokenStorage, new class([
|
||||
'session' => function () use ($session) { return $session; }
|
||||
]) implements ContainerInterface {
|
||||
use ServiceLocatorTrait;
|
||||
});
|
||||
$sessionTrackerEnabler = [$tokenStorage, 'enableUsageTracking'];
|
||||
}
|
||||
|
||||
$listener = new ContextListener($tokenStorage, $userProviders, 'context_key', null, null, null, $sessionTrackerEnabler);
|
||||
$listener(new RequestEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST));
|
||||
|
||||
if (null !== $usageIndex) {
|
||||
$this->assertSame($usageIndex, $session->getUsageIndex());
|
||||
$tokenStorage->getToken();
|
||||
$this->assertSame(1 + $usageIndex, $session->getUsageIndex());
|
||||
}
|
||||
|
||||
return $tokenStorage;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"symfony/security-core": "^4.3",
|
||||
"symfony/security-core": "^4.4",
|
||||
"symfony/http-foundation": "^3.4|^4.0|^5.0",
|
||||
"symfony/http-kernel": "^4.3",
|
||||
"symfony/property-access": "^3.4|^4.0|^5.0"
|
||||
|
Reference in New Issue
Block a user