How do you Unit Test a Zend_Form that includes the CSRF form element?
Asked Answered
P

5

6

I'm using the CSRF hidden hash element with Zend_Form and trying to Unit Test the login but don't know how to write a Unit Test to include that element. Looked in the docs and read as many tutorials as I could find. I even delicioused them all, but no one mentions this.

Partite answered 10/7, 2009 at 18:26 Comment(2)
Update: It's a bit kludgy, but I was able to get a testable form because I'm loading my Zend_Form from an .ini file. I split out the csrf section into a testing only section so my tests allow a login without passing through the csrf. Hope this helps someone else. BTW, I think an .ini file is the best place to load your forms from.Partite
The creator of PHPUnit mentioned to me that "End-to-End Testing != Unit Testing", which is true of course. In this case I didn't care about getting the csrf in as much as I wanted to test that I could login and see some element on the resulting page. Not being able to include this element in the test was killing the login...Partite
E
3

The correct hash is stored in the session, and the Hash form element has a Zend_Session_Namespace instance which contains the namespace for the hash.

To unit test the element, you would replace the Zend_Session_Namespace instance in the element (with setSession) with one you create yourself which contains the correct hash (the hash is stored in key "hash")

For further examples you could probably look at the Zend Framework unit tests for the Zend_Form_Element_Hash class. I would assume they have had to deal with this as well.

Extrapolate answered 12/7, 2009 at 19:56 Comment(2)
I feel like there's something wrong when you have to know that much about the implementation of CSRF to write tests for controllers that use it.Elmira
In a more contained case you probably would replace the entire CSRF form element with a mocked instance, then you won't need to deal with the session storage stuffExtrapolate
P
9

Csrf value is generated each time form is rendered. Hidden element of the form gets prefilled with that value. This value also gets stored in session. After submitting form, validation checks if value posted from the form is stored in session, if not then validation fails. It is essential, that form must be rendered during the test (so it can generate the hidden value and store it to session), then we can extract what is the hidden value out of rendered html, and later we can add hidden hash value into our request. Consider this example:

function testAddPageStoreValidData()
{
    // render the page with form 
    $this->dispatch('/form-page');

    // fetch content of the page 
    $html = $this->getResponse()->getBody();

    // parse page content, find the hash value prefilled to the hidden element
    $dom = new Zend_Dom_Query($html);
    $csrf = $dom->query('#csrf')->current()->getAttribute('value');

    // reset tester for one more request
    $this->resetRequest()
         ->resetResponse();

    // now include $csrf value parsed from form, to the next request
    $this->request->setMethod('POST')
                  ->setPost(array('title'=>'MyNewTitle',
                                  'body'=>'Body',
                                  'csrf'=>$csrf));
    $this->dispatch('/form-page');

    // ...
}
Porche answered 22/8, 2010 at 13:14 Comment(4)
This looks promising, however $html = $this->getResponse()->getBody(); is always empty in my case.Alamein
@Alamein don't forget to dispatch the page with the form before asking for a body ;)Fluvial
#as of to date, i performed unit tests using this approach and it works. +1 to Lukas.Edgerton
If the form class is available in tests, you may actually write $form = new MyForm(); $form->render(); instead of dispatching the page twice. It is definitely faster.Socher
E
3

The correct hash is stored in the session, and the Hash form element has a Zend_Session_Namespace instance which contains the namespace for the hash.

To unit test the element, you would replace the Zend_Session_Namespace instance in the element (with setSession) with one you create yourself which contains the correct hash (the hash is stored in key "hash")

For further examples you could probably look at the Zend Framework unit tests for the Zend_Form_Element_Hash class. I would assume they have had to deal with this as well.

Extrapolate answered 12/7, 2009 at 19:56 Comment(2)
I feel like there's something wrong when you have to know that much about the implementation of CSRF to write tests for controllers that use it.Elmira
In a more contained case you probably would replace the entire CSRF form element with a mocked instance, then you won't need to deal with the session storage stuffExtrapolate
R
1

I set an environment variable in my Apache vhost file, which tells the code which server it's running on: development, staging, or production

The line for the vhost file is:

SetEnv SITE_ENV "dev" 

