stffn-declarative_authorization 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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