building a 'two-way' OO dynamic ACL system
Asked Answered
P

4

16

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.

Pitchstone answered 13/9, 2011 at 13:57 Comment(1)
What is the argument against putting the fetchAllItems() method in the Acl class? I am not sure I really understand the question.Pretender
P
1

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

Pitchstone answered 16/9, 2011 at 7:56 Comment(1)
@Xeoncross: sure, what is it that you don't understand?Pitchstone
G
0

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

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

Galumph answered 22/9, 2011 at 0:55 Comment(1)
I find it hard to understand.. However, the permissions still seem to be hard-coded per user/group, right? I need them to be granted dynamically..Pitchstone
I
0

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"

Iridize answered 22/9, 2011 at 13:9 Comment(2)
But that is a permission system, you only have as see owner,group,and all entities, and a important part of that permissions is have an owner. This are ACL, you assign specified users and groups to some actions and that have inherits rules, and group rules.Declinatory
I fail to see the difference between a file and an Activity object -- they're both 'entities' right? And a user can have a set of permissions on the entity, like 'view', 'edit', 'remove', or 'read', 'write', 'execute' in case of Linux. The difference between my system and Linux's, is that my permissions are derived from the context instead of hard-coded per entity.Pitchstone
D
0

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

Declinatory answered 22/9, 2011 at 14:59 Comment(2)
Thanks for your comment! I've look into RBAC, but it doesn't seem to fit my case. Wikipedia states that "RBAC is not a model for assigning authorizations dynamically" -- I believe assigning dynamically is what I'm trying to do here.Pitchstone
The point i try to say is about RBAC are a centralized acces control entity, and to get a grant to an object you need to ask to this entity for the permision. In the other hand, ACL have a list linked or in the object, and the API ask if the ACL of the object grant acces to user. If you want to assign dinamically without get all from db, your only solution is a UDF Function/Stored procedure, if you have some doubts how write it, ask it.Declinatory

© 2022 - 2024 — McMap. All rights reserved.