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:
But unfortunately they don't seem to answer the question either.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With