passing a hash to can? will check permissions on association, this is done automatically in authorize_resource - closes #121

This commit is contained in:
Ryan Bates 2010-09-03 11:01:55 -07:00
parent 737f8acff5
commit c88cb8f459
5 changed files with 67 additions and 35 deletions

View File

@ -21,6 +21,7 @@ module CanCan
# Matches both the subject and action, not necessarily the conditions
def relevant?(action, subject)
subject = subject.values.first if subject.kind_of? Hash
@match_all || (matches_action?(action) && matches_subject?(subject))
end
@ -28,9 +29,11 @@ module CanCan
def matches_conditions?(action, subject, extra_args)
if @match_all
call_block_with_all(action, subject, extra_args)
elsif @block && subject.class != Class
elsif @block && !subject_class?(subject)
@block.call(subject, *extra_args)
elsif @conditions.kind_of?(Hash) && subject.class != Class
elsif @conditions.kind_of?(Hash) && subject.kind_of?(Hash)
nested_subject_matches_conditions?(subject)
elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
matches_conditions_hash?(subject)
else
@base_behavior
@ -66,6 +69,10 @@ module CanCan
private
def subject_class?(subject)
(subject.kind_of?(Hash) ? subject.values.first : subject).class == Class
end
def matches_action?(action)
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
end
@ -95,6 +102,11 @@ module CanCan
end
end
def nested_subject_matches_conditions?(subject_hash)
parent, child = subject_hash.shift
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym])
end
def call_block_with_all(action, subject, extra_args)
if subject.class == Class
@block.call(action, subject, nil, *extra_args)

View File

@ -32,7 +32,7 @@ module CanCan
end
def authorize_resource
@controller.authorize!(authorization_action, resource_instance || resource_class)
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
end
def parent?
@ -86,6 +86,10 @@ module CanCan
end
end
def resource_class_with_parent
parent_resource ? {parent_resource => resource_class} : resource_class
end
def resource_instance
@controller.instance_variable_get("@#{instance_name}")
end
@ -94,15 +98,15 @@ module CanCan
# If the :through option is passed it will go through an association on that instance.
# If the :singleton option is passed it won't use the association because it needs to be handled later.
def resource_base
if through_resource
@options[:singleton] ? through_resource : through_resource.send(name.to_s.pluralize)
if parent_resource
@options[:singleton] ? parent_resource : parent_resource.send(name.to_s.pluralize)
else
resource_class
end
end
# The object to load this resource through.
def through_resource
def parent_resource
@options[:through] && [@options[:through]].flatten.map { |i| @controller.instance_variable_get("@#{i}") }.compact.first
end

View File

@ -57,11 +57,11 @@ describe CanCan::Ability do
end
it "should pass only object for global manage actions" do
@ability.can :manage, Array do |object|
object.should == [1, 2]
@ability.can :manage, String do |object|
object.should == "foo"
@block_called = true
end
@ability.can?(:stuff, [1, 2]).should
@ability.can?(:stuff, "foo").should
@block_called.should be_true
end
@ -130,9 +130,9 @@ describe CanCan::Ability do
end
it "should be able to specify multiple classes and match any" do
@ability.can :update, [String, Array]
@ability.can :update, [String, Range]
@ability.can?(:update, "foo").should be_true
@ability.can?(:update, []).should be_true
@ability.can?(:update, 1..3).should be_true
@ability.can?(:update, 123).should be_false
end
@ -210,44 +210,50 @@ describe CanCan::Ability do
end
it "should use conditions as third parameter and determine abilities from it" do
@ability.can :read, Array, :first => 1, :last => 3
@ability.can?(:read, [1, 2, 3]).should be_true
@ability.can?(:read, [1, 2, 3, 4]).should be_false
@ability.can?(:read, Array).should be_true
@ability.can :read, Range, :begin => 1, :end => 3
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 1..4).should be_false
@ability.can?(:read, Range).should be_true
end
it "should allow an array of options in conditions hash" do
@ability.can :read, Array, :first => [1, 3, 5]
@ability.can?(:read, [1, 2, 3]).should be_true
@ability.can?(:read, [2, 3]).should be_false
@ability.can?(:read, [3, 4]).should be_true
@ability.can :read, Range, :begin => [1, 3, 5]
@ability.can?(:read, 1..3).should be_true
@ability.can?(:read, 2..4).should be_false
@ability.can?(:read, 3..5).should be_true
end
it "should allow a range of options in conditions hash" do
@ability.can :read, Array, :first => 1..3
@ability.can?(:read, [1, 2, 3]).should be_true
@ability.can?(:read, [3, 4]).should be_true
@ability.can?(:read, [4, 5]).should be_false
@ability.can :read, Range, :begin => 1..3
@ability.can?(:read, 1..10).should be_true
@ability.can?(:read, 3..30).should be_true
@ability.can?(:read, 4..40).should be_false
end
it "should allow nested hashes in conditions hash" do
@ability.can :read, Array, :first => { :length => 5 }
@ability.can?(:read, ["foo", "bar"]).should be_false
@ability.can?(:read, ["test1", "foo"]).should be_true
@ability.can :read, Range, :begin => { :to_i => 5 }
@ability.can?(:read, 5..7).should be_true
@ability.can?(:read, 6..8).should be_false
end
it "should allow nested hash of arrays and match any element" do
@ability.can :read, Array, :first => { :to_i => 3 }
@ability.can?(:read, [[1, 2, 3]]).should be_true
@ability.can?(:read, [[4, 5, 6]]).should be_false
it "should match 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, 1..5).should be_true
@ability.can?(:read, 4..6).should be_false
end
it "should not stop at cannot definition when comparing class" do
@ability.can :read, Array
@ability.cannot :read, Array, :first => 1
@ability.can?(:read, [2, 3, 5]).should be_true
@ability.can?(:read, [1, 3, 5]).should be_false
@ability.can?(:read, Array).should be_true
@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 "passing a hash of subjects should check permissions through association" do
@ability.can :read, Range, :string => {:length => 3}
@ability.can?(:read, "foo" => Range).should be_true
@ability.can?(:read, "foobar" => Range).should be_false
end
describe "unauthorized message" do

View File

@ -161,6 +161,15 @@ describe CanCan::ControllerResource do
@controller.instance_variable_get(:@ability).should == :some_ability
end
it "should authorize nested resource through parent association on index action" do
@params.merge!(:action => "index")
person = Object.new
@controller.instance_variable_set(:@person, person)
stub(@controller).authorize!(:index, person => Ability) { raise CanCan::AccessDenied }
resource = CanCan::ControllerResource.new(@controller, :through => :person)
lambda { resource.authorize_resource }.should raise_error(CanCan::AccessDenied)
end
it "should load through first matching if multiple are given" do
@params.merge!(:action => "show", :id => 123)
person = Object.new

View File

@ -1 +1,2 @@
--color
--backtrace