[Security] Make stateful firewalls turn responses private only when needed

This commit is contained in:
Nicolas Grekas 2018-07-30 18:06:05 +02:00
parent ba313d3d25
commit 20df3a125c
15 changed files with 353 additions and 41 deletions

View File

@ -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']);
}
}
}

View File

@ -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" />

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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" />

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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,
];
}
}

View File

@ -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);
}
}

View File

@ -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",

View File

@ -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);

View File

@ -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;
}
}
/**

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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"