CakePHP - Controller testing failed because of Sec

2019-07-28 03:02发布

问题:

I am trying to test controller methods (add, edit, ...) that use Security component.

ContactsController

public function initialize() {
    $this->loadComponent('Security');
}

public function add() {
    $contact = $this->Contacts->newEntity();
    if ($this->request->is('post')) {
        $contact = $this->Contacts->patchEntity($contact, $this->request->data);
        if ($this->Contacts->save($contact)) {
            $this->Flash->success(__d('contact_manager', 'The contact has been saved.'));
            return $this->redirect(['action' => 'index']);
        } else {
            $this->Flash->error(__d('contact_manager', 'The contact could not be saved. Please, try again.'));
        }
    }
    $this->set(compact('contact'));
    $this->set('_serialize', ['contact']);
}

ContactsControllerTest

public function testAdd() {
    $data = $this->_getData();
    $this->post('/contacts/add', $data);
    $this->assertResponseSuccess();

    $query = $this->Contacts->find()->where([
        'Profiles.lastname' => $data['profile']['lastname'],
        'Profiles.firstname' => $data['profile']['firstname']
    ]);
    $this->assertEquals(1, $query->count());
}

protected function _getData() {
    $data = [
        'id' => '',
        'organization_id' => 2,
        'profile_id' => '',
        'profile' => [
            'lastname' => 'Demo',
            'firstname' => 'Demo',
            'gender' => 'f',
            'birthday' => '1990-05-20',
            'email' => 'demo@demo.com',
            'phone' => '0102030405',
            'phone_mobile' => '0607080900'
        ]
    ];
    return $data;
}

testAdd() always failed because the request is black-holed (with 'Auth' indicator), but add() works well in browser.

回答1:

That is to be expected, as you're not sending the necessary security token, which is what the security component requires.

Just look at your generated form, it will contain hidden inputs for the _Token field, with the subkeys fields and unlocked, where fields will contain a hash and possibly the names of locked fields, and unlocked holds the names of unlocked fields.

Just add the token data to your request and everything should be fine. Here's an example

$data = [
    'id' => '',
    'organization_id' => 2,
    'profile_id' => '',
    'profile' => [
        'lastname' => 'Demo',
        // ...
    ],
    '_Token' => [
        'fields' => 'e87e3ad9579abcd289ccec2a7a42065b338cacd0%3Aid'
        'unlocked' => ''
    ]
];

Note that the unlocked key must be present, even if it doesn't hold any data!

You should be able to simply copy the token values from the generated form, and if you're interested in how the token is being generated and validated, have a look at FormHelper::secure(), FormHelper::_secure(), and SecurityComponent::_validatePost().

See also Cookbook > Controllers > Components > Security



回答2:

Since Cake 3.1.2 the best way is to add

$this->enableCsrfToken();
$this->enableSecurityToken();

to your TestFunction.

Doku: https://book.cakephp.org/3.0/en/development/testing.html#testing-actions-protected-by-csrfcomponent-or-securitycomponent