Где реализовать Zend_ACL с объектами домена и Data Mapper?

Прочитав множество постов Мэтью Вейера О'Финни о внедрении ACL в модели, я сосредоточился на том, как лучше всего это сделать. Однако после дальнейшего изучения передовых методов работы с объектами домена я понимаю, что эти модели не должны содержать никаких ссылок на Data Mappers или какие-либо операции CRUD.

Возьмем, к примеру, программное обеспечение ERM, которое ведет инвентаризацию и обрабатывает поставки в / из компаний на основе заказов на продажу и покупку. Я представляю себе несколько доменов ...

  • Компания
  • Отгрузка
  • Заказ
  • Продукт
  • сборка
  • И еще несколько

Поскольку компании могут иметь разные типы (например, производитель, поставщик, продавец), эта информация хранится в многочисленных таблицах в моей базе данных (например, компании, типы, company_types). Таким образом, у меня есть Data Mapper для моего домена компании, который использует объекты для экземпляров Zend_Db_Table каждой таблицы базы данных.

В своих действиях Контроллера я понимаю, что логики должно быть очень мало. Например, создание новой компании может выглядеть примерно так ...

public function createAction()
{
  // Receive JSON request from front end
  $data = Zend_Json::decode($request);
  $companyObj = new App_Model_Company();
  $companyObj->populate($data);
  $companyMapper = new App_Model_DataMapper_Company();
  $companyMapper->save($companyObj);
}

Имея это в виду, я считаю, что лучше всего включить мои проверки ACL в DataMapper, а проверку - в объект домена. Все объекты My Domain выходят за пределы базового абстрактного класса, который перегружает магические методы PHP __set и __get. В конструкторе каждого объекта домена я определяю свойства объекта, заполняя массив $_properties ключами. Таким образом, мой метод __set выглядит примерно так ...

public function __set($property, $value)
{

    $className = __CLASS__;
    if(!array_key_exists($property, $this->_properties))
    {
        throw new Zend_Exception("Class [ $className ] has no property [ $property ]");
    }

    // @return Zend_Form
    $validator = $this->getValidator();

    /*
     * Validate provided $value against Zend_Form element $property
     */

    $this->properties[$property] = $value;
    }
}

Все save() методы моего Data Mapper typehint App_Model_DomainObjectAbstract $obj.

Вопрос №1. Поскольку мой Data Mapper будет обрабатывать все действия CRUD, а объект Domain должен действительно содержать только свойства, специфичные для этого домена, я чувствую, что проверки ACL относятся к Data Mapper - это приемлемо?

Я пытался избежать создания экземпляров Data Mapper в своих контроллерах, но теперь это кажется нерациональным, поскольку я думаю, что лучше понимаю этот шаблон проектирования.

Вопрос № 2 - Не слишком ли я усложняю этот процесс и должен ли я вместо этого написать подключаемый модуль ACL, который расширяет Zend_Controller_Plugin_Abstract и обрабатывает ACL на основе входящих запросов в методе preDispatch()?

Спасибо вам большое за ваше время!


person John Hall    schedule 23.08.2012    source источник


Ответы (2)


Здесь есть согласие (внимательно прочтите ответ @teresko), что ACLs лучше всего вписывается в Шаблон декоратора (контейнер безопасности).

Если определения привилегий вашего ACL хранятся в базе данных, тогда у вас должен быть DataMapper для сопоставления между определениями acl в вашей базе данных и вашей фактической реализацией объекта Zend_Acl с его resources, roles и privileges.

Вероятно, вы не будете реализовывать с помощью декораторов контроллеров из-за природы ZF 1 (множество антипаттернов, глобального состояния и т. Д.). Вместо этого вы будете использовать сквозные проблемы с плагином (preDispatch), который проверяет его за вас. Итак, ваш ACL должен быть одним из первых инициализированных объектов.

Учитывая, что ваши определения ACL основаны на именах controller и action, ваш плагин вызовет ваш AclMapper, чтобы получить заполненный объект ACL, а затем проверит, разрешен ли текущий пользователь доступ к данному ресурсу.

Проверьте этот пример кода:

