1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Security\Http\Firewall;
13
14use Psr\Log\LoggerInterface;
15use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16use Symfony\Component\HttpFoundation\Request;
17use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
18use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
19use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
20use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
21use Symfony\Component\Security\Core\Exception\BadCredentialsException;
22use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
23use Symfony\Component\Security\Core\Security;
24use Symfony\Component\Security\Csrf\CsrfToken;
25use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
26use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
27use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
28use Symfony\Component\Security\Http\HttpUtils;
29use Symfony\Component\Security\Http\ParameterBagUtils;
30use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
31
32/**
33 * UsernamePasswordFormAuthenticationListener is the default implementation of
34 * an authentication via a simple form composed of a username and a password.
35 *
36 * @author Fabien Potencier <fabien@symfony.com>
37 */
38class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener
39{
40    private $csrfTokenManager;
41
42    public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = [], LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null)
43    {
44        parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge([
45            'username_parameter' => '_username',
46            'password_parameter' => '_password',
47            'csrf_parameter' => '_csrf_token',
48            'csrf_token_id' => 'authenticate',
49            'post_only' => true,
50        ], $options), $logger, $dispatcher);
51
52        $this->csrfTokenManager = $csrfTokenManager;
53    }
54
55    /**
56     * {@inheritdoc}
57     */
58    protected function requiresAuthentication(Request $request)
59    {
60        if ($this->options['post_only'] && !$request->isMethod('POST')) {
61            return false;
62        }
63
64        return parent::requiresAuthentication($request);
65    }
66
67    /**
68     * {@inheritdoc}
69     */
70    protected function attemptAuthentication(Request $request)
71    {
72        if (null !== $this->csrfTokenManager) {
73            $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
74
75            if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) {
76                throw new InvalidCsrfTokenException('Invalid CSRF token.');
77            }
78        }
79
80        if ($this->options['post_only']) {
81            $username = ParameterBagUtils::getParameterBagValue($request->request, $this->options['username_parameter']);
82            $password = ParameterBagUtils::getParameterBagValue($request->request, $this->options['password_parameter']);
83        } else {
84            $username = ParameterBagUtils::getRequestParameterValue($request, $this->options['username_parameter']);
85            $password = ParameterBagUtils::getRequestParameterValue($request, $this->options['password_parameter']);
86        }
87
88        if (!\is_string($username) && (!\is_object($username) || !method_exists($username, '__toString'))) {
89            throw new BadRequestHttpException(sprintf('The key "%s" must be a string, "%s" given.', $this->options['username_parameter'], \gettype($username)));
90        }
91
92        $username = trim($username);
93
94        if (\strlen($username) > Security::MAX_USERNAME_LENGTH) {
95            throw new BadCredentialsException('Invalid username.');
96        }
97
98        $request->getSession()->set(Security::LAST_USERNAME, $username);
99
100        return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey));
101    }
102}
103