updating some documentation for CanCan 2.0

This commit is contained in:
Ryan Bates 2011-03-25 18:48:37 -07:00
parent e5b76210e4
commit c6f9abb6ab
3 changed files with 96 additions and 118 deletions

View File

@ -1,111 +1,108 @@
= CanCan
Wiki[https://github.com/ryanb/cancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
This is the branch for CanCan 2.0 which is in very early development. For a stable release please check out the {master branch}[https://github.com/ryanb/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.
Here are some instructions for setting up CanCan 2.0. Try this out and provide feedback in the {issue tracker}[https://github.com/ryanb/cancan/issues].
== Installation
== Setup
In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
CanCan expects your controllers to have a +current_user+ method. Add some authentication for this (such as Devise[https://github.com/plataformatec/devise], Authlogic[https://github.com/binarylogic/authlogic] or {nifty:authentication}[https://github.com/ryanb/nifty-generators]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/Changing-Defaults] to customize this behavior.
gem "cancan"
To install CanCan, add it to your Gemfile and run the `bundle` command.
In <b>Rails 2</b>, add this to your environment.rb file.
gem "cancan", :git => "git://github.com/ryanb/cancan.git", :branch => "2.0"
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[https://github.com/binarylogic/authlogic] or Devise[https://github.com/plataformatec/devise]). See {Changing Defaults}[https://github.com/ryanb/cancan/wiki/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.
Next generate an Ability class, this is where your permissions will be defined.
rails g cancan:ability
See {Defining Abilities}[https://github.com/ryanb/cancan/wiki/defining-abilities] for details.
Add authorization by calling {enable_authorization}[https://github.com/ryanb/cancan/wiki/enable_authorization] in your ApplicationController.
class ApplicationController < ActionController::Base
enable_authorization
end
This will add an authorization check locking down every controller action. If you try visiting a page, a <tt>CanCan::Unauthorized</tt> exception will be raised since you have not granted the user ability to access it.
=== 2. Check Abilities & Authorization
== Defining Abilities
The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
You grant access to controller actions through the +Ability+ class which was generated above. The +current_user+ is passed in allowing you to define permissions based on user attributes. For example:
<% if can? :update, @article %>
<%= link_to "Edit", edit_article_path(@article) %>
if user
can :access, :all
else
can :access, :home
can :create, [:users, :sessions]
end
Here if there is a user he will be able to perform any action on any controller. If someone is not logged in he can only access the home, users, and sessions controllers.
The first argument to +can+ is the action the user can perform. The second argument is the controller name they can perform that action on. You can pass <tt>:access</tt> and <tt>:all</tt> to represent any action and controller respectively.
As shown above, pass an array to either of these will grant permission on each item in the array. It isn't necessary to pass the +new+ action here because CanCan includes some default aliases. See the {Aliases}[https://github.com/ryanb/cancan/wiki/Aliases] page for details.
You can check permissions in any controller or view using the <tt>can?</tt> method.
<% if can? :create, :comments %>
<%= link_to "New Comment", new_comment_path %>
<% end %>
See {Checking Abilities}[https://github.com/ryanb/cancan/wiki/checking-abilities] for more information
Here the link will only show up the user can create comments.
The <tt>authorize!</tt> 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
== Resource Conditions
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 to +can+. For example, if you want to only allow one to access projects which he owns you can set the <tt>:user_id</tt>.
can :access, :projects, :user_id => user.id
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 <tt>CanCan::InsufficientAuthorizationCheck</tt> exception is raised. This is because the default authorization has no way to check permissions on the <tt>@project</tt> instance. You can check permissions on an object manually using the <tt>authorize!</tt> method.
def edit
@project = Project.find(params[:id])
authorize! :edit, @project
end
Setting 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.
However this can get tedious. Instead CanCan provides a +load_and_authorize_resource+ method to load the <tt>@project</tt> instance in every controller action and authorize it.
class ArticlesController < ApplicationController
class ProjectsController < ApplicationController
load_and_authorize_resource
def show
# @article is already loaded and authorized
def edit
# @project already loaded here and authorized
end
end
See {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/authorizing-controller-actions] for more information.
The +index+ (and other collection actions) will load the <tt>@projects</tt> 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.
=== 3. Handle Unauthorized Access
If the user authorization fails, a <tt>CanCan::Unauthorized</tt> exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
class ApplicationController < ActionController::Base
rescue_from CanCan::Unauthorized do |exception|
redirect_to root_url, :alert => exception.message
end
def index
@projects = @projects.where(:hidden => false)
end
See {Exception Handling}[https://github.com/ryanb/cancan/wiki/exception-handling] for more information.
You can check permissions on instances using the <tt>can?</tt> method.
<%= link_to "Edit Project", edit_project_path if can? :update, @project %>
Here it will only show the edit link if the +user_id+ matches.
=== 4. Lock It Down
== Resource Attributes
If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
In CanCan 2.0 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 to +can+.
class ApplicationController < ActionController::Base
check_authorization
end
can :update, :projects, [:name, :priority]
This 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}[https://github.com/ryanb/cancan/wiki/Ensure-Authorization] for more information.
If you use this in combination with +load_and_authorize_resource+ it will ensure that only those two attributes exist in <tt>params[:project]</tt> 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 update all attributes except the price when the product is discontinued.
== Wiki Docs
can :update, :products
cannot :update, :products, :price, :discontinued => true
* {Upgrading to 1.6}[https://github.com/ryanb/cancan/wiki/Upgrading-to-1.6]
* {Defining Abilities}[https://github.com/ryanb/cancan/wiki/Defining-Abilities]
* {Checking Abilities}[https://github.com/ryanb/cancan/wiki/Checking-Abilities]
* {Authorizing Controller Actions}[https://github.com/ryanb/cancan/wiki/Authorizing-Controller-Actions]
* {Exception Handling}[https://github.com/ryanb/cancan/wiki/Exception-Handling]
* {Changing Defaults}[https://github.com/ryanb/cancan/wiki/Changing-Defaults]
* {See more}[https://github.com/ryanb/cancan/wiki]
You can check permissions on specific attributes to determine what to show in the form.
<%= f.text_field :name if can? :update, @project, :name %>
== Questions or Problems?
If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/ryanb/cancan/wiki], please add an {issue on GitHub}[https://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/ryanb/cancan/blob/master/spec/README.rdoc] for more information.
== Special Thanks
CanCan was inspired by declarative_authorization[https://github.com/stffn/declarative_authorization/] and aegis[https://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[https://github.com/ryanb/cancan/contributors]. See the CHANGELOG[https://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.

View File

@ -22,21 +22,12 @@ module CanCan
#
# You can also pass the class instead of an instance (if you don't have one handy).
#
# can? :create, Project
# can? :create, :projects
#
# Nested resources can be passed through a hash, this way conditions which are
# dependent upon the association will work when using a class.
#
# can? :create, @category => Project
#
# Any additional arguments will be passed into the "can" block definition. This
# can be used to pass more information about the user's request for example.
#
# can? :create, Project, request.remote_ip
#
# can :create Project do |project, remote_ip|
# # ...
# end
# can? :create, @category => :projects
#
# Not only can you use the can? method in the controller and view (see ControllerAdditions),
# but you can also call it directly on an ability instance.
@ -71,22 +62,22 @@ module CanCan
# Defines which abilities are allowed using 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
# can :update, :articles
#
# You can pass an array for either of these parameters to match any one.
# Here the user has the ability to update or destroy both articles and comments.
#
# can [:update, :destroy], [Article, Comment]
# can [:update, :destroy], [:articles, :comments]
#
# You can pass :all to match any object and :access to match any action. Here are some examples.
#
# can :access, :all
# can :update, :all
# can :access, Project
# can :access, :projects
#
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
#
# can :read, Project, :active => true, :user_id => user.id
# can :read, :projects, :active => true, :user_id => user.id
#
# See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
# are also used for initial attributes when building a record in ControllerAdditions#load_resource.
@ -94,7 +85,7 @@ module CanCan
# If the conditions hash does not give you enough control over defining abilities, you can use a block
# along with any Ruby code you want.
#
# can :update, Project do |project|
# can :update, :projects do |project|
# project.groups.include?(user.group)
# end
#
@ -102,22 +93,16 @@ module CanCan
# will be denied access. The downside to using a block is that it cannot be used to generate
# conditions for database queries.
#
# You can pass custom objects into this "can" method, this is usually done with a symbol
# and is useful if a class isn't available to define permissions on.
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a symbol.
#
# can :read, :stats
# can? :read, :stats # => true
#
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
#
# can :update, Project, :priority => 3
# can? :update, Project # => true
# can :update, :projects, :priority => 3
# can? :update, :projects # => true
#
# If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
# block will always be executed. This allows you to override the full behavior if the permissions are
# defined in an external source such as the database.
#
# can do |action, object_class, object|
# can do |action, subject, object|
# # check the database and return true/false
# end
#
@ -133,7 +118,7 @@ module CanCan
# A block can be passed just like "can", however if the logic is complex it is recommended
# to use the "can" method.
#
# cannot :read, Product do |product|
# cannot :read, :projects do |product|
# product.invisible?
# end
#
@ -144,19 +129,19 @@ module CanCan
# Alias one or more actions into another one.
#
# alias_action :update, :destroy, :to => :modify
# can :modify, Comment
# can :modify, :comments
#
# Then :modify permission will apply to both :update and :destroy requests.
#
# can? :update, Comment # => true
# can? :destroy, Comment # => true
# can? :update, :comments # => true
# can? :destroy, :comments # => true
#
# This only works in one direction. Passing the aliased action into the "can?" call
# will not work because aliases are meant to generate more generic actions.
#
# alias_action :update, :destroy, :to => :modify
# can :update, Comment
# can? :modify, Comment # => false
# can :update, :comments
# can? :modify, :comments # => false
#
# The following aliases are added by default for conveniently mapping common controller actions.
#

View File

@ -2,26 +2,22 @@ class Ability
include CanCan::Ability
def initialize(user)
# Define abilities for the passed in user here. For example:
# Define abilities for the passed in (current) user. For example:
#
# user ||= User.new # guest user (not logged in)
# if user.admin?
# can :manage, :all
# if user
# can :access, :all
# else
# can :read, :all
# can :access, :home
# can :create, [:users, :sessions]
# end
#
# The first argument to `can` is the action you are giving the user permission to do.
# If you pass :manage it will apply to every action. Other common actions here are
# :read, :create, :update and :destroy.
# Here if there is a user he will be able to perform any action on any controller.
# If someone is not logged in he can only access the home, users, and sessions controllers.
#
# The second argument is the resource the user can perform the action on. If you pass
# :all it will apply to every resource. Otherwise pass a Ruby class of the resource.
#
# The third argument is an optional hash of conditions to further filter the objects.
# For example, here the user can only update published articles.
#
# can :update, Article, :published => true
# The first argument to `can` is the action the user can perform. The second argument
# is the controller name they can perform that action on. You can pass :access and :all
# to represent any action and controller respectively. Passing an array to either of
# these will grant permission on each item in the array.
#
# See the wiki for details: https://github.com/ryanb/cancan/wiki/Defining-Abilities
end