stffn-declarative_authorization 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/CHANGELOG +9 -0
  2. data/README.rdoc +22 -6
  3. data/app/controllers/authorization_rules_controller.rb +135 -14
  4. data/app/helpers/authorization_rules_helper.rb +96 -13
  5. data/app/views/authorization_rules/_change.erb +49 -0
  6. data/app/views/authorization_rules/_show_graph.erb +37 -0
  7. data/app/views/authorization_rules/_suggestion.erb +9 -0
  8. data/app/views/authorization_rules/_suggestions.erb +24 -0
  9. data/app/views/authorization_rules/change.html.erb +124 -0
  10. data/app/views/authorization_rules/graph.dot.erb +23 -4
  11. data/app/views/authorization_rules/graph.html.erb +1 -0
  12. data/app/views/authorization_rules/index.html.erb +3 -2
  13. data/app/views/authorization_usages/index.html.erb +2 -11
  14. data/config/routes.rb +2 -1
  15. data/lib/declarative_authorization/authorization.rb +87 -35
  16. data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
  17. data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
  18. data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
  19. data/lib/declarative_authorization/development_support/development_support.rb +243 -0
  20. data/lib/declarative_authorization/helper.rb +6 -2
  21. data/lib/declarative_authorization/in_controller.rb +254 -26
  22. data/lib/declarative_authorization/in_model.rb +27 -2
  23. data/lib/declarative_authorization/maintenance.rb +22 -8
  24. data/lib/declarative_authorization/obligation_scope.rb +14 -9
  25. data/lib/declarative_authorization/reader.rb +10 -2
  26. data/test/authorization_test.rb +44 -0
  27. data/test/controller_filter_resource_access_test.rb +385 -0
  28. data/test/controller_test.rb +14 -6
  29. data/test/helper_test.rb +21 -0
  30. data/test/maintenance_test.rb +26 -0
  31. data/test/model_test.rb +28 -0
  32. data/test/test_helper.rb +14 -1
  33. metadata +15 -5
  34. data/lib/declarative_authorization/authorization_rules_analyzer.rb +0 -138
  35. data/test/authorization_rules_analyzer_test.rb +0 -123
