Access Control & Permissions in Rails
Access Control is a simple idea. We want company employees to be able to delete inappropriate content; but random Users cannot. Here I propose one way to implement Access Control that has the particular advantage of being very general, very concise, and unlikely to be violated. I call it RESTful Access Control.
What is RESTful Access Control?
When we model RESTfully the Application Interface (i.e., the HTTP interface), we construe Resources as responding to a simple set of Verbs: Get, Put, Post, Delete. To the extent that our entire application is designed this way, we can Control all Access to our Application with perfect granularity: as being a set of Permissions to perform these four Verbs on a corresponding Resource. Let's start with an example.
Suppose a User can add Members to a Group. How do we determine if this or that User can create a Membership? This logic doesn't belong in the controller -- call me a prig, but Business Logic belongs in the Model. Anything else is unbecoming. Therefore, we have a Controller like:
class MembershipsController < ApplicationController
before_filter :load_group
def create
membership = @group.memberships.build(params[:membership])
raise SecurityTransgression unless current_user.can_create?(membership)
...
end
def load_group
@group = Group.find(params[:group_id])
end
end
Let's implement the can_create? method. Despite the above Formulaic code, the User Class isn't really the proper place for the Logic: different kinds of resources are likely to have different rules. Thus, we implement the Logic in the Model being created:
class Membership < ActiveRecord::Base
def can_be_created_by?(user)
...
end
end
I call the above "the Passive Voice." I prefer, for various reasons, to use the "Active Voice", and therefore we implement a simple Proxy in the User Class:
class User < ActiveRecord::Base
def can_create?(resource)
resource.can_be_created_by?(self)
end
end
Of course, this "Active Voice" implementation requires that we follow the Null Object Pattern for Users who are not Logged In, otherwise we'll produce Errors when we ask if a Logged Out User has Permission to do something:
class AnonymousUser < User
...
end
Why we Always Create An Instance Before Checking Permission
In our Controller Action above, we first build an instance of a Model in memory, then ask for Permission.
membership = @group.memberships.build(params[:membership])
raise SecurityTransgression unless current_user.can_create?(membership)
But why even create the Model in memory it shall not be persisted? Well, for some advanced Access Control Rules, we need all of the data available to an Instance in order to proceed. Suppose, for example, that only the Owner of a Group can create Memberships. The simplest way to enforce this rule is to reference the Group Owner in the can_be_created_by? method:
class Membership
belongs_to :group
def can_be_created_by?(user)
group.owner == user
end
end
Similarly, can_be_destroyed_by?, can_be_updated_by?, and can_be_read_by? all must be instance methods. This provides the maximum flexibility and a consistent Interface.
This is a robust Pattern for Access Control. It extends brilliantly to User Roles:
def can_be_destroyed_by?(user)
user.admin? or ...
end
Even a per-user Access Control scheme where we grant individual Users permission to manipulate individual objects can elegantly fit in. Consider something like the following:
class Permission
belongs_to :user
belongs_to :resource, :polymorphic => true
end
class CreatePermission < Permission
end
class Membership
has_many :create_permissions, :as => :resource
def can_be_created_by?(user)
create_permissions.find_by_user_id(user)
end
end
Handling Access Control in the Views
Access Control has View consequences as well. For example, we shouldn't show a destroy button if the User lacks permission to perform that Action. Given the above implementation, this is simple enough:
<%= link_to_unless current_user.can_destroy?(resource), 'destroy', ... %>
Why Exceptions?
Above, I've proposed using Exceptions to handle Security Transgressions. (At Pivotal, we call these Exceptions SecurityTransgressions because it has a more romantic air than the banal AccessDenied). Normally, these Transgressions cannot happen: in your Templates you will not even display links to modify an object if the User lacks Permission. So Transgressions occur only when a User is fiddling about with nefarious intent. So we can handle misbehavior consistently across the site, by (for example) rendering a HTTP 403 Forbidden Response. Exceptions are a neat way to do this, because Rails gives us a systematic way to handle exceptions with rescue_action:
class SecurityTransgression < StandardError; end
def create
raise SecurityTransgression unless ...
end
class ApplicationController < ActionController::Base
def rescue_action(e)
case e
when SecurityTransgression
head :forbidden
end
end
end
Ensuring Programmers Remember to do Access Control And How to Avoid Over-Engineering
The biggest danger with Access Control rules is the possibility that a careless Programmer might forget to implement them. Having a consistent, terse pattern for Access Control goes some way towards mitigating this problem. When all Access Control is as simple as adding a can_create? check to your Actions, Access Control is unlikely to be overlooked.
The Second biggest danger in Access Control is over-engineering. Suppose we want to enforce Access Control logic. Rails gives us some tools to do this. Well, Rails gives us just enough rope to hang ourselves. There are some serious disadvantages to the approach I am going to propose now, but we'll get to that shortly.
Rails has something called an Around Filter. An Around Filter, like a Before and After Filter, is a Filter applied to every Action. But this Around filter is like the Heavenly Union of the Before and After Filters: both great tasting and less filling, it can do work before and after invoking the Action.
class ApplicationController
around_filter :ensure_permission_to_create, :only => :create
def ensure_permission_to_create
class_name = ModelsController.to_s.demodulize.gsub(/Controller$/,'').singularize
param_name = class_name.downcase
instance_variable_name = "@#{class_name.downcase}"
instance_variable_set instance_variable_name, class_name.constantize.new(params[param_name])
if current_user.can_create?(instance_variable_get instance_variable_name)
yield # the create acction is invoked here.
else
head :forbidden
end
end
end
Whew. In the above implementation, control is yielded to the Action only if the User has permission. The instance of the Model is also built for us automatically; so, our Formulaic create Action becomes:
def create
if @model.save
...
end
end
Very DRY, But this is, again, just enough rope to hang ourselves with. Our Controllers need to be named exactly right, our parameters without an inconsistency, and so forth. This is not a problem in the World of Forms, but in Reality it may require some coding contortions. The tricky metaprogramming and impertinent assumptions in ensure_permision... only get nastier when we want to make a Nested Resource, and build our Model using a proxy (as in the previous example: @group.memberships.build(params([:membership])). Finally, where we have Controllers that do not map directly to ActiveRecords, we would need to introduce a Model layer that responded to the Interface defined in ensure_permission... We often want to do this, but not always. Being forced to might suck. So I offer this design strategy with much caution.
The Exception based approach, while a bit more verbose, is battle-tested and works in Practice. In any case, pushing the logic to the Model with can_be_created_by? will certainly work, regardless of whether you take the Around Filter approach or the Exception-Based one.








One issue I have run into: 401 is not always the most appropriate response code for security transgressions. Returning a 401 has the effect of confirming that the resource does, in fact, exist. In many situations this is fine, but if the URIs include any human interpretable information it might be problem.
We have solved this problem having a
#visible_to?(user)method on models. With that you can easily implement "permit" function that will return 404 if you try to do something with a resource that you are not allowed to know exists, or a 401 if you try to do something you are not allowed to on a resource that you are allowed to know exists.remove
This is really a great idea. I have been starting down this road by using in my controllers
resource.destroyable?( current_user ) resource.readable?( current_user )
and raiseing Exceptions on those, but I think I really like this method a lot more.
Great post. Thanx
remove
Hobo (http://hobocentral.net) has a nice inbuilt authorisation concept.
On each model, you can specify access methods, e.g. (from myconfplan code for Conference model)
def creatable_by?(creator) !creator.guest? && creator == user end def updatable_by?(updater, new) !updater.guest? && updater == user and same_fields?(new, :user) end def deletable_by?(deleter) !deleter.guest? && deleter == user end def viewable_by?(viewer, field) viewer == user || conference_sessions.count > 0 endThen, all the widgets and all the views can access helper methods to query "can current_user see/edit/delete this object?"
Its a killer feature of hobo, imo.
remove
Interesting article, and a very nice pattern for doing this. The resultant code looks really good. But call me_ a prig, for while I agree with you that the can-x don't belong in the controllers, I don't think that they should be called from the controllers either. In my opinion, it is the _model's responsibility to ensure that model-space modifications are done in a consistent way. So I'd rather make the controllers even skinnier and the models fatter, and have the controllers just call create-this-or-that-if-i-am-allowed.
That being said, I haven't done so in practice yet, because it felt too complicated. But if I moved your clever build-first, test-after pattern to the model, perhaps it wouldn't be so hard after all.
Thanks for an interesting article!
remove
Vrensk: your approach sounds promising. Let me know how it works out. I'm skeptical though. Consider,
The asymmetry of the last indicates to me that the Controller is the proper layer for this. After all, it's really only the controller that has a concept of a current_user. Models are timeless (users are not
current, they just are).remove
@Nick Kallen,
Good article. I'm just getting into Rails a bit and I've been looking for a good access control system. I'm already using RESTful authentication, so your solution seems to fit right in. By any chance, can I get some of your source code? or do you know of any location where I can get some (at least for learning more)? Thanks for the article!
remove
I'm a heavy object thinker, and I gotta say, I dig your style. Recognizing and labeling a distinction between "active" and "passive" voices blew my mind.
I like your stance on "who should do what," because that's a problem I've often encountered. Does the user add himself to the group or does the group add the user to itself?
Correct me if I'm wrong but it sounds like you're saying that the object to be altered should be responsible for deciding whether it can be altered. Is that accurate?
I have a question though... regarding the active voice. While I love the sentence-like nature of it (I often write code like this - I love asking questions in Ruby!) it feels like it's more tightly coupled that it "should" be. Class User knows that class Group has a "can be created by(user)" method.
How do you feel about that? Is inevitable? Am I worrying too much? (I hope that's the case...)
remove
Rabbit -- thanks for the flattering comments. FYI, since this article was written, our convention has changed to
#creatable_by?fromcan_be_created_by?. The former is terse and works better with RSpec'sbe_creatable_bymatcher. (Thanks to Hobo for this convention).The original motivation regarding the active voice is the following. In practice when you want to mock out security rules, it's great to have one centralized place to do it. Similarly, when implementing caching schemes (e.g., making highly personalized pages identical across various users for performance reasons) it's great to be able to identify the axes of variability in one centralized place (that is, as possible combinations of attributes of the user, since the user acts as the cache key), rather than as attributes of various objects. This sounds really convoluted but in practice it's quite simple.
The last two projects I've worked on have basically made it a requirement that all ActiveRecords implement
#creatable_by?etc. (Actually, it's implemented in ActiveRecord::Base asdef creatable_by true endso you only have to override it if you need it to be more restrictive.Couplingis such a scary word and I wish people wouldn't bandy it about as if just uttering it is a way to end an argument. Not that you're doing that Rabbit, it's just a pet peeve, sorry to bark at you.Ultimately, I've enforced that all ActiveRecords adhere to a certain interface, namely they have these permissions rules. If that's coupling, it's not a bad thing. We've even, taboo of taboos, enforced the rules automatically by decorating the ActiveRecord #find, #create, #update, and #destroy methods to raise SecurityTransgressions if the rules do not obtain; we ended up setting the current_user as a thread-local variable (horror of horrors!).
But shouldn't our pristine model objects know nothing of the session (i.e., the current user)? Aren't globals evil?
I too am a heavy object thinker. I'm always looking for formal solutions to problems. But some of the "received wisdom"--i.e., dogma--should be questioned. It should be questioned philosophically, and it can be rejected on philosophical grounds, even if everyone thinks you're crazy.
Beautiful code doesn't ignore rules, it makes up its own rules.
remove
What about the index method? How can a user or resource know whether the user has access to something like Object.find(:all)?
Checking for instance-level access is easy (as shown above in a number of ways), but how do you check for access to the class at all?
remove
Crazy. Your comment appeared as soon as I posted mine.
Agreed. In my experience there are a lot of people that don't like when you don't play by their rules. Screw them.
I like that. I was just writing out all the "can be ... by" methods and thought, this is terse. So I came up with something like...
And as I was writing that I thought, I should probably put this into a module. Extending Active Record would works, too (apparently).
I like that, too. One could argue that ActionController is tightly coupled with ActionView. The argument is stupid. Coupling occurs at some point regardless what you're programming. Hell, your keyboard is coupled to your computer; get over it. It's knowing when and what to couple. (Know the rules, then break them.)
I'm glad I found your blog. It's definitely top-notch. Please keep writing. There are few programmers where I live, fewer are anything above mediocre, and still fewer have even heard of Ruby, so it's nice when someone picks their brain and shares the findings. I consider myself an advanced beginner and I've been programming for six years.
remove
Sorry to post so much, I just keep getting new ideas... What do you think of this?
<code>class Store < ActiveRecord::Base def creatable_by?(user) # Style one. Permissions are attributes attached to model. user.director? or user.manager? # Style two. Permissions are abstracted away from model. user.permissions.find_by_name('Can create stores') end end </code>The first style is what I gather you are doing. The answer to the question comes directly from the model, from the programmer. I like it, it's clean and it's easy to implement. But it's limited (not a bad thing).
The second style allows users of the system to cherry-pick what permissions each user has, probably via an interface with check boxes (my goal). This gets away from the idea of roles as concrete programmatic constructs. Instead, roles (e.g. director, manager) are defined by convention. All directors have these permissions, all managers have these permissions. This guy's a doofus so give him very few permissions.
You could even emulate the first style by doing something like:
<code>permissions.find_by_name('User is a manager') </code>I think that was part of my confusion, too. You sub-classed Permission into CreatePermission (I assume to mean the "create" verb, implying there would be an EditablePermission and DestroyablePermission as well), but the "creatable by?" code used methods attached to the user; methods that are probably hard-coded. I was trying to do both; abstract the implementation of whether or not someone has permission and trying to write the implementation.
Whew! It's time for bed. Thanks for taking the time to comment, Nick. I appreciate it. Have a good night.
remove
I've been playing with implementing a role/permission based system similar to this but I can't seem to wrap my head around everything that is going on and how it all relates in the model. Do you know of any tutorials that I can look into some more that may help?
I've found quite a few tutorials but they are VERY outdated for rails 2.0 and RESTful design.
remove
I've been playing with implementing a role/permission based system similar to this but I can't seem to wrap my head around everything that is going on and how it all relates in the model. Do you know of any tutorials that I can look into some more that may help?
I've found quite a few tutorials but they are VERY outdated for rails 2.0 and RESTful design.
remove