Authorization Gem for Ruby on Rails.
Go to file
2010-04-15 23:28:04 -07:00
lib adding caching to current_ability class method, if you're overriding this be sure to add caching there too 2010-04-15 23:28:04 -07:00
spec default ActiveRecordAdditions#can method action to :read and use 'scoped' if 'where' is not available 2010-04-15 23:18:49 -07:00
.gitignore releasing gem v0.1.0 2009-11-16 22:15:10 -08:00
cancan.gemspec use Dir globbing more efficiently in gemspec 2010-04-05 08:22:02 -07:00
CHANGELOG.rdoc adding caching to current_ability class method, if you're overriding this be sure to add caching there too 2010-04-15 23:28:04 -07:00
init.rb turning into a funtioning Rails plugin 2009-11-16 19:24:04 -08:00
LICENSE adding basic ability module 2009-11-16 14:28:52 -08:00
Rakefile making spec the default rake task so it works at runcoderun.com 2009-12-30 20:22:26 -08:00
README.rdoc adding caching to current_ability class method, if you're overriding this be sure to add caching there too 2010-04-15 23:28:04 -07:00

= CanCan

RDocs[http://rdoc.info/projects/ryanb/cancan] | Wiki[http://wiki.github.com/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan] | Metrics[http://getcaliper.com/caliper/project?repo=git%3A%2F%2Fgithub.com%2Fryanb%2Fcancan.git]

This is a simple authorization solution for Ruby on Rails to restrict what a given user is allowed to access in the application. This is completely decoupled from any role based implementation allowing you to define user roles the way you want. All permissions are stored in a single location and not duplicated across the controller, view, and database.

This assumes you already have authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]) which provides a +current_user+ model.

== Installation

You can set CanCan up as a gem in your environment.rb file.

  config.gem "cancan"

And then install the gem.

  sudo rake gems:install

Alternatively you can install it as a Rails plugin.

  script/plugin install git://github.com/ryanb/cancan.git


== Getting Started

First, define a class called Ability in "models/ability.rb".

  class Ability
    include CanCan::Ability
  
    def initialize(user)
      if user.admin?
        can :manage, :all
      else
        can :read, :all
      end
    end
  end

This is where all permissions will go. See the "Defining Abilities" section below for more information.

You can access the current permissions at any point using the "can?" and "cannot?" methods in the view.

  <% if can? :update, @article %>
    <%= link_to "Edit", edit_article_path(@article) %>
  <% end %>

You can also use these methods in a controller along with the "unauthorized!" method to restrict access.

  def show
    @article = Article.find(params[:id])
    unauthorized! if cannot? :read, @article
  end

Setting this for every action can be tedious, therefore the load_and_authorize_resource method is also provided to automatically authorize all actions in a RESTful style resource controller. It will set up a before filter which loads the resource into the instance variable and authorizes it.

  class ArticlesController < ApplicationController
    load_and_authorize_resource
    
    def show
      # @article is already loaded
    end
  end

If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the ApplicationController.

  class ApplicationController < ActionController::Base
    rescue_from CanCan::AccessDenied do |exception|
      flash[:error] = exception.message
      redirect_to root_url
    end
  end


== Defining Abilities

As shown above, the Ability class is where all user permissions are defined. The user model is passed into the initialize method so you are free to modify the permissions based on the user's attributes. This way CanCan is completely decoupled with how you choose to handle roles.

The "can" method accepts two arguments, the first one is the action you're setting the permission for, the second one is the class of object you're setting it on.

  can :update, Article

You can pass an array for either of these parameters to match any one.

  can [:update, :destroy], [Article, Comment]

In this case the user has the ability to update or destroy both articles and comments.

You can pass a hash of conditions as the third argument.

  can :read, Project, :active => true, :user_id => user.id

Here the user can only see active projects which he owns. See ControllerAdditions#conditions for a way to use this in database queries.

If the conditions hash does not give you enough control over defining abilities, you can use a block to write any Ruby code you want.

  can :update, Project do |project|
    project && project.groups.include?(user.group)
  end

If the block returns true then the user has that :update ability for that project, otherwise he will be denied access. It's possible for the passed in model to be nil if one isn't specified, so be sure to take that into consideration.

The downside to using a block is that it cannot be used to generate conditions for database queries.

You can pass :all to reference every type of object. In this case the object type will be passed into the block as well (just in case object is nil).

  can :read, :all do |object_class, object|
    object_class != Order
  end

Here the user has permission to read all objects except orders.

You can also pass :manage as the action which will match any action. In this case the action is passed to the block.

  can :manage, Comment do |action, comment|
    action != :destroy
  end
  
Finally, the "cannot" method works similar to "can" but defines which abilities cannot be done.

  can :read, :all
  cannot :read, Product


== Checking Abilities

Use the "can?" method in the controller or view to check the user's permission for a given action and object.

  can? :destroy, @project

You can also pass the class instead of an instance (if you don't have one handy).

  <% if can? :create, Project %>
    <%= link_to "New Project", new_project_path %>
  <% end %>

The "cannot?" method is for convenience and performs the opposite check of "can?"

  cannot? :destroy, @project


== Aliasing Actions

You can use the "alias_action" method to alias one or more actions into one.

  alias_action :update, :destroy, :to => :modify
  can :modify, Comment
  can? :update, Comment # => true

The following aliases are added by default for conveniently mapping common controller actions.

  alias_action :index, :show, :to => :read
  alias_action :new, :to => :create
  alias_action :edit, :to => :update


== Authorizing Controller Actions

As mentioned in the Getting Started section, you can use the +load_and_authorize_resource+ method in your controller to load the resource into an instance variable and authorize it. If you have a nested resource you can specify that as well.

  load_and_authorize_resource :nested => :author

You can also pass an array to the :+nested+ attribute for deep nesting.

If you want to customize the loading behavior on certain actions, you can do so in a before filter.

  class BooksController < ApplicationController
    before_filter :find_book_by_permalink, :only => :show
    load_and_authorize_resource
  
    private
  
    def find_book_by_permalink
      @book = Book.find_by_permalink!(params[:id)
    end
  end

Here the @book instance variable is already set so it will not be loaded again for that action. This works for nested resources as well.


== Fetching Records

Sometimes you need to restrict which records are returned from the database based on what the user is able to access. This can be done with the +can+ method on any Active Record model. Simply pass it the current ability and an action.

  @articles = Article.can(current_ability, :read)

The action defaults to :read so that can optionally be left out.

  @articles = Article.can(current_ability)

Here only the records which the user can read will be returned. This is an Active Record scope so other scopes and pagination can be chained onto it.

Note: This does not work for abilities which were defined by a block because the conditions hash can not be determined from them. Instead an exception will be raised.


== Assumptions & Configuring

CanCan makes two assumptions about your application.

* You have an Ability class which defines the permissions.
* You have a current_user method in the controller which returns the current user model.

You can override these by overriding the "current_ability" method in your ApplicationController.

  def current_ability
    # instead of Ability.new(current_user)
    @current_ability ||= UserAbility.new(current_account)
  end

That's it!


== Testing Abilities

It is very easy to test the Ability model since you can call "can?" directly on it as you would in the view or controller.

  def test "user can only destroy projects which he owns"
    user = User.new
    ability = Ability.new(user)
    assert ability.can?(:destroy, Project.new(:user => user))
    assert ability.cannot?(:destroy, Project.new)
  end

A matcher called +be_able_to+ is provided for testing abilities in RSpec.

  require "cancan/matchers"
  # ...
  ability.should be_able_to(:destroy, Project.new)


== Special Thanks

CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Many thanks to the authors and contributors.