annotation_security 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -0
- data/HOW-TO +261 -0
- data/MIT-LICENSE +18 -0
- data/README +39 -0
- data/Rakefile +56 -0
- data/assets/app/helpers/annotation_security_helper.rb +9 -0
- data/assets/config/initializers/annotation_security.rb +12 -0
- data/assets/config/security/relations.rb +20 -0
- data/assets/config/security/rights.yml +16 -0
- data/assets/vendor/plugins/annotation_security/init.rb +14 -0
- data/bin/annotation_security +8 -0
- data/lib/annotation_security/exceptions.rb +125 -0
- data/lib/annotation_security/exec.rb +189 -0
- data/lib/annotation_security/filters.rb +38 -0
- data/lib/annotation_security/includes/action_controller.rb +144 -0
- data/lib/annotation_security/includes/active_record.rb +28 -0
- data/lib/annotation_security/includes/helper.rb +215 -0
- data/lib/annotation_security/includes/resource.rb +85 -0
- data/lib/annotation_security/includes/role.rb +31 -0
- data/lib/annotation_security/includes/user.rb +27 -0
- data/lib/annotation_security/manager/policy_factory.rb +30 -0
- data/lib/annotation_security/manager/policy_manager.rb +80 -0
- data/lib/annotation_security/manager/relation_loader.rb +273 -0
- data/lib/annotation_security/manager/resource_manager.rb +36 -0
- data/lib/annotation_security/manager/right_loader.rb +88 -0
- data/lib/annotation_security/model_observer.rb +61 -0
- data/lib/annotation_security/policy/abstract_policy.rb +345 -0
- data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
- data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
- data/lib/annotation_security/policy/rule.rb +340 -0
- data/lib/annotation_security/policy/rule_set.rb +139 -0
- data/lib/annotation_security/rails.rb +39 -0
- data/lib/annotation_security/user_wrapper.rb +74 -0
- data/lib/annotation_security/utils.rb +142 -0
- data/lib/annotation_security.rb +98 -0
- data/lib/extensions/action_controller.rb +33 -0
- data/lib/extensions/active_record.rb +35 -0
- data/lib/extensions/filter.rb +134 -0
- data/lib/extensions/object.rb +11 -0
- data/lib/security_context.rb +551 -0
- data/spec/annotation_security/exceptions_spec.rb +17 -0
- data/spec/annotation_security/includes/helper_spec.rb +82 -0
- data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
- data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
- data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
- data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
- data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
- data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
- data/spec/annotation_security/policy/rule_spec.rb +78 -0
- data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
- data/spec/annotation_security/security_context_spec.rb +78 -0
- data/spec/annotation_security/utils_spec.rb +74 -0
- data/spec/helper/test_controller.rb +66 -0
- data/spec/helper/test_helper.rb +5 -0
- data/spec/helper/test_relations.rb +7 -0
- data/spec/helper/test_resource.rb +39 -0
- data/spec/helper/test_rights.yml +5 -0
- data/spec/helper/test_role.rb +22 -0
- data/spec/helper/test_user.rb +32 -0
- data/spec/rails_stub.rb +38 -0
- data/spec/spec_helper.rb +43 -0
- metadata +157 -0
@@ -0,0 +1,551 @@
|
|
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)
|
534
|
+
end
|
535
|
+
|
536
|
+
# create singleton methods
|
537
|
+
security_methods.each do |method|
|
538
|
+
if method.to_s.ends_with? '='
|
539
|
+
# setters need a different handling
|
540
|
+
class_eval %{
|
541
|
+
def self.#{method}(value)
|
542
|
+
current.#{method} value
|
543
|
+
end }
|
544
|
+
else
|
545
|
+
class_eval %{
|
546
|
+
def self.#{method}(*args,&proc)
|
547
|
+
current.#{method}(*args,&proc)
|
548
|
+
end }
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe AnnotationSecurity::RuleExecutionError do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
AnnotationSecurity.define_relations(:rule_ex_error_test) do
|
7
|
+
broken_relation { 1/0 }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should be raised if a relation throws an error' do
|
12
|
+
lambda {
|
13
|
+
RuleExErrorTestPolicy.new(:user,:res).broken_relation?
|
14
|
+
}.should raise_error(AnnotationSecurity::RuleExecutionError)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe AnnotationSecurity::Helper do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
SecurityContext.initialize(TestController.new)
|
7
|
+
SecurityContext.credential = TestUser.new 'theuser'
|
8
|
+
@helper = TestHelper.new
|
9
|
+
@res = TestResource.new 'theuser'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should understand options hash" do
|
13
|
+
options = { :action => :edit, :controller => :test, :id => @res }
|
14
|
+
expect(:test, :edit, [], {:id => @res})
|
15
|
+
@helper.action_allowed?(options).should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should understand path strings" do
|
19
|
+
path = 'test/theuser/edit'
|
20
|
+
with_path_info path
|
21
|
+
expect :test, :edit, [], {:id => 'theuser'}
|
22
|
+
@helper.action_allowed?(path).should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should understand resource objects" do
|
26
|
+
with_path_info 'test/theuser', :get, {:action => :show}
|
27
|
+
expect :test, :show, [], {:id => 'theuser'}
|
28
|
+
@helper.expects(:url_for).with(@res).returns('test/theuser')
|
29
|
+
@helper.action_allowed?(@res).should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should take html options into account" do
|
33
|
+
with_path_info 'test/theuser', :delete, {:action => :destroy}
|
34
|
+
expect :test, :destroy, [], {:id => 'theuser'}
|
35
|
+
@helper.expects(:url_for).with(@res).returns('test/theuser')
|
36
|
+
@helper.action_allowed?(@res, { :method => :delete}).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should call named routes" do
|
40
|
+
with_path_info 'test/theuser/edit'
|
41
|
+
expect :test, :edit, [@res], {}
|
42
|
+
@helper.expects(:edit_test_path).with(@res, {}).returns('test/theuser/edit')
|
43
|
+
@helper.action_allowed?(:edit_test_path, @res).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should support defining all parameters explicitly" do
|
47
|
+
expect :test, :edit, [@res], {:option => true}
|
48
|
+
params = { :action => :edit, :controller => :test, :option => true }
|
49
|
+
@helper.action_allowed?('path/to/something', @res, params).should be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should create links if allowed" do
|
53
|
+
options = { :action => :edit, :controller => :test, :id => @res }
|
54
|
+
expect(:test, :edit, [], {:id => @res})
|
55
|
+
@helper.expects(:link_to_if).with(true, "Edit", options, {}).returns("<a>success</a>")
|
56
|
+
@helper.link_to_if_allowed("Edit", options){'no access'}.should == "<a>success</a>"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not create links if forbidden" do
|
60
|
+
options = { :action => :edit, :controller => :test, :id => @res }
|
61
|
+
expect(:test, :edit, [], {:id => @res}, false)
|
62
|
+
@helper.expects(:link_to_if).with(false, "Edit", options, {}).returns("no access")
|
63
|
+
@helper.link_to_if_allowed("Edit", options){"no access"}.should == "no access"
|
64
|
+
end
|
65
|
+
|
66
|
+
def expect(ctrl, action, obj, param, result=true)
|
67
|
+
SecurityContext.expects(:allow_action?).with(ctrl, action, obj, param).returns(result)
|
68
|
+
end
|
69
|
+
|
70
|
+
# prepares #recognize_path to resolve the request path
|
71
|
+
def with_path_info(path, env = nil, result={})
|
72
|
+
env = { :method => env } if env.is_a? Symbol
|
73
|
+
env ||= { :method => :get }
|
74
|
+
parts = path.split('/')
|
75
|
+
result[:controller] ||= parts.first.to_sym
|
76
|
+
result[:id] ||= parts.second
|
77
|
+
result[:action] ||= parts.third.to_sym
|
78
|
+
ActionController::Routing::Routes.expects(:recognize_path).with(path, env).returns(result)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe AnnotationSecurity::PolicyManager do
|
4
|
+
|
5
|
+
it "should provide policy factories" do
|
6
|
+
AnnotationSecurity::PolicyManager.policy_factory(:policy_manager)
|
7
|
+
(defined? PolicyManagerPolicy).should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return the policy class for a resource" do
|
11
|
+
AnnotationSecurity::PolicyManager.policy_class(:policy_manager_2).
|
12
|
+
should == PolicyManager2Policy
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe AnnotationSecurity::ResourceManager do
|
4
|
+
|
5
|
+
it "should provide resource classes" do
|
6
|
+
klass = AnnotationSecurity::ResourceManager.get_resource_class :test_resource
|
7
|
+
klass.should == TestResource
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should find resource instances" do
|
11
|
+
res = AnnotationSecurity::ResourceManager.get_resource :test_resource, 'xy'
|
12
|
+
res.should be_instance_of(TestResource)
|
13
|
+
res.name.should == 'xy'
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
describe AnnotationSecurity::RightLoader do
|
4
|
+
|
5
|
+
it "should allow right definitions by hash" do
|
6
|
+
AnnotationSecurity::RightLoader.define_rights({
|
7
|
+
:right_loader => {
|
8
|
+
:right1 => 'if logged_in',
|
9
|
+
:right2 => 'if may_right1',
|
10
|
+
}})
|
11
|
+
(defined? RightLoaderPolicy).should_not be_nil
|
12
|
+
RightLoaderPolicy.has_rule?(:right1).should be_true
|
13
|
+
RightLoaderPolicy.has_rule?(:right2).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|