Jump to content

Designing a ACL / User Permissions system


Xeoncross

Recommended Posts

I need to figure out how to implement a user/group access control system. I started by creating roles (admin, mod, author, member) and then setting in a config file resources (like "add post") and the maximum level a user must be to access it.

 

 

<?php
if($resouce['level'] >= $current_user['level']) { 
    allow;
} else { 
    deny;
}
?>

 

 

I made the "Admin" level "1" and everything else higher (mod = level 2 and so on). So if a resource demanded a level of a most "2" - then only mods and admins could access it. I figured I could store resources in a config file and just add to it whenever there was a new one.

 

<?php
$resouce = array('add post' => 2,
                'edit post' => 2,
                'read post' => 6);

?>

 

 

I took a look at <a href="http://framework.zend.com/manual/en/zend.acl.html">Zend Framework</a> and found that while it was pretty much the same thing - it was a bit more structured. However, I didn't like the mess that it created:

 

 

<?php
require_once 'Zend/Acl.php';
$acl = new Zend_Acl();

require_once 'Zend/Acl/Role.php';
$acl->addRole(new Zend_Acl_Role('guest'))
    ->addRole(new Zend_Acl_Role('member'))
    ->addRole(new Zend_Acl_Role('admin'));

$parents = array('guest', 'member', 'admin');
$acl->addRole(new Zend_Acl_Role('someUser'), $parents);

require_once 'Zend/Acl/Resource.php';
$acl->add(new Zend_Acl_Resource('someResource'));

$acl->deny('guest', 'someResource');
$acl->allow('member', 'someResource');

echo $acl->isAllowed('someUser', 'someResource') ? 'allowed' : 'denied';

?>

 

One object, two at most should be enough. Why there are 6 here is beyond me. However, since it is Zend I would expect them to know what they are doing.

 

Now, how should I structure this system? should I try to store every resource and it's level in a DB table? What if there are 50 different resources? - I don't want to keep making calls to the DB every page. How should I build the user object? Does anyone have any ideas about this?

 

 

Link to comment
Share on other sites

There is no easy answer to your question.

 

Yes, Zend_Acl works fine, as long as you don't have too many "exception rules". As very short ACL goes a long way. As such, you can do something that I would normally (and do so in this case - but hey) frown upon: Serialized LOB.

 

Just serialize the whole list and put it in the database. On every request you need to recreate it to check permissions and possibly modify it.

 

I know, this ain't pretty, but the alternative you'll like even less.

 

Link to comment
Share on other sites

Here is a basic ACL system I just plotted out. The basic idea is to check to see

1) is the user an ADMIN?

2) is the user level lower than the required level? (are they 1+ stronger)

3) if the user level is equal (allowed) -check to see if the author id is the same as the user id so that a runaway author doesn't destroy other authors work.

 

Can anyone add to this?

 

<?php

//////////////////////////////////////
//We would need Three types of info to make a ACL

//A list of resources
$resources = array('plugin1' => array('edit' => 2,
                                    'add' => 2,
                                    'delete' => 1
                                ),
                    'plugin2' => array('view' => 4,
                                    'add' => 3,
                                    'delete' => 2
                                )
                );

//A list of groups/roles
$roles = array('', 'admin', 'mod', 'author', 'guest');

//a list of users
$users = array('John' => 1, 'Jane' => 3);




//We could make one global $user object that would have 
//the current users information.
class user {
    
    
    //call a function here to fill this class with
    //the current users information (from a DB)
    var $data = array('id' => 4, 'name' => 'John', 'level' => 2);
    
    
    //Check the value of this users data
    function value($value) {
        return $this->$data[$value];
    }
    
    function allowed($resource=null, $author_id=null) {
        
        //If they are admins allow anything
        if( ($this->data['level'] == 1) ||
        
            //Or if the level of the user is less 
            //than the level required.
            //(this lets them edit weaker users stuff)
            ($this->data['level'] < $resource) ||
            
            ( 
                //Or if the level of the user is equal 
                //to the level required AND
                ($this->data['level'] == $resource) 
                &&
                //They are the author (only applies if author_id isset)
                (($author_id ? $this->data['id'] == $author_id : true)) 
            )
        ){
            //print 'allow';
            return true;
        } else {
            //print 'Deny';
            return false;
        }
    }
}

