методы фильтрации в контроллере

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

  1. были вызваны в POST, в отличие от GET или какого-либо другого метода
  2. and have the pageInstanceIDs which I set in the forms on my views
    • protects against xss
    • protects against double submission of a form
      • from submit button double click
      • от кнопки «Назад», нажатой после отправки
      • с сохраненного или добавленного в закладки 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 work и оператор return.


person ton.yeung    schedule 21.03.2012    source источник
comment
Я думаю, вы забыли вопрос...   -  person jeroen    schedule 21.03.2012
comment
@jeroen Вопрос в первом предложении, я думаю, это скорее заявление о желании? Я хочу знать, как создать фильтр для методов контроллера.   -  person ton.yeung    schedule 21.03.2012


Ответы (4)


Вероятно, вам следует начать с фильтра 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
});
person Rob    schedule 21.03.2012
comment
это может быть близко, я попытаюсь заставить его работать, сопоставив добавление/обновление/удаление в запросе. - person ton.yeung; 21.03.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.

person greut    schedule 21.03.2012
comment
На самом деле я потратил несколько часов, пытаясь заставить встроенную защиту CRLF работать с сеансом. Код, который я использую сейчас, был добавлен в OP. Общий характер фильтров — причина, по которой мне нравится этот подход. Если я не ошибаюсь, я могу фильтровать определенные методы (действия: добавить, удалить, обновить). Которые одинаковы в том, что они делают, по крайней мере, в моем случае - person ton.yeung; 21.03.2012
comment
Я добавил некоторые пояснения к OP: часть CRLF важна, но также важно предотвратить двойную отправку в моей форме. - person ton.yeung; 22.03.2012
comment
два совета, чтобы избежать двойной отправки: после отправки вы замораживаете форму с помощью javascript; каждое действие публикации (которое вы не хотите дважды отправлять) заканчивается перенаправлением. - person greut; 22.03.2012
comment
PRG не обращается к кнопке «Назад». См. en.wikipedia.org/wiki/Post/Redirect/Get. - person ton.yeung; 22.03.2012

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

У меня есть это в моем 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.

person Housni    schedule 21.03.2012
comment
Верно, но я не думаю, что вы также можете проверить, является ли это повторной отправкой формы. - person ton.yeung; 22.03.2012
comment
Ну, вы можете просто сгенерировать и установить уникальный идентификатор экземпляра страницы в дочернем контроллере и выполнить проверку в родительском контроллере (то есть в том, который я опубликовал выше) внутри метода _init(). - person Housni; 22.03.2012
comment
извините, я не знал, что вы также использовали родительский контроллер. Я отредактировал свой оригинальный пост, чтобы показать, что у меня есть, и он довольно близок к тому, что есть у вас. Я хочу отказаться от этого метода и использовать фильтр на своем контроллере, аналогичный ASP.NET MVC. Это намного чище и управляемее. - person ton.yeung; 22.03.2012

Я реализовал нечто подобное в недавнем проекте, создав подкласс \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, . . .), но мы решили оставить более общий фильтр.

person AL the X    schedule 02.04.2013