stffn-declarative_authorization 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,6 +76,10 @@ module Authorization
76
76
  #
77
77
  # If an operation is not permitted, a Authorization::AuthorizationError
78
78
  # is raised.
79
+ #
80
+ # To activate model security on all models, call using_access_control
81
+ # on ActiveRecord::Base
82
+ # ActiveRecord::Base.using_access_control
79
83
  #
80
84
  # Available options
81
85
  # [:+context+] Specify context different from the models table name.
@@ -86,28 +90,19 @@ module Authorization
86
90
  :context => nil,
87
91
  :include_read => false
88
92
  }.merge(options)
89
- context = (options[:context] || self.table_name).to_sym
90
93
 
91
94
  class_eval do
92
- before_create do |object|
93
- Authorization::Engine.instance.permit!(:create, :object => object,
94
- :context => context)
95
- end
96
-
97
- before_update do |object|
98
- Authorization::Engine.instance.permit!(:update, :object => object,
99
- :context => context)
95
+ [:create, :update, [:destroy, :delete]].each do |action, privilege|
96
+ send(:"before_#{action}") do |object|
97
+ Authorization::Engine.instance.permit!(privilege || action,
98
+ :object => object, :context => options[:context])
99
+ end
100
100
  end
101
-
102
- before_destroy do |object|
103
- Authorization::Engine.instance.permit!(:delete, :object => object,
104
- :context => context)
105
- end
106
-
107
- # only called if after_find is implemented
101
+
102
+ # after_find is only called if after_find is implemented
108
103
  after_find do |object|
109
104
  Authorization::Engine.instance.permit!(:read, :object => object,
110
- :context => context)
105
+ :context => options[:context])
111
106
  end
112
107
 
113
108
  if options[:include_read]
@@ -48,7 +48,7 @@ module Authorization
48
48
  @current_obligation = obligation
49
49
  obligation_conditions[@current_obligation] ||= {}
50
50
  follow_path( obligation )
51
-
51
+
52
52
  rebuild_condition_options!
53
53
  rebuild_join_options!
54
54
  end
@@ -133,7 +133,7 @@ module Authorization
133
133
  parent.reflect_on_association( path.last )
134
134
  end
135
135
  raise "invalid path #{path.inspect}" if reflection.nil?
136
-
136
+
137
137
  reflections[path] = reflection
138
138
  map_table_alias_for( path ) # Claim a table alias for the path.
139
139
 
@@ -208,17 +208,15 @@ module Authorization
208
208
  end
209
209
  bindvar = "#{attribute_table_alias}__#{attribute_name}_#{obligation_index}".to_sym
210
210
 
211
- attribute_value = value.respond_to?( :descends_from_active_record? ) && value.descends_from_active_record? && value.id ||
212
- value.is_a?( Array ) && value[0].respond_to?( :descends_from_active_record? ) && value[0].descends_from_active_record? && value.map( &:id ) ||
213
- value
214
211
  attribute_operator = case operator
215
212
  when :contains, :is then "= :#{bindvar}"
216
213
  when :does_not_contain, :is_not then "<> :#{bindvar}"
217
- when :is_in then "IN (:#{bindvar})"
214
+ when :is_in, :intersects_with then "IN (:#{bindvar})"
218
215
  when :is_not_in then "NOT IN (:#{bindvar})"
216
+ else raise AuthorizationUsageError, "Unknown operator: #{operator}"
219
217
  end
220
218
  obligation_conds << "#{connection.quote_table_name(attribute_table_alias)}.#{connection.quote_table_name(attribute_name)} #{attribute_operator}"
221
- binds[bindvar] = attribute_value
219
+ binds[bindvar] = attribute_value(value)
222
220
  end
223
221
  end
224
222
  obligation_conds << "1=1" if obligation_conds.empty?
@@ -227,36 +225,40 @@ module Authorization
227
225
  (delete_paths - used_paths).each {|path| reflections.delete(path)}
