From 7b91bcb068248ef89807df5d9db56939b7214098 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 18 Apr 2022 00:26:17 +0200 Subject: [PATCH] [Security] Move the `Security` helper to SecurityBundle --- .../FrameworkExtension.php | 4 +- .../FrameworkExtensionTest.php | 4 +- .../Bundle/SecurityBundle/CHANGELOG.md | 5 ++ .../Resources/config/security.php | 2 +- .../SecurityBundle/Security/Security.php | 28 ++++++ .../LoginFormAuthenticator.php | 4 +- .../Form/UserLoginType.php | 10 +-- .../Controller/LocalizedController.php | 10 +-- .../Controller/LoginController.php | 10 +-- .../Tests/Security/SecurityTest.php | 89 +++++++++++++++++++ .../Bundle/SecurityBundle/composer.json | 2 +- .../Component/Security/Core/CHANGELOG.md | 5 ++ .../Component/Security/Core/Security.php | 32 ++++++- .../Security/Core/Tests/SecurityTest.php | 3 + .../Authentication/AuthenticationUtils.php | 18 ++-- .../DefaultAuthenticationFailureHandler.php | 6 +- .../AbstractLoginFormAuthenticator.php | 4 +- .../Authenticator/AuthenticatorInterface.php | 2 + .../Authenticator/FormLoginAuthenticator.php | 6 +- .../Authenticator/JsonLoginAuthenticator.php | 3 +- .../EventListener/LoginThrottlingListener.php | 4 +- .../Http/Firewall/ExceptionListener.php | 4 +- .../Component/Security/Http/HttpUtils.php | 13 ++- .../RateLimiter/DefaultLoginRateLimiter.php | 4 +- .../Http/SecurityRequestAttributes.php | 24 +++++ ...efaultAuthenticationFailureHandlerTest.php | 8 +- .../FormLoginAuthenticatorTest.php | 6 +- .../JsonLoginAuthenticatorTest.php | 4 +- .../Security/Http/Tests/HttpUtilsTest.php | 14 +-- 29 files changed, 255 insertions(+), 73 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/Security/Security.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php create mode 100644 src/Symfony/Component/Security/Http/SecurityRequestAttributes.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3770d337850a4..bcaf3ccc32624 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -186,8 +186,8 @@ use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface; use Symfony\Component\Semaphore\Semaphore; @@ -1020,7 +1020,7 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } - if (!class_exists(Security::class)) { + if (!class_exists(AuthenticationEvents::class)) { throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 51cb4346eb2d2..7dcaeef3dfcb4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -57,7 +57,7 @@ use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Symfony\Component\Messenger\Transport\TransportFactory; use Symfony\Component\PropertyAccess\PropertyAccessor; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\AuthenticationEvents; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader; use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader; @@ -1036,7 +1036,7 @@ public function testTranslator() $files, '->registerTranslatorConfiguration() finds Form translation resources' ); - $ref = new \ReflectionClass(Security::class); + $ref = new \ReflectionClass(AuthenticationEvents::class); $this->assertContains( strtr(\dirname($ref->getFileName()).'/Resources/translations/security.en.xlf', '/', \DIRECTORY_SEPARATOR), $files, diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 35631064bacf3..d7db94f32d4a8 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Add the `Security` helper class + 6.1 --- diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php index e655520b0e745..2202f2fdd339f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.php @@ -17,6 +17,7 @@ use Symfony\Bundle\SecurityBundle\Security\FirewallContext; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext; +use Symfony\Bundle\SecurityBundle\Security\Security; use Symfony\Component\Ldap\Security\LdapUserProvider; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; @@ -33,7 +34,6 @@ use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\InMemoryUserChecker; use Symfony\Component\Security\Core\User\InMemoryUserProvider; diff --git a/src/Symfony/Bundle/SecurityBundle/Security/Security.php b/src/Symfony/Bundle/SecurityBundle/Security/Security.php new file mode 100644 index 0000000000000..9ff5bf7f85156 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Security/Security.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Security; + +use Psr\Container\ContainerInterface; +use Symfony\Component\Security\Core\Security as LegacySecurity; + +/** + * Helper class for commonly-needed security tasks. + * + * @final + */ +class Security extends LegacySecurity +{ + public function __construct(ContainerInterface $container) + { + parent::__construct($container, false); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php index 1004ee2c10ba7..963be4141d27e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/LoginFormAuthenticator.php @@ -16,11 +16,11 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Component\Security\Http\Util\TargetPathTrait; class LoginFormAuthenticator extends AbstractLoginFormAuthenticator @@ -39,7 +39,7 @@ public function authenticate(Request $request): Passport { $username = $request->request->get('_username', ''); - $request->getSession()->set(Security::LAST_USERNAME, $username); + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $username); return new Passport( new UserBadge($username), diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php index 63b27512d5d27..8ccd51e72d0f0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Form/UserLoginType.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\FormEvents; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * Form type for use with the Security component's form-based authentication @@ -55,10 +55,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) * session for an authentication error and last username. */ $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($request) { - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } if ($error) { @@ -66,7 +66,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) } $event->setData(array_replace((array) $event->getData(), [ - 'username' => $request->getSession()->get(Security::LAST_USERNAME), + 'username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), ])); }); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php index 11d00e257e98a..9733b0788aac6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LocalizedController.php @@ -14,7 +14,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -30,15 +30,15 @@ public function __construct(ContainerInterface $container) public function loginAction(Request $request) { // get the login error if there is one - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } return new Response($this->container->get('twig')->render('@FormLogin/Localized/login.html.twig', [ // last username entered by the user - 'last_username' => $request->getSession()->get(Security::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), 'error' => $error, ])); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php index db6aacca8cfc2..7d90b2a671e86 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Controller/LoginController.php @@ -15,8 +15,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; @@ -32,15 +32,15 @@ public function __construct(ContainerInterface $container) public function loginAction(Request $request, UserInterface $user = null) { // get the login error if there is one - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $error = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } else { - $error = $request->getSession()->get(Security::AUTHENTICATION_ERROR); + $error = $request->getSession()->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); } return new Response($this->container->get('twig')->render('@FormLogin/Login/login.html.twig', [ // last username entered by the user - 'last_username' => $request->getSession()->get(Security::LAST_USERNAME), + 'last_username' => $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME), 'error' => $error, ])); } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php new file mode 100644 index 0000000000000..8eeff16181e6e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Security/SecurityTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\Tests\Security; + +use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\Security; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\User\InMemoryUser; + +class SecurityTest extends TestCase +{ + public function testGetToken() + { + $token = new UsernamePasswordToken(new InMemoryUser('foo', 'bar'), 'provider'); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($token, $security->getToken()); + } + + /** + * @dataProvider getUserTests + */ + public function testGetUser($userInToken, $expectedUser) + { + $token = $this->createMock(TokenInterface::class); + $token->expects($this->any()) + ->method('getUser') + ->willReturn($userInToken); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $container = $this->createContainer('security.token_storage', $tokenStorage); + + $security = new Security($container); + $this->assertSame($expectedUser, $security->getUser()); + } + + public function getUserTests() + { + yield [null, null]; + + $user = new InMemoryUser('nice_user', 'foo'); + yield [$user, $user]; + } + + public function testIsGranted() + { + $authorizationChecker = $this->createMock(AuthorizationCheckerInterface::class); + + $authorizationChecker->expects($this->once()) + ->method('isGranted') + ->with('SOME_ATTRIBUTE', 'SOME_SUBJECT') + ->willReturn(true); + + $container = $this->createContainer('security.authorization_checker', $authorizationChecker); + + $security = new Security($container); + $this->assertTrue($security->isGranted('SOME_ATTRIBUTE', 'SOME_SUBJECT')); + } + + private function createContainer(string $serviceId, object $serviceObject): ContainerInterface + { + return new ServiceLocator([$serviceId => fn () => $serviceObject]); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 9c3db8d752dc6..a73251091a5e7 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -27,7 +27,7 @@ "symfony/password-hasher": "^5.4|^6.0", "symfony/security-core": "^5.4|^6.0", "symfony/security-csrf": "^5.4|^6.0", - "symfony/security-http": "^5.4|^6.0" + "symfony/security-http": "^6.2" }, "require-dev": { "doctrine/annotations": "^1.10.4", diff --git a/src/Symfony/Component/Security/Core/CHANGELOG.md b/src/Symfony/Component/Security/Core/CHANGELOG.md index c30f0fca4b4c4..92f4cccaecb07 100644 --- a/src/Symfony/Component/Security/Core/CHANGELOG.md +++ b/src/Symfony/Component/Security/Core/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + +* Deprecate the `Security` class, use `Symfony\Bundle\SecurityBundle\Security\Security` instead + 6.0 --- diff --git a/src/Symfony/Component/Security/Core/Security.php b/src/Symfony/Component/Security/Core/Security.php index 3c7c9473877c1..d50e37b5062e5 100644 --- a/src/Symfony/Component/Security/Core/Security.php +++ b/src/Symfony/Component/Security/Core/Security.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core; use Psr\Container\ContainerInterface; +use Symfony\Bundle\SecurityBundle\Security\Security as NewSecurityHelper; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -19,20 +20,47 @@ /** * Helper class for commonly-needed security tasks. * - * @final + * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security\Security instead */ class Security implements AuthorizationCheckerInterface { + /** + * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security\Security::ACCESS_DENIED_ERROR instead + * + * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes:ACCESS_DENIED_ERROR. + */ public const ACCESS_DENIED_ERROR = '_security.403_error'; + + /** + * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security\Security::AUTHENTICATION_ERROR instead + * + * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes:AUTHENTICATION_ERROR. + */ public const AUTHENTICATION_ERROR = '_security.last_error'; + + /** + * @deprecated since Symfony 6.2, use \Symfony\Bundle\SecurityBundle\Security\Security::LAST_USERNAME instead + * + * In 7.0, move this constant to the NewSecurityHelper class and make it reference SecurityRequestAttributes:LAST_USERNAME. + */ public const LAST_USERNAME = '_security.last_username'; + + /** + * @deprecated since Symfony 6.2, use \Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface::MAX_USERNAME_LENGTH instead + * + * In 7.0, move this constant to the NewSecurityHelper class and make it reference AuthenticatorInterface:MAX_USERNAME_LENGTH. + */ public const MAX_USERNAME_LENGTH = 4096; private ContainerInterface $container; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, bool $triggerDeprecation = true) { $this->container = $container; + + if ($triggerDeprecation) { + trigger_deprecation('symfony/security-core', '6.2', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, NewSecurityHelper::class); + } } public function getUser(): ?UserInterface diff --git a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php index 064998f97293e..63eca289cc287 100644 --- a/src/Symfony/Component/Security/Core/Tests/SecurityTest.php +++ b/src/Symfony/Component/Security/Core/Tests/SecurityTest.php @@ -20,6 +20,9 @@ use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\InMemoryUser; +/** + * @group legacy + */ class SecurityTest extends TestCase { public function testGetToken() diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php index 69b07d0efed83..78bea03be0008 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticationUtils.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * Extracts Security Errors from Request. @@ -35,13 +35,13 @@ public function getLastAuthenticationError(bool $clearSession = true): ?Authenti $request = $this->getRequest(); $authenticationException = null; - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $authenticationException = $request->attributes->get(Security::AUTHENTICATION_ERROR); - } elseif ($request->hasSession() && ($session = $request->getSession())->has(Security::AUTHENTICATION_ERROR)) { - $authenticationException = $session->get(Security::AUTHENTICATION_ERROR); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $authenticationException = $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); + } elseif ($request->hasSession() && ($session = $request->getSession())->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $authenticationException = $session->get(SecurityRequestAttributes::AUTHENTICATION_ERROR); if ($clearSession) { - $session->remove(Security::AUTHENTICATION_ERROR); + $session->remove(SecurityRequestAttributes::AUTHENTICATION_ERROR); } } @@ -52,11 +52,11 @@ public function getLastUsername(): string { $request = $this->getRequest(); - if ($request->attributes->has(Security::LAST_USERNAME)) { - return $request->attributes->get(Security::LAST_USERNAME, ''); + if ($request->attributes->has(SecurityRequestAttributes::LAST_USERNAME)) { + return $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME, ''); } - return $request->hasSession() ? $request->getSession()->get(Security::LAST_USERNAME, '') : ''; + return $request->hasSession() ? $request->getSession()->get(SecurityRequestAttributes::LAST_USERNAME, '') : ''; } /** diff --git a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php index 90254455bfdb2..804cc4f52749c 100644 --- a/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php +++ b/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationFailureHandler.php @@ -16,9 +16,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * Class with the default authentication failure handling logic. @@ -84,14 +84,14 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio $this->logger?->debug('Authentication failure, forward triggered.', ['failure_path' => $options['failure_path']]); $subRequest = $this->httpUtils->createRequest($request, $options['failure_path']); - $subRequest->attributes->set(Security::AUTHENTICATION_ERROR, $exception); + $subRequest->attributes->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); return $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); } $this->logger?->debug('Authentication failure, redirect triggered.', ['failure_path' => $options['failure_path']]); - $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); + $request->getSession()->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); return $this->httpUtils->createRedirectResponse($request, $options['failure_path']); } diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php index 25413b73cbc0f..18670f759a67c 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php @@ -15,8 +15,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * A base class to make form login authentication easier! @@ -50,7 +50,7 @@ public function supports(Request $request): bool public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response { if ($request->hasSession()) { - $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception); + $request->getSession()->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $exception); } $url = $this->getLoginUrl($request); diff --git a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php index 124e0bf94885d..6db05533746f0 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php @@ -26,6 +26,8 @@ */ interface AuthenticatorInterface { + public const MAX_USERNAME_LENGTH = 4096; + /** * Does the authenticator support the given Request? * diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php index 9af51b3e6dda7..d2de5f1f23038 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php @@ -19,7 +19,6 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; @@ -32,6 +31,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\ParameterBagUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * @author Wouter de Jong @@ -132,11 +132,11 @@ private function getCredentials(Request $request): array $credentials['username'] = trim($credentials['username']); - if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) { + if (\strlen($credentials['username']) > self::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } - $request->getSession()->set(Security::LAST_USERNAME, $credentials['username']); + $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $credentials['username']); return $credentials; } diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index 97f38a69e7b99..b2dc98c24d29d 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -22,7 +22,6 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; @@ -151,7 +150,7 @@ private function getCredentials(Request $request) throw new BadRequestHttpException(sprintf('The key "%s" must be a string.', $this->options['username_path'])); } - if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) { + if (\strlen($credentials['username']) > self::MAX_USERNAME_LENGTH) { throw new BadCredentialsException('Invalid username.'); } } catch (AccessException $e) { diff --git a/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php b/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php index 920137174e556..ec9bfe2758779 100644 --- a/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/LoginThrottlingListener.php @@ -15,10 +15,10 @@ use Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Security\Core\Exception\TooManyLoginAttemptsAuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Component\Security\Http\Event\LoginSuccessEvent; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * @author Wouter de Jong @@ -42,7 +42,7 @@ public function checkPassport(CheckPassportEvent $event): void } $request = $this->requestStack->getMainRequest(); - $request->attributes->set(Security::LAST_USERNAME, $passport->getBadge(UserBadge::class)->getUserIdentifier()); + $request->attributes->set(SecurityRequestAttributes::LAST_USERNAME, $passport->getBadge(UserBadge::class)->getUserIdentifier()); $limit = $this->limiter->consume($request); if (!$limit->isAccepted()) { diff --git a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php index 14c1e2ac887f5..936fa6d49bde2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php @@ -28,11 +28,11 @@ use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException; use Symfony\Component\Security\Core\Exception\LazyResponseException; use Symfony\Component\Security\Core\Exception\LogoutException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface; use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\EntryPoint\Exception\NotAnEntryPointException; use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; use Symfony\Component\Security\Http\Util\TargetPathTrait; /** @@ -164,7 +164,7 @@ private function handleAccessDeniedException(ExceptionEvent $event, AccessDenied } } elseif (null !== $this->errorPage) { $subRequest = $this->httpUtils->createRequest($event->getRequest(), $this->errorPage); - $subRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $exception); + $subRequest->attributes->set(SecurityRequestAttributes::ACCESS_DENIED_ERROR, $exception); $event->setResponse($event->getKernel()->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true)); $event->allowCustomResponseCode(); diff --git a/src/Symfony/Component/Security/Http/HttpUtils.php b/src/Symfony/Component/Security/Http/HttpUtils.php index b6d28e10fc90f..7b57442d913c9 100644 --- a/src/Symfony/Component/Security/Http/HttpUtils.php +++ b/src/Symfony/Component/Security/Http/HttpUtils.php @@ -18,7 +18,6 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; -use Symfony\Component\Security\Core\Security; /** * Encapsulates the logic needed to create sub-requests, redirect the user, and match URLs. @@ -80,14 +79,14 @@ public function createRequest(Request $request, string $path): Request } $setSession($newRequest, $request); - if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) { - $newRequest->attributes->set(Security::AUTHENTICATION_ERROR, $request->attributes->get(Security::AUTHENTICATION_ERROR)); + if ($request->attributes->has(SecurityRequestAttributes::AUTHENTICATION_ERROR)) { + $newRequest->attributes->set(SecurityRequestAttributes::AUTHENTICATION_ERROR, $request->attributes->get(SecurityRequestAttributes::AUTHENTICATION_ERROR)); } - if ($request->attributes->has(Security::ACCESS_DENIED_ERROR)) { - $newRequest->attributes->set(Security::ACCESS_DENIED_ERROR, $request->attributes->get(Security::ACCESS_DENIED_ERROR)); + if ($request->attributes->has(SecurityRequestAttributes::ACCESS_DENIED_ERROR)) { + $newRequest->attributes->set(SecurityRequestAttributes::ACCESS_DENIED_ERROR, $request->attributes->get(SecurityRequestAttributes::ACCESS_DENIED_ERROR)); } - if ($request->attributes->has(Security::LAST_USERNAME)) { - $newRequest->attributes->set(Security::LAST_USERNAME, $request->attributes->get(Security::LAST_USERNAME)); + if ($request->attributes->has(SecurityRequestAttributes::LAST_USERNAME)) { + $newRequest->attributes->set(SecurityRequestAttributes::LAST_USERNAME, $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME)); } if ($request->get('_format')) { diff --git a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php index 465b39a5b52af..2db7ee144b98a 100644 --- a/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php +++ b/src/Symfony/Component/Security/Http/RateLimiter/DefaultLoginRateLimiter.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\RateLimiter\AbstractRequestRateLimiter; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\RateLimiter\RateLimiterFactory; -use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Http\SecurityRequestAttributes; /** * A default login throttling limiter. @@ -37,7 +37,7 @@ public function __construct(RateLimiterFactory $globalFactory, RateLimiterFactor protected function getLimiters(Request $request): array { - $username = $request->attributes->get(Security::LAST_USERNAME, ''); + $username = $request->attributes->get(SecurityRequestAttributes::LAST_USERNAME, ''); $username = preg_match('//u', $username) ? mb_strtolower($username, 'UTF-8') : strtolower($username); return [ diff --git a/src/Symfony/Component/Security/Http/SecurityRequestAttributes.php b/src/Symfony/Component/Security/Http/SecurityRequestAttributes.php new file mode 100644 index 0000000000000..1c71ea072eeb4 --- /dev/null +++ b/src/Symfony/Component/Security/Http/SecurityRequestAttributes.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http; + +/** + * List of request attributes used along the security flow. + * + * @author Robin Chalas + */ +final class SecurityRequestAttributes +{ + public const ACCESS_DENIED_ERROR = '_security.403_error'; + public const AUTHENTICATION_ERROR = '_security.last_error'; + public const LAST_USERNAME = '_security.last_username'; +} diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationFailureHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationFailureHandlerTest.php index 4241fbac7af30..aa41fb726c142 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationFailureHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authentication/DefaultAuthenticationFailureHandlerTest.php @@ -20,9 +20,9 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; class DefaultAuthenticationFailureHandlerTest extends TestCase { @@ -56,7 +56,7 @@ public function testForward() $subRequest = $this->getRequest(); $subRequest->attributes->expects($this->once()) - ->method('set')->with(Security::AUTHENTICATION_ERROR, $this->exception); + ->method('set')->with(SecurityRequestAttributes::AUTHENTICATION_ERROR, $this->exception); $this->httpUtils->expects($this->once()) ->method('createRequest')->with($this->request, '/login') ->willReturn($subRequest); @@ -83,7 +83,7 @@ public function testRedirect() public function testExceptionIsPersistedInSession() { $this->session->expects($this->once()) - ->method('set')->with(Security::AUTHENTICATION_ERROR, $this->exception); + ->method('set')->with(SecurityRequestAttributes::AUTHENTICATION_ERROR, $this->exception); $handler = new DefaultAuthenticationFailureHandler($this->httpKernel, $this->httpUtils, [], $this->logger); $handler->onAuthenticationFailure($this->request, $this->exception); @@ -95,7 +95,7 @@ public function testExceptionIsPassedInRequestOnForward() $subRequest = $this->getRequest(); $subRequest->attributes->expects($this->once()) - ->method('set')->with(Security::AUTHENTICATION_ERROR, $this->exception); + ->method('set')->with(SecurityRequestAttributes::AUTHENTICATION_ERROR, $this->exception); $this->httpUtils->expects($this->once()) ->method('createRequest')->with($this->request, '/login') diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php index 3536b103a3041..b6c343cf1c25a 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/FormLoginAuthenticatorTest.php @@ -16,10 +16,10 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\FormLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; @@ -62,8 +62,8 @@ public function testHandleWhenUsernameLength($username, $ok) public function provideUsernamesForLength() { - yield [str_repeat('x', Security::MAX_USERNAME_LENGTH + 1), false]; - yield [str_repeat('x', Security::MAX_USERNAME_LENGTH - 1), true]; + yield [str_repeat('x', AuthenticatorInterface::MAX_USERNAME_LENGTH + 1), false]; + yield [str_repeat('x', AuthenticatorInterface::MAX_USERNAME_LENGTH - 1), true]; } /** diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php index 47e02689ead93..87a0ff375f021 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php @@ -16,8 +16,8 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\User\InMemoryUserProvider; +use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\HttpUtils; @@ -121,7 +121,7 @@ public function provideInvalidAuthenticateData() $request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], '{"username": "dunglas", "password": 1}'); yield [$request, 'The key "password" must be a string.']; - $username = str_repeat('x', Security::MAX_USERNAME_LENGTH + 1); + $username = str_repeat('x', AuthenticatorInterface::MAX_USERNAME_LENGTH + 1); $request = new Request([], [], [], [], [], ['HTTP_CONTENT_TYPE' => 'application/json'], sprintf('{"username": "%s", "password": 1}', $username)); yield [$request, 'Invalid username.', BadCredentialsException::class]; } diff --git a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php index e4fa637ddb15c..f47e27ba5f723 100644 --- a/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php +++ b/src/Symfony/Component/Security/Http/Tests/HttpUtilsTest.php @@ -20,8 +20,8 @@ use Symfony\Component\Routing\Matcher\RequestMatcherInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Routing\RequestContext; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\SecurityRequestAttributes; class HttpUtilsTest extends TestCase { @@ -162,9 +162,9 @@ public function testCreateRequestPassesSessionToTheNewRequest() } /** - * @dataProvider provideSecurityContextAttributes + * @dataProvider provideSecurityRequestAttributes */ - public function testCreateRequestPassesSecurityContextAttributesToTheNewRequest($attribute) + public function testCreateRequestPassesSecurityRequestAttributesToTheNewRequest($attribute) { $request = $this->getRequest(); $request->attributes->set($attribute, 'foo'); @@ -175,12 +175,12 @@ public function testCreateRequestPassesSecurityContextAttributesToTheNewRequest( $this->assertSame('foo', $subRequest->attributes->get($attribute)); } - public function provideSecurityContextAttributes() + public function provideSecurityRequestAttributes() { return [ - [Security::AUTHENTICATION_ERROR], - [Security::ACCESS_DENIED_ERROR], - [Security::LAST_USERNAME], + [SecurityRequestAttributes::AUTHENTICATION_ERROR], + [SecurityRequestAttributes::ACCESS_DENIED_ERROR], + [SecurityRequestAttributes::LAST_USERNAME], ]; }