@@ -0,0 +1,243 @@
1
+
2
+ module Authorization
3
+ module DevelopmentSupport
4
+ class AbstractAnalyzer
5
+ attr_reader :engine
6
+
7
+ def initialize (engine)
8
+ @engine = engine
9
+ end
10
+
11
+ def roles
12
+ AnalyzerEngine.roles(engine)
13
+ end
14
+
15
+ def rules
16
+ roles.collect {|role| role.rules }.flatten
17
+ end
18
+ end
19
+
20
+ # Groups utility methods and classes to better work with authorization object
21
+ # model.
22
+ module AnalyzerEngine
23
+
24
+ def self.roles (engine)
25
+ Role.all(engine)
26
+ end
27
+
28
+ def self.relevant_roles (engine, users)
29
+ users.collect {|user| user.role_symbols.map {|role_sym| Role.for_sym(role_sym, engine)}}.
30
+ flatten.uniq.collect {|role| [role] + role.ancestors}.flatten.uniq
31
+ end
32
+
33
+ def self.rule_for_permission (engine, privilege, context, role)
34
+ AnalyzerEngine.roles(engine).
35
+ find {|cloned_role| cloned_role.to_sym == role.to_sym}.rules.find do |rule|
36
+ rule.contexts.include?(context) and rule.privileges.include?(privilege)
37
+ end
38
+ end
39
+
40
+ def self.apply_change (engine, change)
41
+ case change[0]
42
+ when :add_role
43
+ role_symbol = change[1]
44
+ if engine.roles.include?(role_symbol)
45
+ false
46
+ else
47
+ engine.roles << role_symbol
48
+ true
49
+ end
50
+ when :add_privilege
51
+ privilege, context, role = change[1,3]
52
+ role = Role.for_sym(role.to_sym, engine)
53
+ privilege = Privilege.for_sym(privilege.to_sym, engine)
54
+ if ([privilege] + privilege.ancestors).any? {|ancestor_privilege| ([role] + role.ancestors).any? {|ancestor_role| !ancestor_role.rules_for_permission(ancestor_privilege, context).empty?}}
55
+ false
56
+ else
57
+ engine.auth_rules << AuthorizationRule.new(role.to_sym,
58
+ [privilege.to_sym], [context])
59
+ true
60
+ end
61
+ when :remove_privilege
62
+ privilege, context, role = change[1,3]
63
+ role = Role.for_sym(role.to_sym, engine)
64
+ privilege = Privilege.for_sym(privilege.to_sym, engine)
65
+ rules_with_priv = role.rules_for_permission(privilege, context)
66
+ if rules_with_priv.empty?
67
+ false
68
+ else
69
+ rules_with_priv.each do |rule|
70
+ rule.rule.privileges.delete(privilege.to_sym)
71
+ engine.auth_rules.delete(rule.rule) if rule.rule.privileges.empty?
72
+ end
73
+ true
74
+ end
75
+ end
76
+ end
77
+
78
+ class Role
79
+ @@role_objects = {}
80
+ attr_reader :role
81
+ def initialize (role, rules, engine)
82
+ @role = role
83
+ @rules = rules
84
+ @engine = engine
85
+ end
86
+
87
+ def source_line
88
+ @rules.empty? ? nil : @rules.first.source_line
89
+ end
90
+ def source_file
91
+ @rules.empty? ? nil : @rules.first.source_file
92
+ end
93
+
94
+ # ancestors' privileges are included in in the current role
95
+ def ancestors (role_symbol = nil)
96
+ role_symbol ||= @role
97
+ (@engine.role_hierarchy[role_symbol] || []).
98
+ collect {|lower_role| ancestors(lower_role) }.flatten +
99
+ (role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
100
+ end
101
+ def descendants (role_symbol = nil)
102
+ role_symbol ||= @role
103
+ (@engine.rev_role_hierarchy[role_symbol] || []).
104
+ collect {|higher_role| descendants(higher_role) }.flatten +
105
+ (role_symbol == @role ? [] : [Role.for_sym(role_symbol, @engine)])
106
+ end
107
+
108
+ def rules
109
+ @rules ||= @engine.auth_rules.select {|rule| rule.role == @role}.
110
+ collect {|rule| Rule.new(rule, @engine)}
111
+ end
112
+ def rules_for_permission (privilege, context)
113
+ rules.select do |rule|
114
+ rule.matches?([@role], [privilege.to_sym], context)
115
+ end
116
+ end
117
+
118
+ def to_sym
119
+ @role
120
+ end
121
+ def self.for_sym (role_sym, engine)
122
+ @@role_objects[[role_sym, engine]] ||= new(role_sym, nil, engine)
123
+ end
124
+
125
+ def self.all (engine)
126
+ rules_by_role = engine.auth_rules.inject({}) do |memo, rule|
127
+ memo[rule.role] ||= []
128
+ memo[rule.role] << rule
129
+ memo
130
+ end
131
+ engine.roles.collect do |role|
132
+ new(role, (rules_by_role[role] || []).
133
+ collect {|rule| Rule.new(rule, engine)}, engine)
134
+ end
135
+ end
136
+ def self.all_for_privilege (privilege, context, engine)
137
+ privilege = privilege.is_a?(Symbol) ? Privilege.for_sym(privilege, engine) : privilege
138
+ privilege_symbols = ([privilege] + privilege.ancestors).map(&:to_sym)
139
+ all(engine).select {|role| role.rules.any? {|rule| rule.matches?([role.to_sym], privilege_symbols, context)}}.
140
+ collect {|role| [role] + role.descendants}.flatten.uniq
141
+ end
142
+ end
143
+
144
+ class Rule
145
+ @@rule_objects = {}
146
+ delegate :source_line, :source_file, :contexts, :matches?, :to => :@rule
147
+ attr_reader :rule
148
+ def initialize (rule, engine)
149
+ @rule = rule
150
+ @engine = engine
151
+ end
152
+ def privileges
153
+ PrivilegesSet.new(self, @engine, @rule.privileges.collect {|privilege| Privilege.for_sym(privilege, @engine) })
154
+ end
155
+ def self.for_rule (rule, engine)
156
+ @@rule_objects[[rule, engine]] ||= new(rule, engine)
157
+ end
158
+ end
159
+
160
+ class Privilege
161
+ @@privilege_objects = {}
162
+ def initialize (privilege, engine)
163
+ @privilege = privilege
164
+ @engine = engine
165
+ end
166
+
167
+ # Ancestor privileges are higher in the hierarchy.
168
+ # Doesn't take context into account.
169
+ def ancestors (priv_symbol = nil)
170
+ priv_symbol ||= @privilege
171
+ # context-specific?
172
+ (@engine.rev_priv_hierarchy[[priv_symbol, nil]] || []).
173
+ collect {|higher_priv| ancestors(higher_priv) }.flatten +
174
+ (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
175
+ end
176
+ def descendants (priv_symbol = nil)
177
+ priv_symbol ||= @privilege
178
+ # context-specific?
179
+ (@engine.privilege_hierarchy[priv_symbol] || []).
180
+ collect {|lower_priv, context| descendants(lower_priv) }.flatten +
181
+ (priv_symbol == @privilege ? [] : [Privilege.for_sym(priv_symbol, @engine)])
182
+ end
183
+
184
+ def rules
185
+ @rules ||= find_rules_for_privilege
186
+ end
187
+ def source_line
188
+ rules.empty? ? nil : rules.first.source_line
189
+ end
190
+ def source_file
191
+ rules.empty? ? nil : rules.first.source_file
192
+ end
193
+
194
+ def to_sym
195
+ @privilege
196
+ end
197
+ def self.for_sym (privilege_sym, engine)
198
+ @@privilege_objects[[privilege_sym, engine]] ||= new(privilege_sym, engine)
199
+ end
200
+
201
+ private
202
+ def find_rules_for_privilege
203
+ @engine.auth_rules.select {|rule| rule.privileges.include?(@privilege)}.
204
+ collect {|rule| Rule.for_rule(rule, @engine)}
205
+ end
206
+ end
207
+
208
+ class PrivilegesSet < Set
209
+ def initialize (*args)
210
+ if args.length > 2
211
+ @rule = args.shift
212
+ @engine = args.shift
213
+ end
214
+ super(*args)
215
+ end
216
+ def include? (privilege)
217
+ if privilege.is_a?(Symbol)
218
+ super(privilege_from_symbol(privilege))
219
+ else
220
+ super
221
+ end
222
+ end
223
+ def delete (privilege)
224
+ @rule.rule.privileges.delete(privilege.to_sym)
225
+ if privilege.is_a?(Symbol)
226
+ super(privilege_from_symbol(privilege))
227
+ else
228
+ super
229
+ end
230
+ end
231
+
232
+ def intersects? (privileges)
233
+ intersection(privileges).length > 0
234
+ end
235
+
236
+ private
237
+ def privilege_from_symbol (privilege_sym)
238
+ Privilege.for_sym(privilege_sym, @engine)
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -23,9 +23,13 @@ module Authorization
23
23
  # <% for user in @users %>
24
24
  # <%= link_to 'Edit', edit_user_path(user) if permitted_to? :update, user %>
25
25
  # <% end %>
26
+ #
27
+ # To pass in an object and override the context, you can use the optional
28
+ # options:
29
+ # permitted_to? :update, user, :context => :account
26
30
  #
27
- def permitted_to? (privilege, object_or_sym = nil, &block)
28
- controller.permitted_to?(privilege, object_or_sym, &block)
31
+ def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
32
+ controller.permitted_to?(privilege, object_or_sym, options, &block)
29
33
  end
30
34
 
31
35
  # While permitted_to? is used for authorization in views, in some cases
@@ -24,36 +24,38 @@ module Authorization
24
24
  #
25
25
  # See examples for Authorization::AuthorizationHelper #permitted_to?
26
26
  #
27
- def permitted_to? (privilege, object_or_sym = nil, &block)
28
- context = object = nil
29
- if object_or_sym.is_a?(Symbol)
30
- context = object_or_sym
31
- else
32
- object = object_or_sym
33
- end
34
- # TODO infer context also from self.class.name
35
- authorization_engine.permit?(privilege,
36
- {:user => current_user,
37
- :object => object,
38
- :context => context,
39
- :skip_attribute_test => object.nil?},
40
- &block)
27
+ # If no object or context is specified, the controller_name is used as
28
+ # context.
29
+ #
30
+ def permitted_to? (privilege, object_or_sym = nil, options = {}, &block)
31
+ permitted_to!(privilege, object_or_sym, options.merge(:non_bang => true), &block)
41
32
  end
42
33
 
43
- # Works similar to the permitted_to? method, but doesn't accept a block
44
- # and throws the authorization exceptions, just like Engine#permit!
45
- def permitted_to! (privilege, object_or_sym = nil)
34
+ # Works similar to the permitted_to? method, but
35
+ # throws the authorization exceptions, just like Engine#permit!
36
+ def permitted_to! (privilege, object_or_sym = nil, options = {}, &block)
46
37
  context = object = nil
47
- if object_or_sym.is_a?(Symbol)
38
+ if object_or_sym.nil?
39
+ context = self.class.controller_name.to_sym
40
+ elsif object_or_sym.is_a?(Symbol)
48
41
  context = object_or_sym
49
42
  else
50
43
  object = object_or_sym
51
44
  end
52
- authorization_engine.permit!(privilege,
53
- {:user => current_user,
54
- :object => object,
55
- :context => context,
56
- :skip_attribute_test => object.nil?})
45
+
46
+ non_bang = options.delete(:non_bang)
47
+ args = [
48
+ privilege,
49
+ {:user => current_user,
50
+ :object => object,
51
+ :context => context,
52
+ :skip_attribute_test => object.nil?}.merge(options)
53
+ ]
54
+ if non_bang
55
+ authorization_engine.permit?(*args, &block)
56
+ else
57
+ authorization_engine.permit!(*args, &block)
58
+ end
57
59
  end
58
60
 
59
61
  # While permitted_to? is used for authorization, in some cases
@@ -115,7 +117,36 @@ module Authorization
115
117
  end
116
118
  end
117
119
  end
118
-
120
+
121
+ def load_controller_object (context) # :nodoc:
122
+ instance_var = :"@#{context.to_s.singularize}"
123
+ model = context.to_s.classify.constantize
124
+ instance_variable_set(instance_var, model.find(params[:id]))
125
+ end
126
+
127
+ def load_parent_controller_object (parent_context) # :nodoc:
128
+ instance_var = :"@#{parent_context.to_s.singularize}"
129
+ model = parent_context.to_s.classify.constantize
130
+ instance_variable_set(instance_var, model.find(params[:"#{parent_context.to_s.singularize}_id"]))
131
+ end
132
+
133
+ def new_controller_object_from_params (context, parent_context) # :nodoc:
134
+ model_or_proxy = parent_context ?
135
+ instance_variable_get(:"@#{parent_context.to_s.singularize}").send(context.to_sym) :
136
+ context.to_s.classify.constantize
137
+ instance_var = :"@#{context.to_s.singularize}"
138
+ instance_variable_set(instance_var,
139
+ model_or_proxy.new(params[context.to_s.singularize]))
140
+ end
141
+
142
+ def new_controller_object_for_collection (context, parent_context) # :nodoc:
143
+ model_or_proxy = parent_context ?
144
+ instance_variable_get(:"@#{parent_context.to_s.singularize}").send(context.to_sym) :
145
+ context.to_s.classify.constantize
146
+ instance_var = :"@#{context.to_s.singularize}"
147
+ instance_variable_set(instance_var, model_or_proxy.new)
148
+ end
149
+
119
150
  module ClassMethods
120
151
  #
121
152
  # Defines a filter to be applied according to the authorization of the
@@ -258,6 +289,176 @@ module Authorization
258
289
  end
259
290
  end
260
291
  end
292
+
293
+ # To DRY up the filter_access_to statements in restful controllers,
294
+ # filter_resource_access combines typical filter_access_to and
295
+ # before_filter calls, which set up the instance variables.
296
+ #
297
+ # The simplest case are top-level resource controllers with only the
298
+ # seven CRUD methods, e.g.
299
+ # class CompanyController < ApplicationController
300
+ # filter_resource_access
301
+ #
302
+ # def index...
303
+ # end
304
+ # Here, all CRUD actions are protected through a filter_access_to :all
305
+ # statement. :+attribute_check+ is enabled for all actions except for
306
+ # the collection action :+index+. To have an object for attribute checks
307
+ # available, filter_resource_access will set the instance variable
308
+ # @+company+ in before filters. For the member actions (:+show+, :+edit+,
309
+ # :+update+, :+destroy+) @company is set to Company.find(params[:id]).
310
+ # For +new+ actions (:+new+, :+create+), filter_resource_access creates
311
+ # a new object from company parameters: Company.new(params[:company].
312
+ #
313
+ # For nested resources, the parent object may be loaded automatically.
314
+ # class BranchController < ApplicationController
315
+ # filter_resource_access :nested_in => :companies
316
+ # end
317
+ # Again, the CRUD actions are protected. Now, for all CRUD actions,
318
+ # the parent object @company is loaded from params[:company_id]. It is
319
+ # also used when creating @branch for +new+ actions. Here, attribute_check
320
+ # is enabled for the collection :+index+ as well, checking attributes on a
321
+ # @company.branches.new method.
322
+ #
323
+ # You can override the default object loading by implementing any of the
324
+ # following instance methods on the controller. Examples are given for the
325
+ # BranchController (with +nested_in+ set to :+companies+):
326
+ # [+new_branch_from_params+]
327
+ # Used for +new+ actions.
328
+ # [+new_branch_for_collection+]
329
+ # Used for +collection+ actions if the +nested_in+ option is set.
330
+ # [+load_branch+]
331
+ # Used for +member+ actions.
332
+ # [+load_company+]
333
+ # Used for all +new+, +member+, and +collection+ actions if the
334
+ # +nested_in+ option is set.
335
+ #
336
+ # All options:
337
+ # [:+member+]
338
+ # Member methods are actions like +show+, which have an params[:id] from
339
+ # which to load the controller object and assign it to @controller_name,
340
+ # e.g. @+branch+. By default, member actions are [:+show+, :+edit+, :+update+,
341
+ # :+destroy+].
342
+ # [:+additional_member+]
343
+ # Allows to add additional member actions to the default resource +member+
344
+ # actions.
345
+ # [:+collection+]
346
+ # Collection actions are like :+index+, actions without any controller object
347
+ # to check attributes of. If +nested_in+ is given, a new object is
348
+ # created from the parent object, e.g. @company.branches.new. Without
349
+ # +nested_in+, attribute check is deactivated for these actions. By
350
+ # default, collection is set to :+index+.
351
+ # [:+additional_collection+]
352
+ # Allows to add additional collaction actions to the default resource +collection+
353
+ # actions.
354
+ # [:+new+]
355
+ # +new+ methods are actions such as +new+ and +create+, which don't
356
+ # receive a params[:id] to load an object from, but
357
+ # a params[:controller_name_singular] hash with attributes for a new
358
+ # object. The attributes will be used here to create a new object and
359
+ # check the object against the authorization rules. The object is
360
+ # assigned to @controller_name_singular, e.g. @branch.
361
+ #
362
+ # If +nested_in+ is given, the new object
363
+ # is created from the parent_object.controller_name
364
+ # proxy, e.g. company.branches.new(params[:branch]). By default,
365
+ # +new+ is set to [:new, :create].
366
+ # [:+additional_new+]
367
+ # Allows to add additional new actions to the default resource +new+ actions.
368
+ # [:+context+]
369
+ # The context is used to determine the model to load objects from for the
370
+ # before_filters and the context of privileges to use in authorization
371
+ # checks.
372
+ # [:+nested_in+]
373
+ # Specifies the parent controller if the resource is nested in another
374
+ # one. This is used to automatically load the parent object, e.g.
375
+ # @+company+ from params[:company_id] for a BranchController nested in
376
+ # a CompanyController.
377
+ # [:+no_attribute_check+]
378
+ # Allows to set actions for which no attribute check should be perfomed.
379
+ # See filter_access_to on details. By default, with no +nested_in+,
380
+ # +no_attribute_check+ is set to all collections. If +nested_in+ is given
381
+ # +no_attribute_check+ is empty by default.
382
+ #
383
+ def filter_resource_access(options = {})
384
+ options = {
385
+ :new => [:new, :create],
386
+ :additional_new => nil,
387
+ :member => [:show, :edit, :update, :destroy],
388
+ :additional_member => nil,
389
+ :collection => [:index],
390
+ :additional_collection => nil,
391
+ #:new_method_for_collection => nil, # only symbol method name
392
+ #:new_method => nil, # only symbol method name
393
+ #:load_method => nil, # only symbol method name
394
+ :no_attribute_check => nil,
395
+ :context => nil,
396
+ :nested_in => nil,
397
+ }.merge(options)
398
+
399
+ new_actions = actions_from_option(options[:new]).merge(
400
+ actions_from_option(options[:additional_new]))
401
+ members = actions_from_option(options[:member]).merge(
402
+ actions_from_option(options[:additional_member]))
403
+ collections = actions_from_option(options[:collection]).merge(
404
+ actions_from_option(options[:additional_collection]))
405
+
406
+ options[:no_attribute_check] ||= collections.keys unless options[:nested_in]
407
+
408
+ unless options[:nested_in].blank?
409
+ load_method = :"load_#{options[:nested_in].to_s.singularize}"
410
+ before_filter do |controller|
411
+ if controller.respond_to?(load_method)
412
+ controller.send(load_method)
413
+ else
414
+ controller.send(:load_parent_controller_object, options[:nested_in])
415
+ end
416
+ end
417
+
418
+ new_for_collection_method = :"new_#{controller_name.singularize}_for_collection"
419
+ before_filter :only => collections.keys do |controller|
420
+ # new_for_collection
421
+ if controller.respond_to?(new_for_collection_method)
422
+ controller.send(new_for_collection_method)
423
+ else
424
+ controller.send(:new_controller_object_for_collection,
425
+ options[:context] || controller_name, options[:nested_in])
426
+ end
427
+ end
428
+ end
429
+
430
+ new_from_params_method = :"new_#{controller_name.singularize}_from_params"
431
+ before_filter :only => new_actions.keys do |controller|
432
+ # new_from_params
433
+ if controller.respond_to?(new_from_params_method)
434
+ controller.send(new_from_params_method)
435
+ else
436
+ controller.send(:new_controller_object_from_params,
437
+ options[:context] || controller_name, options[:nested_in])
438
+ end
439
+ end
440
+ load_method = :"load_#{controller_name.singularize}"
441
+ before_filter :only => members.keys do |controller|
442
+ # load controller object
443
+ if controller.respond_to?(load_method)
444
+ controller.send(load_method)
445
+ else
446
+ controller.send(:load_controller_object, options[:context] || controller_name)
447
+ end
448
+ end
449
+ filter_access_to :all, :attribute_check => true, :context => options[:context]
450
+
451
+ members.merge(new_actions).merge(collections).each do |action, privilege|
452
+ if action != privilege or (options[:no_attribute_check] and options[:no_attribute_check].include?(action))
453
+ filter_options = {
454
+ :context => options[:context],
455
+ :attribute_check => !options[:no_attribute_check] || !options[:no_attribute_check].include?(action)
456
+ }
457
+ filter_options[:require] = privilege if action != privilege
458
+ filter_access_to(action, filter_options)
459
+ end
460
+ end
461
+ end
261
462
 
262
463
  protected
263
464
  def filter_access_permissions # :nodoc:
@@ -273,6 +474,26 @@ module Authorization
273
474
  def filter_access_permissions? # :nodoc:
274
475
  class_variable_defined?(:@@declarative_authorization_permissions)
275
476
  end
477
+
478
+ def actions_from_option (option) # :nodoc:
479
+ case option
480
+ when nil
481
+ {}
482
+ when Symbol, String
483
+ {option.to_sym => option.to_sym}
484
+ when Hash
485
+ option
486
+ when Enumerable
487
+ option.each_with_object({}) do |action, hash|
488
+ if action.is_a?(Array)
489
+ raise "Unexpected option format: #{option.inspect}" if action.length != 2
490
+ hash[action.first] = action.last
491
+ else
492
+ hash[action.to_sym] = action.to_sym
493
+ end
494
+ end
495
+ end
496
+ end
276
497
  end
277
498
  end
278
499
 
@@ -332,8 +553,15 @@ module Authorization
332
553
  instance_var = :"@#{load_object_model.name.underscore}"
333
554
  object = contr.instance_variable_get(instance_var)
334
555
  unless object
335
- # catch ActiveRecord::RecordNotFound?
336
- object = load_object_model.find(contr.params[:id])
556
+ begin
557
+ object = load_object_model.find(contr.params[:id])
558
+ rescue ActiveRecord::RecordNotFound
559
+ logger.debug("filter_access_to tried to find " +
560
+ "#{load_object_model.inspect} from params[:id] " +
561
+ "(#{contr.params[:id].inspect}), because attribute_check is enabled " +
562
+ "and #{instance_var.to_s} isn't set.")
563
+ raise
564
+ end
337
565
  contr.instance_variable_set(instance_var, object)
338
566
  end
339
567
  object