1

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!

John Hall
  • 1,346
  • 13
  • 27

2 Answers2

2

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');
        }
    }
}
Community
  • 1
  • 1
Keyne Viana
  • 6,194
  • 2
  • 24
  • 55
  • +1 Thanks for the reference! This makes a lot of sense to me. My current ACL class handles building `resources`, `roles`, and `privileges` from the DB -- that is all working great. I see how I can implement this with the Decorator pattern proposed by @teresko. However, I am slightly confused as to how I would implement this pattern as a plugin. I understand that in the `preDispatch` method I could easily build an ACL object for the current Zend_Auth instance, but I am confused as to how I would build the domain object based on the data contained in the incoming request (JSON, from ExtJS). – John Hall Aug 24 '12 at 14:45
  • @Keyne - This is of great help, thank you so very much! My last concern is how to go about having controller actions available for specific (i.e. not CRUD) tasks such as "Get In Stock Products". I could perhaps take care of this on the client side, since ExtJS builds datastores which can be filtered. I WERE to build such functionality in my back-end (which I need to in order to allow external services to make API calls), I could treat such a request as a READ (as far as the ACL is concerned) and use a method in my Service Layer to build the response for the controller action. Is this correct? – John Hall Aug 24 '12 at 20:36
  • @JohnHall Not sure if I got it right... Are you serving a web service too or actually are you connecting to a 3rd part API? If you have any actions on your controllers that doesn't perform any interaction with the database, there's no problem, you still can add it to the ACL. No need to make it on the client side. It looks right to tie it to the READ privilege. IIRC, if you're connection to an API, it's also a service. Then, you need a privilege for it (or at least make it an unsecured resource). – Keyne Viana Aug 24 '12 at 20:55
  • @Keyne This back-end will serve both my front-end (ExtJS app) as well as provide an API for 3rd parties to use. In either case, if I wanted to allow a request for "Out of Stock Products", I suppose I could implement this as a parameter in my request. For example, my request could include `controller` (resource) `action` (privilege) and some `extra` parameter for further filtering of the data being requested. Thus a valid request may be for the `Product` controller, `Read` action, and `outOfStock` as the `extra` parameter. Does that seem like a valid/legitimate approach? – John Hall Aug 26 '12 at 18:44
  • Yes, it's valid. Just like the Twitter API, i.e. https://dev.twitter.com/docs/api/1/get/search You're on the right path. – Keyne Viana Aug 26 '12 at 18:54
1

@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.

Jojo
  • 2,720
  • 1
  • 17
  • 24
  • +1 thank you! This makes sense to me, however as I described in my comment in response to @Keyne, I am confused as to how I would build the `$entity` in the front controller plugin. For example, if I am attempting to `create` a new company, should the `preDispatch` method contain the logic to decode the incoming JSON request and `_populate()` the properties of a `new App_Model_Company()` before sending it to the `SecurityContainer` object? I feel like I must be missing something pretty huge, as what I just described would lead to a ton of conditional code in the `preDispatch` method. – John Hall Aug 24 '12 at 14:56
  • Youre missing something very small, actually. Use the predispatch-plugin if you are about to base your acl on the request, meaning a user-role is allowed to access certain actions (privileges) on certain-controllers (resources). Use the Entity-based ACl per Decorator (decorating your mapper/service) if the above is not sufficient for you. Are things more understandable now? Otherwise I try to update my answer with some more code this evening – Jojo Aug 24 '12 at 15:29
  • Ahhhh! Thank you again! I was trying to combine the two approaches into one, and if I understand you correctly, they are in fact two separate approaches and should be treated as such. My application does not have complex permissions; a `role` will have access to a `privilege` on a `resource`, not individual properties of that resource. Still though, I am confused as to how to avoid using the domain's DataMapper in my controller actions. How will my `saveAction` be able to write to the DB without first building a domain model, then sending that model to the save method of the DataMapper? – John Hall Aug 24 '12 at 16:00
  • @Jojo Almost it I think, as you may have noted, Teresko answer is decorating controllers, which will handle per request. The main problem of implementing controller's decorators as a security shell on Zend Framework 1, is that you'll need to tweak the framework to achieve it, since it's not easy to decorate controllers given the actual architecture (maybe on ZF 2 it's possible). There's a second option, that's letting the ACL checks to your service layer, if you have one. So you'll decorate your services with the ACL. All in all, if you're using ZF 1, the plugin will fit well. – Keyne Viana Aug 24 '12 at 17:16