]> Raphaël G. Git Repositories - userbundle/blob - Handler/AuthenticationFailureHandler.php
fea0824e0e107d9c9a59fd20260ab5045231f841
[userbundle] / Handler / AuthenticationFailureHandler.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys UserBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\UserBundle\Handler;
13
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;
29
30 use Rapsys\PackBundle\Util\SluggerUtil;
31 use Rapsys\UserBundle\Exception\UnactivatedException;
32 use Rapsys\UserBundle\RapsysUserBundle;
33
34 /**
35 * {@inheritdoc}
36 */
37 class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {
38 /**
39 * Config array
40 */
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',
48 ];
49
50 /**
51 * Router instance
52 */
53 protected RouterInterface $router;
54
55 /**
56 * Slugger instance
57 */
58 protected SluggerUtil $slugger;
59
60 /**
61 * @xxx Second argument will be replaced by security.firewalls.main.logout.target
62 * @see vendor/symfony/security-bundle/DependencyInjection/SecurityExtension.php +360
63 *
64 * {@inheritdoc}
65 */
66 public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options, LoggerInterface $logger, ContainerInterface $container, RouterInterface $router, SluggerUtil $slugger) {
67 //Set config
68 $this->config = $container->getParameter(self::getAlias());
69
70 //Set router
71 $this->router = $router;
72
73 //Set slugger
74 $this->slugger = $slugger;
75
76 //Call parent constructor
77 parent::__construct($httpKernel, $httpUtils, $options, $logger);
78 }
79
80 /**
81 * This is called when an interactive authentication attempt fails
82 *
83 * User may retrieve mail + field + hash for each unactivated/locked accounts
84 *
85 * {@inheritdoc}
86 */
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()) {
92 //Retrieve login
93 //TODO: check form _token validity ???
94 if (
95 $request->request->has('login') &&
96 !empty($login = $request->request->get('login')) &&
97 !empty($mail = $login['mail'])
98 ) {
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)];
103
104 //With failure target path option
105 if (!empty($failurePath = $this->options['failure_path'])) {
106 //With path
107 if ($failurePath[0] == '/') {
108 //Create login path request instance
109 $req = Request::create($failurePath);
110
111 //Get login path pathinfo
112 $path = $req->getPathInfo();
113
114 //Remove script name
115 $path = str_replace($request->getScriptName(), '', $path);
116
117 //Try with login path path
118 try {
119 //Save old context
120 $oldContext = $this->router->getContext();
121
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());
126
127 //Retrieve route matching path
128 $route = $this->router->match($path);
129
130 //Reset context
131 $this->router->setContext($oldContext);
132
133 //Clear old context
134 unset($oldContext);
135
136 //With route name
137 if ($name = $route['_route']) {
138 //Remove route and controller from route defaults
139 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
140
141 //Generate url
142 $url = $this->router->generate($name, $extra+$route);
143
144 //Return redirect to url response
145 return new RedirectResponse($url, 302);
146 }
147 //No route matched
148 } catch (ResourceNotFoundException $e) {
149 //Unset default path, name and route
150 unset($failurePath, $name, $route);
151 }
152 //With route name
153 } else {
154 //Try with login path route
155 try {
156 //Retrieve route matching path
157 $url = $this->router->generate($failurePath, $extra);
158
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);
165 }
166 }
167 }
168
169 //With index route from config
170 if (!empty($name = $this->config['route']['register']['name']) && is_array($context = $this->config['route']['register']['context'])) {
171 //Try index route
172 try {
173 //Generate url
174 $url = $this->router->generate($name, $extra+$context);
175
176 //Return generated route
177 return new RedirectResponse($url, 302);
178 //No route matched
179 } catch (ResourceNotFoundException $e) {
180 //Unset name and context
181 unset($name, $context);
182 }
183 }
184
185 //With login target path option
186 if (!empty($loginPath = $this->options['login_path'])) {
187 //With path
188 if ($loginPath[0] == '/') {
189 //Create login path request instance
190 $req = Request::create($loginPath);
191
192 //Get login path pathinfo
193 $path = $req->getPathInfo();
194
195 //Remove script name
196 $path = str_replace($request->getScriptName(), '', $path);
197
198 //Try with login path path
199 try {
200 //Save old context
201 $oldContext = $this->router->getContext();
202
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());
207
208 //Retrieve route matching path
209 $route = $this->router->match($path);
210
211 //Reset context
212 $this->router->setContext($oldContext);
213
214 //Clear old context
215 unset($oldContext);
216
217 //With route name
218 if ($name = $route['_route']) {
219 //Remove route and controller from route defaults
220 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
221
222 //Generate url
223 $url = $this->router->generate($name, $extra+$route);
224
225 //Return redirect to url response
226 return new RedirectResponse($url, 302);
227 }
228 //No route matched
229 } catch (ResourceNotFoundException $e) {
230 //Unset default path, name and route
231 unset($loginPath, $name, $route);
232 }
233 //With route name
234 } else {
235 //Try with login path route
236 try {
237 //Retrieve route matching path
238 $url = $this->router->generate($loginPath, $extra);
239
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);
246 }
247 }
248 }
249 }
250 }
251 }
252 }
253
254 //Call parent function
255 return parent::onAuthenticationFailure($request, $exception);
256 }
257
258 /**
259 * {@inheritdoc}
260 */
261 public function getAlias(): string {
262 return RapsysUserBundle::getAlias();
263 }
264 }