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.
Files changed (62) hide show
  1. data/CHANGELOG +2 -0
  2. data/HOW-TO +261 -0
  3. data/MIT-LICENSE +18 -0
  4. data/README +39 -0
  5. data/Rakefile +56 -0
  6. data/assets/app/helpers/annotation_security_helper.rb +9 -0
  7. data/assets/config/initializers/annotation_security.rb +12 -0
  8. data/assets/config/security/relations.rb +20 -0
  9. data/assets/config/security/rights.yml +16 -0
  10. data/assets/vendor/plugins/annotation_security/init.rb +14 -0
  11. data/bin/annotation_security +8 -0
  12. data/lib/annotation_security/exceptions.rb +125 -0
  13. data/lib/annotation_security/exec.rb +189 -0
  14. data/lib/annotation_security/filters.rb +38 -0
  15. data/lib/annotation_security/includes/action_controller.rb +144 -0
  16. data/lib/annotation_security/includes/active_record.rb +28 -0
  17. data/lib/annotation_security/includes/helper.rb +215 -0
  18. data/lib/annotation_security/includes/resource.rb +85 -0
  19. data/lib/annotation_security/includes/role.rb +31 -0
  20. data/lib/annotation_security/includes/user.rb +27 -0
  21. data/lib/annotation_security/manager/policy_factory.rb +30 -0
  22. data/lib/annotation_security/manager/policy_manager.rb +80 -0
  23. data/lib/annotation_security/manager/relation_loader.rb +273 -0
  24. data/lib/annotation_security/manager/resource_manager.rb +36 -0
  25. data/lib/annotation_security/manager/right_loader.rb +88 -0
  26. data/lib/annotation_security/model_observer.rb +61 -0
  27. data/lib/annotation_security/policy/abstract_policy.rb +345 -0
  28. data/lib/annotation_security/policy/abstract_static_policy.rb +76 -0
  29. data/lib/annotation_security/policy/all_resources_policy.rb +21 -0
  30. data/lib/annotation_security/policy/rule.rb +340 -0
  31. data/lib/annotation_security/policy/rule_set.rb +139 -0
  32. data/lib/annotation_security/rails.rb +39 -0
  33. data/lib/annotation_security/user_wrapper.rb +74 -0
  34. data/lib/annotation_security/utils.rb +142 -0
  35. data/lib/annotation_security.rb +98 -0
  36. data/lib/extensions/action_controller.rb +33 -0
  37. data/lib/extensions/active_record.rb +35 -0
  38. data/lib/extensions/filter.rb +134 -0
  39. data/lib/extensions/object.rb +11 -0
  40. data/lib/security_context.rb +551 -0
  41. data/spec/annotation_security/exceptions_spec.rb +17 -0
  42. data/spec/annotation_security/includes/helper_spec.rb +82 -0
  43. data/spec/annotation_security/manager/policy_manager_spec.rb +15 -0
  44. data/spec/annotation_security/manager/resource_manager_spec.rb +17 -0
  45. data/spec/annotation_security/manager/right_loader_spec.rb +17 -0
  46. data/spec/annotation_security/policy/abstract_policy_spec.rb +17 -0
  47. data/spec/annotation_security/policy/all_resources_policy_spec.rb +24 -0
  48. data/spec/annotation_security/policy/rule_set_spec.rb +112 -0
  49. data/spec/annotation_security/policy/rule_spec.rb +78 -0
  50. data/spec/annotation_security/policy/test_policy_spec.rb +81 -0
  51. data/spec/annotation_security/security_context_spec.rb +78 -0
  52. data/spec/annotation_security/utils_spec.rb +74 -0
  53. data/spec/helper/test_controller.rb +66 -0
  54. data/spec/helper/test_helper.rb +5 -0
  55. data/spec/helper/test_relations.rb +7 -0
  56. data/spec/helper/test_resource.rb +39 -0
  57. data/spec/helper/test_rights.yml +5 -0
  58. data/spec/helper/test_role.rb +22 -0
  59. data/spec/helper/test_user.rb +32 -0
  60. data/spec/rails_stub.rb +38 -0
  61. data/spec/spec_helper.rb +43 -0
  62. 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