]> Raphaël G. Git Repositories - userbundle/blob - Handler/AuthenticationSuccessHandler.php
Remove mail and hash possible leak from failure_path context
[userbundle] / Handler / AuthenticationSuccessHandler.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 Symfony\Component\HttpFoundation\RedirectResponse;
15 use Symfony\Component\HttpFoundation\Request;
16 use Symfony\Component\HttpFoundation\Response;
17 use Symfony\Component\Routing\Exception\InvalidParameterException;
18 use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
19 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
20 use Symfony\Component\Routing\Exception\RouteNotFoundException;
21 use Symfony\Component\Routing\RequestContext;
22 use Symfony\Component\Routing\RouterInterface;
23 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
24 use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
25 use Symfony\Component\Security\Http\ParameterBagUtils;
26 use Symfony\Component\Security\Http\Util\TargetPathTrait;
27
28 /**
29 * {@inheritdoc}
30 */
31 class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {
32 /**
33 * Allows to use getTargetPath and removeTargetPath private functions
34 */
35 use TargetPathTrait;
36
37 /**
38 * Default options
39 */
40 protected array $defaultOptions = [
41 'always_use_default_target_path' => false,
42 'default_target_path' => '/',
43 'login_path' => '/login',
44 'target_path_parameter' => '_target_path',
45 'use_referer' => false,
46 ];
47
48 /**
49 * Options
50 */
51 protected array $options;
52
53 /**
54 * Router instance
55 */
56 protected RouterInterface $router;
57
58 /**
59 * {@inheritdoc}
60 */
61 public function __construct(RouterInterface $router, array $options = []) {
62 //Set router
63 $this->router = $router;
64
65 //Set options
66 $this->setOptions($options);
67 }
68
69 /**
70 * This is called when an interactive authentication attempt succeeds
71 *
72 * In use_referer case it will handle correctly when login_path is a route name or path
73 *
74 * {@inheritdoc}
75 */
76 public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response {
77 //Set login route
78 $login = $request->get('_route');
79
80 //With login path option
81 if (!empty($loginPath = $this->options['login_path'])) {
82 //With path
83 if ($loginPath[0] == '/') {
84 //Create login path request instance
85 $req = Request::create($loginPath);
86
87 //Get login path pathinfo
88 $path = $req->getPathInfo();
89
90 //Remove script name
91 $path = str_replace($request->getScriptName(), '', $path);
92
93 //Try with login path path
94 try {
95 //Save old context
96 $oldContext = $this->router->getContext();
97
98 //Force clean context
99 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
100 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
101 $this->router->setContext(new RequestContext());
102
103 //Retrieve route matching path
104 $route = $this->router->match($path);
105
106 //Reset context
107 $this->router->setContext($oldContext);
108
109 //Clear old context
110 unset($oldContext);
111
112 //Set login route
113 if (!empty($route['_route'])) {
114 //Set login route
115 $login = $route['_route'];
116 }
117 //No route matched
118 } catch (ResourceNotFoundException $e) {
119 throw new \UnexpectedValueException(sprintf('The "login_path" path "%s" must match a route', $this->options['login_path']), $e->getCode(), $e);
120 }
121 //With route
122 } else {
123 //Try with login path route
124 try {
125 //Retrieve route matching path
126 $path = $this->router->generate($loginPath);
127
128 //Set login route
129 $login = $loginPath;
130 //No route found
131 } catch (RouteNotFoundException $e) {
132 throw new \UnexpectedValueException(sprintf('The "login_path" route "%s" must match a route name', $this->options['login_path']), $e->getCode(), $e);
133 //Ignore missing or invalid parameter
134 //XXX: useless or would not work ?
135 } catch (MissingMandatoryParametersException|InvalidParameterException $e) {
136 //Set login route
137 $login = $loginPath;
138 }
139 }
140 }
141
142 //Without always_use_default_target_path
143 if (empty($this->options['always_use_default_target_path'])) {
144 //With _target_path
145 if ($targetUrl = ParameterBagUtils::getRequestParameterValue($request, $this->options['target_path_parameter'])) {
146 //Set target url
147 $url = $targetUrl;
148
149 //Return redirect to url response
150 return new RedirectResponse($url, 302);
151 //With session and target path in session
152 } elseif (
153 !empty($this->providerKey) &&
154 ($session = $request->getSession()) &&
155 ($targetUrl = $this->getTargetPath($session, $this->providerKey))
156 ) {
157 //Remove session target path
158 $this->removeTargetPath($session, $this->providerKey);
159
160 //Set target url
161 $url = $targetUrl;
162
163 //Return redirect to url response
164 return new RedirectResponse($url, 302);
165 //Extract and process referer
166 } elseif ($this->options['use_referer'] && ($targetUrl = $request->headers->get('referer'))) {
167 //Create referer request instance
168 $req = Request::create($targetUrl);
169
170 //Get referer path
171 $path = $req->getPathInfo();
172
173 //Get referer query string
174 $query = $req->getQueryString();
175
176 //Remove script name
177 $path = str_replace($request->getScriptName(), '', $path);
178
179 //Try with referer path
180 try {
181 //Save old context
182 $oldContext = $this->router->getContext();
183
184 //Force clean context
185 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
186 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
187 $this->router->setContext(new RequestContext());
188
189 //Retrieve route matching path
190 $route = $this->router->match($path);
191
192 //Reset context
193 $this->router->setContext($oldContext);
194
195 //Clear old context
196 unset($oldContext);
197
198 //With differing route from login one
199 if (($name = $route['_route']) != $login) {
200 //Remove route and controller from route defaults
201 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
202
203 //Set url to generated one from referer route
204 $url = $this->router->generate($name, $route);
205
206 //Return redirect to url response
207 return new RedirectResponse($url, 302);
208 }
209 //No route matched
210 } catch (ResourceNotFoundException $e) {
211 //Unset target url, route and name
212 unset($targetUrl, $route, $name);
213 }
214 }
215 }
216
217 //With default target path option
218 if (!empty($defaultPath = $this->options['default_target_path'])) {
219 //With path
220 if ($defaultPath[0] == '/') {
221 //Create login path request instance
222 $req = Request::create($defaultPath);
223
224 //Get login path pathinfo
225 $path = $req->getPathInfo();
226
227 //Remove script name
228 $path = str_replace($request->getScriptName(), '', $path);
229
230 //Try with login path path
231 try {
232 //Save old context
233 $oldContext = $this->router->getContext();
234
235 //Force clean context
236 //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
237 //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
238 $this->router->setContext(new RequestContext());
239
240 //Retrieve route matching path
241 $route = $this->router->match($path);
242
243 //Reset context
244 $this->router->setContext($oldContext);
245
246 //Clear old context
247 unset($oldContext);
248
249 //Without login route name
250 if (($name = $route['_route']) != $login) {
251 //Remove route and controller from route defaults
252 unset($route['_route'], $route['_controller'], $route['_canonical_route']);
253
254 //Generate url
255 $url = $this->router->generate($name, $route);
256
257 //Return redirect to url response
258 return new RedirectResponse($url, 302);
259 //With logout route name
260 } else {
261 //Unset default path, name and route
262 unset($defaultPath, $name, $route);
263 }
264 //No route matched
265 } catch (ResourceNotFoundException $e) {
266 throw \Exception('', $e->getCode(), $e);
267 //Unset default path, name and route
268 unset($defaultPath, $name, $route);
269 }
270 //Without login route name
271 } elseif ($defaultPath != $login) {
272 //Try with login path route
273 try {
274 //Retrieve route matching path
275 $url = $this->router->generate($defaultPath);
276
277 //Return redirect to url response
278 return new RedirectResponse($url, 302);
279 //Route not found, missing parameter or invalid parameter
280 } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
281 //Unset default path and url
282 unset($defaultPath, $url);
283 }
284 }
285 }
286
287 //Throw exception
288 throw new \UnexpectedValueException('You must provide a valid login target url or route name');
289 }
290 }