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,142 +1,142 @@
1
- #
2
- # = lib/annotation_security/utils.rb
3
- #
4
- # Provides some methods that are needed at several locations in the plug-in.
5
- #
6
-
7
- class AnnotationSecurity::Utils # :nodoc:
8
-
9
- PREFIXES = /\A(may|is|can|has)_/
10
- SUFFIXES = /(_(for|in|of|to)|\?)\Z/
11
-
12
- # Removes pre- and suffixes from +method+,
13
- # returns +nil+ if no change was made.
14
- #
15
- def self.method_body(method)
16
- body = method.to_s.gsub(PREFIXES,'').gsub(SUFFIXES,'')
17
- method.to_s == body ? nil : body
18
- end
19
-
20
- # Parses a description string
21
- # * +description+ description of a controller action
22
- # * +allow_binding+ if false, an exception is raised if the description
23
- # contains a variable
24
- # Returns right, resource and binding
25
- #
26
- def self.parse_description(description,allow_binding=false)
27
- ActionAnnotation::Utils.parse_description(description,allow_binding)
28
- end
29
-
30
- # Parses arguments provided to #apply_policy or #allowed? and returns
31
- # [ [:action, :resource_type, resource || nil], ... ]
32
- #
33
- # See SecurityContext#allowed? for details.
34
- #
35
- # Each element of the result can be send to a policy using
36
- # policy_of_res_type.allowed?(rule, resource)
37
- # or
38
- # policy_of_res_type.static_policy.allowed?(rule, nil)
39
- #
40
- # Raises ArgumentError if args could not be parsed.
41
- #
42
- def self.parse_policy_arguments(args)
43
- if args.first.is_a? String
44
- hash = AnnotationSecurity::Utils.parse_description(args.first)
45
- elsif args.first.is_a? Hash
46
- hash = args.first
47
- end
48
- if hash
49
- action = hash.delete(:action) || hash.delete('action')
50
- resource = hash.delete(:resource) || hash.delete('resource')
51
- unless resource.__is_resource?
52
- resource_type = resource
53
- resource = nil
54
- end
55
- resource_type ||= hash.delete(:resource_type)
56
- resource_type ||= resource ? resource.resource_type : nil
57
- a = [action, resource_type]
58
- a << resource if resource
59
- args = a + args[1..-1]
60
- end
61
-
62
- args << :all_resources unless args.size > 1
63
-
64
- action, resource = args
65
-
66
- if resource.__is_resource?
67
- args = [action, resource.resource_type] + args[1..-1]
68
- end
69
- # if args.size > 2 && args.third == nil
70
- # raise ArgumentError, "Did not expect nil as resource"
71
- # end
72
- args
73
- end
74
-
75
- # returns resource type and resource object without action
76
- # expects [resource object], [resource type], or both
77
- def self.parse_resource_arguments(args)
78
- parse_policy_arguments([:r]+args)[1..2]
79
- end
80
-
81
- # Returns controller, action, objects and parameters
82
- def self.parse_action_args(args)
83
- controller = parse_controller(args.first)
84
- action = args.second.to_sym
85
-
86
- objects = args.third || []
87
- objects = [objects] unless objects.is_a? Array
88
- prepare_objects_resources(controller, objects)
89
-
90
- params = args.fourth || {}
91
- prepare_params_resources(controller, params)
92
-
93
- objects += params.values
94
-
95
- objects = objects.select { |o| o and o.__is_resource? }
96
- return [controller, action, objects, params]
97
- end
98
-
99
- # Try to find the controller class from a name.
100
- # Looks for [name](s)Controller.
101
- #
102
- # parse_controller :welcome #=> WelcomeController
103
- # parse_controller :user # => UsersController
104
- #
105
- def self.parse_controller(controller) # :nodoc:
106
- begin
107
- "#{controller.to_s.camelize}Controller".constantize
108
- rescue NameError
109
- "#{controller.to_s.pluralize.camelize}Controller".constantize
110
- end
111
- rescue NameError
112
- raise NameError, "Controller '#{controller}' was not found"
113
- end
114
-
115
- # if there are non-resources in objects, use the values to get resources
116
- # from the controllers default resource type
117
- #
118
- def self.prepare_objects_resources(controller, objects)
119
- res_type = controller.default_resource
120
- objects.collect! do |o|
121
- if o.__is_resource?
122
- o
123
- else
124
- AnnotationSecurity::ResourceManager.get_resource(res_type, o)
125
- end
126
- end
127
- end
128
-
129
- # if there are non-resources in objects, use the values to get resources
130
- # assuming the keys are the resource types (:id is defalut resource)
131
- #
132
- def self.prepare_params_resources(controller, params)
133
- params.each do |k, v|
134
- unless v.__is_resource?
135
- res_type = k == :id ? controller.default_resource : k
136
- v = AnnotationSecurity::ResourceManager.get_resource(res_type, v)
137
- params[k] = v
138
- end
139
- end
140
- end
141
-
1
+ #
2
+ # = lib/annotation_security/utils.rb
3
+ #
4
+ # Provides some methods that are needed at several locations in the plug-in.
5
+ #
6
+
7
+ class AnnotationSecurity::Utils # :nodoc:
8
+
9
+ PREFIXES = /\A(may|is|can|has)_/
10
+ SUFFIXES = /(_(for|in|of|to)|\?)\Z/
11
+
12
+ # Removes pre- and suffixes from +method+,
13
+ # returns +nil+ if no change was made.
14
+ #
15
+ def self.method_body(method)
16
+ body = method.to_s.gsub(PREFIXES,'').gsub(SUFFIXES,'')
17
+ method.to_s == body ? nil : body
18
+ end
19
+
20
+ # Parses a description string
21
+ # * +description+ description of a controller action
22
+ # * +allow_binding+ if false, an exception is raised if the description
23
+ # contains a variable
24
+ # Returns right, resource and binding
25
+ #
26
+ def self.parse_description(description,allow_binding=false)
27
+ ActionAnnotation::Utils.parse_description(description,allow_binding)
28
+ end
29
+
30
+ # Parses arguments provided to #apply_policy or #allowed? and returns
31
+ # [ [:action, :resource_type, resource || nil], ... ]
32
+ #
33
+ # See SecurityContext#allowed? for details.
34
+ #
35
+ # Each element of the result can be send to a policy using
36
+ # policy_of_res_type.allowed?(rule, resource)
37
+ # or
38
+ # policy_of_res_type.static_policy.allowed?(rule, nil)
39
+ #
40
+ # Raises ArgumentError if args could not be parsed.
41
+ #
42
+ def self.parse_policy_arguments(args)
43
+ if args.first.is_a? String
44
+ hash = AnnotationSecurity::Utils.parse_description(args.first)
45
+ elsif args.first.is_a? Hash
46
+ hash = args.first
47
+ end
48
+ if hash
49
+ action = hash.delete(:action) || hash.delete('action')
50
+ resource = hash.delete(:resource) || hash.delete('resource') || args.delete_at(1)
51
+ unless resource.__is_resource?
52
+ resource_type = resource
53
+ resource = nil
54
+ end
55
+ resource_type ||= hash.delete(:resource_type)
56
+ resource_type ||= resource ? resource.resource_type : nil
57
+ a = [action, resource_type]
58
+ a << resource if resource
59
+ args = a + args[1..-1]
60
+ end
61
+
62
+ args << :all_resources unless args.size > 1
63
+
64
+ action, resource = args
65
+
66
+ if resource.__is_resource?
67
+ args = [action, resource.resource_type] + args[1..-1]
68
+ end
69
+ # if args.size > 2 && args.third == nil
70
+ # raise ArgumentError, "Did not expect nil as resource"
71
+ # end
72
+ args
73
+ end
74
+
75
+ # returns resource type and resource object without action
76
+ # expects [resource object], [resource type], or both
77
+ def self.parse_resource_arguments(args)
78
+ parse_policy_arguments([:r]+args)[1..2]
79
+ end
80
+
81
+ # Returns controller, action, objects and parameters
82
+ def self.parse_action_args(args)
83
+ controller = parse_controller(args.first)
84
+ action = args.second.to_sym
85
+
86
+ objects = args.third || []
87
+ objects = [objects] unless objects.is_a? Array
88
+ prepare_objects_resources(controller, objects)
89
+
90
+ params = args.fourth || {}
91
+ prepare_params_resources(controller, params)
92
+
93
+ objects += params.values
94
+
95
+ objects = objects.select { |o| o and o.__is_resource? }
96
+ return [controller, action, objects, params]
97
+ end
98
+
99
+ # Try to find the controller class from a name.
100
+ # Looks for [name](s)Controller.
101
+ #
102
+ # parse_controller :welcome #=> WelcomeController
103
+ # parse_controller :user # => UsersController
104
+ #
105
+ def self.parse_controller(controller) # :nodoc:
106
+ begin
107
+ "#{controller.to_s.camelize}Controller".constantize
108
+ rescue NameError
109
+ "#{controller.to_s.pluralize.camelize}Controller".constantize
110
+ end
111
+ rescue NameError
112
+ raise NameError, "Controller '#{controller}' was not found"
113
+ end
114
+
115
+ # if there are non-resources in objects, use the values to get resources
116
+ # from the controllers default resource type
117
+ #
118
+ def self.prepare_objects_resources(controller, objects)
119
+ res_type = controller.default_resource
120
+ objects.collect! do |o|
121
+ if o.__is_resource?
122
+ o
123
+ else
124
+ AnnotationSecurity::ResourceManager.get_resource(res_type, o)
125
+ end
126
+ end
127
+ end
128
+
129
+ # if there are non-resources in objects, use the values to get resources
130
+ # assuming the keys are the resource types (:id is defalut resource)
131
+ #
132
+ def self.prepare_params_resources(controller, params)
133
+ params.each do |k, v|
134
+ unless v.__is_resource?
135
+ res_type = k == :id ? controller.default_resource : k
136
+ v = AnnotationSecurity::ResourceManager.get_resource(res_type, v)
137
+ params[k] = v
138
+ end
139
+ end
140
+ end
141
+
142
142
  end
