Как я могу реализовать Список контроля доступа в моем приложении Web MVC? - PullRequest
94 голосов
/ 07 августа 2010

Первый вопрос

Пожалуйста, не могли бы вы объяснить, как простейший ACL может быть реализован в MVC.

Вот первый подход к использованию Acl в контроллере ...

<?php
class MyController extends Controller {

  public function myMethod() {        
    //It is just abstract code
    $acl = new Acl();
    $acl->setController('MyController');
    $acl->setMethod('myMethod');
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...    
  }

}
?>

Это очень плохой подход, и его минус в том, что мы должны добавить кусок кода Acl в метод каждого контроллера, но нам не нужны никакие дополнительные зависимости!

Следующий подход - сделать все методы контроллера private и добавить код ACL в метод контроллера __call.

<?php
class MyController extends Controller {

  private function myMethod() {
    ...
  }

  public function __call($name, $params) {
    //It is just abstract code
    $acl = new Acl();
    $acl->setController(__CLASS__);
    $acl->setMethod($name);
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...   
  }

}
?>

Это лучше, чем предыдущий код, но основные минусы ...

  • Все методы контроллера должны быть приватными
  • Мы должны добавить код ACL в метод __call каждого контроллера.

Следующий подход состоит в том, чтобы поместить код Acl в родительский контроллер, но нам все еще нужно сохранять методы всех дочерних контроллеров закрытыми.

Какое решение? И какова лучшая практика? Где я должен вызывать функции Acl, чтобы решить, разрешить или запретить выполнение метода.

Второй вопрос

Второй вопрос касается получения роли с использованием Acl. Давайте представим, что у нас есть гости, пользователи и друзья пользователя. Пользователь ограничил доступ к своему профилю, чтобы его могли просматривать только друзья. Все гости не могут просматривать профиль этого пользователя. Итак, вот логика ..

  • мы должны убедиться, что вызываемый метод является профилем
  • мы должны определить владельца этого профиля
  • мы должны определить, является ли зритель владельцем этого профиля или нет
  • мы должны прочитать правила ограничения для этого профиля
  • мы должны решить, выполнять или не выполнять метод профиля

Основной вопрос касается определения владельца профиля. Мы можем определить, кто является владельцем профиля, только выполнив метод модели $ model-> getOwner (), но у Acl нет доступа к модели. Как мы можем это реализовать?

Я надеюсь, что мои мысли ясны. Извините за мой английский.

Спасибо.

Ответы [ 3 ]

180 голосов
/ 13 марта 2012

Первая часть / ответ (реализация ACL)

По моему скромному мнению, лучший способ достичь этого - использовать шаблон декоратора . По сути, это означает, что вы берете свой объект и помещаете его внутри другого объекта, который будет действовать как защитная оболочка. Это НЕ потребует от вас расширения исходного класса. Вот пример:

class SecureContainer
{

    protected $target = null;
    protected $acl = null;

    public function __construct( $target, $acl )
    {
        $this->target = $target;
        $this->acl = $acl;
    }

    public function __call( $method, $arguments )
    {
        if ( 
             method_exists( $this->target, $method )
          && $this->acl->isAllowed( get_class($this->target), $method )
        ){
            return call_user_func_array( 
                array( $this->target, $method ),
                $arguments
            );
        }
    }

}

И вот как вы будете использовать такую ​​структуру:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller 
// only now they will be checked against ACL
$controller->actionIndex();

Как вы могли заметить, это решение имеет несколько преимуществ:

  1. сдерживание может использоваться на любом объекте, а не только в экземплярах Controller
  2. проверка на авторизацию происходит вне целевого объекта, что означает, что:
    • оригинальный объект не отвечает за контроль доступа, придерживается SRP
    • когда вы получаете «разрешение запрещено», вы не заблокированы внутри контроллера, дополнительные параметры
  3. Вы можете внедрить этот защищенный экземпляр в любой другой объект, он сохранит защиту
  4. завернуть и забыть об этом .. вы можете притвориться , что это исходный объект, он будет реагировать так же

Но , у этого метода также есть одна серьезная проблема - вы не можете изначально проверить, реализует ли защищенный объект объект и интерфейс (который также применяется для поиска существующих методов) или является частью некоторой цепочки наследования.

Вторая часть / ответ (RBAC для объектов)

В этом случае основное отличие, которое вы должны признать, заключается в том, что вы Доменные объекты (например: Profile) сами содержат сведения о владельце. Это означает, что для проверки того, есть ли у пользователя (и на каком уровне) доступ к нему, вам потребуется изменить эту строку:

$this->acl->isAllowed( get_class($this->target), $method )

