Pivotal Labs

Access Control & Permissions in Rails

edit Posted by Nick Kallen on Thursday July 26, 2007 at 02:55AM

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.

Comments

  1. Peter Williams Peter Williams on July 26, 2007 at 04:24AM

    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.

  2. Daniel Neighman Daniel Neighman on July 26, 2007 at 04:31AM

    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

  3. Dr Nic Dr Nic on July 26, 2007 at 09:14AM

    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
      end
    

    Then, 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.

  4. Vrensk Vrensk on July 26, 2007 at 11:01AM

    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!

  5. Nick Kallen Nick Kallen on July 27, 2007 at 06:16AM

    Vrensk: your approach sounds promising. Let me know how it works out. I'm skeptical though. Consider,

    <code>model.create_as(current_user)
    model.update_as(current_user)
    model.destroy_as(current_user)
    model.read_as(current_user) # well, I dunno about this one!
    </code>

    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).

  6. David Parker David Parker on August 08, 2007 at 08:30PM

    @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!

  7. Rabbit Rabbit on October 16, 2007 at 08:43PM

    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.

    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:

    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...)

  8. Nick Kallen Nick Kallen on October 17, 2007 at 05:18AM

    Rabbit -- thanks for the flattering comments. FYI, since this article was written, our convention has changed to #creatable_by? from can_be_created_by?. The former is terse and works better with RSpec's be_creatable_by matcher. (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 as def creatable_by true end so you only have to override it if you need it to be more restrictive.

    Coupling is 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.

  9. Rabbit Rabbit on October 17, 2007 at 06:03AM

    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?

  10. Rabbit Rabbit on October 17, 2007 at 06:25AM

    Crazy. Your comment appeared as soon as I posted mine.

    Beautiful code doesn't ignore rules, it makes up its own rules.

    Agreed. In my experience there are a lot of people that don't like when you don't play by their rules. Screw them.

    The last two projects I've worked on have basically made it a requirement that all ActiveRecords implement #creatableby? etc. (Actually, it's implemented in ActiveRecord::Base as def creatableby true end so you only have to override it if you need it to be more restrictive.

    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...

    <code>def has_access_to?(verb, user); ... end
    </code>

    And as I was writing that I thought, I should probably put this into a module. Extending Active Record would works, too (apparently).

    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.

    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.

  11. Rabbit Rabbit on October 17, 2007 at 06:59AM

    Sorry to post so much, I just keep getting new ideas... What do you think of this?

    <code>class Store &lt; 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.

  12. Nate Nate on December 04, 2007 at 10:14PM

    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.

  13. Nate Nate on December 04, 2007 at 10:14PM

    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.

Add a Comment (MarkDown available)