annotation_security 1.0.2 → 1.3.1

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 (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