@@ -1,589 +1,588 @@
1
- #
2
- # = lib/security_context.rb
3
- #
4
- # Contains the SecurityContext
5
- require 'active_support'
6
-
7
- # = SecurityContext
8
- #
9
- # The SecurityContext provides methods for all security concerns of
10
- # the current request.
11
- #
12
- # For every request, it has to be initialized using #current_user=. It is
13
- # recommended to do this in a security filter, which can be used to catch
14
- # AnnotationSecurityExceptions as well.
15
- #
16
- # The SecurityContext is implemented as a singleton for the current thread.
17
- # Thus, all instance methods can be send to the class as well.
18
- #
19
- class SecurityContext
20
-
21
- # Returns current security context
22
- #
23
- def self.current
24
- Thread.current[:security_context]
25
- end
26
-
27
- # At the begin of a request, the security context will be initialized for the
28
- # current controller.
29
- #
30
- def self.initialize(controller) # :nodoc:
31
- load(new(controller))
32
- end
33
-
34
- # As the security context is a singleton bound to the current thread,
35
- # it will not be available in other threads. The following example shows
36
- # how to use the security context inside of a spawn block
37
- #
38
- # copy = SecurityContext.copy
39
- # spawn do
40
- # SecurityContext.load(copy)
41
- # begin
42
- # # ...
43
- # rescue SecurityViolationError
44
- # puts 'Security was violated'
45
- # end
46
- # end
47
- #
48
- def self.load(sec_context)
49
- Thread.current[:security_context] = sec_context
50
- end
51
-
52
- if Rails.env == 'development'
53
- # Disables all security checkings.
54
- # Is only available in development mode.
55
- def self.ignore_security!
56
- security_methods.each do |method|
57
- class_eval "def self.#{method}(*args); true; end"
58
- end
59
- end
60
- end
61
-
62
- ## ===========================================================================
63
- ## Instance
64
-
65
- # Initialize context for the given controller
66
- #
67
- def initialize(controller) # :nodoc:
68
- super()
69
-
70
- @controller = controller
71
-
72
- # initialize rule
73
-
74
- # rules that are not bound to any source,
75
- # will be triggered by model observer
76
- @context_rules = new_rules_hash
77
- @valid_objects = new_valid_objects_hash
78
-
79
- # rules bound to request param
80
- @param_rules = new_bound_rules_hash
81
- @param_valid_objects = new_bound_valid_objects_hash
82
-
83
- # rules bound to variable
84
- @var_rules = new_bound_rules_hash
85
- @var_valid_objects = new_bound_valid_objects_hash
86
-
87
- # Hash with all required policies
88
- @policies = new_policy_hash
89
- end
90
-
91
- # Sets the current user. This has to be done in a before or around filter,
92
- # *before* entering the action. Elsewise, the user will be interpreted as
93
- # not being logged in. Once set, the current user cannot be changed.
94
- #
95
- def credential=(user)
96
- if @cred_set
97
- raise AnnotationSecurity::AnnotationSecurityError,
98
- "Credential already set for this request"
99
- end
100
- @cred_set = true
101
- @credential = user
102
- end
103
-
104
- # Get the current credential
105
- def credential
106
- @credential
107
- end
108
-
109
- alias current_credential= credential=
110
- alias current_credential credential
111
-
112
- # Creates a copy of the current security context.
113
- # See #load for more information.
114
- def copy
115
- self.class.new(@controller).tap { |sc| sc.credential = credential }
116
- end
117
-
118
- # Will be set if an security exception was catched by the security filter
119
- def security_exception=(ex) # :nodoc:
120
- @security_exception = ex
121
- @controller.security_exception = ex
122
- end
123
-
124
- # If the action was aborted due to a security exception, this returns the
125
- # exception that was raised. Returns nil if no exception occurred.
126
- #
127
- def security_exception
128
- @security_exception
129
- end
130
-
131
- # See eval_with_security.
132
- def send_with_security(rules, obj, msg, *args, &proc)
133
- eval_with_security(rules) { obj.send(msg, *args, &proc) }
134
- end
135
-
136
- # Evaluates the given block, additionally using the given rules.
137
- # rules == [ { :action => action, :resource => res_type, :source => binding }, ...]
138
- # action and res_type should be symbols, binding is optional
139
- #
140
- def eval_with_security(rules)
141
- install_rules(rules)
142
-
143
- apply_rules_before_action
144
-
145
- result = yield
146
-
147
- apply_rules_after_action
148
-
149
- result
150
- rescue AnnotationSecurity::SecurityError
151
- SecurityContext.security_exception = $!
152
- raise $!
153
- ensure
154
- uninstall_rules(rules)
155
- result
156
- end
157
-
158
- def apply_rules_before_action # :nodoc:
159
- # apply static rules before entering the action
160
- apply_static_rules
161
- # bindings may apply to parameters, try to check them too
162
- apply_param_rules
163
- apply_var_rules
164
- end
165
-
166
- def apply_rules_after_action # :nodoc:
167
- # check again, bindings may have been changed
168
- apply_var_rules
169
- end
170
-
171
- # Returns true iif the operation defined by +policy_args+ is allowed.
172
- #
173
- # The following calls to \#allowed? are allowed:
174
- #
175
- # allowed? :show, :resource, @resource
176
- # # => true if the current user has the right to show @resource,
177
- # # which belongs to the :resource resource-class
178
- #
179
- # In case of model objects or other classes which implement a #resource_type
180
- # method the the second argument may be ommited
181
- #
182
- # allowed? :show, @resource
183
- # # equivalent to the above call if @resource.resource_type == :resource
184
- #
185
- # A policy description used as a controller annotation may also be to check
186
- # a right
187
- #
188
- # allowed? "show resource", @resource
189
- # # => true if the current user has the right "show resource" for @resource
190
- #
191
- # A policy may also be applied without an object representing the context:
192
- #
193
- # allowed? :show, :resource
194
- # # => true if the current may show resources.
195
- #
196
- # This will only check system and pretest rules. The result +true+ does not
197
- # mean that the user may show all resources. However, a +false+ indicates
198
- # that the user is not allowed to show any resources.
199
- #
200
- # If the resource type is omitted as well, only rules defined for all
201
- # resources can be tested. See RelationLoader#all_resources for details.
202
- #
203
- # allowed? :administrate
204
- # # => true if the user is allowed to administrate all resources.
205
- #
206
- def allowed?(*policy_args)
207
- policy_args = AnnotationSecurity::Utils.parse_policy_arguments(policy_args)
208
- __allowed?(*policy_args)
209
- end
210
-
211
- # Equivalent to allowed?; is? is provided for better readability.
212
- #
213
- # SecurityContext.allowed? :logged_in
214
- # vs
215
- # SecurityContext.is? :logged_in
216
- #
217
- alias is? allowed?
218
-
219
- # Raises a SecurityViolationError if the rule defined by +policy_args+ is not
220
- # allowed. See allowed? for details.
221
- #
222
- def apply_rule(*args)
223
- self.class.raise_access_denied(*args) unless allowed?(*args)
224
- end
225
-
226
- # Checks the rules of an other action. Note that rules that are bound to a
227
- # variable can not be checked.
228
- #
229
- # ==== Parameters
230
- # * +controller+ Symbol representing the controller, like :resource
231
- # * +action+ The called action, like :update
232
- # * +objects+ (optional) List of objects that will be relevant for that action.
233
- # * +params+ (optional) Hash of the passed parameters, like :id => 1.
234
- #
235
- # ==== Examples
236
- #
237
- # Checks static and pretest rules.
238
- # allow_action? :resource, :create
239
- # # => true if the current user may execute ResourcesController#create
240
- #
241
- # Checks static, pretest and context rules
242
- # allow_action? :resource, :edit, [@resource]
243
- # # => true if the current user may execute ResourcesController#edit,
244
- # # assuming that @resource will be used in that action
245
- #
246
- # Checks static, pretest and context rules and all rules that are bound
247
- # to :id.
248
- # allow_action? :resource, :edit, [@resource], {:id => 4}
249
- # # => true if the current user may execute ResourcesController#edit,
250
- # # assuming that @resource will be used in that action
251
- #
252
- def allow_action?(*args) # :nodoc:
253
-
254
- controller, action, objects, params =
255
- AnnotationSecurity::Utils.parse_action_args(args)
256
-
257
- # var rules are ignored here
258
- context_rules, param_rules, _ = get_rule_set(controller, action)
259
-
260
- # check static rules
261
- evaluate_statically(context_rules)
262
-
263
- # check context rules for all objects
264
- objects.each do |o|
265
- res_type = o.resource_type
266
- evaluate_context_rules(context_rules, res_type, o)
267
- end
268
-
269
- evaluate_bound_rules_for_params(param_rules, params)
270
-
271
- true
272
- rescue SecurityViolationError
273
- return false
274
- end
275
-
276
- # Applies all system and pretest rules of the current action.
277
- # Raises a SecurityViolationError if a rule is violated.
278
- #
279
- def apply_static_rules # :nodoc:
280
- evaluate_statically(@context_rules)
281
- end
282
-
283
- def apply_param_rules # :nodoc:
284
- evaluate_bound_rules(@param_rules, @param_valid_objects)
285
- end
286
-
287
- def apply_var_rules # :nodoc:
288
- evaluate_bound_rules(@var_rules, @var_valid_objects)
289
- end
290
-
291
- # Applies all rules of the current action to the resource defined by
292
- # +resource_args+. Raises a SecurityViolationError if a rule is
293
- # violated.
294
- #
295
- def apply_context_rules(*res_args) # :nodoc:
296
- restype, res = AnnotationSecurity::Utils.parse_resource_arguments(res_args)
297
- evaluate_context_rules(@context_rules, restype, res)
298
- end
299
-
300
- alias apply_rules apply_context_rules # :nodoc:
301
-
302
- # Call if a resource object was touched during an action. Will be called
303
- # automatically for model objects.
304
- #
305
- # Applies all rules that are currently active to the resource defined by
306
- # +resource_args+. Raises a SecurityViolationError if a rule is
307
- # violated.
308
- #
309
- # ==== Usage
310
- # observe :resource, @resource
311
- # where <tt>:resource</tt> is the resource type @resource belongs to, or
312
- # observe @resource
313
- # which is equivalent if <tt>@resource.resource_name == :resource</tt>
314
- #
315
- def observe(*resource_args)
316
- apply_context_rules(*resource_args)
317
- end
318
-
319
- # Raise a SecurityViolationError.
320
- # See allowed? for details on +policy_args+.
321
- #
322
- def self.raise_access_denied(*policy_args)
323
- log_access_denied(policy_args)
324
- raise SecurityViolationError.access_denied(credential,*policy_args)
325
- end
326
-
327
- # Activates access logging for the current request.
328
- #
329
- def log!(&proc)
330
- @enable_logging = true
331
- @log = proc || Proc.new do |result, action, res_type, resource|
332
- result = result ? 'ALLOWED' : 'REFUSED' unless result.is_a? String
333
- msg = "%-8s %-10s %-16s %s" % [result, action, res_type, resource]
334
- puts msg
335
- end
336
- end
337
-
338
- def log_access_denied(policy_args) # :nodoc:
339
- @log.call('DENIED!', *policy_args) if @enable_logging
340
- end
341
-
342
- protected
343
-
344
- def log_access_check(*policy_args)
345
- @log.call(*policy_args) if @enable_logging
346
- end
347
-
348
- private
349
-
350
- # data =========================================================================
351
-
352
- # { binding => { :res_type1 => [:action1, ...], ... }, ... }
353
- def new_bound_rules_hash() # :nodoc:
354
- Hash.new { |h,k| h[k] = new_rules_hash }
355
- end
356
-
357
- # { :res_type1 => [:action1, ...], ... }
358
- def new_rules_hash() # :nodoc:
359
- Hash.new { |h,k| h[k] = [] }
360
- end
361
-
362
- # { binding => [object1, ...], ...}
363
- def new_bound_valid_objects_hash() # :nodoc:
364
- Hash.new { |h,k| h[k] = [] }
365
- end
366
-
367
- # { :res_type1 => { :action1 => [object1, ...], ...}, ...}
368
- def new_valid_objects_hash() # :nodoc:
369
- Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
370
- end
371
-
372
- # {:res_type1 => policy1, ...}
373
- def new_policy_hash() # :nodoc:
374
- Hash.new { |h,k| h[k] = new_policy(k) }
375
- end
376
-
377
- def new_policy(resource_type) # :nodoc:
378
- AnnotationSecurity::PolicyManager.create_policy(resource_type, credential)
379
- end
380
-
381
- # Get the policy for a resource type from the cache
382
- def policy(res_type) # :nodoc:
383
- @policies[res_type]
384
- end
385
-
386
- # rules management =============================================================
387
-
388
- def install_rules(rules, rule_set=nil, controller=@controller.class)
389
- rules.each { |rule| install_rule rule, rule_set, controller }
390
- end
391
-
392
- def install_rule(rule, rule_set, controller)
393
- rule_list(rule, rule_set, controller) << rule[:action]
394
- end
395
-
396
- def uninstall_rules(rules, rule_set=nil, controller=@controller.class)
397
- rules.each { |rule| uninstall_rule rule, rule_set, controller }
398
- end
399
-
400
- def uninstall_rule(rule, rule_set, controller)
401
- list = rule_list(rule, rule_set, controller)
402
- i = list.index(rule[:action])
403
- list.delete_at(i) if i
404
- end
405
-
406
- def rule_list(rule, rule_set, controller)
407
- rule_set ||= [@context_rules, @param_rules, @var_rules]
408
- resource = rule[:resource] || controller.default_resource
409
- source = rule[:source]
410
- if source.nil?
411
- list = rule_set.first[resource]
412
- elsif source.is_a? Symbol
413
- list = rule_set.second[source][resource]
414
- else
415
- list = rule_set.third[source][resource]
416
- end
417
- list
418
- end
419
-
420
- # returns rule set for other controller actions
421
- def get_rule_set(controller, action) # :nodoc:
422
- @rule_sets ||= Hash.new { |h,k| h[k] = {} }
423
- rule_set = @rule_sets[controller][action]
424
- unless rule_set
425
- rule_set = [new_rules_hash, new_bound_rules_hash, new_bound_rules_hash]
426
- rules = controller.descriptions_of action
427
- install_rules rules, rule_set, controller
428
- @rule_sets[controller][action] = rule_set
429
- end
430
- rule_set
431
- end
432
-
433
- # rule evaluation ==============================================================
434
-
435
- # Evaluate the rules statically, skips all rules that are static only.
436
- # * +rules+ a Hash like {:resource_type => [:right1, :right2]}
437
- def evaluate_statically(rules) # :nodoc:
438
- # rules == { :resource1 => [:right1, ...], ... }
439
- rules.each_pair do |resource_type,rights|
440
- policy(resource_type).evaluate_statically(rights)
441
- end
442
- end
443
-
444
- # Checks bound rules. Evaluates the bindings, on success adds objects
445
- # to valid objects.
446
- # rules == { binding1 => { :res_type1 => [:action1, ...], ... }, ... }
447
- # valid_objects == { binding1 => [object1, ...], ... }
448
- def evaluate_bound_rules(rules, valid_objects) # :nodoc:
449
- evaluate_bound_rules_with_binding(rules, valid_objects) do |binding|
450
- @controller.values_of_source(binding)
451
- end
452
- end
453
-
454
- # Checks bound rules using the values from params
455
- # rules == { binding1 => { :res_type1 => [:action1, ...], ... }, ... }
456
- # params == { binding1 => object1, ... }
457
- def evaluate_bound_rules_for_params(rules, params) # :nodoc:
458
- valid_objects = new_bound_valid_objects_hash
459
- evaluate_bound_rules_with_binding(rules, valid_objects) do |binding|
460
- values = params[binding]
461
- values.is_a?(Array) ? values : [values]
462
- end
463
- end
464
-
465
- def evaluate_bound_rules_with_binding(rules, valid_objects, &proc) # :nodoc:
466
- rules.each_key do |binding|
467
- value_ids = proc.call(binding)
468
- rules[binding].each_key do |res_type|
469
- values = value_ids.collect do |id|
470
- AnnotationSecurity::ResourceManager.get_resource res_type, id
471
- end
472
- values.compact!
473
- values_of_res_type = values - valid_objects[binding]
474
- values_of_res_type.each do |resource|
475
- evaluate_rules(rules[binding][res_type],
476
- res_type,
477
- resource)
478
- valid_objects[binding] << resource
479
- end
480
- end
481
- end
482
- end
483
-
484
- # Checks context rules for given resource
485
- # rules == { :res_type1 => [:action1, ...], ... }
486
- def evaluate_context_rules(rules, res_type, res) # :nodoc:
487
- evaluate_rules(rules[res_type], res_type, res)
488
- end
489
-
490
- # Checks if actions on resource are allowed. If true, adds to valid objects.
491
- # Returns true
492
- # actions == [:action1, ...]
493
- # valid_objects == { :action1 => [object1, ...], ... }
494
- def evaluate_rules(actions, res_type, resource) # :nodoc:
495
- valid_objects = @valid_objects[res_type]
496
- actions.each do |action|
497
- unless valid_objects[action].index(resource)
498
- __apply_rule(action, res_type, resource)
499
- valid_objects[action] << resource
500
- end
501
- end
502
- true
503
- end
504
-
505
- # Usage:
506
- # __allowed? :show, :assignment, an_assignment
507
- def __allowed?(action, res_type, resource=nil) # :nodoc:
508
-
509
- block = lambda do
510
- if resource
511
- policy(res_type).allowed?(action, resource)
512
- else
513
- policy(res_type).static_policy.allowed?(action, nil)
514
- end
515
- end
516
-
517
- block.call.tap do |r|
518
- log_access_check r, action, res_type, resource
519
- end
520
- end
521
-
522
- # Raises a SecurityViolationError if the rule defined by +policy_args+ is not
523
- # allowed. See __allowed? for details.
524
- #
525
- def __apply_rule(*args) # :nodoc:
526
- self.class.raise_access_denied(*args) unless __allowed?(*args)
527
- end
528
-
529
- #=============================================================================
530
- # Singleton
531
-
532
- def self.security_methods
533
- instance_methods(false).delete_if { |m| [:enabled?].member? m.to_sym }
534
- end
535
-
536
- #=============================================================================
537
- # Without security block implementation
538
-
539
- class SecurityContextDummy
540
- attr_accessor :credential
541
-
542
- def initialize(credential)
543
- self.credential = credential
544
- end
545
-
546
- def method_missing(symbol, *args)
547
- # puts "#{self.class}##{symbol}(#{args})"
548
- end
549
-
550
- def enabled?
551
- false
552
- end
553
- end
554
-
555
- public
556
-
557
- def enabled?
558
- true
559
- end
560
-
561
- # Runs a given block with security disabled. Inside the block, the context
562
- # will be disabled for the current thread.
563
- #
564
- def self.without_security!(&block)
565
- old_current = current
566
-
567
- credential = old_current.credential if old_current
568
- load SecurityContextDummy.new(credential)
569
- return_value = yield
570
- load old_current
571
- return_value
572
- end
573
-
574
- # create singleton methods
575
- security_methods.each do |method|
576
- if method.to_s.end_with? '='
577
- # setters need a different handling
578
- class_eval %{
579
- def self.#{method}(value)
580
- current.#{method}(value) if current
581
- end }
582
- else
583
- class_eval %{
584
- def self.#{method}(*args,&proc)
585
- current.#{method}(*args,&proc) if current
586
- end }
587
- end
588
- end
589
- end
1
+ #
2
+ # = lib/security_context.rb
3
+ #
4
+ # Contains the SecurityContext
5
+ require 'active_support'
6
+
7
+ # = SecurityContext
8
+ #
9
+ # The SecurityContext provides methods for all security concerns of
10
+ # the current request.
11
+ #
12
+ # For every request, it has to be initialized using #current_user=. It is
13
+ # recommended to do this in a security filter, which can be used to catch
14
+ # AnnotationSecurityExceptions as well.
15
+ #
16
+ # The SecurityContext is implemented as a singleton for the current thread.
17
+ # Thus, all instance methods can be send to the class as well.
18
+ #
19
+ class SecurityContext
20
+
21
+ # Returns current security context
22
+ #
23
+ def self.current
24
+ Thread.current[:security_context]
25
+ end
26
+
27
+ # At the begin of a request, the security context will be initialized for the
28
+ # current controller.
29
+ #
30
+ def self.initialize(controller) # :nodoc:
31
+ load(new(controller))
32
+ end
33
+
34
+ # As the security context is a singleton bound to the current thread,
35
+ # it will not be available in other threads. The following example shows
36
+ # how to use the security context inside of a spawn block
37
+ #
38
+ # copy = SecurityContext.copy
39
+ # spawn do
40
+ # SecurityContext.load(copy)
41
+ # begin
42
+ # # ...
43
+ # rescue SecurityViolationError
44
+ # puts 'Security was violated'
45
+ # end
46
+ # end
47
+ #
48
+ def self.load(sec_context)
49
+ Thread.current[:security_context] = sec_context
50
+ end
51
+
52
+ if Rails.env == 'development'
53
+ # Disables all security checkings.
54
+ # Is only available in development mode.
55
+ def self.ignore_security!
56
+ security_methods.each do |method|
57
+ class_eval "def self.#{method}(*args); true; end"
58
+ end
59
+ end
60
+ end
61
+
62
+ ## ===========================================================================
63
+ ## Instance
64
+
65
+ # Initialize context for the given controller
66
+ #
67
+ def initialize(controller) # :nodoc:
68
+ super()
69
+
70
+ @controller = controller
71
+
72
+ # initialize rule
73
+
74
+ # rules that are not bound to any source,
75
+ # will be triggered by model observer
76
+ @context_rules = new_rules_hash
77
+ @valid_objects = new_valid_objects_hash
78
+
79
+ # rules bound to request param
80
+ @param_rules = new_bound_rules_hash
81
+ @param_valid_objects = new_bound_valid_objects_hash
82
+
83
+ # rules bound to variable
84
+ @var_rules = new_bound_rules_hash
85
+ @var_valid_objects = new_bound_valid_objects_hash
86
+
87
+ # Hash with all required policies
88
+ @policies = new_policy_hash
89
+ end
90
+
91
+ # Sets the current user. This has to be done in a before or around filter,
92
+ # *before* entering the action. Elsewise, the user will be interpreted as
93
+ # not being logged in. Once set, the current user cannot be changed.
94
+ #
95
+ def credential=(user)
96
+ if @cred_set
97
+ raise AnnotationSecurity::AnnotationSecurityError,
98
+ "Credential already set for this request"
99
+ end
100
+ @cred_set = true
101
+ @credential = user
102
+ end
103
+
104
+ # Get the current credential
105
+ def credential
106
+ @credential
107
+ end
108
+
109
+ alias current_credential= credential=
110
+ alias current_credential credential
111
+
112
+ # Creates a copy of the current security context.
113
+ # See #load for more information.
114
+ def copy
115
+ returning self.class.new(@controller) { |sc| sc.credential = credential }
116
+ end
117
+
118
+ # Will be set if an security exception was catched by the security filter
119
+ def security_exception=(ex) # :nodoc:
120
+ @security_exception = ex
121
+ @controller.security_exception = ex
122
+ end
123
+
124
+ # If the action was aborted due to a security exception, this returns the
125
+ # exception that was raised. Returns nil if no exception occurred.
126
+ #
127
+ def security_exception
128
+ @security_exception
129
+ end
130
+
131
+ # See eval_with_security.
132
+ def send_with_security(rules, obj, msg, *args, &proc)
133
+ eval_with_security(rules) { obj.send(msg, *args, &proc) }
134
+ end
135
+
136
+ # Evaluates the given block, additionally using the given rules.
137
+ # rules == [ { :action => action, :resource => res_type, :source => binding }, ...]
138
+ # action and res_type should be symbols, binding is optional
139
+ #
140
+ def eval_with_security(rules)
141
+ install_rules(rules)
142
+
143
+ apply_rules_before_action
144
+
145
+ result = yield
146
+
147
+ apply_rules_after_action
148
+
149
+ result
150
+ rescue AnnotationSecurity::SecurityError
151
+ SecurityContext.security_exception = $!
152
+ raise $!
153
+ ensure
154
+ uninstall_rules(rules)
155
+ result
156
+ end
157
+
158
+ def apply_rules_before_action # :nodoc:
159
+ # apply static rules before entering the action
160
+ apply_static_rules
161
+ # bindings may apply to parameters, try to check them too
162
+ apply_param_rules
163
+ apply_var_rules
164
+ end
165
+
166
+ def apply_rules_after_action # :nodoc:
167
+ # check again, bindings may have been changed
168
+ apply_var_rules
169
+ end
170
+
171
+ # Returns true iif the operation defined by +policy_args+ is allowed.
172
+ #
173
+ # The following calls to \#allowed? are allowed:
174
+ #
175
+ # allowed? :show, :resource, @resource
176
+ # # => true if the current user has the right to show @resource,
177
+ # # which belongs to the :resource resource-class
178
+ #
179
+ # In case of model objects or other classes which implement a #resource_type
180
+ # method the the second argument may be ommited
181
+ #
182
+ # allowed? :show, @resource
183
+ # # equivalent to the above call if @resource.resource_type == :resource
184
+ #
185
+ # A policy description used as a controller annotation may also be to check
186
+ # a right
187
+ #
188
+ # allowed? "show resource", @resource
189
+ # # => true if the current user has the right "show resource" for @resource
190
+ #
191
+ # A policy may also be applied without an object representing the context:
192
+ #
193
+ # allowed? :show, :resource
194
+ # # => true if the current may show resources.
195
+ #
196
+ # This will only check system and pretest rules. The result +true+ does not
197
+ # mean that the user may show all resources. However, a +false+ indicates
198
+ # that the user is not allowed to show any resources.
199
+ #
200
+ # If the resource type is omitted as well, only rules defined for all
201
+ # resources can be tested. See RelationLoader#all_resources for details.
202
+ #
203
+ # allowed? :administrate
204
+ # # => true if the user is allowed to administrate all resources.
205
+ #
206
+ def allowed?(*policy_args)
207
+ policy_args = AnnotationSecurity::Utils.parse_policy_arguments(policy_args)
208
+ __allowed?(*policy_args)
209
+ end
210
+
211
+ # Equivalent to allowed?; is? is provided for better readability.
212
+ #
213
+ # SecurityContext.allowed? :logged_in
214
+ # vs
215
+ # SecurityContext.is? :logged_in
216
+ #
217
+ alias is? allowed?
218
+
219
+ # Raises a SecurityViolationError if the rule defined by +policy_args+ is not
220
+ # allowed. See allowed? for details.
221
+ #
222
+ def apply_rule(*args)
223
+ self.class.raise_access_denied(*args) unless allowed?(*args)
224
+ end
225
+
226
+ # Checks the rules of an other action. Note that rules that are bound to a
227
+ # variable can not be checked.
228
+ #
229
+ # ==== Parameters
230
+ # * +controller+ Symbol representing the controller, like :resource
231
+ # * +action+ The called action, like :update
232
+ # * +objects+ (optional) List of objects that will be relevant for that action.
233
+ # * +params+ (optional) Hash of the passed parameters, like :id => 1.
234
+ #
235
+ # ==== Examples
236
+ #
237
+ # Checks static and pretest rules.
238
+ # allow_action? :resource, :create
239
+ # # => true if the current user may execute ResourcesController#create
240
+ #
241
+ # Checks static, pretest and context rules
242
+ # allow_action? :resource, :edit, [@resource]
243
+ # # => true if the current user may execute ResourcesController#edit,
244
+ # # assuming that @resource will be used in that action
245
+ #
246
+ # Checks static, pretest and context rules and all rules that are bound
247
+ # to :id.
248
+ # allow_action? :resource, :edit, [@resource], {:id => 4}
249
+ # # => true if the current user may execute ResourcesController#edit,
250
+ # # assuming that @resource will be used in that action
251
+ #
252
+ def allow_action?(*args) # :nodoc:
253
+
254
+ controller, action, objects, params =
255
+ AnnotationSecurity::Utils.parse_action_args(args)
256
+
257
+ # var rules are ignored here
258
+ context_rules, param_rules, _ = get_rule_set(controller, action)
259
+
260
+ # check static rules
261
+ evaluate_statically(context_rules)
262
+
263
+ # check context rules for all objects
264
+ objects.each do |o|
265
+ res_type = o.resource_type
266
+ evaluate_context_rules(context_rules, res_type, o)
267
+ end
268
+
269
+ evaluate_bound_rules_for_params(param_rules, params)
270
+
271
+ true
272
+ rescue SecurityViolationError
273
+ return false
274
+ end
275
+
276
+ # Applies all system and pretest rules of the current action.
277
+ # Raises a SecurityViolationError if a rule is violated.
278
+ #
279
+ def apply_static_rules # :nodoc:
280
+ evaluate_statically(@context_rules)
281
+ end
282
+
283
+ def apply_param_rules # :nodoc:
284
+ evaluate_bound_rules(@param_rules, @param_valid_objects)
285
+ end
286
+
287
+ def apply_var_rules # :nodoc:
288
+ evaluate_bound_rules(@var_rules, @var_valid_objects)
289
+ end
290
+
291
+ # Applies all rules of the current action to the resource defined by
292
+ # +resource_args+. Raises a SecurityViolationError if a rule is
293
+ # violated.
294
+ #
295
+ def apply_context_rules(*res_args) # :nodoc:
296
+ restype, res = AnnotationSecurity::Utils.parse_resource_arguments(res_args)
297
+ evaluate_context_rules(@context_rules, restype, res)
298
+ end
299
+
300
+ alias apply_rules apply_context_rules # :nodoc:
301
+
302
+ # Call if a resource object was touched during an action. Will be called
303
+ # automatically for model objects.
304
+ #
305
+ # Applies all rules that are currently active to the resource defined by
306
+ # +resource_args+. Raises a SecurityViolationError if a rule is
307
+ # violated.
308
+ #
309
+ # ==== Usage
310
+ # observe :resource, @resource
311
+ # where <tt>:resource</tt> is the resource type @resource belongs to, or
312
+ # observe @resource
313
+ # which is equivalent if <tt>@resource.resource_name == :resource</tt>
314
+ #
315
+ def observe(*resource_args)
316
+ apply_context_rules(*resource_args)
317
+ end
318
+
319
+ # Raise a SecurityViolationError.
320
+ # See allowed? for details on +policy_args+.
321
+ #
322
+ def self.raise_access_denied(*policy_args)
323
+ log_access_denied(policy_args)
324
+ raise SecurityViolationError.access_denied(credential,*policy_args)
325
+ end
326
+
327
+ # Activates access logging for the current request.
328
+ #
329
+ def log!(&proc)
330
+ @enable_logging = true
331
+ @log = proc || Proc.new do |result, action, res_type, resource|
332
+ result = result ? 'ALLOWED' : 'REFUSED' unless result.is_a? String
333
+ msg = "%-8s %-10s %-16s %s" % [result, action, res_type, resource]
334
+ puts msg
335
+ end
336
+ end
337
+
338
+ def log_access_denied(policy_args) # :nodoc:
339
+ @log.call('DENIED!', *policy_args) if @enable_logging
340
+ end
341
+
342
+ protected
343
+
344
+ def log_access_check(*policy_args)
345
+ @log.call(*policy_args) if @enable_logging
346
+ end
347
+
348
+ private
349
+
350
+ # data =========================================================================
351
+
352
+ # { binding => { :res_type1 => [:action1, ...], ... }, ... }
353
+ def new_bound_rules_hash() # :nodoc:
354
+ Hash.new { |h,k| h[k] = new_rules_hash }
355
+ end
356
+
357
+ # { :res_type1 => [:action1, ...], ... }
358
+ def new_rules_hash() # :nodoc:
359
+ Hash.new { |h,k| h[k] = [] }
360
+ end
361
+
362
+ # { binding => [object1, ...], ...}
363
+ def new_bound_valid_objects_hash() # :nodoc:
364
+ Hash.new { |h,k| h[k] = [] }
365
+ end
366
+
367
+ # { :res_type1 => { :action1 => [object1, ...], ...}, ...}
368
+ def new_valid_objects_hash() # :nodoc:
369
+ Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
370
+ end
371
+
372
+ # {:res_type1 => policy1, ...}
373
+ def new_policy_hash() # :nodoc:
374
+ Hash.new { |h,k| h[k] = new_policy(k) }
375
+ end
376
+
377
+ def new_policy(resource_type) # :nodoc:
378
+ AnnotationSecurity::PolicyManager.create_policy(resource_type, credential)
379
+ end
380
+
381
+ # Get the policy for a resource type from the cache
382
+ def policy(res_type) # :nodoc:
383
+ @policies[res_type]
384
+ end
385
+
386
+ # rules management =============================================================
387
+
388
+ def install_rules(rules, rule_set=nil, controller=@controller.class)
389
+ rules.each { |rule| install_rule rule, rule_set, controller }
390
+ end
391
+
392
+ def install_rule(rule, rule_set, controller)
393
+ rule_list(rule, rule_set, controller) << rule[:action]
394
+ end
395
+
396
+ def uninstall_rules(rules, rule_set=nil, controller=@controller.class)
397
+ rules.each { |rule| uninstall_rule rule, rule_set, controller }
398
+ end
399
+
400
+ def uninstall_rule(rule, rule_set, controller)
401
+ list = rule_list(rule, rule_set, controller)
402
+ i = list.index(rule[:action])
403
+ list.delete_at(i) if i
404
+ end
405
+
406
+ def rule_list(rule, rule_set, controller)
407
+ rule_set ||= [@context_rules, @param_rules, @var_rules]
408
+ resource = rule[:resource] || controller.default_resource
409
+ source = rule[:source]
410
+ if source.nil?
411
+ list = rule_set.first[resource]
412
+ elsif source.is_a? Symbol
413
+ list = rule_set.second[source][resource]
414
+ else
415
+ list = rule_set.third[source][resource]
416
+ end
417
+ list
418
+ end
419
+
420
+ # returns rule set for other controller actions
421
+ def get_rule_set(controller, action) # :nodoc:
422
+ @rule_sets ||= Hash.new { |h,k| h[k] = {} }
423
+ rule_set = @rule_sets[controller][action]
424
+ unless rule_set
425
+ rule_set = [new_rules_hash, new_bound_rules_hash, new_bound_rules_hash]
426
+ rules = controller.descriptions_of action
427
+ install_rules rules, rule_set, controller
428
+ @rule_sets[controller][action] = rule_set
429
+ end
430
+ rule_set
431
+ end
432
+
433
+ # rule evaluation ==============================================================
434
+
435
+ # Evaluate the rules statically, skips all rules that are static only.
436
+ # * +rules+ a Hash like {:resource_type => [:right1, :right2]}
437
+ def evaluate_statically(rules) # :nodoc:
438
+ # rules == { :resource1 => [:right1, ...], ... }
439
+ rules.each_pair do |resource_type,rights|
440
+ policy(resource_type).evaluate_statically(rights)
441
+ end
442
+ end
443
+
444
+ # Checks bound rules. Evaluates the bindings, on success adds objects
445
+ # to valid objects.
446
+ # rules == { binding1 => { :res_type1 => [:action1, ...], ... }, ... }
447
+ # valid_objects == { binding1 => [object1, ...], ... }
448
+ def evaluate_bound_rules(rules, valid_objects) # :nodoc:
449
+ evaluate_bound_rules_with_binding(rules, valid_objects) do |binding|
450
+ @controller.values_of_source(binding)
451
+ end
452
+ end
453
+
454
+ # Checks bound rules using the values from params
455
+ # rules == { binding1 => { :res_type1 => [:action1, ...], ... }, ... }
456
+ # params == { binding1 => object1, ... }
457
+ def evaluate_bound_rules_for_params(rules, params) # :nodoc:
458
+ valid_objects = new_bound_valid_objects_hash
459
+ evaluate_bound_rules_with_binding(rules, valid_objects) do |binding|
460
+ values = params[binding]
461
+ values.is_a?(Array) ? values : [values]
462
+ end
463
+ end
464
+
465
+ def evaluate_bound_rules_with_binding(rules, valid_objects, &proc) # :nodoc:
466
+ rules.each_key do |binding|
467
+ value_ids = proc.call(binding)
468
+ rules[binding].each_key do |res_type|
469
+ values = value_ids.collect do |id|
470
+ AnnotationSecurity::ResourceManager.get_resource res_type, id
471
+ end
472
+ values.compact!
473
+ values_of_res_type = values - valid_objects[binding]
474
+ values_of_res_type.each do |resource|
475
+ evaluate_rules(rules[binding][res_type],
476
+ res_type,
477
+ resource)
478
+ valid_objects[binding] << resource
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ # Checks context rules for given resource
485
+ # rules == { :res_type1 => [:action1, ...], ... }
486
+ def evaluate_context_rules(rules, res_type, res) # :nodoc:
487
+ evaluate_rules(rules[res_type], res_type, res)
488
+ end
489
+
490
+ # Checks if actions on resource are allowed. If true, adds to valid objects.
491
+ # Returns true
492
+ # actions == [:action1, ...]
493
+ # valid_objects == { :action1 => [object1, ...], ... }
494
+ def evaluate_rules(actions, res_type, resource) # :nodoc:
495
+ valid_objects = @valid_objects[res_type]
496
+ actions.each do |action|
497
+ unless valid_objects[action].index(resource)
498
+ __apply_rule(action, res_type, resource)
499
+ valid_objects[action] << resource
500
+ end
501
+ end
502
+ true
503
+ end
504
+
505
+ # Usage:
506
+ # __allowed? :show, :assignment, an_assignment
507
+ def __allowed?(action, res_type, resource=nil) # :nodoc:
508
+
509
+ block = lambda do
510
+ if resource
511
+ policy(res_type).allowed?(action, resource)
512
+ else
513
+ policy(res_type).static_policy.allowed?(action, nil)
514
+ end
515
+ end
516
+
517
+ returning block.call do |r|
518
+ log_access_check r, action, res_type, resource
519
+ end
520
+ end
521
+
522
+ # Raises a SecurityViolationError if the rule defined by +policy_args+ is not
523
+ # allowed. See __allowed? for details.
524
+ #
525
+ def __apply_rule(*args) # :nodoc:
526
+ self.class.raise_access_denied(*args) unless __allowed?(*args)
527
+ end
528
+
529
+ #=============================================================================
530
+ # Singleton
531
+
532
+ def self.security_methods
533
+ instance_methods(false).delete_if { |m| [:enabled?].member? m.to_sym }
534
+ end
535
+
536
+ #=============================================================================
537
+ # Without security block implementation
538
+
539
+ class SecurityContextDummy
540
+ attr_accessor :credential
541
+
542
+ def initialize(credential)
543
+ self.credential = credential
544
+ end
545
+
546
+ def method_missing(symbol, *args)
547
+ puts "#{self.class}##{symbol}(#{args})"
548
+ end
549
+
550
+ def enabled?
551
+ false
552
+ end
553
+ end
554
+
555
+ public
556
+
557
+ def enabled?
558
+ true
559
+ end
560
+
561
+ # Runs a given block with security disabled. Inside the block, the context
562
+ # will be disabled for the current thread.
563
+ #
564
+ def self.without_security!(&block)
565
+ old_current = current
566
+
567
+ load(SecurityContextDummy.new(old_current.credential))
568
+ return_value = yield
569
+ load old_current
570
+ return_value
571
+ end
572
+
573
+ # create singleton methods
574
+ security_methods.each do |method|
575
+ if method.to_s.end_with? '='
576
+ # setters need a different handling
577
+ class_eval %{
578
+ def self.#{method}(value)
579
+ current.#{method}(value) if current
580
+ end }
581
+ else
582
+ class_eval %{
583
+ def self.#{method}(*args,&proc)
584
+ current.#{method}(*args,&proc) if current
585
+ end }
586
+ end
587
+ end
588
+ end