228
226
  @proxy_options[:conditions] = [ conds.join( " OR " ), binds ]
229
227
  end
228
+
229
+ def attribute_value (value)
230
+ value.respond_to?( :descends_from_active_record? ) && value.descends_from_active_record? && value.id ||
231
+ value.is_a?( Array ) && value[0].respond_to?( :descends_from_active_record? ) && value[0].descends_from_active_record? && value.map( &:id ) ||
232
+ value
233
+ end
230
234
 
231
235
  # Parses all of the defined obligation joins and defines the scope's :joins or :includes option.
232
236
  # TODO: Support non-linear association paths. Right now, we just break down the longest path parsed.
233
237
  def rebuild_join_options!
234
- joins = @proxy_options[:joins] || []
238
+ joins = (@proxy_options[:joins] || []) + (@proxy_options[:includes] || [])
235
239
 
236
- reflections.keys.reverse.each do |path|
240
+ reflections.keys.each do |path|
237
241
  next if path.empty?
238
-
242
+
239
243
  existing_join = joins.find do |join|
240
- join.is_a?(Symbol) ? (join == path.first) : join.key?(path.first)
244
+ existing_path = join_to_path(join)
245
+ min_length = [existing_path.length, path.length].min
246
+ existing_path.first(min_length) == path.first(min_length)
241
247
  end
242
- path_join = path_to_join(path)
243
248
 
244
- case [existing_join.class, path_join.class]
245
- when [Symbol, Hash]
246
- joins.delete(existing_join)
247
- joins << path_join
248
- when [Hash, Hash]
249
- joins.delete(existing_join)
250
- joins << path_join.deep_merge(existing_join)
251
- when [NilClass, Hash], [NilClass, Symbol]
252
- joins << path_join
249
+ if existing_join
250
+ if join_to_path(existing_join).length < path.length
251
+ joins[joins.index(existing_join)] = path_to_join(path)
252
+ end
253
+ else
254
+ joins << path_to_join(path)
253
255
  end
254
256
  end
255
257
 
256
258
  case obligation_conditions.length
257
- when 0:
259
+ when 0 then
258
260
  # No obligation conditions means we don't have to mess with joins or includes at all.
259
- when 1:
261
+ when 1 then
260
262
  @proxy_options[:joins] = joins
261
263
  @proxy_options.delete( :include )
262
264
  else
@@ -277,5 +279,14 @@ module Authorization
277
279
  hash
278
280
  end
279
281
  end
282
+
283
+ def join_to_path (join)
284
+ case join
285
+ when Symbol
286
+ [join]
287
+ when Hash
288
+ [join.keys.first] + join_to_path(join[join.keys.first])
289
+ end
290
+ end
280
291
  end
281
292
  end
@@ -24,6 +24,7 @@ module Authorization
24
24
  # Methods to be used in if_attribute statements
25
25
  # * AuthorizationRulesReader#contains,
26
26
  # * AuthorizationRulesReader#does_not_contain,
27
+ # * AuthorizationRulesReader#intersects_with,
27
28
  # * AuthorizationRulesReader#is,
28
29
  # * AuthorizationRulesReader#is_not,
29
30
  # * AuthorizationRulesReader#is_in,
@@ -209,22 +210,27 @@ module Authorization
209
210
  # The block form allows to describe restrictions on the permissions
210
211
  # using if_attribute. Multiple has_permission_on statements are
211
212
  # OR'ed when evaluating the permissions. Also, multiple if_attribute
212
- # statements in one block are OR'ed.
213
+ # statements in one block are OR'ed if no :+join_by+ option is given
214
+ # (see below). To AND conditions, either set :+join_by+ to :and or place
215
+ # them in one if_attribute statement.
213
216
  #
214
217
  # Available options
215
218
  # [:+to+]
216
219
  # A symbol or an array of symbols representing the privileges that
217
220
  # should be granted in this statement.
