* * 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 router $this->router = $router; //Set options $this->setOptions($options); } /** * 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'); } }