+ */ +class UsageTrackingTokenStorage implements UsageTrackingTokenStorageInterface +{ + private $storage; + private $usageTracker; + + public function __construct(TokenStorageInterface $storage = null) + { + $this->storage = $storage ?? new TokenStorage(); + } + + /** + * {@inheritdoc} + */ + public function getToken(bool $trackUsage = true): ?TokenInterface + { + if ($trackUsage && $usageTracker = $this->usageTracker) { + $usageTracker(); + } + + return $this->storage->getToken(); + } + + /** + * {@inheritdoc} + */ + public function setToken(TokenInterface $token = null, \Closure $usageTracker = null): void + { + $this->usageTracker = $usageTracker; + $this->storage->setToken($token); + } +} diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorageInterface.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorageInterface.php new file mode 100644 index 0000000000000..6e9b4150f0326 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorageInterface.php @@ -0,0 +1,32 @@ + + * + * 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 Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * A token storage that optionally calls a closure when the token is accessed. + * + * @author Nicolas Grekas
+ */ +interface UsageTrackingTokenStorageInterface extends TokenStorageInterface +{ + /** + * {@inheritdoc} + */ + public function getToken(bool $trackUsage = true): ?TokenInterface; + + /** + * {@inheritdoc} + */ + public function setToken(TokenInterface $token = null, \Closure $usageTracker = null): void; +} 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 0000000000000..588245ce6e01d --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php @@ -0,0 +1,43 @@ + + * + * 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 Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; + +class UsageTrackingTokenStorageTest extends TestCase +{ + public function testGetSetToken() + { + $counter = 0; + $usageTracker = function () use (&$counter) { ++$counter; }; + + $tokenStorage = new UsageTrackingTokenStorage(); + $this->assertNull($tokenStorage->getToken()); + $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + + $tokenStorage->setToken($token); + $this->assertSame($token, $tokenStorage->getToken()); + + $tokenStorage->setToken($token, $usageTracker); + $this->assertSame($token, $tokenStorage->getToken(false)); + $this->assertSame(0, $counter); + $this->assertSame($token, $tokenStorage->getToken()); + $this->assertSame(1, $counter); + + $tokenStorage->setToken(null, $usageTracker); + $this->assertNull($tokenStorage->getToken(false)); + $this->assertSame(1, $counter); + $this->assertNull($tokenStorage->getToken()); + $this->assertSame(2, $counter); + } +} diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php index e285a249f3713..b4f4c80824a37 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php @@ -14,6 +14,8 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; 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\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; @@ -33,7 +35,7 @@ class AccessListener implements ListenerInterface public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionManagerInterface $accessDecisionManager, AccessMapInterface $map, AuthenticationManagerInterface $authManager) { - $this->tokenStorage = $tokenStorage; + $this->tokenStorage = $tokenStorage instanceof UsageTrackingTokenStorageInterface ? $tokenStorage : new UsageTrackingTokenStorage($tokenStorage); $this->accessDecisionManager = $accessDecisionManager; $this->map = $map; $this->authManager = $authManager; @@ -47,18 +49,18 @@ public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionM */ public function handle(GetResponseEvent $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/AnonymousAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php index 9615ed0b3d935..986502d306f52 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AnonymousAuthenticationListener.php @@ -16,6 +16,8 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; 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\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; /** @@ -33,7 +35,7 @@ class AnonymousAuthenticationListener implements ListenerInterface public function __construct(TokenStorageInterface $tokenStorage, string $secret, LoggerInterface $logger = null, AuthenticationManagerInterface $authenticationManager = null) { - $this->tokenStorage = $tokenStorage; + $this->tokenStorage = $tokenStorage instanceof UsageTrackingTokenStorageInterface ? $tokenStorage : new UsageTrackingTokenStorage($tokenStorage); $this->secret = $secret; $this->authenticationManager = $authenticationManager; $this->logger = $logger; @@ -44,7 +46,7 @@ public function __construct(TokenStorageInterface $tokenStorage, string $secret, */ public function handle(GetResponseEvent $event) { - if (null !== $this->tokenStorage->getToken()) { + if (null !== $this->tokenStorage->getToken(false)) { return; } diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index f25e5a4854863..ee45370d7d9fe 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -13,6 +13,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -21,6 +22,8 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; 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\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; @@ -36,6 +39,7 @@ */ class ContextListener implements ListenerInterface { + private $trackUsage; private $tokenStorage; private $sessionKey; private $logger; @@ -53,7 +57,8 @@ public function __construct(TokenStorageInterface $tokenStorage, iterable $userP throw new \InvalidArgumentException('$contextKey must not be empty.'); } - $this->tokenStorage = $tokenStorage; + $this->trackUsage = $tokenStorage instanceof UsageTrackingTokenStorageInterface; + $this->tokenStorage = $this->trackUsage ? $tokenStorage : new UsageTrackingTokenStorage($tokenStorage); $this->userProviders = $userProviders; $this->sessionKey = '_security_'.$contextKey; $this->logger = $logger; @@ -84,10 +89,18 @@ public function handle(GetResponseEvent $event) } $request = $event->getRequest(); - $session = $request->hasPreviousSession() ? $request->getSession() : null; - if (null === $session || null === $token = $session->get($this->sessionKey)) { - $this->tokenStorage->setToken(null); + if (null !== $session = $request->hasPreviousSession() ? $request->getSession() : null) { + $usageIndexValue = \method_exists($request, 'getAcceptableFormats') && $session instanceof Session ? $usageIndexReference = &$session->getUsageIndex() : 0; + $sessionId = $session->getId(); + $token = $session->get($this->sessionKey); + if ($this->trackUsage && $session->getId() === $sessionId) { + $usageIndexReference = $usageIndexValue; + } + } + + if (null === $session || null === $token) { + $this->tokenStorage->setToken(null, function () use (&$usageIndexReference) { ++$usageIndexReference; }); return; } @@ -111,7 +124,7 @@ public function handle(GetResponseEvent $event) $token = null; } - $this->tokenStorage->setToken($token); + $this->tokenStorage->setToken($token, function () use (&$usageIndexReference) { ++$usageIndexReference; }); } /** @@ -132,6 +145,8 @@ public function onKernelResponse(FilterResponseEvent $event) $this->dispatcher->removeListener(KernelEvents::RESPONSE, array($this, 'onKernelResponse')); $this->registered = false; $session = $request->getSession(); + $sessionId = $session->getId(); + $usageIndexValue = \method_exists($request, 'getAcceptableFormats') && $session instanceof Session ? $usageIndexValue = $usageIndexReference = &$session->getUsageIndex() : null; if ((null === $token = $this->tokenStorage->getToken()) || $this->trustResolver->isAnonymous($token)) { if ($request->hasPreviousSession()) { @@ -144,6 +159,9 @@ public function onKernelResponse(FilterResponseEvent $event) $this->logger->debug('Stored the security token in the session.', array('key' => $this->sessionKey)); } } + if ($this->trackUsage && $session->getId() === $sessionId) { + $usageIndexReference = $usageIndexValue; + } } /** diff --git a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php index b81a6712a5027..2d17ffc2ffba2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/RememberMeListener.php @@ -16,6 +16,8 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; 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\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; @@ -40,7 +42,7 @@ class RememberMeListener implements ListenerInterface public function __construct(TokenStorageInterface $tokenStorage, RememberMeServicesInterface $rememberMeServices, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, bool $catchExceptions = true, SessionAuthenticationStrategyInterface $sessionStrategy = null) { - $this->tokenStorage = $tokenStorage; + $this->tokenStorage = $tokenStorage instanceof UsageTrackingTokenStorageInterface ? $tokenStorage : new UsageTrackingTokenStorage($tokenStorage); $this->rememberMeServices = $rememberMeServices; $this->authenticationManager = $authenticationManager; $this->logger = $logger; @@ -54,7 +56,7 @@ public function __construct(TokenStorageInterface $tokenStorage, RememberMeServi */ public function handle(GetResponseEvent $event) { - if (null !== $this->tokenStorage->getToken()) { + if (null !== $this->tokenStorage->getToken(false)) { return; } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php index 200da819a0ad9..8d2041d8fae06 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimplePreAuthenticationListener.php @@ -22,6 +22,8 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; 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\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; @@ -53,7 +55,7 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM throw new \InvalidArgumentException('$providerKey must not be empty.'); } - $this->tokenStorage = $tokenStorage; + $this->tokenStorage = $tokenStorage instanceof UsageTrackingTokenStorageInterface ? $tokenStorage : new UsageTrackingTokenStorage($tokenStorage); $this->authenticationManager = $authenticationManager; $this->providerKey = $providerKey; $this->simpleAuthenticator = $simpleAuthenticator; @@ -83,7 +85,7 @@ public function handle(GetResponseEvent $event) $this->logger->info('Attempting SimplePreAuthentication.', array('key' => $this->providerKey, 'authenticator' => \get_class($this->simpleAuthenticator))); } - if ((null !== $token = $this->tokenStorage->getToken()) && !$this->trustResolver->isAnonymous($token)) { + if ((null !== $token = $this->tokenStorage->getToken(false)) && !$this->trustResolver->isAnonymous($token)) { return; } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index c49527bc14d66..3f94308525c60 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -183,6 +183,41 @@ public function testHandleWhenThereIsNoAccessMapEntryMatchingTheRequest() $listener->handle($event); } + public function testHandleWhenAccessMapReturnsEmptyAttributes() + { + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + + $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); + $accessMap + ->expects($this->any()) + ->method('getPatterns') + ->with($this->equalTo($request)) + ->will($this->returnValue(array(array(), null))) + ; + + $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + $tokenStorage + ->expects($this->never()) + ->method('getToken') + ; + + $listener = new AccessListener( + $tokenStorage, + $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(), + $accessMap, + $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock() + ); + + $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock(); + $event + ->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + + $listener->handle($event); + } + /** * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException */ @@ -195,14 +230,29 @@ public function testHandleWhenTheSecurityTokenStorageHasNoToken() ->will($this->returnValue(null)) ; + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->disableOriginalConstructor()->disableOriginalClone()->getMock(); + + $accessMap = $this->getMockBuilder('Symfony\Component\Security\Http\AccessMapInterface')->getMock(); + $accessMap + ->expects($this->any()) + ->method('getPatterns') + ->with($this->equalTo($request)) + ->will($this->returnValue(array(array('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('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock(); + $event + ->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; $listener->handle($event); } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php index 5bd0c80829664..eff8755300c29 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AnonymousAuthenticationListenerTest.php @@ -13,16 +13,18 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener; class AnonymousAuthenticationListenerTest extends TestCase { public function testHandleWithTokenStorageHavingAToken() { - $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + $tokenStorage = $this->getMockBuilder(UsageTrackingTokenStorageInterface::class)->getMock(); $tokenStorage ->expects($this->any()) ->method('getToken') + ->with(false) ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())) ; $tokenStorage @@ -42,10 +44,11 @@ public function testHandleWithTokenStorageHavingAToken() public function testHandleWithTokenStorageHavingNoToken() { - $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + $tokenStorage = $this->getMockBuilder(UsageTrackingTokenStorageInterface::class)->getMock(); $tokenStorage ->expects($this->any()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index f38ddfdf979ea..b30055f077923 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -23,7 +23,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; @@ -53,7 +53,7 @@ public function testItRequiresContextKey() */ public function testUserProvidersNeedToImplementAnInterface() { - $this->handleEventWithPreviousSession(new TokenStorage(), array(new \stdClass())); + $this->handleEventWithPreviousSession(array(new \stdClass())); } public function testOnKernelResponseWillAddSession() @@ -207,6 +207,7 @@ public function testHandleAddsKernelResponseListener() public function testOnKernelResponseListenerRemovesItself() { + $session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')->getMock(); $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\FilterResponseEvent') @@ -219,6 +220,9 @@ public function testOnKernelResponseListenerRemovesItself() $request->expects($this->any()) ->method('hasSession') ->will($this->returnValue(true)); + $request->expects($this->any()) + ->method('getSession') + ->will($this->returnValue($session)); $event->expects($this->any()) ->method('isMasterRequest') @@ -253,35 +257,31 @@ public function testHandleRemovesTokenIfNoPreviousSessionWasFound() public function testIfTokenIsDeauthenticated() { - $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))); + $tokenStorage = $this->handleEventWithPreviousSession(array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))); $this->assertNull($tokenStorage->getToken()); } public function testTryAllUserProvidersUntilASupportingUserProviderIsFound() { - $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser)), $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession(array(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, array(new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)), $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession(array(new SupportingUserProvider(), new SupportingUserProvider($refreshedUser)), $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProviders() { - $tokenStorage = new TokenStorage(); - $this->handleEventWithPreviousSession($tokenStorage, array(new NotSupportingUserProvider(), new SupportingUserProvider())); + $tokenStorage = $this->handleEventWithPreviousSession(array(new NotSupportingUserProvider(), new SupportingUserProvider())); $this->assertNull($tokenStorage->getToken()); } @@ -291,14 +291,13 @@ public function testTokenIsSetToNullIfNoUserWasLoadedByTheRegisteredUserProvider */ public function testRuntimeExceptionIsThrownIfNoSupportingUserProviderWasRegistered() { - $this->handleEventWithPreviousSession(new TokenStorage(), array(new NotSupportingUserProvider(), new NotSupportingUserProvider())); + $this->handleEventWithPreviousSession(array(new NotSupportingUserProvider(), new NotSupportingUserProvider())); } public function testAcceptsProvidersAsTraversable() { - $tokenStorage = new TokenStorage(); $refreshedUser = new User('foobar', 'baz'); - $this->handleEventWithPreviousSession($tokenStorage, new \ArrayObject(array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))), $refreshedUser); + $tokenStorage = $this->handleEventWithPreviousSession(new \ArrayObject(array(new NotSupportingUserProvider(), new SupportingUserProvider($refreshedUser))), $refreshedUser); $this->assertSame($refreshedUser, $tokenStorage->getToken()->getUser()); } @@ -311,13 +310,16 @@ protected function runSessionOnKernelResponse($newToken, $original = null) $session->set('_security_session', $original); } - $tokenStorage = new TokenStorage(); + $tokenStorage = new UsageTrackingTokenStorage(); $tokenStorage->setToken($newToken); $request = new Request(); $request->setSession($session); $request->cookies->set('MOCKSESSID', true); + $sessionId = $session->getId(); + $usageIndex = \method_exists($request, 'getAcceptableFormats') ? $session->getUsageIndex() : null; + $event = new FilterResponseEvent( $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), $request, @@ -328,10 +330,18 @@ protected function runSessionOnKernelResponse($newToken, $original = null) $listener = new ContextListener($tokenStorage, array(), 'session', null, new EventDispatcher()); $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()); @@ -341,8 +351,19 @@ private function handleEventWithPreviousSession(TokenStorageInterface $tokenStor $request->setSession($session); $request->cookies->set('MOCKSESSID', true); + $tokenStorage = \method_exists($request, 'getAcceptableFormats') ? new UsageTrackingTokenStorage() : new TokenStorage(); + $usageIndex = \method_exists($request, 'getAcceptableFormats') ? $session->getUsageIndex() : null; + $listener = new ContextListener($tokenStorage, $userProviders, 'context_key'); $listener->handle(new GetResponseEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->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/Tests/Firewall/RememberMeListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php index 9acb804132636..912675017a918 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/RememberMeListenerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Firewall\RememberMeListener; use Symfony\Component\Security\Http\SecurityEvents; @@ -26,6 +27,7 @@ public function testOnCoreSecurityDoesNotTryToPopulateNonEmptyTokenStorage() $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())) ; @@ -44,6 +46,7 @@ public function testOnCoreSecurityDoesNothingWhenNoCookieIsSet() $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -72,6 +75,7 @@ public function testOnCoreSecurityIgnoresAuthenticationExceptionThrownByAuthenti $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -114,6 +118,7 @@ public function testOnCoreSecurityIgnoresAuthenticationOptionallyRethrowsExcepti $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -152,6 +157,7 @@ public function testOnCoreSecurityAuthenticationExceptionDuringAutoLoginTriggers $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -189,6 +195,7 @@ public function testOnCoreSecurity() $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -228,6 +235,7 @@ public function testSessionStrategy() $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -293,6 +301,7 @@ public function testSessionIsMigratedByDefault() $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -356,6 +365,7 @@ public function testOnCoreSecurityInteractiveLoginEventIsDispatchedIfDispatcherI $tokenStorage ->expects($this->once()) ->method('getToken') + ->with(false) ->will($this->returnValue(null)) ; @@ -440,7 +450,7 @@ protected function getService() protected function getTokenStorage() { - return $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); + return $this->getMockBuilder(UsageTrackingTokenStorageInterface::class)->getMock(); } protected function getDispatcher() diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SimplePreAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SimplePreAuthenticationListenerTest.php index 0c4229856b4f5..17447767a93f1 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/SimplePreAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SimplePreAuthenticationListenerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\Firewall\SimplePreAuthenticationListener; @@ -93,6 +94,25 @@ public function testHandlecatchAuthenticationException() $listener->handle($this->event); } + public function testHandleWithTokenStorageHavingAToken() + { + $tokenStorage = $this->getMockBuilder(UsageTrackingTokenStorageInterface::class)->getMock(); + $tokenStorage + ->expects($this->any()) + ->method('getToken') + ->with(false) + ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock())) + ; + $tokenStorage + ->expects($this->never()) + ->method('setToken') + ; + $simpleAuthenticator = $this->getMockBuilder('Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface')->getMock(); + + $listener = new SimplePreAuthenticationListener($tokenStorage, $this->authenticationManager, 'secured_area', $simpleAuthenticator, $this->logger, $this->dispatcher); + $listener->handle($this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent')->disableOriginalConstructor()->getMock()); + } + protected function setUp() { $this->authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager') diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index ef81706f2a5fa..bba640270152d 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": "~3.4|~4.0", + "symfony/security-core": "~4.2", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0",