1

在阅读了 Matthew Weier O'Phinney 的许多关于在模型中实施 ACL 的文章后,我一直专注于实现这一目标的最佳方法。然而,在进一步研究领域对象的最佳实践后,我了解到这些模型不应包含对数据映射器或任何 CRUD 操作的任何引用。

以 ERM 软件为例,它根据销售和采购订单维护库存并处理进出公司的货物。我想象有几个域......

  • 公司
  • 运输
  • 命令
  • 产品
  • 集会
  • 还有其他几个

由于公司可以有不同的类型(例如制造商、供应商、零售商),因此此信息存储在我的数据库中的许多表中(例如公司、类型、公司类型)。因此,我的公司域有一个数据映射器,它使用每个数据库表的 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()方法 typehint App_Model_DomainObjectAbstract $obj

问题 #1 - 由于我的数据映射器将处理所有 CRUD 操作,并且域对象实际上应该只包含特定于该域的属性,我觉得 ACL 检查属于数据映射器 - 这可以接受吗?

我试图避免在我的控制器中实例化数据映射器,但这似乎不合理,因为我认为我对这种设计模式有了更好的理解。

问题 #2 - 我是否过于复杂了这个过程,我是否应该编写一个 ACL 插件来Zend_Controller_Plugin_Abstract根据方法中的传入请求扩展和处理 ACL preDispatch()

非常感谢您的宝贵时间!

4

2 回答 2

2

这里有一个共识(仔细阅读@teresko 的答案)ACLs最适合装饰器模式(一个安全容器)。

如果您的权限定义ACL存储在数据库中,那么您必须有一个 DataMapper 来映射acl数据库上的定义和您的Zend_Acl对象的实际实现及其resources,rolesprivileges.

由于ZF 1 的特性(许多反模式、全局状态等),您可能不会使用控制器装饰器来实现。相反,您将使用一个插件(preDispatch)来为您检查横切关注点。因此,您的 ACL 必须是最先初始化的对象之一。

考虑到您的 ACL 定义基于controlleraction名称,您的插件将调用您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');
        }
    }
}
于 2012-08-24T00:48:46.620 回答
1

@问题#1: 不,ACL 不属于您的映射器。牢记关注点分离。如果您决定将 ACL 建立在基于每个对象的基础上,那么上面链接的装饰器方法就是您要走的路。但是,装饰器可以很好地围绕映射器实现。考虑 ZF1 提供的这个 acl 结构:资源:您的域实体,例如类名角色:用户角色特权:CRUD

<?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 操作都有一个操作,那么您可以简单地通过一个 FrontController-Plugin 来实现您的 acl,正如 ZF1 的许多和更多教程向您展示的那样。如果您需要更细粒度的 ACL,例如角色 GUEST 可能会更新公司名称,但经理可能会更新整个实体,或者如果您有更改多个实体的操作,则基于实体的方法是更好的一个海事组织。

关于您概述的设计的其他一些想法:我认为让实体验证自己不是一个好主意。尝试实现一个由具体验证器验证类型的解决方案。你甚至可以再次使用装饰器;)这仍然是一个更清洁的解决方案。

您不应该在控制器操作中使用映射器是有原因的。一是做与数据库分离的验收测试变得更加困难(不过取决于您的实现)。你指出了另一个:让你的动作尽可能短。使用 ACL 和验证器,您的操作将变得更大。考虑按照另一个问题中所述的@teresko 实施服务层。如果您需要,这对于基于属性的 ACL 也很有帮助。

希望对您有所帮助。

于 2012-08-24T05:36:58.180 回答