//create the object
$user = new user;


if($user->allowed($resources['plugin1']['add'])) { 
    print 'Allowed<br />';
} else {
    print 'Denied<br />';
}


if($user->allowed($resources['plugin1']['delete'])) { 
    print 'Allowed<br />';
} else {
    print 'Denied<br />';
}




?>

 

The output if run is:

 

Allowed
Denied

 

Link to comment
Share on other sites

You're going to run into a problem here:

 

<?php
    //call a function here to fill this class with
    //the current users information (from a DB)
    var $data = array('id' => 4, 'name' => 'John', 'level' => 2);
?>

 

Give it a try. I have the proper solution for you, but as I said, you're not going to like it.

Link to comment
Share on other sites

You're going to run into a problem here:

 

<?php
    //call a function here to fill this class with
    //the current users information (from a DB)
    var $data = array('id' => 4, 'name' => 'John', 'level' => 2);
?>

 

Give it a try. I have the proper solution for you, but as I said, you're not going to like it.

You call the DB $row of the authenticated user_id set in $_SESSION. then you place that row of data in the class - I already do it and it works fine. That way the current users data is only needed once for any plugin that wants it.

 

 

@Xeoncross: Then what would you do if you wished to add another group which has less rights than 'admin' but more rights than 'mod'?

 

This code is as much pseudo-code as it is real code. In reality you would use the group name instead of hard-coding the group number in, that way you COULD add more groups.

 

<?php
//A list of resources
$resources = array('plugin1' => array('edit' => 'admin',
                                    'add' => 'mod',
                                    'delete' => 'admin'
                                ),
                    'plugin2' => array('view' => 'guest',
                                    'add' => 'aurthur',
                                    'delete' => 'mod'
                                )
                );
?>

Link to comment
Share on other sites

I admit, I didn't really examine your code.

 

But, your title says you're using ACL, and you are in fact NOT, hence my misinterpretation.

 

If 'John' was an 'admin' and admins get level 2, how do I dissallow anything that admins would be allowed?

 

I think you need to look into ACL. Sure, you can make this work, but it's not ACL, and will never be as flexible.

Link to comment
Share on other sites

How would you do that? Your authorization check is based on checking if integers are greater or lower than another integer.

 

Perhapes it isn't ACL - but it is a User Permissions system. ;)

Can't you imagine a more complex array?

 

<?php

$resources = array('plugin1' => array('edit' => 'admin',
                                    'add' => 'mod',
                                    'delete' => 'admin'
                                ),
                    'plugin2' => array('view' => 'guest',
                                    'add' => 'aurthur',
                                    'delete' => 'mod'
                                )
                );

//A list of groups/roles
$roles = array('admin' => 1, 'mod' => 2, 'author' => 3, 'guest' => 4);

if($user->allowed($roles[$resources['plugin1']['delete']])) { 
?>

Link to comment
Share on other sites

Again, you can make this work. In fact, I imagine you can make it even simpler with the same functionality.

 

But it's not by far as flexible as ACL. Aside from my previously posted question, which you've avoided, let me ask you another, more pressing question:

 

What if I request plugin9999999999999999?

Link to comment
Share on other sites

Sorry, you had posted your last question after I had already requested the reply form.

At any rate, would you re-fraze the question? I don't understand what you're saying.  ;D

 

If 'John' was an 'admin' and admins get level 2, how do I dissallow anything that admins would be allowed?

 

let me ask you another, more pressing question:

 

What if I request plugin9999999999999999?

 

What is the problem? go ahead and request it. If the value is not in the array (null/false) we will do a check and default to false.

 

Also, would you mind sharing any links you have on ACL?  ;)

Link to comment
Share on other sites

Sorry, you had posted your last question after I had already requested the reply form.

At any rate, would you re-fraze the question? I don't understand what you're saying.  ;D

 

Simple: if admins are allowed to edit plugin1, how do say that John, who is an admin, is not allowed to do so?

 

What is the problem? go ahead and request it. If the value is not in the array (null/false) we will do a check and default to false.

 

