annotation_security 1.0.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 (62) hide show
  1. data/CHANGELOG +2 -0
  2. data/HOW-TO +261 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README +39 -0
  5. data/Rakefile +56 -0
  6. data/assets/app/helpers/annotation_security_helper.rb +9 -0
  7. data/assets/config/initializers/annotation_security.rb +12 -0
  8. data/assets/config/security/relations.rb +20 -0
  9. data/assets/config/security/rights.yml +16 -0
  10. data/assets/vendor/plugins/annotation_security/init.rb +14 -0
  11. data/bin/annotation_security +8 -0
  12. data/lib/annotation_security/exceptions.rb +125 -0
  13. data/lib/annotation_security/exec.rb +189 -0
  14. data/lib/annotation_security/filters.rb +38 -0
  15. data/lib/annotation_security/includes/action_controller.rb +144 -0
  16. data/lib/annotation_security/includes/active_record.rb +28 -0
  17. data/lib/annotation_security/includes/helper.rb +215 -0
  18. data/lib/annotation_security/includes/resource.rb +85 -0
  19. data/lib/annotation_security/includes/role.rb +31 -0
  20. data/lib/annotation_security/includes/user.rb +27 -0
  21. data/lib/annotation_security/manager/policy_factory.rb +30 -0
  22. data/lib/annotation_security/manager/policy_manager.rb +80 -0
  23. data/lib/annotation_security/manager/relation_loader.rb +273 -0
  24. data/lib/annotation_security/manager/resource_manager.rb +36 -0
  25. data/lib/annotation_security/manager/right_loader.rb +88 -0
  26. data/lib/annotation_security/model_observer.rb +61 -0
  27. data/lib/annotation_security/policy/abstract_policy.rb +345 -0
  28. data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
  29. data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
  30. data/lib/annotation_security/policy/rule.rb +340 -0
  31. data/lib/annotation_security/policy/rule_set.rb +139 -0
  32. data/lib/annotation_security/rails.rb +39 -0
  33. data/lib/annotation_security/user_wrapper.rb +74 -0
  34. data/lib/annotation_security/utils.rb +142 -0
  35. data/lib/annotation_security.rb +98 -0
  36. data/lib/extensions/action_controller.rb +33 -0
  37. data/lib/extensions/active_record.rb +35 -0
  38. data/lib/extensions/filter.rb +134 -0
  39. data/lib/extensions/object.rb +11 -0
  40. data/lib/security_context.rb +551 -0
  41. data/spec/annotation_security/exceptions_spec.rb +17 -0
  42. data/spec/annotation_security/includes/helper_spec.rb +82 -0
  43. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
  44. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
  45. data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
  46. data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
  47. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
  48. data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
  49. data/spec/annotation_security/policy/rule_spec.rb +78 -0
  50. data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
  51. data/spec/annotation_security/security_context_spec.rb +78 -0
  52. data/spec/annotation_security/utils_spec.rb +74 -0
  53. data/spec/helper/test_controller.rb +66 -0
  54. data/spec/helper/test_helper.rb +5 -0
  55. data/spec/helper/test_relations.rb +7 -0
  56. data/spec/helper/test_resource.rb +39 -0
  57. data/spec/helper/test_rights.yml +5 -0
  58. data/spec/helper/test_role.rb +22 -0
  59. data/spec/helper/test_user.rb +32 -0
  60. data/spec/rails_stub.rb +38 -0
  61. data/spec/spec_helper.rb +43 -0
  62. metadata +157 -0
