[CORE][Router][Form] Add Router::sanitizeLocalURL and use it in Form::forceRedirect

This commit is contained in:
Hugo Sales 2022-03-06 11:50:49 +00:00
parent f540711948
commit 9a9eed1457
No known key found for this signature in database
GPG Key ID: 7D0C7EAFC9D835A0
3 changed files with 30 additions and 14 deletions

View File

@ -47,7 +47,6 @@ use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface as SymfFormInterface; use Symfony\Component\Form\FormInterface as SymfFormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/** /**
* This class converts our own form representation to Symfony's * This class converts our own form representation to Symfony's
@ -221,18 +220,14 @@ abstract class Form
public static function forceRedirect(SymfFormInterface $form, Request $request): RedirectResponse public static function forceRedirect(SymfFormInterface $form, Request $request): RedirectResponse
{ {
$next = $form->get('_next')->getData(); $next = $form->get('_next')->getData();
try { $url = Router::sanitizeLocalURL($next, ['next' => false]);
if ($pos = mb_strrpos($next, '#')) { if (!\is_null($url)) {
$fragment = mb_substr($next, $pos); return new RedirectResponse(url: $url);
$next = mb_substr($next, 0, $pos); } else {
}
Router::match($next);
return new RedirectResponse(url: $next . ($fragment ?? ''));
} catch (ResourceNotFoundException $e) {
$user = Common::user(); $user = Common::user();
$user_id = !\is_null($user) ? $user->getId() : '(not logged in)'; $user_id = !\is_null($user) ? $user->getId() : '(not logged in)';
Log::warning("Suspicious activity: User with ID {$user_id} submitted a form where the `_next` parameter is not a valid local URL ({$next})"); Log::warning("Suspicious activity: User with ID {$user_id} submitted a form where the `_next` parameter is not a valid local URL ({$next})");
throw new ClientException(_m('Invalid form submission'), $e); throw new ClientException(_m('Invalid form submission'), previous: $e);
} }
} }
} }

View File

@ -33,6 +33,8 @@ declare(strict_types = 1);
namespace App\Core\Router; namespace App\Core\Router;
use App\Core\Log; use App\Core\Log;
use App\Util\Common;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Router as SymfonyRouter; use Symfony\Component\Routing\Router as SymfonyRouter;
@ -89,6 +91,26 @@ abstract class Router
return self::$router->generate($id, $args, $type); return self::$router->generate($id, $args, $type);
} }
public static function sanitizeLocalURL(string $url, array $unset_query_args = []): ?string
{
try {
$parts = parse_url($url);
if ($parts === false || (isset($parts['host']) && $parts['host'] !== Common::config('site', 'server'))) {
return null;
}
self::match($parts['path']);
if ($unset_query_args !== [] && isset($parts['query'])) {
$args = [];
parse_str($parts['query'], $args);
$args = array_diff_key($args, $unset_query_args);
$parts['query'] = http_build_query($args);
}
return $parts['path'] . (empty($parts['query']) ? '' : ('?' . $parts['query'])) . (empty($parts['fragment']) ? '' : ('#' . $parts['fragment']));
} catch (ResourceNotFoundException) {
return null;
}
}
/** /**
* function match($url) throws Symfony\Component\Routing\Exception\ResourceNotFoundException * function match($url) throws Symfony\Component\Routing\Exception\ResourceNotFoundException
*/ */

View File

@ -148,11 +148,10 @@ abstract class Common
return self::ensureLoggedIn()->getId(); return self::ensureLoggedIn()->getId();
} }
public static function ensureLoggedIn(): LocalUser public static function ensureLoggedIn(?Request $request = null): LocalUser
{ {
if (($user = self::user()) == null) { if (\is_null($user = self::user())) {
throw new NoLoggedInUser(self::getRequest()); throw new NoLoggedInUser($request ?? self::getRequest());
// TODO Maybe redirect to login page and back
} else { } else {
return $user; return $user;
} }