You're ignorant of the underlying issue. Doing so would would imply that I would have to explicitly define elaborate rules for EVERY resource available in the system. Imagine a system as populated as the PHPFreaks forums. If I need to establish rules for every post, that would result in a HUGE database and the processing time accompanied with it. You can add so called exception logic, as you did with by assigning special rights to authors, but again, this is not flexible.

 

As I said, try and map it to the database.

Link to comment
Share on other sites

Simple: if admins are allowed to edit plugin1, how do say that John, who is an admin, is not allowed to do so?

"how do say that John" makes no sense to me  ;)

If you asking "If admins are allowed to edit "plugin1"; how is it that "John" (who is an admin) is not allowed to do so?"

 

The answer is that IF John is apart of the "Ultimate/admin/whateversuperuseriscalled" group - he can do anything.

 

You're ignorant of the underlying issue. Doing so would would imply that I would have to explicitly define elaborate rules for EVERY resource available in the system. Imagine a system as populated as the PHPFreaks forums. If I need to establish rules for every post, that would result in a HUGE database and the processing time accompanied with it.

 

For a system like SMF you would have three rules - hardly elaborate.

Level to post posts

Level to edit own posts

Level to delete posts

 

Then only users with the same id as the post id (or 1up in level from the level stated || or level "ADMIN") could edit/delete posts. I would imagine that this system would take about 30 rules for a complex site like this.

 

As I said, try and map it to the database.

Yes, that is the real test.  ;D

 

CREATE TABLE `roles` (
`id` INT( 2 ) NOT NULL ,
`name` VARCHAR( 100 ) NOT NULL ,
`weight` INT( 2 ) NOT NULL
)

CREATE TABLE `resources` (
`id` INT( 10 ) NOT NULL ,
`name` VARCHAR( 100 ) NOT NULL ,
`plugin` VARCHAR( 100 ) NOT NULL ,
`role` INT( 2 ) NOT NULL ,
PRIMARY KEY ( `id` )
)

The "plugin" column would be for something like "posts" that way you could group "edit", "add", and "delete" by "plugin"

 

Link to comment
Share on other sites

Simple: if admins are allowed to edit plugin1, how do say that John, who is an admin, is not allowed to do so?

"how do say that John" makes no sense to me  ;)

If you asking "If admins are allowed to edit "plugin1"; how is it that "John" (who is an admin) is not allowed to do so?"

 

The answer is that IF John is apart of the "Ultimate/admin/whateversuperuseriscalled" group - he can do anything.

 

He asked how you would deny John access despite the fact that he is in the admin group.

Link to comment
Share on other sites

He asked how you would deny John access despite the fact that he is in the admin group.

 

That is impossible. Why would you deny the highest level user?

You are thinking about this too literally. Don't let the fact that I choose to call that group

"admin" and the fact that other sites have groups called "admin" mess you up. You can call

it "admin", you can call it "super user" you can call it whatever you want - but if they are

in that group THEY control all.

 

If John isn't trustworthy - place him in a group called "Site Admin" that is less than "Admin".

Link to comment
Share on other sites

Or, John is just a regular user, normally not allowed to view admin topics, but on this particular topic, he can view it.

 

That is a good point and a valuable thing for complex sites. However, I don't think that average small sites would have that problem - and even if they did there are other ways of getting around it. In all my sites I know that I have never had that need. What if we just leave that part off for now?

 

Unless of course that is the only thing separating my idea from a valid ACL...?

Link to comment
Share on other sites

Unless of course that is the only thing separating my idea from a valid ACL...?

 

It is not. Your idea is conceptually different from ACL. You can add exception rules, but that doesn't make it nearly as flexible as ACL.

 

EDIT:

 

A word of advice: just use Zend_Acl and store the ACL serialized. It'll do what you want and more, no reason to reject it.

Link to comment
Share on other sites

 

Ok, I finally finished a working version of this idea. This is a Object based script that will demonstrate a basic Permission system. The only thing that I am worried about is the fact that I have to query the database for all the resources/rules. Which means it takes over .004 seconds for the whole script to run.

 

To run this script:

 

First, run tables.sql on your MySQL databse.

Second, open db.php and add your db info.

Third, browse to ../acl.php

 

Let me know what you think

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.