2009-11-16 22:28:52 +00:00
module CanCan
2010-05-21 20:41:24 +00:00
2009-11-18 00:56:16 +00:00
# This module is designed to be included into an Ability class. This will
# provide the "can" methods for defining and checking abilities.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# class Ability
# include CanCan::Ability
#
# def initialize(user)
# if user.admin?
# can :manage, :all
# else
# can :read, :all
# end
# end
# end
2010-05-21 20:41:24 +00:00
#
2009-11-16 22:28:52 +00:00
module Ability
2010-09-03 23:19:39 +00:00
# Check if the user has permission to perform a given action on an object.
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# can? :destroy, @project
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# You can also pass the class instead of an instance (if you don't have one handy).
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# can? :create, Project
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# 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
#
2010-04-15 18:28:58 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2010-04-15 18:28:58 +00:00
# can? :create, Project, request.remote_ip
2010-05-21 20:41:24 +00:00
#
2010-04-15 18:28:58 +00:00
# can :create Project do |project, remote_ip|
# # ...
# end
2010-05-21 20:41:24 +00:00
#
# Not only can you use the can? method in the controller and view (see ControllerAdditions),
2009-11-18 00:56:16 +00:00
# but you can also call it directly on an ability instance.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# ability.can? :destroy, @project
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# This makes testing a user's abilities very easy.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# 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
2010-05-21 20:41:24 +00:00
#
# Also see the RSpec Matchers to aid in testing.
2010-04-16 15:55:36 +00:00
def can? ( action , subject , * extra_args )
2010-09-24 09:15:09 +00:00
match = relevant_can_definitions_for_match ( action , subject ) . detect do | can_definition |
2010-07-20 18:04:03 +00:00
can_definition . matches_conditions? ( action , subject , extra_args )
2010-05-16 18:13:02 +00:00
end
2010-07-20 18:04:03 +00:00
match ? match . base_behavior : false
2009-11-16 22:28:52 +00:00
end
2010-05-21 20:41:24 +00:00
2009-11-18 00:56:16 +00:00
# Convenience method which works the same as "can?" but returns the opposite value.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# cannot? :destroy, @project
2010-05-21 20:41:24 +00:00
#
2009-11-17 18:46:16 +00:00
def cannot? ( * args )
! can? ( * args )
end
2010-05-21 20:41:24 +00:00
2009-11-18 00:56:16 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# can :update, Article
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# You can pass an array for either of these parameters to match any one.
2010-09-03 23:19:39 +00:00
# Here the user has the ability to update or destroy both articles and comments.
2009-11-18 00:56:16 +00:00
#
# can [:update, :destroy], [Article, Comment]
#
2010-09-03 23:19:39 +00:00
# You can pass :all to match any object and :manage to match any action. Here are some examples.
#
# can :manage, :all
# can :update, :all
# can :manage, Project
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
2009-11-18 00:56:16 +00:00
#
2010-04-15 23:50:47 +00:00
# can :read, Project, :active => true, :user_id => user.id
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# 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.
2009-11-18 00:56:16 +00:00
#
2010-04-15 23:50:47 +00:00
# can :update, Project do |project|
2010-09-03 23:19:39 +00:00
# project.groups.include?(user.group)
2009-11-18 00:56:16 +00:00
# end
2010-05-21 20:41:24 +00:00
#
2010-04-15 23:50:47 +00:00
# If the block returns true then the user has that :update ability for that project, otherwise he
2010-09-03 23:19:39 +00:00
# will be denied access. The downside to using a block is that it cannot be used to generate
# conditions for database queries.
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# can :read, :stats
# can? :read, :stats # => true
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# can :update, Project, :priority => 3
# can? :update, Project # => true
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2010-09-03 23:19:39 +00:00
# can do |action, object_class, object|
# # check the database and return true/false
2009-11-18 00:56:16 +00:00
# end
2010-05-21 20:41:24 +00:00
#
2010-09-02 21:46:38 +00:00
def can ( action = nil , subject = nil , conditions = nil , & block )
2010-04-18 07:11:15 +00:00
can_definitions << CanDefinition . new ( true , action , subject , conditions , block )
2009-11-25 18:25:58 +00:00
end
2010-05-21 20:41:24 +00:00
# Defines an ability which cannot be done. Accepts the same arguments as "can".
#
2009-11-25 18:25:58 +00:00
# can :read, :all
# cannot :read, Comment
2010-05-21 20:41:24 +00:00
#
2009-11-25 18:25:58 +00:00
# A block can be passed just like "can", however if the logic is complex it is recommended
# to use the "can" method.
2010-05-21 20:41:24 +00:00
#
2009-11-25 18:25:58 +00:00
# cannot :read, Product do |product|
# product.invisible?
# end
2010-05-21 20:41:24 +00:00
#
2010-09-02 21:46:38 +00:00
def cannot ( action = nil , subject = nil , conditions = nil , & block )
2010-04-18 07:11:15 +00:00
can_definitions << CanDefinition . new ( false , action , subject , conditions , block )
2009-11-17 03:59:40 +00:00
end
2010-05-21 20:41:24 +00:00
2009-11-19 17:46:30 +00:00
# Alias one or more actions into another one.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# alias_action :update, :destroy, :to => :modify
# can :modify, Comment
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# Then :modify permission will apply to both :update and :destroy requests.
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# can? :update, Comment # => true
# can? :destroy, Comment # => true
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# 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.
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# alias_action :update, :destroy, :to => :modify
# can :update, Comment
# can? :modify, Comment # => false
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# Unless that exact alias is used.
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# can :modify, Comment
# can? :modify, Comment # => true
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# The following aliases are added by default for conveniently mapping common controller actions.
2010-05-21 20:41:24 +00:00
#
2009-11-18 00:56:16 +00:00
# alias_action :index, :show, :to => :read
# alias_action :new, :to => :create
# alias_action :edit, :to => :update
2010-05-21 20:41:24 +00:00
#
2009-11-19 17:46:30 +00:00
# This way one can use params[:action] in the controller to determine the permission.
2009-11-17 03:59:40 +00:00
def alias_action ( * args )
target = args . pop [ :to ]
2009-12-31 01:49:49 +00:00
aliased_actions [ target ] || = [ ]
aliased_actions [ target ] += args
2009-11-17 03:59:40 +00:00
end
2010-05-21 20:41:24 +00:00
2009-12-31 02:01:40 +00:00
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
2009-11-26 01:31:40 +00:00
def aliased_actions
@aliased_actions || = default_alias_actions
end
2010-05-21 20:41:24 +00:00
2009-12-31 02:01:40 +00:00
# Removes previously aliased actions including the defaults.
def clear_aliased_actions
@aliased_actions = { }
end
2010-05-21 20:41:24 +00:00
2010-07-20 20:20:01 +00:00
# Returns a CanCan::Query instance to help generate database queries based on the ability.
2010-07-20 23:00:22 +00:00
# If any relevant can definitions use a block then an exception will be raised because an
# SQL query cannot be generated from blocks of code.
2010-07-20 20:43:43 +00:00
def query ( action , subject )
2010-07-20 23:00:22 +00:00
Query . new ( subject , relevant_can_definitions_for_query ( action , subject ) )
2010-04-21 00:02:28 +00:00
end
2010-07-21 18:45:26 +00:00
2010-09-03 00:01:10 +00:00
# See ControllerAdditions#authorize! for documentation.
def authorize! ( action , subject , * args )
message = nil
if args . last . kind_of? ( Hash ) && args . last . has_key? ( :message )
message = args . pop [ :message ]
end
if cannot? ( action , subject , * args )
message || = unauthorized_message ( action , subject )
raise AccessDenied . new ( message , action , subject )
end
end
def unauthorized_message ( action , subject )
keys = unauthorized_message_keys ( action , subject )
2010-09-07 22:48:07 +00:00
variables = { :action = > action . to_s }
variables [ :subject ] = ( subject . class == Class ? subject : subject . class ) . to_s . downcase
message = I18n . translate ( nil , variables . merge ( :scope = > :unauthorized , :default = > keys + [ " " ] ) )
2010-09-03 00:01:10 +00:00
message . blank? ? nil : message
end
2010-09-03 18:53:47 +00:00
def attributes_for ( action , subject )
attributes = { }
relevant_can_definitions ( action , subject ) . map do | can_definition |
2010-09-03 21:00:46 +00:00
attributes . merge! ( can_definition . attributes_from_conditions ) if can_definition . base_behavior
2010-09-03 18:53:47 +00:00
end
attributes
end
2010-09-07 23:25:02 +00:00
def has_block? ( action , subject )
relevant_can_definitions ( action , subject ) . any? ( & :only_block? )
end
2010-09-24 09:15:09 +00:00
def has_raw_sql? ( action , subject )
relevant_can_definitions ( action , subject ) . any? ( & :only_raw_sql? )
end
2010-09-07 23:25:02 +00:00
2009-12-31 02:01:40 +00:00
private
2010-07-21 18:45:26 +00:00
2010-09-03 00:01:10 +00:00
def unauthorized_message_keys ( action , subject )
subject = ( subject . class == Class ? subject : subject . class ) . name . underscore unless subject . kind_of? Symbol
[ subject , :all ] . map do | try_subject |
[ aliases_for_action ( action ) , :manage ] . flatten . map do | try_action |
:" #{ try_action } . #{ try_subject } "
end
end . flatten
end
# Accepts an array of actions and returns an array of actions which match.
2010-07-19 23:03:09 +00:00
# This should be called before "matches?" and other checking methods since they
# rely on the actions to be expanded.
def expand_actions ( actions )
actions . map do | action |
aliased_actions [ action ] ? [ action , * expand_actions ( aliased_actions [ action ] ) ] : action
end . flatten
end
2010-05-21 20:41:24 +00:00
2010-09-03 00:01:10 +00:00
# Given an action, it will try to find all of the actions which are aliased to it.
# This does the opposite kind of lookup as expand_actions.
def aliases_for_action ( action )
results = [ action ]
aliased_actions . each do | aliased_action , actions |
results += aliases_for_action ( aliased_action ) if actions . include? action
end
results
end
2010-04-18 07:11:15 +00:00
def can_definitions
@can_definitions || = [ ]
end
2010-05-21 20:41:24 +00:00
2010-07-20 18:04:03 +00:00
# Returns an array of CanDefinition instances which match the action and subject
# This does not take into consideration any hash conditions or block statements
def relevant_can_definitions ( action , subject )
can_definitions . reverse . select do | can_definition |
can_definition . expanded_actions = expand_actions ( can_definition . actions )
can_definition . relevant? action , subject
2010-04-15 23:50:47 +00:00
end
end
2010-09-24 09:15:09 +00:00
def relevant_can_definitions_for_match ( action , subject )
relevant_can_definitions ( action , subject ) . each do | can_definition |
if can_definition . only_raw_sql?
raise Error , " The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{ action . inspect } #{ subject . inspect } "
end
end
end
2010-05-21 20:41:24 +00:00
2010-07-20 23:00:22 +00:00
def relevant_can_definitions_for_query ( action , subject )
2010-07-20 20:20:01 +00:00
relevant_can_definitions ( action , subject ) . each do | can_definition |
if can_definition . only_block?
2010-08-30 20:40:31 +00:00
raise Error , " The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{ action . inspect } #{ subject . inspect } "
2010-07-20 20:20:01 +00:00
end
end
end
2009-11-17 03:59:40 +00:00
def default_alias_actions
{
:read = > [ :index , :show ] ,
:create = > [ :new ] ,
:update = > [ :edit ] ,
}
end
2009-11-16 22:28:52 +00:00
end
end