Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

passing a hash to can? will check permissions on association, this is…

… done automatically in authorize_resource - closes #121
  • Loading branch information...
commit c88cb8f4593148f99e15627d23fbf67116dd8be2 1 parent 737f8ac
@ryanb authored
View
16 lib/cancan/can_definition.rb
@@ -21,6 +21,7 @@ def initialize(base_behavior, action, subject, conditions, block)
# 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 @@ 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 != 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 @@ def associations_hash(conditions = @conditions)
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 @@ def matches_conditions_hash?(subject, conditions = @conditions)
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
12 lib/cancan/controller_resource.rb
@@ -32,7 +32,7 @@ def load_resource
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 @@ def resource_class
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 @@ def resource_instance
# 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
64 spec/cancan/ability_spec.rb
@@ -57,11 +57,11 @@
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 @@
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 @@
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
9 spec/cancan/controller_resource_spec.rb
@@ -161,6 +161,15 @@
@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
1  spec/spec.opts
@@ -1 +1,2 @@
--color
+--backtrace
Please sign in to comment.
Something went wrong with that request. Please try again.