[CORE][FORM][FormTypeNonceExtension] Add a nonce to all forms with a CSRF token
This commit is contained in:
parent
b7e4f79ccc
commit
de8eab2cf8
68
src/Core/Form/FormTypeNonceExtension.php
Normal file
68
src/Core/Form/FormTypeNonceExtension.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace App\Core\Form;
|
||||||
|
|
||||||
|
use App\Core\Cache;
|
||||||
|
use function App\Core\I18n\_m;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Form\AbstractTypeExtension;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\FormError;
|
||||||
|
use Symfony\Component\Form\FormEvent;
|
||||||
|
use Symfony\Component\Form\FormEvents;
|
||||||
|
use Symfony\Component\Form\Util\ServerParams;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||||
|
|
||||||
|
class FormTypeNonceExtension extends AbstractTypeExtension implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public CsrfTokenManagerInterface $token_manager,
|
||||||
|
public ServerParams $serverParams = new ServerParams(),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getExtendedTypes(): iterable
|
||||||
|
{
|
||||||
|
return [FormType::class];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder->addEventSubscriber($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [FormEvents::PRE_SUBMIT => 'preSubmit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults(['nonce_protection' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before the form gets to the controller, piggy-back on the CSRF token and use it as a nonce, ensuring it was only submitted once
|
||||||
|
*/
|
||||||
|
public function preSubmit(FormEvent $event)
|
||||||
|
{
|
||||||
|
$form = $event->getForm();
|
||||||
|
$postRequestSizeExceeded = 'POST' === $form->getConfig()->getMethod() && $this->serverParams->hasPostMaxSizeBeenExceeded();
|
||||||
|
|
||||||
|
if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) {
|
||||||
|
$data = $event->getData();
|
||||||
|
$token_id = $form->getConfig()->getOption('csrf_token_id') ?: ($form->getName() ?: \get_class($form->getConfig()->getType()->getInnerType()));
|
||||||
|
$token_value = \is_string($data['_token'] ?? null) ? $data['_token'] : null;
|
||||||
|
$csrf_token = new CsrfToken($token_id, $token_value);
|
||||||
|
|
||||||
|
if (null === $token_value || !$this->token_manager->isTokenValid($csrf_token) || Cache::incr("nonce:{$token_value}") !== 1) { // TODO add TTL
|
||||||
|
$form->addError(new FormError(_m('Invalid nonce'), null, [], null, $csrf_token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user