[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
Signed by untrusted user: someonewithpc
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\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
/**
* 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
{
$next = $form->get('_next')->getData();
try {
if ($pos = mb_strrpos($next, '#')) {
$fragment = mb_substr($next, $pos);
$next = mb_substr($next, 0, $pos);
}
Router::match($next);
return new RedirectResponse(url: $next . ($fragment ?? ''));
} catch (ResourceNotFoundException $e) {
$url = Router::sanitizeLocalURL($next, ['next' => false]);
if (!\is_null($url)) {
return new RedirectResponse(url: $url);
} else {
$user = Common::user();
$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})");
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;
use App\Core\Log;
use App\Util\Common;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Router as SymfonyRouter;
@ -89,6 +91,26 @@ abstract class Router
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
*/

View File

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