[CORE][SECURITY] Replicate 'next' form submission feature on login form

This commit is contained in:
Hugo Sales 2022-03-05 14:46:05 +00:00 committed by Diogo Peralta Cordeiro
parent 46c91a4b39
commit 9a0c74cb0c
No known key found for this signature in database
GPG Key ID: 18D2D35001FBFAB0
4 changed files with 30 additions and 4 deletions

View File

@ -22,9 +22,11 @@ declare(strict_types = 1);
namespace App\Security; namespace App\Security;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
use App\Core\Log;
use App\Core\Router\Router; use App\Core\Router\Router;
use App\Entity\LocalUser; use App\Entity\LocalUser;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\NoSuchActorException; use App\Util\Exception\NoSuchActorException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException; use App\Util\Exception\ServerException;
@ -32,6 +34,7 @@ use App\Util\Nickname;
use Stringable; use Stringable;
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;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
@ -158,9 +161,23 @@ class Authenticator extends AbstractFormLoginAuthenticator implements Authentica
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) { if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath); return new RedirectResponse($targetPath);
} elseif (!\is_null($next = $request->request->get('_next') ?? $request->query->get('next'))) {
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) {
$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);
}
} else {
return new RedirectResponse(url: Router::url('root'));
} }
return new RedirectResponse(Router::url('root'));
} }
public function authenticate(Request $request): PassportInterface public function authenticate(Request $request): PassportInterface

View File

@ -151,7 +151,7 @@ abstract class Common
public static function ensureLoggedIn(): LocalUser public static function ensureLoggedIn(): LocalUser
{ {
if (($user = self::user()) == null) { if (($user = self::user()) == null) {
throw new NoLoggedInUser(); throw new NoLoggedInUser(self::getRequest());
// TODO Maybe redirect to login page and back // TODO Maybe redirect to login page and back
} else { } else {
return $user; return $user;

View File

@ -21,6 +21,8 @@ declare(strict_types = 1);
namespace App\Util\Exception; namespace App\Util\Exception;
use Symfony\Component\HttpFoundation\Request;
/** /**
* No user logged in * No user logged in
* *
@ -31,6 +33,10 @@ namespace App\Util\Exception;
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class NoLoggedInUser extends ClientException class NoLoggedInUser extends RedirectException
{ {
public function __construct(Request $request)
{
parent::__construct('security_login', ['next' => $request->getRequestUri()]);
}
} }

View File

@ -41,6 +41,9 @@
<input type="checkbox" name="_remember_me" id="inputRememberMe"> <input type="checkbox" name="_remember_me" id="inputRememberMe">
</span> </span>
{% if app.request.query.has('next') %}
<input type="hidden" name="_next" value="{{ app.request.query.get('next') }}">
{% endif %}
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}"> <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
<div> <div>
<button id="signIn" class="btn btn-lg btn-primary" type="submit">Sign in</button> <button id="signIn" class="btn btn-lg btn-primary" type="submit">Sign in</button>