221
+ # [:+join_by+]
222
+ # Join operator to logically connect the constraint statements inside
223
+ # of the has_permission_on block. May be :+and+ or :+or+. Defaults to :+or+.
218
224
  #
219
225
  def has_permission_on (context, options = {}, &block)
220
226
  raise DSLError, "has_permission_on only allowed in role blocks" if @current_role.nil?
221
- options = {:to => []}.merge(options)
227
+ options = {:to => [], :join_by => :or}.merge(options)
222
228
 
223
229
  privs = options[:to]
224
230
  privs = [privs] unless privs.is_a?(Array)
225
231
  raise DSLError, "has_permission_on either needs a block or :to option" if !block_given? and privs.empty?
226
232
 
227
- rule = AuthorizationRule.new(@current_role, privs, context)
233
+ rule = AuthorizationRule.new(@current_role, privs, context, options[:join_by])
228
234
  @auth_rules << rule
229
235
  if block_given?
230
236
  @current_rule = rule
@@ -292,11 +298,21 @@ module Authorization
292
298
  # end
293
299
  # end
294
300
  #
295
- # Multiple if_attribute statements are OR'ed.
301
+ # Multiple attributes in one :if_attribute statement are AND'ed.
302
+ # Multiple if_attribute statements are OR'ed if the join operator for the
303
+ # has_permission_on block isn't explicitly set. Thus, the following would
304
+ # require the current user either to be of the same branch AND the employee
305
+ # to be "changeable_by_coworker". OR the current user has to be the
306
+ # employee in question.
307
+ # has_permission_on :employees, :to => :manage do
308
+ # if_attribute :branch => is {user.branch}, :changeable_by_coworker => true
309
+ # if_attribute :id => is {user.id}
310
+ # end
296
311
  #
297
312
  # Arrays and fixed values may be used directly as hash values:
298
- # if_attribute :id => 1
299
- # if_attribute :id => [1,2]
313
+ # if_attribute :id => 1
314
+ # if_attribute :type => "special"
315
+ # if_attribute :id => [1,2]
300
316
  #
301
317
  def if_attribute (attr_conditions_hash)
302
318
  raise DSLError, "if_attribute only in has_permission blocks" if @current_rule.nil?
@@ -322,6 +338,18 @@ module Authorization
322
338
  # if_permitted_to associations may be nested as well:
323
339
  # if_permitted_to :read, :branch => :company
324
340
  #
341
+ # To check permissions based on the current object, the attribute has to
342
+ # be left out:
343
+ # has_permission_on :branches, :to => :manage do
344
+ # if_attribute :employees => includes { user }
345
+ # end
346
+ # has_permission_on :branches, :to => :paint_green do
347
+ # if_permitted_to :update
348
+ # end
349
+ # Normally, one would merge those rules into one. Deviding makes sense
350
+ # if additional if_attribute are used in the second rule or those rules
351
+ # are applied to different roles.
352
+ #
325
353
  # Options:
326
354
  # [:+context+]
327
355
  # If the context of the refered object may not be infered from the
@@ -329,9 +357,11 @@ module Authorization
329
357
  # if_permitted_to :read, :home_branch, :context => :branches
330
358
  # if_permitted_to :read, :branch => :main_company, :context => :companies
331
359
  #
332
- def if_permitted_to (privilege, attr_or_hash, options = {})
360
+ def if_permitted_to (privilege, attr_or_hash = nil, options = {})
333
361
  raise DSLError, "if_permitted_to only in has_permission blocks" if @current_rule.nil?
334
362
  options[:context] ||= attr_or_hash.delete(:context) if attr_or_hash.is_a?(Hash)
363
+ # only :context option in attr_or_hash:
364
+ attr_or_hash = nil if attr_or_hash.is_a?(Hash) and attr_or_hash.empty?
335
365
  @current_rule.append_attribute AttributeWithPermission.new(privilege,
336
366
  attr_or_hash, options[:context])
