annotation_security 1.0.1 → 1.0.2

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.
Files changed (63) hide show
  1. data/CHANGELOG.md +14 -0
  2. data/HOW-TO.md +275 -0
  3. data/{MIT-LICENSE → LICENSE} +1 -1
  4. data/README.md +39 -0
  5. data/Rakefile +62 -55
  6. data/assets/app/helpers/annotation_security_helper.rb +8 -8
  7. data/assets/config/initializers/annotation_security.rb +11 -11
  8. data/assets/config/security/relations.rb +20 -20
  9. data/assets/vendor/plugins/annotation_security/init.rb +13 -13
  10. data/bin/annotation_security +7 -7
  11. data/lib/annotation_security/exceptions.rb +124 -124
  12. data/lib/annotation_security/exec.rb +188 -188
  13. data/lib/annotation_security/filters.rb +37 -37
  14. data/lib/annotation_security/includes/action_controller.rb +144 -143
  15. data/lib/annotation_security/includes/active_record.rb +27 -27
  16. data/lib/annotation_security/includes/helper.rb +215 -215
  17. data/lib/annotation_security/includes/resource.rb +84 -84
  18. data/lib/annotation_security/includes/role.rb +30 -30
  19. data/lib/annotation_security/includes/user.rb +26 -26
  20. data/lib/annotation_security/manager/policy_factory.rb +29 -29
  21. data/lib/annotation_security/manager/policy_manager.rb +79 -79
  22. data/lib/annotation_security/manager/relation_loader.rb +272 -272
  23. data/lib/annotation_security/manager/resource_manager.rb +36 -36
  24. data/lib/annotation_security/manager/right_loader.rb +87 -87
  25. data/lib/annotation_security/model_observer.rb +61 -61
  26. data/lib/annotation_security/policy/abstract_policy.rb +344 -344
  27. data/lib/annotation_security/policy/abstract_static_policy.rb +75 -75
  28. data/lib/annotation_security/policy/all_resources_policy.rb +20 -20
  29. data/lib/annotation_security/policy/rule.rb +340 -340
  30. data/lib/annotation_security/policy/rule_set.rb +138 -138
  31. data/lib/annotation_security/rails.rb +38 -38
  32. data/lib/annotation_security/user_wrapper.rb +73 -73
  33. data/lib/annotation_security/utils.rb +141 -141
  34. data/lib/annotation_security/version.rb +10 -0
  35. data/lib/annotation_security.rb +102 -97
  36. data/lib/extensions/action_controller.rb +32 -32
  37. data/lib/extensions/active_record.rb +34 -34
  38. data/lib/extensions/filter.rb +133 -133
  39. data/lib/extensions/object.rb +10 -10
  40. data/lib/security_context.rb +589 -551
  41. data/spec/annotation_security/exceptions_spec.rb +16 -16
  42. data/spec/annotation_security/includes/helper_spec.rb +82 -82
  43. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -15
  44. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -17
  45. data/spec/annotation_security/manager/right_loader_spec.rb +17 -17
  46. data/spec/annotation_security/policy/abstract_policy_spec.rb +16 -16
  47. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -24
  48. data/spec/annotation_security/policy/rule_set_spec.rb +112 -112
  49. data/spec/annotation_security/policy/rule_spec.rb +77 -77
  50. data/spec/annotation_security/policy/test_policy_spec.rb +80 -80
  51. data/spec/annotation_security/security_context_spec.rb +78 -78
  52. data/spec/annotation_security/utils_spec.rb +73 -73
  53. data/spec/helper/test_controller.rb +65 -65
  54. data/spec/helper/test_helper.rb +5 -5
  55. data/spec/helper/test_relations.rb +6 -6
  56. data/spec/helper/test_resource.rb +38 -38
  57. data/spec/helper/test_role.rb +21 -21
  58. data/spec/helper/test_user.rb +31 -31
  59. data/spec/rails_stub.rb +37 -37
  60. metadata +94 -72
  61. data/CHANGELOG +0 -2
  62. data/HOW-TO +0 -261
  63. data/README +0 -39
