diff --git a/.rbenv-version b/.rbenv-version deleted file mode 100644 index 651f0b9a..00000000 --- a/.rbenv-version +++ /dev/null @@ -1 +0,0 @@ -1.8.7-p357 diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index 2a3dc808..83ddde44 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -16,6 +16,7 @@ module CanCan # end # module Ability + # Check if the user has permission to perform a given action on an object. # # can? :destroy, @project @@ -122,7 +123,7 @@ def cannot?(*args) # end # def can(action = nil, subject = nil, conditions = nil, &block) - rules << Rule.new(true, action, subject, conditions, block) + rules << Rule.new(true, action, subject, conditions, @strict_class_access, block) end # Defines an ability which cannot be done. Accepts the same arguments as "can". @@ -138,7 +139,32 @@ def can(action = nil, subject = nil, conditions = nil, &block) # end # def cannot(action = nil, subject = nil, conditions = nil, &block) - rules << Rule.new(false, action, subject, conditions, block) + rules << Rule.new(false, action, subject, conditions, @strict_class_access, block) + end + + # Set strict class access. + # + # If you define a block for testing objects of a certain class, the default behaviour + # is that "can?" returns true for all methods you test on that class: + # + # ability.can :destroy, :all { |object| false } + # + # ability.can? :destroy, {} => false # block called, and returns false + # ability.can? :destroy, Hash # => true # careful - block not called, true returned by default + # + # If you enable strick class access, you need to specifically permit class methods: + # + # ability.strict_class_access = true # enable strict class access + # ability.can :destroy, :all { |object| false } + # + # ability.can? :destroy, {} => false # block is called, and returns false + # ability.can? :destroy, Hash # => false # block not called, false returned since strict class access is on + # + # ability.can :destroy, Hash # specifically allow destroying Hashes + # ability.can? :destroy, Hash # => true # true returned, since we specifically allowed it + # + def strict_class_access + @strict_class_access = true end # Alias one or more actions into another one. diff --git a/lib/cancan/rule.rb b/lib/cancan/rule.rb index c0415de8..ac3b1d36 100644 --- a/lib/cancan/rule.rb +++ b/lib/cancan/rule.rb @@ -10,13 +10,14 @@ class Rule # :nodoc: # value. True for "can" and false for "cannot". The next two arguments are the action # and subject respectively (such as :read, @project). The third argument is a hash # of conditions and the last one is the block passed to the "can" call. - def initialize(base_behavior, action, subject, conditions, block) + def initialize(base_behavior, action, subject, conditions, strict_class_access=false, block=nil) raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil? @match_all = action.nil? && subject.nil? @base_behavior = base_behavior @actions = [action].flatten @subjects = [subject].flatten @conditions = conditions || {} + @strict_class_access = strict_class_access @block = block end @@ -30,8 +31,8 @@ def relevant?(action, subject) def matches_conditions?(action, subject, extra_args) if @match_all call_block_with_all(action, subject, extra_args) - elsif @block && !subject_class?(subject) - @block.call(subject, *extra_args) + elsif @block + subject_class?(subject) ? @strict_class_access!=true : @block.call(subject, *extra_args) elsif @conditions.kind_of?(Hash) && subject.class == Hash nested_subject_matches_conditions?(subject) elsif @conditions.kind_of?(Hash) && !subject_class?(subject) diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 38595cb7..8643515e 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -64,6 +64,29 @@ @block_called.should be_false end + it "should not allow methods by default when strict class access was enabled" do + @block_called = false + @ability.strict_class_access + @ability.can :destroy, :all do |object| + @block_called = true + false + end + @ability.can?(:destroy, Hash).should be_false + @block_called.should be_false + end + + it "should allow granted method when strict class access was enabled" do + @block_called = false + @ability.strict_class_access + @ability.can :destroy, :all do |object| + @block_called = true + false + end + @ability.can :destroy, Hash + @ability.can?(:destroy, 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"