@@ -0,0 +1,345 @@
1
+ #
2
+ # = lib/annotation_security/policy/abstract_policy.rb
3
+ #
4
+
5
+ # Abstract superclass for all policies
6
+ #
7
+ # For each resource type there is a corresponding policy class.
8
+ # In its entire lifetime, a policy object is responsible for a single user.
9
+ # A policy object can validate the rights for only one resource
10
+ # object at a time (it is not thread safe!).
11
+ #
12
+ class AnnotationSecurity::AbstractPolicy
13
+
14
+ # Creates a new policy class for a resource type.
15
+ #
16
+ def self.new_subclass(resource_type) #:nodoc:
17
+ returning Class.new(self) do |c|
18
+ c.initialize(resource_type)
19
+ end
20
+ end
21
+
22
+ # Initializes a subclass of AbstractPolicy
23
+ #
24
+ def self.initialize(resource_type) #:nodoc:
25
+ @resource_type = resource_type.to_s.underscore.to_sym
26
+
27
+ # register the class as constant
28
+ name = resource_type.to_s.camelize + classname_suffix
29
+ Object.const_set name, self
30
+
31
+ unless static?
32
+ # Each policy has a static partner
33
+ @static_policy_class = AnnotationSecurity::AbstractStaticPolicy.new_subclass(@resource_type)
34
+ @static_policy_class.belongs_to self
35
+ reset
36
+ end
37
+ end
38
+
39
+ # Suffix that is appended to the camlized resource type to
40
+ # generate a class name.
41
+ #
42
+ def self.classname_suffix # :nodoc:
43
+ static? ? 'StaticPolicy' : 'Policy'
44
+ end
45
+
46
+ # (Re-)Initializes the policy class.
47
+ # Removes all generated methods and clears the rule set.
48
+ #
49
+ def self.reset # :nodoc:
50
+ instance_methods(false).each { |m| remove_method m }
51
+ @has_rule = Hash.new {|h,k| h[k] = !get_rule(k).nil?}
52
+ @my_rules = Hash.new {|h,k| h[k] = load_rule(k)}
53
+ unless static?
54
+ # set of all rule objects available for this policy
55
+ @rule_set = AnnotationSecurity::RuleSet.new(self)
56
+ # {:rule => boolean} if true, the rule can be evaluated statically only
57
+ @static_only = Hash.new(false)
58
+ # {:rule => boolean} if true, the rule can be evaluated dynamically
59
+ @has_dynamic = Hash.new {|h,k| h[k] = has_rule?(k) && !@static_only[k]}
60
+ @static_policy_class.reset
61
+ end
62
+ end
63
+
64
+ # List of strings that are not allowed as rule names (maybe incomplete).
65
+ #
66
+ def self.forbidden_rule_names # :nodoc:
67
+ instance_methods
68
+ end
69
+
70
+ # Rules that are defined for all resource types can be found here.
71
+ # (Overwritten by static policy)
72
+ def self.all_resources_policy # :nodoc:
73
+ AllResourcesPolicy
74
+ end
75
+
76
+ # Symbol representing the resource type this policy is responsible for.
77
+ #
78
+ def self.resource_type # :nodoc:
79
+ @resource_type
80
+ end
81
+
82
+ # The corresponding static policy class.
83
+ #
84
+ def self.static_policy_class # :nodoc:
85
+ @static_policy_class
86
+ end
87
+
88
+ # Returns true iif this is policy class is responsible for static rules.
89
+ #
90
+ def self.static? # :nodoc:
91
+ false
92
+ end
93
+
94
+ # Rule set for this classes resource type
95
+ #
96
+ def self.rule_set # :nodoc:
97
+ @rule_set
98
+ end
99
+
100
+ # Returns true iif this policy can evaluate the rule
101
+ # * +symbol+ Name of the rule
102
+ def self.has_rule?(symbol) # :nodoc:
103
+ @has_rule[symbol]
104
+ end
105
+
106
+ # Returns true iif the rule can be evaluated statically
107
+ # * +symbol+ Name of the rule
108
+ def self.has_static_rule?(symbol) # :nodoc:
109
+ static_policy_class.has_rule? symbol
110
+ end
111
+
112
+ # Return true iif the rule can be evaluated dynamically
113
+ # * +symbol+ Name of the rule
114
+ def self.has_dynamic_rule?(symbol) # :nodoc:
115
+ @has_dynamic[symbol]
116
+ end
117
+
118
+ # Get a rule object
119
+ # * +symbol+ Name of the rule
120
+ def self.get_rule(symbol) #:nodoc:
121
+ @my_rules[symbol]
122
+ end
123
+
124
+ # The rule +symbol+ was requested, try to find and load it.
125
+ # Returns a rule object or nil.
126
+ def self.load_rule(symbol) #:nodoc:
127
+ # 1. Have a look in the rule set
128
+ # 2. Maybe the rule is defined for all resources
129
+ # 3. Redirect the rule to the static side
130
+ r = rule_set.get_rule(symbol,static?) ||
131
+ copy_rule_from(symbol,all_resources_policy) ||
132
+ use_static_rule(symbol)
133
+ # Create a method for the rule
134
+ r.extend_class(self) if r
135
+ r
136
+ end
137
+
138
+ # If possible, copies a rule from another policy class.
139
+ # * +symbol+ Name of the rule
140
+ # * +source_policy+ policy class to copy from
141
+ # Returns a rule object or nil.
142
+ def self.copy_rule_from(symbol,source_policy) # :nodoc:
143
+ rule_set.copy_rule_from(symbol,source_policy.rule_set,static?)
144
+ end
145
+
146
+ # If possible, redirects the rule to the static side.
147
+ # Returns a rule object or nil.
148
+ def self.use_static_rule(symbol) #:nodoc:
149
+ if has_static_rule?(symbol)
150
+ @static_only[symbol] = true
151
+ rule_set.create_dynamic_copy(symbol)
152
+ end
153
+ end
154
+
155
+ # Add a rule
156
+ # * +symbol+ rule name
157
+ # * +args+ additional arguments
158
+ # * +block+ code block
159
+ # See AnnotationSecurity::Rule#initialize for details
160
+ #
161
+ def self.add_rule(symbol,*args,&block) #:nodoc:
162
+ rule_set.add_rule(symbol,*args,&block)
163
+ end
164
+
165
+ # Initialize the instance for a user
166
+ # * +user+ user object this policy object is responsible for
167
+ # * +resource+ (optional) usually the resource object will be set using
168
+ # #allowed? or #with_resource
169
+ def initialize(user,resource=nil)
170
+ @user = user
171
+ @resource = resource
172
+ end
173
+
174
+ # Static policy object to evaluate the static rules
175
+ def static_policy # :nodoc:
176
+ @static_policy ||= self.class.static_policy_class.new(@user)
177
+ end
178
+
179
+ # Symbol representing the resource type this policy is responsible for.
180
+ #
181
+ def resource_type # :nodoc:
182
+ self.class.resource_type
183
+ end
184
+
185
+ # Returns true iif the user has the +right+ for +resource_obj+
186
+ # * +right+ symbol
187
+ # * +resource_obj+ resource object to test the right for
188
+ # * +args+ (optional) additional arguments passed when evaluating the right
189
+ # This is not thread safe! Don't share policy objects between different
190
+ # threads (should be no problem though).
191
+ # ==== Example
192
+ # policy.allowed? :show, obj #=> true or false
193
+ def allowed?(right,resource_obj,*args)
194
+ @resource = resource_obj
195
+ __send__(right,*args)
196
+ # rescue
197
+ # raise "#{$!} in #{resource_type} policy " +
198
+ # "during rule #{right} of #{resource_obj} with args [#{args.join(", ")}]"
199
+ end
200
+
201
+ # Sets the resource object and returns self
202
+ # ==== Example
203
+ # policy.with_resource(obj).show? #=> true or false
204
+ def with_resource(resource_obj)
205
+ @resource = resource_obj
206
+ self
207
+ end
208
+
209
+ # Evaluate the rules in static mode.
210
+ # Rules that cannot be evaluated are skipped.
211
+ # * +rules+ array of symbols
212
+ # Throws a SecurityViolationError if a rule fails,
213
+ # returns true if all rules succeed.
214
+ def evaluate_statically(rules) #:nodoc:
215
+ static_policy.evaluate_statically(rules)
216
+ end
217
+
218
+ # Evaluate the rules in dynamic mode.
219
+ # Rules that cannot be evaluated are skipped.
220
+ # * +rules+ array of symbols
221
+ # Throws a SecurityViolationError if a rule fails,
222
+ # returns true if all rules succeed.
223
+ def evaluate_dynamically(rules) #:nodoc:
224
+ rules.each do |rule|
225
+ if self.class.has_dynamic_rule?(rule) && !__send__(rule)
226
+ raise_access_denied(rule)
227
+ end
228
+ end
229
+ true
230
+ end
231
+
232
+ # Evalutates all rules.
233
+ # * +rules+ array of symbols
234
+ # Throws a SecurityViolationError if a rule fails,
235
+ # returns true if all rules succeed.
236
+ def evaluate(rules) #:nodoc:
237
+ rules.each do |rule|
238
+ unless __send__(rule)
239
+ raise_access_denied(rule)
240
+ end
241
+ end
242
+ true
243
+ end
244
+
245
+ # Returns true iif this policy can evaluate this rule
246
+ # * +symbol+ Name of the rule
247
+ def has_rule?(symbol)
248
+ self.class.has_rule? symbol
249
+ end
250
+
251
+ # Returns a rule object or raises an exception.
252
+ # * +symbol+ Name of the rule
253
+ def get_rule!(symbol) # :nodoc:
254
+ get_rule(symbol) or raise_rule_missing(symbol)
255
+ end
256
+
257
+ # Returns a rule object or nil if it does not exist
258
+ # * +symbol+ Name of the rule
259
+ def get_rule(symbol) # :nodoc:
260
+ self.class.get_rule(symbol)
261
+ end
262
+
263
+ # Returns a list of user wrappers for a role.
264
+ # See #all_for_role for details.
265
+ # * +symbol+ Name of the role
266
+ # * +require_user+ (boolean) Indicating if the rule that requested the roles
267
+ # requires a user for evaluation. If @user is nil and +require_user+ is
268
+ # true, an empty array is returned, which will make the rule fail
269
+ # immediately. If +require_user+ is false, an array containing nil is
270
+ # returned and the rule will be evaluated once (with +nil+ as current user).
271
+ def user_roles(symbol,require_user) # :nodoc:
272
+ return [nil] if @user.nil? && !require_user
273
+ # AnnotationSecurity::UserWrapper.all_for_role(@user,symbol)
274
+ all_for_role(@user,symbol)
275
+ end
276
+
277
+ # Return objects for the requested role. The role(s) will be
278
+ # determined with sending user.as_'role'.
279
+ # (Normally a user has a role only once, however it will work when he
280
+ # has many roles of the same kind as well)
281
+ def all_for_role(user,role_name) # :nodoc:
282
+ return [] if user.nil?
283
+ # is it possible that user is a role? if so, implement conversion to user
284
+ return [user] if role_name.nil?
285
+ roles = user.__send__("as_#{role_name}")
286
+ return [] if roles.blank?
287
+ roles = [roles] unless roles.is_a?(Array)
288
+ roles.compact
289
+ end
290
+
291
+ # Evaluate a rule that is defined with a proc
292
+ # * +symbol+ Name of the rule
293
+ # * +user+ user object that has to fulfill the rule
294
+ # * +args+ List of additional arguments
295
+ def evaluate_rule(symbol,user,args) # :nodoc:
296
+ get_rule!(symbol).evaluate(self,user,@resource,*args)
297
+ end
298
+
299
+ # Invoked by ruby when this object recieved a message it cannot handle.
300
+ # * +symbol+ Name of the method
301
+ # * +args+ Any arguments that were passed
302
+ def method_missing(symbol,*args) # :nodoc:
303
+
304
+ # If possible, create the missing method and send it again
305
+ if add_method_for_rule(symbol)
306
+ # method was created, try again
307
+ return __send__(symbol,*args)
308
+ end
309
+
310
+ # this will fail and an exception will be raised
311
+ get_rule!(symbol)
312
+ end
313
+
314
+ # Return true if it was possible to create a method for the rule
315
+ # * +symbol+ Name of the method
316
+ def add_method_for_rule(symbol) # :nodoc:
317
+ # Check if symbol is a known rule. If available, it will be loaded and
318
+ # a method will be created automatically.
319
+ if has_rule?(symbol)
320
+ # method was created
321
+ return true
322
+ else
323
+ # Remove prefix or suffix if available
324
+ cleaned = AnnotationSecurity::Utils.method_body(symbol)
325
+ if cleaned
326
+ # Redirect to the cleaned method
327
+ self.class.class_eval "def #{symbol}(*args); #{cleaned}(*args); end"
328
+ return true
329
+ end
330
+ end
331
+ # Hopeless
332
+ false
333
+ end
334
+
335
+ # Raises an error saying that a rule could not be found this policy class
336
+ # * +symbol+ Name of the rule
337
+ def raise_rule_missing(symbol) # :nodoc:
338
+ raise AnnotationSecurity::RuleNotFoundError.for_rule(symbol,self.class)
339
+ end
340
+
341
+ def raise_access_denied(rule) #:nodoc:
342
+ SecurityContext.raise_access_denied(rule,resource_type,@resource)
343
+ end
344
+
345
+ end
@@ -0,0 +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
+
76
+ end
@@ -0,0 +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?}
21
+ end