How do I restrict a user's access to an object?

Hi genius brethern,

I have an admin application that has multiple user types and various objects. What I’d like to do is control access within the object itself - that is, it will behave one way for one type of user, and another way for other users. For example…

Director Mike can override Reception user Sally’s registration date. One would assume that Mike could set any date both in the past or in the future. Then we have Payroll user Steve who can also modify Sally’s registration date, but only for dates in the past up until (for example) one year ago. To spice things up, then we have the HR Manager Mary who can also amend Sally’s registration date, but only for dates from precisely 23rd June 2007 up until one month from now…

How can I program the access restrictions so that on the front end, the form control is restricted with a min and max date, and in the backend, the validator checks the entered date to make sure it falls between those dates? I’d obviously need to be able to tweak the min and max dates for each user type. Other objects might have different parameters - maximum amount on a discount field or days of the week for overtime, for example.

I’ve trawled through all the major frameworks to see if they have this type of thing, but alas, no. AXO’s in phpGACL come close, but no cigar…

What do you even call this type of access control…?

I would make some sort of role system and in the object, check for role and change behavior based on the role



switch ( $User.Role)
{
  case (Role.Admin):
    $min = "01.01";
    $max = " 31.12";
    break;
  case (Role.Secretary):
    $min = "01.05";
    $min = "01.08";
    break;
  ....
}

You can pull those max and min values from somewhere else, like a database or a config file. Or even calculate them based on some constant :slight_smile:

Thanks for your reply -T-

Embedding the behaviour in the object means that I wouldn’t be able to update it as and when. Also, if a new user type were to be created, then there wouldn’t be any rule for them to follow.

I’ve been mulling it over and would like it if my access control did something like the following. Say each user was to get a $permissions array or object:

// In the HTML <body>.
$body[] = html('text', array('name' => 'discount', 'value' => $objects['discount']['value']));
// In the document <head>.
$head[] = html('jQuery.onready', array('function' => '$("#discount").rules("add", {required:true, min:' . $permissions['discount']['min'] . ', max:' . $permissions['discount']['max'] . '});'));

That would create HTML like:

<head>
	<script>
		$(document).ready(function(){
			...
			$("#discount").rules("add", {required:true, min:0, max:15});
			...
		});
	</script>
</head>

<body>
	...
	<input type="text" ID="discount" name="discount" value="5" />
	...
</body>

(The html function could also be a method of a UI class. I also know that the Javascript ‘Model’ expression is now in my ‘View’ - but does it count as presentation or logic?)

How would I go about populating the $permissions array/object? Where do they come from?

This is all new territory for me - please let me know if this type of thing exists somewhere already or am I going down the wrong path!

instead of allowing the object to decide how it should behave itself, i would either have a proxy that would filter/validate the setter code based on an rbac or acl lookup.

Alternatively, the state pattern springs to mind - when the object is “owned” by a certain role, it modifies it’s state to allow the behaviour defined by that role.

Another simplified way, abstract all the common functions into a base object then create concrete implementations for each role inheriting from the base.

Create a new folder for each role, usings a unique name, each folder will have a concrete object, you can then determine which object to use at runtime after validation.

this means you split your implementation but give you flexibility.

You could use zend acl to define permissions like ‘can_delete’, ‘can_update’, store the acl in a global and check in each function , that still wont help if you want extra class properties.

@skinnymonkey, icomefromthenet: I don’t suppose you have any examples or links to someone who does?

Surely this is a design problem that has been answered elsewhere?


class User implements User_Interface
{
    protected
        $name   = null,
        $email  = null;
    
    public function __construct($name, $email){
        $this->name = $name;
        $this->email = $email;
    }
    
    public function getName(){
        return $this->name;
    }
    
    public function getEmail(){
        return $this->email;
    }
}

class User_Proxy implements User_Interface
{
    protected
        $user   = null,
        $acl    = null;

    public function __construct(User_Interface $user, $acl){
        $this->user = $user;
        $this->acl = $acl;
    }
    
    public function getName(){
        if(true === $acl->permits('user.getName')){
            return $this->user->getName();
        }
        throw new Access_Denied_Via_Acl_Exception();
    }
    
    public function getEmail(){
        if(true === $acl->permits('user.getEmail')){
            return $this->user->getEmail();
        }
        throw new Access_Denied_Via_Acl_Exception();
    }
}

It should be pretty explanatory, but give me a shout if not. :slight_smile:

AnthonySterling, I would use the __call() magic function, so the proxy object can be dynamic.

Yes, possibly.

However, this would not work if the OP had object type hinting on this objects dependencies.

Of course, they would also have to have type hinted the interface too for this to be applicable.

