123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\Security\Http\Authenticator;
- use Symfony\Component\HttpFoundation\JsonResponse;
- use Symfony\Component\HttpFoundation\Request;
- use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
- use Symfony\Component\PropertyAccess\Exception\AccessException;
- use Symfony\Component\PropertyAccess\PropertyAccess;
- use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
- use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
- use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
- use Symfony\Component\Security\Core\Exception\AuthenticationException;
- use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
- 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\UserInterface;
- use Symfony\Component\Security\Core\User\UserProviderInterface;
- use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
- use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
- use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
- 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\Authenticator\Passport\PassportInterface;
- use Symfony\Component\Security\Http\HttpUtils;
- use Symfony\Contracts\Translation\TranslatorInterface;
- /**
- * Provides a stateless implementation of an authentication via
- * a JSON document composed of a username and a password.
- *
- * @author Kévin Dunglas <dunglas@gmail.com>
- * @author Wouter de Jong <wouter@wouterj.nl>
- *
- * @final
- * @experimental in 5.2
- */
- class JsonLoginAuthenticator implements InteractiveAuthenticatorInterface
- {
- private $options;
- private $httpUtils;
- private $userProvider;
- private $propertyAccessor;
- 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);
- $this->httpUtils = $httpUtils;
- $this->successHandler = $successHandler;
- $this->failureHandler = $failureHandler;
- $this->userProvider = $userProvider;
- $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
- }
- public function supports(Request $request): ?bool
- {
- if (false === strpos($request->getRequestFormat(), 'json') && false === strpos($request->getContentType(), 'json')) {
- return false;
- }
- if (isset($this->options['check_path']) && !$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
- return false;
- }
- return true;
- }
- public function authenticate(Request $request): PassportInterface
- {
- try {
- $credentials = $this->getCredentials($request);
- } catch (BadRequestHttpException $e) {
- $request->setRequestFormat('json');
- throw $e;
- }
- $passport = new Passport(new UserBadge($credentials['username'], function ($username) {
- $user = $this->userProvider->loadUserByUsername($username);
- if (!$user instanceof UserInterface) {
- throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
- }
- return $user;
- }), new PasswordCredentials($credentials['password']));
- if ($this->userProvider instanceof PasswordUpgraderInterface) {
- $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider));
- }
- return $passport;
- }
- public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
- {
- return new UsernamePasswordToken($passport->getUser(), null, $firewallName, $passport->getUser()->getRoles());
- }
- public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
- {
- if (null === $this->successHandler) {
- return null; // let the original request continue
- }
- return $this->successHandler->onAuthenticationSuccess($request, $token);
- }
- public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
- {
- if (null === $this->failureHandler) {
- if (null !== $this->translator) {
- $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
- } else {
- $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
- }
- return new JsonResponse(['error' => $errorMessage], JsonResponse::HTTP_UNAUTHORIZED);
- }
- return $this->failureHandler->onAuthenticationFailure($request, $exception);
- }
- public function isInteractive(): bool
- {
- return true;
- }
- public function setTranslator(TranslatorInterface $translator)
- {
- $this->translator = $translator;
- }
- private function getCredentials(Request $request)
- {
- $data = json_decode($request->getContent());
- if (!$data instanceof \stdClass) {
- throw new BadRequestHttpException('Invalid JSON.');
- }
- $credentials = [];
- try {
- $credentials['username'] = $this->propertyAccessor->getValue($data, $this->options['username_path']);
- if (!\is_string($credentials['username'])) {
- throw new BadRequestHttpException(sprintf('The key "%s" must be a string.', $this->options['username_path']));
- }
- if (\strlen($credentials['username']) > Security::MAX_USERNAME_LENGTH) {
- throw new BadCredentialsException('Invalid username.');
- }
- } catch (AccessException $e) {
- throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['username_path']), $e);
- }
- try {
- $credentials['password'] = $this->propertyAccessor->getValue($data, $this->options['password_path']);
- if (!\is_string($credentials['password'])) {
- throw new BadRequestHttpException(sprintf('The key "%s" must be a string.', $this->options['password_path']));
- }
- } catch (AccessException $e) {
- throw new BadRequestHttpException(sprintf('The key "%s" must be provided.', $this->options['password_path']), $e);
- }
- return $credentials;
- }
- }
|