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 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
;
31 class AuthenticationSuccessHandler
extends DefaultAuthenticationSuccessHandler
{
33 * Allows to use getTargetPath and removeTargetPath private functions
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,
51 public function __construct(protected RouterInterface
$router, protected array $options = []) {
53 $this->setOptions($options);
57 * This is called when an interactive authentication attempt succeeds
59 * In use_referer case it will handle correctly when login_path is a route name or path
63 public function onAuthenticationSuccess(Request
$request, TokenInterface
$token): Response
{
65 $login = $request->get('_route');
67 //With login path option
68 if (!empty($loginPath = $this->options
['login_path'])) {
70 if ($loginPath[0] == '/') {
71 //Create login path request instance
72 $req = Request
::create($loginPath);
74 //Get login path pathinfo
75 $path = $req->getPathInfo();
78 $path = str_replace($request->getScriptName(), '', $path);
80 //Try with login path path
83 $oldContext = $this->router
->getContext();
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());
90 //Retrieve route matching path
91 $route = $this->router
->match($path);
94 $this->router
->setContext($oldContext);
100 if (!empty($route['_route'])) {
102 $login = $route['_route'];
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);
110 //Try with login path route
112 //Retrieve route matching path
113 $path = $this->router
->generate($loginPath);
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) {
129 //Without always_use_default_target_path
130 if (empty($this->options
['always_use_default_target_path'])) {
132 if ($targetUrl = ParameterBagUtils
::getRequestParameterValue($request, $this->options
['target_path_parameter'])) {
136 //Return redirect to url response
137 return new RedirectResponse($url, 302);
138 //With session and target path in session
140 !empty($this->providerKey
) &&
141 ($session = $request->getSession()) &&
142 ($targetUrl = $this->getTargetPath($session, $this->providerKey
))
144 //Remove session target path
145 $this->removeTargetPath($session, $this->providerKey
);
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);
158 $path = $req->getPathInfo();
160 //Get referer query string
161 $query = $req->getQueryString();
164 $path = str_replace($request->getScriptName(), '', $path);
166 //Try with referer path
169 $oldContext = $this->router
->getContext();
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());
176 //Retrieve route matching path
177 $route = $this->router
->match($path);
180 $this->router
->setContext($oldContext);
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']);
190 //Set url to generated one from referer route
191 $url = $this->router
->generate($name, $route);
193 //Return redirect to url response
194 return new RedirectResponse($url, 302);
197 } catch (ResourceNotFoundException
$e) {
198 //Unset target url, route and name
199 unset($targetUrl, $route, $name);
204 //With default target path option
205 if (!empty($defaultPath = $this->options
['default_target_path'])) {
207 if ($defaultPath[0] == '/') {
208 //Create login path request instance
209 $req = Request
::create($defaultPath);
211 //Get login path pathinfo
212 $path = $req->getPathInfo();
215 $path = str_replace($request->getScriptName(), '', $path);
217 //Try with login path path
220 $oldContext = $this->router
->getContext();
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());
227 //Retrieve route matching path
228 $route = $this->router
->match($path);
231 $this->router
->setContext($oldContext);
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']);
242 $url = $this->router
->generate($name, $route);
244 //Return redirect to url response
245 return new RedirectResponse($url, 302);
246 //With logout route name
248 //Unset default path, name and route
249 unset($defaultPath, $name, $route);
252 } catch (ResourceNotFoundException
$e) {
253 throw \
Exception('', $e->getCode(), $e);
254 //Unset default path, name and route
255 unset($defaultPath, $name, $route);
257 //Without login route name
258 } elseif ($defaultPath != $login) {
259 //Try with login path route
261 //Retrieve route matching path
262 $url = $this->router
->generate($defaultPath);
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);
275 throw new \
UnexpectedValueException('You must provide a valid login target url or route name');