+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Rapsys UserBundle package.
+ *
+ * (c) Raphaël Gertz <symfony@rapsys.eu>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Rapsys\UserBundle\Handler;
+
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Exception\InvalidParameterException;
+use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
+use Symfony\Component\Security\Http\ParameterBagUtils;
+use Symfony\Component\Security\Http\Util\TargetPathTrait;
+
+/**
+ * {@inheritdoc}
+ */
+class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {
+ /**
+ * Allows to use getTargetPath and removeTargetPath private functions
+ */
+ use TargetPathTrait;
+
+ /**
+ * Default options
+ */
+ protected $defaultOptions = [
+ 'always_use_default_target_path' => false,
+ 'default_target_path' => '/',
+ 'login_path' => '/login',
+ 'target_path_parameter' => '_target_path',
+ 'use_referer' => false,
+ ];
+
+ /**
+ * Options
+ */
+ protected $options;
+
+ /**
+ * Router instance
+ */
+ protected $router;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct(RouterInterface $router, array $options = []) {
+ //Set options
+ $this->setOptions($options);
+
+ //Set router
+ $this->router = $router;
+ }
+
+ /**
+ * This is called when an interactive authentication attempt succeeds
+ *
+ * In use_referer case it will handle correctly when login_path is a route name or path
+ *
+ * {@inheritdoc}
+ */
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response {
+ //Set login route
+ $login = $request->get('_route');
+
+ //With login path option
+ if (!empty($loginPath = $this->options['login_path'])) {
+ //With path
+ if ($loginPath[0] == '/') {
+ //Create login path request instance
+ $req = Request::create($loginPath);
+
+ //Get login path pathinfo
+ $path = $req->getPathInfo();
+
+ //Remove script name
+ $path = str_replace($request->getScriptName(), '', $path);
+
+ //Try with login path path
+ try {
+ //Save old context
+ $oldContext = $this->router->getContext();
+
+ //Force clean context
+ //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
+ //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
+ $this->router->setContext(new RequestContext());
+
+ //Retrieve route matching path
+ $route = $this->router->match($path);
+
+ //Reset context
+ $this->router->setContext($oldContext);
+
+ //Clear old context
+ unset($oldContext);
+
+ //Set login route
+ if (!empty($route['_route'])) {
+ //Set login route
+ $login = $route['_route'];
+ }
+ //No route matched
+ } catch (ResourceNotFoundException $e) {
+ throw new \UnexpectedValueException(sprintf('The "login_path" path "%s" must match a route', $this->options['login_path']), $e->getCode(), $e);
+ }
+ //With route
+ } else {
+ //Try with login path route
+ try {
+ //Retrieve route matching path
+ $path = $this->router->generate($loginPath);
+
+ //Set login route
+ $login = $loginPath;
+ //No route found
+ } catch (RouteNotFoundException $e) {
+ throw new \UnexpectedValueException(sprintf('The "login_path" route "%s" must match a route name', $this->options['login_path']), $e->getCode(), $e);
+ //Ignore missing or invalid parameter
+ //XXX: useless or would not work ?
+ } catch (MissingMandatoryParametersException|InvalidParameterException $e) {
+ //Set login route
+ $login = $loginPath;
+ }
+ }
+ }
+
+ //Without always_use_default_target_path
+ if (empty($this->options['always_use_default_target_path'])) {
+ //With _target_path
+ if ($targetUrl = ParameterBagUtils::getRequestParameterValue($request, $this->options['target_path_parameter'])) {
+ //Set target url
+ $url = $targetUrl;
+
+ //Return redirect to url response
+ return new RedirectResponse($url, 302);
+ //With session and target path in session
+ } elseif (
+ !empty($this->providerKey) &&
+ ($session = $request->getSession()) &&
+ ($targetUrl = $this->getTargetPath($session, $this->providerKey))
+ ) {
+ //Remove session target path
+ $this->removeTargetPath($session, $this->providerKey);
+
+ //Set target url
+ $url = $targetUrl;
+
+ //Return redirect to url response
+ return new RedirectResponse($url, 302);
+ //Extract and process referer
+ } elseif ($this->options['use_referer'] && ($targetUrl = $request->headers->get('referer'))) {
+ //Create referer request instance
+ $req = Request::create($targetUrl);
+
+ //Get referer path
+ $path = $req->getPathInfo();
+
+ //Get referer query string
+ $query = $req->getQueryString();
+
+ //Remove script name
+ $path = str_replace($request->getScriptName(), '', $path);
+
+ //Try with referer path
+ try {
+ //Save old context
+ $oldContext = $this->router->getContext();
+
+ //Force clean context
+ //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
+ //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
+ $this->router->setContext(new RequestContext());
+
+ //Retrieve route matching path
+ $route = $this->router->match($path);
+
+ //Reset context
+ $this->router->setContext($oldContext);
+
+ //Clear old context
+ unset($oldContext);
+
+ //With differing route from login one
+ if (($name = $route['_route']) != $login) {
+ //Remove route and controller from route defaults
+ unset($route['_route'], $route['_controller'], $route['_canonical_route']);
+
+ //Set url to generated one from referer route
+ $url = $this->router->generate($name, $route);
+
+ //Return redirect to url response
+ return new RedirectResponse($url, 302);
+ }
+ //No route matched
+ } catch (ResourceNotFoundException $e) {
+ //Unset target url, route and name
+ unset($targetUrl, $route, $name);
+ }
+ }
+ }
+
+ //With default target path option
+ if (!empty($defaultPath = $this->options['default_target_path'])) {
+ //With path
+ if ($defaultPath[0] == '/') {
+ //Create login path request instance
+ $req = Request::create($defaultPath);
+
+ //Get login path pathinfo
+ $path = $req->getPathInfo();
+
+ //Remove script name
+ $path = str_replace($request->getScriptName(), '', $path);
+
+ //Try with login path path
+ try {
+ //Save old context
+ $oldContext = $this->router->getContext();
+
+ //Force clean context
+ //XXX: prevent MethodNotAllowedException on GET only routes because our context method is POST
+ //XXX: see vendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php +42
+ $this->router->setContext(new RequestContext());
+
+ //Retrieve route matching path
+ $route = $this->router->match($path);
+
+ //Reset context
+ $this->router->setContext($oldContext);
+
+ //Clear old context
+ unset($oldContext);
+
+ //Without login route name
+ if (($name = $route['_route']) != $login) {
+ //Remove route and controller from route defaults
+ unset($route['_route'], $route['_controller'], $route['_canonical_route']);
+
+ //Generate url
+ $url = $this->router->generate($name, $route);
+
+ //Return redirect to url response
+ return new RedirectResponse($url, 302);
+ //With logout route name
+ } else {
+ //Unset default path, name and route
+ unset($defaultPath, $name, $route);
+ }
+ //No route matched
+ } catch (ResourceNotFoundException $e) {
+ throw \Exception('', $e->getCode(), $e);
+ //Unset default path, name and route
+ unset($defaultPath, $name, $route);
+ }
+ //Without login route name
+ } elseif ($defaultPath != $login) {
+ //Try with login path route
+ try {
+ //Retrieve route matching path
+ $url = $this->router->generate($defaultPath);
+
+ //Return redirect to url response
+ return new RedirectResponse($url, 302);
+ //Route not found, missing parameter or invalid parameter
+ } catch (RouteNotFoundException|MissingMandatoryParametersException|InvalidParameterException $e) {
+ //Unset default path and url
+ unset($defaultPath, $url);
+ }
+ }
+ }
+
+ //Throw exception
+ throw new \UnexpectedValueException('You must provide a valid login target url or route name');
+ }
+}