class Admin_Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract 
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        if($request->getModuleName() != 'admin')
        {
            return;
        }


        $auth = Zend_Auth::getInstance();
        $action = null;

        if(!$auth->hasIdentity())
        {
           $action = 'login'; 
        }
        else
        {
            /**
             * Note that this is not a good practice (singletons). 
             * But in this case it's avoiding re-loading the Acl from database
             *  every time you need it. Also, considering that ZF 1 is full of 
             * singletons, it'll not hurt, I think ;)
             * YOU CAN CHANGE THIS LINE TO $aclMapper->getAcl();
             */

            $acl = Acl::getInstance();

            $resource = $request->getModuleName() . ':' . $request->getControllerName();
            $privilege = $request->getActionName();

            $identity = $auth->getStorage()->read();
            $role = $identity->role_id;

            if($acl->has($resource))
            {
                if(!$acl->isAllowed($role,$resource,$privilege))
                {
                    $action = 'access-denied';
                }
            }
        }

        if($action)
        {
            $request->setControllerName('authentication')
                    ->setActionName($action)
                    ->setModuleName('admin');
        }
    }
}
person Keyne Viana    schedule 24.08.2012
comment
+1 Спасибо за ссылку! Для меня это имеет большой смысл. Мой текущий класс ACL обрабатывает сборку resources, roles и privileges из БД - все это отлично работает. Я вижу, как я могу реализовать это с помощью шаблона Decorator, предложенного @teresko. Однако меня немного смущает то, как я могу реализовать этот шаблон как плагин. Я понимаю, что в методе preDispatch я мог бы легко создать объект ACL для текущего экземпляра Zend_Auth, но я не понимаю, как я буду создавать объект домена на основе данных, содержащихся во входящем запросе (JSON, из ExtJS). - person John Hall; 24.08.2012
comment
@Keyne - Это очень помогает, большое вам спасибо! Моя последняя проблема - как сделать так, чтобы действия контроллера были доступны для определенных (то есть не CRUD) задач, таких как получение продуктов на складе. Возможно, я мог бы позаботиться об этом на стороне клиента, поскольку ExtJS создает хранилища данных, которые можно фильтровать. Я БЫЛ, чтобы создать такую ​​функциональность в моем бэкэнде (что мне нужно, чтобы позволить внешним службам делать вызовы API), я мог бы рассматривать такой запрос как ЧТЕНИЕ (что касается ACL) и использовать метод в моем сервисном слое, чтобы создать ответ на действие контроллера. Это правильно? - person John Hall; 25.08.2012
comment
@JohnHall Не уверен, правильно ли я понял ... Вы тоже обслуживаете веб-службу или действительно подключаетесь к API третьей части? Если у вас есть какие-либо действия на ваших контроллерах, которые не осуществляют никакого взаимодействия с базой данных, нет проблем, вы все равно можете добавить их в ACL. Не нужно делать это на стороне клиента. Кажется правильным связать это с привилегией READ. IIRC, если вы подключаетесь к API, это тоже услуга. Затем вам потребуются привилегии для него (или, по крайней мере, сделать его незащищенным ресурсом). - person Keyne Viana; 25.08.2012
comment
@Keyne Этот бэкэнд будет обслуживать как мой интерфейс (приложение ExtJS), так и предоставлять API для использования третьими сторонами. В любом случае, если бы я хотел разрешить запрос на товары, отсутствующие на складе, я полагаю, что мог бы реализовать это как параметр в моем запросе. Например, мой запрос может включать controller (ресурс) action (привилегия) и некоторый параметр extra для дальнейшей фильтрации запрашиваемых данных. Таким образом, допустимый запрос может быть для Product контроллера, Read действия и outOfStock в качестве параметра extra. Кажется ли это правильным / законным подходом? - person John Hall; 26.08.2012
comment
Да, это действительно так. Так же, как Twitter API, т.е. dev.twitter.com/docs/api/1 / get / search Вы на правильном пути. - person Keyne Viana; 26.08.2012

@Question # 1: Нет, ACL не принадлежит вашим картографам. Помните о разделении проблем. Если вы решите основывать свой ACL на основе каждого объекта, подход декоратора, как указано выше, - ваш путь. Однако декоратор вполне может быть реализован вокруг картографа. Рассмотрим эту acl-структуру, предоставленную ZF1: resource: your Domain-entity, например classname role: привилегия роли пользователя: C-R-U-D

