Skip to content

Commit

Permalink
[Form] Skip CSRF validation on form when POST max size is exceeded
Browse files Browse the repository at this point in the history
  • Loading branch information
jameshalsall authored and fabpot committed Aug 15, 2016
1 parent 6299312 commit aefba0d
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 12 deletions.
12 changes: 10 additions & 2 deletions Extension/Csrf/EventListener/CsrfValidationListener.php
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Translation\TranslatorInterface;
Expand Down Expand Up @@ -68,14 +69,19 @@ class CsrfValidationListener implements EventSubscriberInterface
*/
private $translationDomain;

/**
* @var ServerParams
*/
private $serverParams;

public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SUBMIT => 'preSubmit',
);
}

public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null)
public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
{
if ($tokenManager instanceof CsrfProviderInterface) {
$tokenManager = new CsrfProviderAdapter($tokenManager);
Expand All @@ -89,13 +95,15 @@ public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage,
$this->errorMessage = $errorMessage;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
$this->serverParams = $serverParams ?: new ServerParams();
}

public function preSubmit(FormEvent $event)
{
$form = $event->getForm();
$postRequestSizeExceeded = $form->getConfig()->getMethod() === 'POST' && $this->serverParams->hasPostMaxSizeBeenExceeded();

if ($form->isRoot() && $form->getConfig()->getOption('compound')) {
if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) {
$data = $event->getData();

if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) {
Expand Down
12 changes: 10 additions & 2 deletions Extension/Csrf/Type/FormTypeCsrfExtension.php
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Util\ServerParams;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
Expand Down Expand Up @@ -55,7 +56,12 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
*/
private $translationDomain;

public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null)
/**
* @var ServerParams
*/
private $serverParams;

public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
{
if ($defaultTokenManager instanceof CsrfProviderInterface) {
$defaultTokenManager = new CsrfProviderAdapter($defaultTokenManager);
Expand All @@ -68,6 +74,7 @@ public function __construct($defaultTokenManager, $defaultEnabled = true, $defau
$this->defaultFieldName = $defaultFieldName;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
$this->serverParams = $serverParams;
}

/**
Expand All @@ -89,7 +96,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())),
$options['csrf_message'],
$this->translator,
$this->translationDomain
$this->translationDomain,
$this->serverParams
))
;
}
Expand Down
5 changes: 1 addition & 4 deletions Extension/HttpFoundation/HttpFoundationRequestHandler.php
Expand Up @@ -73,10 +73,7 @@ public function handleRequest(FormInterface $form, $request = null)
// Mark the form with an error if the uploaded size was too large
// This is done here and not in FormValidator because $_POST is
// empty when that error occurs. Hence the form is never submitted.
$contentLength = $this->serverParams->getContentLength();
$maxContentLength = $this->serverParams->getPostMaxSize();

if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
// Submit the form, but don't clear the default values
$form->submit(null, false);

Expand Down
5 changes: 1 addition & 4 deletions NativeRequestHandler.php
Expand Up @@ -81,10 +81,7 @@ public function handleRequest(FormInterface $form, $request = null)
// Mark the form with an error if the uploaded size was too large
// This is done here and not in FormValidator because $_POST is
// empty when that error occurs. Hence the form is never submitted.
$contentLength = $this->serverParams->getContentLength();
$maxContentLength = $this->serverParams->getPostMaxSize();

if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
// Submit the form, but don't clear the default values
$form->submit(null, false);

Expand Down
22 changes: 22 additions & 0 deletions Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener;

use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
Expand Down Expand Up @@ -72,4 +73,25 @@ public function testStringFormData()
// Validate accordingly
$this->assertSame($data, $event->getData());
}

public function testMaxPostSizeExceeded()
{
$serverParams = $this
->getMockBuilder('\Symfony\Component\Form\Util\ServerParams')
->disableOriginalConstructor()
->getMock()
;

$serverParams
->expects($this->once())
->method('hasPostMaxSizeBeenExceeded')
->willReturn(true)
;

$event = new FormEvent($this->form, array('csrf' => 'token'));
$validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Error message', null, null, $serverParams);

$validation->preSubmit($event);
$this->assertEmpty($this->form->getErrors());
}
}
13 changes: 13 additions & 0 deletions Util/ServerParams.php
Expand Up @@ -25,6 +25,19 @@ public function __construct(RequestStack $requestStack = null)
$this->requestStack = $requestStack;
}

/**
* Returns true if the POST max size has been exceeded in the request.
*
* @return bool
*/
public function hasPostMaxSizeBeenExceeded()
{
$contentLength = $this->getContentLength();
$maxContentLength = $this->getPostMaxSize();

return $maxContentLength && $contentLength > $maxContentLength;
}

/**
* Returns maximum post size in bytes.
*
Expand Down

0 comments on commit aefba0d

Please sign in to comment.