开发者

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 sys开发者_StackOverflow中文版tems, 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.


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


you might want to review the cakephp approach to this problem:

http://book.cakephp.org/view/1543/Simple-Acl-controlled-Application


As far as I know ACL should be used to check general permissions. Entity based permissions shouldn't be subject of ACL. For such task I'll look at how Linux/Unix manage file permissions.

          owner    group   all
read        1        1      1
write       1        0      0
execute     1        0      0
--------------------------------------
            7        4      4

With similar implementation both fetching and checking permissions are easy but you need to add one more layer to your application.

edit: Also this architecture will respect Single Responsibility Principle as far as checking "if user is allowed to open a page" is different from "what user will see at the page"


That seems to be more like a Role Based Access Control or RBAC, than an Access Control List ACL, because you are granting permissions to the user on object depends on what role have, if they are in the group, collaborative list or is the owner, but you are not storing as a role, so cannot be a RBAC, but you are not storing permissions because you are assuming it so it cannot be an ACL.

So if you want to query for a group of objects by the permission that you are assuming, obviously you need to compute that assigned permissions first to get them.

Then you could compute that in a stored procedure/udf function if you want in the db to not fetch all, or made an ACL table/list, or a Role permission linked to the objects you want to give them permissions.

Hope this be useful, good question it fried a bit my brain, but now i need to do something similar.. have a nice day

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