337
367
  end
@@ -355,10 +385,19 @@ module Authorization
355
385
  [:contains, block]
356
386
  end
357
387
 
358
- # The negation of contains.
388
+ # The negation of contains. Currently, query rewriting is disabled
389
+ # for does_not_contain.
359
390
  def does_not_contain (&block)
360
391
  [:does_not_contain, block]
361
392
  end
393
+
394
+ # In an if_attribute statement, intersects_with requires that at least
395
+ # one of the values has to be part of the collection specified by the
396
+ # if_attribute attribute. The value block needs to evaluate to an
397
+ # Enumerable. For information on the block argument, see if_attribute.
398
+ def intersects_with (&block)
399
+ [:intersects_with, block]
400
+ end
362
401
 
363
402
  # In an if_attribute statement, is_in says that the value has to
364
403
  # contain the attribute value.
@@ -381,7 +420,7 @@ module Authorization
381
420
  elsif !value.is_a?(Array)
382
421
  merge_hash[key] = [:is, lambda { value }]
383
422
  elsif value.is_a?(Array) and !value[0].is_a?(Symbol)
384
- merge_hash[key] = [:is_in, value]
423
+ merge_hash[key] = [:is_in, lambda { value }]
385
424
  end
386
425
  end
387
426
  hash.merge!(merge_hash)
@@ -0,0 +1,123 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper.rb')
2
+ require File.join(File.dirname(__FILE__), %w{.. lib declarative_authorization authorization_rules_analyzer})
3
+
4
+ class AuthorizationRulesAnalyzerTest < Test::Unit::TestCase
5
+
6
+ def test_analyzing_complex_rules
7
+ assert_nothing_raised do
8
+ engine, analyzer = engine_analyzer_for %{
9
+ authorization do
10
+ role :guest do
11
+ has_permission_on :conferences, :to => :read do
12
+ if_attribute :published => true
13
+ end
14
+ has_permission_on :talks, :to => :read do
15
+ if_permitted_to :read, :conference
16
+ end
17
+ has_permission_on :users, :to => :create
18
+ has_permission_on :authorization_rules, :to => :read
19
+ has_permission_on :authorization_usages, :to => :read
20
+ end
21
+
22
+ role :user do
23
+ includes :guest
24
+ has_permission_on :conference_attendees, :to => :create do
25
+ if_attribute :user => is {user},
26
+ :conference => { :published => true }
27
+ end
28
+ has_permission_on :conference_attendees, :to => :delete do
29
+ if_attribute :user => is {user},
30
+ :conference => { :attendees => contains {user} }
31
+ end
32
+ has_permission_on :talk_attendees, :to => :create do
33
+ if_attribute :talk => { :conference => { :attendees => contains {user} }}
34
+ end
35
+ has_permission_on :talk_attendees, :to => :delete do
36
+ if_attribute :user => is {user}
37
+ end
38
+ end
39
+
40
+ role :conference_organizer do
41
+ has_permission_on :conferences do
42
+ to :manage
43
+ # if...
44
+ end
45
+ has_permission_on [:conference_attendees, :talks, :talk_attendees], :to => :manage
46
+ end
47
+
48
+ role :admin do
49
+ has_permission_on [:conferences, :users, :talks], :to => :manage
50
+ has_permission_on :authorization_rules, :to => :read
51
+ has_permission_on :authorization_usages, :to => :read
52
+ end
53
+ end
54
+
55
+ privileges do
56
+ privilege :manage, :includes => [:create, :read, :update, :delete]
57
+ privilege :read, :includes => [:index, :show]
58
+ privilege :create, :includes => :new
59
+ privilege :update, :includes => :edit
60
+ privilege :delete, :includes => :destroy
61
+ end
62
+ }
63
+ end
64
+ end
65
+
66
+ def test_mergeable_rules_without_constraints
67
+ engine, analyzer = engine_analyzer_for %{
68
+ authorization do
69
+ role :test_role do
70
+ has_permission_on :permissions, :to => :test
71
+ has_permission_on :permissions, :to => :test2
72
+ end
73
+ end
74
+ }
75
+
76
+ report = analyzer.reports.find {|report| report.type == :mergeable_rules}
77
+ assert report
78
+ assert_equal 4, report.line
79
+ end
80
+
81
+ def test_mergeable_rules_with_in_block_to
82
+ assert_nothing_raised do
83
+ engine, analyzer = engine_analyzer_for %{
84
+ authorization do
85
+ role :test_role do
86
+ has_permission_on :permissions do
87
+ to :test
88
+ end
89
+ end
90
+ end
91
+ }
92
+ end
93
+ end
94
+
95
+ def test_no_mergeable_rules_with_constraints
96
+ engine, analyzer = engine_analyzer_for %{
97
+ authorization do
98
+ role :test_role do
99
+ has_permission_on :permissions, :to => :test do
100
+ if_attribute :some_attr => is {bla}
101
+ end
102
+ has_permission_on :permissions, :to => :test2 do
103
+ if_attribute :some_attr_2 => is {bla}
104
+ end
105
+ end
106
+ end
107
+ }
108
+
109
+ assert !analyzer.reports.find {|report| report.type == :mergeable_rules}
110
+ end
111
+
112
+ protected
113
+ def engine_analyzer_for (rules)
114
+ reader = Authorization::Reader::DSLReader.new
115
+ reader.parse rules
116
+ engine = Authorization::Engine.new(reader)
117
+
118
+ analyzer = Authorization::Analyzer.new(engine)
119
+ analyzer.analyze rules
120
+
121
+ [engine, analyzer]
122
+ end
123
+ end
@@ -82,6 +82,42 @@ class AuthorizationTest < Test::Unit::TestCase
82
82
  engine.obligations(:test, :context => :permissions,
83
83
  :user => MockUser.new(:test_role, :attr => 1))
