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,36 +1,36 @@
1
- #
2
- # = lib/annotation_security/manager/resource_manager.rb
3
- #
4
-
5
- # Needed to find resource objects when only their id is known
6
- #
7
- class AnnotationSecurity::ResourceManager # :nodoc:
8
-
9
- @classes = {}
10
-
11
- def self.add_resource_class(res_type,klass)
12
- @classes.delete_if { |k,v| v == klass }
13
- @classes[res_type] = klass
14
- end
15
-
16
- def self.get_resource_class(res_type)
17
- @classes[res_type]
18
- end
19
-
20
- unless RAILS_ENV == 'production'
21
- def self.get_resource_class(res_type)
22
- c = @classes[res_type]
23
- unless c
24
- res_type.to_s.camelize.constantize # load the class
25
- c = @classes[res_type]
26
- end
27
- c
28
- end
29
- end
30
-
31
- # Call get_resource of the class that is registered for +res_type+
32
- def self.get_resource(res_type,object)
33
- c = get_resource_class(res_type)
34
- c ? c.get_resource(object) : object
35
- end
36
- end
1
+ #
2
+ # = lib/annotation_security/manager/resource_manager.rb
3
+ #
4
+
5
+ # Needed to find resource objects when only their id is known
6
+ #
7
+ class AnnotationSecurity::ResourceManager # :nodoc:
8
+
9
+ @classes = {}
10
+
11
+ def self.add_resource_class(res_type,klass)
12
+ @classes.delete_if { |k,v| v == klass }
13
+ @classes[res_type] = klass
14
+ end
15
+
16
+ def self.get_resource_class(res_type)
17
+ @classes[res_type]
18
+ end
19
+
20
+ unless Rails.env == 'production'
21
+ def self.get_resource_class(res_type)
22
+ c = @classes[res_type]
23
+ unless c
24
+ res_type.to_s.camelize.constantize # load the class
25
+ c = @classes[res_type]
26
+ end
27
+ c
28
+ end
29
+ end
30
+
31
+ # Call get_resource of the class that is registered for +res_type+
32
+ def self.get_resource(res_type,object)
33
+ c = get_resource_class(res_type)
34
+ c ? c.get_resource(object) : object
35
+ end
36
+ end
@@ -1,88 +1,88 @@
1
- #
2
- # = lib/annotation_security/manager/right_loader.rb
3
- #
4
-
5
- # = AnnotationSecurity::RightLoader
6
- # Contains the right loader class, which is responsible for loading
7
- # right definitions for resources. Load rights from a yaml file or a hash.
8
- #
9
- # == Example YAML
10
- #
11
- # The file <tt>config/security/rights.yml</tt> inside a rails app
12
- # might look like this:
13
- # picture:
14
- # # a user may show a picture if he fulfils the 'related'-relation
15
- # show: if related
16
- # comment:
17
- # # you have to be logged in to view comments
18
- # show: if logged_in
19
- # user:
20
- # # like in ruby, 'unless' is equivalent to 'if not'
21
- # register: unless logged_in
22
- # delete: if administrator # comments are also possible behind a line
23
- # user_content:
24
- # # all rights of 'user_content' are defined for 'picture' and 'comment' too
25
- # applies_to: picture, comment
26
- # create: if logged_in
27
- # edit: if owner
28
- # delete: if owner or administrator
29
- # The file can be loaded via <code>AnnotationSecurity#load_rights('rights')</code>.
30
- #
31
- # A right's condition can use the keywords +if+, +unless+, +and+, +or+ and
32
- # +not+, brackets, other rights and all of the resource's relations
33
- # (see AnnotationSecurity::RelationLoader). For better readability you may
34
- # add the prefixes +may+, +is+, +can+ or +has+,
35
- # or append one of the suffixes +for+, +in+, +of+ or +to+.
36
- #
37
- # user_content:
38
- # edit: if is_owner_of
39
- # delete: if may_edit or is_administrator
40
- #
41
- # However, it is recommended to use this feature sparingly.
42
- #
43
- class AnnotationSecurity::RightLoader
44
-
45
- # Goes through all resources of +hash+ and load the defined rights.
46
- #
47
- def self.define_rights(hash) # :nodoc:
48
- if hash
49
- hash.each_pair do |resource_class, rights|
50
- new(resource_class).define_rights(rights)
51
- end
52
- end
53
- end
54
-
55
- # An instance of RightLoader is responsible for loading the rights of a
56
- # resource class.
57
- #
58
- def initialize(resource) # :nodoc:
59
- @factory = AnnotationSecurity::PolicyManager.policy_factory(resource)
60
- end
61
-
62
- # Goes through all rights in +hash+ and creates rules for all policies these
63
- # rights apply to.
64
- #
65
- def define_rights(hash) # :nodoc:
66
- factories = extract_applies_to(hash) << @factory
67
- hash.each_pair do |right,condition|
68
- # Important: set the :right-flag to activate automatic detection of
69
- # the other flags (static,dynamic,require_user)
70
- factories.each { |f| f.add_rule(right,:right,condition) }
71
- end
72
- end
73
-
74
- private
75
-
76
- # Looks for the key 'applies_to', which is no right but a command to apply
77
- # all rights of the current resource class to the resource classes listed
78
- # in the value.
79
- #
80
- def extract_applies_to(hash)
81
- applies_to = hash.delete('applies_to') || hash.delete(:applies_to)
82
- return [] if applies_to.blank?
83
- applies_to = [applies_to] if applies_to.is_a? String
84
- applies_to.collect{ |r| r.split(',') }.flatten.
85
- collect{ |r| AnnotationSecurity::PolicyManager.policy_factory(r.strip) }
86
- end
87
-
1
+ #
2
+ # = lib/annotation_security/manager/right_loader.rb
3
+ #
4
+
5
+ # = AnnotationSecurity::RightLoader
6
+ # Contains the right loader class, which is responsible for loading
7
+ # right definitions for resources. Load rights from a yaml file or a hash.
8
+ #
9
+ # == Example YAML
10
+ #
11
+ # The file <tt>config/security/rights.yml</tt> inside a rails app
12
+ # might look like this:
13
+ # picture:
14
+ # # a user may show a picture if he fulfils the 'related'-relation
15
+ # show: if related
16
+ # comment:
17
+ # # you have to be logged in to view comments
18
+ # show: if logged_in
19
+ # user:
20
+ # # like in ruby, 'unless' is equivalent to 'if not'
21
+ # register: unless logged_in
22
+ # delete: if administrator # comments are also possible behind a line
23
+ # user_content:
24
+ # # all rights of 'user_content' are defined for 'picture' and 'comment' too
25
+ # applies_to: picture, comment
26
+ # create: if logged_in
27
+ # edit: if owner
28
+ # delete: if owner or administrator
29
+ # The file can be loaded via <code>AnnotationSecurity#load_rights('rights')</code>.
30
+ #
31
+ # A right's condition can use the keywords +if+, +unless+, +and+, +or+ and
32
+ # +not+, brackets, other rights and all of the resource's relations
33
+ # (see AnnotationSecurity::RelationLoader). For better readability you may
34
+ # add the prefixes +may+, +is+, +can+ or +has+,
35
+ # or append one of the suffixes +for+, +in+, +of+ or +to+.
36
+ #
37
+ # user_content:
38
+ # edit: if is_owner_of
39
+ # delete: if may_edit or is_administrator
40
+ #
41
+ # However, it is recommended to use this feature sparingly.
42
+ #
43
+ class AnnotationSecurity::RightLoader
44
+
45
+ # Goes through all resources of +hash+ and load the defined rights.
46
+ #
47
+ def self.define_rights(hash) # :nodoc:
48
+ if hash
49
+ hash.each_pair do |resource_class, rights|
50
+ new(resource_class).define_rights(rights)
51
+ end
52
+ end
53
+ end
54
+
55
+ # An instance of RightLoader is responsible for loading the rights of a
56
+ # resource class.
57
+ #
58
+ def initialize(resource) # :nodoc:
59
+ @factory = AnnotationSecurity::PolicyManager.policy_factory(resource)
60
+ end
61
+
62
+ # Goes through all rights in +hash+ and creates rules for all policies these
63
+ # rights apply to.
64
+ #
65
+ def define_rights(hash) # :nodoc:
66
+ factories = extract_applies_to(hash) << @factory
67
+ hash.each_pair do |right,condition|
68
+ # Important: set the :right-flag to activate automatic detection of
69
+ # the other flags (static,dynamic,require_user)
70
+ factories.each { |f| f.add_rule(right,:right,condition) }
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # Looks for the key 'applies_to', which is no right but a command to apply
77
+ # all rights of the current resource class to the resource classes listed
78
+ # in the value.
79
+ #
80
+ def extract_applies_to(hash)
81
+ applies_to = hash.delete('applies_to') || hash.delete(:applies_to)
82
+ return [] if applies_to.blank?
83
+ applies_to = [applies_to] if applies_to.is_a? String
84
+ applies_to.collect{ |r| r.split(',') }.flatten.
85
+ collect{ |r| AnnotationSecurity::PolicyManager.policy_factory(r.strip) }
86
+ end
87
+
88
88
  end
@@ -1,345 +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
- Class.new(self).tap 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
-
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
+ Class.new(self).tap 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
345
  end