I'm building a community-based site in Rails for the members of a real-world organization. I'm trying to adhere to the best practices of RESTful design, and most of it is more or less by-the-book. The issue that's making my brain run in neat RESTful circles is that of authorization. Authentication is an easy, long-solved problem with widely-accepted RESTful solutions, but RESTful authorization seems to be a bit of a black art. I'm trying to find the approach that will provide the most general and flexible framework for controlling access to resources while being as simple as possible, all while conforming to a RESTful architecture. (Also, a pony.)
Considerations:
- I need to control access to a variety of recourses, such as Users, Pages, Posts, et cetera.
- Authorization for a given resource must be finer-grained than simple CRUD.
- I wish to allow myself and others to edit the authorization rules from within the application.
- Authorization rules should be allowed to depend on predicates, such as (conceptually) Owner(User, Resource) or Locked(Topic)
Consideration (2) is the one that concerns me the most. There seems to be an impedance mismatch between my conception of permissions and the RESTful conception of actions. For example, take Posts (as in a message board). REST dictates the existence of four operations on the Post resource: Create, Read, Update, and Delete. It's simple to say that a user should be able to Update his own Posts, but only certain users (or roles, or groups) should be permitted to Lock them. The traditional way to represent locking is within the state of the Post, but that leads to the smell that a User under the same conditions may or may not be able to Update a Post depending on the (completely valid) values he supplies. It seems clear to me that there are really two different actions to change the state of the Post, and to shoehorn them is merely to disguise a violation of RESTful principles.
(I should note that this problem is quite distinct from the problem of an Update failing due to invalid or inconsistent data—a lock request from an unprivileged user is in principle quite valid, simply disallowed.)
Isn't decomposition another word for rot?
This may be overcome by decomposing the Post: a Lock is a subresource of a particular post, and to Create or Destroy one may then have separate permissions. This solution has the ring of REST to it, but is brings with it both theoretical and practical difficulties. If I factor out locks, then what about other attributes? Suppose I decide, in a fit of caprice, that only a member of Administrator should be allowed to modify the Post's title? A simple change in authorization would then require a restructuring of the database to accommodate it! This is not much of a solution. To allow for this kind of flexibility under a strategy of decomposition would require that every attribute be a resource. This presents a bit of a dilemma. My implicit assumption has been that a resource is represented in the database as a table. Under this assumption, a resource for every attribute means a table for every attribute. Clearly, this is not practical. However, to remove this assumption presents an impedance mismatch between tables and resources, which could open up its own can of worms. To use this approach would require far more in-depth consideration than I have given it. For one thing, users reasonably expect to be able to edit multiple attributes at once. Where does the request go? To the smallest resource that contains all attributes? To each individual resource in parallel? To the moon?
Some of these things are not like the others…
Suppose then that I do not decompose attributes. The alternative then seems to be defining a set of privileges for each resource. At this point, however, the homogeneity of REST is lost. In order to define access rules for a resource, the system must have specific knowledge of that resource's capabilities. Furthermore, it is now impossible to generically propagate permissions to descendent resources—even if a child resource had a privilege of the same name, there's no inherent semantic connection between the privileges. Defining a REST-like set of standard privileges seems to me to be the worst of both worlds, so I's be stuck with a separate permissions hierarchy for each type of resource.
Well, we did do the nose. And the hat. But it's a resource!
One suggestion I've seen that mitigates some of the disadvantages of the above approach is to define permissions as create/delete on resources and read/write on attributes. This system is a compromise between attributes-as-resources and privileges-per-resource: one is still left with only CRUD, but for the purposes of authorization, Read and Update pertain to attributes, which could be thought of as pseudo-resources. This provides many of the practical benefits of the attributes-as-resources approach, although the conceptual integrity is, to a certain extent, compromised. Permissions could still propagate from resource to resource and from resource to pseudo-resource, but never from a pseudo-resource. I have not fully explored the ramifications of this strategy, but it seems as though it may be promising. It occurs to me that such a system would best function as an integral part of the Model. In Rails, for example, it could be a retrofit of ActiveRecord. This seems rather drastic to me, but authorization is such a fundamental cross-cutting concern that this may be justified.
Oh, and don't forget about the pony
All of this ignores the issue of predicative permissions. Obviously, a User should be able to edit his own Posts, but no one else's. Equally obviously, the admin-written permissions table should not have separate records for each user. This is hardly an uncommon requirement—the trick is making it generic. I think that all of the functionality I need could be gained by making only the rules predicative, so that the applicability of the rule could be decided quickly and immediately. A rule "allow User write Post where Author(User, Post)
" would translate to "for all User, Post such that Author(User, Post), allow User write Post
", and "deny all write Post where Locked(Post)
" to "for all Post such that Locked(Post), deny all write Post
". (It would be grand if all such predicates could be expressed in terms of one User and one Resource.) The conceptually resultant "final" rules would be non-predicative. This raises the question of how to implement such a system. The predicates should be members of the Model classes, but I'm not sure how one could refer to them gracefully in the context of the rules. To do so safely would require some sort of reflection. Here again I have a feeling that this would require a retrofit of the Model implementation.
How do you spell that again?
The final question is how to best represent these authorization rules as data. A database table might do the trick, with enum columns for allow/deny and C/R/U/D (or perhaps CRUD bits? or perhaps {C, R, U, D} × {allow, deny, inherit}?), and a resource column with a path. Perhaps, as a convenience, an "inherit" bit. I'm at a loss as far as predicates. Separate table? Certainly lots of caching to prevent it from being too ungodly slow.
I guess that this is a lot to ask for. I tried to do my homework before asking the question, but at this point I really need an outside perspective. I'd appreciate any input that any of y'all might have on the problem.