annotation_security 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|