annotation_security 1.0.2 → 1.3.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 +22 -0
- data/HOW-TO +261 -0
- data/{LICENSE → MIT-LICENSE} +1 -1
- data/README +39 -0
- data/Rakefile +53 -62
- 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 +14 -14
- data/bin/annotation_security +7 -7
- data/lib/annotation_security.rb +94 -103
- data/lib/annotation_security/exceptions.rb +124 -124
- data/lib/annotation_security/exec.rb +188 -188
- 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 +87 -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/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 +22 -39
- data/lib/{extensions → annotation_security/rails/2/extensions}/filter.rb +131 -133
- data/lib/annotation_security/rails/2/includes/action_controller.rb +144 -0
- data/lib/annotation_security/rails/2/includes/active_record.rb +28 -0
- data/lib/annotation_security/rails/2/initializer.rb +35 -0
- data/lib/annotation_security/{model_observer.rb → rails/2/model_observer.rb} +61 -61
- data/lib/annotation_security/rails/3/extensions/filter.rb +28 -0
- data/lib/annotation_security/{includes → rails/3/includes}/action_controller.rb +143 -144
- data/lib/annotation_security/{includes → rails/3/includes}/active_record.rb +27 -27
- data/lib/annotation_security/rails/3/initializer.rb +40 -0
- data/lib/annotation_security/rails/3/model_observer.rb +61 -0
- data/lib/annotation_security/rails/extensions.rb +21 -0
- data/lib/{extensions → annotation_security/rails/extensions}/action_controller.rb +31 -32
- data/lib/{extensions → annotation_security/rails/extensions}/active_record.rb +33 -34
- data/lib/{extensions → annotation_security/rails/extensions}/object.rb +10 -10
- data/lib/annotation_security/{filters.rb → rails/filters.rb} +37 -37
- data/lib/annotation_security/user_wrapper.rb +73 -73
- data/lib/annotation_security/utils.rb +141 -141
- data/lib/security_context.rb +588 -589
- 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 +129 -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 +44 -37
- metadata +110 -96
- data/CHANGELOG.md +0 -14
- data/HOW-TO.md +0 -275
- data/README.md +0 -39
- data/lib/annotation_security/version.rb +0 -10
@@ -1,76 +1,76 @@
|
|
1
|
-
#
|
2
|
-
# = lib/annotation_security/policy/abstract_static_policy.rb
|
3
|
-
#
|
4
|
-
|
5
|
-
# Abstract superclass for all static policies.
|
6
|
-
# For each policy there is a static policy that is responsible for evaluating
|
7
|
-
# static rules.
|
8
|
-
#
|
9
|
-
class AnnotationSecurity::AbstractStaticPolicy < AnnotationSecurity::AbstractPolicy # :nodoc:
|
10
|
-
|
11
|
-
# Rules that are defined for all resource types can be found here.
|
12
|
-
def self.all_resources_policy # :nodoc:
|
13
|
-
AllResourcesPolicy.static_policy_class
|
14
|
-
end
|
15
|
-
|
16
|
-
# Sets the dynamic policy class this policy class belongs to
|
17
|
-
def self.belongs_to(dynamic_policy_class) #:nodoc:
|
18
|
-
@dynamic_policy_class = dynamic_policy_class
|
19
|
-
end
|
20
|
-
|
21
|
-
# A static policy class has no other corresponding static policy class.
|
22
|
-
# This should never be called.
|
23
|
-
def self.static_policy_class #:nodoc:
|
24
|
-
method_missing(:static_policy_class)
|
25
|
-
end
|
26
|
-
|
27
|
-
# The corresponding dynamic policy class.
|
28
|
-
#
|
29
|
-
def self.dynamic_policy_class #:nodoc:
|
30
|
-
@dynamic_policy_class
|
31
|
-
end
|
32
|
-
|
33
|
-
# Returns true iif this is policy class is responsible for static rules.
|
34
|
-
#
|
35
|
-
def self.static? # :nodoc:
|
36
|
-
true
|
37
|
-
end
|
38
|
-
|
39
|
-
# Rule set for this classes resource type
|
40
|
-
#
|
41
|
-
def self.rule_set # :nodoc:
|
42
|
-
# Each dynamic and static policy pair shares one rule set.
|
43
|
-
dynamic_policy_class.rule_set
|
44
|
-
end
|
45
|
-
|
46
|
-
# If possible, redirects the rule to the static side.
|
47
|
-
# Returns a rule object or nil.
|
48
|
-
def self.use_static_rule(symbol) #:nodoc:
|
49
|
-
nil # This is not possible
|
50
|
-
end
|
51
|
-
|
52
|
-
# Evaluate the rules in static mode.
|
53
|
-
# Rules that cannot be evaluated are skipped.
|
54
|
-
# * +rules+ array of symbols
|
55
|
-
# Throws a SecurityViolationError if a rule fails,
|
56
|
-
# returns true if all rules succeed.
|
57
|
-
def evaluate_statically(rules) #:nodoc:
|
58
|
-
rules.each do |rule|
|
59
|
-
if has_rule?(rule) && !__send__(rule)
|
60
|
-
raise_access_denied(rule)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
true
|
64
|
-
end
|
65
|
-
|
66
|
-
# Evaluate a rule that is defined with a proc
|
67
|
-
# * +symbol+ Name of the rule
|
68
|
-
# * +user+ user object that has to fulfill the rule
|
69
|
-
# * +args+ List of additional arguments
|
70
|
-
def evaluate_rule(rule,user,args) #:nodoc:
|
71
|
-
# In contrast to AbstractPolicy#evaluate_rule,
|
72
|
-
# no resource is passed as argument
|
73
|
-
get_rule!(rule).evaluate(self,user,*args)
|
74
|
-
end
|
75
|
-
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/policy/abstract_static_policy.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
# Abstract superclass for all static policies.
|
6
|
+
# For each policy there is a static policy that is responsible for evaluating
|
7
|
+
# static rules.
|
8
|
+
#
|
9
|
+
class AnnotationSecurity::AbstractStaticPolicy < AnnotationSecurity::AbstractPolicy # :nodoc:
|
10
|
+
|
11
|
+
# Rules that are defined for all resource types can be found here.
|
12
|
+
def self.all_resources_policy # :nodoc:
|
13
|
+
AllResourcesPolicy.static_policy_class
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sets the dynamic policy class this policy class belongs to
|
17
|
+
def self.belongs_to(dynamic_policy_class) #:nodoc:
|
18
|
+
@dynamic_policy_class = dynamic_policy_class
|
19
|
+
end
|
20
|
+
|
21
|
+
# A static policy class has no other corresponding static policy class.
|
22
|
+
# This should never be called.
|
23
|
+
def self.static_policy_class #:nodoc:
|
24
|
+
method_missing(:static_policy_class)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The corresponding dynamic policy class.
|
28
|
+
#
|
29
|
+
def self.dynamic_policy_class #:nodoc:
|
30
|
+
@dynamic_policy_class
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns true iif this is policy class is responsible for static rules.
|
34
|
+
#
|
35
|
+
def self.static? # :nodoc:
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Rule set for this classes resource type
|
40
|
+
#
|
41
|
+
def self.rule_set # :nodoc:
|
42
|
+
# Each dynamic and static policy pair shares one rule set.
|
43
|
+
dynamic_policy_class.rule_set
|
44
|
+
end
|
45
|
+
|
46
|
+
# If possible, redirects the rule to the static side.
|
47
|
+
# Returns a rule object or nil.
|
48
|
+
def self.use_static_rule(symbol) #:nodoc:
|
49
|
+
nil # This is not possible
|
50
|
+
end
|
51
|
+
|
52
|
+
# Evaluate the rules in static mode.
|
53
|
+
# Rules that cannot be evaluated are skipped.
|
54
|
+
# * +rules+ array of symbols
|
55
|
+
# Throws a SecurityViolationError if a rule fails,
|
56
|
+
# returns true if all rules succeed.
|
57
|
+
def evaluate_statically(rules) #:nodoc:
|
58
|
+
rules.each do |rule|
|
59
|
+
if has_rule?(rule) && !__send__(rule)
|
60
|
+
raise_access_denied(rule)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Evaluate a rule that is defined with a proc
|
67
|
+
# * +symbol+ Name of the rule
|
68
|
+
# * +user+ user object that has to fulfill the rule
|
69
|
+
# * +args+ List of additional arguments
|
70
|
+
def evaluate_rule(rule,user,args) #:nodoc:
|
71
|
+
# In contrast to AbstractPolicy#evaluate_rule,
|
72
|
+
# no resource is passed as argument
|
73
|
+
get_rule!(rule).evaluate(self,user,*args)
|
74
|
+
end
|
75
|
+
|
76
76
|
end
|
@@ -1,21 +1,21 @@
|
|
1
|
-
#
|
2
|
-
# = lib/annotation_security/policy/all_resources_policy.rb
|
3
|
-
#
|
4
|
-
# By default, two relations are provided for all resources.
|
5
|
-
#
|
6
|
-
# The system relation +logged_in+ evaluates to true if the provided
|
7
|
-
# credentials are not nil.
|
8
|
-
# logged_in(:system, :require_credential => false) {|u| not u.nil?}
|
9
|
-
#
|
10
|
-
# The relation +self+ is true when the accessed resource is the current user
|
11
|
-
# himself or a role that belongs to the current user.
|
12
|
-
# __self__ { |user, resource| resource.is_user?(user) }
|
13
|
-
#
|
14
|
-
AnnotationSecurity.define_relations :all_resources do
|
15
|
-
|
16
|
-
# can be used as "self" in a right definition
|
17
|
-
# success if the accessed resource is the user himself or one of his roles
|
18
|
-
__self__ { |user, resource| resource.is_user?(user) }
|
19
|
-
|
20
|
-
logged_in(:system, :require_credential => false) {|u| not u.nil?}
|
1
|
+
#
|
2
|
+
# = lib/annotation_security/policy/all_resources_policy.rb
|
3
|
+
#
|
4
|
+
# By default, two relations are provided for all resources.
|
5
|
+
#
|
6
|
+
# The system relation +logged_in+ evaluates to true if the provided
|
7
|
+
# credentials are not nil.
|
8
|
+
# logged_in(:system, :require_credential => false) {|u| not u.nil?}
|
9
|
+
#
|
10
|
+
# The relation +self+ is true when the accessed resource is the current user
|
11
|
+
# himself or a role that belongs to the current user.
|
12
|
+
# __self__ { |user, resource| resource.is_user?(user) }
|
13
|
+
#
|
14
|
+
AnnotationSecurity.define_relations :all_resources do
|
15
|
+
|
16
|
+
# can be used as "self" in a right definition
|
17
|
+
# success if the accessed resource is the user himself or one of his roles
|
18
|
+
__self__ { |user, resource| resource.is_user?(user) }
|
19
|
+
|
20
|
+
logged_in(:system, :require_credential => false) {|u| not u.nil?}
|
21
21
|
end
|
@@ -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
|