Skip to content

Commit

Permalink
New operator: intersects_with for comparing collections with a set of…
Browse files Browse the repository at this point in the history
… values
  • Loading branch information
stffn committed Apr 1, 2009
1 parent 938bc7b commit 29d81ec
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
@@ -1,3 +1,5 @@
* New operator: intersects_with, comparing two Enumerables in if_attribute

* Improved if_permitted_to syntax: if the attribute is left out, permissions are checked on for the current object * Improved if_permitted_to syntax: if the attribute is left out, permissions are checked on for the current object


* Added #has_role_with_hierarchy? method to retrieve explicit and calculated roles [jeremyf] * Added #has_role_with_hierarchy? method to retrieve explicit and calculated roles [jeremyf]
Expand Down
40 changes: 36 additions & 4 deletions lib/declarative_authorization/authorization.rb
Expand Up @@ -385,13 +385,45 @@ def validate? (attr_validator, object = nil, hash = nil)
when :is_not when :is_not
attr_value != evaluated attr_value != evaluated
when :contains when :contains
attr_value.include?(evaluated) begin
attr_value.include?(evaluated)
rescue NoMethodError => e
raise AuthorizationUsageError, "Operator contains requires a " +
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
"contains #{evaluated.inspect}: #{e}"
end
when :does_not_contain when :does_not_contain
!attr_value.include?(evaluated) begin
!attr_value.include?(evaluated)
rescue NoMethodError => e
raise AuthorizationUsageError, "Operator does_not_contain requires a " +
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
"does_not_contain #{evaluated.inspect}: #{e}"
end
when :intersects_with
begin
!(evaluated.to_set & attr_value.to_set).empty?
rescue NoMethodError => e
raise AuthorizationUsageError, "Operator intersects_with requires " +
"subclasses of Enumerable, got: #{attr_value.inspect} " +
"intersects_with #{evaluated.inspect}: #{e}"
end
when :is_in when :is_in
evaluated.include?(attr_value) begin
evaluated.include?(attr_value)
rescue NoMethodError => e
raise AuthorizationUsageError, "Operator is_in requires a " +
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
"is_in #{evaluated.inspect}: #{e}"
end
when :is_not_in when :is_not_in
!evaluated.include?(attr_value) begin
!evaluated.include?(attr_value)
rescue NoMethodError => e
raise AuthorizationUsageError, "Operator is_not_in requires a " +
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
"is_not_in #{evaluated.inspect}: #{e}"
end
else else
raise AuthorizationError, "Unknown operator #{value[0]}" raise AuthorizationError, "Unknown operator #{value[0]}"
end end
Expand Down
3 changes: 2 additions & 1 deletion lib/declarative_authorization/obligation_scope.rb
Expand Up @@ -211,8 +211,9 @@ def rebuild_condition_options!
attribute_operator = case operator attribute_operator = case operator
when :contains, :is then "= :#{bindvar}" when :contains, :is then "= :#{bindvar}"
when :does_not_contain, :is_not then "<> :#{bindvar}" when :does_not_contain, :is_not then "<> :#{bindvar}"
when :is_in then "IN (:#{bindvar})" when :is_in, :intersects_with then "IN (:#{bindvar})"
when :is_not_in then "NOT IN (:#{bindvar})" when :is_not_in then "NOT IN (:#{bindvar})"
else raise AuthorizationUsageError, "Unknown operator: #{operator}"
end end
obligation_conds << "#{connection.quote_table_name(attribute_table_alias)}.#{connection.quote_table_name(attribute_name)} #{attribute_operator}" obligation_conds << "#{connection.quote_table_name(attribute_table_alias)}.#{connection.quote_table_name(attribute_name)} #{attribute_operator}"
binds[bindvar] = attribute_value(value) binds[bindvar] = attribute_value(value)
Expand Down
12 changes: 11 additions & 1 deletion lib/declarative_authorization/reader.rb
Expand Up @@ -24,6 +24,7 @@ module Authorization
# Methods to be used in if_attribute statements # Methods to be used in if_attribute statements
# * AuthorizationRulesReader#contains, # * AuthorizationRulesReader#contains,
# * AuthorizationRulesReader#does_not_contain, # * AuthorizationRulesReader#does_not_contain,
# * AuthorizationRulesReader#intersects_with,
# * AuthorizationRulesReader#is, # * AuthorizationRulesReader#is,
# * AuthorizationRulesReader#is_not, # * AuthorizationRulesReader#is_not,
# * AuthorizationRulesReader#is_in, # * AuthorizationRulesReader#is_in,
Expand Down Expand Up @@ -379,10 +380,19 @@ def contains (&block)
[:contains, block] [:contains, block]
end end


