While there are plenty of tutorials out there covering how to use CakePHP’s ACL Component, and getting it to jive with CakePHP’s Auth Component, most of the tutorials out there take different approaches towards implementing this technology. If this is your first time using ACL, you might be feeling a little confused about exactly how ACL works, and if you’re like me, diving through all of these conflicting tutorials (some which use deprecated functions based on prior versions of cake) only serves to further confuse.

So now that I’ve spent a good couple of days wrapping my head around this concept, I thought I’d share what I’ve learned with you, so hopefully you can get this up and running much quicker than I did. To do this you’re going to need to understand some basic concepts of ACL, reading up on this in depth will only help you. I’ll try and point out some of the major pitfalls I ran into, so you don’t make the same mistakes. I’m not going to give you code, since this will just further confuse you. You’re going to have to write a fair bit of code on your own to get this working for your application. But I will provide a link to my source code for my project and an sql dump of my ARO tables so you can see what they should look like at the end of the tutorial.

Step One, What You Need to Learn

ACL basically defines what actions each person or group (AROs) can do to each item (ACOs). The important thing to remember is that an ARO and an ACO are really exactly the same. In a web application, everything is a row in a table. Each user or group or post or comment is just a row in the User, Group, Post or Comment table. So what we are doing is saying, when someone is logged in as a certain row in the User table, which rows in the Post table are they able to access? Groups are used so that users can inherit permissions. This saves us effort and results in fewer rows in our ACL tables because we don’t have to redefine permissions for each user, they simply inherit the permissions of the group they are in. A user can have a combination of any of the following permissions on an item: create, read, update and delete.

Setup the ACL tables by following the ACL section of the CakePHP manual. Once you have it setup, open up phpmyadmin or cocoa and have a look at the tables that were created, they should be acos, aros, and aros_acos. Lets look at the fields in these tables to understand what is going on. If it helps, whenever you see ARO think User, and whenever you see ACO think Post.

The acos and aros tables

These tables are identical. This is because (as I said before), everything in a web application is just rows in tables. A User is just a row in a table, and a Post is just a row in a table. When a user is logged in and tries to edit their profile, we tell the application to check if the ARO that corresponds to that users ID has permission to update the ACO that corresponds to the users ID. If this permission is setup in the aros_acos table, then we let the action take place, otherwise we block it. Here is an explanation of each field in the aros and acos table:

  • The “id” field is simply the primary key.
  • The “parent_id” field contains the id of the parent ARO or ACO. For example, if you have a row in the ARO table for the Admin group with an id of 3, then the User who belongs to the Admin group would have a parent_id of 3. Both AROs and ACOs can have parents, this means you can let all AROs in a group edit an ACO, or you can let one ARO edit all the ACOs in a group.
  • The “model” and “foreign_key” field tells ACL which row in which table this ARO or ACO controls. For example if you are creating an ARO for the user in your User table with an id of 4, then the “model” would be “User” and the “foreign_key” would be 4. If you are trying to create a group that doesn’t have a model associated with it these fields can be NULL.
  • The “alias” is a simple keyword used to quickly identify an ARO or ACO. You want this value to be unique. I use the following format for my User aliases “User::80″ where 80 is the id of the User in the User table. For an Admin group I simply use “Admin”. One of my first mistakes was trying to use the alias to describe what group a user was in. So the Administrator user has the alias “Administrator::User::80″. There’s no point to having this kind of “path” as an alias, and it only complicates things.
  • The “lft” and “rght” fields get filled in automatically as long as you use the correct “parent_id”. From my understanding this is how ACL makes inheritance work. Basically if your Admin group has “lft” and “rght” values of 1 and 4, then the user with “lft” and “rght” values 2 and 3, will inherit the permissions of the Admin Group.

Remember that a row in your applications table will sometimes be both an ARO and an ACO. If you have a user profile, you want to make sure that when this user is logged in, they are able to edit their profile. Therefore you have to grant “Update” access for the ARO User::20, to the ACO User::20.

The aros_acos table

This is the table that defines what each aro can do to each aco. Simple enough? The fields are as follows:

  • The “id” field is simply the primary key.
  • The “aro_id” field is just the row in the AROs table we’re defining permissions for.
  • The “aco_id” field is just the row in the ACOs table we’re defining permissions for.
  • The “_create”, “_read”, “_update”, “_delete” fields are the actions we are allowing. If its empty, its not allowed, if it is 1 it is allowed, placing a -1 in all columns denies all actions.

Step Two, Setting Things Up

