annotation_security 1.0.2 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/CHANGELOG +22 -0
  2. data/HOW-TO +261 -0
  3. data/{LICENSE → MIT-LICENSE} +1 -1
  4. data/README +39 -0
  5. data/Rakefile +53 -62
  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 +14 -14
  10. data/bin/annotation_security +7 -7
  11. data/lib/annotation_security.rb +94 -103
  12. data/lib/annotation_security/exceptions.rb +124 -124
  13. data/lib/annotation_security/exec.rb +188 -188
  14. data/lib/annotation_security/includes/helper.rb +215 -215
  15. data/lib/annotation_security/includes/resource.rb +84 -84
  16. data/lib/annotation_security/includes/role.rb +30 -30
  17. data/lib/annotation_security/includes/user.rb +26 -26
  18. data/lib/annotation_security/manager/policy_factory.rb +29 -29
  19. data/lib/annotation_security/manager/policy_manager.rb +87 -79
  20. data/lib/annotation_security/manager/relation_loader.rb +272 -272
  21. data/lib/annotation_security/manager/resource_manager.rb +36 -36
  22. data/lib/annotation_security/manager/right_loader.rb +87 -87
  23. data/lib/annotation_security/policy/abstract_policy.rb +344 -344
  24. data/lib/annotation_security/policy/abstract_static_policy.rb +75 -75
  25. data/lib/annotation_security/policy/all_resources_policy.rb +20 -20
  26. data/lib/annotation_security/policy/rule.rb +340 -340
  27. data/lib/annotation_security/policy/rule_set.rb +138 -138
  28. data/lib/annotation_security/rails.rb +22 -39
  29. data/lib/{extensions → annotation_security/rails/2/extensions}/filter.rb +131 -133
  30. data/lib/annotation_security/rails/2/includes/action_controller.rb +144 -0
  31. data/lib/annotation_security/rails/2/includes/active_record.rb +28 -0
  32. data/lib/annotation_security/rails/2/initializer.rb +35 -0
  33. data/lib/annotation_security/{model_observer.rb → rails/2/model_observer.rb} +61 -61
  34. data/lib/annotation_security/rails/3/extensions/filter.rb +28 -0
  35. data/lib/annotation_security/{includes → rails/3/includes}/action_controller.rb +143 -144
  36. data/lib/annotation_security/{includes → rails/3/includes}/active_record.rb +27 -27
  37. data/lib/annotation_security/rails/3/initializer.rb +40 -0
  38. data/lib/annotation_security/rails/3/model_observer.rb +61 -0
  39. data/lib/annotation_security/rails/extensions.rb +21 -0
  40. data/lib/{extensions → annotation_security/rails/extensions}/action_controller.rb +31 -32
  41. data/lib/{extensions → annotation_security/rails/extensions}/active_record.rb +33 -34
  42. data/lib/{extensions → annotation_security/rails/extensions}/object.rb +10 -10
  43. data/lib/annotation_security/{filters.rb → rails/filters.rb} +37 -37
  44. data/lib/annotation_security/user_wrapper.rb +73 -73
  45. data/lib/annotation_security/utils.rb +141 -141
  46. data/lib/security_context.rb +588 -589
  47. data/spec/annotation_security/exceptions_spec.rb +16 -16
  48. data/spec/annotation_security/includes/helper_spec.rb +82 -82
  49. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -15
  50. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -17
  51. data/spec/annotation_security/manager/right_loader_spec.rb +17 -17
  52. data/spec/annotation_security/policy/abstract_policy_spec.rb +16 -16
  53. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -24
  54. data/spec/annotation_security/policy/rule_set_spec.rb +112 -112
  55. data/spec/annotation_security/policy/rule_spec.rb +77 -77
  56. data/spec/annotation_security/policy/test_policy_spec.rb +80 -80
  57. data/spec/annotation_security/security_context_spec.rb +129 -78
  58. data/spec/annotation_security/utils_spec.rb +73 -73
  59. data/spec/helper/test_controller.rb +65 -65
  60. data/spec/helper/test_helper.rb +5 -5
  61. data/spec/helper/test_relations.rb +6 -6
  62. data/spec/helper/test_resource.rb +38 -38
  63. data/spec/helper/test_role.rb +21 -21
  64. data/spec/helper/test_user.rb +31 -31
  65. data/spec/rails_stub.rb +44 -37
  66. metadata +110 -96
  67. data/CHANGELOG.md +0 -14
  68. data/HOW-TO.md +0 -275
  69. data/README.md +0 -39
  70. data/lib/annotation_security/version.rb +0 -10