84
84
  end
85
+
86
+ def test_obligations_with_anded_conditions
87
+ reader = Authorization::Reader::DSLReader.new
88
+ reader.parse %{
89
+ authorization do
90
+ role :test_role do
91
+ has_permission_on :permissions, :to => :test, :join_by => :and do
92
+ if_attribute :attr => is { user.attr }
93
+ if_attribute :attr_2 => is { user.attr_2 }
94
+ end
95
+ end
96
+ end
97
+ }
98
+ engine = Authorization::Engine.new(reader)
99
+ assert_equal [{:attr => [:is, 1], :attr_2 => [:is, 2]}],
100
+ engine.obligations(:test, :context => :permissions,
101
+ :user => MockUser.new(:test_role, :attr => 1, :attr_2 => 2))
102
+ end
103
+
104
+ def test_obligations_with_deep_anded_conditions
105
+ reader = Authorization::Reader::DSLReader.new
106
+ reader.parse %{
107
+ authorization do
108
+ role :test_role do
109
+ has_permission_on :permissions, :to => :test, :join_by => :and do
110
+ if_attribute :attr => { :deeper_attr => is { user.deeper_attr }}
111
+ if_attribute :attr => { :deeper_attr_2 => is { user.deeper_attr_2 }}
112
+ end
113
+ end
114
+ end
115
+ }
116
+ engine = Authorization::Engine.new(reader)
117
+ assert_equal [{:attr => { :deeper_attr => [:is, 1], :deeper_attr_2 => [:is, 2] } }],
118
+ engine.obligations(:test, :context => :permissions,
119
+ :user => MockUser.new(:test_role, :deeper_attr => 1, :deeper_attr_2 => 2))
120
+ end
85
121
 
86
122
  def test_obligations_with_conditions_and_empty
87
123
  reader = Authorization::Reader::DSLReader.new
