Name: Password: Want to register?

October 1, 2009 / Admin

A Diferent Way to Think about User Permissions

Handling permissions (or user privileges, or access rights) is one of those things that is fairly universal for dynamic websites, security-critical, and almost never done in a way that is convenient to work with. Not so long ago, I've seen an ACL (Access Control List) implementation three thousand lines long, but still not flexible enough to allow all registered users to submit articles for the CMS in question. Of course, the task of handling permissions is not completely trivial, but it's not that complex either. The complexity of access control systems usually comes from the approach the developers choose to take and the abstractions they choose to employ.

Theory

I could spend some time looking at the common approaches first, but there are a lot of them, and while having many similarities, they often differ enough to thwart any attempts at generalization. Instead, I want to start the article by looking at access control in general. I will begin with basics, which are, nevertheless, frequently forgotten.

One, there are users of some sort who work with your system. If there are none, then having permissions simply doesn't make any sense. Two, a user tries to perform some kind of action. Again, if there are no user-initiated actions, permissions are meaningless. Three, the goal of access control subsystem is to say whether the user is allowed to perform that action or not.

If you don't add anything else into the picture, core functionality of access control can be handled by a single function. It would take a user and an action as parameters, and return TRUE only if that user is allowed to perform the action. As long as the returned booleans are correct, this is sufficient to implement anything any other permission system can possibly do (for a website, anyway).

$permission = getPermission($action, $user);

Logically, this means permissions are simply a many-to-many relation between certain users and actions.

action <-> user

I'll refer to this approach as functional permission map (FPM).

With this setup, permission checks are completely separated from permission management. A class running a check doesn't need to know how to grant or revoke user rights, or how the check is implemented. It doesn't have to initialize anything before running the check either. You could, therefore, switch from a simple hardcoded mapping to reading a config file or querying a database - without making any alterations to the "client side" code. Even better, you could introduce new abstraction layers (like groups) and different resolution logic. On the outside, you would still have the same function I described earlier.

If you're wondering what's so "different" about this setup, you can take a look at Zend ACL, which is much more typical of what is commonly used in frameworks and CMS'es out there. Reading one of its tutorials is probably the best way to see how it works.

Design

Above, the function to check permissions deals with actions. In reality, though, we don't have "actions"; we have methods and other blocks of code. The simplest thing to do would be to get the current user, and execute a block code only if the user is the right one. But that's not practical, since several blocks of code can represent one logical action. Viewing an article could be done in 10 different ways. Creating an article can take 3 different steps. Plus, there is often the need to alter HTML pages based on permissions, hiding the links that would lead to the pages the current user cannot see.

code -> action <-> user

But mapping users to actions is not practical either. It would result in having a huge number of permission records even for websites of moderate complexity. What we can do instead is introduce some intermediate entities, which are easier to work with.

When defining permissions, it would be nice to have an ability to refer to more than one user. Let's call such reference a subject. Subject of a permission is not a role or a group. It could be "user 2", or "group 8", or even "all registered users".

code -> action <-> subject <-> user

Is this enough? Not really. You might want to allow actions only on specific things (articles, posts, etc.) In this case, viewing article 1 would be an action separate from viewing article 6. If you have one thousand articles, you would have to create huge number of records. Again, that's not practical. Therefore we need something that can refer to many actions (delete article X) at once. There is a subject already, so let's call the new entity a predicate.

code -> action <-> predicate <-> subject <-> user

With this scheme, we are going to store (somewhere) subject -to-predicate mappings. There will be relatively few of them, and it will be easy to make them up based on the requirements. Here is one example given in a natural language:

SubjectPredicate
admindo anything
logged in usersadd articles
anyoneread articles

Naturally, users, actions, predicates and subjects would need to be encoded in some computer-readable form. In the later versions of Caffeine Web Framework, for example, actions are encoded as strings separated by comas ("Article.show.1"). Predicates are prefixes of such strings ("Articles.show"). Subjects can be either separate users, user-defined groups, "all" or "registered". A lot of the mappings there are implied, so the resulting system is simple, yet offers a good degree of flexibility.

I want to stress that this way of mapping permissions is just one of many possible implementations. You don't have to use a database, for example. However, it's probably a good idea for a website of even moderate complexity. When you do use a database, you have one of the following options:

  1. Load all the permissions at once, check against all of them. This is viable as long as you have less than several hundred records.
  2. Resolve permissions from the user side (which is what CWF does). Since user is not likely to change during a single request, there will be only one query. This is viable for a system with a lot of users and some amount of user-specific permissions for many of them.
  3. Resolve permissions from the action side.
  4. Generate a query that covers all possible paths from the user to the action for every permission check. This approach can be useful when the system has a really large number of permissions.

Practice

The concept behind FPM is very simple and abstract. In fact, it might seem too simplistic to deal with real-life permission schemes. How would you prohibit things? How would you grant authors the permission to edit their own articles? How would you map real code to the virtual "actions"? All of these questions have something to do with the way other parts of the system use the permission-checking function.

I'll answer the last question first. The way it's done in CWF, the controller base class has permits() method. Before one of its actions is invoked, the class calls this method with the action name as an argument. The method then composes a standard permission string (e.g. "ClassName.action"), runs it through FPM and returns the result. If the result is negative, the action is not run. This way every controller automatically gets permission handling built-in, but can override it to get fancier logic. No plugins or hooks necessary.

Next, there is no way to "prohibit" an action for someone via FPM. FPM is purely a white list, which makes resolving permissions much simpler and more efficient than it would be otherwise. Finding any relevant permission terminates the search with the positive result. This is different from most other systems I've seen, which have to find all the relevant permissions and them run some algorithm on them to determine which one has the highest priority.

All this doesn't mean you cannot achieve the same end result with FPM. You just have to think about it in positive. Let's say have a website with 1000 articles. Most of those are public, but there are a couple that are internal documents. Here is how this could be achieved:

SubjectPredicate
allArticle.viewNormal
group.specialArticle.viewInternal

//inside the controller that's responsible for article viewing
$kind = ($article->isInternal() ? 'Internal' : 'Normal');
$allowedToView = getPermission("Article.view$kind", $user);

Checking for authorship can be done in a similar way. You could have two logical actions, one for the admins ("Article.editAll"), and one for the users ("Article.editYourOwn"). The check itself would look like this:


//inside the controller that's responsible for article editing
$isAuthor = $article->userId == $currentUser->id;
$allowedToEdit = getPermission("Article.edit", $user) 
|| ($isAuthor && getPermission("Article.editYourOwn", $user));

This approach is very different from what many other systems do, trying to handle such things internally. Unlike those systems, FPM does not attribute meaning to actions, which, in my experience, results in much more straightforward designs. (And I mean that the code that uses the permission system ends up being much simpler as well.)

Summary

In conclusion, I want to summarize the main ideas behind FPM.

  • Permissions are thought of as a map between certain users and actions.
  • An "action" in this context is an abstract concept. It does not have to correspond to a real method in your code or some resource, although it can.
  • Permissions are checked by invoking a function (or method) that looks like $boolean = getPermission($action, $user);.
  • That function's interface is not dependent on the implementation. Also, checking for permissions is independent from permission management.
Made with Notepad++ Also on SourceForge.net