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