методы фильтрации в контроллере - PullRequest
2 голосов
/ 21 марта 2012

Я хочу создать фильтр для моих действий добавления, обновления и удаления в моих контроллерах, чтобы автоматически проверять, были ли они

  1. вызваны в POST, в отличие от GET или какого-либо другого метода
  2. и имеют идентификаторы pageInstanceID, которые я установил в формах на своих представлениях
    • защищает от xss
    • защищает от двойной отправки формы
      • от кнопки отправки doubleнажмите
      • с кнопки «Назад», нажатой после отправки
      • с URL-адреса, сохраняемого или добавленного в закладки

В настоящее время ярасширенный \ lithium \ action \ Controller с помощью AppController и мои действия добавления, обновления и удаления, определенные там.У меня также есть логическая функция в моем AppController, которая проверяет, находятся ли соответствующие pageInstanceID в сеансе или нет.

Ниже мой код:

public function isNotPostBack() {

    // pull in the session
    $pageInstanceIDs = Session::read('pageInstanceIDs');
    $pageInstanceID = uniqid('', true);
    $this->set(compact('pageInstanceID'));
    $pageInstanceIDs[] = $pageInstanceID;
    Session::write('pageInstanceIDs', $pageInstanceIDs);

    // checks if this is a save operation
    if ($this->request->data){

        $pageInstanceIDs = Session::read('pageInstanceIDs');
        $pageIDIndex = array_search($this->request->data['pageInstanceID'], $pageInstanceIDs);

        if ($pageIDIndex !== false) {
            // remove the key
            unset($pageInstanceIDs[$pageIDIndex]);
            Session::write('pageInstanceIDs', $pageInstanceIDs);

            return true;

        }
        else
            return false;

    } else {
        return true;
    }

}

public function add() {
    if (!$this->request->is('post') && exist($this->request->data())) {
        $msg = "Add can only be called with http:post.";
        throw new DispatchException($msg);
    }

}

Затем в моих контроллерах я наследую от AppController иВыполните действие следующим образом:

public function add() {
    parent::add();
    if (parent::isNotPostBack()){
        //do work

    }
    return $this->render(array('layout' => false));

}

, который обеспечит, чтобы форма использовала POST и была , а не дважды отправлена ​​ (кнопка возврата или щелчок счастливыми пользователями).Это также помогает защитить от XSS.

Я знаю, что для этого есть плагин, но я хочу реализовать его в качестве фильтра, чтобы методы моего контроллера были чище.Выполненный таким образом, единственный код в моих действиях - это рабочая часть // do и оператор return.

Ответы [ 4 ]

2 голосов
/ 21 марта 2012

Прежде всего, используйте встроенную защиту CSRF (XSRF).

Класс RequestToken создает криптографически защищенные токены и ключи, которые можно использовать для проверки подлинности клиентских запросов.

- http://li3.me/docs/lithium/security/validation/RequestToken

Проверьте токен CSRF следующим образом:

if ($this->request->data && !RequestToken::check($this->request)) {
    /* do your stuff */
}

Вы можете даже проверить метод HTTP, используемый с помощью is()

$this->request->is('post');

Проблема фильтров (для этого варианта использования) заключается в том, что они очень универсальны.Поэтому, если вы не хотите писать все свои действия в виде фильтруемого кода (который может быть болезненным и излишним), вам нужно будет найти способ определить, какой метод блокирует что, и отфильтровать Dispatcher::_call.

2 голосов
/ 21 марта 2012

Вероятно, вам следует начать с фильтра на lithium\action\Dispatcher::run(), здесь приведен псевдокод.Ничего не могу поделать, не увидев ваш parent::isNotPostBack() метод, но это должно привести вас на правильный путь.

<?php
use lithium\action\Dispatcher;

Dispatcher::applyFilter('run', function($self, $params, $chain) {
    $request = $params['request'];

    // Request method is in $request->method
    // Post data is in $request->data

    if($not_your_conditions) {
        return new Response(); // set up your custom response
    }

    return $chain->next($self, $params, $chain); // to continue on the path of execution
});
0 голосов
/ 03 апреля 2013

Я реализовал нечто подобное в недавнем проекте, подклассифицировав \lithium\action\Controller как app\controllers\ApplicationController (абстрактный) и применив фильтры к invokeMethod(), так как именно так диспетчер вызывает методы действия. Вот соответствующий кусок:

namespace app\controllers;

class ApplicationController extends \lithium\action\Controller {
    /**
     * Essential because you cannot invoke `parent::invokeMethod()` from within the closure passed to `_filter()`... But it makes me sad.
     *
     * @see \lithium\action\Controller::invokeMethod()
     *
     * @param string $method to be invoked with $arguments
     * @param array $arguments to pass to $method
     */
    public function _invokeMethod($method, array $arguments = array()) {
        return parent::invokeMethod($method, $arguments);
    }

    /**
     * Overridden to make action methods filterable with `applyFilter()`
     *
     * @see \lithium\action\Controller::invokeMethod()
     * @see \lithium\core\Object::applyFilter()
     *
     * @param string $method to be invoked with $arguments
     * @param array $arguments to pass to $method
     */
    public function invokeMethod($method, array $arguments = array()) {
        return $this->_filter(__METHOD__, compact('method', 'arguments'), function($self, $params){
            return $self->_invokeMethod($params['method'], $params['arguments']);
        });
    }
}

Затем вы можете использовать applyFilter() внутри _init() для запуска фильтров вашего метода. Вместо проверки $method в каждом фильтре вы можете изменить _filter(__METHOD__, . . .) на _filter($method, . . .), но мы решили оставить более общий фильтр.

0 голосов
/ 21 марта 2012

Для защиты от CSRF я использую что-то похожее на предложение Греута.

У меня есть это в моем extensions/action/Controller.php

protected function _init() {
    parent::_init();

    if ($this->request->is('post') ||
        $this->request->is('put') ||
        $this->request->is('delete')) {
        //on add, update and delete, if the security token exists, we will verify the token
        if ('' != Session::read('security.token') && !RequestToken::check($this->request)) {
            RequestToken::get(array('regenerate' => true));
            throw new DispatchException('There was an error submitting the form.');
        }
    }
}

Конечно, это означает, что вам также необходимо добавить следующее в начало вашего файла:

use \lithium\storage\Session;
use lithium\security\validation\RequestToken;
use lithium\action\DispatchException;

При этом мне не нужно повторно проверять CSRF.

...