По сути, у вас есть два варианта:

  • Предоставьте ACL с данным объектом. Но вы должны быть осторожны, чтобы не нарушить Закон Деметры :

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • Запросите все необходимые данные и предоставьте ACL только то, что ему нужно, что также сделает его более удобным для модульного тестирования:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )
    

Пара видео, которые могут помочь вам создать собственную реализацию:

Дополнительные примечания

Похоже, у вас достаточно общее (и совершенно неверное) понимание того, что такое Модель в MVC. модель не класса . Если у вас есть класс с именем FooBarModel или что-то, что наследует AbstractModel, то вы делаете это неправильно.

В надлежащем MVC Модель - это слой, который содержит много классов. Большая часть классов может быть разделена на две группы в зависимости от ответственности:

- Доменная бизнес-логика

( подробнее : здесь и здесь ):

Экземпляры из этой группы классов имеют дело с вычислением значений, проверяют различные условия, реализуют правила продаж и делают все остальное, что вы бы назвали "бизнес-логикой". Они не имеют ни малейшего представления о том, как хранятся данные, где они хранятся или даже если хранилище существует на первом месте.

Домен Бизнес-объект не зависит от базы данных. Когда вы создаете счет, не имеет значения, откуда поступают данные. Это может быть либо SQL, либо удаленный REST API, либо даже снимок экрана документа MSWord. Бизнес-логика не меняется.

- Доступ и хранение данных

Экземпляры, созданные из этой группы классов, иногда называют объектами доступа к данным. Обычно структуры, которые реализуют шаблон Data Mapper (не путайте с ORM с тем же именем .. никакого отношения). Это где ваши операторы SQL (или, возможно, ваш DomDocument, потому что вы храните его в XML).

Помимо двух основных частей, следует упомянуть еще одну группу экземпляров / классов:

- Услуги

Здесь вступают в игру ваши и сторонние компоненты.Например, вы можете думать об «аутентификации» как о сервисе, который может быть предоставлен вашим собственным или каким-либо внешним кодом.Также «отправителем почты» может быть служба, которая может связать некоторый объект домена с PHPMailer или SwiftMailer или вашим собственным компонентом отправителя почты.

Другим источником services является абстракция нана уровне домена и доступа к данным.Они созданы для упрощения кода, используемого контроллерами.Например: для создания новой учетной записи пользователя может потребоваться работа с несколькими объектами домена и mappers .Но при использовании службы ему потребуется только одна или две строки в контроллере.

При создании служб следует помнить, что весь слой должен быть тонкий ,В услугах нет бизнес-логики.Они предназначены только для манипулирования доменными объектами, компонентами и сопоставителями.

Одна из общих черт заключается в том, что сервисы не влияют на слой View каким-либо прямым образом и являются автономными до такой степени,что их можно (и часто прекращать) использовать за пределами самой структуры MVC.Кроме того, такие самодостаточные структуры значительно упрощают переход на другую структуру / архитектуру из-за крайне низкой связи между службой и остальным приложением.

16 голосов
/ 13 марта 2012

ACL и контроллеры

Прежде всего: это разные вещи / слои чаще всего. Когда вы критикуете примерный код контроллера, он объединяет оба - очевидно, слишком жесткий.

Терешко уже обрисовал в общих чертах способ, которым вы могли бы отделить это больше с рисунком декоратора.

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

С одной стороны, вы хотите иметь контроллеры, которые просто выполняют ту работу, которую им предписано (команда или действие, давайте назовем это командой).

С другой стороны, вы хотите иметь возможность использовать ACL в своем приложении. Область работы этих ACL должна состоять в том, чтобы - если я правильно понял ваш вопрос - контролировать доступ к определенным командам ваших приложений.

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

Давайте подведем итог к этому моменту, что мы имеем:

  • Команда
  • ACL
  • Пользователь

Компонент ACL является здесь центральным: он должен знать хотя бы кое-что о команде (чтобы точно определить команду) и уметь идентифицировать пользователя. Пользователи обычно легко идентифицируются по уникальному идентификатору. Но часто в веб-приложениях есть пользователи, которые вообще не идентифицированы, часто называются гостями, анонимами, всеми и т. Д. В этом примере мы предполагаем, что ACL может потреблять пользовательский объект и инкапсулировать эти детали. Пользовательский объект привязан к объекту запроса приложения, и ACL может его использовать.

А как насчет идентификации команды? Ваша интерпретация шаблона MVC предполагает, что команда представляет собой соединение имени класса и имени метода. Если мы посмотрим более внимательно, то есть даже аргументы (параметры) для команды. Таким образом, уместно спросить, что именно идентифицирует команду? Имя класса, имя метода, количество или имена аргументов, даже данные внутри любого из аргументов или смесь всего этого?