@@ -390,6 +426,42 @@ class AuthorizationTest < Test::Unit::TestCase
390
426
  :user => MockUser.new(:test_role),
391
427
  :object => MockDataObject.new(:test_attr => 4))
392
428
  end
429
+
430
+ def test_attribute_intersects_with
431
+ reader = Authorization::Reader::DSLReader.new
432
+ reader.parse %{
433
+ authorization do
434
+ role :test_role do
435
+ has_permission_on :permissions, :to => :test do
436
+ if_attribute :test_attrs => intersects_with { [1,2] }
437
+ end
438
+ end
439
+ role :test_role_2 do
440
+ has_permission_on :permissions, :to => :test do
441
+ if_attribute :test_attrs => intersects_with { 1 }
442
+ end
443
+ end
444
+ end
445
+ }
446
+
447
+ engine = Authorization::Engine.new(reader)
448
+ assert_raise Authorization::AuthorizationUsageError do
449
+ engine.permit?(:test, :context => :permissions,
450
+ :user => MockUser.new(:test_role),
451
+ :object => MockDataObject.new(:test_attrs => 1 ))
452
+ end
453
+ assert_raise Authorization::AuthorizationUsageError do
454
+ engine.permit?(:test, :context => :permissions,
455
+ :user => MockUser.new(:test_role_2),
456
+ :object => MockDataObject.new(:test_attrs => [1, 2] ))
457
+ end
458
+ assert engine.permit?(:test, :context => :permissions,
459
+ :user => MockUser.new(:test_role),
460
+ :object => MockDataObject.new(:test_attrs => [1,3] ))
461
+ assert !engine.permit?(:test, :context => :permissions,
462
+ :user => MockUser.new(:test_role),
463
+ :object => MockDataObject.new(:test_attrs => [3,4] ))
464
+ end
393
465
 
394
466
  def test_attribute_deep
395
467
  reader = Authorization::Reader::DSLReader.new
@@ -514,6 +586,137 @@ class AuthorizationTest < Test::Unit::TestCase
514
586
  :object => MockDataObject.new(:shallow_permission =>
515
587
  MockDataObject.new(:permission => perm_data_attr_2)))
516
588
  end