# The negation of contains. # The negation of contains. Currently, query rewriting is disabled
# for does_not_contain.
def does_not_contain (&block) def does_not_contain (&block)
[:does_not_contain, block] [:does_not_contain, block]
end end

# In an if_attribute statement, intersects_with requires that at least
# one of the values has to be part of the collection specified by the
# if_attribute attribute. The value block needs to evaluate to an
# Enumerable. For information on the block argument, see if_attribute.
def intersects_with (&block)
[:intersects_with, block]
end


# In an if_attribute statement, is_in says that the value has to # In an if_attribute statement, is_in says that the value has to
# contain the attribute value. # contain the attribute value.
Expand Down
36 changes: 36 additions & 0 deletions test/authorization_test.rb
Expand Up @@ -390,6 +390,42 @@ def test_attribute_not_in_array
:user => MockUser.new(:test_role), :user => MockUser.new(:test_role),
:object => MockDataObject.new(:test_attr => 4)) :object => MockDataObject.new(:test_attr => 4))
end end

def test_attribute_intersects_with
reader = Authorization::Reader::DSLReader.new
reader.parse %{
authorization do
role :test_role do
has_permission_on :permissions, :to => :test do
if_attribute :test_attrs => intersects_with { [1,2] }
end
end
role :test_role_2 do
has_permission_on :permissions, :to => :test do
if_attribute :test_attrs => intersects_with { 1 }
end
end
end
}

engine = Authorization::Engine.new(reader)
assert_raise Authorization::AuthorizationUsageError do
engine.permit?(:test, :context => :permissions,
:user => MockUser.new(:test_role),
:object => MockDataObject.new(:test_attrs => 1 ))
end
assert_raise Authorization::AuthorizationUsageError do
engine.permit?(:test, :context => :permissions,
:user => MockUser.new(:test_role_2),
:object => MockDataObject.new(:test_attrs => [1, 2] ))
end
assert engine.permit?(:test, :context => :permissions,
:user => MockUser.new(:test_role),
:object => MockDataObject.new(:test_attrs => [1,3] ))
assert !engine.permit?(:test, :context => :permissions,
:user => MockUser.new(:test_role),
:object => MockDataObject.new(:test_attrs => [3,4] ))
end


def test_attribute_deep def test_attribute_deep
reader = Authorization::Reader::DSLReader.new reader = Authorization::Reader::DSLReader.new
Expand Down
32 changes: 32 additions & 0 deletions test/model_test.rb
Expand Up @@ -586,6 +586,38 @@ def test_named_scope_with_contains_through_primary_key
TestAttr.delete_all TestAttr.delete_all
end end
end end

def test_named_scope_with_intersects_with
reader = Authorization::Reader::DSLReader.new
reader.parse %{
authorization do
role :test_role do
has_permission_on :test_models, :to => :read do
if_attribute :test_attrs => intersects_with { user.test_attrs }
end
end
end
}
Authorization::Engine.instance(reader)

test_model_1 = TestModel.create!
test_model_2 = TestModel.create!
test_model_1.test_attrs.create!
test_model_1.test_attrs.create!
test_model_1.test_attrs.create!
test_model_2.test_attrs.create!

user = MockUser.new(:test_role,
:test_attrs => [test_model_1.test_attrs.first, TestAttr.create!])
assert_equal 1, TestModel.with_permissions_to(:read, :user => user).length

user = MockUser.new(:test_role,
:test_attrs => [TestAttr.create!])
assert_equal 0, TestModel.with_permissions_to(:read, :user => user).length

TestModel.delete_all
TestAttr.delete_all
end


def test_named_scope_with_is_and_has_one def test_named_scope_with_is_and_has_one
reader = Authorization::Reader::DSLReader.new reader = Authorization::Reader::DSLReader.new
Expand Down

0 comments on commit 29d81ec

Please sign in to comment.