Thursday, December 31, 2009

Implement Zend ACL with database backend for our CMS

Possibly a better and purhaps more proper way to use Zend ACL in my CMS would be to
treat every user as a special 'role' and use groups as roles, and allow user to belong to multiple groups.

Then create a site-wide ACL first and store it in cache
Then when authorization is required, add the userObject to the ACL object, passing the array of his groups as second arg, like this:

$parents = array('guest', 'member', 'admin');
$acl->addRole($oUser, $parents); //
/*

$oUser is basically the $this->oViewer and must implement Zend_Acl_Role
*/

So now a user is added to the ACL, and even though he may now have any permissions
set directly on his object, he would get permissions from being member of
groups.

Another possibility is to NOT use user account as role, and only use user's primary
group_id as Role (the getRoleId() in user object will return the group_id)
And pass the array of other groups if user has any secondary groups or null

Which one is more flexible?
First of all I must verify that it's possible to set permissions to ACL before
the Role has been added because we will not be adding individual user to the ACL
at the time ACL is created, buy only at a time we need to check permissions when
we will be using just an instance of the ACL.

Update: It is definetely NOT POSSIBLE to add rule to ACL on a role that has not
been added yet! Registry::get() with throw an exception! This means we cannot just add rule for some specific userID before
we actually add that user (as object) to the ACL, which is not convenient at all!

Must examine the Zend ACL class for that - is it possible to set allow() or deny()
on Role before Role object has been added?

If that is too messy, then we can definitely use only groups for permission management
and if a user needs special permission then either use custom Accertion class OR
create a special custom group just for that user, set permissions on a group
and add user to that group. This looks much cleaner and probably more
easier to understand.

So groups should be Everyone, Guest, Unvalidated, Registered, Moderator, SuperModerator, Administrator

Where every group inherits from Everyone, then Guest may not even be needed,
what is the difference of guest and Everyone anyway?
Then if a special group is needed it may be created and set to inherit from
Members then special permissions added to it or explicit deny is added to it.

Then either an extra table will be required USER2GROUP to store
user to group mapping OR store all user groupIDs in USERS.groups field as comma-separated value
This may be somewhat not clean and may be difficult to set the correct order
and order of groups is important in ACL.

Another possibility is that by default every user belongs to just 1 group but
USER2GROUP will store ONLY secondary groups for some users that need them.

This will allow us to keep the USERS table as is, while still allowing users
to be added to extra groups if needed. Also we do have a user_type field in USERS
so we can use that for assertion test. We can also use userID for
accertion test if needed to check individual userID permission or
to check that user have not been deleted.

Having it setup this way - USER2GROUPS table for some users will require to get
the array from USER2GROUPS for userID at the time we need to check the permission
since the oViewer object only contains 1 group - default one per user. We can get it via cache
like u2g_$userID key, that's easy enough but still something is not 'beautiful' about it.

Another possibility - to allow pre-defined number of groups per user, say only 2 or 3
and then we can store then all in USERS table in 3 fields.

And another one - just stick to one group per user and when we need special permissions
when one group is just not enough we can use assertion and use userID

For example: to moderate specific forum a user must be in moderators group
AND be added to forum moderators for this one particular forum.

We can't possibly create a new group for every forum, that would be stupid
to have 40 extra usergroups and then if we want a user to be able to moderate
15 forums then we would have to add him to 15 extra groups. That's too much.

But with accertion that's easy, just pass oViewer to accertion, it will extract
userID and checks if it exists in array of users who can moderate that one forum.

Accertions can also easily be used to allow only 'Friends' of a user to add comments
or rate pics or something like that.

So, in conclusion: we start with one user = one group. The User implements the Acl_Role and getRoleId returns
the groupID (maybe with prefix like 'g2' for group2

but that may not be necessary, just cast groupID to string and we should be fine.

If at any time we would need the feature where user can belong to multiple groups
we can then add an extra table like USER2EXTRA_GROUP and then deal with it OR
go with comma separated groups list in USERS or even serialized array of groups in groups
which would searching by group_id impossible by the way.....

Or maybe we can have some clever mysql select trick that would return
comma-separate list of groups.

Anyway, for now just one man one vote (one group that is)
And use Assertions when just groupID is not enough.

Also need to decide in how to store permissions - probably in
unum type of field with possible values ALLOW,DENY,NONE where none is the default
meaning no permission.

2 comments:

  1. Thanks for the posts we are the professional web design and development company offering an array of services like template customization,web designing, CMS solutions, eCommerce solutions, Search engine optimization and Internet marketing.
    CMS DESIGN

    ReplyDelete
  2. I think this is among the most significant info for me. And i am glad reading your article. But wanna remark on some general things, The site style is ideal, the articles is really great and also i was enjoy in this webpage
    register website

    ReplyDelete