Then I just make my forms react to the appropriate environment:

if($_SERVER['SITE_ENV']!='dev')
{
   $form_element->addValidator($csrf_validator);
}

I use this same technique for lots of stuff. For example, if it IS dev, I redirect all outgoing email to me, etc.

Rigsdaler answered 12/7, 2009 at 19:36 Comment(1)
That's similar to what I ended up doing in the forms.ini file... I don't know why but not a fan of IF statements outside of scaffolding or .ini files for environment specific handling inside the main code. Just seems like a new environment would require looking in a ton of places to fix it up rather than .ini and bootstrap where you expect differences....Partite
K
1

I answered a more recent question similar to this one. I'm putting my answer here as well in case it helps anybody in the future.

I recently found a great way of testing forms with hash elements. This will use a mock object to stub away the hash element and you won't have to worry about it. You won't even have to do a session_start or anything this way. You won't have to 'prerender' the form either.

First create a 'stub' class like so

class My_Form_Element_HashStub extends Zend_Form_Element_Hash
{
    public function __construct(){}
}

Then, add the following to the form somewhere.

class MyForm extends Zend_Form
{

    protected $_hashElement;

    public function setHashElement( Zend_Form_Hash_Element $hash )
    { 
        $this->_hashElement = $hash; 
        return $this; 
    }

    protected function _getHashElement( $name = 'hashElement' )
    { 
        if( !isset( $this->_hashElement )
        {
            if( isset( $name ) )
            {
                $element = new Zend_Form_Element_Hash( $name, 
                                                  array( 'id' => $name ) );
            }
            else
            {
                $element = new Zend_Form_Element_Hash( 'hashElement', 
                                        array( 'id' => 'hashElement' ) );
            }

            $this->setHashElement( $element );
            return $this->_hashElement;
        }
    }

    /**
     * In your init method you can now add the hash element like below
     */
    public function init()
    {
        //other code
        $this->addElement( $this->_getHashElement( 'myotherhashelementname' );
        //other code
    }
}

The set method is there just for testing purposes really. You probably won't use it at all during real use but now in phpunit you can right the following.

class My_Form_LoginTest extends PHPUnit_Framework_TestCase
{

    /**
     *
     * @var My_Form_Login
     */
    protected $_form;
    /**
     *
     * @var PHPUnit_Framework_MockObject_MockObject
     */
    protected $_hash;

    public function setUp()
    {
        parent::setUp();
        $this->_hash = $this->getMock( 'My_Form_Element_HashStub' );

        $this->_form = new My_Form_Login( array(
                    'action'                    => '/',
                    'hashElement'               => $this->_hash
    }

    public function testTrue()
    {   
        //The hash element will now always validate to true
        $this->_hash
             ->expects( $this->any() )
             ->method( 'isValid' )
             ->will( $this->returnValue( true ) );

        //OR if you need it to validate to false
        $this->_hash
             ->expects( $this->any() )
             ->method( 'isValid' )
             ->will( $this->returnValue( true ) );
    }
}

You HAVE to create your own stub. You can't just call the phpunit method getMockObject() because that will directly extend the hash element and the normal hash element does 'evil' stuff in its constructor.

With this method you don't even need to be connected to a database to test your forms! It took me a while to think of this.

If you want, you can push the setHashElement() method ( along with the variable and the get method ) into some FormAbstract base class.

REMEMBER, in phpunit you HAVE to pass the hash element during form construction. If you don't, your init() method will get called before your stub hash can be set with the set method and you'll end up using the regular hash element. You'll know you're using the regular hash element because you'll probably get some session error if you're NOT connected to a database.

Let me know if you find this helpful or if you use it.

Kathrinkathrine answered 26/4, 2012 at 5:18 Comment(0)
S
1

Solution for ZF2 is creating your form in test, and getting value from your csrf form element:

        $form = new  \User\Form\SignupForm('create-user');
        $data = [
            'security' => $form->get('security')->getValue(),
            'email' => '[email protected]',
            'password' => '123456',
            'repeat-password' => '123456',
        ];
        $this->dispatch('/signup', 'POST', $data);
Store answered 6/1, 2015 at 9:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.