<?php
class SecurityContainer {
    /**@var Zend_Acl*/
    protected $acl;

    /**@var DataMapper */
    protected $mapper;

    /**@var User|rolename*/
    protected $user;

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

    public function __call($method, $entity) {
        if (method_exists($this->mapper, $method) {
            if ($this->acl->isAllowed($user, get_class($entity), $method) {
                $this->mapper->$method($entity);
        }
    }
}

Это приводит к вопросу 2: Это действительно зависит от того, как вы разработали интерфейс приложения. Если есть одно действие для каждой CRUD-операции каждого типа сущности, вы можете реализовать свой acl просто с помощью FrontController-Plugin, как показывают многие и другие учебные пособия для ZF1. Если вам нужен более детализированный ACL, скажем, роль GUEST может обновлять название компании, но менеджер может обновлять всю сущность, или если у вас есть действия, в которых изменяется более одной сущности, подход на основе сущностей является имо лучше.

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

Есть причины, по которым вы не должны использовать свои картографы внутри действий контроллера. Во-первых, становится все труднее проводить приемочные тесты, которые не связаны с вашей базой данных (хотя это зависит от вашей реализации). Вы указали на другого: делайте свои действия как можно короче. С ACL и валидаторами ваше действие станет больше. Рассмотрите возможность реализации Servicelayer, как указано в другом вопросе @teresko. Это также будет полезно для ACL на основе свойств, если вам это нужно.

Надеюсь, это вам как-то помогло.

person Jojo    schedule 24.08.2012
comment
+1 спасибо! Для меня это имеет смысл, однако, как я описал в своем комментарии в ответ на @Keyne, я не понимаю, как я буду создавать $entity в подключаемом модуле фронт-контроллера. Например, если я пытаюсь create новую компанию, должен ли метод preDispatch содержать логику для декодирования входящего запроса JSON и _populate() свойства new App_Model_Company() перед отправкой его объекту SecurityContainer? Я чувствую, что мне не хватает чего-то очень важного, поскольку то, что я только что описал, привело бы к тонне условного кода в методе preDispatch. - person John Hall; 24.08.2012
comment
На самом деле вы упускаете что-то очень маленькое. Используйте плагин predispatch-plugin, если вы собираетесь основывать свой acl на запросе, что означает, что роли пользователя разрешен доступ к определенным действиям (привилегиям) на определенных контроллерах (ресурсах). Используйте ACl на основе сущностей для каждого декоратора (украшение вашего картографа / службы), если вам недостаточно вышеперечисленного. Теперь все стало понятнее? В противном случае я пытаюсь обновить свой ответ еще одним кодом сегодня вечером - person Jojo; 24.08.2012
comment
Аааааааааааааааааааааа! Еще раз спасибо! Я пытался объединить два подхода в один, и, если я правильно вас понял, на самом деле это два разных подхода, и к ним следует относиться как к таковым. У моего приложения нет сложных разрешений; role будет иметь доступ к privilege на resource, а не к отдельным свойствам этого ресурса. Тем не менее, я не понимаю, как избежать использования DataMapper домена в действиях моего контроллера. Как мой saveAction сможет писать в БД без предварительного построения модели предметной области, а затем отправки этой модели методу сохранения DataMapper? - person John Hall; 24.08.2012
comment
@Jojo Я думаю, что, как вы, возможно, заметили, ответ Тереско украшает контроллеры, которые будут обрабатывать каждый запрос. Основная проблема реализации декораторов контроллеров в качестве оболочки безопасности на Zend Framework 1 заключается в том, что вам необходимо настроить структуру для достижения этой цели, поскольку при реальной архитектуре декорировать контроллеры непросто (возможно, в ZF 2 это возможно). Есть второй вариант, позволяющий ACL проверять уровень вашего сервиса, если он у вас есть. Таким образом, вы украсите свои службы списком ACL. В общем, если вы используете ZF 1, плагин подойдет. - person Keyne Viana; 24.08.2012