setDefault() and clear() functions for form elements don't seem to be working

I'm having problems with CSRF verification on my sites because setDefault and clear functions don't seem to have any effect to the form elements.

The CSRF field is defined in this way (for testing):

$csrf = new Hidden('csrf');
$csrf->setUserOption('type', 'hidden');
$csrf->addValidator(new Identical([
    'value' => $this->security->getSessionToken(),
    'message' => 'CSRF validation failed, expected '. $this->security->getSessionToken()
]));
$csrf->setDefault($this->security->getToken());

$this->add($csrf);

It works successfully on the first submit, but submitting again will fail because the first token gets remembered as the value of the csrf field. Adding $csrf->clear(); changes nothing. I am yet to find a working way of resetting it to the new token. Any help would be appreciated!

Hi, maybe

<?php
unset($csrf) //and then recreate it


804

Hi! That doesn't help because $csrf doesn't exist before the lines above. It is actually created anew when the document reloads, but it forces the $_POST['csrf'] value instead of the one I define. I even tried this right before rendering the view:

$myForm->get('csrf')->setAttribute('value', $this->security->getToken());

It still ignores this and uses the posted one.

Okey, look at this topic: csrf's Problem



7.0k
edited Feb '17

Just a guess: maybe you binding your post request values to form in controller. In that case post request values can override clear(). You should do $myForm->get("csrf")->clear() from controller after binding or don't use bind at all for csrf form.



804

CSRF is not the problem here. I could've taken any field as an example, this one is just simplest because it has to regenerate on every page load.

Here's what happens. I open a form on the page and the CSRF token is "WUJ0ejZwaWFoQk1YVXM5Vk5rdmZIZz09". I click submit, everything works well and I'm back at the form. The newly generated token is "bWsrckZ1NHgvRUJIdGFmeFUrODRMUT09", but the form still displays the old one: "WUJ0ejZwaWFoQk1YVXM5Vk5rdmZIZz09".

Check this out:

// Clear the form element of a posted value ("WUJ0ejZwaWFoQk1YVXM5Vk5rdmZIZz09")
$profileForm->get('csrf')->clear();
// Set new CSRF token ("bWsrckZ1NHgvRUJIdGFmeFUrODRMUT09")
$newToken  = $this->security->getToken();
$profileForm->get('csrf')->setDefault($newToken);
$profileForm->get('csrf')->setAttributes(['value' => $newToken]);

// Echo out what is the element's value
echo $profileForm->get('csrf')->getValue();

// Output: WUJ0ejZwaWFoQk1YVXM5Vk5rdmZIZz09

So clear(), setDefault() and even setAttributes() got completely ignored and I got the old token out. I know these worked before because I used clear() to clear the password fields if user would enter wrong password and the login form would reload. Now the wrong password gets loaded with the page again.



7.0k
edited Feb '17

Try setValue() instead of setDefault()

UPD: sorry I meant setAttributes('value'=>$newToken);



804

You have that im my last message.

$profileForm->get('csrf')->setAttributes(['value' => $newToken]);


7.0k
edited Feb '17

Ok. Just checked how my Form with csrf working:

I don't set value for csrf field neither in Form nor in controller. I set it in a view:

{{ form.render('csrf', ['value': security.getSessionToken()]) }}

And I clear it's value in controller just in case if request is not post request.

So no SetDefaul, no setAttribute and so on. This way we can be sure that form get's new csrf token at rendering time.

UPD: And for validation:

$csrf->addValidator(new Identical([
            'value' => $this->security->getSessionToken(),
            'message' => 'CSRF validation failed'
        ]));


804
edited Feb '17

Ok, so I did this by your suggestion:

{{ form.render('csrf', ['value': security.getSessionToken()]) }}
Token: {{ security.getSessionToken() }}

And here's the output:

Initial form load:

<input type="hidden" id="csrf" name="csrf" value="bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09">
Token: bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09

After clicking Submit for the first time:

<input type="hidden" id="csrf" name="csrf" value="bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09">
Token: WXo2WW1SNnlzRzR5WUc0VytvbnZVZz09

The form just stubbornly refuses to accept the input for value and goes with what was posted. Tested in Firefox and Chrome.



7.0k
edited Feb '17

It's very strange, but please check again if you have something like:

$yourForm->bind($this->request->getPost(),$yourForm);

Possibly somwhere in your controller. It's the only way old value may get there, I can thing of.

Ok, so I did this by your suggestion:

{{ form.render('csrf', ['value': security.getSessionToken()]) }} Token: {{ security.getSessionToken() }}

And here's the output:

Initial form load:

<input type="hidden" id="csrf" name="csrf" value="bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09"> Token: bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09

After clicking Submit for the first time:

<input type="hidden" id="csrf" name="csrf" value="bDkyM0FEOW1mSnI0Y3pXT3FIL3hMUT09"> Token: WXo2WW1SNnlzRzR5WUc0VytvbnZVZz09

The form just stubbornly refuses to accept the input for value and goes with what was posted. Tested in Firefox and Chrome.



804

I do have $profileForm->bind($this->request->getPost(), $profileForm);, how else can I validate the input?

That's why I'm trying to clear the posted value with $profileForm->get("csrf")->clear();. Description of clear() is "Clears every element in the form to its default value". That used to work in phalcon 2, but in phalcon 3.0.3 it seems that clear() does nothing.

The code I'm having trouble with used to work as expected several months ago.

Even the Vökuró, PhalconPHP's tutorial project uses clear to remove the POSTed value. E.g. https://github.com/phalcon/vokuro/blob/master/app/forms/LoginForm.php#L44



7.0k
edited Feb '17

I do have $profileForm->bind($this->request->getPost(), $profileForm);, how else can I validate the input?

You can validate input this way:

$profileForm->isValid($this->request->getPost())

If form is valid assign values from Post straight to Profile's model instance, no need to bind values to form. On the other hand, I agree that bind post and do clear() is more comfy, and it seem not working as expected, sorry I don't know why.



804
edited Feb '17

I do have $profileForm->bind($this->request->getPost(), $profileForm);, how else can I validate the input?

You can validate input this way:

$profileForm->isValid($this->request->getPost())

If form is valid assign values from Post straight to Profile's model instance, no need to bind values to form. On the other hand, I agree that bind post and do clear() is more comfy, and it seem not working as expected, sorry I don't know why.

Good idea, thank you! However, it didn't fix the problem. The fields' default values still get ignored and the posted ones get loaded. I've re-tested with all the clear(), setAttributes() and setDefault() functions. They just get ignored and these two end up outputting different tokens:

{{ form.render('csrf', ['value': security.getSessionToken()]) }}
Got: {{ form.get('csrf').getValue() }} <br>
Expected: {{ security.getSessionToken() }} <br>

Result:

<input id="csrf" name="csrf" value="THc3M2pqUm52WFgranduSkUyWlh4Zz09" type="hidden">
Got: THc3M2pqUm52WFgranduSkUyWlh4Zz09
Expected: ZFduTmkyejJzY3VsWHRCUnZIUE9IZz09 

I believe this is a bug in Phalcon 3.

Edit: I've tested it by removing the whole form's code from controller apart from the initialization and forwarding to the view, then I restored and tested line by line to see when will the posted values start to show up despite the clear. I discovered that the form elements start ignoring the setDefault, setAttributes and clear methods only if binding or validation is executed:

// Either of these will prevent the wanted value to show up
$profileForm->bind($this->request->getPost(), $profileForm);
$profileForm->isValid($this->request->getPost());


7.0k

Going back to the subject, I think it worth creating issue on github so Phalcon team could fix it or propose the workaround/way to use clear() setDefault().