diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 928a505ed5..7c7db43bf7 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -102,6 +102,9 @@ Routing Security -------- + * Deprecate using `UsageTrackingTokenStorage` with tracking enabled without a main request. Use the untracked token + storage (service ID: `security.untracked_token_storage`) instead, or disable usage tracking + completely using `UsageTrackingTokenStorage::disableUsageTracking()`. * [BC BREAK] Remove method `checkIfCompletelyResolved()` from `PassportInterface`, checking that passport badges are resolved is up to `AuthenticatorManager` * Deprecate class `User`, use `InMemoryUser` or your own implementation instead. diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index e8c3d88388..a8a15a642e 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -24,6 +24,7 @@ CHANGELOG * Add `LegacyPasswordAuthenticatedUserInterface` for user classes that use user-provided salts in addition to passwords * Deprecate all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead * Deprecate the `SessionInterface $session` constructor argument of `SessionTokenStorage`, inject a `\Symfony\Component\HttpFoundation\RequestStack $requestStack` instead + * Deprecate using `UsageTrackingTokenStorage` without a main request * Deprecate the `session` service provided by the ServiceLocator injected in `UsageTrackingTokenStorage`, provide a `request_stack` service instead * Deprecate using `SessionTokenStorage` outside a request context, it will throw a `SessionNotFoundException` in Symfony 6.0 * Randomize CSRF tokens to harden BREACH attacks diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php index 0b8d9c3201..4b2cac747a 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/UsageTrackingTokenStorage.php @@ -39,7 +39,7 @@ final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceS */ public function getToken(): ?TokenInterface { - if ($this->enableUsageTracking) { + if ($this->shouldTrackUsage()) { // increments the internal session usage index $this->getSession()->getMetadataBag(); } @@ -54,7 +54,7 @@ final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceS { $this->storage->setToken($token); - if ($token && $this->enableUsageTracking) { + if ($token && $this->shouldTrackUsage()) { // increments the internal session usage index $this->getSession()->getMetadataBag(); } @@ -88,4 +88,24 @@ final class UsageTrackingTokenStorage implements TokenStorageInterface, ServiceS return $this->container->get('request_stack')->getSession(); } + + private function shouldTrackUsage(): bool + { + if (!$this->enableUsageTracking) { + return false; + } + + // BC for symfony/security-bundle < 5.3 + if ($this->container->has('session')) { + return true; + } + + if (!$this->container->get('request_stack')->getMainRequest()) { + trigger_deprecation('symfony/security-core', '5.3', 'Using "%s" (service ID: "security.token_storage") outside the request-response cycle is deprecated, use the "%s" class (service ID: "security.untracked_token_storage") instead or disable usage tracking using "disableUsageTracking()".', __CLASS__, TokenStorage::class); + + return false; + } + + return true; + } } 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 index 38806efa8a..0d074bd4b0 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php @@ -13,31 +13,37 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Token\Storage; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Authentication\Token\NullToken; 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 { + use ExpectDeprecationTrait; + public function testGetSetToken() { $sessionAccess = 0; $sessionLocator = new class(['request_stack' => function () use (&$sessionAccess) { - ++$sessionAccess; - $session = $this->createMock(SessionInterface::class); - $session->expects($this->once()) - ->method('getMetadataBag'); $request = new Request(); $request->setSession($session); - $requestStack = new RequestStack(); + $requestStack = $this->getMockBuilder(RequestStack::class)->setMethods(['getSession'])->getMock(); $requestStack->push($request); + $requestStack->expects($this->any())->method('getSession')->willReturnCallback(function () use ($session, &$sessionAccess) { + ++$sessionAccess; + + $session->expects($this->once()) + ->method('getMetadataBag'); + + return $session; + }); return $requestStack; }]) implements ContainerInterface { @@ -62,4 +68,22 @@ class UsageTrackingTokenStorageTest extends TestCase $this->assertSame($token, $trackingStorage->getToken()); $this->assertSame(1, $sessionAccess); } + + /** + * @group legacy + */ + public function testWithoutMainRequest() + { + $locator = new class(['request_stack' => function () { + return new RequestStack(); + }]) implements ContainerInterface { + use ServiceLocatorTrait; + }; + $tokenStorage = new TokenStorage(); + $trackingStorage = new UsageTrackingTokenStorage($tokenStorage, $locator); + $trackingStorage->enableUsageTracking(); + + $this->expectDeprecation('Since symfony/security-core 5.3: Using "%s" (service ID: "security.token_storage") outside the request-response cycle is deprecated, use the "%s" class (service ID: "security.untracked_token_storage") instead or disable usage tracking using "disableUsageTracking()".'); + $trackingStorage->getToken(); + } }