modifying Ability to use symbol for subject instead of class, also adding subject aliases
This commit is contained in:
parent
5d97cfb236
commit
98ed39264e
|
@ -8,7 +8,7 @@ module CanCan
|
||||||
#
|
#
|
||||||
# def initialize(user)
|
# def initialize(user)
|
||||||
# if user.admin?
|
# if user.admin?
|
||||||
# can :manage, :all
|
# can :access, :all
|
||||||
# else
|
# else
|
||||||
# can :read, :all
|
# can :read, :all
|
||||||
# end
|
# end
|
||||||
|
@ -78,11 +78,11 @@ module CanCan
|
||||||
#
|
#
|
||||||
# can [:update, :destroy], [Article, Comment]
|
# can [:update, :destroy], [Article, Comment]
|
||||||
#
|
#
|
||||||
# You can pass :all to match any object and :manage to match any action. Here are some examples.
|
# You can pass :all to match any object and :access to match any action. Here are some examples.
|
||||||
#
|
#
|
||||||
# can :manage, :all
|
# can :access, :all
|
||||||
# can :update, :all
|
# can :update, :all
|
||||||
# can :manage, Project
|
# can :access, Project
|
||||||
#
|
#
|
||||||
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
# You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
|
||||||
#
|
#
|
||||||
|
@ -158,11 +158,6 @@ module CanCan
|
||||||
# can :update, Comment
|
# can :update, Comment
|
||||||
# can? :modify, Comment # => false
|
# can? :modify, Comment # => false
|
||||||
#
|
#
|
||||||
# Unless that exact alias is used.
|
|
||||||
#
|
|
||||||
# can :modify, Comment
|
|
||||||
# can? :modify, Comment # => true
|
|
||||||
#
|
|
||||||
# The following aliases are added by default for conveniently mapping common controller actions.
|
# The following aliases are added by default for conveniently mapping common controller actions.
|
||||||
#
|
#
|
||||||
# alias_action :index, :show, :to => :read
|
# alias_action :index, :show, :to => :read
|
||||||
|
@ -172,18 +167,42 @@ module CanCan
|
||||||
# This way one can use params[:action] in the controller to determine the permission.
|
# This way one can use params[:action] in the controller to determine the permission.
|
||||||
def alias_action(*args)
|
def alias_action(*args)
|
||||||
target = args.pop[:to]
|
target = args.pop[:to]
|
||||||
aliased_actions[target] ||= []
|
aliases[:actions][target] ||= []
|
||||||
aliased_actions[target] += args
|
aliases[:actions][target] += args
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
|
# Alias one or more subjects into another one.
|
||||||
def aliased_actions
|
#
|
||||||
@aliased_actions ||= default_alias_actions
|
# alias_subject :admins, :moderators, :to => :users
|
||||||
|
# can :update, :users
|
||||||
|
#
|
||||||
|
# Then :modify permission will apply to both :update and :destroy requests.
|
||||||
|
#
|
||||||
|
# can? :update, :admins # => true
|
||||||
|
# can? :update, :moderators # => true
|
||||||
|
#
|
||||||
|
# This only works in one direction. Passing the aliased subject into the "can?" call
|
||||||
|
# will not work because aliases are meant to generate more generic subjects.
|
||||||
|
#
|
||||||
|
# alias_subject :admins, :moderators, :to => :users
|
||||||
|
# can :update, :admins
|
||||||
|
# can? :update, :users # => false
|
||||||
|
#
|
||||||
|
def alias_subject(*args)
|
||||||
|
target = args.pop[:to]
|
||||||
|
aliases[:subjects][target] ||= []
|
||||||
|
aliases[:subjects][target] += args
|
||||||
end
|
end
|
||||||
|
|
||||||
# Removes previously aliased actions including the defaults.
|
# Returns a hash of action and subject aliases.
|
||||||
def clear_aliased_actions
|
def aliases
|
||||||
@aliased_actions = {}
|
@aliases ||= default_aliases
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes previously aliased actions or subjects including the defaults.
|
||||||
|
def clear_aliases
|
||||||
|
aliases[:actions] = {}
|
||||||
|
aliases[:subjects] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def model_adapter(model_class, action)
|
def model_adapter(model_class, action)
|
||||||
|
@ -206,7 +225,7 @@ module CanCan
|
||||||
def unauthorized_message(action, subject)
|
def unauthorized_message(action, subject)
|
||||||
keys = unauthorized_message_keys(action, subject)
|
keys = unauthorized_message_keys(action, subject)
|
||||||
variables = {:action => action.to_s}
|
variables = {:action => action.to_s}
|
||||||
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
variables[:subject] = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
|
||||||
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
|
||||||
message.blank? ? nil : message
|
message.blank? ? nil : message
|
||||||
end
|
end
|
||||||
|
@ -230,9 +249,9 @@ module CanCan
|
||||||
private
|
private
|
||||||
|
|
||||||
def unauthorized_message_keys(action, subject)
|
def unauthorized_message_keys(action, subject)
|
||||||
subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
|
subject = (subject.kind_of?(Symbol) ? subject.to_s : subject.class.to_s.underscore.humanize.downcase.pluralize)
|
||||||
[subject, :all].map do |try_subject|
|
[aliases_for(:subjects, subject.to_sym), :all].flatten.map do |try_subject|
|
||||||
[aliases_for_action(action), :manage].flatten.map do |try_action|
|
[aliases_for(:actions, action.to_sym), :access].flatten.map do |try_action|
|
||||||
:"#{try_action}.#{try_subject}"
|
:"#{try_action}.#{try_subject}"
|
||||||
end
|
end
|
||||||
end.flatten
|
end.flatten
|
||||||
|
@ -241,18 +260,18 @@ module CanCan
|
||||||
# Accepts an array of actions and returns an array of actions which match.
|
# Accepts an array of actions and returns an array of actions which match.
|
||||||
# This should be called before "matches?" and other checking methods since they
|
# This should be called before "matches?" and other checking methods since they
|
||||||
# rely on the actions to be expanded.
|
# rely on the actions to be expanded.
|
||||||
def expand_actions(actions)
|
def expand_aliases(type, items)
|
||||||
actions.map do |action|
|
items.map do |item|
|
||||||
aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
|
aliases[type][item] ? [item, *expand_aliases(type, aliases[type][item])] : item
|
||||||
end.flatten
|
end.flatten
|
||||||
end
|
end
|
||||||
|
|
||||||
# Given an action, it will try to find all of the actions which are aliased to it.
|
# 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.
|
# This does the opposite kind of lookup as expand_aliases.
|
||||||
def aliases_for_action(action)
|
def aliases_for(type, action)
|
||||||
results = [action]
|
results = [action]
|
||||||
aliased_actions.each do |aliased_action, actions|
|
aliases[type].each do |aliased_action, actions|
|
||||||
results += aliases_for_action(aliased_action) if actions.include? action
|
results += aliases_for(type, aliased_action) if actions.include? action
|
||||||
end
|
end
|
||||||
results
|
results
|
||||||
end
|
end
|
||||||
|
@ -265,7 +284,8 @@ module CanCan
|
||||||
# This does not take into consideration any hash conditions or block statements
|
# This does not take into consideration any hash conditions or block statements
|
||||||
def relevant_rules(action, subject)
|
def relevant_rules(action, subject)
|
||||||
rules.reverse.select do |rule|
|
rules.reverse.select do |rule|
|
||||||
rule.expanded_actions = expand_actions(rule.actions)
|
rule.expanded_actions = expand_aliases(:actions, rule.actions)
|
||||||
|
rule.expanded_subjects = expand_aliases(:subjects, rule.subjects)
|
||||||
rule.relevant? action, subject
|
rule.relevant? action, subject
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -286,11 +306,15 @@ module CanCan
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_alias_actions
|
def default_aliases
|
||||||
{
|
{
|
||||||
:read => [:index, :show],
|
:subjects => {},
|
||||||
:create => [:new],
|
:actions => {
|
||||||
:update => [:edit],
|
:read => [:index, :show],
|
||||||
|
:create => [:new],
|
||||||
|
:update => [:edit],
|
||||||
|
:destroy => [:delete],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ module CanCan
|
||||||
# helpful methods to determine permission checking and conditions hash generation.
|
# helpful methods to determine permission checking and conditions hash generation.
|
||||||
class Rule # :nodoc:
|
class Rule # :nodoc:
|
||||||
attr_reader :base_behavior, :subjects, :actions, :conditions
|
attr_reader :base_behavior, :subjects, :actions, :conditions
|
||||||
attr_writer :expanded_actions
|
attr_writer :expanded_actions, :expanded_subjects
|
||||||
|
|
||||||
# The first argument when initializing is the base_behavior which is a true/false
|
# The first argument when initializing is the base_behavior which is a true/false
|
||||||
# value. True for "can" and false for "cannot". The next two arguments are the action
|
# value. True for "can" and false for "cannot". The next two arguments are the action
|
||||||
|
@ -30,11 +30,11 @@ module CanCan
|
||||||
def matches_conditions?(action, subject, extra_args)
|
def matches_conditions?(action, subject, extra_args)
|
||||||
if @match_all
|
if @match_all
|
||||||
call_block_with_all(action, subject, extra_args)
|
call_block_with_all(action, subject, extra_args)
|
||||||
elsif @block && !subject_class?(subject)
|
elsif @block && subject_object?(subject)
|
||||||
@block.call(subject, *extra_args)
|
@block.call(subject, *extra_args)
|
||||||
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
elsif @conditions.kind_of?(Hash) && subject.class == Hash
|
||||||
nested_subject_matches_conditions?(subject)
|
nested_subject_matches_conditions?(subject)
|
||||||
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
|
elsif @conditions.kind_of?(Hash) && subject_object?(subject)
|
||||||
matches_conditions_hash?(subject)
|
matches_conditions_hash?(subject)
|
||||||
else
|
else
|
||||||
# Don't stop at "cannot" definitions when there are conditions.
|
# Don't stop at "cannot" definitions when there are conditions.
|
||||||
|
@ -72,21 +72,24 @@ module CanCan
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def subject_class?(subject)
|
def subject_object?(subject)
|
||||||
klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
# klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
|
||||||
klass == Class || klass == Module
|
# klass == Class || klass == Module
|
||||||
|
!subject.kind_of?(Symbol)
|
||||||
end
|
end
|
||||||
|
|
||||||
def matches_action?(action)
|
def matches_action?(action)
|
||||||
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
|
@expanded_actions.include?(:access) || @expanded_actions.include?(action)
|
||||||
end
|
end
|
||||||
|
|
||||||
def matches_subject?(subject)
|
def matches_subject?(subject)
|
||||||
@subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
|
subject = subject_name(subject) if subject_object? subject
|
||||||
|
@expanded_subjects.include?(:all) || @expanded_subjects.include?(subject) # || matches_subject_class?(subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO deperecate this
|
||||||
def matches_subject_class?(subject)
|
def matches_subject_class?(subject)
|
||||||
@subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
|
@expanded_subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if the given subject matches the given conditions hash.
|
# Checks if the given subject matches the given conditions hash.
|
||||||
|
@ -128,15 +131,19 @@ module CanCan
|
||||||
end
|
end
|
||||||
|
|
||||||
def call_block_with_all(action, subject, extra_args)
|
def call_block_with_all(action, subject, extra_args)
|
||||||
if subject.class == Class
|
if subject_object? subject
|
||||||
@block.call(action, subject, nil, *extra_args)
|
@block.call(action, subject_name(subject), subject, *extra_args)
|
||||||
else
|
else
|
||||||
@block.call(action, subject.class, subject, *extra_args)
|
@block.call(action, subject, nil, *extra_args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def subject_name(subject)
|
||||||
|
subject.class.to_s.underscore.humanize.downcase.pluralize.to_sym
|
||||||
|
end
|
||||||
|
|
||||||
def model_adapter(subject)
|
def model_adapter(subject)
|
||||||
ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
ModelAdapters::AbstractAdapter.adapter_class(subject_object?(subject) ? subject.class : subject)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,341 +6,255 @@ describe CanCan::Ability do
|
||||||
@ability.extend(CanCan::Ability)
|
@ability.extend(CanCan::Ability)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be able to :read anything" do
|
|
||||||
@ability.can :read, :all
|
# Basic Action & Subject
|
||||||
@ability.can?(:read, String).should be_true
|
|
||||||
@ability.can?(:read, 123).should be_true
|
it "allows access to only what is defined" do
|
||||||
|
@ability.can?(:paint, :fences).should be_false
|
||||||
|
@ability.can :paint, :fences
|
||||||
|
@ability.can?(:paint, :fences).should be_true
|
||||||
|
@ability.can?(:wax, :fences).should be_false
|
||||||
|
@ability.can?(:paint, :cars).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not have permission to do something it doesn't know about" do
|
it "allows access to everything when :access, :all is used" do
|
||||||
@ability.can?(:foodfight, String).should be_false
|
@ability.can?(:paint, :fences).should be_false
|
||||||
|
@ability.can :access, :all
|
||||||
|
@ability.can?(:paint, :fences).should be_true
|
||||||
|
@ability.can?(:wax, :fences).should be_true
|
||||||
|
@ability.can?(:paint, :cars).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should pass true to `can?` when non false/nil is returned in block" do
|
it "allows access to multiple actions and subjects" do
|
||||||
@ability.can :read, :all
|
@ability.can [:paint, :sand], [:fences, :decks]
|
||||||
@ability.can :read, Symbol do |sym|
|
@ability.can?(:paint, :fences).should be_true
|
||||||
"foo" # TODO test that sym is nil when no instance is passed
|
@ability.can?(:sand, :fences).should be_true
|
||||||
end
|
@ability.can?(:paint, :decks).should be_true
|
||||||
@ability.can?(:read, :some_symbol).should == true
|
@ability.can?(:sand, :decks).should be_true
|
||||||
|
@ability.can?(:wax, :fences).should be_false
|
||||||
|
@ability.can?(:paint, :cars).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should pass nil to a block when no instance is passed" do
|
|
||||||
@ability.can :read, Symbol do |sym|
|
# Aliases
|
||||||
sym.should be_nil
|
|
||||||
true
|
it "has default index, show, new, update, delete aliases" do
|
||||||
end
|
@ability.can :read, :projects
|
||||||
@ability.can?(:read, Symbol).should be_true
|
@ability.can?(:index, :projects).should be_true
|
||||||
|
@ability.can?(:show, :projects).should be_true
|
||||||
|
@ability.can :create, :projects
|
||||||
|
@ability.can?(:new, :projects).should be_true
|
||||||
|
@ability.can :update, :projects
|
||||||
|
@ability.can?(:edit, :projects).should be_true
|
||||||
|
@ability.can :destroy, :projects
|
||||||
|
@ability.can?(:delete, :projects).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should pass to previous rule, if block returns false or nil" do
|
it "follows deep action aliases" do
|
||||||
@ability.can :read, Symbol
|
|
||||||
@ability.can :read, Integer do |i|
|
|
||||||
i < 5
|
|
||||||
end
|
|
||||||
@ability.can :read, Integer do |i|
|
|
||||||
i > 10
|
|
||||||
end
|
|
||||||
@ability.can?(:read, Symbol).should be_true
|
|
||||||
@ability.can?(:read, 11).should be_true
|
|
||||||
@ability.can?(:read, 1).should be_true
|
|
||||||
@ability.can?(:read, 6).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not pass class with object if :all objects are accepted" do
|
|
||||||
@ability.can :preview, :all do |object|
|
|
||||||
object.should == 123
|
|
||||||
@block_called = true
|
|
||||||
end
|
|
||||||
@ability.can?(:preview, 123)
|
|
||||||
@block_called.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not call block when only class is passed, only return true" do
|
|
||||||
@block_called = false
|
|
||||||
@ability.can :preview, :all do |object|
|
|
||||||
@block_called = true
|
|
||||||
end
|
|
||||||
@ability.can?(:preview, Hash).should be_true
|
|
||||||
@block_called.should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should pass only object for global manage actions" do
|
|
||||||
@ability.can :manage, String do |object|
|
|
||||||
object.should == "foo"
|
|
||||||
@block_called = true
|
|
||||||
end
|
|
||||||
@ability.can?(:stuff, "foo").should
|
|
||||||
@block_called.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should alias update or destroy actions to modify action" do
|
|
||||||
@ability.alias_action :update, :destroy, :to => :modify
|
@ability.alias_action :update, :destroy, :to => :modify
|
||||||
@ability.can :modify, :all
|
@ability.can :modify, :projects
|
||||||
@ability.can?(:update, 123).should be_true
|
@ability.can?(:update, :projects).should be_true
|
||||||
@ability.can?(:destroy, 123).should be_true
|
@ability.can?(:destroy, :projects).should be_true
|
||||||
|
@ability.can?(:edit, :projects).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow deeply nested aliased actions" do
|
it "adds up action aliases" do
|
||||||
@ability.alias_action :increment, :to => :sort
|
|
||||||
@ability.alias_action :sort, :to => :modify
|
|
||||||
@ability.can :modify, :all
|
|
||||||
@ability.can?(:increment, 123).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should always call block with arguments when passing no arguments to can" do
|
|
||||||
@ability.can do |action, object_class, object|
|
|
||||||
action.should == :foo
|
|
||||||
object_class.should == 123.class
|
|
||||||
object.should == 123
|
|
||||||
@block_called = true
|
|
||||||
end
|
|
||||||
@ability.can?(:foo, 123)
|
|
||||||
@block_called.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should pass nil to object when comparing class with can check" do
|
|
||||||
@ability.can do |action, object_class, object|
|
|
||||||
action.should == :foo
|
|
||||||
object_class.should == Hash
|
|
||||||
object.should be_nil
|
|
||||||
@block_called = true
|
|
||||||
end
|
|
||||||
@ability.can?(:foo, Hash)
|
|
||||||
@block_called.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should automatically alias index and show into read calls" do
|
|
||||||
@ability.can :read, :all
|
|
||||||
@ability.can?(:index, 123).should be_true
|
|
||||||
@ability.can?(:show, 123).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should automatically alias new and edit into create and update respectively" do
|
|
||||||
@ability.can :create, :all
|
|
||||||
@ability.can :update, :all
|
|
||||||
@ability.can?(:new, 123).should be_true
|
|
||||||
@ability.can?(:edit, 123).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not respond to prepare (now using initialize)" do
|
|
||||||
@ability.should_not respond_to(:prepare)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should offer cannot? method which is simply invert of can?" do
|
|
||||||
@ability.cannot?(:tie, String).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be able to specify multiple actions and match any" do
|
|
||||||
@ability.can [:read, :update], :all
|
|
||||||
@ability.can?(:read, 123).should be_true
|
|
||||||
@ability.can?(:update, 123).should be_true
|
|
||||||
@ability.can?(:count, 123).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should be able to specify multiple classes and match any" do
|
|
||||||
@ability.can :update, [String, Range]
|
|
||||||
@ability.can?(:update, "foo").should be_true
|
|
||||||
@ability.can?(:update, 1..3).should be_true
|
|
||||||
@ability.can?(:update, 123).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should support custom objects in the rule" do
|
|
||||||
@ability.can :read, :stats
|
|
||||||
@ability.can?(:read, :stats).should be_true
|
|
||||||
@ability.can?(:update, :stats).should be_false
|
|
||||||
@ability.can?(:read, :nonstats).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should check ancestors of class" do
|
|
||||||
@ability.can :read, Numeric
|
|
||||||
@ability.can?(:read, Integer).should be_true
|
|
||||||
@ability.can?(:read, 1.23).should be_true
|
|
||||||
@ability.can?(:read, "foo").should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should support 'cannot' method to define what user cannot do" do
|
|
||||||
@ability.can :read, :all
|
|
||||||
@ability.cannot :read, Integer
|
|
||||||
@ability.can?(:read, "foo").should be_true
|
|
||||||
@ability.can?(:read, 123).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should pass to previous rule, if block returns false or nil" do
|
|
||||||
@ability.can :read, :all
|
|
||||||
@ability.cannot :read, Integer do |int|
|
|
||||||
int > 10 ? nil : ( int > 5 )
|
|
||||||
end
|
|
||||||
@ability.can?(:read, "foo").should be_true
|
|
||||||
@ability.can?(:read, 3).should be_true
|
|
||||||
@ability.can?(:read, 8).should be_false
|
|
||||||
@ability.can?(:read, 123).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should always return `false` for single cannot definition" do
|
|
||||||
@ability.cannot :read, Integer do |int|
|
|
||||||
int > 10 ? nil : ( int > 5 )
|
|
||||||
end
|
|
||||||
@ability.can?(:read, "foo").should be_false
|
|
||||||
@ability.can?(:read, 3).should be_false
|
|
||||||
@ability.can?(:read, 8).should be_false
|
|
||||||
@ability.can?(:read, 123).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should pass to previous cannot definition, if block returns false or nil" do
|
|
||||||
@ability.cannot :read, :all
|
|
||||||
@ability.can :read, Integer do |int|
|
|
||||||
int > 10 ? nil : ( int > 5 )
|
|
||||||
end
|
|
||||||
@ability.can?(:read, "foo").should be_false
|
|
||||||
@ability.can?(:read, 3).should be_false
|
|
||||||
@ability.can?(:read, 10).should be_true
|
|
||||||
@ability.can?(:read, 123).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should append aliased actions" do
|
|
||||||
@ability.alias_action :update, :to => :modify
|
@ability.alias_action :update, :to => :modify
|
||||||
@ability.alias_action :destroy, :to => :modify
|
@ability.alias_action :destroy, :to => :modify
|
||||||
@ability.aliased_actions[:modify].should == [:update, :destroy]
|
@ability.can :modify, :projects
|
||||||
|
@ability.can?(:update, :projects).should be_true
|
||||||
|
@ability.can?(:destroy, :projects).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should clear aliased actions" do
|
it "follows deep subject aliases" do
|
||||||
@ability.alias_action :update, :to => :modify
|
@ability.alias_subject :mammals, :to => :animals
|
||||||
@ability.clear_aliased_actions
|
@ability.alias_subject :cats, :to => :mammals
|
||||||
@ability.aliased_actions[:modify].should be_nil
|
@ability.can :pet, :animals
|
||||||
|
@ability.can?(:pet, :mammals).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should pass additional arguments to block from can?" do
|
it "clears current and default aliases" do
|
||||||
@ability.can :read, Integer do |int, x|
|
@ability.alias_action :update, :destroy, :to => :modify
|
||||||
int > x
|
@ability.clear_aliases
|
||||||
end
|
@ability.can :modify, :projects
|
||||||
@ability.can?(:read, 2, 1).should be_true
|
@ability.can?(:update, :projects).should be_false
|
||||||
@ability.can?(:read, 2, 3).should be_false
|
@ability.can :read, :projects
|
||||||
|
@ability.can?(:show, :projects).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should use conditions as third parameter and determine abilities from it" do
|
|
||||||
@ability.can :read, Range, :begin => 1, :end => 3
|
# Hash Conditions
|
||||||
|
|
||||||
|
it "maps object to pluralized subject name" do
|
||||||
|
@ability.can :read, :ranges
|
||||||
|
@ability.can?(:read, :ranges).should be_true
|
||||||
@ability.can?(:read, 1..3).should be_true
|
@ability.can?(:read, 1..3).should be_true
|
||||||
@ability.can?(:read, 1..4).should be_false
|
@ability.can?(:read, 123).should be_false
|
||||||
@ability.can?(:read, Range).should be_true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow an array of options in conditions hash" do
|
it "checks conditions hash on instances only" do
|
||||||
@ability.can :read, Range, :begin => [1, 3, 5]
|
@ability.can :read, :ranges, :begin => 1
|
||||||
|
@ability.can?(:read, :ranges).should be_true
|
||||||
|
@ability.can?(:read, 1..3).should be_true
|
||||||
|
@ability.can?(:read, 2..4).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "checks conditions on both rules and matches either one" do
|
||||||
|
@ability.can :read, :ranges, :begin => 1
|
||||||
|
@ability.can :read, :ranges, :begin => 2
|
||||||
|
@ability.can?(:read, 1..3).should be_true
|
||||||
|
@ability.can?(:read, 2..4).should be_true
|
||||||
|
@ability.can?(:read, 3..5).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "checks an array of options in conditions hash" do
|
||||||
|
@ability.can :read, :ranges, :begin => [1, 3, 5]
|
||||||
@ability.can?(:read, 1..3).should be_true
|
@ability.can?(:read, 1..3).should be_true
|
||||||
@ability.can?(:read, 2..4).should be_false
|
@ability.can?(:read, 2..4).should be_false
|
||||||
@ability.can?(:read, 3..5).should be_true
|
@ability.can?(:read, 3..5).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow a range of options in conditions hash" do
|
it "checks a range of options in conditions hash" do
|
||||||
@ability.can :read, Range, :begin => 1..3
|
@ability.can :read, :ranges, :begin => 1..3
|
||||||
@ability.can?(:read, 1..10).should be_true
|
@ability.can?(:read, 1..10).should be_true
|
||||||
@ability.can?(:read, 3..30).should be_true
|
@ability.can?(:read, 3..30).should be_true
|
||||||
@ability.can?(:read, 4..40).should be_false
|
@ability.can?(:read, 4..40).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow nested hashes in conditions hash" do
|
it "checks nested conditions hash" do
|
||||||
@ability.can :read, Range, :begin => { :to_i => 5 }
|
@ability.can :read, :ranges, :begin => { :to_i => 5 }
|
||||||
@ability.can?(:read, 5..7).should be_true
|
@ability.can?(:read, 5..7).should be_true
|
||||||
@ability.can?(:read, 6..8).should be_false
|
@ability.can?(:read, 6..8).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should match any element passed in to nesting if it's an array (for has_many associations)" do
|
it "matches any element passed in to nesting if it's an array (for has_many associations)" do
|
||||||
@ability.can :read, Range, :to_a => { :to_i => 3 }
|
@ability.can :read, :ranges, :to_a => { :to_i => 3 }
|
||||||
@ability.can?(:read, 1..5).should be_true
|
@ability.can?(:read, 1..5).should be_true
|
||||||
@ability.can?(:read, 4..6).should be_false
|
@ability.can?(:read, 4..6).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not stop at cannot definition when comparing class" do
|
|
||||||
@ability.can :read, Range
|
|
||||||
@ability.cannot :read, Range, :begin => 1
|
|
||||||
@ability.can?(:read, 2..5).should be_true
|
|
||||||
@ability.can?(:read, 1..5).should be_false
|
|
||||||
@ability.can?(:read, Range).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should stop at cannot definition when no hash is present" do
|
# Block Conditions
|
||||||
@ability.can :read, :all
|
|
||||||
@ability.cannot :read, Range
|
|
||||||
@ability.can?(:read, 1..5).should be_false
|
|
||||||
@ability.can?(:read, Range).should be_false
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should allow to check ability for Module" do
|
it "executes block passing object only when instance is used" do
|
||||||
module B; end
|
@ability.can :read, :ranges do |range|
|
||||||
class A; include B; end
|
range.begin == 5
|
||||||
@ability.can :read, B
|
|
||||||
@ability.can?(:read, A).should be_true
|
|
||||||
@ability.can?(:read, A.new).should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should pass nil to a block for ability on Module when no instance is passed" do
|
|
||||||
module B; end
|
|
||||||
class A; include B; end
|
|
||||||
@ability.can :read, B do |sym|
|
|
||||||
sym.should be_nil
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
@ability.can?(:read, B).should be_true
|
@ability.can?(:read, :ranges).should be_true
|
||||||
@ability.can?(:read, A).should be_true
|
@ability.can?(:read, 5..7).should be_true
|
||||||
|
@ability.can?(:read, 6..8).should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
it "passing a hash of subjects should check permissions through association" do
|
it "returns true when other object is returned in block" do
|
||||||
@ability.can :read, Range, :string => {:length => 3}
|
@ability.can :read, :ranges do |range|
|
||||||
@ability.can?(:read, "foo" => Range).should be_true
|
"foo"
|
||||||
@ability.can?(:read, "foobar" => Range).should be_false
|
end
|
||||||
@ability.can?(:read, 123 => Range).should be_true
|
@ability.can?(:read, 5..7).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow to check ability on Hash-like object" do
|
it "passes to previous rule when block returns false" do
|
||||||
|
@ability.can :read, :fixnums do |i|
|
||||||
|
i < 5
|
||||||
|
end
|
||||||
|
@ability.can :read, :fixnums do |i|
|
||||||
|
i > 10
|
||||||
|
end
|
||||||
|
@ability.can?(:read, 11).should be_true
|
||||||
|
@ability.can?(:read, 1).should be_true
|
||||||
|
@ability.can?(:read, 6).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls block passing arguments when no arguments are given to can" do
|
||||||
|
@ability.can do |action, subject, object|
|
||||||
|
action.should == :read
|
||||||
|
subject.should == :ranges
|
||||||
|
object.should == (2..4)
|
||||||
|
@block_called = true
|
||||||
|
end
|
||||||
|
@ability.can?(:read, 2..4)
|
||||||
|
@block_called.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
||||||
|
lambda {
|
||||||
|
@ability.can :read, :ranges, :published => true do
|
||||||
|
false
|
||||||
|
end
|
||||||
|
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read ranges ability. Use either one.")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Cannot
|
||||||
|
|
||||||
|
it "offers cannot? method which inverts can?" do
|
||||||
|
@ability.cannot?(:wax, :cars).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "supports 'cannot' method to define what user cannot do" do
|
||||||
|
@ability.can :read, :all
|
||||||
|
@ability.cannot :read, :ranges
|
||||||
|
@ability.can?(:read, :books).should be_true
|
||||||
|
@ability.can?(:read, 1..3).should be_false
|
||||||
|
@ability.can?(:read, :ranges).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "passes to previous rule if cannot check returns false" do
|
||||||
|
@ability.can :read, :all
|
||||||
|
@ability.cannot :read, :ranges, :begin => 3
|
||||||
|
@ability.cannot :read, :ranges do |range|
|
||||||
|
range.begin == 5
|
||||||
|
end
|
||||||
|
@ability.can?(:read, :books).should be_true
|
||||||
|
@ability.can?(:read, 2..4).should be_true
|
||||||
|
@ability.can?(:read, 3..7).should be_false
|
||||||
|
@ability.can?(:read, 5..9).should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Hash Association
|
||||||
|
|
||||||
|
it "checks permission through association when hash is passed as subject" do
|
||||||
|
@ability.can :read, :books, :range => {:begin => 3}
|
||||||
|
@ability.can?(:read, (1..4) => :books).should be_false
|
||||||
|
@ability.can?(:read, (3..5) => :books).should be_true
|
||||||
|
@ability.can?(:read, 123 => :books).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "checks ability on hash subclass" do
|
||||||
class Container < Hash; end
|
class Container < Hash; end
|
||||||
@ability.can :read, Container
|
@ability.can :read, :containers
|
||||||
@ability.can?(:read, Container.new).should be_true
|
@ability.can?(:read, Container.new).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have initial attributes based on hash conditions of 'new' action" do
|
|
||||||
@ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
|
# Initial Attributes
|
||||||
@ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
|
|
||||||
@ability.can :new, Range, :baz => "baz", :range => 1..3
|
it "has initial attributes based on hash conditions for a given action" do
|
||||||
@ability.cannot :new, Range, :ignore => "me"
|
@ability.can :access, :ranges, :foo => "foo", :hash => {:skip => "hashes"}
|
||||||
@ability.attributes_for(:new, Range).should == {:foo => "foo", :bar => 123, :baz => "baz"}
|
@ability.can :create, :ranges, :bar => 123, :array => %w[skip arrays]
|
||||||
|
@ability.can :new, :ranges, :baz => "baz", :range => 1..3
|
||||||
|
@ability.cannot :new, :ranges, :ignore => "me"
|
||||||
|
@ability.attributes_for(:new, :ranges).should == {:foo => "foo", :bar => 123, :baz => "baz"}
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise access denied exception if ability us unauthorized to perform a certain action" do
|
|
||||||
|
# Unauthorized Exception
|
||||||
|
|
||||||
|
it "raises CanCan::AccessDenied when calling authorize! on unauthorized action" do
|
||||||
begin
|
begin
|
||||||
@ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
|
@ability.authorize! :read, :books, :message => "Access denied!"
|
||||||
rescue CanCan::AccessDenied => e
|
rescue CanCan::AccessDenied => e
|
||||||
e.message.should == "Access denied!"
|
e.message.should == "Access denied!"
|
||||||
e.action.should == :read
|
e.action.should == :read
|
||||||
e.subject.should == :foo
|
e.subject.should == :books
|
||||||
else
|
else
|
||||||
fail "Expected CanCan::AccessDenied exception to be raised"
|
fail "Expected CanCan::AccessDenied exception to be raised"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not raise access denied exception if ability is authorized to perform an action" do
|
|
||||||
@ability.can :read, :foo
|
|
||||||
lambda { @ability.authorize!(:read, :foo) }.should_not raise_error
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should know when block is used in conditions" do
|
|
||||||
@ability.can :read, :foo
|
|
||||||
@ability.should_not have_block(:read, :foo)
|
|
||||||
@ability.can :read, :foo do |foo|
|
|
||||||
false
|
|
||||||
end
|
|
||||||
@ability.should have_block(:read, :foo)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should know when raw sql is used in conditions" do
|
|
||||||
@ability.can :read, :foo
|
|
||||||
@ability.should_not have_raw_sql(:read, :foo)
|
|
||||||
@ability.can :read, :foo, 'false'
|
|
||||||
@ability.should have_raw_sql(:read, :foo)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should raise access denied exception with default message if not specified" do
|
it "should raise access denied exception with default message if not specified" do
|
||||||
begin
|
begin
|
||||||
@ability.authorize! :read, :foo
|
@ability.authorize! :read, :books
|
||||||
rescue CanCan::AccessDenied => e
|
rescue CanCan::AccessDenied => e
|
||||||
e.default_message = "Access denied!"
|
e.default_message = "Access denied!"
|
||||||
e.message.should == "Access denied!"
|
e.message.should == "Access denied!"
|
||||||
|
@ -349,7 +263,32 @@ describe CanCan::Ability do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should determine model adapter class by asking AbstractAdapter" do
|
it "does not raise access denied exception if ability is authorized to perform an action" do
|
||||||
|
@ability.can :read, :books
|
||||||
|
lambda { @ability.authorize!(:read, :books) }.should_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Determining Conditions
|
||||||
|
|
||||||
|
it "knows when a block is used for conditions" do
|
||||||
|
@ability.can :read, :books
|
||||||
|
@ability.should_not have_block(:read, :books)
|
||||||
|
@ability.can :read, :books do |foo|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
@ability.should have_block(:read, :books)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "knows when raw sql is used for conditions" do
|
||||||
|
@ability.can :read, :books
|
||||||
|
@ability.should_not have_raw_sql(:read, :books)
|
||||||
|
@ability.can :read, :books, 'false'
|
||||||
|
@ability.should have_raw_sql(:read, :books)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "determines model adapter class by asking AbstractAdapter" do
|
||||||
|
pending
|
||||||
model_class = Object.new
|
model_class = Object.new
|
||||||
adapter_class = Object.new
|
adapter_class = Object.new
|
||||||
stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
|
stub(CanCan::ModelAdapters::AbstractAdapter).adapter_class(model_class) { adapter_class }
|
||||||
|
@ -357,54 +296,50 @@ describe CanCan::Ability do
|
||||||
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
@ability.model_adapter(model_class, :read).should == :adapter_instance
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise an error when attempting to use a block with a hash condition since it's not likely what they want" do
|
|
||||||
lambda {
|
# Unauthorized I18n Message
|
||||||
@ability.can :read, Array, :published => true do
|
|
||||||
false
|
|
||||||
end
|
|
||||||
}.should raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "unauthorized message" do
|
describe "unauthorized message" do
|
||||||
after(:each) do
|
after(:each) do
|
||||||
I18n.backend = nil
|
I18n.backend = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should use action/subject in i18n" do
|
it "uses action/subject in i18n" do
|
||||||
I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
|
I18n.backend.store_translations :en, :unauthorized => {:update => {:ranges => "update ranges"}}
|
||||||
@ability.unauthorized_message(:update, Array).should == "foo"
|
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||||
@ability.unauthorized_message(:update, [1, 2, 3]).should == "foo"
|
@ability.unauthorized_message(:update, 2..4).should == "update ranges"
|
||||||
@ability.unauthorized_message(:update, :missing).should be_nil
|
@ability.unauthorized_message(:update, :missing).should be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should use symbol as subject directly" do
|
it "uses symbol as subject directly" do
|
||||||
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
|
I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
|
||||||
@ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
|
@ability.unauthorized_message(:has, :cheezburger).should == "Nom nom nom. I eated it."
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should fall back to 'manage' and 'all'" do
|
it "falls back to 'access' and 'all'" do
|
||||||
I18n.backend.store_translations :en, :unauthorized => {
|
I18n.backend.store_translations :en, :unauthorized => {
|
||||||
:manage => {:all => "manage all", :array => "manage array"},
|
:access => {:all => "access all", :ranges => "access ranges"},
|
||||||
:update => {:all => "update all", :array => "update array"}
|
:update => {:all => "update all", :ranges => "update ranges"}
|
||||||
}
|
}
|
||||||
@ability.unauthorized_message(:update, Array).should == "update array"
|
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||||
@ability.unauthorized_message(:update, Hash).should == "update all"
|
@ability.unauthorized_message(:update, :hashes).should == "update all"
|
||||||
@ability.unauthorized_message(:foo, Array).should == "manage array"
|
@ability.unauthorized_message(:create, :ranges).should == "access ranges"
|
||||||
@ability.unauthorized_message(:foo, Hash).should == "manage all"
|
@ability.unauthorized_message(:create, :hashes).should == "access all"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should follow aliased actions" do
|
it "follows aliases" do
|
||||||
I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
|
I18n.backend.store_translations :en, :unauthorized => {:modify => {:ranges => "modify ranges"}}
|
||||||
@ability.alias_action :update, :to => :modify
|
@ability.alias_action :update, :to => :modify
|
||||||
@ability.unauthorized_message(:update, Array).should == "modify array"
|
@ability.alias_subject :areas, :to => :ranges
|
||||||
@ability.unauthorized_message(:edit, Array).should == "modify array"
|
@ability.unauthorized_message(:update, :areas).should == "modify ranges"
|
||||||
|
@ability.unauthorized_message(:edit, :ranges).should == "modify ranges"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have variables for action and subject" do
|
it "has variables for action and subject" do
|
||||||
I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
I18n.backend.store_translations :en, :unauthorized => {:access => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
|
||||||
@ability.unauthorized_message(:update, Array).should == "update array"
|
@ability.unauthorized_message(:update, :ranges).should == "update ranges"
|
||||||
@ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
@ability.unauthorized_message(:edit, 1..3).should == "edit ranges"
|
||||||
@ability.unauthorized_message(:edit, 1..3).should == "edit range"
|
# @ability.unauthorized_message(:update, ArgumentError).should == "update argument error"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user