Aug 24, 2008

A common requirement for Rails applications is to check permissions for certain actions on your RESTful application’s resources. There are many ways to solve this problem, ranging from simple boolean flags to full fledged role based access control. I have tried a lot of approaches and have settled on a fairly simple way that is RESTful and quite flexible.

Updates

  • 20081023 – see the code for this and other recipes at github
  • 20081018 – changed syntax highlighter, replaced Person with User

Scope

What it does and what it does not: We are just controlling access to our app’s resources at the view and controller level, i.e. access through the web. We do not check permissions on a model or file level. That means anybody with access to the server can mess with your files, database, and models.

I consider this approach the sweet spot for most of my applications which don’t contain super sensitive information.

Overview

There are a number of places where one needs to check for permissions in a rails app:

  • in the View when deciding which controls a user should see: Should we display the edit button on that resource? Is this user allowed to delete this record? This approach renders UI elements only for those actions the current user has permission for.
  • in the Controller when protecting certain URLs. Even if you don’t provide a link to access a resource in the view, it would be easy to guess the RESTful URL for certain operations. So every controller action checks permissions for the current user before any data is modified.
  • if you want it really secure, you also have to protect your Model. I might write about this later. It is beyond the scope of this article. Quick pointer: you could use ActiveRecord callbacks and raise exceptions if current_user does not have permissions.

One issue that comes up when checking permissions is “How do models get access to current_user?”. This is important for auditing and model security. I copied what Rails is doing: store the current user in Thread.current[:user] at the beginning of each request and wrap User.current around that variable. Then User.current is available everywhere in your app, not only in controllers and views.

Recipe

The following is the recipe for implementing RESTful permissions in an Online Syllabus Builder Rails app:

User Model

There isn’t much you need on the user model. I assume you are using restful_authentication to handle users, signups, and logins. All I usually do these days is to add a boolean field for each role a user can have.

In our example the roles are ‘is_admin’ and ‘is_editor’. For each role a user holds, the corresponding field is set to ‘true’. You can then ask the user: current_user.is_admin? when checking for permissions.

Resource Model

Tell your model that it has permissions and customize any special permissions:

In app/models/syllabus.rb

class Syllabus < ActiveRecord::Base

  has_restful_permissions

  ...

  def updatable_by?(actor)
    actor.is_a?(User) && (actor.is_admin? || self.owned_by?(actor))
  end

  def owned_by?(actor)
    actor == self.author
  end

end

The statement has_restful_permissions includes the standard set of permissions (see bottom of this post for an example). You will find that in a RESTful app, most resources have the same rules for permissions, so you can extract the permission rules into a module and include it into your resources. Then you can override individual rules for resources that are different.

The method updatable_by? overrides the default permissions for the update action on Syllabus. In this case it grants permission to update a Syllabus to logged in users (actor.is_a?(User)) who are either admins (actor.is_admin?) or who own this syllabus (self.owned_by?(actor)).

The method owned_by? defines who owns this resource. This is the method you will have to override most often as there is no general solution that fits all. In this case we consider the author of a syllabus its owner. For nested resources, you can call owned_by? on the parent resource. Example:

In app/models/text_block.rb

class TextBlock < ActiveRecord::Base

  has_restful_permissions
  belongs_to :syllabus

  def owned_by?(actor)
    self.syllabus.owned_by?(actor)
  end

end

View

Whenever you have a resource action link that is rendered based on the current user’s permissions:

In app/views/syllabi/index.html.erb

<%= link_to('Edit', edit_syllabus_path(@syllabus)) if @syllabus.updatable_by?(current_user) %>

The ‘Edit’ link is rendered only if the current_user has permission to update this syllabus.

There is a slight edge case when rendering links for creating a new resource. Because you don’t have the resource instantiated when the view is rendered, you might have to call creatable_by? on the resource class instead of an instance. So Syllabus.creatable_by? instead of @syllabus.creatable_by?

Controller

The remaining place to check permissions is in your controller actions. I usually instantiate the resource first and then check permissions so that I can consider ownership associations as well as any other permissions that depend on the state of the resource:

In app/controllers/syllabi_controller.rb

class SyllabiController < ApplicationController

  def index
    # Note that this is the only case where we check permissions on the class
    raise PermissionViolation unless Syllabus.listable_by?(current_user)
    @syllabi = ...
    ...
  end

  def show
    @syllabus = Syllabus.find(params[:id])
    raise PermissionViolation unless @syllabus.updatable_by?(current_user)
    ...
  end

  def new
    @syllabus = Syllabus.new
    raise PermissionViolation unless @syllabus.creatable_by?(current_user)
    ...
  end

  def edit
    @syllabus = Syllabus.find(params[:id])
    raise PermissionViolation unless @syllabus.updatable_by?(current_user)
    ...
  end

  def create
    @syllabus = Syllabus.new(params[:syllabus])
    raise PermissionViolation unless @syllabus.creatable_by?(current_user)
    ...
  end

  def update
    @syllabus = Syllabus.find(params[:id])
    raise PermissionViolation unless @syllabus.updatable_by?(current_user)
    ...
  end

  def destroy
    @syllabus = Syllabus.find(params[:id])
    raise PermissionViolation unless @syllabus.destroyable_by?(current_user)
    ...
  end
