Rails CanCan
CanCan is an authorization library for Ruby on Rails which restricts what
resources a given user is allowed to access. All permissions are defined in
a single location (the Ability class) and not duplicated across
controllers, views, and database queries.
The authorize! method in the controller will raise an exception if the user is not able to perform the given action.
The current user model is passed into the initialize method, so the
permissions can be modified based on any user attributes. CanCan makes
no assumption about how roles are handled in your application. See Role Based Authorization for an example.
The
The
You can pass
Common actions are
You can pass an array for either of these parameters to match any one. For example, here the user will have the ability to update or destroy both articles and comments.
Important notice about :manage. As you read above it represents ANY action on the object. So if you have something like:
and if you take a test of last
If you want only CRUD actions on object, you should create custom action that called
It is important to only use database columns for these conditions so it can be used for Fetching Records.
You can use nested hashes to define conditions on associations. Here the project can only be read if the category it belongs to is visible.
The above will issue a query that performs an
An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3.
Anything that you can pass to a hash of conditions in Active Record
will work here. The only exception is working with model ids. You can't
pass in the model objects directly, you must pass in the ids.
If you have a complex case which cannot be done through a hash of conditions, see Defining Abilities with Blocks or MetaWhere.
The
The order of these calls is important. See Ability Precedence for more details
The
Also see Authorizing Controller Actions and Custom Actions.
Important: If a block or hash of conditions exist they will be ignored when checking on a class, and it will return
It is impossible to answer this
Think of it as asking "can the current user read a project?". The user can read a project, so this returns
The reason for this behavior is because of the controller
That is why passing a class to
The biggest change is that basic controller action authorization will be handled by default. This means everything will be locked down with one simple
This means abilities will be more focused on controller actions than resource classes, but this doesn't mean resources are being left in the dust. Another big feature is that you can add fine-grain permissions on specific resource attributes. See the Resource Attributes section below.
To install CanCan, add it to your Gemfile and run the
Next generate an Ability class, this is where your permissions will be defined.
Then enable authorization in your ApplicationController or any controller you want authorization to happen in.
This will add an authorization check locking down every controller action. If you try visiting a page a
or you can rescue from the exception itself.
Here it will redirect the user to the home page with an alert message
when unauthorized. If you do this, make sure the user has permission to
access the home page.
If you're using devise and only want to enable authorization for non-devise controllers, then you can achieve it like the code below.
As you can see here, if a logged in user exists he can access the
entire application, but guest users can only access the HomeController.
The first argument to
Here the user will be able to create and update both posts and comments. You don't need to mention the
You can check permissions in any controller or view using the
Here the link will only show up if one can create comments.
The specific rule in the end will override a previous generic rule.
A block can also be used for complex condition checks just like in CanCan 1, but here it is not necessary.
If you try visiting any of the project pages at this point you will see a
However this can get tedious. Instead CanCan provides a
The
You can check permissions on instances using the
Here it will only show the edit link if the
If you use this in combination with
You can combine this with a hash of conditions. For example, here the user can only update the price if the product isn't discontinued.
You can check permissions on specific attributes to determine what to show in the form.
Installation
In Rails 3, add this to your Gemfile and run the bundle command.gem "cancan"In Rails 2, add this to your environment.rb file.
config.gem "cancan"Alternatively, you can install it as a plugin.
rails plugin install git://github.com/ryanb/cancan.git
Getting Started
CanCan expects a current_user method to exist in the controller. First, set up some authentication (such as Authlogic or Devise). See Changing Defaults if you need different behavior.1. Define Abilities
User permissions are defined in an Ability class. CanCan 1.5 includes a Rails 3 generator for creating this class.rails g cancan:abilityIn Rails 2.3, just add a new class in `app/models/ability.rb` with the following contents:
class Ability include CanCan::Ability def initialize(user) end endSee Defining Abilities for details.
2. Check Abilities & Authorization
The current user’s permissions can then be checked using the can? and cannot? methods in the view and controller.<% if can? :update, @article %> <%= link_to "Edit", edit_article_path(@article) %> <% end %>See Checking Abilities for more information
The authorize! method in the controller will raise an exception if the user is not able to perform the given action.
def show @article = Article.find(params[:id]) authorize! :read, @article endSetting this for every action can be tedious, therefore the load_and_authorize_resource method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.
class ArticlesController < ApplicationController
load_and_authorize_resource
def show
# @article is already loaded and authorized
end
end
See Authorizing
Controller Actions for more information.3. Handle Unauthorized Access
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|
redirect_to root_url, :alert => exception.message
end
end
See Exception
Handling for more information.4. Lock It Down
If you want to ensure authorization happens on every action in your application, add check_authorization to your ApplicationController.class ApplicationController < ActionController::Base check_authorization endThis will raise an exception if authorization is not performed in an action. If you want to skip this add skip_authorization_check to a controller subclass. See Ensure Authorization for more information.
Defining Abilities
TheAbility class is where all user permissions are defined. An example class looks like this.class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
end
end
The can Method
The can method is used to define permissions and
requires 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
:manage to represent any action and :all to represent any object.can :manage, Article # user can perform any action on the article
can :read, :all # user can read any object
can :manage, :all # user can perform any action on any object
:read, :create, :update and :destroy but it can be anything. See Action Aliases and Custom Actions for more information on actions.You can pass an array for either of these parameters to match any one. For example, here the user will have the ability to update or destroy both articles and comments.
can [:update, :destroy], [Article, Comment]
can :manage, User
can :invite, User
:invite rule you always get true. Why? That's because :manage represents ANY action on object and :manage is not just :create, :read, :update, :destroy on object.If you want only CRUD actions on object, you should create custom action that called
:crud for example, and use it instead of :manage:def initialize(user)
user ||= User.new
alias_action :create, :read, :update, :destroy, :to => :crud
can :crud, User
can :invite, User
end
Hash of Conditions
A hash of conditions can be passed to further restrict which records this permission applies to. Here the user will only have permission to read active projects which he owns.can :read, Project, :active => true, :user_id => user.id
You can use nested hashes to define conditions on associations. Here the project can only be read if the category it belongs to is visible.
can :read, Project, :category => { :visible => true }
INNER JOIN to query conditions on associated records. If you require the associations to be queried with a LEFT OUTER JOIN then you can pass in a scope. The example below will use a scope that returns all Photos that do not belong to a group.class Photo
has_and_belongs_to_many :groups
scope :unowned, includes(:groups).where(:groups => {:id => nil})
end
class Group
has_and_belongs_to_many :photos
end
class Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :read, Photo, Photo.unowned do |photo|
photo.groups.empty?
end
end
end
can :read, Project, :priority => 1..3
can :manage, Project, :group => { :id => user.group_ids }
Combining Abilities
It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview.can :read, Project, :released => true
can :read, Project, :preview => true
cannot method takes the same arguments as can and defines which actions the user is unable to perform. This is normally done after a more generic can call.can :manage, Project
cannot :destroy, Project
Checking Abilities
After abilities are defined, you can use thecan? method in the controller or view to check the user's permission for a given action and object.can? :destroy, @project
cannot? method is for convenience and performs the opposite check of can?cannot? :destroy, @project
Checking with Class
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 %>
true. For example:can :read, Project, :priority => 3
can? :read, Project # returns true
can? question completely because not enough detail is given. Here the class does not have a priority attribute to check on.Think of it as asking "can the current user read a project?". The user can read a project, so this returns
true.
However it depends on which specific project you're talking about. If
you are doing a class check, it is important you do another check once
an instance becomes available so the hash of conditions can be used.The reason for this behavior is because of the controller
index action. Since the authorize_resource before filter has no instance to check on, it will use the Project class. If the authorization failed at that point then it would be impossible to filter the results later when Fetching Records.That is why passing a class to
can? will return true.Authorizing controller actions
You can use the
However that can be tedious to apply to each action. Instead you can use the
This is the same as calling
As of CanCan 1.5 you can use the
Also see Controller Authorization Example, Ensure Authorization and Non RESTful Controllers.
You can specify which actions to affect using the
The first skip_authorize_resource skips authorization check for
comment and the second for post. Both are needed if you want to skip all
authorization checks for an action.
If you want custom find options such as includes or pagination, you can build on this further since it is a scope.
The
Then the product will be built with that attribute in the controller.
This way it will pass authorization when the user accesses the
The attributes are then overridden by whatever is passed by the user in
If the model class is namespaced differently than the controller you will need to specify the
It is important that any custom loading behavior happens before the call to
Also see Nested Resources and Non RESTful Controllers.
authorize! method to manually handle authorization in a controller action. This will raise a CanCan::AccessDenied exception when the user does not have permission. See Exception Handling for how to react to this.def show
@project = Project.find(params[:project])
authorize! :show, @project
end
load_and_authorize_resource
method in your controller to load the resource into an instance
variable and authorize it automatically for every action in that
controller.class ProductsController < ActionController::Base
load_and_authorize_resource
end
load_resource and authorize_resource because they are two separate steps and you can choose to use one or the other.class ProductsController < ActionController::Base
load_resource
authorize_resource
end
skip_load_and_authorize_resource, skip_load_resource or skip_authorize_resource methods to skip any of the applied behavior and specify specific actions like in a before filter. For example.class ProductsController < ActionController::Base
load_and_authorize_resource
skip_authorize_resource :only => :new
end
Choosing Actions
By default this will apply to every action in the controller even if it is not one of the 7 RESTful actions. The action name will be passed in when authorizing. For example, if we have adiscontinue action on ProductsController it will have this behavior.class ProductsController < ActionController::Base
load_and_authorize_resource
def discontinue
# Automatically does the following:
# @product = Product.find(params[:id])
# authorize! :discontinue, @product
end
end
:except and :only options, just like a before_filter.load_and_authorize_resource :only => [:index, :show]
Choosing actions on nested resources
For this you can pass a name to skip_authorize_resource. For example:class CommentsController < ApplicationController
load_and_authorize_resource :post
load_and_authorize_resource :through => :post
skip_authorize_resource :only => :show
skip_authorize_resource :post, :only => :show
end
load_resource
index action
As of 1.4 the index action will load the collection resource usingaccessible_by.def index
# @products automatically set to Product.accessible_by(current_ability)
end
def index
@products = @products.includes(:category).page(params[:page])
end
@products variable will not be set initially if Product does not respond to accessible_by (such as if you aren't using a supported ORM). It will also not be set if you are only using a block in the can definitions because there is no way to determine which records to fetch from the database.show, edit, update and destroy actions
These member actions simply fetch the record directly.def show
# @product automatically set to Product.find(params[:id])
end
new and create actions
As of 1.4 these builder actions will initialize the resource with the attributes in the hash conditions. For example, if we have thiscan definition.can :manage, Product, :discontinued => false
@product = Product.new(:discontinued => false)
new action.The attributes are then overridden by whatever is passed by the user in
params[:product].Custom class
If the model is named differently than the controller, then you may explicitly name the model that should be loaded; however, you must specify that it is not a parent in a nested routing situation, ie:class ArticlesController < ApplicationController
load_and_authorize_resource :post, :parent => false
end
:class option.class ProductsController < ApplicationController
load_and_authorize_resource :class => "Store::Product"
end
Custom find
If you want to fetch a resource by something other thanid it can be done so using the find_by option.load_resource :find_by => :permalink # will use find_by_permalink!(params[:id])
authorize_resource
Override loading
The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separatebefore_filter.class BooksController < ApplicationController
before_filter :find_published_book, :only => :show
load_and_authorize_resource
private
def find_published_book
@book = Book.released.find(params[:id])
end
end
load_and_authorize_resource. If you have authorize_resource in your ApplicationController then you need to use prepend_before_filter to do the loading in the controller subclasses so it happens before authorization.authorize_resource
Addingauthorize_resource will make a before filter which calls authorize!,
passing the resource instance variable if it exists. If the instance
variable isn't set (such as in the index action) it will pass in the
class name. For example, if we have a ProductsController it will do this before each action.authorize!(params[:action], @product || Product)
More info
For additional information see theload_resource and authorize_resource methods in the RDoc.Also see Nested Resources and Non RESTful Controllers.
Resetting Current Ability
If you ever update a User record which may be the current user, it will make the current ability for that request stale. This means anycan? checks will use the user record before it was updated. You will need to reset the current_ability instance so it will be reloaded. Do the same for the current_user if you are caching that too.if @user.update_attributes(params[:user])
@current_ability = nil
@current_user = nil
# ...
end
Exception Handling
The
This exception can also be raised manually if you want more custom behavior.
The message can also be customized through internationalization.
Notice
You can catch the exception and modify its behavior in the
The action and subject can be retrieved through the exception to customize the behavior further.
The default error message can also be customized through the exception. This will be used if no message was provided.
If you prefer to return the 403 Forbidden HTTP code, create a
If you are getting unexpected behavior when rescuing from the exception it is best to add some logging . See Debugging Abilities for details.
See Authorization in Web Services for rescuing exceptions for XML responses.
CanCan::AccessDenied exception is raised when calling authorize! in the controller and the user is not able to perform the given action. A message can optionally be provided.authorize! :read, Article, :message => "Unable to read this article."
raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
# in config/locales/en.yml
en:
unauthorized:
manage:
all: "Not authorized to %{action} %{subject}."
user: "Not allowed to manage other user accounts."
update:
project: "Not allowed to update this project."
manage and all can be used to generalize the subject and actions. Also %{action} and %{subject} can be used as variables in the message.You can catch the exception and modify its behavior in the
ApplicationController. For example here we set the error message to a flash and redirect to the home page.class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
redirect_to main_app.root_url, :alert => exception.message
end
end
exception.action # => :read
exception.subject # => Article
exception.default_message = "Default error message"
exception.message # => "Default error message"
public/403.html file and write a rescue_from statement like this example in ApplicationController:class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied do |exception|
render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false
## to avoid deprecation warnings with Rails 3.2.x (and incidentally using Ruby 1.9.3 hash syntax)
## this render call should be:
# render file: "#{Rails.root}/public/403", formats: [:html], status: 403, layout: false
end
end
403.html must be pure HTML, CSS, and JavaScript--not a template. The fields of the exception are not available to it.If you are getting unexpected behavior when rescuing from the exception it is best to add some logging . See Debugging Abilities for details.
See Authorization in Web Services for rescuing exceptions for XML responses.
Ensure Authorization
If you want to be certain authorization is not forgotten in some controller action, add
This will add an
Here's another example where authorization is only ensured for the admin subdomain.
Note: The
check_authorization to your ApplicationController.class ApplicationController < ActionController::Base
check_authorization
end
after_filter to ensure authorization takes place in every inherited controller action. If no authorization happens it will raise a CanCan::AuthorizationNotPerformed exception. You can skip this check by adding skip_authorization_check to that controller. Both of these methods take the same arguments as before_filter so you can exclude certain actions with :only and :except.class UsersController < ApplicationController
skip_authorization_check :only => [:new, :create]
# ...
end
Conditionally Check Authorization
As of CanCan 1.6, thecheck_authorization method supports :if and :unless
options. Either one takes a method name as a symbol. This method will
be called to determine if the authorization check will be performed.
This makes it very easy to skip this check on all Devise controllers
since they provide a devise_controller? method.class ApplicationController < ActionController::Base
check_authorization :unless => :devise_controller?
end
class ApplicationController < ActionController::Base
check_authorization :if => :admin_subdomain?
private
def admin_subdomain?
request.subdomain == "admin"
end
end
check_authorization only ensures that authorization is performed. If you have authorize_resource the authorization will still be performed no matter what is returned here.Changing Defaults
CanCan makes two assumptions about your application.
The
Sometimes you might have a gem in your project which provides its own
Rails engine which also uses CanCan such as LocomotiveCMS. In this case
the current_ability override in the ApplicationController can also be
useful.
If your method that returns the currently logged in user just has another name than
That's it! See Accessing Request Data for a more complex example of what you can do here.
- You have an
Abilityclass which defines the permissions. - You have a
current_usermethod in the controller which returns the current user model.
current_ability method in your ApplicationController. The current method looks like this.def current_ability
@current_ability ||= Ability.new(current_user)
end
Ability class and current_user method can easily be changed to something else.# in ApplicationController
def current_ability
@current_ability ||= AccountAbility.new(current_account)
end
# in ApplicationController
def current_ability
if request.fullpath =~ /\/locomotive/
@current_ability ||= Locomotive::Ability.new(current_user)
else
@current_ability ||= Ability.new(current_user)
end
end
current_user, it may be the easiest solution to simply alias the method in your ApplicationController like this:class ApplicationController < ActionController::Base
alias_method :current_user, :name_of_your_method # Could be :current_member or :logged_in_user
end
Translating your app
To use translations in your app define some yaml like this:
Enjoy!
# en.yml
en:
unauthorized:
manage:
all: "You have no access!"
Translation for individual abilities
If you want to customize messages for some model or even for some ability define translation like this:# models/ability.rb
...
can :create, Article
...
# en.yml
en:
unauthorized:
create:
article: "Only admin may do this!"
Translating custom abilities
Also translations is available for your custom abilities:# models/ability.rb
...
can :vote, Article
...
# en.yml
en:
unauthorized:
vote:
article: "Only users which have one or more article may do this!"
Variables for translations
Finally you may useaction(which contain ability like 'create') and subject(for example 'article') variables in your translation:# en.yml
en:
unauthorized:
manage:
all: "You do not have access to %{action} %{subject}!"
CanCan 2.0
My ultimate goal in CanCan 2.0 is to make the behavior more intuitive. I've seen times in the issue tracker where it is used in a way it wasn't intended, or cases it authorized access when they didn't think it should. I'm taking all of this into account.The biggest change is that basic controller action authorization will be handled by default. This means everything will be locked down with one simple
enable_authorization call. The load_and_authorize_resource behavior is still there, but now only necessary if needing to check permission on resource instances.This means abilities will be more focused on controller actions than resource classes, but this doesn't mean resources are being left in the dust. Another big feature is that you can add fine-grain permissions on specific resource attributes. See the Resource Attributes section below.
README
CanCan 2.0 is still in very early development. Currently little of what I'm describing here works, but I am writing the readme first. See the 2.0 branch for progress on the implementation and the issue tracker for feedback.Setup
First add authentication if you haven't already. This can be done through Devise, Authlogic, etc. The only thing CanCan requires by default is acurrent_user method in the controller.To install CanCan, add it to your Gemfile and run the
bundle command.gem "cancan", "2.0" # not yet available
rails g cancan:ability
class ApplicationController < ActionController::Base
enable_authorization
end
CanCan::Unauthorized
exception will be raised since you have not granted the user ability to
access it. You can customize the behavior of this exception by passing a
block.enable_authorization do |exception|
redirect_to root_url, :alert => exception.message
end
enable_authorization
rescue_from CanCan::Unauthorized do |exception|
redirect_to root_url, :alert => exception.message
end
If you're using devise and only want to enable authorization for non-devise controllers, then you can achieve it like the code below.
enable_authorization do |exception|
redirect_to root_url, :alert => exception.message
end unless :devise_controller?
Defining Abilities
You grant access to controller actions through theAbility class. The current_user is passed into the initialize method allowing you to define permissions based on user attributes.def initialize(user)
if user
can :access, :all
else
can :access, :home
end
end
The first argument to
can is the name of the controller action. Using :access here will allow access to all actions on that controller. The second argument is the name of the controller. Using :all here will represent all controllers. Either one can be an array to represent multiple actions and controllers.can [:create, :update], [:posts, :comments]
new and edit actions because CanCan includes some default aliases. See the Aliases page for details.You can check permissions in any controller or view using the
can? method.<% if can? :create, :comments %>
<%= link_to "New Comment", new_comment_path %>
<% end %>
Ability Precedence
In CanCan 2.0, there's a slight change in the way in which can rules are evaluated. A more specificcan rule below a general can rule will overwrite it. can :read, :projects
can :read, :projects, :title => 'Sir'
Resources
What if you need to change authorization based on a model's attributes? You can do so by passing a hash of conditions as the last argument tocan. For example, if you want to only allow one to access projects which he owns you can set the :user_id option.can :access, :projects, :user_id => user.id
If you try visiting any of the project pages at this point you will see a
CanCan::InsufficientCheck exception is raised. This is because the default authorization has no way to check permissions on the @project instance. You can check permissions on an object manually using the authorize! method.def edit
@project = Project.find(params[:id])
authorize! :edit, @project
end
load_and_authorize_resource method to load the @project instance in every controller action and authorize it.class ProjectsController < ApplicationController
load_and_authorize_resource
def edit
# @project already loaded here and authorized
end
end
index (and other collection actions) will load the @projects
instance which automatically limits the projects the user is allowed to
access. This is a scope so you can make further calls to where to limit what is returned from the database.You can check permissions on instances using the
can? method as well.<%= link_to "Edit Project", edit_project_path if can? :update, @project %>
user_id matches.Resource Attributes
It is possible to define permissions on specific resource attributes. For example, if you want to allow a user to only update the name and priority of a project, pass that as the third argument tocan.can :update, :projects, [:name, :priority]
load_and_authorize_resource it will ensure that only those two attributes exist in params[:project] when updating the project. If you do this everywhere it will not be necessary to use attr_accessible in your models.You can combine this with a hash of conditions. For example, here the user can only update the price if the product isn't discontinued.
can :update, :products, :price, :discontinued => false
<%= f.text_field :name if can? :update, @project, :name %>
Comentários
Postar um comentário