annotation_security 1.0.1 → 1.0.2
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.md +14 -0
- data/HOW-TO.md +275 -0
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/README.md +39 -0
- data/Rakefile +62 -55
- data/assets/app/helpers/annotation_security_helper.rb +8 -8
- data/assets/config/initializers/annotation_security.rb +11 -11
- data/assets/config/security/relations.rb +20 -20
- data/assets/vendor/plugins/annotation_security/init.rb +13 -13
- data/bin/annotation_security +7 -7
- data/lib/annotation_security/exceptions.rb +124 -124
- data/lib/annotation_security/exec.rb +188 -188
- data/lib/annotation_security/filters.rb +37 -37
- data/lib/annotation_security/includes/action_controller.rb +144 -143
- data/lib/annotation_security/includes/active_record.rb +27 -27
- data/lib/annotation_security/includes/helper.rb +215 -215
- data/lib/annotation_security/includes/resource.rb +84 -84
- data/lib/annotation_security/includes/role.rb +30 -30
- data/lib/annotation_security/includes/user.rb +26 -26
- data/lib/annotation_security/manager/policy_factory.rb +29 -29
- data/lib/annotation_security/manager/policy_manager.rb +79 -79
- data/lib/annotation_security/manager/relation_loader.rb +272 -272
- data/lib/annotation_security/manager/resource_manager.rb +36 -36
- data/lib/annotation_security/manager/right_loader.rb +87 -87
- data/lib/annotation_security/model_observer.rb +61 -61
- data/lib/annotation_security/policy/abstract_policy.rb +344 -344
- data/lib/annotation_security/policy/abstract_static_policy.rb +75 -75
- data/lib/annotation_security/policy/all_resources_policy.rb +20 -20
- data/lib/annotation_security/policy/rule.rb +340 -340
- data/lib/annotation_security/policy/rule_set.rb +138 -138
- data/lib/annotation_security/rails.rb +38 -38
- data/lib/annotation_security/user_wrapper.rb +73 -73
- data/lib/annotation_security/utils.rb +141 -141
- data/lib/annotation_security/version.rb +10 -0
- data/lib/annotation_security.rb +102 -97
- data/lib/extensions/action_controller.rb +32 -32
- data/lib/extensions/active_record.rb +34 -34
- data/lib/extensions/filter.rb +133 -133
- data/lib/extensions/object.rb +10 -10
- data/lib/security_context.rb +589 -551
- data/spec/annotation_security/exceptions_spec.rb +16 -16
- data/spec/annotation_security/includes/helper_spec.rb +82 -82
- data/spec/annotation_security/manager/policy_manager_spec.rb +15 -15
- data/spec/annotation_security/manager/resource_manager_spec.rb +17 -17
- data/spec/annotation_security/manager/right_loader_spec.rb +17 -17
- data/spec/annotation_security/policy/abstract_policy_spec.rb +16 -16
- data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -24
- data/spec/annotation_security/policy/rule_set_spec.rb +112 -112
- data/spec/annotation_security/policy/rule_spec.rb +77 -77
- data/spec/annotation_security/policy/test_policy_spec.rb +80 -80
- data/spec/annotation_security/security_context_spec.rb +78 -78
- data/spec/annotation_security/utils_spec.rb +73 -73
- data/spec/helper/test_controller.rb +65 -65
- data/spec/helper/test_helper.rb +5 -5
- data/spec/helper/test_relations.rb +6 -6
- data/spec/helper/test_resource.rb +38 -38
- data/spec/helper/test_role.rb +21 -21
- data/spec/helper/test_user.rb +31 -31
- data/spec/rails_stub.rb +37 -37
- metadata +94 -72
- data/CHANGELOG +0 -2
- data/HOW-TO +0 -261
- data/README +0 -39
@@ -1,340 +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
|
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
|