Update: Row-level Model Access Control for CakePHP
so, there apparently is a bit of scuffle about my last post, so let me clear things up.
first off, this is my first real blog, and first real blog post of any substance, so i’m still learning.
secondly, i wasn’t attacking cake, cake devs, or cake ACLs. all of the above rock the llama’s ass.
thirdly, cake devs have said that using cake ACLs for row-level access/deny is probably not the best way to accomplish what you probably want to do. nate abele’s opinion as to whether or not any cake dev would say that is irrelevant, as it was said, for better or worse. i happen to agree with the sentiment, not because cake’s ACL implementation is poor (it’s not), but because using ACLs for row level access is flawed from the get-go. sure, it can be done, just as an entire application can be built without a framework, or as a monolithic index.php with a shit-ton of functions. it is just not good separation of concerns, and i didn’t see that until after i had tried three different ways to limit access to specific rows in a database using ACLs. i reiterate the quote from mark story that if you use cake ACLs to limit row access you “have to know the answer to the question before you even ask it”.
what did mark mean by this? he meant that if you have even a simple setup with users being in groups (and groups not being hierarchical), you must query the model in question joining in various ACL models to find allowed ids before you can even ask the original model for rows that match those ids. when you deal with inherited permissions from a group model that is tree-based, you have even more complexity. i.e. for the group that the user is in, you have to iterate upwards in the tree to see if that particular group has access to the row in question. and if your requirements call for your users to be in multiple groups (which cake default ACL behavior/component doesn’t support at all), then it’s even more complex, since you have to loop through the ancestor tree of every group that the user is in. and to top it all off, if the model you are trying to read is also a tree, you not only have to do all of the above, but you also solve the problem of denied parent nodes, and figure out how to deal with non-explicitly denied children of denied parent nodes. it’s a mess. i had a completely working implementation of what i just described and when i issued a single Model::read() on a record owned by a user who was in three groups, it performed fourteen queries to determine if you user could see he his own record! did it work? sure. was it optimal? fuck no. would indexing help me? sure, until my tree got huge, and i would never be able to shake the feeling that i was doing things the wrong way just to stick to purely cake components.
fourthly, i am not advocating or urging the use of either ACLs or UNIX-y permissions. both are good when used appropriately. both can be overkill for simple “can user x do y” scenarios. it’s merely what worked for me, and i want to share. in fact, what is missing from my previous article is noting that to achieve a “real” RBAC permissioning system, both row-level access like this and ACLs are required (at least in cake terms). ACLs to know who can access what part of the system, and a simple method like mine to determine who can change what in the data. if someone can address my concerns noted above as to how this can be done in a generic implementation of ACL like cake’s, and it does not require a whole handful of queries before the actual model query requested (note: a simple find(‘all’) with the Permissionable behavior doesn’t increase the query count at all), then i’ll eat crow. 😉