ae_declarative_authorization 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile.lock +2 -2
- data/README.md +24 -24
- data/README.rdoc +29 -29
- data/gemfiles/rails4252.gemfile.lock +29 -1
- data/gemfiles/rails4271.gemfile.lock +2 -2
- data/gemfiles/rails507.gemfile +1 -0
- data/gemfiles/rails507.gemfile.lock +30 -2
- data/gemfiles/rails516.gemfile +1 -0
- data/gemfiles/rails521.gemfile +1 -0
- data/gemfiles/rails521.gemfile.lock +2 -2
- data/lib/declarative_authorization.rb +10 -5
- data/lib/declarative_authorization/controller/dsl.rb +208 -0
- data/lib/declarative_authorization/controller/grape.rb +79 -0
- data/lib/declarative_authorization/controller/rails.rb +340 -0
- data/lib/declarative_authorization/controller/runtime.rb +149 -0
- data/lib/declarative_authorization/controller_permission.rb +82 -0
- data/lib/declarative_authorization/reader.rb +2 -1
- data/lib/declarative_authorization/version.rb +1 -1
- data/log/test.log +36953 -12956
- data/pkg/ae_declarative_authorization-0.9.0.gem +0 -0
- data/pkg/ae_declarative_authorization-0.9.0.tim1.gem +0 -0
- data/test/grape_api_test.rb +508 -0
- data/test/profiles/access_checking +20 -0
- data/test/{controller_test.rb → rails_controller_test.rb} +2 -2
- data/test/test_helper.rb +14 -71
- data/test/test_support/grape.rb +93 -0
- data/test/test_support/rails.rb +69 -0
- metadata +18 -9
- data/gemfiles/rails516.gemfile.lock +0 -136
- data/lib/declarative_authorization/in_controller.rb +0 -713
- data/pkg/ae_declarative_authorization-0.7.1.gem +0 -0
- data/pkg/ae_declarative_authorization-0.8.0.gem +0 -0
@@ -1,713 +0,0 @@
|
|
1
|
-
# Authorization::AuthorizationInController
|
2
|
-
require File.dirname(__FILE__) + '/authorization.rb'
|
3
|
-
|
4
|
-
module Authorization
|
5
|
-
module AuthorizationInController
|
6
|
-
|
7
|
-
def self.included(base) # :nodoc:
|
8
|
-
base.extend(ClassMethods)
|
9
|
-
base.module_eval do
|
10
|
-
before_action(:filter_access_filter) if method_defined?(:filter_access_filter)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
DEFAULT_DENY = false
|
15
|
-
|
16
|
-
# If attribute_check is set for filter_access_to, decl_auth_context will try to
|
17
|
-
# load the appropriate object from the current controller's model with
|
18
|
-
# the id from params[:id]. If that fails, a 404 Not Found is often the
|
19
|
-
# right way to handle the error. If you have additional measures in place
|
20
|
-
# that restricts the find scope, handling this error as a permission denied
|
21
|
-
# might be a better way. Set failed_auto_loading_is_not_found to false
|
22
|
-
# for the latter behavior.
|
23
|
-
@@failed_auto_loading_is_not_found = true
|
24
|
-
def self.failed_auto_loading_is_not_found?
|
25
|
-
@@failed_auto_loading_is_not_found
|
26
|
-
end
|
27
|
-
def self.failed_auto_loading_is_not_found=(new_value)
|
28
|
-
@@failed_auto_loading_is_not_found = new_value
|
29
|
-
end
|
30
|
-
|
31
|
-
# Returns the Authorization::Engine for the current controller.
|
32
|
-
def authorization_engine
|
33
|
-
@authorization_engine ||= Authorization::Engine.instance
|
34
|
-
end
|
35
|
-
|
36
|
-
# If the current user meets the given privilege, permitted_to? returns true
|
37
|
-
# and yields to the optional block. The attribute checks that are defined
|
38
|
-
# in the authorization rules are only evaluated if an object is given
|
39
|
-
# for context.
|
40
|
-
#
|
41
|
-
# See examples for Authorization::AuthorizationHelper #permitted_to?
|
42
|
-
#
|
43
|
-
# If no object or context is specified, the controller_name is used as
|
44
|
-
# context.
|
45
|
-
#
|
46
|
-
def permitted_to?(privilege, object_or_sym = nil, options = {})
|
47
|
-
if authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, false))
|
48
|
-
yield if block_given?
|
49
|
-
true
|
50
|
-
else
|
51
|
-
false
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Works similar to the permitted_to? method, but
|
56
|
-
# throws the authorization exceptions, just like Engine#permit!
|
57
|
-
def permitted_to!(privilege, object_or_sym = nil, options = {})
|
58
|
-
authorization_engine.permit!(privilege, options_for_permit(object_or_sym, options, true))
|
59
|
-
end
|
60
|
-
|
61
|
-
# While permitted_to? is used for authorization, in some cases
|
62
|
-
# content should only be shown to some users without being concerned
|
63
|
-
# with authorization. E.g. to only show the most relevant menu options
|
64
|
-
# to a certain group of users. That is what has_role? should be used for.
|
65
|
-
def has_role?(*roles)
|
66
|
-
user_roles = authorization_engine.roles_for(current_user)
|
67
|
-
result = roles.all? do |role|
|
68
|
-
user_roles.include?(role)
|
69
|
-
end
|
70
|
-
yield if result and block_given?
|
71
|
-
result
|
72
|
-
end
|
73
|
-
|
74
|
-
# Intended to be used where you want to allow users with any single listed role to view
|
75
|
-
# the content in question
|
76
|
-
def has_any_role?(*roles)
|
77
|
-
user_roles = authorization_engine.roles_for(current_user)
|
78
|
-
result = roles.any? do |role|
|
79
|
-
user_roles.include?(role)
|
80
|
-
end
|
81
|
-
yield if result and block_given?
|
82
|
-
result
|
83
|
-
end
|
84
|
-
|
85
|
-
# As has_role? except checks all roles included in the role hierarchy
|
86
|
-
def has_role_with_hierarchy?(*roles)
|
87
|
-
user_roles = authorization_engine.roles_with_hierarchy_for(current_user)
|
88
|
-
result = roles.all? do |role|
|
89
|
-
user_roles.include?(role)
|
90
|
-
end
|
91
|
-
yield if result and block_given?
|
92
|
-
result
|
93
|
-
end
|
94
|
-
|
95
|
-
# As has_any_role? except checks all roles included in the role hierarchy
|
96
|
-
def has_any_role_with_hierarchy?(*roles)
|
97
|
-
user_roles = authorization_engine.roles_with_hierarchy_for(current_user)
|
98
|
-
result = roles.any? do |role|
|
99
|
-
user_roles.include?(role)
|
100
|
-
end
|
101
|
-
yield if result and block_given?
|
102
|
-
result
|
103
|
-
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
def filter_access_filter # :nodoc:
|
107
|
-
permissions = self.class.all_filter_access_permissions
|
108
|
-
all_permissions = permissions.select {|p| p.actions.include?(:all)}
|
109
|
-
matching_permissions = permissions.select {|p| p.matches?(action_name)}
|
110
|
-
allowed = false
|
111
|
-
auth_exception = nil
|
112
|
-
begin
|
113
|
-
allowed = if !matching_permissions.empty?
|
114
|
-
matching_permissions.all? {|perm| perm.permit!(self)}
|
115
|
-
elsif !all_permissions.empty?
|
116
|
-
all_permissions.all? {|perm| perm.permit!(self)}
|
117
|
-
else
|
118
|
-
!DEFAULT_DENY
|
119
|
-
end
|
120
|
-
rescue NotAuthorized => e
|
121
|
-
auth_exception = e
|
122
|
-
end
|
123
|
-
|
124
|
-
unless allowed
|
125
|
-
if all_permissions.empty? and matching_permissions.empty?
|
126
|
-
logger.warn "Permission denied: No matching filter access " +
|
127
|
-
"rule found for #{self.class.controller_name}.#{action_name}"
|
128
|
-
elsif auth_exception
|
129
|
-
logger.info "Permission denied: #{auth_exception}"
|
130
|
-
end
|
131
|
-
if respond_to?(:permission_denied, true)
|
132
|
-
# permission_denied needs to render or redirect
|
133
|
-
send(:permission_denied)
|
134
|
-
else
|
135
|
-
send(:render, :plain => "You are not allowed to access this action.",
|
136
|
-
:status => :forbidden)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def load_controller_object(context_without_namespace = nil, model = nil) # :nodoc:
|
142
|
-
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
143
|
-
model = model ? model.classify.constantize : context_without_namespace.to_s.classify.constantize
|
144
|
-
instance_variable_set(instance_var, model.find(params[:id]))
|
145
|
-
end
|
146
|
-
|
147
|
-
def load_parent_controller_object(parent_context_without_namespace) # :nodoc:
|
148
|
-
instance_var = :"@#{parent_context_without_namespace.to_s.singularize}"
|
149
|
-
model = parent_context_without_namespace.to_s.classify.constantize
|
150
|
-
instance_variable_set(instance_var, model.find(params[:"#{parent_context_without_namespace.to_s.singularize}_id"]))
|
151
|
-
end
|
152
|
-
|
153
|
-
def new_controller_object_from_params(context_without_namespace, parent_context_without_namespace, strong_params) # :nodoc:
|
154
|
-
model_or_proxy = parent_context_without_namespace ?
|
155
|
-
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
156
|
-
context_without_namespace.to_s.classify.constantize
|
157
|
-
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
158
|
-
instance_variable_set(instance_var,
|
159
|
-
model_or_proxy.new(params[context_without_namespace.to_s.singularize]))
|
160
|
-
end
|
161
|
-
|
162
|
-
def new_blank_controller_object(context_without_namespace, parent_context_without_namespace, strong_params, model) # :nodoc:
|
163
|
-
if model
|
164
|
-
model_or_proxy = model.to_s.classify.constantize
|
165
|
-
else
|
166
|
-
model_or_proxy = parent_context_without_namespace ?
|
167
|
-
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
168
|
-
context_without_namespace.to_s.classify.constantize
|
169
|
-
end
|
170
|
-
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
171
|
-
instance_variable_set(instance_var,
|
172
|
-
model_or_proxy.new())
|
173
|
-
end
|
174
|
-
|
175
|
-
def new_controller_object_for_collection(context_without_namespace, parent_context_without_namespace, strong_params) # :nodoc:
|
176
|
-
model_or_proxy = parent_context_without_namespace ?
|
177
|
-
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
178
|
-
context_without_namespace.to_s.classify.constantize
|
179
|
-
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
180
|
-
instance_variable_set(instance_var, model_or_proxy.new)
|
181
|
-
end
|
182
|
-
|
183
|
-
def options_for_permit(object_or_sym = nil, options = {}, bang = true)
|
184
|
-
context = object = nil
|
185
|
-
if object_or_sym.nil?
|
186
|
-
context = self.class.decl_auth_context
|
187
|
-
elsif !Authorization.is_a_association_proxy?(object_or_sym) and object_or_sym.is_a?(Symbol)
|
188
|
-
context = object_or_sym
|
189
|
-
else
|
190
|
-
object = object_or_sym
|
191
|
-
end
|
192
|
-
|
193
|
-
result = {:object => object,
|
194
|
-
:context => context,
|
195
|
-
:skip_attribute_test => object.nil?,
|
196
|
-
:bang => bang}.merge(options)
|
197
|
-
result[:user] = current_user unless result.key?(:user)
|
198
|
-
result
|
199
|
-
end
|
200
|
-
|
201
|
-
module ClassMethods
|
202
|
-
#
|
203
|
-
# Defines a filter to be applied according to the authorization of the
|
204
|
-
# current user. Requires at least one symbol corresponding to an
|
205
|
-
# action as parameter. The special symbol :+all+ refers to all actions.
|
206
|
-
# The all :+all+ statement is only employed if no specific statement is
|
207
|
-
# present.
|
208
|
-
# class UserController < ApplicationController
|
209
|
-
# filter_access_to :index
|
210
|
-
# filter_access_to :new, :edit
|
211
|
-
# filter_access_to :all
|
212
|
-
# ...
|
213
|
-
# end
|
214
|
-
#
|
215
|
-
# The default is to allow access unconditionally if no rule matches.
|
216
|
-
# Thus, including the +filter_access_to+ :+all+ statement is a good
|
217
|
-
# idea, implementing a default-deny policy.
|
218
|
-
#
|
219
|
-
# When the access is denied, the method +permission_denied+ is called
|
220
|
-
# on the current controller, if defined. Else, a simple "you are not
|
221
|
-
# allowed" string is output. Log.info is given more information on the
|
222
|
-
# reasons of denial.
|
223
|
-
#
|
224
|
-
# def permission_denied
|
225
|
-
# flash[:error] = 'Sorry, you are not allowed to the requested page.'
|
226
|
-
# respond_to do |format|
|
227
|
-
# format.html { redirect_to(:back) rescue redirect_to('/') }
|
228
|
-
# format.xml { head :unauthorized }
|
229
|
-
# format.js { head :unauthorized }
|
230
|
-
# end
|
231
|
-
# end
|
232
|
-
#
|
233
|
-
# By default, required privileges are inferred from the action name and
|
234
|
-
# the controller name. Thus, in UserController :+edit+ requires
|
235
|
-
# :+edit+ +users+. To specify required privilege, use the option :+require+
|
236
|
-
# filter_access_to :new, :create, :require => :create, :context => :users
|
237
|
-
#
|
238
|
-
# Without the :+attribute_check+ option, no constraints from the
|
239
|
-
# authorization rules are enforced because for some actions (collections,
|
240
|
-
# +new+, +create+), there is no object to evaluate conditions against. To
|
241
|
-
# allow attribute checks on all actions, it is a common pattern to provide
|
242
|
-
# custom objects through +before_actions+:
|
243
|
-
# class BranchesController < ApplicationController
|
244
|
-
# before_action :load_company
|
245
|
-
# before_action :new_branch_from_company_and_params,
|
246
|
-
# :only => [:index, :new, :create]
|
247
|
-
# filter_access_to :all, :attribute_check => true
|
248
|
-
#
|
249
|
-
# protected
|
250
|
-
# def new_branch_from_company_and_params
|
251
|
-
# @branch = @company.branches.new(params[:branch])
|
252
|
-
# end
|
253
|
-
# end
|
254
|
-
# NOTE: +before_actions+ need to be defined before the first
|
255
|
-
# +filter_access_to+ call.
|
256
|
-
#
|
257
|
-
# For further customization, a custom filter expression may be formulated
|
258
|
-
# in a block, which is then evaluated in the context of the controller
|
259
|
-
# on a matching request. That is, for checking two objects, use the
|
260
|
-
# following:
|
261
|
-
# filter_access_to :merge do
|
262
|
-
# permitted_to!(:update, User.find(params[:original_id])) and
|
263
|
-
# permitted_to!(:delete, User.find(params[:id]))
|
264
|
-
# end
|
265
|
-
# The block should raise a Authorization::AuthorizationError or return
|
266
|
-
# false if the access is to be denied.
|
267
|
-
#
|
268
|
-
# Later calls to filter_access_to with overlapping actions overwrite
|
269
|
-
# previous ones for that action.
|
270
|
-
#
|
271
|
-
# All options:
|
272
|
-
# [:+require+]
|
273
|
-
# Privilege required; defaults to action_name
|
274
|
-
# [:+context+]
|
275
|
-
# The privilege's context, defaults to decl_auth_context, which consists
|
276
|
-
# of controller_name, prepended by any namespaces
|
277
|
-
# [:+attribute_check+]
|
278
|
-
# Enables the check of attributes defined in the authorization rules.
|
279
|
-
# Defaults to false. If enabled, filter_access_to will use a context
|
280
|
-
# object from one of the following sources (in that order):
|
281
|
-
# * the method from the :+load_method+ option,
|
282
|
-
# * an instance variable named after the singular of the context
|
283
|
-
# (by default from the controller name, e.g. @post for PostsController),
|
284
|
-
# * a find on the context model, using +params+[:id] as id value.
|
285
|
-
# Any of these methods will only be employed if :+attribute_check+
|
286
|
-
# is enabled.
|
287
|
-
# [:+model+]
|
288
|
-
# The data model to load a context object from. Defaults to the
|
289
|
-
# context, singularized.
|
290
|
-
# [:+load_method+]
|
291
|
-
# Specify a method by symbol or a Proc object which should be used
|
292
|
-
# to load the object. Both should return the loaded object.
|
293
|
-
# If a Proc object is given, e.g. by way of
|
294
|
-
# +lambda+, it is called in the instance of the controller.
|
295
|
-
# Example demonstrating the default behavior:
|
296
|
-
# filter_access_to :show, :attribute_check => true,
|
297
|
-
# :load_method => lambda { User.find(params[:id]) }
|
298
|
-
#
|
299
|
-
|
300
|
-
def filter_access_to(*args, &filter_block)
|
301
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
302
|
-
options = {
|
303
|
-
:require => nil,
|
304
|
-
:context => nil,
|
305
|
-
:attribute_check => false,
|
306
|
-
:model => nil,
|
307
|
-
:load_method => nil,
|
308
|
-
:strong_parameters => nil
|
309
|
-
}.merge!(options)
|
310
|
-
privilege = options[:require]
|
311
|
-
context = options[:context]
|
312
|
-
actions = args.flatten
|
313
|
-
|
314
|
-
# prevent setting filter_access_filter multiple times
|
315
|
-
skip_before_action(:filter_access_filter) if method_defined?(:filter_access_filter)
|
316
|
-
before_action :filter_access_filter
|
317
|
-
|
318
|
-
filter_access_permissions.each do |perm|
|
319
|
-
perm.remove_actions(actions)
|
320
|
-
end
|
321
|
-
filter_access_permissions <<
|
322
|
-
ControllerPermission.new(actions, privilege, context,
|
323
|
-
options[:strong_parameters],
|
324
|
-
options[:attribute_check],
|
325
|
-
options[:model],
|
326
|
-
options[:load_method],
|
327
|
-
filter_block)
|
328
|
-
end
|
329
|
-
|
330
|
-
# Disables authorization entirely. Requires at least one symbol corresponding
|
331
|
-
# to an action as parameter. The special symbol :+all+ refers to all actions.
|
332
|
-
# The all :+all+ statement is only employed if no specific statement is
|
333
|
-
# present.
|
334
|
-
def no_filter_access_to(*args)
|
335
|
-
filter_access_to args do
|
336
|
-
true
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
# Collecting all the ControllerPermission objects from the controller
|
341
|
-
# hierarchy. Permissions for actions are overwritten by calls to
|
342
|
-
# filter_access_to in child controllers with the same action.
|
343
|
-
def all_filter_access_permissions # :nodoc:
|
344
|
-
ancestors.inject([]) do |perms, mod|
|
345
|
-
if mod.respond_to?(:filter_access_permissions, true)
|
346
|
-
perms +
|
347
|
-
mod.filter_access_permissions.collect do |p1|
|
348
|
-
p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions})
|
349
|
-
end
|
350
|
-
else
|
351
|
-
perms
|
352
|
-
end
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
# To DRY up the filter_access_to statements in restful controllers,
|
357
|
-
# filter_resource_access combines typical filter_access_to and
|
358
|
-
# before_action calls, which set up the instance variables.
|
359
|
-
#
|
360
|
-
# The simplest case are top-level resource controllers with only the
|
361
|
-
# seven CRUD methods, e.g.
|
362
|
-
# class CompanyController < ApplicationController
|
363
|
-
# filter_resource_access
|
364
|
-
#
|
365
|
-
# def index...
|
366
|
-
# end
|
367
|
-
# Here, all CRUD actions are protected through a filter_access_to :all
|
368
|
-
# statement. :+attribute_check+ is enabled for all actions except for
|
369
|
-
# the collection action :+index+. To have an object for attribute checks
|
370
|
-
# available, filter_resource_access will set the instance variable
|
371
|
-
# @+company+ in before filters. For the member actions (:+show+, :+edit+,
|
372
|
-
# :+update+, :+destroy+) @company is set to Company.find(params[:id]).
|
373
|
-
# For +new+ actions (:+new+, :+create+), filter_resource_access creates
|
374
|
-
# a new object from company parameters: Company.new(params[:company].
|
375
|
-
#
|
376
|
-
# For nested resources, the parent object may be loaded automatically.
|
377
|
-
# class BranchController < ApplicationController
|
378
|
-
# filter_resource_access :nested_in => :companies
|
379
|
-
# end
|
380
|
-
# Again, the CRUD actions are protected. Now, for all CRUD actions,
|
381
|
-
# the parent object @company is loaded from params[:company_id]. It is
|
382
|
-
# also used when creating @branch for +new+ actions. Here, attribute_check
|
383
|
-
# is enabled for the collection :+index+ as well, checking attributes on a
|
384
|
-
# @company.branches.new method.
|
385
|
-
#
|
386
|
-
# In many cases, the default seven CRUD actions are not sufficient. As in
|
387
|
-
# the resource definition for routing you may thus give additional member,
|
388
|
-
# new and collection methods. The +options+ allow you to specify the
|
389
|
-
# required privileges for each action by providing a hash or an array of
|
390
|
-
# pairs. By default, for each action the action name is taken as privilege
|
391
|
-
# (action search in the example below requires the privilege :index
|
392
|
-
# :companies). Any controller action that is not specified and does not
|
393
|
-
# belong to the seven CRUD actions is handled as a member method.
|
394
|
-
# class CompanyController < ApplicationController
|
395
|
-
# filter_resource_access :collection => [[:search, :index], :index],
|
396
|
-
# :additional_member => {:mark_as_key_company => :update}
|
397
|
-
# end
|
398
|
-
# The +additional_+* options add to the respective CRUD actions,
|
399
|
-
# the other options (:+member+, :+collection+, :+new+) replace their
|
400
|
-
# respective CRUD actions.
|
401
|
-
# filter_resource_access :member => { :toggle_open => :update }
|
402
|
-
# Would declare :toggle_open as the only member action in the controller and
|
403
|
-
# require that permission :update is granted for the current user.
|
404
|
-
# filter_resource_access :additional_member => { :toggle_open => :update }
|
405
|
-
# Would add a member action :+toggle_open+ to the default members, such as :+show+.
|
406
|
-
#
|
407
|
-
# If :+collection+ is an array of method names filter_resource_access will
|
408
|
-
# associate a permission with the method that is the same as the method
|
409
|
-
# name and no attribute checks will be performed unless
|
410
|
-
# :attribute_check => true
|
411
|
-
# is added in the options.
|
412
|
-
#
|
413
|
-
# You can override the default object loading by implementing any of the
|
414
|
-
# following instance methods on the controller. Examples are given for the
|
415
|
-
# BranchController (with +nested_in+ set to :+companies+):
|
416
|
-
# [+new_branch_from_params+]
|
417
|
-
# Used for +new+ actions.
|
418
|
-
# [+new_branch_for_collection+]
|
419
|
-
# Used for +collection+ actions if the +nested_in+ option is set.
|
420
|
-
# [+load_branch+]
|
421
|
-
# Used for +member+ actions.
|
422
|
-
# [+load_company+]
|
423
|
-
# Used for all +new+, +member+, and +collection+ actions if the
|
424
|
-
# +nested_in+ option is set.
|
425
|
-
#
|
426
|
-
# All options:
|
427
|
-
# [:+member+]
|
428
|
-
# Member methods are actions like +show+, which have an params[:id] from
|
429
|
-
# which to load the controller object and assign it to @controller_name,
|
430
|
-
# e.g. @+branch+.
|
431
|
-
#
|
432
|
-
# By default, member actions are [:+show+, :+edit+, :+update+,
|
433
|
-
# :+destroy+]. Also, any action not belonging to the seven CRUD actions
|
434
|
-
# are handled as member actions.
|
435
|
-
#
|
436
|
-
# There are three different syntax to specify member, collection and
|
437
|
-
# new actions.
|
438
|
-
# * Hash: Lets you set the required privilege for each action:
|
439
|
-
# {:+show+ => :+show+, :+mark_as_important+ => :+update+}
|
440
|
-
# * Array of actions or pairs: [:+show+, [:+mark_as_important+, :+update+]],
|
441
|
-
# with single actions requiring the privilege of the same name as the method.
|
442
|
-
# * Single method symbol: :+show+
|
443
|
-
# [:+additional_member+]
|
444
|
-
# Allows to add additional member actions to the default resource +member+
|
445
|
-
# actions.
|
446
|
-
# [:+collection+]
|
447
|
-
# Collection actions are like :+index+, actions without any controller object
|
448
|
-
# to check attributes of. If +nested_in+ is given, a new object is
|
449
|
-
# created from the parent object, e.g. @company.branches.new. Without
|
450
|
-
# +nested_in+, attribute check is deactivated for these actions. By
|
451
|
-
# default, collection is set to :+index+.
|
452
|
-
# [:+additional_collection+]
|
453
|
-
# Allows to add additional collection actions to the default resource +collection+
|
454
|
-
# actions.
|
455
|
-
# [:+new+]
|
456
|
-
# +new+ methods are actions such as +new+ and +create+, which don't
|
457
|
-
# receive a params[:id] to load an object from, but
|
458
|
-
# a params[:controller_name_singular] hash with attributes for a new
|
459
|
-
# object. The attributes will be used here to create a new object and
|
460
|
-
# check the object against the authorization rules. The object is
|
461
|
-
# assigned to @controller_name_singular, e.g. @branch.
|
462
|
-
#
|
463
|
-
# If +nested_in+ is given, the new object
|
464
|
-
# is created from the parent_object.controller_name
|
465
|
-
# proxy, e.g. company.branches.new(params[:branch]). By default,
|
466
|
-
# +new+ is set to [:new, :create].
|
467
|
-
# [:+additional_new+]
|
468
|
-
# Allows to add additional new actions to the default resource +new+ actions.
|
469
|
-
# [:+context+]
|
470
|
-
# The context is used to determine the model to load objects from for the
|
471
|
-
# before_actions and the context of privileges to use in authorization
|
472
|
-
# checks.
|
473
|
-
# [:+nested_in+]
|
474
|
-
# Specifies the parent controller if the resource is nested in another
|
475
|
-
# one. This is used to automatically load the parent object, e.g.
|
476
|
-
# @+company+ from params[:company_id] for a BranchController nested in
|
477
|
-
# a CompanyController.
|
478
|
-
# [:+shallow+]
|
479
|
-
# Only relevant when used in conjunction with +nested_in+. Specifies a nested resource
|
480
|
-
# as being a shallow nested resource, resulting in the controller not attempting to
|
481
|
-
# load a parent object for all member actions defined by +member+ and
|
482
|
-
# +additional_member+ or rather the default member actions (:+show+, :+edit+,
|
483
|
-
# :+update+, :+destroy+).
|
484
|
-
# [:+no_attribute_check+]
|
485
|
-
# Allows to set actions for which no attribute check should be performed.
|
486
|
-
# See filter_access_to on details. By default, with no +nested_in+,
|
487
|
-
# +no_attribute_check+ is set to all collections. If +nested_in+ is given
|
488
|
-
# +no_attribute_check+ is empty by default.
|
489
|
-
# [:+strong_parameters+]
|
490
|
-
# If set to true, relies on controller to provide instance variable and
|
491
|
-
# create new object in :create action. Set true if you use strong_params
|
492
|
-
# and false if you use protected_attributes.
|
493
|
-
#
|
494
|
-
def filter_resource_access(options = {})
|
495
|
-
options = {
|
496
|
-
:new => [:new, :create],
|
497
|
-
:additional_new => nil,
|
498
|
-
:member => [:show, :edit, :update, :destroy],
|
499
|
-
:additional_member => nil,
|
500
|
-
:collection => [:index],
|
501
|
-
:additional_collection => nil,
|
502
|
-
#:new_method_for_collection => nil, # only symbol method name
|
503
|
-
#:new_method => nil, # only symbol method name
|
504
|
-
#:load_method => nil, # only symbol method name
|
505
|
-
:no_attribute_check => nil,
|
506
|
-
:context => nil,
|
507
|
-
:model => nil,
|
508
|
-
:nested_in => nil,
|
509
|
-
:strong_parameters => nil
|
510
|
-
}.merge(options)
|
511
|
-
options.merge!({ :strong_parameters => true }) if options[:strong_parameters] == nil
|
512
|
-
|
513
|
-
new_actions = actions_from_option( options[:new] ).merge(
|
514
|
-
actions_from_option(options[:additional_new]) )
|
515
|
-
members = actions_from_option(options[:member]).merge(
|
516
|
-
actions_from_option(options[:additional_member]))
|
517
|
-
collections = actions_from_option(options[:collection]).merge(
|
518
|
-
actions_from_option(options[:additional_collection]))
|
519
|
-
|
520
|
-
no_attribute_check_actions = options[:strong_parameters] ? actions_from_option(options[:collection]).merge(actions_from_option([:create])) : collections
|
521
|
-
|
522
|
-
options[:no_attribute_check] ||= no_attribute_check_actions.keys unless options[:nested_in]
|
523
|
-
|
524
|
-
unless options[:nested_in].blank?
|
525
|
-
load_parent_method = :"load_#{options[:nested_in].to_s.singularize}"
|
526
|
-
shallow_exceptions = options[:shallow] ? {:except => members.keys} : {}
|
527
|
-
before_action shallow_exceptions do |controller|
|
528
|
-
if controller.respond_to?(load_parent_method, true)
|
529
|
-
controller.send(load_parent_method)
|
530
|
-
else
|
531
|
-
controller.send(:load_parent_controller_object, options[:nested_in])
|
532
|
-
end
|
533
|
-
end
|
534
|
-
|
535
|
-
new_for_collection_method = :"new_#{controller_name.singularize}_for_collection"
|
536
|
-
before_action :only => collections.keys do |controller|
|
537
|
-
# new_for_collection
|
538
|
-
if controller.respond_to?(new_for_collection_method, true)
|
539
|
-
controller.send(new_for_collection_method)
|
540
|
-
else
|
541
|
-
controller.send(:new_controller_object_for_collection,
|
542
|
-
options[:context] || controller_name, options[:nested_in], options[:strong_parameters])
|
543
|
-
end
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
unless options[:strong_parameters]
|
548
|
-
new_from_params_method = :"new_#{controller_name.singularize}_from_params"
|
549
|
-
before_action :only => new_actions.keys do |controller|
|
550
|
-
# new_from_params
|
551
|
-
if controller.respond_to?(new_from_params_method, true)
|
552
|
-
controller.send(new_from_params_method)
|
553
|
-
else
|
554
|
-
controller.send(:new_controller_object_from_params,
|
555
|
-
options[:context] || controller_name, options[:nested_in], options[:strong_parameters])
|
556
|
-
end
|
557
|
-
end
|
558
|
-
else
|
559
|
-
new_object_method = :"new_#{controller_name.singularize}"
|
560
|
-
before_action :only => :new do |controller|
|
561
|
-
# new_from_params
|
562
|
-
if controller.respond_to?(new_object_method, true)
|
563
|
-
controller.send(new_object_method)
|
564
|
-
else
|
565
|
-
controller.send(:new_blank_controller_object,
|
566
|
-
options[:context] || controller_name, options[:nested_in], options[:strong_parameters], options[:model])
|
567
|
-
end
|
568
|
-
end
|
569
|
-
end
|
570
|
-
|
571
|
-
load_method = :"load_#{controller_name.singularize}"
|
572
|
-
before_action :only => members.keys do |controller|
|
573
|
-
# load controller object
|
574
|
-
if controller.respond_to?(load_method, true)
|
575
|
-
controller.send(load_method)
|
576
|
-
else
|
577
|
-
controller.send(:load_controller_object, options[:context] || controller_name, options[:model])
|
578
|
-
end
|
579
|
-
end
|
580
|
-
filter_access_to :all, :attribute_check => true, :context => options[:context], :model => options[:model]
|
581
|
-
|
582
|
-
members.merge(new_actions).merge(collections).each do |action, privilege|
|
583
|
-
if action != privilege or (options[:no_attribute_check] and options[:no_attribute_check].include?(action))
|
584
|
-
filter_options = {
|
585
|
-
:strong_parameters => options[:strong_parameters],
|
586
|
-
:context => options[:context],
|
587
|
-
:attribute_check => !options[:no_attribute_check] || !options[:no_attribute_check].include?(action),
|
588
|
-
:model => options[:model]
|
589
|
-
}
|
590
|
-
filter_options[:require] = privilege if action != privilege
|
591
|
-
filter_access_to(action, filter_options)
|
592
|
-
end
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
# Returns the context for authorization checks in the current controller.
|
597
|
-
# Uses the controller_name and prepends any namespaces underscored and
|
598
|
-
# joined with underscores.
|
599
|
-
#
|
600
|
-
# E.g.
|
601
|
-
# AllThosePeopleController => :all_those_people
|
602
|
-
# AnyName::Space::ThingsController => :any_name_space_things
|
603
|
-
#
|
604
|
-
def decl_auth_context
|
605
|
-
prefixes = name.split('::')[0..-2].map(&:underscore)
|
606
|
-
((prefixes + [controller_name]) * '_').to_sym
|
607
|
-
end
|
608
|
-
|
609
|
-
protected
|
610
|
-
def filter_access_permissions # :nodoc:
|
611
|
-
unless filter_access_permissions?
|
612
|
-
ancestors[1..-1].reverse.each do |mod|
|
613
|
-
mod.filter_access_permissions if mod.respond_to?(:filter_access_permissions, true)
|
614
|
-
end
|
615
|
-
end
|
616
|
-
class_variable_set(:@@declarative_authorization_permissions, {}) unless filter_access_permissions?
|
617
|
-
class_variable_get(:@@declarative_authorization_permissions)[self.name] ||= []
|
618
|
-
end
|
619
|
-
|
620
|
-
def filter_access_permissions? # :nodoc:
|
621
|
-
class_variable_defined?(:@@declarative_authorization_permissions)
|
622
|
-
end
|
623
|
-
|
624
|
-
def actions_from_option(option) # :nodoc:
|
625
|
-
case option
|
626
|
-
when nil
|
627
|
-
{}
|
628
|
-
when Symbol, String
|
629
|
-
{option.to_sym => option.to_sym}
|
630
|
-
when Hash
|
631
|
-
option
|
632
|
-
when Enumerable
|
633
|
-
option.each_with_object({}) do |action, hash|
|
634
|
-
if action.is_a?(Array)
|
635
|
-
raise "Unexpected option format: #{option.inspect}" if action.length != 2
|
636
|
-
hash[action.first] = action.last
|
637
|
-
else
|
638
|
-
hash[action.to_sym] = action.to_sym
|
639
|
-
end
|
640
|
-
end
|
641
|
-
end
|
642
|
-
end
|
643
|
-
end
|
644
|
-
end
|
645
|
-
|
646
|
-
class ControllerPermission # :nodoc:
|
647
|
-
attr_reader :actions, :privilege, :context, :attribute_check, :strong_params
|
648
|
-
def initialize(actions, privilege, context, strong_params, attribute_check = false,
|
649
|
-
load_object_model = nil, load_object_method = nil,
|
650
|
-
filter_block = nil)
|
651
|
-
@actions = actions.to_set
|
652
|
-
@privilege = privilege
|
653
|
-
@context = context
|
654
|
-
@load_object_model = load_object_model
|
655
|
-
@load_object_method = load_object_method
|
656
|
-
@filter_block = filter_block
|
657
|
-
@attribute_check = attribute_check
|
658
|
-
@strong_params = strong_params
|
659
|
-
end
|
660
|
-
|
661
|
-
def matches?(action_name)
|
662
|
-
@actions.include?(action_name.to_sym)
|
663
|
-
end
|
664
|
-
|
665
|
-
def permit!(contr)
|
666
|
-
if @filter_block
|
667
|
-
return contr.instance_eval(&@filter_block)
|
668
|
-
end
|
669
|
-
object = @attribute_check ? load_object(contr) : nil
|
670
|
-
privilege = @privilege || :"#{contr.action_name}"
|
671
|
-
|
672
|
-
contr.authorization_engine.permit!(privilege,
|
673
|
-
:user => contr.send(:current_user),
|
674
|
-
:object => object,
|
675
|
-
:skip_attribute_test => !@attribute_check,
|
676
|
-
:context => @context || contr.class.decl_auth_context)
|
677
|
-
end
|
678
|
-
|
679
|
-
def remove_actions(actions)
|
680
|
-
@actions -= actions
|
681
|
-
self
|
682
|
-
end
|
683
|
-
|
684
|
-
private
|
685
|
-
|
686
|
-
def load_object(contr)
|
687
|
-
if @load_object_method and @load_object_method.is_a?(Symbol)
|
688
|
-
contr.send(@load_object_method)
|
689
|
-
elsif @load_object_method and @load_object_method.is_a?(Proc)
|
690
|
-
contr.instance_eval(&@load_object_method)
|
691
|
-
else
|
692
|
-
load_object_model = @load_object_model ||
|
693
|
-
(@context ? @context.to_s.classify.constantize : contr.class.controller_name.classify.constantize)
|
694
|
-
load_object_model = load_object_model.classify.constantize if load_object_model.is_a?(String)
|
695
|
-
instance_var = "@#{load_object_model.name.demodulize.underscore}"
|
696
|
-
object = contr.instance_variable_get(instance_var)
|
697
|
-
unless object
|
698
|
-
begin
|
699
|
-
object = @strong_params ? load_object_model.find_or_initialize_by(:id => contr.params[:id]) : load_object_model.find(contr.params[:id])
|
700
|
-
rescue => e
|
701
|
-
contr.logger.debug("filter_access_to tried to find " +
|
702
|
-
"#{load_object_model} from params[:id] " +
|
703
|
-
"(#{contr.params[:id].inspect}), because attribute_check is enabled " +
|
704
|
-
"and #{instance_var.to_s} isn't set, but failed: #{e.class.name}: #{e}")
|
705
|
-
raise if AuthorizationInController.failed_auto_loading_is_not_found?
|
706
|
-
end
|
707
|
-
contr.instance_variable_set(instance_var, object)
|
708
|
-
end
|
709
|
-
object
|
710
|
-
end
|
711
|
-
end
|
712
|
-
end
|
713
|
-
end
|