589
+
590
+ def test_attribute_with_permissions_nil
591
+ reader = Authorization::Reader::DSLReader.new
592
+ reader.parse %{
593
+ authorization do
594
+ role :test_role do
595
+ has_permission_on :permissions, :to => :test do
596
+ if_attribute :test_attr => 1
597
+ end
598
+ has_permission_on :permission_children, :to => :test do
599
+ if_permitted_to :test, :permission
600
+ end
601
+ end
602
+ end
603
+ }
604
+ engine = Authorization::Engine.new(reader)
605
+
606
+ assert_nothing_raised do
607
+ engine.permit?(:test, :context => :permission_children,
608
+ :user => MockUser.new(:test_role),
609
+ :object => MockDataObject.new(:permission => nil))
610
+ end
611
+
612
+ assert !engine.permit?(:test, :context => :permission_children,
613
+ :user => MockUser.new(:test_role),
614
+ :object => MockDataObject.new(:permission => nil))
615
+ end
616
+
617
+ def test_attribute_with_permissions_on_self
618
+ reader = Authorization::Reader::DSLReader.new
619
+ reader.parse %{
620
+ authorization do
621
+ role :test_role do
622
+ has_permission_on :permissions, :to => :test do
623
+ if_attribute :test_attr => 1
624
+ end
625
+ has_permission_on :permissions, :to => :another_test do
626
+ if_permitted_to :test
627
+ end
628
+ end
629
+ end
630
+ }
631
+ engine = Authorization::Engine.new(reader)
632
+
633
+ perm_data_attr_1 = PermissionMock.new({:test_attr => 1})
634
+ perm_data_attr_2 = PermissionMock.new({:test_attr => 2})
635
+ assert engine.permit?(:another_test, :context => :permissions,
636
+ :user => MockUser.new(:test_role),
637
+ :object => perm_data_attr_1)
638
+ assert !engine.permit?(:another_test, :context => :permissions,
639
+ :user => MockUser.new(:test_role),
640
+ :object => perm_data_attr_2)
641
+ end
642
+
643
+ def test_attribute_with_permissions_on_self_with_context
644
+ reader = Authorization::Reader::DSLReader.new
645
+ reader.parse %{
646
+ authorization do
647
+ role :test_role do
648
+ has_permission_on :permissions, :to => :test do
649
+ if_attribute :test_attr => 1
650
+ end
651
+ has_permission_on :permissions, :to => :another_test do
652
+ if_permitted_to :test, :context => :permissions
653
+ end
654
+ end
655
+ end
656
+ }
657
+ engine = Authorization::Engine.new(reader)
658
+
659
+ perm_data_attr_1 = PermissionMock.new({:test_attr => 1})
660
+ perm_data_attr_2 = PermissionMock.new({:test_attr => 2})
661
+ assert engine.permit?(:another_test, :context => :permissions,
662
+ :user => MockUser.new(:test_role),
663
+ :object => perm_data_attr_1)
664
+ assert !engine.permit?(:another_test, :context => :permissions,
665
+ :user => MockUser.new(:test_role),
666
+ :object => perm_data_attr_2)
667
+ end
668
+
669
+ def test_attribute_with_permissions_and_anded_rules
670
+ reader = Authorization::Reader::DSLReader.new
671
+ reader.parse %{
672
+ authorization do
673
+ role :test_role do
674
+ has_permission_on :permissions, :to => :test do
675
+ if_attribute :test_attr => 1
676
+ end
677
+ has_permission_on :permission_children, :to => :test, :join_by => :and do
678
+ if_permitted_to :test, :permission
679
+ if_attribute :test_attr => 1
680
+ end
681
+ end
682
+ end
683
+ }
684
+ engine = Authorization::Engine.new(reader)
685
+
686
+ perm_data_attr_1 = PermissionMock.new({:test_attr => 1})
687
+ perm_data_attr_2 = PermissionMock.new({:test_attr => 2})
688
+ assert engine.permit?(:test, :context => :permission_children,
689
+ :user => MockUser.new(:test_role),
690
+ :object => MockDataObject.new(:permission => perm_data_attr_1, :test_attr => 1))
691
+ assert !engine.permit?(:test, :context => :permission_children,
692
+ :user => MockUser.new(:test_role),
693
+ :object => MockDataObject.new(:permission => perm_data_attr_2, :test_attr => 1))
694
+ assert !engine.permit?(:test, :context => :permission_children,
695
+ :user => MockUser.new(:test_role),
696
+ :object => MockDataObject.new(:permission => perm_data_attr_1, :test_attr => 2))
697
+ end
698
+
699
+ def test_attribute_with_anded_rules
700
+ reader = Authorization::Reader::DSLReader.new
701
+ reader.parse %{
702
+ authorization do
703
+ role :test_role do
704
+ has_permission_on :permissions, :to => :test, :join_by => :and do
705
+ if_attribute :test_attr => 1
706
+ if_attribute :test_attr_2 => 2
707
+ end
708
+ end
709
+ end
710
+ }
711
+ engine = Authorization::Engine.new(reader)
712
+
713
+ assert engine.permit?(:test, :context => :permissions,
714
+ :user => MockUser.new(:test_role),
715
+ :object => MockDataObject.new(:test_attr => 1, :test_attr_2 => 2))
716
+ assert !engine.permit?(:test, :context => :permissions,
717
+ :user => MockUser.new(:test_role),
718
+ :object => MockDataObject.new(:test_attr => 1, :test_attr_2 => 3))
719
+ end
517
720
 
518
721
  def test_raise_on_if_attribute_hash_on_collection
519
722
  reader = Authorization::Reader::DSLReader.new