cancancan 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cancan.rb +1 -1
- data/lib/cancan/ability.rb +13 -175
- data/lib/cancan/ability/actions.rb +91 -0
- data/lib/cancan/ability/rules.rb +85 -0
- data/lib/cancan/conditions_matcher.rb +93 -0
- data/lib/cancan/controller_resource.rb +22 -207
- data/lib/cancan/controller_resource_builder.rb +24 -0
- data/lib/cancan/controller_resource_finder.rb +35 -0
- data/lib/cancan/controller_resource_loader.rb +116 -0
- data/lib/cancan/controller_resource_name_finder.rb +21 -0
- data/lib/cancan/controller_resource_sanitizer.rb +30 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +55 -70
- data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +39 -0
- data/lib/cancan/rule.rb +4 -83
- data/lib/cancan/version.rb +1 -1
- metadata +11 -2
@@ -1,8 +1,11 @@
|
|
1
|
+
require_relative 'controller_resource_loader.rb'
|
1
2
|
module CanCan
|
2
3
|
# Handle the load and authorization controller logic
|
3
4
|
# so we don't clutter up all controllers with non-interface methods.
|
4
5
|
# This class is used internally, so you do not need to call methods directly on it.
|
5
6
|
class ControllerResource # :nodoc:
|
7
|
+
include ControllerResourceLoader
|
8
|
+
|
6
9
|
def self.add_before_action(controller_class, method, *args)
|
7
10
|
options = args.extract_options!
|
8
11
|
resource_name = args.first
|
@@ -29,15 +32,6 @@ module CanCan
|
|
29
32
|
authorize_resource
|
30
33
|
end
|
31
34
|
|
32
|
-
def load_resource
|
33
|
-
return if skip?(:load)
|
34
|
-
if load_instance?
|
35
|
-
self.resource_instance ||= load_resource_instance
|
36
|
-
elsif load_collection?
|
37
|
-
self.collection_instance ||= load_collection
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
35
|
def authorize_resource
|
42
36
|
return if skip?(:authorize)
|
43
37
|
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
|
@@ -56,11 +50,19 @@ module CanCan
|
|
56
50
|
|
57
51
|
protected
|
58
52
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
53
|
+
# Returns the class used for this resource. This can be overriden by the :class option.
|
54
|
+
# If +false+ is passed in it will use the resource name as a symbol in which case it should
|
55
|
+
# only be used for authorization, not loading since there's no class to load through.
|
56
|
+
def resource_class
|
57
|
+
case @options[:class]
|
58
|
+
when false
|
59
|
+
name.to_sym
|
60
|
+
when nil
|
61
|
+
namespaced_name.to_s.camelize.constantize
|
62
|
+
when String
|
63
|
+
@options[:class].constantize
|
64
|
+
else
|
65
|
+
@options[:class]
|
64
66
|
end
|
65
67
|
end
|
66
68
|
|
@@ -72,95 +74,12 @@ module CanCan
|
|
72
74
|
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
|
73
75
|
end
|
74
76
|
|
75
|
-
def load_collection
|
76
|
-
resource_base.accessible_by(current_ability, authorization_action)
|
77
|
-
end
|
78
|
-
|
79
|
-
def build_resource
|
80
|
-
resource = resource_base.new(resource_params || {})
|
81
|
-
assign_attributes(resource)
|
82
|
-
end
|
83
|
-
|
84
|
-
def assign_attributes(resource)
|
85
|
-
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
86
|
-
initial_attributes.each do |attr_name, value|
|
87
|
-
resource.send("#{attr_name}=", value)
|
88
|
-
end
|
89
|
-
resource
|
90
|
-
end
|
91
|
-
|
92
|
-
def initial_attributes
|
93
|
-
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value|
|
94
|
-
resource_params && resource_params.include?(key)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def find_resource
|
99
|
-
if @options[:singleton] && parent_resource.respond_to?(name)
|
100
|
-
parent_resource.send(name)
|
101
|
-
elsif @options[:find_by]
|
102
|
-
find_resource_using_find_by
|
103
|
-
else
|
104
|
-
adapter.find(resource_base, id_param)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def find_resource_using_find_by
|
109
|
-
if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
|
110
|
-
resource_base.send("find_by_#{@options[:find_by]}!", id_param)
|
111
|
-
elsif resource_base.respond_to? 'find_by'
|
112
|
-
resource_base.send('find_by', @options[:find_by].to_sym => id_param)
|
113
|
-
else
|
114
|
-
resource_base.send(@options[:find_by], id_param)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def adapter
|
119
|
-
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
|
120
|
-
end
|
121
|
-
|
122
|
-
def authorization_action
|
123
|
-
parent? ? parent_authorization_action : @params[:action].to_sym
|
124
|
-
end
|
125
|
-
|
126
|
-
def parent_authorization_action
|
127
|
-
@options[:parent_action] || :show
|
128
|
-
end
|
129
|
-
|
130
|
-
def id_param
|
131
|
-
@params[id_param_key].to_s if @params[id_param_key].present?
|
132
|
-
end
|
133
|
-
|
134
|
-
def id_param_key
|
135
|
-
if @options[:id_param]
|
136
|
-
@options[:id_param]
|
137
|
-
else
|
138
|
-
parent? ? :"#{name}_id" : :id
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
77
|
def member_action?
|
143
78
|
new_actions.include?(@params[:action].to_sym) || @options[:singleton] ||
|
144
79
|
((@params[:id] || @params[@options[:id_param]]) &&
|
145
80
|
!collection_actions.include?(@params[:action].to_sym))
|
146
81
|
end
|
147
82
|
|
148
|
-
# Returns the class used for this resource. This can be overriden by the :class option.
|
149
|
-
# If +false+ is passed in it will use the resource name as a symbol in which case it should
|
150
|
-
# only be used for authorization, not loading since there's no class to load through.
|
151
|
-
def resource_class
|
152
|
-
case @options[:class]
|
153
|
-
when false then
|
154
|
-
name.to_sym
|
155
|
-
when nil then
|
156
|
-
namespaced_name.to_s.camelize.constantize
|
157
|
-
when String then
|
158
|
-
@options[:class].constantize
|
159
|
-
else
|
160
|
-
@options[:class]
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
83
|
def resource_class_with_parent
|
165
84
|
parent_resource ? { parent_resource => resource_class } : resource_class
|
166
85
|
end
|
@@ -183,114 +102,10 @@ module CanCan
|
|
183
102
|
@controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
|
184
103
|
end
|
185
104
|
|
186
|
-
# The object that methods (such as "find", "new" or "build") are called on.
|
187
|
-
# If the :through option is passed it will go through an association on that instance.
|
188
|
-
# If the :shallow option is passed it will use the resource_class if there's no parent
|
189
|
-
# If the :singleton option is passed it won't use the association because it needs to be handled later.
|
190
|
-
def resource_base
|
191
|
-
if @options[:through]
|
192
|
-
resource_base_through
|
193
|
-
else
|
194
|
-
resource_class
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def resource_base_through
|
199
|
-
if parent_resource
|
200
|
-
if @options[:singleton]
|
201
|
-
resource_class
|
202
|
-
else
|
203
|
-
parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
204
|
-
end
|
205
|
-
elsif @options[:shallow]
|
206
|
-
resource_class
|
207
|
-
else
|
208
|
-
# maybe this should be a record not found error instead?
|
209
|
-
raise AccessDenied.new(nil, authorization_action, resource_class)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def parent_name
|
214
|
-
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
|
215
|
-
end
|
216
|
-
|
217
|
-
# The object to load this resource through.
|
218
|
-
def parent_resource
|
219
|
-
parent_name && fetch_parent(parent_name)
|
220
|
-
end
|
221
|
-
|
222
|
-
def fetch_parent(name)
|
223
|
-
if @controller.instance_variable_defined? "@#{name}"
|
224
|
-
@controller.instance_variable_get("@#{name}")
|
225
|
-
elsif @controller.respond_to?(name, true)
|
226
|
-
@controller.send(name)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def current_ability
|
231
|
-
@controller.send(:current_ability)
|
232
|
-
end
|
233
|
-
|
234
|
-
def name
|
235
|
-
@name || name_from_controller
|
236
|
-
end
|
237
|
-
|
238
|
-
def resource_params
|
239
|
-
if parameters_require_sanitizing? && params_method.present?
|
240
|
-
case params_method
|
241
|
-
when Symbol then
|
242
|
-
@controller.send(params_method)
|
243
|
-
when String then
|
244
|
-
@controller.instance_eval(params_method)
|
245
|
-
when Proc then
|
246
|
-
params_method.call(@controller)
|
247
|
-
end
|
248
|
-
else
|
249
|
-
resource_params_by_namespaced_name
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
105
|
def parameters_require_sanitizing?
|
254
106
|
save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
|
255
107
|
end
|
256
108
|
|
257
|
-
def resource_params_by_namespaced_name
|
258
|
-
if @options[:instance_name] && @params.key?(extract_key(@options[:instance_name]))
|
259
|
-
@params[extract_key(@options[:instance_name])]
|
260
|
-
elsif @options[:class] && @params.key?(extract_key(@options[:class]))
|
261
|
-
@params[extract_key(@options[:class])]
|
262
|
-
else
|
263
|
-
params = @params[extract_key(namespaced_name)]
|
264
|
-
params.is_a?(Hash) ? params : nil
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def params_method
|
269
|
-
params_methods.each do |method|
|
270
|
-
return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
|
271
|
-
method.is_a?(String) || method.is_a?(Proc)
|
272
|
-
end
|
273
|
-
nil
|
274
|
-
end
|
275
|
-
|
276
|
-
def params_methods
|
277
|
-
methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
|
278
|
-
methods.unshift(@options[:param_method]) if @options[:param_method].present?
|
279
|
-
methods
|
280
|
-
end
|
281
|
-
|
282
|
-
def namespace
|
283
|
-
@params[:controller].split('/')[0..-2]
|
284
|
-
end
|
285
|
-
|
286
|
-
def namespaced_name
|
287
|
-
[namespace, name].join('/').singularize.camelize.safe_constantize || name
|
288
|
-
end
|
289
|
-
|
290
|
-
def name_from_controller
|
291
|
-
@params[:controller].split('/').last.singularize
|
292
|
-
end
|
293
|
-
|
294
109
|
def instance_name
|
295
110
|
@options[:instance_name] || name
|
296
111
|
end
|
@@ -299,10 +114,6 @@ module CanCan
|
|
299
114
|
[:index] + Array(@options[:collection])
|
300
115
|
end
|
301
116
|
|
302
|
-
def new_actions
|
303
|
-
%i[new create] + Array(@options[:new])
|
304
|
-
end
|
305
|
-
|
306
117
|
def save_actions
|
307
118
|
%i[create update]
|
308
119
|
end
|
@@ -313,8 +124,12 @@ module CanCan
|
|
313
124
|
Array(options).include?(@params[:action].to_sym)
|
314
125
|
end
|
315
126
|
|
316
|
-
def
|
317
|
-
|
127
|
+
def adapter
|
128
|
+
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
|
129
|
+
end
|
130
|
+
|
131
|
+
def current_ability
|
132
|
+
@controller.send(:current_ability)
|
318
133
|
end
|
319
134
|
end
|
320
135
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ControllerResourceBuilder
|
3
|
+
protected
|
4
|
+
|
5
|
+
def build_resource
|
6
|
+
resource = resource_base.new(resource_params || {})
|
7
|
+
assign_attributes(resource)
|
8
|
+
end
|
9
|
+
|
10
|
+
def assign_attributes(resource)
|
11
|
+
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
12
|
+
initial_attributes.each do |attr_name, value|
|
13
|
+
resource.send("#{attr_name}=", value)
|
14
|
+
end
|
15
|
+
resource
|
16
|
+
end
|
17
|
+
|
18
|
+
def initial_attributes
|
19
|
+
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value|
|
20
|
+
resource_params && resource_params.include?(key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ControllerResourceFinder
|
3
|
+
protected
|
4
|
+
|
5
|
+
def find_resource
|
6
|
+
if @options[:singleton] && parent_resource.respond_to?(name)
|
7
|
+
parent_resource.send(name)
|
8
|
+
elsif @options[:find_by]
|
9
|
+
find_resource_using_find_by
|
10
|
+
else
|
11
|
+
adapter.find(resource_base, id_param)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_resource_using_find_by
|
16
|
+
if resource_base.respond_to? 'find_by'
|
17
|
+
resource_base.send('find_by', @options[:find_by].to_sym => id_param)
|
18
|
+
else
|
19
|
+
resource_base.send(@options[:find_by], id_param)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def id_param
|
24
|
+
@params[id_param_key].to_s if @params[id_param_key].present?
|
25
|
+
end
|
26
|
+
|
27
|
+
def id_param_key
|
28
|
+
if @options[:id_param]
|
29
|
+
@options[:id_param]
|
30
|
+
else
|
31
|
+
parent? ? :"#{name}_id" : :id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require_relative 'controller_resource_finder.rb'
|
2
|
+
require_relative 'controller_resource_name_finder.rb'
|
3
|
+
require_relative 'controller_resource_builder.rb'
|
4
|
+
require_relative 'controller_resource_sanitizer.rb'
|
5
|
+
module CanCan
|
6
|
+
module ControllerResourceLoader
|
7
|
+
include CanCan::ControllerResourceNameFinder
|
8
|
+
include CanCan::ControllerResourceFinder
|
9
|
+
include CanCan::ControllerResourceBuilder
|
10
|
+
include CanCan::ControllerResourceSanitizer
|
11
|
+
|
12
|
+
def load_resource
|
13
|
+
return if skip?(:load)
|
14
|
+
if load_instance?
|
15
|
+
self.resource_instance ||= load_resource_instance
|
16
|
+
elsif load_collection?
|
17
|
+
self.collection_instance ||= load_collection
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def new_actions
|
24
|
+
%i[new create] + Array(@options[:new])
|
25
|
+
end
|
26
|
+
|
27
|
+
def resource_params_by_key(key)
|
28
|
+
return unless @options[key] && @params.key?(extract_key(@options[key]))
|
29
|
+
@params[extract_key(@options[key])]
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource_params_by_namespaced_name
|
33
|
+
resource_params_by_key(:instance_name) || resource_params_by_key(:class) || (
|
34
|
+
params = @params[extract_key(namespaced_name)]
|
35
|
+
params.respond_to?(:to_h) ? params : nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def resource_params
|
39
|
+
if parameters_require_sanitizing? && params_method.present?
|
40
|
+
sanitize_parameters
|
41
|
+
else
|
42
|
+
resource_params_by_namespaced_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch_parent(name)
|
47
|
+
if @controller.instance_variable_defined? "@#{name}"
|
48
|
+
@controller.instance_variable_get("@#{name}")
|
49
|
+
elsif @controller.respond_to?(name, true)
|
50
|
+
@controller.send(name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The object to load this resource through.
|
55
|
+
def parent_resource
|
56
|
+
parent_name && fetch_parent(parent_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def parent_name
|
60
|
+
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def resource_base_through_parent_resource
|
64
|
+
if @options[:singleton]
|
65
|
+
resource_class
|
66
|
+
else
|
67
|
+
parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def resource_base_through
|
72
|
+
if parent_resource
|
73
|
+
resource_base_through_parent_resource
|
74
|
+
elsif @options[:shallow]
|
75
|
+
resource_class
|
76
|
+
else
|
77
|
+
# maybe this should be a record not found error instead?
|
78
|
+
raise AccessDenied.new(nil, authorization_action, resource_class)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# The object that methods (such as "find", "new" or "build") are called on.
|
83
|
+
# If the :through option is passed it will go through an association on that instance.
|
84
|
+
# If the :shallow option is passed it will use the resource_class if there's no parent
|
85
|
+
# If the :singleton option is passed it won't use the association because it needs to be handled later.
|
86
|
+
def resource_base
|
87
|
+
@options[:through] ? resource_base_through : resource_class
|
88
|
+
end
|
89
|
+
|
90
|
+
def parent_authorization_action
|
91
|
+
@options[:parent_action] || :show
|
92
|
+
end
|
93
|
+
|
94
|
+
def authorization_action
|
95
|
+
parent? ? parent_authorization_action : @params[:action].to_sym
|
96
|
+
end
|
97
|
+
|
98
|
+
def load_collection
|
99
|
+
resource_base.accessible_by(current_ability, authorization_action)
|
100
|
+
end
|
101
|
+
|
102
|
+
def load_resource_instance
|
103
|
+
if !parent? && new_actions.include?(@params[:action].to_sym)
|
104
|
+
build_resource
|
105
|
+
elsif id_param || @options[:singleton]
|
106
|
+
find_resource
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def extract_key(value)
|
113
|
+
value.to_s.underscore.tr('/', '_')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|