@@ -1,76 +1,76 @@
1
- #
2
- # = lib/annotation_security/policy/abstract_static_policy.rb
3
- #
4
-
5
- # Abstract superclass for all static policies.
6
- # For each policy there is a static policy that is responsible for evaluating
7
- # static rules.
8
- #
9
- class AnnotationSecurity::AbstractStaticPolicy < AnnotationSecurity::AbstractPolicy # :nodoc:
10
-
11
- # Rules that are defined for all resource types can be found here.
12
- def self.all_resources_policy # :nodoc:
13
- AllResourcesPolicy.static_policy_class
14
- end
15
-
16
- # Sets the dynamic policy class this policy class belongs to
17
- def self.belongs_to(dynamic_policy_class) #:nodoc:
18
- @dynamic_policy_class = dynamic_policy_class
19
- end
20
-
21
- # A static policy class has no other corresponding static policy class.
22
- # This should never be called.
23
- def self.static_policy_class #:nodoc:
24
- method_missing(:static_policy_class)
25
- end
26
-
27
- # The corresponding dynamic policy class.
28
- #
29
- def self.dynamic_policy_class #:nodoc:
30
- @dynamic_policy_class
31
- end
32
-
33
- # Returns true iif this is policy class is responsible for static rules.
34
- #
35
- def self.static? # :nodoc:
36
- true
37
- end
38
-
39
- # Rule set for this classes resource type
40
- #
41
- def self.rule_set # :nodoc:
42
- # Each dynamic and static policy pair shares one rule set.
43
- dynamic_policy_class.rule_set
44
- end
45
-
46
- # If possible, redirects the rule to the static side.
47
- # Returns a rule object or nil.
48
- def self.use_static_rule(symbol) #:nodoc:
49
- nil # This is not possible
50
- end
51
-
52
- # Evaluate the rules in static mode.
53
- # Rules that cannot be evaluated are skipped.
54
- # * +rules+ array of symbols
55
- # Throws a SecurityViolationError if a rule fails,
56
- # returns true if all rules succeed.
57
- def evaluate_statically(rules) #:nodoc:
58
- rules.each do |rule|
59
- if has_rule?(rule) && !__send__(rule)
60
- raise_access_denied(rule)
61
- end
62
- end
63
- true
64
- end
65
-
66
- # Evaluate a rule that is defined with a proc
67
- # * +symbol+ Name of the rule
68
- # * +user+ user object that has to fulfill the rule
69
- # * +args+ List of additional arguments
70
- def evaluate_rule(rule,user,args) #:nodoc:
71
- # In contrast to AbstractPolicy#evaluate_rule,
72
- # no resource is passed as argument
73
- get_rule!(rule).evaluate(self,user,*args)
74
- end
75
-
1
+ #
2
+ # = lib/annotation_security/policy/abstract_static_policy.rb
3
+ #
4
+
5
+ # Abstract superclass for all static policies.
6
+ # For each policy there is a static policy that is responsible for evaluating
7
+ # static rules.
8
+ #
9
+ class AnnotationSecurity::AbstractStaticPolicy < AnnotationSecurity::AbstractPolicy # :nodoc:
10
+
11
+ # Rules that are defined for all resource types can be found here.
12
+ def self.all_resources_policy # :nodoc:
13
+ AllResourcesPolicy.static_policy_class
14
+ end
15
+
16
+ # Sets the dynamic policy class this policy class belongs to
17
+ def self.belongs_to(dynamic_policy_class) #:nodoc:
18
+ @dynamic_policy_class = dynamic_policy_class
19
+ end
20
+
21
+ # A static policy class has no other corresponding static policy class.
22
+ # This should never be called.
23
+ def self.static_policy_class #:nodoc:
24
+ method_missing(:static_policy_class)
25
+ end
26
+
27
+ # The corresponding dynamic policy class.
28
+ #
29
+ def self.dynamic_policy_class #:nodoc:
30
+ @dynamic_policy_class
31
+ end
32
+
33
+ # Returns true iif this is policy class is responsible for static rules.
34
+ #
35
+ def self.static? # :nodoc:
36
+ true
37
+ end
38
+
39
+ # Rule set for this classes resource type
40
+ #
41
+ def self.rule_set # :nodoc:
42
+ # Each dynamic and static policy pair shares one rule set.
43
+ dynamic_policy_class.rule_set
44
+ end
45
+
46
+ # If possible, redirects the rule to the static side.
47
+ # Returns a rule object or nil.
48
+ def self.use_static_rule(symbol) #:nodoc:
49
+ nil # This is not possible
50
+ end
51
+
52
+ # Evaluate the rules in static mode.
53
+ # Rules that cannot be evaluated are skipped.
54
+ # * +rules+ array of symbols
55
+ # Throws a SecurityViolationError if a rule fails,
56
+ # returns true if all rules succeed.
57
+ def evaluate_statically(rules) #:nodoc:
58
+ rules.each do |rule|
59
+ if has_rule?(rule) && !__send__(rule)
60
+ raise_access_denied(rule)
61
+ end
62
+ end
63
+ true
64
+ end
65
+
66
+ # Evaluate a rule that is defined with a proc
67
+ # * +symbol+ Name of the rule
68
+ # * +user+ user object that has to fulfill the rule
69
+ # * +args+ List of additional arguments
70
+ def evaluate_rule(rule,user,args) #:nodoc:
71
+ # In contrast to AbstractPolicy#evaluate_rule,
72
+ # no resource is passed as argument
73
+ get_rule!(rule).evaluate(self,user,*args)
74
+ end
75
+
76
76
  end
