CakePHP and Acl - Why is it so difficult?
Posted on 31/5/06 by Felix Geisendörfer
The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.
Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.
Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).
Reason #1 - Lag of documentation
For something as important and often needed as Acl there is really a huge gap when it comes to documentation. The two main sources for me have been the Wiki and the Manual chapter about Acl but besides the fact that the information on those pages are rare there is another issue with them, which brings me to the next point:
Reason #2 - Wrong, Confusing & Outdated Information
If you try to figure out something as complex as Acl, it can really cost you hours to figure out why something doesn't work, if the examples you are trying to do are simply wrong or incomplete. Let me show you a couple of examples:
Example 1 - Accessing Aro/Aco Object
No matter where I've looked so far, I've always seen the Aro or Aco object used like this:
$aro->create( $user_id, $parent_id, $alias );
But (almost) nobody tells you what those objects actually are - Models! And how do we use Models in CakePHP? We put them to our $uses array!
var $components = array('Acl');
var $uses = array('Aro', 'Aco');
$this->Aro->create( $user_id, $parent_id, $alias );
Makes more sense doesn't it? I think following the CakePHP conventions on this one should be a good idea, because that way people will instantly see that we deal with custom Models here and not with some totally unknown objects.
Example 2 - The user_id field / Accessing Aros / Acos
One of the first problems you'll run into when working with Acl is that you need to be able to have a unique id for all the Items in your trees. Well If you just have users as Aros and they are only one level in depth, things are easy - usernames are unique and therefor good enough for identifying each element/aro. But what if you want to setup more complex systems that include users, groups, and possibly other Aros? Then each of them needs to have a unique alias. One idea is to use a prefix for different types like "User.John" or "Group.Admins", but I would much rather have a tree that is clean of such things and identify my items via unique id's so there won't ever be any problems with duplicated aliases. Well the wiki has one solution for that but you should easily see why it's just wrong:
$aro->create($aro->findCount()+1, null, 'User.' . $this->User->getLastInsertID());
The thing that can't work is the $aro->findCount()+1 statement to create the user_id for the aro. Because if you have 7 aro's and you delete number 3 this function will create a new aro with the id 7 which makes you loose your unique id's. So the only way to create unique user_id's is this one (using Aro as a Controller Model this time, as shown above):
$this->Aro->save(array('id' => $this->Aro->id, 'user_id' => $this->Aro->id));
This approach allows you to always keep the id and user_id field in your aro table in sync, making it easy to identify *any* element just by saving the aco_id ($this->Aco->id) to a field in the table of item's you want to be aro's (like users). And then you don't even need to use an ugly prefix like "User.23" as an alias in your tree, but can directly set it to "Jim", "Jack", or "Johnny".
Example 3 - Outdated stuff & Confusion
There are information spread out everywhere that are not up to date any more. Let me show you an Example from the Wiki:
Question (Olle): What do the fields lft and rght mean in the ARO table? /cake/controller/components/dbacl/models/aclnode.php refers to rght a lot. And the controller uses an “order by lft ASC”. Answer (ChrisPart): The lft and rght fields are for MPTT (Modified Pre-Order Tree Traversal), a method used to hold the tree structure of the permissions.
Question (Olle): Is the database field aros.user_id for integrating dbAcl with my other database tables holding user information? Answer (Chris Partridge): That field is used to identify the record in the DB, however there is no support for specifying the model which the id belongs to, so it’s best to set an alias (3rd parameter passed to create()) for each. I am using the following alias format, to easily identify records: $ModelName.$id. For example: User.27
Olle mentions the missing feature of specifying a model certain data belongs to by saying: "however there is no support for specifying the model which the id belongs to", but if you take the db_acl.sql from the latest nightly you'll see there actually *is* a model field in the table. Well nice, but how do I make use of that? As mentioned before Aro/Aco objects are Models, so you propably have to manually execute save() and find() statements in order to identify Aro/Aco elements by using the Model field. Which again, puts you in the need of having either a unique user_id or alias in your table (because all the Acl functions only take those to identify items). And since I already explained why I think it's a bad idea to force aliases to be unique, you need to have unique user_id's. Which brings me to my next Reason for Acl to be difficult:
Reason #3 - Why, oh why do we need the user_id field?
I think the user_id field is the worst part when working with acl. It's a numeric field that's ment to be unique across multiple Aro types like Users, Groups, etc., but it *does not* auto-increment (same goes for Acos with 'object_id')! So we have to figure out our own way to make it unique, which leads to more confusion. Why can't we just simple use the id field of the aro/aco table to identify elements? Would'nt the sun shine much brighter for all of use if the Aro::create() function would return the id of the Aro that was created so we could simply store it in our other models as the aro_id? Just look how much easier things would be:
$user = array('name' => 'John', 'pw' => 'secret-hash');
$user['aro_id'] = $this->Aro->create($parent_id, $user['name']);
This is how I feel Acl should be implemented, since it's just *way* easier for people to work out, and doesn't limit you in any ways. Using auto_incrementing id fields you could also use a combination of alias & model to identify users / groups without knowing their aco_id. I'll leave this idea for discussion since it's quite possible that I might be wrong on this since I'm no experienced Acl user, but in case people agree I'll open a RFC / Enhancment ticket on Trac for this. Meanwhile you can use the workaround I posted above if you want to go with this approach.
Reason #4 - MPTT What!??
One of the things that makes Acl very inaccesible to many of us is the fact that the AclTree's are stored with a method known as Modified Preorder Tree Traversal (short MPTT). While I don't want to go into great detail about how MPTT works I just want to point out why it's used. The advantage of trees stored in MPTT format is that you can retrieve (and build) the tree a lot faster then you can with the other method of using a parent_id. The only disadvantage is, that it takes longer to modify the tree (to delete or add nodes), but that's no issue for right management. The best resource about MPTT that I know of is over at sitepoint in their article called: Storing Hierarchical Data in a Database. If you look at the article you'll quickly be like "rght, lft, WHAT!?!?" and your enthusiasm about Acl will be gone before you've even started to get really into it. And as of right now, you'll not be able to work with Acl without having a certain understanding about the MPTT way of storing tree data.
Reason #5 - The incompletness of scripts/acl.php
Imagine you are new to this entire Acl thing, you have a project to finish, and all the stuff mentioned above is already making your life hard enough. All you want is a simple tool to play around & debug your Acl trees. Well, there is such a tool for the command line inside the cake/scripts/acl.php and for certain things it works like a charm. For others, it simply doesn't. Just 2 examples that'll ruin it for you right away:
Example #1 - View your Aco/Aro tree
You just added your first 4-5 elements to your Aro tree and now you want to see if the tree is stored in the db as you hope it would be. Well easy: Fire up the command line, switch to your /cake/scripts/ directory and type in "php.exe acl.php view aro" (windows). Voila, you see the Aco tree like you've created it. Hmm, but what are those numbers for? Ah right! We specified an $user_id for the Aro's we created, that's what it must be! Well ... Wrong! It's not the id you specified when you called $aro->create($user_id, $parent_id, $alias);, but it is the auto-incrementing primary key of your aros table. It's so easy to get this thing wrong in the beginning (especially if both numbers are the same for user 1,2,3) and it can cost you hours to figure out. I can't say the acl.php is incomplete on this one, but the documentation for it is. It's also questionable why the primary key id get's displayed when it *can not* (directly) be used to identify the elements when working with the Acl component. Another reason why I think user_id and object_id need to go.
Example #2 - Delete doesn't work
Well let's expect you've not figured out that the id's displayed by the "aro view" can not be used for identifying elements and you want to try to delete an item from your tree using one of those id's (which is what I went through). You type in "php.exe acl.php delete aro 2" and hit enter. An HTTP header get's returned and no error message shows up, hmm, did it work? Back to "aro view" - the item is still there. Must have been the wrong id, try it again, back to "aro view" - item is still there! What!? Alright you don't expect the delete function to be undone yet, because it would throw an error in that case (right?), so you try to figure out what *you* did wrong. Well it will take you a while to figure out that both is the case. delete() is undone, and you are wrong about the id's display by "aro view" ... What a mess : /.
Summing everything up
Why did I write this article? I wrote this article to point out a couple things that I struggled with when I started working with Acl a couple of days ago (including some earlier experiments with it as well), so people who still got their first Acl experience ahead of them don't have to go through the same problems. However, I'm sure there are still more then those I mentioned. I already see a dev asking why I've not opened tickets for all of this stuff so far and the answer is: "I will open them, but when you get into Acl for the first time, things are just so confusing that you really start to question yourself and get to the point where you think you don't know anything about programming. So give me some more days until I'm sure the stuff I've found is really worth a couple of tickets and you'll see them on Trac".
As said, I will use the next couple of days to figure out what tickets need to be opened, and to see if my idea of removing user_id and object_id is a product of my ignorance torwards the concept of Acl or worth an enhancement ticket. After that I'll do a proof of concept of my idea on how to integrate Acl into my SpliceIt! project, and if this works out I'll start a series of 2-3 articles where I'll explain everything I've learned about Acl together with a (small) real world example. I'll also release some source code that will help people to work with MPTT trees, allowing them to convert them into nested arrays and such. Meanwhile I would appriciate every comment on the stuff I've talked about above so I don't go off running into the wrong direction ; ).
PS: I know I've been sarcastic more then once in the article above, but it's not ment to offend anybody in the community or from the core dev's. I simply tried to express how I felt when working with Acl for the first time.
You can skip to the end and add a comment.
Nice article. I have yet to figure out ACL, then again, I havn't really tried. I like the idea of your Spliceit! project, it'll be really helpful to the rest of us who like building sites, but aren't that good at all the little details that keep popping up that we have to learn. Some premade plugins would put the Rapid back into RAD!
Thanks for this article! Acl support has been one of the last features I've been intending to add to my app, mainly because cursory looks at it have been daunting, to say the least. These pointers are great and much appreciated.
Great article. You should speak to the cake documentation project about getting it included :-)
thanks for the article!
it helps with a little clarification.
here is what i found when i wanted to properly delete an ARO from the tree.
you have to promote the item first.
use setParent to set it's parent to null, that moves it to the top AND end of the tree and sets the rgt and lft values correctly. You can then del() the id (primarykey) safely. without too much worry.
I spent a lot of time researching MPTT, and i understand what lft and rgt mean and how they work, but once you get on node out of place, things can go weird quickly.
what really needs to happen though, is that a better delete() method needs to be created, one that ensures the tree doesn't get broken and that removes relevant permissions from aros_acos as well.
oh, and i really don't get the action='*' example in the manual.
What is that supposed to mean? I thought it was a wildcard tye of thing, so i had a user_id that had read status but nothing else, based on the example in the manual, and i didn't get a true back. not quite what i was expecting, but what the hell, now i know.
oh, and i should add, that i haven't figured out how ACM works either. the documentation (and lack thereof) is confusing. People have mentioned it as a plugin that just fires up. Boy, is that wrong.
We need a plugin for managing ACLs that really is a drop in, good to go solution. One that asks you what table holds your authentication information and sets up the configuration.
I don't even understand how to write a plugin yet, so... that won't be me.
Thanks to all who have commented on this, I appreciate the fact that you read this big junk of text ; ).
I did not work with the delete function too much so far, but when I used it, it seemed to work (but I might have not checked the lft/rght values afterwards). I thought there is a function inside of AclNode called _syncTable (or sth. with sync in it) that takes care of that, but I could be wrong. If it's an issue I'll try to make a convenient solution for it.
Regarding MPTT in general, I've figured out who it works as well, but boy, writing a function that would create an nested array with the tree wasn't easy for me. If you got other functions for MPTT in CakePHP I'd be glad to include them in the articles I'm going to write, contact me at FelixGe@web.de / #cakephp about it if you are interested.
As far as a plugin goes, I'm in the process of writing one for SpliceIt! and SpliceIt! plugins are ment to work without changes in other projects as well. I'll keep you up to date on this one.
Meanwhile there is more Acl homework and research for me to do before I'll start to write the article. Next thing I'll do is post a list of tickets that I will open, so you can track the progress on them. Stay tuned ; )
[...] ThinkingPHP » CakePHP and Acl - Why is it so difficult? A great tutorial on CakePHP authentication system ACL (Access Control Lists) (tags: cakephp acl) [...]
hey, great post - guess we all run into the same problems sooner or later, and it's good to see some of them documented.
after a lot of wiki-bashing, especially in the context of acl-documentation, i'd really like to get some feedback on my wiki-entry about acl-access-checking. am i totally wrong with this concept? is the code bad or not up-to-date?
i'd love to see some kind of "official component" for acl-access-checking - felix: is the "rights management" in spliceIt! based on acl?
some comments on your examples:
example 1: although accessing the models this way would be more clean, it poses other problems on the user: he has to merge his $uses-settings in any subsequent controller with the already existent array from app_controller - and because this behaviour isn't really documented, i guess it leads to much more confusion than simply instanciating models in a slightly different way.
example 2: yeah, haven't really thought of this - changed it in the acl-access-checking methods :)
Useful article - I've just started looking into ACL for my first project with CakePHP (it's a kind of learn-as-you-go project :p)
Thanks for the article, it has helped me clear up a few points! I have been trying to get ACL to work, but has had many of problems as the ones you list above. The theory behind ACL is easy to undertstand, and the entry on ACL in the manual is great for explaning the basic concept. The problem has been to get from the textbook examples described in the manual, to the real world of CakePHP.
Here is a tip for when your directory layout is not the default (eg you re-arranged the cake, app and webroot folders a bit to suit your environment more)
Even if you configured the ROOT, APP_DIR etc in index.php in the webroot folder well so that your application works perfectly, the acl.php script can't 'know' these settings, but the good news is, you can pass these as arguments.
For example i had to do this to make it work:
php acl.php initdb -app -core
if i didn't do this, the script would choke on line 86 where it can't find the config/core.php
son of a... your comment system stripped away my "tags"
anyway the right command is this:
php acl.php initdb -app [path to your app folder] -core [path to your cake folder]
[...] Anyway, that's not really what I want to talk about today. One of the topics I have been very silent about for month is ACL. At the end of May I was somewhat unhappy with some of the things regarding the CakePHP DB ACL implementation. And it wasn't until last week that I finally decided to implement some basic rights management in one of my applications again. So since I didn't want to bother to frustrate myself with Cake's ACL again, I started to roll my own solution. While I was happily hacking away at some code, I suddenly realized that there were a lot of familarities between the code I was writing and the way ACL was working. A couple minutes later and I was already convinced that I basically had created 33 lines of code giving me pretty much all the flexibility CakePHP's well over 500 lines would ever give me. [...]
Great comments and information for ACL newcomers! I promise to get a new, enhanced version of ACM out in the near future to help people who need a ACL plugin for their apps which will also include full documention. How is SliceIt! coming along BTW?
hey rpeterson: This post is pretty old so some information might be outdated. I'm not working on SpliceIt! any more because my priorities have shifted, but I regularly get mails from people who are interested in it. Maybe a new development team will arise at some point?
Forget Cake's Acl and check out phpGACL. The manual is 1,000 times better and it has a web-based system for setting access controls.
^--- check out what Eric said. that's pretty sweet.
One big limitation to me is the fact, that a "user" cannot be member of multiple "groups". Or am I wrong?
boa dica cara!!!
mas gostaria que vc colocasse algum projeto,como exemplo!!!
Estou precisando usar ACL no cake,mas não estou conseguindo implementar!!
vlw, se poder me responder agradeço!!
It's WAY faster to manually build a query which returns all allowed results than relying on acl for access controll. especially because acl is url-based and not record-based.
Thanks, You wrote this article in 2006 on CakePHP version 1.xx may be. In 2009 I still don't understand why is it so complicated even in cake version 1.2. Whenever I try to solve a problem using CakePHP ACL, I get bored...
This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.
Heck, this article really puts ACL in perspective.
I, too, have a project that is in it's first stage and I was gonna attack the ACL stuff, so YOU saved me tones of problems with this little article!!
Many THANKS mate !!