end

I first instantiate the current resource and then I ask it whether the current_user has permission to perform the requested action. This assumes that instantiating the resource has no side effects.

You can handle a PermissionViolation in the application controller.

In app/controllers/application.rb

class ApplicationController < ActionController::Base

  def rescue_action(exception)
    case exception
      when PermissionViolation
        flash[:warning] = "You do not have permission for this action."
        redirect_to :back
      else
        super
    end
  end

end

Permission types

This list contains the most common permissions for a RESTful resource. You might need additional permissions for each custom action method you add in routes.rb to member or collection:

  • show: @syllabus.viewable_by?(current_user)
  • edit and update: @syllabus.updatable_by?(current_user)
  • new and create: @syllabus.creatable_by?(current_user) — your initial instinct might be to make this a class method, however I found it useful to instantiate the new object, then check permissions before I actually save it. Allows you to consider associated resources for permission checks. Sometimes you need to call creatable_by? on the class. For example when rendering a ‘new’ link in a view that allows you to create a new resource, however you don’t have one instantiated at that point to check permissions on.
  • destroy: @syllabus.destroyable_by?(current_user)
  • index: Syllabus.listable_by?(current_user) — for list I use a class method because there is no single resource for lists.

So what do these methods look like? Below are some example methods that you would define in your resource class:

Anybody can list syllabi:

def self.listable_by?(actor)
  true
end

Logged in users can view this syllabus:

def viewable_by?(actor)
  actor.is_a?(User)
end

Admins and owners can update this post:

def updatable_by?(actor)
  actor.is_a?(User) && (actor.is_admin? || self.owned_by?(actor))
end

Nobody can destroy this post:

def destroyable_by?(actor)
  false
end

Other implementations

Nick Kallen’s post

I found an article by Nick Kallen that proposed a very similar approach a while ago.

He uses active voice: current_user.can_create(@syllabus). That works if you have an AnonymousUser object. And you also need to do the double dispatch where the user object then asks the resource whether it has permission. I find using the passive voice directly more concise — even though in general I prefer active voice. It is much less ambiguous.

One aspect Nick does not mention is the concept of resource ownership, handled by “owned_by?(actor)” in this example.

Also Hobo and spot.us, written by Hashrocket use a similar approach.

make_resourceful

This recipe integrates very nicely with the make_resourceful plugin. All you need to do is call

raise PermissionViolation unless current_object.viewable_by?(current_user)

in the before_show callback. And then the resources take care of answering that question. I actually modified the plugin and added permissions checks in /vendor/plugins/make_resourceful/lib/resourceful/default/actions.rb:

module Actions
  # GET /foos
  def index
    #load_objects
    raise PermissionViolation unless current_model.listable_by?(User.current)
    before :index
    response_for :index
  end
  ...
end

Confused deputy problem

Something to keep in mind when working with permissions is what is called the “confused deputy problem“. Where the app intends to give a particular permission to an actor. The actor then does something either inadvertently or on purpose that goes beyond the permission granted. This usually applies to file system resources and file permissions. However it can also happen in Rails through model associations. A user might have permission to delete resource A. Resource A has_many :bs, dependent => :destroy. When the user deletes resource A, Rails automatically also deletes resource B — which the user might not have permission for. You would need model based permissions to prevent this from happening.

Code for default permissions

Here is the code I usually include in resources that have permissions with “has_restful_permissions” in the class definition.

Require it with

 require 'has_restful_permissions'

In lib/has_restful_permissions.rb

class PermissionViolation < StandardError; end

module HasRestfulPermissions

  # call this in resource class
  def has_restful_permissions
    extend  ClassMethods
    include InstanceMethods
  end

  module InstanceMethods

    # permission rules, override these in the resource class

    # Returns true if actor can create this new instance.
    # I prefer the instance method over the class method
    # because sometimes you need to look at the state
    # of the newly instantiated object or its related objects
    # to decide whether the current user is permitted to create it.
    def creatable_by?(actor)
      actor.is_a?(User)
    end

    # Returns true if actor can destroy this resource.
    def destroyable_by?(actor)
      actor.is_a?(User) && self.owned_by?(actor)
    end

    # Returns true if actor can update this resource.
    def updatable_by?(actor)
      actor.is_a?(User) && self.owned_by?(actor)
    end

    # Returns true if actor can view this resource.
    def viewable_by?(actor)
      actor.is_a?(User)
    end

    # Returns true if actor owns this resource.
    # Expresses a control/ownership association.
    # Expects actor to be a User (usually checked for in other
    # permission methods before this method is called)
    def owned_by?(actor)
      # defaults to false. Override if resource has owner
      false
    end

  end

  module ClassMethods
    # Returns true if actor can view a list of resources of this class.
    def listable_by?(actor)
      actor.is_a?(User)
    end

    # alternative way to check if resource is creatable by actor.
    # use this class method instead of the instance method if resource
    # is not instantiated at time of checking permissions
    # This is useful when deciding whether to render a "new" link
    # used for creating a new resource.
    def creatable_by?(actor)
      actor.is_a?(User)
    end
  end

end

ActiveRecord::Base.send :extend, HasRestfulPermissions