From c6f9abb6abf561a7265a45dc7f76f68132393bfc Mon Sep 17 00:00:00 2001 From: Ryan Bates Date: Fri, 25 Mar 2011 18:48:37 -0700 Subject: [PATCH] updating some documentation for CanCan 2.0 --- README.rdoc | 139 +++++++++--------- lib/cancan/ability.rb | 49 +++--- .../cancan/ability/templates/ability.rb | 26 ++-- 3 files changed, 96 insertions(+), 118 deletions(-) diff --git a/README.rdoc b/README.rdoc index e87c71f..e72fb29 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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 Rails 3, 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 Rails 2, 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 CanCan::Unauthorized 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 can? and cannot? 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 :access and :all 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 can? 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 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 +== 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 :user_id. + + 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 CanCan::InsufficientAuthorizationCheck 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 -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 @project 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 @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. - -=== 3. Handle Unauthorized Access - -If the user authorization fails, a CanCan::Unauthorized 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 can? 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 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 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. diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index 9e825ba..2ec2a23 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -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. # diff --git a/lib/generators/cancan/ability/templates/ability.rb b/lib/generators/cancan/ability/templates/ability.rb index 58929d7..306e78d 100644 --- a/lib/generators/cancan/ability/templates/ability.rb +++ b/lib/generators/cancan/ability/templates/ability.rb @@ -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