:slight_smile:

@AnthonySterling: thanks for your reply!

I’m happy with the classic boolean ALLOW/DENY access list permissions like you outline in your post, but I’m still at a loss as to how to include the permission particulars like I mentioned before - for example the registration date problem above;

user type 1: DENY any access
user type 2: ALLOW readonly
user type 3: ALLOW EDIT minimum date “-1 year” maximum date “now()”
user type 4: ALLOW EDIT minimum date “23rd June 2007” maximum date “+1 month”
user type 5: ALLOW EDIT minimum date “none” maximum date “now()”

I can implement the classic ACL to choose whether to display the object or not, but I’m still not sure how to sneak in the minimum/maximum validation on the front end (by way of jQuery) or have the backend check it without code duplication…

It sounds like two distinct roles, related yes, but certainly separate.

Specification, then validation against said specification.

To enable the transfer of the specification to jQuery, you could look at using native arrays along with [fphp]json_encode[/fphp] to compose the specification.

Although, untested, I’d look to extend ArrayObject.


class Permissions extends ArrayObject
{
    public function import(Permissions $permissions){
        $this->exchangeArray(
            array_merge(
                $this->toArray(),
                $permissions->toArray()
            )
        );
    }
}

This would allow you import other ‘specifications’ and render to a native array for jQuery.

This is how I usually do it, and worked so far.

#1 set users in groups
#2 devide your project in ‘modules’ (i usually have each model as a module)
#2 set permissions to groups for each module
That way, if the permissions change, you just move the user in the right group, and each group has access to features on your site.
If you add a new module, you have to add it to the permissions.


# dtoAbstract takes care of all the DB interaction
class myModel extends dtoAbstract {
  
  # Normally, this loads the passed in $id, but this time it will check the permissions also.
  public function __construct($id, $user) {
    if (ACL::access($user->group, 'module1', 'read', array($user))) {  # Can the user READ this object?
      return;
    }
    return parent::__construct($id);
  }

  # This usually saves the object, but this time it will check the permissions of the passed in user.
  public function save($user) {
    if (ACL::access($user->group, 'module1', 'write', array($user))) {  # Can the user WRITE this object?
      return false;
    }
    return parent::save();
  }
}

The code in your ACL class would have something like this in it:


# load the permissions (this gets cached between requests), and ends up with some array like:
$permissions = array (
    'group1' => array (
      'module1' => array (
          'read' => 'ok'
        ),
      'module2' => array (
          'read' => 'ok',
          'write' => 'ok'
      ),
    ),
    'group2' => array (
      'module1' => array (
        'read' => 'ok',
        'write' => 'specialFunction'
      ),
      'module2' => array (
        'read' => 'specialFunction2',
        'write' => 'specialFunction2',
        'special' => 'specialFunction3',
      ),
    ),
 );
# Basically
# $arr[group name][module name][permission name] = "function to call that returns true if all good"
# The function gets as parameter the object to verify

public static function access($group,$module,$permission, array $data = null) {
  // make sure we have this permission
  if(!$permissions[$group][$module][$permission]) {
    return false;
  }
  // hate typing
  $func = $permissions[$group][$module][$permission];
  // normal permission
  if ($func === true) {
    return true;
  }
  // we call the special permission function
  return call_user_func_array(array("ACL", $func), $data);
}

public static function specialFunction($user) {
  return ($user->hasGreenEyes == true);
}

public static function specialFunction2($user) {
  return ($user->age > 25);
}

public static function specialFunction3($user, $otherObject) {
  return ($otherObject->value > $user->minValue);
}

The idea is this:

  • when you design the system, you build your groups, modules and permissions.
  • then after, users can be moved between them without any problems.
  • if you have a new special conditions for a page or part of a page, you just add a function for it.

Ps: this code is more or less pseudo code.

Thanks for the updates folks.

@Vali, that looks like where I’m headed, except some of those static functions specialFunction etc as you name it would be dynamically created from a name stored in the db (still not sure how I’d go about that yet!)

@AnthonySterling - you’re right validation and specification - the specification needs to be stored in the db and override the ‘default model’ at runtime. The trick is to have one coherent Model then then two Views as such - one regular HTML View and one JSON array View destined for my jQuery goodies…

What are the community’s thoughts if I paste up here what I end up with? It just seems that since I haven’t seen any implementations of this maybe it’d be good to start a mini-project?

boatingcow: sure you can post what you end up with.
As for my specialFunction, you need a place to add your

ALLOW EDIT minimum date “23rd June 2007” maximum date “+1 month”
functionality. (the dates could come from the db, but the IFs and so on, you need that code somewhere.