В зависимости от того, какой уровень детализации вам необходим для определения команды в вашем ACL'инге, это может сильно варьироваться. Для примера давайте оставим это просто и укажите, что команда идентифицируется по имени класса и имени метода.

Так что контекст того, как эти три части (ACL, Command и User) принадлежат друг другу, теперь более понятен.

Можно сказать, что с мнимым компонентом ACL мы уже можем сделать следующее:

$acl->commandAllowedForUser($command, $user);

Посмотрите, что происходит здесь: сделав команду и пользователя идентифицируемыми, ACL может выполнять свою работу. Работа ACL не связана с работой как пользовательского объекта, так и конкретной команды.

Не хватает только одной части, она не может жить в воздухе. И это не так. Таким образом, вам нужно найти место, где должен включиться контроль доступа. Давайте посмотрим, что происходит в стандартном веб-приложении:

User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User

Чтобы найти это место, мы знаем, что это должно быть до того, как конкретная команда будет выполнена, поэтому мы можем сократить этот список и нам нужно только изучить следующие (потенциальные) места:

User -> Browser -> Request (HTTP)
   -> Request (Command)

В какой-то момент в вашем приложении вы знаете, что конкретный пользователь запросил выполнение конкретной команды. Вы уже выполняете здесь какие-то ACL-операции: если пользователь запрашивает команду, которая не существует, вы не разрешаете выполнение этой команды. Поэтому, когда бы это ни происходило в вашем приложении, можно добавить «настоящие» проверки ACL:

Команда была найдена, и мы можем создать ее идентификацию, чтобы ACL мог с ней справиться. В случае, если команда не разрешена для пользователя, команда не будет выполнена (действие). Возможно CommandNotAllowedResponse вместо CommandNotFoundResponse для случая, когда запрос не может быть разрешен для конкретной команды.

Место, в котором сопоставление конкретного HTTPRequest сопоставлено с командой, часто называют Routing . Поскольку Routing уже имеет задание найти команду, почему бы не расширить его, чтобы проверить, действительно ли команда разрешена для каждого ACL? Например. расширив Router до маршрутизатора с поддержкой ACL: RouterACL. Если ваш маршрутизатор еще не знает User, то Router - не то место, потому что для работы ACL должна быть идентифицирована не только команда, но и пользователь. Так что это место может меняться, но я уверен, что вы легко сможете найти место, которое вам нужно расширить, потому что это место, которое удовлетворяет требованиям пользователя и команды:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Пользователь доступен с начала, сначала команда с Request(Command).

Таким образом, вместо того, чтобы помещать проверки ACL внутри каждой конкретной реализации команды, вы помещаете ее перед ней. Вам не нужны тяжелые шаблоны, магия или что-то еще, ACL выполняет свою работу, пользователь выполняет свою работу, и особенно команда выполняет свою работу: просто команда, ничего больше. Команде не интересно знать, применяются ли к ней роли, если она где-то охраняется или нет.

Так что просто держите вещи отдельно, которые не принадлежат друг другу. Используйте слегка перефразировку Принципа единой ответственности (SRP) : должна быть только одна причина для изменения команды - потому что команда изменилась. Не потому, что вы теперь вводите ACL'ing в своем приложении. Не потому, что вы переключаете объект User. Не потому, что вы переходите с интерфейса HTTP / HTML на интерфейс SOAP или интерфейс командной строки.

ACL в вашем случае контролирует доступ к команде, а не к самой команде.

13 голосов
/ 07 августа 2010

Одна из возможностей - это обернуть все ваши контроллеры в другой класс, который расширяет Controller, и разрешить ему делегировать все вызовы функций для обернутого экземпляра после проверки на авторизацию.

Вы также можете сделать это более восходящим потоком в диспетчере (если оно действительно есть в вашем приложении) и искать разрешения на основе URL-адресов вместо методов управления.

edit : Требуется ли вам доступ к базе данных, серверу LDAP и т. Д., Вопрос ортогональный. Я хотел сказать, что вы можете реализовать авторизацию на основе URL-адресов вместо методов контроллера. Они более надежны, потому что вы, как правило, не будете изменять свои URL-адреса (вид области URL-адресов общедоступного интерфейса), но вы также можете изменить реализации ваших контроллеров.

Как правило, у вас есть один или несколько файлов конфигурации, в которых вы сопоставляете конкретные шаблоны URL с конкретными методами аутентификации и директивами авторизации. Диспетчер перед отправкой запроса контролерам определяет, авторизован ли пользователь, и отменяет диспетчеризацию, если он не авторизован.

...