1 <?php
declare(strict_types
=1);
4 * This file is part of the Rapsys UserBundle package.
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Rapsys\UserBundle\Handler
;
14 use Psr\Log\LoggerInterface
;
15 use Symfony\Component\DependencyInjection\ContainerInterface
;
16 use Symfony\Component\HttpFoundation\RedirectResponse
;
17 use Symfony\Component\HttpFoundation\Request
;
18 use Symfony\Component\HttpFoundation\Response
;
19 use Symfony\Component\HttpKernel\HttpKernelInterface
;
20 use Symfony\Component\Routing\Exception\ResourceNotFoundException
;
21 use Symfony\Component\Routing\RequestContext
;
22 use Symfony\Component\Routing\RouterInterface
;
23 use Symfony\Component\Security\Core\Exception\AuthenticationException
;
24 use Symfony\Component\Security\Core\Exception\BadCredentialsException
;
25 use Symfony\Component\Security\Core\Exception\DisabledException
;
26 use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler
;
27 use Symfony\Component\Security\Http\HttpUtils
;
28 use Symfony\Component\Security\Http\ParameterBagUtils
;
30 use Rapsys\PackBundle\Util\SluggerUtil
;
31 use Rapsys\UserBundle\Exception\UnactivatedException
;
32 use Rapsys\UserBundle\RapsysUserBundle
;
37 class AuthenticationFailureHandler
extends DefaultAuthenticationFailureHandler
{
41 protected array $config;
42 protected array $options;
43 protected array $defaultOptions = [
44 'failure_path' => null,
45 'failure_forward' => false,
46 'login_path' => '/login',
47 'failure_path_parameter' => '_failure_path',
53 protected RouterInterface
$router;
58 protected SluggerUtil
$slugger;
61 * @xxx Second argument will be replaced by security.firewalls.main.logout.target
62 * @see vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php +360
66 public function __construct(HttpKernelInterface
$httpKernel, HttpUtils
$httpUtils, array $options, LoggerInterface
$logger, ContainerInterface
$container, RouterInterface
$router, SluggerUtil
$slugger) {
68 $this->config
= $container->getParameter(self
::getAlias());
71 $this->router
= $router;
74 $this->slugger
= $slugger;
76 //Call parent constructor
77 parent
::__construct($httpKernel, $httpUtils, $options, $logger);
81 * This is called when an interactive authentication attempt fails
83 * User may retrieve mail + field + hash for each unactivated/locked accounts
87 public function onAuthenticationFailure(Request
$request, AuthenticationException
$exception): Response
{
88 //With bad credential exception
89 if ($exception instanceof BadCredentialsException
) {
90 //With parent exception
91 if ($parent = $exception->getPrevious()) {
93 //TODO: check form _token validity ???
95 $request->request
->has('login') &&
96 !empty($login = $request->request
->get('login')) &&
97 !empty($mail = $login['mail'])
99 //Redirect on register
100 if ($parent instanceof UnactivatedException
|| $parent instanceof DisabledException
) {
101 //Set extra parameters
102 $extra = ['mail' => $smail = $this->slugger
->short($mail), 'field' => $sfield = $this->slugger
->serialize([]), 'hash' => $this->slugger
->hash($smail.$sfield)];
104 //With failure target path option
105 if (!empty($failurePath = $this->options
['failure_path'])) {
107 if ($failurePath[0] == '/') {
108 //Create login path request instance
109 $req = Request
::create($failurePath);
111 //Get login path pathinfo
112 $path = $req->getPathInfo();
115 $path = str_replace($request->getScriptName(), '', $path);
117 //Try with login path path
120 $oldContext = $this->router
->getContext();
122 //Force clean context
123 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
124 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
125 $this->router
->setContext(new RequestContext());
127 //Retrieve route matching path
128 $route = $this->router
->match($path);
131 $this->router
->setContext($oldContext);
137 if ($name = $route['_route']) {
138 //Remove route and controller from route defaults
139 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
142 $url = $this->router
->generate($name, $extra+
$route);
144 //Return redirect to url response
145 return new RedirectResponse($url, 302);
148 } catch (ResourceNotFoundException
$e) {
149 //Unset default path, name and route
150 unset($failurePath, $name, $route);
154 //Try with login path route
156 //Retrieve route matching path
157 $url = $this->router
->generate($failurePath, $extra);
159 //Return redirect to url response
160 return new RedirectResponse($url, 302);
161 //Route not found, missing parameter or invalid parameter
162 } catch (RouteNotFoundException
|MissingMandatoryParametersException
|InvalidParameterException
$e) {
163 //Unset default path and url
164 unset($failurePath, $url);
169 //With index route from config
170 if (!empty($name = $this->config
['route']['register']['name']) && is_array($context = $this->config
['route']['register']['context'])) {
174 $url = $this->router
->generate($name, $extra+
$context);
176 //Return generated route
177 return new RedirectResponse($url, 302);
179 } catch (ResourceNotFoundException
$e) {
180 //Unset name and context
181 unset($name, $context);
185 //With login target path option
186 if (!empty($loginPath = $this->options
['login_path'])) {
188 if ($loginPath[0] == '/') {
189 //Create login path request instance
190 $req = Request
::create($loginPath);
192 //Get login path pathinfo
193 $path = $req->getPathInfo();
196 $path = str_replace($request->getScriptName(), '', $path);
198 //Try with login path path
201 $oldContext = $this->router
->getContext();
203 //Force clean context
204 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
205 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
206 $this->router
->setContext(new RequestContext());
208 //Retrieve route matching path
209 $route = $this->router
->match($path);
212 $this->router
->setContext($oldContext);
218 if ($name = $route['_route']) {
219 //Remove route and controller from route defaults
220 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
223 $url = $this->router
->generate($name, $extra+
$route);
225 //Return redirect to url response
226 return new RedirectResponse($url, 302);
229 } catch (ResourceNotFoundException
$e) {
230 //Unset default path, name and route
231 unset($loginPath, $name, $route);
235 //Try with login path route
237 //Retrieve route matching path
238 $url = $this->router
->generate($loginPath, $extra);
240 //Return redirect to url response
241 return new RedirectResponse($url, 302);
242 //Route not found, missing parameter or invalid parameter
243 } catch (RouteNotFoundException
|MissingMandatoryParametersException
|InvalidParameterException
$e) {
244 //Unset default path and url
245 unset($loginPath, $url);
254 //Call parent function
255 return parent
::onAuthenticationFailure($request, $exception);
261 public function getAlias(): string {
262 return RapsysUserBundle
::getAlias();