Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

building a 'two-way' OO dynamic ACL system

This question came up while designing a dedicated ACL system for a custom application, but I think it applies to ACL systems in general, as I haven't found out how to tackle this problem by looking at some of the mainstream systems, like Zend_ACL.

In my application, the permissions are granted dynamically, for example: a user gets view permissions on an activity because he is a member of the team the activity is linked to. This builds on the assumption that you always have an Employee (user) that wants to perform an action (view/edit/etc) on an Item (one of the objects in my application, eg Activity, Team, etc). This is sufficient for my targeted use;

$Activity = new Activity( $_POST['activity_id'] );

$Acl = new Acl( $Activity );
if ( !$Acl->check( 'edit' ) {
    throw new AclException('no permission to edit');
}

My Acl class contains all the business rules to grant the permissions, and they're created 'on the fly' (although sometimes cached for performance reasons);

/**
 * Check the permissions on a given activity.
 * @param Activity $Activity
 * @param int $permission (optional) check for a specific permission
 * @return mixed integer containing all the permissions, or a bool when $permission is set
 */
public function checkActivity( Activity $Activity, $permission = null ) {
    $permissions = 0;

    if ( $Activity->owner_actor_id == $this->Employee->employee_id ) {
        $permissions |= $this->activity['view'];
        $permissions |= $this->activity['remove'];
        $permissions |= $this->activity['edit'];
    } elseif ( in_array( $this->Employee->employee_id, $Activity->contributor_ids_arr ) ) {
        $permissions |= $this->activity['view'];
    } else {
        /**
         * Logged in user is not the owner of the activity, he can contribute 
         * if he's in the team the activity is linked to
         */
        if ( $Activity->getTeam()->isMember( $this->Employee ) ) {
            $permissions |= $this->activity['view'];
        }
    }

    return ( $permission ? ( ( $permission & $permissions ) === $permission ) : $permissions );
}

This system works fine as-is.

The problem with this approach arises when you want to 'reverse' the ACL rules. For instance, "fetch all activities that I'm allowed to edit". I don't want to put any logic like WHERE owner_actor_id = $Employee->employee_id in the code that needs the activities, because this is the responsibility of the Acl class and it should be kept centralized. With the current implementation, I have no other option that to fetch all activities in the code, and then assert them one by one. This is of course a very inefficient approach.

So what I'm looking for is some ideas on a good architecture (or a pointer to an existing ACL implementation or some relevant design patterns) to create an ACL system that can somehow do both hasPermission( $Item, $permission ) and fetchAllItems( $permission ), ideally with the same set of business rules.

Thank you all in advance!


I've looked at the Zend_ACL implementation, but that focuses more on general permissions. I also found the following questions here on SO:

  • Building a generic OO ACL using Doctrine
  • How to organize and manage an ACL?

But unfortunately they don't seem to answer the question either.

like image 386
Rijk Avatar asked Sep 13 '11 13:09

Rijk


1 Answers

A colleague offered me another view on the matter, that might also be the solution to this problem.

What I thought I wanted is put all access-related code in the ACL class (mirroring my statement that "I don't want to put any logic like WHERE owner_actor_id = $Employee->employee_id in the code that needs the activities, because this is the responsibility of the Acl class and it should be kept centralized.").

What I really want is to make sure the user can never access something that doesn't comply to the rules listed in the ACL class. It's not really a problem if the 'worker code' already fetches a subset of the data -- as long as it's ultimately checked against the 'real' ACL. The worst that can happen is that the user sees less than he's supposed to, which is way better than more.

With this 'solution' (alternative approach if you will), you avoid fetching all the data, while maintaining the benefit of having all the rules in one place. Any other solution that I could think of would involve a duplication of the rules, since you need rules in PHP for checking a given resource, and rules written in MySQL for fetching all.

It's still possible, by the way, to put the subset fetch code in the Acl class -- however I think it would be better to keep the class small and focused (because I think the readability of the code in that class is also very important).

like image 138
Rijk Avatar answered Sep 19 '22 21:09

Rijk