vendor/symfony/security-http/Authenticator/JsonLoginAuthenticator.php line 44

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Security\Http\Authenticator;
  11. use Symfony\Component\HttpFoundation\JsonResponse;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  15. use Symfony\Component\PropertyAccess\Exception\AccessException;
  16. use Symfony\Component\PropertyAccess\PropertyAccess;
  17. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  18. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  19. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  20. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  21. use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
  22. use Symfony\Component\Security\Core\User\UserProviderInterface;
  23. use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
  24. use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
  25. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
  26. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  27. use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
  28. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  29. use Symfony\Component\Security\Http\HttpUtils;
  30. use Symfony\Contracts\Translation\TranslatorInterface;
  31. /**
  32.  * Provides a stateless implementation of an authentication via
  33.  * a JSON document composed of a username and a password.
  34.  *
  35.  * @author Kévin Dunglas <dunglas@gmail.com>
  36.  * @author Wouter de Jong <wouter@wouterj.nl>
  37.  *
  38.  * @final
  39.  */
  40. class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface
  41. {
  42.     private array $options;
  43.     private HttpUtils $httpUtils;
  44.     private UserProviderInterface $userProvider;
  45.     private PropertyAccessorInterface $propertyAccessor;
  46.     private ?AuthenticationSuccessHandlerInterface $successHandler;
  47.     private ?AuthenticationFailureHandlerInterface $failureHandler;
  48.     private ?TranslatorInterface $translator null;
  49.     public function __construct(HttpUtils $httpUtilsUserProviderInterface $userProviderAuthenticationSuccessHandlerInterface $successHandler nullAuthenticationFailureHandlerInterface $failureHandler null, array $options = [], PropertyAccessorInterface $propertyAccessor null)
  50.     {
  51.         $this->options array_merge(['username_path' => 'username''password_path' => 'password'], $options);
  52.         $this->httpUtils $httpUtils;
  53.         $this->successHandler $successHandler;
  54.         $this->failureHandler $failureHandler;
  55.         $this->userProvider $userProvider;
  56.         $this->propertyAccessor $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
  57.     }
  58.     public function supports(Request $request): ?bool
  59.     {
  60.         if (
  61.             !str_contains($request->getRequestFormat() ?? '''json')
  62.             && !str_contains((method_exists(Request::class, 'getContentTypeFormat') ? $request->getContentTypeFormat() : $request->getContentType()) ?? '''json')
  63.         ) {
  64.             return false;
  65.         }
  66.         if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request$this->options['check_path'])) {
  67.             return false;
  68.         }
  69.         return true;
  70.     }
  71.     public function authenticate(Request $request): Passport
  72.     {
  73.         try {
  74.             $credentials $this->getCredentials($request);
  75.         } catch (BadRequestHttpException $e) {
  76.             $request->setRequestFormat('json');
  77.             throw $e;
  78.         }
  79.         $userBadge = new UserBadge($credentials['username'], $this->userProvider->loadUserByIdentifier(...));
  80.         $passport = new Passport($userBadge, new PasswordCredentials($credentials['password']));
  81.         if ($this->userProvider instanceof PasswordUpgraderInterface) {
  82.             $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
  83.         }
  84.         return $passport;
  85.     }
  86.     public function createToken(Passport $passportstring $firewallName): TokenInterface
  87.     {
  88.         return new UsernamePasswordToken($passport->getUser(), $firewallName$passport->getUser()->getRoles());
  89.     }
  90.     public function onAuthenticationSuccess(Request $requestTokenInterface $tokenstring $firewallName): ?Response
  91.     {
  92.         if (null === $this->successHandler) {
  93.             return null// let the original request continue
  94.         }
  95.         return $this->successHandler->onAuthenticationSuccess($request$token);
  96.     }
  97.     public function onAuthenticationFailure(Request $requestAuthenticationException $exception): ?Response
  98.     {
  99.         if (null === $this->failureHandler) {
  100.             if (null !== $this->translator) {
  101.                 $errorMessage $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
  102.             } else {
  103.                 $errorMessage strtr($exception->getMessageKey(), $exception->getMessageData());
  104.             }
  105.             return new JsonResponse(['error' => $errorMessage], JsonResponse::HTTP_UNAUTHORIZED);
  106.         }
  107.         return $this->failureHandler->onAuthenticationFailure($request$exception);
  108.     }
  109.     public function isInteractive(): bool
  110.     {
  111.         return true;
  112.     }
  113.     public function setTranslator(TranslatorInterface $translator)
  114.     {
  115.         $this->translator $translator;
  116.     }
  117.     private function getCredentials(Request $request)
  118.     {
  119.         $data json_decode($request->getContent());
  120.         if (!$data instanceof \stdClass) {
  121.             throw new BadRequestHttpException('Invalid JSON.');
  122.         }
  123.         $credentials = [];
  124.         try {
  125.             $credentials['username'] = $this->propertyAccessor->getValue($data$this->options['username_path']);
  126.             if (!\is_string($credentials['username'])) {
  127.                 throw new BadRequestHttpException(sprintf('The key "%s" must be a string.'$this->options['username_path']));
  128.             }
  129.         } catch (AccessException $e) {
  130.             throw new BadRequestHttpException(sprintf('The key "%s" must be provided.'$this->options['username_path']), $e);
  131.         }
  132.         try {
  133.             $credentials['password'] = $this->propertyAccessor->getValue($data$this->options['password_path']);
  134.             if (!\is_string($credentials['password'])) {
  135.                 throw new BadRequestHttpException(sprintf('The key "%s" must be a string.'$this->options['password_path']));
  136.             }
  137.         } catch (AccessException $e) {
  138.             throw new BadRequestHttpException(sprintf('The key "%s" must be provided.'$this->options['password_path']), $e);
  139.         }
  140.         if ('' === $credentials['username'] || '' === $credentials['password']) {
  141.             trigger_deprecation('symfony/security''6.2''Passing an empty string as username or password parameter is deprecated.');
  142.         }
  143.         return $credentials;
  144.     }
  145. }