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