After a thorough search im not seeing the problem.
I have a form im submitting, which isnt tied to an object, its just an emailer form. I want to validate the data. according to the docs the alternative way is to do this.
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
$builder
->add('firstName', 'text', array(
'constraints' => new Length(array('min' => 3)),
))
->add('lastName', 'text', array(
'constraints' => array(
new NotBlank(),
new Length(array('min' => 3)),
),
))
;
now thats done, but i CANNOT call $form->isValid
on this in any shape or form (pun intended), because even though it passes the constraint violations, something seemingly invisible is still causing it to return invalid.
i feel like i might need to extract the form from the post first, and pass that through isValid() but i cant be sure.
heres my method code
/**
* @Route("/share", name="email_share")
* @Method({"POST"})
* @return \Symfony\Component\HttpFoundation\Response
*/
public function shareAction( Request $request, array $lessons){
if(!$lessons || !is_array($lessons))
{
throw new HttpException(404, "Whoops! shareAction didnt get the lessons");
}
//set up our form defaults here
$defaultData = array('comment' => 'Type your comment here');
//build the form here
$form = $this->createFormBuilder($defaultData)
->setAction($this->generateUrl('email_share'))
->add("emails", 'email', array(
'label' => "Recipient's email address (separate with a comma)",
'constraints' => array(
new Length(array('min' => 6, 'max' => 2040)),
new NotBlank(),
),
))
->add('comment', 'textarea', array(
'label' => "Leave a comment",
))
->add('name', 'text', array(
'label' => "Your name",
'constraints' => array(
new Length(array('min' => 3), 'max' => 254)),
new NotBlank(),
),
))
->add('email', 'email', array(
'label' => "Your email address",
'constraints' => array(
new Length(array('min' => 6, 'max' => 254)),
new NotBlank(),
),
))
->add('copy', 'checkbox', array(
'label' => "Send me a copy",
'required' => false,
))
->add('cancel', 'submit', array(
'label' => "Cancel",
))
->add('save', 'submit', array(
'label' => "Email Resources",
))
->getForm();
if ($this->getRequest()->isMethod('POST')) {
//data is already validated by constraints added when the form was created since we are not attaching this particular form to any object
$form->handleRequest($request);
//alternatively (makes no differene from the former)
//$form->submit($request->request->get($form->getName()));
if ($form->isValid())
{
//have YET to see this
echo 'valid';
exit;
}
else{
//echo 'not fuckin valie, WHY?';
//exit;
// get a ConstraintViolationList
$errors = $this->get('validator')->validate( $form );
$result = '';
//nothing returns here when form is valid against constraints, its just empty
echo $errors;
// iterate on it
foreach( $errors as $error )
{
$error->getPropertyPath() : the field that caused the error
$error->getMessage() : the error message
}
}
$data = $form->getData();
return $this->emailUser($data);
}
return $this->render('ResourceBundle:Default:resources.html.twig', array(
'form' => $form->createView(),
));
}
This is how im posting the data
function postForm($form, callback) {
/*
* Get all form values
*/
var values = {};
$.each($form.serializeArray(), function (i, field) {
values[field.name] = field.value;
});
/*
* Throw the form values to the server!
*/
$.ajax({
type: 'POST',
url: '/share',
data: values,
success: function (data) {
callback(data);
}
});
}
$(document).ready(function () {
//bind an event to submit on 'email resources' button
$('div#share form').submit(function (e) {
//disable symfonys default submit button hehaviour
e.preventDefault();
postForm($(this), function (response) {
//replace html here
// Is this where im going wrong? Do i need to replace the form here?
});
});
});
EDIT: Here is the pertinent portion of the main template code that calls the action in the first place
<div id="share" class="hidden" >
<h2>Share Resources</h2>
{% render url('email_share') %}
</div>
here is the form template code thats rendered in the shareAction (in its entirety currently)
{{ form(form) }}
was
{% if form | default %}
{{ form(form) }}
{% endif %}
{% if mail_response | default %}
{{ dump(mail_response) }}
{% endif %}
The hidden token input portion of the form
<input id="form__token" class="form-control" type="hidden" value="8QWLo8xaPZFCKHBJbuc6CGNIcfmpWyT-yFdWScrsiJs" name="form[_token]">
The two underscores worry me a bit (form__token)
EDIT2: The problem is in the CSRF token somewhere. it could be the form input name, the token itself is already expired, or something else.
I pretty much narrowed it down by constructing my own form module like this
//set up our form defaults here
$defaultData = array('comment' => 'Type your comment here');
$session = new Session();
$secret = '123xyz';
$vendorDir = realpath(__DIR__ . '/../vendor');
$vendorFormDir = $vendorDir . '/symfony/form/Symfony/Component/Form';
$vendorValidatorDir =
$vendorDir . '/symfony/validator/Symfony/Component/Validator';
// create the validator - details will vary
$validator = Validation::createValidator();
$formFactory = Forms::createFormFactoryBuilder()
->addExtension(new HttpFoundationExtension())
//->addExtension(new CsrfExtension(new SessionCsrfProvider($session, $secret)))
->addExtension(new ValidatorExtension($validator))
->getFormFactory();
//build the form here
$form = $formFactory->createBuilder('form', $defaultData)
->setAction($this->generateUrl('email_share'))
->setMethod('POST')
->add("emails", 'email', array(
//......
//same as above for the rest......
The form FINALLY passes validation like this, and when i uncomment the line ->addExtension(new CsrfExtension(new SessionCsrfProvider($session, $secret))) i get the same error as i did before, that the CSRF token is invalid.
To me, this is pretty much pointing to somewhere in this module, or im not calling something, or extending something right, or the javascript is returning a form that is older than the CSRF module is expecting, or the hidden token form input has a name that is other than that what the CSRF module is looking for. I dont know enough about symfony's internals to diagnose this, this is why i come here for help. Does anybody see a potential issue?
EDIT:3 i feel like i shouldnt be using isValid(), as mentioned, i am not passing an object, im passing an array. see this URL http://symfony.com/doc/current/book/validation.html#validating-values-and-arrays. Im trying to figure out how to properly check against the constraints, and im thinking isValid() is NOT the way to go after all, or else im missing something fundamental.. I just cant figure if i only check against constraint errors, how can i use the CSRFprotection still, or is that only for objects or something?? Do i need to pass this in manually since im not using an object?
EDIT 4: It looks like i might have uncovered the crux of the problem, yet i cant figure out how to solve it yet.
on this file Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider i put some output to track the token, and it appears that the token is regenerating for the comparison, which seems like IT SHOULD NOT be the case I would expect that it would compare the passed down token to one in memory, but in fact, its being generated twice, once for the form, then once again for the comparison.
At first i suspected it was possible that the browser and the ajax are running from two different sessions, and a mismatch can be caused by this because i was using SessionCsrfProvider()
, but after switching to ->addExtension(new CsrfExtension(new DefaultCsrfProvider($secret))) i had the same problem.
Is this a bug, am i going crazy, or am i missing something as simple as the form id in the building of the form or something?
heres the code, and the results i found from that code.
//Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider
public function isCsrfTokenValid($intention, $token)
{
echo '<pre>Warning, Symfony\Component\Form\Extension\Csrf\CsrfProvider\isCsrfTokenValid';
echo'<br>, here is out token handed down to compare<br>';
var_dump($token);
echo '<br>the new generated token thats being compared to is<br>';
var_dump($this->generateCsrfToken($intention));
echo '</pre>';
return $token === $this->generateCsrfToken($intention);
}
returns
//the form array(6) { ["emails"]=> string(19) "email@email.net"
["comment"]=> string(2) "yo" ["name"]=> string(5) "me"
["email"]=> string(19) "email@email.net" ["copy"]=>
string(1) "1" ["_token"]=> string(40) "a11e10eb323f7a4d19577e6d07e68be951ceb569" }Warning, Symfony\Component\Form\Extension\Csrf\CsrfProvider\isCsrfTokenValid , here is out token handed down to compare string(40) "a11e10eb323f7a4d19577e6d07e68be951ceb569"
the new generated token thats being compared to is string(40) "e83cdf94b15e63e822520b62402eb66e0b1f03d3"
The CSRF token is invalid. Please try to resubmit the form.
Blockquote
EDIT 5: the problem has been traced to here, look at this code in the DefaultCsrfProvider
public function generateCsrfToken($intention)
{
return sha1($this->secret.$intention.$this->getSessionId());
}
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($intention);
}
The token can never be valid during an ajax call, unless a param is set in the generateCsrfToken() token method to allow passing of the session, to which you would want to pass that via ajax, like this
public function generateCsrfToken($intention, $session)
{
if(!$session)
{
$session = $this->getSessionId()
}
return sha1($this->secret.$intention.$session);
}
which i would think would completely reduce teh security of the whole idea of the CSRF in the first place.
is there another provide i can use for ajax calls in within the symfony framework? If were depending on the sesion, this pretty much leaves out both the SessionCsrfProvider class and the DefaultCsrfProvider class to process this, unless im missing something very obvious... should i just grab, pass, then reset the session on the ajax call????
Ok, after i figured this out thus far, i just found this post Symfony CSRF and Ajax ill see if i can make heads or tails from it.