ACL Implementation

After doing a few posts on Access Control Lists (ACLs), the need to look further into the implementation of ACLs in a CakePHP project could be helpful. If there are questions on setting up the ACL tri-table in the database, you can review the previous postings, or check out the CakePHP documentation. But now that you have the ACL tables set up, how does it actually work?

First, the ACL happens after authentication. So whether or not you are using the Auth component, you will still need to authenticate the user some how, some way. Then once the user is authenticated and logged in, that user will have permissions to do different thing. Let’s say one of those things is to edit accounts. If it is a regular user, he should be able to edit his own and no one else. If the user was a “site admin” he should be able to edit his own and any account that is not a “super-admin”. If he is a super admin then he should edit everyone’s account. However, the first part of this is setting up the initial ACL permissions.

This step happens right on the account creation action. After the user has been created, and we grab the last inserted ID, we can update the ACL tables.

$usr = $this->User->getLastInsertID();

// Set up the ARO data for this user, assign as a member only at this point. 
$aro = new Aro();				
// Create the info for this new user
$user = array(
	'alias' => $this->data['User']['username'],
	'parent_id' => 4, // member ARO 
	'model' => 'User',
	'foreign_key' => $usr
);				
// Create the new ARO for the user
$aro->create();
$aro->save($user);
/**********************************************************************/
// Users are a little crazy because they are also ACOs
$aco = new Aco();
$aco_usr = array(
	'alias' => $this->data['User']['username'],
	'parent_id' => 2, // User ACO id
	'model' => 'User',
	'foreign_key' => $usr
);
// Create the new ACO for the user
$aco->create();
$aco->save($aco_usr);

// A User may update itself but not delete itself.  
$this->Acl->allow( array('model' => 'User', 'foreign_key' => $usr), $this->data['User']['username'], 'update' );
$this->Acl->deny( array('model' => 'User', 'foreign_key' => $usr), 'Users', 'delete' );

$this->Session->setFlash(__('The User has been saved', true));
$this->redirect(array('action'=>'index'));

This will provide for the base permissions for the user.

Remember at this point, we are looking at ACLs from a user model only. If we wanted to extend this to groups, posts, calendars, etc we would add this in here as well. Say we also had a “posts” feature in our application, and we wanted to let the user create their own posts, edit their own posts, but no one elses:

$usr = $this->Auth->user('user_id');
$aco_posts = array(
	'alias' => $this->data['Post']['post_id'],
	'parent_id' => 4, // Posts ACO id
	'model' => 'Post',
	'foreign_key' => $usr
);
// Create the new ACO for the user to post
$aco->create();
$aco->save($aco_posts);

// A User may add, edit or delete their own posts, but not other user's posts
$this->Acl->allow( array('model' => 'Post', 'foreign_key' => $usr), $this->data['Post']['post_id'], 'update' );
$this->Acl->allow( array('model' => 'Post', 'foreign_key' => $usr), $this->data['Post']['post_id'], 'delete' );

So for anything that you want to limit the user access to, this is your chance. But remember the old saying “Keep it simple stupid”. Only do something if it makes sense and the project calls for it. I usually only do a User ACL in the users controller.

Now when you a user wants to go to an edit action after they are logged in, we need to check to make sure they can do this action:

$info = $this->User->read(null, $id);

// Check for permissions to edit this account
if ( !$this->Acl->check(array('model' => 'User', 'foreign_key' => $this->Auth->user('user_id')), $info['User']['username'], 'update') ) {
	$this->Session->setFlash(__('You are not allowed to edit this user. -- ' . $this->Auth->user('user_id'), true));
	$this->redirect(array('action'=>'index'));
}

The above code, broken out:

  • $info gets all of the data about the id to be edited. It grabs the data from the table and stores it in the $info array
  • Does a check to verify that they have permission, always checking for a NOT to be on the safe side
  • The Acl->check is looking at the User model, since that is the object that is going to be edited (ACO)
  • The Auth->user(‘user_id’) is the object requesting permission to do an edit (ARO)
  • $info[‘User’][‘username’] is the exact ACO that will be updated
  • ‘update’ is the action that will be taken on the exact ACO

If the test does not pass, and the currently logged in user does not have permission to update the user_id, then they will be redirected to a the index page for the controller.

Let’s also take the example above with the Posts, and lets say that a user is wanting to edit a post. At the very top of the function, we would put in a very similar check:

$info = $this->Post->read(null, $id);

// Check for permissions to edit this account
if ( !$this->Acl->check(array('model' => 'Post', 'foreign_key' => $this->Auth->user('user_id')), $info['Post']['post_id'], 'update') ) {
	$this->Session->setFlash(__('You are not allowed to edit this post. -- ' . $this->Auth->user('user_id'), true));
	$this->redirect(array('action'=>'index'));
}

In this example, we just replace User with Post and the info array elements. The same type of check could be done for delete, create and read actions in the controller. As you can see, this check could be done in a function in the app_controller.php file for a central location, which I will get into the next post.