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
data/gemfiles/rails516.gemfile
CHANGED
data/gemfiles/rails521.gemfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
ae_declarative_authorization (0.
|
4
|
+
ae_declarative_authorization (0.8.0)
|
5
5
|
blockenspiel (~> 0.5.0)
|
6
6
|
rails (>= 4.2.5.2, < 6)
|
7
7
|
|
@@ -68,7 +68,7 @@ GEM
|
|
68
68
|
nokogiri (>= 1.5.9)
|
69
69
|
mail (2.7.0)
|
70
70
|
mini_mime (>= 0.1.1)
|
71
|
-
marcel (0.3.
|
71
|
+
marcel (0.3.3)
|
72
72
|
mimemagic (~> 0.3.2)
|
73
73
|
metaclass (0.0.4)
|
74
74
|
method_source (0.9.0)
|
@@ -1,18 +1,23 @@
|
|
1
1
|
require File.join(%w{declarative_authorization helper})
|
2
|
-
|
2
|
+
if defined?(ActionController)
|
3
|
+
require File.dirname(__FILE__) + '/declarative_authorization/controller/rails.rb'
|
4
|
+
end
|
5
|
+
|
3
6
|
if defined?(ActiveRecord)
|
4
7
|
require File.join(%w{declarative_authorization in_model})
|
5
8
|
require File.join(%w{declarative_authorization obligation_scope})
|
6
9
|
end
|
7
10
|
|
8
|
-
min_rails_version = '4.2.5.2'
|
9
|
-
if Rails::VERSION::STRING < min_rails_version
|
11
|
+
min_rails_version = Gem::Version.new('4.2.5.2')
|
12
|
+
if Gem::Version.new(Rails::VERSION::STRING) < min_rails_version
|
10
13
|
raise "ae_declarative_authorization requires Rails #{min_rails_version}. You are using #{Rails::VERSION::STRING}."
|
11
14
|
end
|
12
15
|
|
13
16
|
require File.join(%w{declarative_authorization railsengine}) if defined?(::Rails::Engine)
|
14
17
|
|
15
|
-
ActionController
|
16
|
-
ActionController::Base.
|
18
|
+
if defined?(ActionController)
|
19
|
+
ActionController::Base.send :include, Authorization::Controller::Rails
|
20
|
+
ActionController::Base.helper Authorization::AuthorizationHelper
|
21
|
+
end
|
17
22
|
|
18
23
|
ActiveRecord::Base.send :include, Authorization::AuthorizationInModel if defined?(ActiveRecord)
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../controller_permission.rb'
|
2
|
+
|
3
|
+
module Authorization
|
4
|
+
module Controller
|
5
|
+
module DSL
|
6
|
+
#
|
7
|
+
# Defines a filter to be applied according to the authorization of the
|
8
|
+
# current user. Requires at least one symbol corresponding to an
|
9
|
+
# action as parameter. The special symbol :+all+ refers to all actions.
|
10
|
+
# The all :+all+ statement is only employed if no specific statement is
|
11
|
+
# present.
|
12
|
+
# class UserController < ApplicationController
|
13
|
+
# filter_access_to :index
|
14
|
+
# filter_access_to :new, :edit
|
15
|
+
# filter_access_to :all
|
16
|
+
# ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# The default is to allow access unconditionally if no rule matches.
|
20
|
+
# Thus, including the +filter_access_to+ :+all+ statement is a good
|
21
|
+
# idea, implementing a default-deny policy.
|
22
|
+
#
|
23
|
+
# When the access is denied, the method +permission_denied+ is called
|
24
|
+
# on the current controller, if defined. Else, a simple "you are not
|
25
|
+
# allowed" string is output. Log.info is given more information on the
|
26
|
+
# reasons of denial.
|
27
|
+
#
|
28
|
+
# def permission_denied
|
29
|
+
# flash[:error] = 'Sorry, you are not allowed to the requested page.'
|
30
|
+
# respond_to do |format|
|
31
|
+
# format.html { redirect_to(:back) rescue redirect_to('/') }
|
32
|
+
# format.xml { head :unauthorized }
|
33
|
+
# format.js { head :unauthorized }
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# By default, required privileges are inferred from the action name and
|
38
|
+
# the controller name. Thus, in UserController :+edit+ requires
|
39
|
+
# :+edit+ +users+. To specify required privilege, use the option :+require+
|
40
|
+
# filter_access_to :new, :create, :require => :create, :context => :users
|
41
|
+
#
|
42
|
+
# Without the :+attribute_check+ option, no constraints from the
|
43
|
+
# authorization rules are enforced because for some actions (collections,
|
44
|
+
# +new+, +create+), there is no object to evaluate conditions against. To
|
45
|
+
# allow attribute checks on all actions, it is a common pattern to provide
|
46
|
+
# custom objects through +before_actions+:
|
47
|
+
# class BranchesController < ApplicationController
|
48
|
+
# before_action :load_company
|
49
|
+
# before_action :new_branch_from_company_and_params,
|
50
|
+
# :only => [:index, :new, :create]
|
51
|
+
# filter_access_to :all, :attribute_check => true
|
52
|
+
#
|
53
|
+
# protected
|
54
|
+
# def new_branch_from_company_and_params
|
55
|
+
# @branch = @company.branches.new(params[:branch])
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
# NOTE: +before_actions+ need to be defined before the first
|
59
|
+
# +filter_access_to+ call.
|
60
|
+
#
|
61
|
+
# For further customization, a custom filter expression may be formulated
|
62
|
+
# in a block, which is then evaluated in the context of the controller
|
63
|
+
# on a matching request. That is, for checking two objects, use the
|
64
|
+
# following:
|
65
|
+
# filter_access_to :merge do
|
66
|
+
# permitted_to!(:update, User.find(params[:original_id])) and
|
67
|
+
# permitted_to!(:delete, User.find(params[:id]))
|
68
|
+
# end
|
69
|
+
# The block should raise a Authorization::AuthorizationError or return
|
70
|
+
# false if the access is to be denied.
|
71
|
+
#
|
72
|
+
# Later calls to filter_access_to with overlapping actions overwrite
|
73
|
+
# previous ones for that action.
|
74
|
+
#
|
75
|
+
# All options:
|
76
|
+
# [:+require+]
|
77
|
+
# Privilege required; defaults to action_name
|
78
|
+
# [:+context+]
|
79
|
+
# The privilege's context, defaults to decl_auth_context, which consists
|
80
|
+
# of controller_name, prepended by any namespaces
|
81
|
+
# [:+attribute_check+]
|
82
|
+
# Enables the check of attributes defined in the authorization rules.
|
83
|
+
# Defaults to false. If enabled, filter_access_to will use a context
|
84
|
+
# object from one of the following sources (in that order):
|
85
|
+
# * the method from the :+load_method+ option,
|
86
|
+
# * an instance variable named after the singular of the context
|
87
|
+
# (by default from the controller name, e.g. @post for PostsController),
|
88
|
+
# * a find on the context model, using +params+[:id] as id value.
|
89
|
+
# Any of these methods will only be employed if :+attribute_check+
|
90
|
+
# is enabled.
|
91
|
+
# [:+model+]
|
92
|
+
# The data model to load a context object from. Defaults to the
|
93
|
+
# context, singularized.
|
94
|
+
# [:+load_method+]
|
95
|
+
# Specify a method by symbol or a Proc object which should be used
|
96
|
+
# to load the object. Both should return the loaded object.
|
97
|
+
# If a Proc object is given, e.g. by way of
|
98
|
+
# +lambda+, it is called in the instance of the controller.
|
99
|
+
# Example demonstrating the default behavior:
|
100
|
+
# filter_access_to :show, :attribute_check => true,
|
101
|
+
# :load_method => lambda { User.find(params[:id]) }
|
102
|
+
#
|
103
|
+
|
104
|
+
def filter_access_to(*args, &filter_block)
|
105
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
106
|
+
options = {
|
107
|
+
:require => nil,
|
108
|
+
:context => nil,
|
109
|
+
:attribute_check => false,
|
110
|
+
:model => nil,
|
111
|
+
:load_method => nil,
|
112
|
+
:strong_parameters => nil
|
113
|
+
}.merge!(options)
|
114
|
+
privilege = options[:require]
|
115
|
+
context = options[:context]
|
116
|
+
actions = args.flatten
|
117
|
+
|
118
|
+
reset_filter!
|
119
|
+
|
120
|
+
filter_access_permissions.each do |perm|
|
121
|
+
perm.remove_actions(actions)
|
122
|
+
end
|
123
|
+
filter_access_permissions <<
|
124
|
+
ControllerPermission.new(actions, privilege, context,
|
125
|
+
options[:strong_parameters],
|
126
|
+
options[:attribute_check],
|
127
|
+
options[:model],
|
128
|
+
options[:load_method],
|
129
|
+
filter_block)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Disables authorization entirely. Requires at least one symbol corresponding
|
133
|
+
# to an action as parameter. The special symbol :+all+ refers to all actions.
|
134
|
+
# The all :+all+ statement is only employed if no specific statement is
|
135
|
+
# present.
|
136
|
+
def no_filter_access_to(*args)
|
137
|
+
filter_access_to args do
|
138
|
+
true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Collecting all the ControllerPermission objects from the controller
|
143
|
+
# hierarchy. Permissions for actions are overwritten by calls to
|
144
|
+
# filter_access_to in child controllers with the same action.
|
145
|
+
def all_filter_access_permissions # :nodoc:
|
146
|
+
ancestors.inject([]) do |perms, mod|
|
147
|
+
if mod.respond_to?(:filter_access_permissions, true)
|
148
|
+
perms +
|
149
|
+
mod.filter_access_permissions.collect do |p1|
|
150
|
+
p1.clone.remove_actions(perms.inject(Set.new) {|actions, p2| actions + p2.actions})
|
151
|
+
end
|
152
|
+
else
|
153
|
+
perms
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the context for authorization checks in the current controller.
|
159
|
+
# Uses the controller_name and prepends any namespaces underscored and
|
160
|
+
# joined with underscores.
|
161
|
+
#
|
162
|
+
# E.g.
|
163
|
+
# AllThosePeopleController => :all_those_people
|
164
|
+
# AnyName::Space::ThingsController => :any_name_space_things
|
165
|
+
#
|
166
|
+
def decl_auth_context
|
167
|
+
prefixes = name.split('::')[0..-2].map(&:underscore)
|
168
|
+
((prefixes + [controller_name]) * '_').to_sym
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
def filter_access_permissions # :nodoc:
|
174
|
+
unless filter_access_permissions?
|
175
|
+
ancestors[1..-1].reverse.each do |mod|
|
176
|
+
mod.filter_access_permissions if mod.respond_to?(:filter_access_permissions, true)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
class_variable_set(:@@declarative_authorization_permissions, {}) unless filter_access_permissions?
|
180
|
+
class_variable_get(:@@declarative_authorization_permissions)[self.name] ||= []
|
181
|
+
end
|
182
|
+
|
183
|
+
def filter_access_permissions? # :nodoc:
|
184
|
+
class_variable_defined?(:@@declarative_authorization_permissions)
|
185
|
+
end
|
186
|
+
|
187
|
+
def actions_from_option(option) # :nodoc:
|
188
|
+
case option
|
189
|
+
when nil
|
190
|
+
{}
|
191
|
+
when Symbol, String
|
192
|
+
{option.to_sym => option.to_sym}
|
193
|
+
when Hash
|
194
|
+
option
|
195
|
+
when Enumerable
|
196
|
+
option.each_with_object({}) do |action, hash|
|
197
|
+
if action.is_a?(Array)
|
198
|
+
raise "Unexpected option format: #{option.inspect}" if action.length != 2
|
199
|
+
hash[action.first] = action.last
|
200
|
+
else
|
201
|
+
hash[action.to_sym] = action.to_sym
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../authorization.rb'
|
2
|
+
require File.dirname(__FILE__) + '/dsl.rb'
|
3
|
+
require File.dirname(__FILE__) + '/runtime.rb'
|
4
|
+
|
5
|
+
#
|
6
|
+
# This mixin can be used to add declarative authorization support to APIs built using Grape
|
7
|
+
# https://github.com/ruby-grape/grape
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# class MyApi < Grape::API
|
11
|
+
# include Authorization::Controller::Grape
|
12
|
+
#
|
13
|
+
# get :hello do
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# NOTE: actions in authorization rules must be named `{METHOD} {URL}`. eg
|
18
|
+
# has_permission_on :my_api, to: 'GET /my_api/hello'
|
19
|
+
#
|
20
|
+
module Authorization
|
21
|
+
module Controller
|
22
|
+
module Grape
|
23
|
+
def self.included(base) # :nodoc:
|
24
|
+
base.extend ClassMethods
|
25
|
+
|
26
|
+
base.extend ::Authorization::Controller::DSL
|
27
|
+
|
28
|
+
base.module_eval do
|
29
|
+
add_filter!
|
30
|
+
end
|
31
|
+
|
32
|
+
base.helpers do
|
33
|
+
include ::Authorization::Controller::Runtime
|
34
|
+
|
35
|
+
def authorization_engine
|
36
|
+
::Authorization::Engine.instance
|
37
|
+
end
|
38
|
+
|
39
|
+
def filter_access_filter # :nodoc:
|
40
|
+
unless allowed?("#{request.request_method} #{route.origin}")
|
41
|
+
if respond_to?(:permission_denied, true)
|
42
|
+
# permission_denied needs to render or redirect
|
43
|
+
send(:permission_denied)
|
44
|
+
else
|
45
|
+
error!('You are not allowed to access this action.', 403)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def logger
|
51
|
+
::Rails.logger
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def api_class
|
57
|
+
options[:for]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module ClassMethods
|
63
|
+
def controller_name
|
64
|
+
name.demodulize.underscore
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_filter!
|
68
|
+
before do
|
69
|
+
send(:filter_access_filter)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset_filter!
|
74
|
+
# Not required with Grape
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../authorization.rb'
|
2
|
+
require File.dirname(__FILE__) + '/dsl.rb'
|
3
|
+
require File.dirname(__FILE__) + '/runtime.rb'
|
4
|
+
|
5
|
+
#
|
6
|
+
# Mixin to be added to rails controllers
|
7
|
+
#
|
8
|
+
module Authorization
|
9
|
+
module Controller
|
10
|
+
module Rails
|
11
|
+
def self.included(base) # :nodoc:
|
12
|
+
base.extend ClassMethods
|
13
|
+
|
14
|
+
base.extend DSL
|
15
|
+
|
16
|
+
base.module_eval do
|
17
|
+
add_filter!
|
18
|
+
end
|
19
|
+
|
20
|
+
base.include Runtime
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
#
|
25
|
+
# Add the filtering before_action
|
26
|
+
#
|
27
|
+
def add_filter!
|
28
|
+
before_action(:filter_access_filter)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Move the filtering to the end of the before_action list
|
33
|
+
#
|
34
|
+
def reset_filter!
|
35
|
+
skip_before_action(:filter_access_filter) if method_defined?(:filter_access_filter)
|
36
|
+
before_action :filter_access_filter
|
37
|
+
end
|
38
|
+
|
39
|
+
# To DRY up the filter_access_to statements in restful controllers,
|
40
|
+
# filter_resource_access combines typical filter_access_to and
|
41
|
+
# before_action calls, which set up the instance variables.
|
42
|
+
#
|
43
|
+
# The simplest case are top-level resource controllers with only the
|
44
|
+
# seven CRUD methods, e.g.
|
45
|
+
# class CompanyController < ApplicationController
|
46
|
+
# filter_resource_access
|
47
|
+
#
|
48
|
+
# def index...
|
49
|
+
# end
|
50
|
+
# Here, all CRUD actions are protected through a filter_access_to :all
|
51
|
+
# statement. :+attribute_check+ is enabled for all actions except for
|
52
|
+
# the collection action :+index+. To have an object for attribute checks
|
53
|
+
# available, filter_resource_access will set the instance variable
|
54
|
+
# @+company+ in before filters. For the member actions (:+show+, :+edit+,
|
55
|
+
# :+update+, :+destroy+) @company is set to Company.find(params[:id]).
|
56
|
+
# For +new+ actions (:+new+, :+create+), filter_resource_access creates
|
57
|
+
# a new object from company parameters: Company.new(params[:company].
|
58
|
+
#
|
59
|
+
# For nested resources, the parent object may be loaded automatically.
|
60
|
+
# class BranchController < ApplicationController
|
61
|
+
# filter_resource_access :nested_in => :companies
|
62
|
+
# end
|
63
|
+
# Again, the CRUD actions are protected. Now, for all CRUD actions,
|
64
|
+
# the parent object @company is loaded from params[:company_id]. It is
|
65
|
+
# also used when creating @branch for +new+ actions. Here, attribute_check
|
66
|
+
# is enabled for the collection :+index+ as well, checking attributes on a
|
67
|
+
# @company.branches.new method.
|
68
|
+
#
|
69
|
+
# In many cases, the default seven CRUD actions are not sufficient. As in
|
70
|
+
# the resource definition for routing you may thus give additional member,
|
71
|
+
# new and collection methods. The +options+ allow you to specify the
|
72
|
+
# required privileges for each action by providing a hash or an array of
|
73
|
+
# pairs. By default, for each action the action name is taken as privilege
|
74
|
+
# (action search in the example below requires the privilege :index
|
75
|
+
# :companies). Any controller action that is not specified and does not
|
76
|
+
# belong to the seven CRUD actions is handled as a member method.
|
77
|
+
# class CompanyController < ApplicationController
|
78
|
+
# filter_resource_access :collection => [[:search, :index], :index],
|
79
|
+
# :additional_member => {:mark_as_key_company => :update}
|
80
|
+
# end
|
81
|
+
# The +additional_+* options add to the respective CRUD actions,
|
82
|
+
# the other options (:+member+, :+collection+, :+new+) replace their
|
83
|
+
# respective CRUD actions.
|
84
|
+
# filter_resource_access :member => { :toggle_open => :update }
|
85
|
+
# Would declare :toggle_open as the only member action in the controller and
|
86
|
+
# require that permission :update is granted for the current user.
|
87
|
+
# filter_resource_access :additional_member => { :toggle_open => :update }
|
88
|
+
# Would add a member action :+toggle_open+ to the default members, such as :+show+.
|
89
|
+
#
|
90
|
+
# If :+collection+ is an array of method names filter_resource_access will
|
91
|
+
# associate a permission with the method that is the same as the method
|
92
|
+
# name and no attribute checks will be performed unless
|
93
|
+
# :attribute_check => true
|
94
|
+
# is added in the options.
|
95
|
+
#
|
96
|
+
# You can override the default object loading by implementing any of the
|
97
|
+
# following instance methods on the controller. Examples are given for the
|
98
|
+
# BranchController (with +nested_in+ set to :+companies+):
|
99
|
+
# [+new_branch_from_params+]
|
100
|
+
# Used for +new+ actions.
|
101
|
+
# [+new_branch_for_collection+]
|
102
|
+
# Used for +collection+ actions if the +nested_in+ option is set.
|
103
|
+
# [+load_branch+]
|
104
|
+
# Used for +member+ actions.
|
105
|
+
# [+load_company+]
|
106
|
+
# Used for all +new+, +member+, and +collection+ actions if the
|
107
|
+
# +nested_in+ option is set.
|
108
|
+
#
|
109
|
+
# All options:
|
110
|
+
# [:+member+]
|
111
|
+
# Member methods are actions like +show+, which have an params[:id] from
|
112
|
+
# which to load the controller object and assign it to @controller_name,
|
113
|
+
# e.g. @+branch+.
|
114
|
+
#
|
115
|
+
# By default, member actions are [:+show+, :+edit+, :+update+,
|
116
|
+
# :+destroy+]. Also, any action not belonging to the seven CRUD actions
|
117
|
+
# are handled as member actions.
|
118
|
+
#
|
119
|
+
# There are three different syntax to specify member, collection and
|
120
|
+
# new actions.
|
121
|
+
# * Hash: Lets you set the required privilege for each action:
|
122
|
+
# {:+show+ => :+show+, :+mark_as_important+ => :+update+}
|
123
|
+
# * Array of actions or pairs: [:+show+, [:+mark_as_important+, :+update+]],
|
124
|
+
# with single actions requiring the privilege of the same name as the method.
|
125
|
+
# * Single method symbol: :+show+
|
126
|
+
# [:+additional_member+]
|
127
|
+
# Allows to add additional member actions to the default resource +member+
|
128
|
+
# actions.
|
129
|
+
# [:+collection+]
|
130
|
+
# Collection actions are like :+index+, actions without any controller object
|
131
|
+
# to check attributes of. If +nested_in+ is given, a new object is
|
132
|
+
# created from the parent object, e.g. @company.branches.new. Without
|
133
|
+
# +nested_in+, attribute check is deactivated for these actions. By
|
134
|
+
# default, collection is set to :+index+.
|
135
|
+
# [:+additional_collection+]
|
136
|
+
# Allows to add additional collection actions to the default resource +collection+
|
137
|
+
# actions.
|
138
|
+
# [:+new+]
|
139
|
+
# +new+ methods are actions such as +new+ and +create+, which don't
|
140
|
+
# receive a params[:id] to load an object from, but
|
141
|
+
# a params[:controller_name_singular] hash with attributes for a new
|
142
|
+
# object. The attributes will be used here to create a new object and
|
143
|
+
# check the object against the authorization rules. The object is
|
144
|
+
# assigned to @controller_name_singular, e.g. @branch.
|
145
|
+
#
|
146
|
+
# If +nested_in+ is given, the new object
|
147
|
+
# is created from the parent_object.controller_name
|
148
|
+
# proxy, e.g. company.branches.new(params[:branch]). By default,
|
149
|
+
# +new+ is set to [:new, :create].
|
150
|
+
# [:+additional_new+]
|
151
|
+
# Allows to add additional new actions to the default resource +new+ actions.
|
152
|
+
# [:+context+]
|
153
|
+
# The context is used to determine the model to load objects from for the
|
154
|
+
# before_actions and the context of privileges to use in authorization
|
155
|
+
# checks.
|
156
|
+
# [:+nested_in+]
|
157
|
+
# Specifies the parent controller if the resource is nested in another
|
158
|
+
# one. This is used to automatically load the parent object, e.g.
|
159
|
+
# @+company+ from params[:company_id] for a BranchController nested in
|
160
|
+
# a CompanyController.
|
161
|
+
# [:+shallow+]
|
162
|
+
# Only relevant when used in conjunction with +nested_in+. Specifies a nested resource
|
163
|
+
# as being a shallow nested resource, resulting in the controller not attempting to
|
164
|
+
# load a parent object for all member actions defined by +member+ and
|
165
|
+
# +additional_member+ or rather the default member actions (:+show+, :+edit+,
|
166
|
+
# :+update+, :+destroy+).
|
167
|
+
# [:+no_attribute_check+]
|
168
|
+
# Allows to set actions for which no attribute check should be performed.
|
169
|
+
# See filter_access_to on details. By default, with no +nested_in+,
|
170
|
+
# +no_attribute_check+ is set to all collections. If +nested_in+ is given
|
171
|
+
# +no_attribute_check+ is empty by default.
|
172
|
+
# [:+strong_parameters+]
|
173
|
+
# If set to true, relies on controller to provide instance variable and
|
174
|
+
# create new object in :create action. Set true if you use strong_params
|
175
|
+
# and false if you use protected_attributes.
|
176
|
+
#
|
177
|
+
def filter_resource_access(options = {})
|
178
|
+
options = {
|
179
|
+
:new => [:new, :create],
|
180
|
+
:additional_new => nil,
|
181
|
+
:member => [:show, :edit, :update, :destroy],
|
182
|
+
:additional_member => nil,
|
183
|
+
:collection => [:index],
|
184
|
+
:additional_collection => nil,
|
185
|
+
#:new_method_for_collection => nil, # only symbol method name
|
186
|
+
#:new_method => nil, # only symbol method name
|
187
|
+
#:load_method => nil, # only symbol method name
|
188
|
+
:no_attribute_check => nil,
|
189
|
+
:context => nil,
|
190
|
+
:model => nil,
|
191
|
+
:nested_in => nil,
|
192
|
+
:strong_parameters => nil
|
193
|
+
}.merge(options)
|
194
|
+
options.merge!({ :strong_parameters => true }) if options[:strong_parameters] == nil
|
195
|
+
|
196
|
+
new_actions = actions_from_option( options[:new] ).merge(
|
197
|
+
actions_from_option(options[:additional_new]) )
|
198
|
+
members = actions_from_option(options[:member]).merge(
|
199
|
+
actions_from_option(options[:additional_member]))
|
200
|
+
collections = actions_from_option(options[:collection]).merge(
|
201
|
+
actions_from_option(options[:additional_collection]))
|
202
|
+
|
203
|
+
no_attribute_check_actions = options[:strong_parameters] ? actions_from_option(options[:collection]).merge(actions_from_option([:create])) : collections
|
204
|
+
|
205
|
+
options[:no_attribute_check] ||= no_attribute_check_actions.keys unless options[:nested_in]
|
206
|
+
|
207
|
+
unless options[:nested_in].blank?
|
208
|
+
load_parent_method = :"load_#{options[:nested_in].to_s.singularize}"
|
209
|
+
shallow_exceptions = options[:shallow] ? {:except => members.keys} : {}
|
210
|
+
before_action shallow_exceptions do |controller|
|
211
|
+
if controller.respond_to?(load_parent_method, true)
|
212
|
+
controller.send(load_parent_method)
|
213
|
+
else
|
214
|
+
controller.send(:load_parent_controller_object, options[:nested_in])
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
new_for_collection_method = :"new_#{controller_name.singularize}_for_collection"
|
219
|
+
before_action :only => collections.keys do |controller|
|
220
|
+
# new_for_collection
|
221
|
+
if controller.respond_to?(new_for_collection_method, true)
|
222
|
+
controller.send(new_for_collection_method)
|
223
|
+
else
|
224
|
+
controller.send(:new_controller_object_for_collection,
|
225
|
+
options[:context] || controller_name, options[:nested_in], options[:strong_parameters])
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
unless options[:strong_parameters]
|
231
|
+
new_from_params_method = :"new_#{controller_name.singularize}_from_params"
|
232
|
+
before_action :only => new_actions.keys do |controller|
|
233
|
+
# new_from_params
|
234
|
+
if controller.respond_to?(new_from_params_method, true)
|
235
|
+
controller.send(new_from_params_method)
|
236
|
+
else
|
237
|
+
controller.send(:new_controller_object_from_params,
|
238
|
+
options[:context] || controller_name, options[:nested_in], options[:strong_parameters])
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
new_object_method = :"new_#{controller_name.singularize}"
|
243
|
+
before_action :only => :new do |controller|
|
244
|
+
# new_from_params
|
245
|
+
if controller.respond_to?(new_object_method, true)
|
246
|
+
controller.send(new_object_method)
|
247
|
+
else
|
248
|
+
controller.send(:new_blank_controller_object,
|
249
|
+
options[:context] || controller_name, options[:nested_in], options[:strong_parameters], options[:model])
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
load_method = :"load_#{controller_name.singularize}"
|
255
|
+
before_action :only => members.keys do |controller|
|
256
|
+
# load controller object
|
257
|
+
if controller.respond_to?(load_method, true)
|
258
|
+
controller.send(load_method)
|
259
|
+
else
|
260
|
+
controller.send(:load_controller_object, options[:context] || controller_name, options[:model])
|
261
|
+
end
|
262
|
+
end
|
263
|
+
filter_access_to :all, :attribute_check => true, :context => options[:context], :model => options[:model]
|
264
|
+
|
265
|
+
members.merge(new_actions).merge(collections).each do |action, privilege|
|
266
|
+
if action != privilege or (options[:no_attribute_check] and options[:no_attribute_check].include?(action))
|
267
|
+
filter_options = {
|
268
|
+
:strong_parameters => options[:strong_parameters],
|
269
|
+
:context => options[:context],
|
270
|
+
:attribute_check => !options[:no_attribute_check] || !options[:no_attribute_check].include?(action),
|
271
|
+
:model => options[:model]
|
272
|
+
}
|
273
|
+
filter_options[:require] = privilege if action != privilege
|
274
|
+
filter_access_to(action, filter_options)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
protected
|
281
|
+
|
282
|
+
def filter_access_filter # :nodoc:
|
283
|
+
unless allowed?(action_name)
|
284
|
+
if respond_to?(:permission_denied, true)
|
285
|
+
# permission_denied needs to render or redirect
|
286
|
+
send(:permission_denied)
|
287
|
+
else
|
288
|
+
render plain: 'You are not allowed to access this action.', status: :forbidden
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def load_controller_object(context_without_namespace = nil, model = nil) # :nodoc:
|
294
|
+
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
295
|
+
model = model ? model.classify.constantize : context_without_namespace.to_s.classify.constantize
|
296
|
+
instance_variable_set(instance_var, model.find(params[:id]))
|
297
|
+
end
|
298
|
+
|
299
|
+
def load_parent_controller_object(parent_context_without_namespace) # :nodoc:
|
300
|
+
instance_var = :"@#{parent_context_without_namespace.to_s.singularize}"
|
301
|
+
model = parent_context_without_namespace.to_s.classify.constantize
|
302
|
+
instance_variable_set(instance_var, model.find(params[:"#{parent_context_without_namespace.to_s.singularize}_id"]))
|
303
|
+
end
|
304
|
+
|
305
|
+
def new_controller_object_from_params(context_without_namespace, parent_context_without_namespace, strong_params) # :nodoc:
|
306
|
+
model_or_proxy = parent_context_without_namespace ?
|
307
|
+
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
308
|
+
context_without_namespace.to_s.classify.constantize
|
309
|
+
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
310
|
+
instance_variable_set(instance_var,
|
311
|
+
model_or_proxy.new(params[context_without_namespace.to_s.singularize]))
|
312
|
+
end
|
313
|
+
|
314
|
+
def new_blank_controller_object(context_without_namespace, parent_context_without_namespace, strong_params, model) # :nodoc:
|
315
|
+
if model
|
316
|
+
model_or_proxy = model.to_s.classify.constantize
|
317
|
+
else
|
318
|
+
model_or_proxy = parent_context_without_namespace ?
|
319
|
+
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
320
|
+
context_without_namespace.to_s.classify.constantize
|
321
|
+
end
|
322
|
+
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
323
|
+
instance_variable_set(instance_var,
|
324
|
+
model_or_proxy.new())
|
325
|
+
end
|
326
|
+
|
327
|
+
def new_controller_object_for_collection(context_without_namespace, parent_context_without_namespace, strong_params) # :nodoc:
|
328
|
+
model_or_proxy = parent_context_without_namespace ?
|
329
|
+
instance_variable_get(:"@#{parent_context_without_namespace.to_s.singularize}").send(context_without_namespace.to_sym) :
|
330
|
+
context_without_namespace.to_s.classify.constantize
|
331
|
+
instance_var = :"@#{context_without_namespace.to_s.singularize}"
|
332
|
+
instance_variable_set(instance_var, model_or_proxy.new)
|
333
|
+
end
|
334
|
+
|
335
|
+
def api_class
|
336
|
+
self.class
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|