diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php new file mode 100644 index 0000000000..63c54bddc7 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterTokenUsageTrackingPass.php @@ -0,0 +1,51 @@ + + * + * 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
+ *
+ * @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']);
+ }
+ }
+}
diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
index 2effc4554b..811c6dfc5c 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml
@@ -9,7 +9,7 @@
+ */
+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,
+ ];
+ }
+}
diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php
new file mode 100644
index 0000000000..3f353594f0
--- /dev/null
+++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php
@@ -0,0 +1,57 @@
+
+ *
+ * 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);
+ }
+}
diff --git a/src/Symfony/Component/Security/Core/composer.json b/src/Symfony/Component/Security/Core/composer.json
index 5b31a9a392..ae44882f36 100644
--- a/src/Symfony/Component/Security/Core/composer.json
+++ b/src/Symfony/Component/Security/Core/composer.json
@@ -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",
diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php
index e915f024b9..a309ab14dd 100644
--- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php
@@ -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);
diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
index e1b300e643..95b1c7d6ef 100644
--- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php
@@ -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;
+ }
}
/**
diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php
index 60c5bcb1a2..0851516497 100644
--- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php
@@ -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);
}
diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
index 3f26f0db5b..4afdd1fe08 100644
--- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
+++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php
@@ -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;
}
}
diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json
index 1da2cab878..d51c9d77e4 100644
--- a/src/Symfony/Component/Security/Http/composer.json
+++ b/src/Symfony/Component/Security/Http/composer.json
@@ -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"