diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php index 4a7453136b..158da4babb 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.php @@ -143,6 +143,7 @@ return static function (ContainerConfigurator $container) { abstract_arg('options'), service('property_accessor')->nullOnInvalid(), ]) + ->call('setTranslator', [service('translator')->ignoreOnInvalid()]) ->set('security.authenticator.remember_me', RememberMeAuthenticator::class) ->abstract() diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php index de0b583dd4..7683ea2484 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.php @@ -189,6 +189,7 @@ return static function (ContainerConfigurator $container) { service('event_dispatcher')->nullOnInvalid(), service('property_accessor')->nullOnInvalid(), ]) + ->call('setTranslator', [service('translator')->ignoreOnInvalid()]) ->tag('monolog.logger', ['channel' => 'security']) ->set('security.authentication.listener.remote_user', RemoteUserAuthenticationListener::class) diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 6a3cb2774f..0257630f8b 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -8,8 +8,9 @@ CHANGELOG * Changed `AuthorizationChecker` to call the access decision manager in unauthenticated sessions with a `NullToken` * [BC break] Removed `AccessListener::PUBLIC_ACCESS` in favor of `AuthenticatedVoter::PUBLIC_ACCESS` * Added `Passport` to `LoginFailureEvent`. - * Deprecated `setProviderKey()`/`getProviderKey()` in favor of `setFirewallName()/getFirewallName()` in `PreAuthenticatedToken`, `RememberMeToken`, `SwitchUserToken`, `UsernamePasswordToken`, `DefaultAuthenticationSuccessHandler`; and deprecated the `AbstractRememberMeServices::$providerKey` property in favor of `AbstractRememberMeServices::$firewallName` + * Deprecated `setProviderKey()`/`getProviderKey()` in favor of `setFirewallName()/getFirewallName()` in `PreAuthenticatedToken`, `RememberMeToken`, `SwitchUserToken`, `UsernamePasswordToken`, `DefaultAuthenticationSuccessHandler`; and deprecated the `AbstractRememberMeServices::$providerKey` property in favor of `AbstractRememberMeServices::$firewallName` * Added `FirewallListenerInterface` to make the execution order of firewall listeners configurable + * Added translator to `\Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator` and `\Symfony\Component\Security\Http\Firewall\UsernamePasswordJsonAuthenticationListener` to translate authentication failure messages 5.1.0 ----- diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index 282cd48c12..f8524695ba 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -35,6 +35,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordC use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Provides a stateless implementation of an authentication via @@ -55,6 +56,11 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface private $successHandler; private $failureHandler; + /** + * @var TranslatorInterface|null + */ + private $translator; + public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, ?AuthenticationSuccessHandlerInterface $successHandler = null, ?AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], ?PropertyAccessorInterface $propertyAccessor = null) { $this->options = array_merge(['username_path' => 'username', 'password_path' => 'password'], $options); @@ -120,7 +126,13 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { if (null === $this->failureHandler) { - return new JsonResponse(['error' => $exception->getMessageKey()], JsonResponse::HTTP_UNAUTHORIZED); + $errorMessage = $exception->getMessageKey(); + + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security'); + } + + return new JsonResponse(['error' => $errorMessage], JsonResponse::HTTP_UNAUTHORIZED); } return $this->failureHandler->onAuthenticationFailure($request, $exception); @@ -131,6 +143,11 @@ class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface return true; } + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + } + private function getCredentials(Request $request) { $data = json_decode($request->getContent()); diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index ef9a76abd3..4363ee93a6 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -34,6 +34,7 @@ use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * UsernamePasswordJsonAuthenticationListener is a stateless implementation of @@ -57,6 +58,11 @@ class UsernamePasswordJsonAuthenticationListener extends AbstractListener private $propertyAccessor; private $sessionStrategy; + /** + * @var TranslatorInterface|null + */ + private $translator; + public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpUtils $httpUtils, string $providerKey, AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, array $options = [], LoggerInterface $logger = null, EventDispatcherInterface $eventDispatcher = null, PropertyAccessorInterface $propertyAccessor = null) { $this->tokenStorage = $tokenStorage; @@ -182,7 +188,13 @@ class UsernamePasswordJsonAuthenticationListener extends AbstractListener } if (!$this->failureHandler) { - return new JsonResponse(['error' => $failed->getMessageKey()], 401); + $errorMessage = $failed->getMessageKey(); + + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($failed->getMessageKey(), $failed->getMessageData(), 'security'); + } + + return new JsonResponse(['error' => $errorMessage], 401); } $response = $this->failureHandler->onAuthenticationFailure($request, $failed); @@ -204,6 +216,11 @@ class UsernamePasswordJsonAuthenticationListener extends AbstractListener $this->sessionStrategy = $sessionStrategy; } + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + } + private function migrateSession(Request $request, TokenInterface $token) { if (!$this->sessionStrategy || !$request->hasSession() || !$request->hasPreviousSession()) {