yossi
yossi

Reputation: 3164

CakePhp and user permissions

I need a system that will allow users to modify data that is related to them.

Like a shopping center, with several stores and a shared catalog. I want the stores to be able to edit and add items, from and in, the global pool. but i want to limit the access of a store to its own items.

how it is done with acl & auth (the concept of course)?

Upvotes: 1

Views: 1058

Answers (1)

pleasedontbelong
pleasedontbelong

Reputation: 20102

I've seen this question a few times. From the cake's doc it says:

Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted.

so in theory, you could handle all the items in your application using ACL, but I see a few problems doing that:

  • lets say you have 10k elements per store in your items table, well, you'll have the same amount of elements in your ACO table. Now if you add a user, you'll have to give him access to the items in the ARO_ACO table (another 10k items FOR EACH USER!)
  • .. ok maybe I exaggerated on the first example. Because you could group the items and in the ACO table, and only set the permissions per group in the ACO_ARO table. But now it might be a nightmare to make the queries in your application, imagine if you want to make a filtered paginated list of items. You'd have to ask the ACO (which now is in a tree structure) and the ARO_ACO table to count and paginate the items... and even if it's done automatically you'd have a performance problem

In my opinion, it's better to use the ACL to only grant permission to the controllers methods (CRUD) and manage the item permissions in your code by setting the condition in your queries:

$this->Items->find('all',array('conditions'=>array('store_id'=>$this->Auth->user('store_id'))))

But you might need to repeat this condition everywhere so you could create a Behavior that will modify the queries in order to add a condition in the WHERE clause, so you would always return/edit the items of the current store. For example, create a behavior like this:

    /** 
     * Filter query conditions with the correct `type' field condition. 
     */ 
    function beforeFind(&$model, $query) 
    {
        /**
         * Condition for the paginators that uses joins
        */
        if(isset($query['joins']) && !empty($query['joins'])){
            foreach($query['joins'] as $key => $joinTable){
                if(ClassRegistry::init($joinTable['alias'])->hasField('store_id')){
                    $query['joins'][$key]['conditions'][] = $joinTable['alias'].".store_id = '".$_SESSION['store_id']."'";
                }
            }
        }

        /**
         * condition for the normal find queries
        */
        if($model->hasField('store_id') && $model->name != "User"){
            $query['conditions'][$model->name.'.store_id'] = $_SESSION['store_id'];
        }
        return $query;
    }

In the controller I'd just need to call the find method normally

$this->Items->find('all');

This way you don't have to make more queries to see if the user can access an item. I've used something like this on a website and it works fine for me.

Upvotes: 1

Related Questions