@@ -1,21 +1,21 @@
1
- #
2
- # = lib/annotation_security/policy/all_resources_policy.rb
3
- #
4
- # By default, two relations are provided for all resources.
5
- #
6
- # The system relation +logged_in+ evaluates to true if the provided
7
- # credentials are not nil.
8
- # logged_in(:system, :require_credential => false) {|u| not u.nil?}
9
- #
10
- # The relation +self+ is true when the accessed resource is the current user
11
- # himself or a role that belongs to the current user.
12
- # __self__ { |user, resource| resource.is_user?(user) }
13
- #
14
- AnnotationSecurity.define_relations :all_resources do
15
-
16
- # can be used as "self" in a right definition
17
- # success if the accessed resource is the user himself or one of his roles
18
- __self__ { |user, resource| resource.is_user?(user) }
19
-
20
- logged_in(:system, :require_credential => false) {|u| not u.nil?}
1
+ #
2
+ # = lib/annotation_security/policy/all_resources_policy.rb
3
+ #
4
+ # By default, two relations are provided for all resources.
5
+ #
6
+ # The system relation +logged_in+ evaluates to true if the provided
7
+ # credentials are not nil.
8
+ # logged_in(:system, :require_credential => false) {|u| not u.nil?}
9
+ #
10
+ # The relation +self+ is true when the accessed resource is the current user
11
+ # himself or a role that belongs to the current user.
12
+ # __self__ { |user, resource| resource.is_user?(user) }
13
+ #
14
+ AnnotationSecurity.define_relations :all_resources do
15
+
16
+ # can be used as "self" in a right definition
17
+ # success if the accessed resource is the user himself or one of his roles
18
+ __self__ { |user, resource| resource.is_user?(user) }
19
+
20
+ logged_in(:system, :require_credential => false) {|u| not u.nil?}
21
21
  end
@@ -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