Question

Having read many of Matthew Weier O'Phinney's posts regarding implementing ACL into Models, I have been focused on the best way to go about doing this. However, upon further research on best practices with Domain Objects, I understand that these models should not contain any reference to Data Mappers, or any CRUD operation.

Take for example ERM software which maintains inventory and handles shipments to/from companies based on sale and purchase orders. I imagine having a few domains...

  • Company
  • Shipment
  • Order
  • Product
  • Assembly
  • And a few others

Since Companies can have different Types (e.g. Manufacturer, Supplier, Retailer), this information is stored in numerous tables across my database (e.g. companies, types, company_types). Thus, I have a Data Mapper for my Company Domain which uses objects for the Zend_Db_Table instances of each database table.

In my Controller actions, I understand that there should be very little logic. For instance, creating a new Company may go something like this...

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);
}

With this in mind, I feel that it is best to incorporate my ACL checks into the DataMapper and Validation into the Domain Object. My Domain objects all extend off a base abstract class, which overloads PHP's magic __set and __get methods. In the constructor of each Domain Object, I define the properties of the object by populating a $_properties array with keys. This way, my __set method looks something like...

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;
    }
}

All my Data Mapper's save() methods typehint App_Model_DomainObjectAbstract $obj.

Question #1 - Since my Data Mapper will handle all CRUD actions, and the Domain object should really just contain properties specific to that domain, I feel like ACL checks belong in the Data Mapper - is this acceptable?

I was trying to avoid instantiating Data Mapper's in my Controllers, but this seems irrational now that I think I have a better understanding of this design pattern.

Question #2 - Am I over complicating this process, and should I instead write an ACL Plugin that extends Zend_Controller_Plugin_Abstract and handles ACL based on incoming requests in the preDispatch() method?

Thank you very much for your time!

Was it helpful?

Solution

There's a consensus here (carefully read the answer of @teresko) that ACLs would best fit into the Decorator Pattern (a security container).

If the privileges definitions of your ACL are stored on the database, then you must have a DataMapper to map between the acl definitions on your database and your actual implementation of Zend_Acl object with its resources, roles and privileges.

Probably you'll not implement with controller decorators due to the ZF 1 nature (a lot of anti-patterns, global state and so on). Instead, you'll be using cross-cutting concerns with a plugin (preDispatch) that checks it for you. So your ACL must be one of the first objects initialized.

Considering that your ACL definitions are based on the controller and action names, your plug-in will call your AclMapper to get the populated ACL object and then check if the current user is allowed to access the given resource.

Check this example code:

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');
        }
    }
}

OTHER TIPS

@Question #1: No, the ACL does not belong inside your mappers. Keep Seperation of concerns in mind. If you decide to base your ACL on a per object-basis the decorator-approach as linked above is your way to go. The decorator, though, may very well be implemented around the mapper. Consider this acl-structure as provided by ZF1: resource: your Domain-entity, e.g classname role: the user-role privilege: 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);
        }
    }
}

This leads to Question 2: It really depends how you designed your application-interface. If there is one action for each CRUD-operation of each entity-type, you may implement your acl simply by a FrontController-Plugin as many and more tutorials for the ZF1 show you. If you have a need for a more fine-grained ACL, say, role GUEST may update a companies name, but a manager may update the whole entity or if you have actions where more then one entity is changed, the entity-based approach is the better one imo.

Some other thoughts about the design you outlined: I don't think its a good idea to let the entities validate themselves. Try to implement a solution where a type is validated by a concrete validator. You may even use the decorator again ;) This would still be a cleaner solution.

There are reasons why you shouldn't use your mappers inside your controller-actions. One is that it becomes harder to do acceptance-tests that are decoupled from your database (depends on your implementation, though). You pointed to another: Keep your actions as short as possible. With ACLs and Validators your action will become larger. Consider implementing a Servicelayer as @teresko stated in the other question. This would also be helpful for the property-based ACL if that is a need of yours.

Hope that helped you somehow.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top