]> Raphaël G. Git Repositories - userbundle/blob - Handler/AuthenticationFailureHandler.php
Add new field configuration entry
[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 $config;
42 protected $options;
43 protected $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 $router;
54
55 /**
56 * Slugger instance
57 */
58 protected $slugger;
59
60 /**
61 * {@inheritdoc}
62 */
63 public function __construct(HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options = [], LoggerInterface $logger, ContainerInterface $container, RouterInterface $router, SluggerUtil $slugger) {
64 //Set config
65 $this->config = $container->getParameter(self::getAlias());
66
67 //Set router
68 $this->router = $router;
69
70 //Set slugger
71 $this->slugger = $slugger;
72
73 //Call parent constructor
74 parent::__construct($httpKernel, $httpUtils, $options, $logger);
75 }
76
77 /**
78 * This is called when an interactive authentication attempt fails
79 *
80 * User may retrieve mail + field + hash for each unactivated/locked accounts
81 *
82 * {@inheritdoc}
83 */
84 public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response {
85 //With bad credential exception
86 if ($exception instanceof BadCredentialsException) {
87 //With parent exception
88 if ($parent = $exception->getPrevious()) {
89 //Retrieve login
90 //TODO: check form _token validity ???
91 if (
92 $request->request->has('login') &&
93 !empty($login = $request->request->get('login')) &&
94 !empty($mail = $login['mail'])
95 ) {
96 //Redirect on register
97 if ($parent instanceof UnactivatedException || $parent instanceof DisabledException) {
98 //Set extra parameters
99 $extra = ['mail' => $smail = $this->slugger->short($mail), 'field' => $sfield = $this->slugger->serialize([]), 'hash' => $this->slugger->hash($smail.$sfield)];
100
101 //With failure target path option
102 if (!empty($failurePath = $this->options['failure_path'])) {
103 //With path
104 if ($failurePath[0] == '/') {
105 //Create login path request instance
106 $req = Request::create($failurePath);
107
108 //Get login path pathinfo
109 $path = $req->getPathInfo();
110
111 //Remove script name
112 $path = str_replace($request->getScriptName(), '', $path);
113
114 //Try with login path path
115 try {
116 //Save old context
117 $oldContext = $this->router->getContext();
118
119 //Force clean context
120 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
121 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
122 $this->router->setContext(new RequestContext());
123
124 //Retrieve route matching path
125 $route = $this->router->match($path);
126
127 //Reset context
128 $this->router->setContext($oldContext);
129
130 //Clear old context
131 unset($oldContext);
132
133 //With route name
134 if ($name = $route['_route']) {
135 //Remove route and controller from route defaults
136 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
137
138 //Generate url
139 $url = $this->router->generate($name, $extra+$route);
140
141 //Return redirect to url response
142 return new RedirectResponse($url, 302);
143 }
144 //No route matched
145 } catch (ResourceNotFoundException $e) {
146 //Unset default path, name and route
147 unset($failurePath, $name, $route);
148 }
149 //With route name
150 } else {
151 //Try with login path route
152 try {
153 //Retrieve route matching path
154 $url = $this->router->generate($failurePath, $extra);
155
156 //Return redirect to url response
157 return new RedirectResponse($url, 302);
158 //Route not found, missing parameter or invalid parameter
159 } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
160 //Unset default path and url
161 unset($failurePath, $url);
162 }
163 }
164 }
165
166 //With index route from config
167 if (!empty($name = $this->config['route']['register']['name']) && is_array($context = $this->config['route']['register']['context'])) {
168 //Try index route
169 try {
170 //Generate url
171 $url = $this->router->generate($name, $extra+$context);
172
173 //Return generated route
174 return new RedirectResponse($url, 302);
175 //No route matched
176 } catch (ResourceNotFoundException $e) {
177 //Unset name and context
178 unset($name, $context);
179 }
180 }
181
182 //With login target path option
183 if (!empty($loginPath = $this->options['login_path'])) {
184 //With path
185 if ($loginPath[0] == '/') {
186 //Create login path request instance
187 $req = Request::create($loginPath);
188
189 //Get login path pathinfo
190 $path = $req->getPathInfo();
191
192 //Remove script name
193 $path = str_replace($request->getScriptName(), '', $path);
194
195 //Try with login path path
196 try {
197 //Save old context
198 $oldContext = $this->router->getContext();
199
200 //Force clean context
201 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
202 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
203 $this->router->setContext(new RequestContext());
204
205 //Retrieve route matching path
206 $route = $this->router->match($path);
207
208 //Reset context
209 $this->router->setContext($oldContext);
210
211 //Clear old context
212 unset($oldContext);
213
214 //With route name
215 if ($name = $route['_route']) {
216 //Remove route and controller from route defaults
217 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
218
219 //Generate url
220 $url = $this->router->generate($name, $extra+$route);
221
222 //Return redirect to url response
223 return new RedirectResponse($url, 302);
224 }
225 //No route matched
226 } catch (ResourceNotFoundException $e) {
227 //Unset default path, name and route
228 unset($loginPath, $name, $route);
229 }
230 //With route name
231 } else {
232 //Try with login path route
233 try {
234 //Retrieve route matching path
235 $url = $this->router->generate($loginPath, $extra);
236
237 //Return redirect to url response
238 return new RedirectResponse($url, 302);
239 //Route not found, missing parameter or invalid parameter
240 } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
241 //Unset default path and url
242 unset($loginPath, $url);
243 }
244 }
245 }
246 }
247 }
248 }
249 }
250
251 //Call parent function
252 return parent::onAuthenticationFailure($request, $exception);
253 }
254
255 /**
256 * {@inheritdoc}
257 */
258 public function getAlias(): string {
259 return RapsysUserBundle::getAlias();
260 }
261 }