To set things up I recommend creating an init_acl controller, with a function inside called initAll() that creates all the default users and groups you want to have. If you end up messing things up, you can call this function to flush your tables and rebuild them from scratch. You can also use the console to do this, but I like the idea of doing this with PHP. Remember to delete or secure this controller once you put your site live ( You can use ACL to do this :) ).

In this controller, first create all the groups that you require. A group is just a row in the aros table. There are two kinds of groups, those based on models and those that are purely theoretical. The difference being that, if you have a Model that represents a group ( Let say you can register your baseball team which adds a row to the Team model with the id 101 ), then when you create this group, set the “model” field in the aros table to “Team” and the “id” to 101, and the alias of this ARO would be something like “Team::101″. You can also have theoretical groups, that aren’t represented by models, but just segment your Users by what actions they are allowed to do. Lets say some Users can delete posts and others can’t, you can have a group with the alias “Users_Deleters” and a group with the alias “Users_Posters”. In this case you can leave “model” and “foreign_key” blank.

What happens when you have groups that can be created by the users of your website? What if these newly created groups have sub groups? Basically you set it up so that creating a group, creates the ARO for that group, and then also creates additional AROs for the subgroups. For example, you register your baseball team that has two sub groups, infielders and outfielders, when the new Team ARO is created, you also create an Infielder and Outfielder ARO whose “parent_id” field points to the Team ARO. A good place to do this is in the AfterSave() function of your model.

Restricting Users, and Handling Guests

So now hopefully you understand the concept of how AROs and ACOs are set up. Now let’s cover how permissions are defined.

To define permissions you simply state the alias for the ARO of the user, the alias for the ACO they want to perform an action on, and the type of action they hope to perform. Lets say the User with “id” 72 wants to edit the post with “id” 33. We check if “User::72″ has access to update “Post::33″. Simple as that. Remember that ACL automatically takes care of inheritance, so even if User 72, hasn’t explicitly been given permission to edit Post 33, if the group that User 72 belongs to has the permissions (and we haven’t overwritten it by explicitly blocking User 72) that user will be able to update that post. This check is performed in the function in your controller that allows the action to occur.

But what about Users that aren’t logged in?

A good way to do this is to pretend like they are a logged in User. Basically if no User is set, you tell your application to treat them as the ARO you have defined as the “Guest” ARO.

The Code

Here is a sample zip file with my controllers / models / behaviours as well as an SQL dump of the related tables, which demonstrates these concepts so you can get a better idea of how to code all of this.

Quick Code Tips

Have an issue with your permissions getting overwritten with the previous one? Use this line of code to ensure you’ve created a new row in the aros_acos table before you create the new permission:

     $this->Acl->Aro->Permission->create();

If you want to automate the creation of ARO and ACO nodes by placing them in the afterSave function of your model use this code:

     $aro = new Aro();
 
     $aro->create();
     $aro->save(array(
          'parent_id' => $parentid,
          'model' => null,
          'foreign_key' => null,
          'alias' => "Alias"
     ));

If you want to automate the creation of permissions by placing them in the afterSave function of your model use this code:

     $acl = new AclComponent();
     $aro = new Aro();
     $admin = $aro->findById($adminId);
     $acl->Aro->Permission->create();
     $acl->allow($aroAlias,$acoAlias, array("create","read","update","delete"));

To retrieve an ARO or ACO to use as a parent use the following code:

     $aro = new Aro();
     $parentAlias = "User::".$this->id;
     $parent = $aro->findByAlias($parentAlias);
//or
     $aco = new Aco();
     $parentAlias = "User::".$this->id;
     $parent = $aco->findByAlias($parentAlias);

If you want to tie your current login session to an Aro node, you simply use this code in app_controller.php’s beforeFilter() function.

     if(!$this->Auth->user()) {
          $guestGroup = $this->Acl->Aro->findByAlias('User_Guest');
          $guestUser = $this->Acl->Aro->findByParentId($guestGroup['Aro']['id']);
          $this->current_user = $this->User->findById($guestUser['Aro']['foreign_key']);
     } else {
          $this->current_user = $this->Auth->user();
     }

Then in your controller, to construct aliases and check permission of a user to edit a profile use the following:

function profile($id=NULL) {
     $aroAlias = "User::".$this->;current_user['User']['id'];
     $acoAlias = 'User::'.$id;
 
     if ($this->Acl->check($aroAlias, $acoAlias, 'update')) {
          echo 'Update access allowed for User Id'.$this->current_user['User']['id'];
     } else {
         echo 'Update access denied for User Id'.$this->current_user['User']['id'];
     }
}