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,340 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/policy/rule.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::Rule
|
6
|
+
# A right or a relation that belongs to a policy.
|
7
|
+
#
|
8
|
+
# Rules can be static or dynamic or both.
|
9
|
+
# If the rule is a right, these values will be evaluated lazily.
|
10
|
+
#
|
11
|
+
class AnnotationSecurity::Rule # :nodoc:
|
12
|
+
|
13
|
+
# Initialize a rule
|
14
|
+
#
|
15
|
+
def initialize(name,policy_class,*args,&block) # :nodoc:
|
16
|
+
super()
|
17
|
+
@name = name.to_sym
|
18
|
+
@policy_class = policy_class
|
19
|
+
@proc = block
|
20
|
+
read_flags(args)
|
21
|
+
read_options(args)
|
22
|
+
if @proc
|
23
|
+
initialize_for_proc(args)
|
24
|
+
else
|
25
|
+
initialize_for_string(args)
|
26
|
+
end
|
27
|
+
raise ArgumentError,
|
28
|
+
"#{self}: Unexpected Arguments: #{args.join ','}" unless args.blank?
|
29
|
+
#puts self
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s # :nodoc:
|
33
|
+
"<#{full_name}[#{flag_s}]>"
|
34
|
+
end
|
35
|
+
|
36
|
+
def full_name # :nodoc:
|
37
|
+
"#@policy_class##@name"
|
38
|
+
end
|
39
|
+
|
40
|
+
def flag_s # :nodoc:
|
41
|
+
(@right ? 'r' : '-') +
|
42
|
+
(@static.nil? ? '?' : (@static ? 's' : '-')) +
|
43
|
+
(@dynamic.nil? ? '?' : (@dynamic ? 'd' : '-')) +
|
44
|
+
(@req_user.nil? ? '?' : (@req_user ? 'u' : '-'))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return if this rule was defined as right
|
48
|
+
#
|
49
|
+
def right? # :nodoc:
|
50
|
+
@right
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return if this rule can be evaluated without a resource
|
54
|
+
#
|
55
|
+
def static? # :nodoc:
|
56
|
+
return @static unless @static.nil?
|
57
|
+
lazy_initialize
|
58
|
+
@static
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return if this rule can be evaluated with a resource
|
62
|
+
#
|
63
|
+
def dynamic? # :nodoc:
|
64
|
+
return @dynamic unless @dynamic.nil?
|
65
|
+
lazy_initialize
|
66
|
+
@dynamic
|
67
|
+
end
|
68
|
+
|
69
|
+
def requires_credential? # :nodoc:
|
70
|
+
return @req_user unless @req_user.nil?
|
71
|
+
lazy_initialize
|
72
|
+
@req_user
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates a method for a policy class that evaluates this rule
|
76
|
+
# * +klass+ either @policy_class or its static partner
|
77
|
+
#
|
78
|
+
def extend_class(klass) # :nodoc:
|
79
|
+
|
80
|
+
# Arguments passed to AbstractPolicy#user_roles
|
81
|
+
# * +role+ symbol identifying the role a user must have (or nil)
|
82
|
+
# * +user_required+ if false, the rule will also be
|
83
|
+
# evaluated if the user is nil
|
84
|
+
user_args = "#{@as ? ":#@as" : 'nil'},#{requires_credential?}"
|
85
|
+
|
86
|
+
# Actual logic of the rule
|
87
|
+
rule_code = @proc ? code_for_proc : code_for_string
|
88
|
+
|
89
|
+
# Arguments passed to RuleExecutionError#new if an error occured
|
90
|
+
# while evaluating the rule
|
91
|
+
# * +rule+ full name of the rule
|
92
|
+
# * +proc+ true iif this rule is defined with a proc
|
93
|
+
# * +ex+ the original exeption
|
94
|
+
ex_args = "'#{full_name}',#{@proc ? true : false},$!"
|
95
|
+
|
96
|
+
code = "def #@name(*args) \n"
|
97
|
+
|
98
|
+
# If parameter :is is given, @user.is_{@is}? has to return true.
|
99
|
+
#
|
100
|
+
code << "return false if @user.nil? || !@user.is_#@is?\n" if @is
|
101
|
+
code << %{
|
102
|
+
# __resource__ = @resource
|
103
|
+
return user_roles(#{user_args}).any? do |__user__|
|
104
|
+
#{rule_code}
|
105
|
+
end
|
106
|
+
rescue StandardError
|
107
|
+
raise $! if $!.is_a? AnnotationSecurity::SecurityError
|
108
|
+
raise AnnotationSecurity::RuleExecutionError.new(#{ex_args})
|
109
|
+
end}
|
110
|
+
klass.class_eval(code)
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# Evaluate proc for policy
|
115
|
+
def evaluate(policy,*args) # :nodoc:
|
116
|
+
raise AnnotationSecurity::RuleError, "#{self}: This rule has no proc" unless @proc
|
117
|
+
if @arity == 0
|
118
|
+
policy.instance_exec(&@proc)
|
119
|
+
elsif @arity > 0
|
120
|
+
policy.instance_exec(*(args[0..@arity-1]),&@proc)
|
121
|
+
else
|
122
|
+
policy.instance_exec(*args,&@proc)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Creates a copy for policy class
|
127
|
+
#
|
128
|
+
def copy(policy_class) # :nodoc:
|
129
|
+
args = [name, policy_class,flag,options,@condition].compact
|
130
|
+
self.class.new(*args,&@proc)
|
131
|
+
end
|
132
|
+
|
133
|
+
def name # :nodoc:
|
134
|
+
@name
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def read_flags(args)
|
140
|
+
@right = false
|
141
|
+
@static = false
|
142
|
+
@dynamic = true
|
143
|
+
@req_user = true
|
144
|
+
if args.delete :right
|
145
|
+
@right = true
|
146
|
+
@req_user = @static = @dynamic = nil
|
147
|
+
elsif args.delete :system
|
148
|
+
@static = true
|
149
|
+
@dynamic = false
|
150
|
+
elsif args.delete :pretest
|
151
|
+
@static = true
|
152
|
+
else
|
153
|
+
args.delete :resource # default
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def flag
|
158
|
+
return :right if right?
|
159
|
+
if static?
|
160
|
+
return :pretest if dynamic?
|
161
|
+
return :system
|
162
|
+
else
|
163
|
+
return :resource
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def read_options(args)
|
168
|
+
hash = args.detect {|h| h.is_a? Hash}
|
169
|
+
args.delete hash
|
170
|
+
return if hash.blank?
|
171
|
+
@as = hash.delete(:as)
|
172
|
+
@is = hash.delete(:is)
|
173
|
+
@req_user = hash.delete(:require_credential)
|
174
|
+
@req_user = true if @req_user.nil? && !right?
|
175
|
+
if (@as || @is) && !@req_user
|
176
|
+
raise ArgumentError, "Options :as and :is always require a user!"
|
177
|
+
end
|
178
|
+
unless hash.empty?
|
179
|
+
raise ArgumentError, "Unexpected keys [#{hash.keys.join(', ')}]"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def options
|
184
|
+
{:is => @is, :as => @as, :require_credential => (right? ? nil : requires_credential?)}
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check for the optional parameter :as => :role
|
188
|
+
def initialize_for_proc(args)
|
189
|
+
@arity = @proc.arity
|
190
|
+
end
|
191
|
+
|
192
|
+
def initialize_for_string(args)
|
193
|
+
@condition = args.detect {|s| s.is_a? String } || 'true'
|
194
|
+
args.delete @condition
|
195
|
+
end
|
196
|
+
|
197
|
+
# Find out if this rule can be evaluated statically
|
198
|
+
def lazy_initialize
|
199
|
+
raise_evil_recursion if @initialize_static
|
200
|
+
@initialize_static = true
|
201
|
+
if @proc
|
202
|
+
# rules with proc must be defined as static explicitly
|
203
|
+
@static = false
|
204
|
+
@dynamic = true
|
205
|
+
@req_user = true
|
206
|
+
else
|
207
|
+
# parse string to find out more
|
208
|
+
if @condition =~ /:|self/
|
209
|
+
# this only works with resources
|
210
|
+
@static = false
|
211
|
+
@dynamic = true
|
212
|
+
@req_user = true
|
213
|
+
else
|
214
|
+
@static = true # a right is static if it uses only static rules
|
215
|
+
@dynamic = false # a right is dynamic if it uses at least one dynamic rule
|
216
|
+
@req_user = false # unless at least one rule requires a user
|
217
|
+
@condition.gsub(/\(|\)/,' ').split.each do |token|
|
218
|
+
unless token =~ /\A(if|unless|or|and|not|true|false|nil)\Z/
|
219
|
+
token = validate_token!(token)
|
220
|
+
@static &= can_be_static?(token)
|
221
|
+
@dynamic |= can_be_dynamic?(token)
|
222
|
+
@req_user |= needs_user?(token)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
raise AnnotationSecurity::RuleError,
|
228
|
+
"#{self} is neither static nor dynamic!" unless @static || @dynamic
|
229
|
+
end
|
230
|
+
|
231
|
+
def validate_token!(token)
|
232
|
+
return token.to_sym if @policy_class.has_rule?(token.to_sym)
|
233
|
+
body = AnnotationSecurity::Utils.method_body(token)
|
234
|
+
return validate_token!(body) if body
|
235
|
+
raise AnnotationSecurity::RuleNotFoundError, "Unknown rule '#{token}' in #{full_name}"
|
236
|
+
end
|
237
|
+
|
238
|
+
def can_be_static?(token)
|
239
|
+
@policy_class.has_static_rule?(token)
|
240
|
+
end
|
241
|
+
|
242
|
+
def can_be_dynamic?(token)
|
243
|
+
@policy_class.has_dynamic_rule?(token)
|
244
|
+
end
|
245
|
+
|
246
|
+
def needs_user?(token)
|
247
|
+
@policy_class.get_rule(token).requires_credential?
|
248
|
+
end
|
249
|
+
|
250
|
+
def raise_evil_recursion
|
251
|
+
raise AnnotationSecurity::RuleError,
|
252
|
+
"Forbidden recursion in #{@policy_class.resource_class}: #{self}"
|
253
|
+
end
|
254
|
+
|
255
|
+
def code_for_proc
|
256
|
+
"evaluate_rule(:#{name},__user__,args)"
|
257
|
+
end
|
258
|
+
|
259
|
+
def code_for_string
|
260
|
+
condition = @condition.dup
|
261
|
+
|
262
|
+
# Apply special role 'self'
|
263
|
+
condition.gsub!('self', '__self__')
|
264
|
+
|
265
|
+
apply_resource_notation(condition)
|
266
|
+
apply_may_property_notation(condition)
|
267
|
+
|
268
|
+
if condition =~ /\A(\s*)(if|unless)/
|
269
|
+
# multilines in case +condition+ contains comments
|
270
|
+
"#{condition} \n true \n else \n false \n end"
|
271
|
+
else
|
272
|
+
condition
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Apply replacements for :resource notation:
|
277
|
+
# Rules of the form prefix(:resource.res_suffix, additional_args) are
|
278
|
+
# rewritten to @resource.res_suffix.policy_for(@user).prefix(additional_args)
|
279
|
+
#
|
280
|
+
# They are per definition dynamic!
|
281
|
+
# Other rules which contain :resource are per se dynamic, too!
|
282
|
+
#
|
283
|
+
def apply_resource_notation(condition)
|
284
|
+
regex = /([^\s\(]+)\(:resource(?:\.([^\s,]*))?(?:,\s*([^\(]*))?\)/
|
285
|
+
|
286
|
+
condition.gsub!(regex) do |match|
|
287
|
+
parse_expr(match.scan(regex).first)
|
288
|
+
end
|
289
|
+
#condition.gsub!(/:resource/, "@resource")
|
290
|
+
end
|
291
|
+
|
292
|
+
def parse_expr(expr)
|
293
|
+
prefix = expr.at(0)
|
294
|
+
res_suffix = expr.at(1)
|
295
|
+
additional_args = expr.at(2)
|
296
|
+
|
297
|
+
case prefix
|
298
|
+
when /^(if|unless|or|and|not)$/
|
299
|
+
# Should not be matched by regex
|
300
|
+
raise ArgumentError, "Invalid syntax."
|
301
|
+
else
|
302
|
+
res_class, right = parse_right(prefix)
|
303
|
+
if res_class
|
304
|
+
"(PolicyManager.get_policy("+
|
305
|
+
":#{res_class},@user,@resource.#{res_suffix}).#{right})"
|
306
|
+
else
|
307
|
+
call = "(!(res = @resource"
|
308
|
+
call << ".#{res_suffix}" if res_suffix
|
309
|
+
call << ").nil? &&"
|
310
|
+
call << "res.policy_for(@user).#{prefix}"
|
311
|
+
call << "(#{additional_args})" if additional_args
|
312
|
+
call << ')'
|
313
|
+
call
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Apply replacements for 'may: property' notation
|
319
|
+
def apply_may_property_notation(condition)
|
320
|
+
rx_may_prop = /(\S+):\s*(\S+)/
|
321
|
+
condition.gsub!(rx_may_prop) do |match|
|
322
|
+
right, resource = match.scan(rx_may_prop).first
|
323
|
+
res_class, right = parse_right(right)
|
324
|
+
if res_class
|
325
|
+
"(PolicyManager.get_policy("+
|
326
|
+
":#{res_class},@user,@resource.#{resource}).#{right})"
|
327
|
+
else
|
328
|
+
"(!(res = @resource.#{resource}).nil? &&" +
|
329
|
+
" res.policy_for(@user).#{right})"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Returns [res_class, right] if +right+ has the form "res_class.right",
|
335
|
+
# else it returns [nil, right].
|
336
|
+
def parse_right(right)
|
337
|
+
rx_class_right = /(\S*)\.(\S*)/
|
338
|
+
(right.scan(rx_class_right).first) || [nil,right]
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/policy/rule_set.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::RuleSet
|
6
|
+
# Contains all rule objects for a policy
|
7
|
+
#
|
8
|
+
class AnnotationSecurity::RuleSet # :nodoc:
|
9
|
+
|
10
|
+
# Initializes the rule set
|
11
|
+
# * +pclass+ Policy class this rule set belongs to
|
12
|
+
#
|
13
|
+
def initialize(pclass)
|
14
|
+
super()
|
15
|
+
@pclass = pclass
|
16
|
+
@rights = {}
|
17
|
+
@static = {}
|
18
|
+
@dynamic = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"<RuleSet of #@pclass>"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a rule object or nil if the rule does not exist.
|
26
|
+
# * +symbol+ name of the rule
|
27
|
+
# * +static+ boolean specifing whether the rule is static or dynamic
|
28
|
+
def get_rule(symbol,static)
|
29
|
+
static ? get_static_rule(symbol) : get_dynamic_rule(symbol)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a dynamic rule or nil if the rule does not exist.
|
33
|
+
# * +symbol+ name of the rule
|
34
|
+
def get_dynamic_rule(symbol)
|
35
|
+
# If no rule is available, maybe there is a right that can be used
|
36
|
+
@dynamic[symbol] ||= get_dynamic_right(symbol)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a static rule or nil if the rule does not exist.
|
40
|
+
# * +symbol+ name of the rule
|
41
|
+
def get_static_rule(symbol)
|
42
|
+
# If no rule is available, maybe there is a right that can be used
|
43
|
+
@static[symbol] ||= get_static_right(symbol)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Copies a rule from another rule set.
|
47
|
+
# Returns the newly created rule or nil if the operation had no effect.
|
48
|
+
# * +symbol+ name of the rule
|
49
|
+
# * +source+ rule set to copy from
|
50
|
+
# * +static+ boolean specifing whether the rule is static or dynamic
|
51
|
+
def copy_rule_from(symbol,source,static)
|
52
|
+
add_copy(source.get_rule(symbol,static))
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a dynamic rule that redirects to a static rule with the same name.
|
56
|
+
# Returns the newly created rule or nil if the operation had no effect.
|
57
|
+
# * +symbol+ name of the rule
|
58
|
+
def create_dynamic_copy(symbol)
|
59
|
+
rule = get_static_rule(symbol)
|
60
|
+
if rule
|
61
|
+
add_rule(symbol,
|
62
|
+
"static_policy.#{symbol}(*args)",
|
63
|
+
:resource,
|
64
|
+
:require_credential => rule.requires_credential?)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Adds a new rule to this rule set. The rule will be classified either
|
69
|
+
# as dynamic, static, both or right.
|
70
|
+
# Returns the newly create rule.
|
71
|
+
# For an explainition of the parameters see AnnotationSecurity::Rule#initialize.
|
72
|
+
def add_rule(symbol,*args,&block)
|
73
|
+
__add__ AnnotationSecurity::Rule.new(symbol,@pclass,*args,&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Copies a rule object to this rule set.
|
79
|
+
# Returns the newly created rule or nil.
|
80
|
+
# * +rule+ rule object to copy or nil.
|
81
|
+
def add_copy(rule)
|
82
|
+
__add__(rule.copy(@pclass)) if rule
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds a new rule to this rule set. The rule will be classified either
|
86
|
+
# as dynamic, static, both or right.
|
87
|
+
# * +rule+ rule object
|
88
|
+
def __add__(rule)
|
89
|
+
if rule.right?
|
90
|
+
# if the rule is a right, its not clear yet whether
|
91
|
+
# it is static or dynamic. These rules will be analyzed later.
|
92
|
+
raise_if_forbidden_name 'right', rule
|
93
|
+
raise_if_exists 'right', @rights[rule.name]
|
94
|
+
@rights[rule.name] = rule
|
95
|
+
else
|
96
|
+
raise_if_forbidden_name 'relation', rule
|
97
|
+
if rule.dynamic?
|
98
|
+
raise_if_exists 'dynamic relation', @dynamic[rule.name]
|
99
|
+
@dynamic[rule.name] = rule
|
100
|
+
end
|
101
|
+
if rule.static?
|
102
|
+
raise_if_exists 'static relation', @static[rule.name]
|
103
|
+
@static[rule.name] = rule
|
104
|
+
end
|
105
|
+
end
|
106
|
+
rule
|
107
|
+
end
|
108
|
+
|
109
|
+
# Raises an error if +rule+ is not nil.
|
110
|
+
# * +type+ type of rule, like 'right' or 'dynamic relation'
|
111
|
+
# * +rule+ existing rule object or nil
|
112
|
+
def raise_if_exists(type,rule)
|
113
|
+
raise AnnotationSecurity::RuleError.defined_twice(type,rule) if rule
|
114
|
+
end
|
115
|
+
|
116
|
+
# Raises an error if +rule+ has a forbidden name.
|
117
|
+
# * +type+ type of rule, like 'right' or 'relation'
|
118
|
+
# * +rule+ rule object
|
119
|
+
def raise_if_forbidden_name(type,rule)
|
120
|
+
if AnnotationSecurity::AbstractPolicy.forbidden_rule_names.include? rule.name.to_s
|
121
|
+
raise AnnotationSecurity::RuleError.forbidden_name(type,rule)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns a dynamic rule that was defined as right
|
126
|
+
# * +symbol+ name of the rule
|
127
|
+
def get_dynamic_right(symbol)
|
128
|
+
r = @rights[symbol]
|
129
|
+
r and r.dynamic? ? r : nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns a static rule that was defined as right
|
133
|
+
# * +symbol+ name of the rule
|
134
|
+
def get_static_right(symbol)
|
135
|
+
r = @rights[symbol]
|
136
|
+
r and r.static? ? r : nil
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# = annotation_security/rails/init.rb
|
3
|
+
#
|
4
|
+
# Loads the annotation security layer for a rails app
|
5
|
+
|
6
|
+
require "action_controller/dispatcher"
|
7
|
+
require "action_controller/base"
|
8
|
+
|
9
|
+
module AnnotationSecurity
|
10
|
+
|
11
|
+
# Contains rails specific initializer
|
12
|
+
class Rails
|
13
|
+
def self.init!(config)
|
14
|
+
|
15
|
+
# Policy files are situated under RAILS_ROOT/config/security
|
16
|
+
# Default policy file is internal, load it
|
17
|
+
::AnnotationSecurity.load_relations(File.dirname(__FILE__) + '/policy/all_resources_policy')
|
18
|
+
|
19
|
+
# Add AnnotationSecurity::ModelObserver to observe changes in models.
|
20
|
+
# See http://riotprojects.com/2009/1/18/active-record-observers-in-gems-plugins
|
21
|
+
#
|
22
|
+
config.after_initialize do
|
23
|
+
# Set up a dummy security context that does not interfer with script
|
24
|
+
::SecurityContext.initialize nil
|
25
|
+
|
26
|
+
::ActiveRecord::Base.observers << ::AnnotationSecurity::ModelObserver
|
27
|
+
|
28
|
+
# In development mode, the models we observe get reloaded with each request. Using
|
29
|
+
# this hook allows us to reload the observer relationships each time as well.
|
30
|
+
::ActionController::Dispatcher.to_prepare(:cache_advance_reload) do
|
31
|
+
::AnnotationSecurity.reset
|
32
|
+
::AnnotationSecurity::ModelObserver.instance.reload_model_observer
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Security layer initialized"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/user_wrapper.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# = AnnotationSecurity::UserWrapper
|
6
|
+
#
|
7
|
+
# This class is not in use!
|
8
|
+
#
|
9
|
+
# Needed for evaluating relations, especially if the :as-option is used.
|
10
|
+
#
|
11
|
+
# Merges a user and a role. If a role is given,
|
12
|
+
#
|
13
|
+
class AnnotationSecurity::UserWrapper # :nodoc:
|
14
|
+
|
15
|
+
# Return user wrappers for the requested role. The role(s) will be
|
16
|
+
# determined with sending user.as_'role'.
|
17
|
+
# (Normally a user has a role only once, however it will work when he
|
18
|
+
# has many roles of the same kind as well)
|
19
|
+
def self.all_for_role(user,role_name)
|
20
|
+
return [] if user.nil?
|
21
|
+
user = user.__user__ if user.is_a? AnnotationSecurity::UserWrapper
|
22
|
+
return [new(user)] if role_name.nil?
|
23
|
+
roles = user.__send__("as_#{role_name}")
|
24
|
+
return [] if roles.blank?
|
25
|
+
roles = [roles] unless roles.is_a?(Array)
|
26
|
+
roles.compact.collect { |role| new(user,role) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(user,role=nil)
|
30
|
+
@user = user
|
31
|
+
@role = role
|
32
|
+
end
|
33
|
+
|
34
|
+
def id
|
35
|
+
@role? @role.id : @user.id
|
36
|
+
end
|
37
|
+
|
38
|
+
def __user__
|
39
|
+
@user
|
40
|
+
end
|
41
|
+
|
42
|
+
def __role__
|
43
|
+
@role
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(obj)
|
47
|
+
@user == obj or (!@role.nil? and @role == obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Try to send to role, user and policy of args[0]
|
51
|
+
#
|
52
|
+
def method_missing(symbol,*args,&block)
|
53
|
+
if @role && (@role.respond_to? symbol)
|
54
|
+
@role.__send__(symbol,*args,&block)
|
55
|
+
elsif @user.respond_to? symbol
|
56
|
+
@user.__send__(symbol,*args,&block)
|
57
|
+
elsif args.first.respond_to? :policy_for
|
58
|
+
args.first.policy_for(@user).__send__(symbol,*args[1..-1])
|
59
|
+
else
|
60
|
+
# This will raise a NoMethodError
|
61
|
+
@user.__send__(symbol,*args,&block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def is_a?(klass)
|
66
|
+
return true if super(klass)
|
67
|
+
if @role
|
68
|
+
@role.is_a?(klass)
|
69
|
+
else
|
70
|
+
@user.is_a?(klass)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|