@@ -1,340 +1,340 @@
1
- #
2
- # = lib/annotation_security/policy/rule.rb
3
- #
4
-
5
- # = AnnotationSecurity::Rule
6
- # A right or a relation that belongs to a policy.
7
- #
8
- # Rules can be static or dynamic or both.
9
- # If the rule is a right, these values will be evaluated lazily.
10
- #
11
- class AnnotationSecurity::Rule # :nodoc:
12
-
13
- # Initialize a rule
14
- #
15
- def initialize(name,policy_class,*args,&block) # :nodoc:
16
- super()
17
- @name = name.to_sym
18
- @policy_class = policy_class
19
- @proc = block
20
- read_flags(args)
21
- read_options(args)
22
- if @proc
23
- initialize_for_proc(args)
24
- else
25
- initialize_for_string(args)
26
- end
27
- raise ArgumentError,
28
- "#{self}: Unexpected Arguments: #{args.join ','}" unless args.blank?
29
- #puts self
30
- end
31
-
32
- def to_s # :nodoc:
33
- "<#{full_name}[#{flag_s}]>"
34
- end
35
-
36
- def full_name # :nodoc:
37
- "#@policy_class##@name"
38
- end
39
-
40
- def flag_s # :nodoc:
41
- (@right ? 'r' : '-') +
42
- (@static.nil? ? '?' : (@static ? 's' : '-')) +
43
- (@dynamic.nil? ? '?' : (@dynamic ? 'd' : '-')) +
44
- (@req_user.nil? ? '?' : (@req_user ? 'u' : '-'))
45
- end
46
-
47
- # Return if this rule was defined as right
48
- #
49
- def right? # :nodoc:
50
- @right
51
- end
52
-
53
- # Return if this rule can be evaluated without a resource
54
- #
55
- def static? # :nodoc:
56
- return @static unless @static.nil?
57
- lazy_initialize
58
- @static
59
- end
60
-
61
- # Return if this rule can be evaluated with a resource
62
- #
63
- def dynamic? # :nodoc:
64
- return @dynamic unless @dynamic.nil?
65
- lazy_initialize
66
- @dynamic
67
- end
68
-
69
- def requires_credential? # :nodoc:
70
- return @req_user unless @req_user.nil?
71
- lazy_initialize
72
- @req_user
73
- end
74
-
75
- # Creates a method for a policy class that evaluates this rule
76
- # * +klass+ either @policy_class or its static partner
77
- #
78
- def extend_class(klass) # :nodoc:
79
-
80
- # Arguments passed to AbstractPolicy#user_roles
81
- # * +role+ symbol identifying the role a user must have (or nil)
82
- # * +user_required+ if false, the rule will also be
83
- # evaluated if the user is nil
84
- user_args = "#{@as ? ":#@as" : 'nil'},#{requires_credential?}"
85
-
86
- # Actual logic of the rule
87
- rule_code = @proc ? code_for_proc : code_for_string
88
-
89
- # Arguments passed to RuleExecutionError#new if an error occured
90
- # while evaluating the rule
91
- # * +rule+ full name of the rule
92
- # * +proc+ true iif this rule is defined with a proc
93
- # * +ex+ the original exeption
94
- ex_args = "'#{full_name}',#{@proc ? true : false},$!"
95
-
96
- code = "def #@name(*args) \n"
97
-
98
- # If parameter :is is given, @user.is_{@is}? has to return true.
99
- #
100
- code << "return false if @user.nil? || !@user.is_#@is?\n" if @is
101
- code << %{
102
- # __resource__ = @resource
103
- return user_roles(#{user_args}).any? do |__user__|
104
- #{rule_code}
105
- end
106
- rescue StandardError
107
- raise $! if $!.is_a? AnnotationSecurity::SecurityError
108
- raise AnnotationSecurity::RuleExecutionError.new(#{ex_args})
109
- end}
110
- klass.class_eval(code)
111
- self
112
- end
113
-
114
- # Evaluate proc for policy
115
- def evaluate(policy,*args) # :nodoc:
116
- raise AnnotationSecurity::RuleError, "#{self}: This rule has no proc" unless @proc
117
- if @arity == 0
118
- policy.instance_exec(&@proc)
119
- elsif @arity > 0
120
- policy.instance_exec(*(args[0..@arity-1]),&@proc)
121
- else
122
- policy.instance_exec(*args,&@proc)
123
- end
124
- end
125
-
126
- # Creates a copy for policy class
127
- #
128
- def copy(policy_class) # :nodoc:
129
- args = [name, policy_class,flag,options,@condition].compact
130
- self.class.new(*args,&@proc)
131
- end
132
-
133
- def name # :nodoc:
134
- @name
135
- end
136
-
137
- private
138
-
139
- def read_flags(args)
140
- @right = false
141
- @static = false
142
- @dynamic = true
143
- @req_user = true
144
- if args.delete :right
145
- @right = true
146
- @req_user = @static = @dynamic = nil
147
- elsif args.delete :system
148
- @static = true
149
- @dynamic = false
150
- elsif args.delete :pretest
151
- @static = true
152
- else
153
- args.delete :resource # default
154
- end
155
- end
156
-
157
- def flag
158
- return :right if right?
159
- if static?
160
- return :pretest if dynamic?
161
- return :system
162
- else
163
- return :resource
164
- end
165
- end
166
-
167
- def read_options(args)
168
- hash = args.detect {|h| h.is_a? Hash}
169
- args.delete hash
170
- return if hash.blank?
171
- @as = hash.delete(:as)
172
- @is = hash.delete(:is)
173
- @req_user = hash.delete(:require_credential)
174
- @req_user = true if @req_user.nil? && !right?
175
- if (@as || @is) && !@req_user
176
- raise ArgumentError, "Options :as and :is always require a user!"
177
- end
178
- unless hash.empty?
179
- raise ArgumentError, "Unexpected keys [#{hash.keys.join(', ')}]"
180
- end
181
- end
182
-
183
- def options
184
- {:is => @is, :as => @as, :require_credential => (right? ? nil : requires_credential?)}
185
- end
186
-
187
- # Check for the optional parameter :as => :role
188
- def initialize_for_proc(args)
189
- @arity = @proc.arity
190
- end
191
-
192
- def initialize_for_string(args)
193
- @condition = args.detect {|s| s.is_a? String } || 'true'
194
- args.delete @condition
195
- end
196
-
197
- # Find out if this rule can be evaluated statically
198
- def lazy_initialize
199
- raise_evil_recursion if @initialize_static
200
- @initialize_static = true
201
- if @proc
202
- # rules with proc must be defined as static explicitly
203
- @static = false
204
- @dynamic = true
205
- @req_user = true
206
- else
207
- # parse string to find out more
208
- if @condition =~ /:|self/
209
- # this only works with resources
210
- @static = false
211
- @dynamic = true
212
- @req_user = true
213
- else
214
- @static = true # a right is static if it uses only static rules
215
- @dynamic = false # a right is dynamic if it uses at least one dynamic rule
216
- @req_user = false # unless at least one rule requires a user
217
- @condition.gsub(/\(|\)/,' ').split.each do |token|
218
- unless token =~ /\A(if|unless|or|and|not|true|false|nil)\Z/
219
- token = validate_token!(token)
220
- @static &= can_be_static?(token)
221
- @dynamic |= can_be_dynamic?(token)
222
- @req_user |= needs_user?(token)
223
- end
224
- end
225
- end
226
- end
227
- raise AnnotationSecurity::RuleError,
228
- "#{self} is neither static nor dynamic!" unless @static || @dynamic
229
- end
230
-
231
- def validate_token!(token)
232
- return token.to_sym if @policy_class.has_rule?(token.to_sym)
233
- body = AnnotationSecurity::Utils.method_body(token)
234
- return validate_token!(body) if body
235
- raise AnnotationSecurity::RuleNotFoundError, "Unknown rule '#{token}' in #{full_name}"
236
- end
237
-
238
- def can_be_static?(token)
239
- @policy_class.has_static_rule?(token)
240
- end
241
-
242
- def can_be_dynamic?(token)
243
- @policy_class.has_dynamic_rule?(token)
244
- end
245
-
246
- def needs_user?(token)
247
- @policy_class.get_rule(token).requires_credential?
248
- end
249
-
250
- def raise_evil_recursion
251
- raise AnnotationSecurity::RuleError,
252
- "Forbidden recursion in #{@policy_class.resource_class}: #{self}"
253
- end
254
-
255
- def code_for_proc
256
- "evaluate_rule(:#{name},__user__,args)"
257
- end
258
-
259
- def code_for_string
260
- condition = @condition.dup
261
-
262
- # Apply special role 'self'
263
- condition.gsub!('self', '__self__')
264
-
265
- apply_resource_notation(condition)
266
- apply_may_property_notation(condition)
267
-
268
- if condition =~ /\A(\s*)(if|unless)/
269
- # multilines in case +condition+ contains comments
270
- "#{condition} \n true \n else \n false \n end"
271
- else
272
- condition
273
- end
274
- end
275
-
276
- # Apply replacements for :resource notation:
277
- # Rules of the form prefix(:resource.res_suffix, additional_args) are
278
- # rewritten to @resource.res_suffix.policy_for(@user).prefix(additional_args)
279
- #
280
- # They are per definition dynamic!
281
- # Other rules which contain :resource are per se dynamic, too!
282
- #
283
- def apply_resource_notation(condition)
284
- regex = /([^\s\(]+)\(:resource(?:\.([^\s,]*))?(?:,\s*([^\(]*))?\)/
285
-
286
- condition.gsub!(regex) do |match|
287
- parse_expr(match.scan(regex).first)
288
- end
289
- #condition.gsub!(/:resource/, "@resource")
290
- end
291
-
292
- def parse_expr(expr)
293
- prefix = expr.at(0)
294
- res_suffix = expr.at(1)
295
- additional_args = expr.at(2)
296
-
297
- case prefix
298
- when /^(if|unless|or|and|not)$/
299
- # Should not be matched by regex
300
- raise ArgumentError, "Invalid syntax."
301
- else
302
- res_class, right = parse_right(prefix)
303
- if res_class
304
- "(PolicyManager.get_policy("+
305
- ":#{res_class},@user,@resource.#{res_suffix}).#{right})"
306
- else
307
- call = "(!(res = @resource"
308
- call << ".#{res_suffix}" if res_suffix
309
- call << ").nil? &&"
310
- call << "res.policy_for(@user).#{prefix}"
311
- call << "(#{additional_args})" if additional_args
312
- call << ')'
313
- call
314
- end
315
- end
316
- end
317
-
318
- # Apply replacements for 'may: property' notation
319
- def apply_may_property_notation(condition)
320
- rx_may_prop = /(\S+):\s*(\S+)/
321
- condition.gsub!(rx_may_prop) do |match|
322
- right, resource = match.scan(rx_may_prop).first
323
- res_class, right = parse_right(right)
324
- if res_class
325
- "(PolicyManager.get_policy("+
326
- ":#{res_class},@user,@resource.#{resource}).#{right})"
327
- else
328
- "(!(res = @resource.#{resource}).nil? &&" +
329
- " res.policy_for(@user).#{right})"
330
- end
331
- end
332
- end
333
-
334
- # Returns [res_class, right] if +right+ has the form "res_class.right",
335
- # else it returns [nil, right].
336
- def parse_right(right)
337
- rx_class_right = /(\S*)\.(\S*)/
338
- (right.scan(rx_class_right).first) || [nil,right]
339
- end
340
- end
1
+ #
2
+ # = lib/annotation_security/policy/rule.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::Rule
6
+ # A right or a relation that belongs to a policy.
7
+ #
8
+ # Rules can be static or dynamic or both.
9
+ # If the rule is a right, these values will be evaluated lazily.
10
+ #
11
+ class AnnotationSecurity::Rule # :nodoc:
12
+
13
+ # Initialize a rule
14
+ #
15
+ def initialize(name,policy_class,*args,&block) # :nodoc:
16
+ super()
17
+ @name = name.to_sym
18
+ @policy_class = policy_class
19
+ @proc = block
20
+ read_flags(args)
21
+ read_options(args)
22
+ if @proc
23
+ initialize_for_proc(args)
24
+ else
25
+ initialize_for_string(args)
26
+ end
27
+ raise ArgumentError,
28
+ "#{self}: Unexpected Arguments: #{args.join ','}" unless args.blank?
29
+ #puts self
30
+ end
31
+
32
+ def to_s # :nodoc:
33
+ "<#{full_name}[#{flag_s}]>"
34
+ end
35
+
36
+ def full_name # :nodoc:
37
+ "#@policy_class##@name"
38
+ end
39
+
40
+ def flag_s # :nodoc:
41
+ (@right ? 'r' : '-') +
42
+ (@static.nil? ? '?' : (@static ? 's' : '-')) +
43
+ (@dynamic.nil? ? '?' : (@dynamic ? 'd' : '-')) +
44
+ (@req_user.nil? ? '?' : (@req_user ? 'u' : '-'))
45
+ end
46
+
47
+ # Return if this rule was defined as right
48
+ #
49
+ def right? # :nodoc:
50
+ @right
51
+ end
52
+
53
+ # Return if this rule can be evaluated without a resource
54
+ #
55
+ def static? # :nodoc:
56
+ return @static unless @static.nil?
57
+ lazy_initialize
58
+ @static
59
+ end
60
+
61
+ # Return if this rule can be evaluated with a resource
62
+ #
63
+ def dynamic? # :nodoc:
64
+ return @dynamic unless @dynamic.nil?
65
+ lazy_initialize
66
+ @dynamic
67
+ end
68
+
69
+ def requires_credential? # :nodoc:
70
+ return @req_user unless @req_user.nil?
71
+ lazy_initialize
72
+ @req_user
73
+ end
74
+
75
+ # Creates a method for a policy class that evaluates this rule
76
+ # * +klass+ either @policy_class or its static partner
77
+ #
78
+ def extend_class(klass) # :nodoc:
79
+
80
+ # Arguments passed to AbstractPolicy#user_roles
81
+ # * +role+ symbol identifying the role a user must have (or nil)
82
+ # * +user_required+ if false, the rule will also be
83
+ # evaluated if the user is nil
84
+ user_args = "#{@as ? ":#@as" : 'nil'},#{requires_credential?}"
85
+
86
+ # Actual logic of the rule
87
+ rule_code = @proc ? code_for_proc : code_for_string
88
+
89
+ # Arguments passed to RuleExecutionError#new if an error occured
90
+ # while evaluating the rule
91
+ # * +rule+ full name of the rule
92
+ # * +proc+ true iif this rule is defined with a proc
93
+ # * +ex+ the original exeption
94
+ ex_args = "'#{full_name}',#{@proc ? true : false},$!"
95
+
96
+ code = "def #@name(*args) \n"
97
+
98
+ # If parameter :is is given, @user.is_{@is}? has to return true.
99
+ #
100
+ code << "return false if @user.nil? || !@user.is_#@is?\n" if @is
101
+ code << %{
102
+ # __resource__ = @resource
103
+ return user_roles(#{user_args}).any? do |__user__|
104
+ #{rule_code}
105
+ end
106
+ rescue StandardError
107
+ raise $! if $!.is_a? AnnotationSecurity::SecurityError
108
+ raise AnnotationSecurity::RuleExecutionError.new(#{ex_args})
109
+ end}
110
+ klass.class_eval(code)
111
+ self
112
+ end
113
+
114
+ # Evaluate proc for policy
115
+ def evaluate(policy,*args) # :nodoc:
116
+ raise AnnotationSecurity::RuleError, "#{self}: This rule has no proc" unless @proc
117
+ if @arity == 0
118
+ policy.instance_exec(&@proc)
119
+ elsif @arity > 0
120
+ policy.instance_exec(*(args[0..@arity-1]),&@proc)
121
+ else
122
+ policy.instance_exec(*args,&@proc)
123
+ end
124
+ end
125
+
126
+ # Creates a copy for policy class
127
+ #
128
+ def copy(policy_class) # :nodoc:
129
+ args = [name, policy_class,flag,options,@condition].compact
130
+ self.class.new(*args,&@proc)
131
+ end
132
+
133
+ def name # :nodoc:
134
+ @name
135
+ end
136
+
137
+ private
138
+
139
+ def read_flags(args)
140
+ @right = false
141
+ @static = false
142
+ @dynamic = true
143
+ @req_user = true
144
+ if args.delete :right
145
+ @right = true
146
+ @req_user = @static = @dynamic = nil
147
+ elsif args.delete :system
148
+ @static = true
149
+ @dynamic = false
150
+ elsif args.delete :pretest
151
+ @static = true
152
+ else
153
+ args.delete :resource # default
154
+ end
155
+ end
156
+
157
+ def flag
158
+ return :right if right?
159
+ if static?
160
+ return :pretest if dynamic?
161
+ return :system
162
+ else
163
+ return :resource
164
+ end
165
+ end
166
+
167
+ def read_options(args)
168
+ hash = args.detect {|h| h.is_a? Hash}
169
+ args.delete hash
170
+ return if hash.blank?
171
+ @as = hash.delete(:as)
172
+ @is = hash.delete(:is)
173
+ @req_user = hash.delete(:require_credential)
174
+ @req_user = true if @req_user.nil? && !right?
175
+ if (@as || @is) && !@req_user
176
+ raise ArgumentError, "Options :as and :is always require a user!"
177
+ end
178
+ unless hash.empty?
179
+ raise ArgumentError, "Unexpected keys [#{hash.keys.join(', ')}]"
180
+ end
181
+ end
182
+
183
+ def options
184
+ {:is => @is, :as => @as, :require_credential => (right? ? nil : requires_credential?)}
185
+ end
186
+
187
+ # Check for the optional parameter :as => :role
188
+ def initialize_for_proc(args)
189
+ @arity = @proc.arity
190
+ end
191
+
192
+ def initialize_for_string(args)
193
+ @condition = args.detect {|s| s.is_a? String } || 'true'
194
+ args.delete @condition
195
+ end
196
+
197
+ # Find out if this rule can be evaluated statically
198
+ def lazy_initialize
199
+ raise_evil_recursion if @initialize_static
200
+ @initialize_static = true
201
+ if @proc
202
+ # rules with proc must be defined as static explicitly
203
+ @static = false
204
+ @dynamic = true
205
+ @req_user = true
206
+ else
207
+ # parse string to find out more
208
+ if @condition =~ /:|self/
209
+ # this only works with resources
210
+ @static = false
211
+ @dynamic = true
212
+ @req_user = true
213
+ else
214
+ @static = true # a right is static if it uses only static rules
215
+ @dynamic = false # a right is dynamic if it uses at least one dynamic rule
216
+ @req_user = false # unless at least one rule requires a user
217
+ @condition.gsub(/\(|\)/,' ').split.each do |token|
218
+ unless token =~ /\A(if|unless|or|and|not|true|false|nil)\Z/
219
+ token = validate_token!(token)
220
+ @static &= can_be_static?(token)
221
+ @dynamic |= can_be_dynamic?(token)
222
+ @req_user |= needs_user?(token)
223
+ end
224
+ end
225
+ end
226
+ end
227
+ raise AnnotationSecurity::RuleError,
228
+ "#{self} is neither static nor dynamic!" unless @static || @dynamic
229
+ end
230
+
231
+ def validate_token!(token)
232
+ return token.to_sym if @policy_class.has_rule?(token.to_sym)
233
+ body = AnnotationSecurity::Utils.method_body(token)
234
+ return validate_token!(body) if body
235
+ raise AnnotationSecurity::RuleNotFoundError, "Unknown rule '#{token}' in #{full_name}"
236
+ end
237
+
238
+ def can_be_static?(token)
239
+ @policy_class.has_static_rule?(token)
240
+ end
241
+
242
+ def can_be_dynamic?(token)
243
+ @policy_class.has_dynamic_rule?(token)
244
+ end
245
+
246
+ def needs_user?(token)
247
+ @policy_class.get_rule(token).requires_credential?
248
+ end
249
+
250
+ def raise_evil_recursion
251
+ raise AnnotationSecurity::RuleError,
252
+ "Forbidden recursion in #{@policy_class.resource_class}: #{self}"
253
+ end
254
+
255
+ def code_for_proc
256
+ "evaluate_rule(:#{name},__user__,args)"
257
+ end
258
+
259
+ def code_for_string
260
+ condition = @condition.dup
261
+
262
+ # Apply special role 'self'
263
+ condition.gsub!('self', '__self__')
264
+
265
+ apply_resource_notation(condition)
266
+ apply_may_property_notation(condition)
267
+
268
+ if condition =~ /\A(\s*)(if|unless)/
269
+ # multilines in case +condition+ contains comments
270
+ "#{condition} \n true \n else \n false \n end"
271
+ else
272
+ condition
273
+ end
274
+ end
275
+
276
+ # Apply replacements for :resource notation:
277
+ # Rules of the form prefix(:resource.res_suffix, additional_args) are
278
+ # rewritten to @resource.res_suffix.policy_for(@user).prefix(additional_args)
279
+ #
280
+ # They are per definition dynamic!
281
+ # Other rules which contain :resource are per se dynamic, too!
282
+ #
283
+ def apply_resource_notation(condition)
284
+ regex = /([^\s\(]+)\(:resource(?:\.([^\s,]*))?(?:,\s*([^\(]*))?\)/
285
+
286
+ condition.gsub!(regex) do |match|
287
+ parse_expr(match.scan(regex).first)
288
+ end
289
+ #condition.gsub!(/:resource/, "@resource")
290
+ end
291
+
292
+ def parse_expr(expr)
293
+ prefix = expr.at(0)
294
+ res_suffix = expr.at(1)
295
+ additional_args = expr.at(2)
296
+
297
+ case prefix
298
+ when /^(if|unless|or|and|not)$/
299
+ # Should not be matched by regex
300
+ raise ArgumentError, "Invalid syntax."
301
+ else
302
+ res_class, right = parse_right(prefix)
303
+ if res_class
304
+ "(PolicyManager.get_policy("+
305
+ ":#{res_class},@user,@resource.#{res_suffix}).#{right})"
306
+ else
307
+ call = "(!(res = @resource"
308
+ call << ".#{res_suffix}" if res_suffix
309
+ call << ").nil? &&"
310
+ call << "res.policy_for(@user).#{prefix}"
311
+ call << "(#{additional_args})" if additional_args
312
+ call << ')'
313
+ call
314
+ end
315
+ end
316
+ end
317
+
318
+ # Apply replacements for 'may: property' notation
319
+ def apply_may_property_notation(condition)
320
+ rx_may_prop = /(\S+):\s*(\S+)/
321
+ condition.gsub!(rx_may_prop) do |match|
322
+ right, resource = match.scan(rx_may_prop).first
323
+ res_class, right = parse_right(right)
324
+ if res_class
325
+ "(PolicyManager.get_policy("+
326
+ ":#{res_class},@user,@resource.#{resource}).#{right})"
327
+ else
328
+ "(!(res = @resource.#{resource}).nil? &&" +
329
+ " res.policy_for(@user).#{right})"
330
+ end
331
+ end
332
+ end
333
+
334
+ # Returns [res_class, right] if +right+ has the form "res_class.right",
335
+ # else it returns [nil, right].
336
+ def parse_right(right)
337
+ rx_class_right = /(\S*)\.(\S*)/
338
+ (right.scan(rx_class_right).first) || [nil,right]
339
+ end
340
+ end