ae_declarative_authorization 0.8.0 → 0.9.0
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.
- 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
|