annotation_security 1.0.1

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