stffn-declarative_authorization 0.3.0 → 0.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 +9 -0
- data/README.rdoc +22 -6
- data/app/controllers/authorization_rules_controller.rb +135 -14
- data/app/helpers/authorization_rules_helper.rb +96 -13
- data/app/views/authorization_rules/_change.erb +49 -0
- data/app/views/authorization_rules/_show_graph.erb +37 -0
- data/app/views/authorization_rules/_suggestion.erb +9 -0
- data/app/views/authorization_rules/_suggestions.erb +24 -0
- data/app/views/authorization_rules/change.html.erb +124 -0
- data/app/views/authorization_rules/graph.dot.erb +23 -4
- data/app/views/authorization_rules/graph.html.erb +1 -0
- data/app/views/authorization_rules/index.html.erb +3 -2
- data/app/views/authorization_usages/index.html.erb +2 -11
- data/config/routes.rb +2 -1
- data/lib/declarative_authorization/authorization.rb +87 -35
- data/lib/declarative_authorization/development_support/analyzer.rb +252 -0
- data/lib/declarative_authorization/development_support/change_analyzer.rb +253 -0
- data/lib/declarative_authorization/development_support/change_supporter.rb +578 -0
- data/lib/declarative_authorization/development_support/development_support.rb +243 -0
- data/lib/declarative_authorization/helper.rb +6 -2
- data/lib/declarative_authorization/in_controller.rb +254 -26
- data/lib/declarative_authorization/in_model.rb +27 -2
- data/lib/declarative_authorization/maintenance.rb +22 -8
- data/lib/declarative_authorization/obligation_scope.rb +14 -9
- data/lib/declarative_authorization/reader.rb +10 -2
- data/test/authorization_test.rb +44 -0
- data/test/controller_filter_resource_access_test.rb +385 -0
- data/test/controller_test.rb +14 -6
- data/test/helper_test.rb +21 -0
- data/test/maintenance_test.rb +26 -0
- data/test/model_test.rb +28 -0
- data/test/test_helper.rb +14 -1
- metadata +15 -5
- data/lib/declarative_authorization/authorization_rules_analyzer.rb +0 -138
- 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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
44
|
-
#
|
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.
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
336
|
-
|
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
|