cancancan 1.10.0 → 3.5.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/cancancan.gemspec +19 -21
- data/init.rb +2 -0
- data/lib/cancan/ability/actions.rb +93 -0
- data/lib/cancan/ability/rules.rb +96 -0
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/ability.rb +114 -146
- data/lib/cancan/class_matcher.rb +30 -0
- data/lib/cancan/conditions_matcher.rb +147 -0
- data/lib/cancan/config.rb +101 -0
- data/lib/cancan/controller_additions.rb +38 -41
- data/lib/cancan/controller_resource.rb +59 -215
- data/lib/cancan/controller_resource_builder.rb +26 -0
- data/lib/cancan/controller_resource_finder.rb +42 -0
- data/lib/cancan/controller_resource_loader.rb +120 -0
- data/lib/cancan/controller_resource_name_finder.rb +23 -0
- data/lib/cancan/controller_resource_sanitizer.rb +32 -0
- data/lib/cancan/exceptions.rb +25 -5
- data/lib/cancan/matchers.rb +17 -3
- data/lib/cancan/model_adapters/abstract_adapter.rb +30 -9
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +43 -15
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +157 -82
- data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
- data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
- data/lib/cancan/model_adapters/strategies/base.rb +40 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
- data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
- data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
- data/lib/cancan/model_additions.rb +6 -3
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/relevant.rb +29 -0
- data/lib/cancan/rule.rb +79 -91
- data/lib/cancan/rules_compressor.rb +23 -0
- data/lib/cancan/sti_detector.rb +12 -0
- data/lib/cancan/unauthorized_message_resolver.rb +24 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancan.rb +16 -12
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +4 -2
- data/lib/generators/cancan/ability/templates/ability.rb +9 -9
- metadata +82 -93
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.travis.yml +0 -48
- data/Appraisals +0 -135
- data/CHANGELOG.rdoc +0 -495
- data/CONTRIBUTING.md +0 -23
- data/Gemfile +0 -3
- data/LICENSE +0 -22
- data/README.md +0 -197
- data/Rakefile +0 -9
- data/gemfiles/activerecord_3.0.gemfile +0 -18
- data/gemfiles/activerecord_3.1.gemfile +0 -20
- data/gemfiles/activerecord_3.2.gemfile +0 -20
- data/gemfiles/activerecord_4.0.gemfile +0 -17
- data/gemfiles/activerecord_4.1.gemfile +0 -17
- data/gemfiles/activerecord_4.2.gemfile +0 -17
- data/gemfiles/datamapper_1.x.gemfile +0 -14
- data/gemfiles/mongoid_2.x.gemfile +0 -20
- data/gemfiles/sequel_3.x.gemfile +0 -20
- data/lib/cancan/inherited_resource.rb +0 -20
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -47
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
- data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
- data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
- data/spec/README.rdoc +0 -27
- data/spec/cancan/ability_spec.rb +0 -487
- data/spec/cancan/controller_additions_spec.rb +0 -141
- data/spec/cancan/controller_resource_spec.rb +0 -648
- data/spec/cancan/exceptions_spec.rb +0 -58
- data/spec/cancan/inherited_resource_spec.rb +0 -71
- data/spec/cancan/matchers_spec.rb +0 -29
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -40
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -446
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
- data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
- data/spec/cancan/rule_spec.rb +0 -52
- data/spec/matchers.rb +0 -13
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -27
- data/spec/support/ability.rb +0 -7
@@ -1,24 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'controller_resource_loader.rb'
|
1
4
|
module CanCan
|
2
|
-
# Handle the load and authorization controller logic
|
5
|
+
# Handle the load and authorization controller logic
|
6
|
+
# so we don't clutter up all controllers with non-interface methods.
|
3
7
|
# This class is used internally, so you do not need to call methods directly on it.
|
4
8
|
class ControllerResource # :nodoc:
|
5
|
-
|
9
|
+
include ControllerResourceLoader
|
10
|
+
|
11
|
+
def self.add_before_action(controller_class, method, *args)
|
6
12
|
options = args.extract_options!
|
7
13
|
resource_name = args.first
|
8
|
-
|
9
|
-
controller_class.send(
|
10
|
-
controller.class.cancan_resource_class
|
14
|
+
before_action_method = before_callback_name(options)
|
15
|
+
controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller|
|
16
|
+
controller.class.cancan_resource_class
|
17
|
+
.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
21
|
+
def self.before_callback_name(options)
|
22
|
+
options.delete(:prepend) ? :prepend_before_action : :before_action
|
23
|
+
end
|
24
|
+
|
14
25
|
def initialize(controller, *args)
|
15
26
|
@controller = controller
|
16
27
|
@params = controller.params
|
17
28
|
@options = args.extract_options!
|
18
29
|
@name = args.first
|
19
|
-
raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
|
20
|
-
raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
|
21
|
-
raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
|
22
30
|
end
|
23
31
|
|
24
32
|
def load_and_authorize_resource
|
@@ -26,46 +34,39 @@ module CanCan
|
|
26
34
|
authorize_resource
|
27
35
|
end
|
28
36
|
|
29
|
-
def load_resource
|
30
|
-
unless skip?(:load)
|
31
|
-
if load_instance?
|
32
|
-
self.resource_instance ||= load_resource_instance
|
33
|
-
elsif load_collection?
|
34
|
-
self.collection_instance ||= load_collection
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
37
|
def authorize_resource
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
return if skip?(:authorize)
|
39
|
+
|
40
|
+
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
|
43
41
|
end
|
44
42
|
|
45
43
|
def parent?
|
46
|
-
@options.
|
44
|
+
@options.key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
|
47
45
|
end
|
48
46
|
|
49
|
-
def skip?(behavior)
|
50
|
-
options = @controller.class.cancan_skipper[behavior][@name]
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
elsif options[:except] && ![options[:except]].flatten.include?(@params[:action].to_sym)
|
56
|
-
true
|
57
|
-
elsif [options[:only]].flatten.include?(@params[:action].to_sym)
|
58
|
-
true
|
59
|
-
end
|
47
|
+
def skip?(behavior)
|
48
|
+
return false unless (options = @controller.class.cancan_skipper[behavior][@name])
|
49
|
+
|
50
|
+
options == {} ||
|
51
|
+
options[:except] && !action_exists_in?(options[:except]) ||
|
52
|
+
action_exists_in?(options[:only])
|
60
53
|
end
|
61
54
|
|
62
55
|
protected
|
63
56
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
57
|
+
# Returns the class used for this resource. This can be overridden by the :class option.
|
58
|
+
# If +false+ is passed in it will use the resource name as a symbol in which case it should
|
59
|
+
# only be used for authorization, not loading since there's no class to load through.
|
60
|
+
def resource_class
|
61
|
+
case @options[:class]
|
62
|
+
when false
|
63
|
+
name.to_sym
|
64
|
+
when nil
|
65
|
+
namespaced_name.to_s.camelize.constantize
|
66
|
+
when String
|
67
|
+
@options[:class].constantize
|
68
|
+
else
|
69
|
+
@options[:class]
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
@@ -77,85 +78,14 @@ module CanCan
|
|
77
78
|
resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
|
78
79
|
end
|
79
80
|
|
80
|
-
def load_collection
|
81
|
-
resource_base.accessible_by(current_ability, authorization_action)
|
82
|
-
end
|
83
|
-
|
84
|
-
def build_resource
|
85
|
-
resource = resource_base.new(resource_params || {})
|
86
|
-
assign_attributes(resource)
|
87
|
-
end
|
88
|
-
|
89
|
-
def assign_attributes(resource)
|
90
|
-
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
91
|
-
initial_attributes.each do |attr_name, value|
|
92
|
-
resource.send("#{attr_name}=", value)
|
93
|
-
end
|
94
|
-
resource
|
95
|
-
end
|
96
|
-
|
97
|
-
def initial_attributes
|
98
|
-
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
|
99
|
-
resource_params && resource_params.include?(key)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def find_resource
|
104
|
-
if @options[:singleton] && parent_resource.respond_to?(name)
|
105
|
-
parent_resource.send(name)
|
106
|
-
else
|
107
|
-
if @options[:find_by]
|
108
|
-
if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
|
109
|
-
resource_base.send("find_by_#{@options[:find_by]}!", id_param)
|
110
|
-
elsif resource_base.respond_to? "find_by"
|
111
|
-
resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
|
112
|
-
else
|
113
|
-
resource_base.send(@options[:find_by], id_param)
|
114
|
-
end
|
115
|
-
else
|
116
|
-
adapter.find(resource_base, id_param)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def adapter
|
122
|
-
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
|
123
|
-
end
|
124
|
-
|
125
|
-
def authorization_action
|
126
|
-
parent? ? :show : @params[:action].to_sym
|
127
|
-
end
|
128
|
-
|
129
|
-
def id_param
|
130
|
-
@params[id_param_key].to_s if @params[id_param_key]
|
131
|
-
end
|
132
|
-
|
133
|
-
def id_param_key
|
134
|
-
if @options[:id_param]
|
135
|
-
@options[:id_param]
|
136
|
-
else
|
137
|
-
parent? ? :"#{name}_id" : :id
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
81
|
def member_action?
|
142
|
-
new_actions.include?(@params[:action].to_sym) || @options[:singleton] ||
|
143
|
-
|
144
|
-
|
145
|
-
# Returns the class used for this resource. This can be overriden by the :class option.
|
146
|
-
# If +false+ is passed in it will use the resource name as a symbol in which case it should
|
147
|
-
# only be used for authorization, not loading since there's no class to load through.
|
148
|
-
def resource_class
|
149
|
-
case @options[:class]
|
150
|
-
when false then name.to_sym
|
151
|
-
when nil then namespaced_name.to_s.camelize.constantize
|
152
|
-
when String then @options[:class].constantize
|
153
|
-
else @options[:class]
|
154
|
-
end
|
82
|
+
new_actions.include?(@params[:action].to_sym) || @options[:singleton] ||
|
83
|
+
((@params[:id] || @params[@options[:id_param]]) &&
|
84
|
+
!collection_actions.include?(@params[:action].to_sym))
|
155
85
|
end
|
156
86
|
|
157
87
|
def resource_class_with_parent
|
158
|
-
parent_resource ? {parent_resource => resource_class} : resource_class
|
88
|
+
parent_resource ? { parent_resource => resource_class } : resource_class
|
159
89
|
end
|
160
90
|
|
161
91
|
def resource_instance=(instance)
|
@@ -163,7 +93,9 @@ module CanCan
|
|
163
93
|
end
|
164
94
|
|
165
95
|
def resource_instance
|
166
|
-
@controller.
|
96
|
+
return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
|
97
|
+
|
98
|
+
@controller.instance_variable_get("@#{instance_name}")
|
167
99
|
end
|
168
100
|
|
169
101
|
def collection_instance=(instance)
|
@@ -171,107 +103,15 @@ module CanCan
|
|
171
103
|
end
|
172
104
|
|
173
105
|
def collection_instance
|
174
|
-
@controller.
|
175
|
-
end
|
176
|
-
|
177
|
-
# The object that methods (such as "find", "new" or "build") are called on.
|
178
|
-
# If the :through option is passed it will go through an association on that instance.
|
179
|
-
# If the :shallow option is passed it will use the resource_class if there's no parent
|
180
|
-
# If the :singleton option is passed it won't use the association because it needs to be handled later.
|
181
|
-
def resource_base
|
182
|
-
if @options[:through]
|
183
|
-
if parent_resource
|
184
|
-
base = @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
185
|
-
base = base.scoped if base.respond_to?(:scoped) && defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
|
186
|
-
base
|
187
|
-
elsif @options[:shallow]
|
188
|
-
resource_class
|
189
|
-
else
|
190
|
-
raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
|
191
|
-
end
|
192
|
-
else
|
193
|
-
resource_class
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def parent_name
|
198
|
-
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
|
199
|
-
end
|
200
|
-
|
201
|
-
# The object to load this resource through.
|
202
|
-
def parent_resource
|
203
|
-
parent_name && fetch_parent(parent_name)
|
204
|
-
end
|
205
|
-
|
206
|
-
def fetch_parent(name)
|
207
|
-
if @controller.instance_variable_defined? "@#{name}"
|
208
|
-
@controller.instance_variable_get("@#{name}")
|
209
|
-
elsif @controller.respond_to?(name, true)
|
210
|
-
@controller.send(name)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def current_ability
|
215
|
-
@controller.send(:current_ability)
|
216
|
-
end
|
106
|
+
return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
|
217
107
|
|
218
|
-
|
219
|
-
@name || name_from_controller
|
220
|
-
end
|
221
|
-
|
222
|
-
def resource_params
|
223
|
-
if parameters_require_sanitizing? && params_method.present?
|
224
|
-
return case params_method
|
225
|
-
when Symbol then @controller.send(params_method)
|
226
|
-
when String then @controller.instance_eval(params_method)
|
227
|
-
when Proc then params_method.call(@controller)
|
228
|
-
end
|
229
|
-
else
|
230
|
-
resource_params_by_namespaced_name
|
231
|
-
end
|
108
|
+
@controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
|
232
109
|
end
|
233
110
|
|
234
111
|
def parameters_require_sanitizing?
|
235
112
|
save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
|
236
113
|
end
|
237
114
|
|
238
|
-
def resource_params_by_namespaced_name
|
239
|
-
if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
|
240
|
-
@params[extract_key(@options[:instance_name])]
|
241
|
-
elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
|
242
|
-
@params[extract_key(@options[:class])]
|
243
|
-
else
|
244
|
-
@params[extract_key(namespaced_name)]
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
def params_method
|
249
|
-
params_methods.each do |method|
|
250
|
-
return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc)
|
251
|
-
end
|
252
|
-
nil
|
253
|
-
end
|
254
|
-
|
255
|
-
def params_methods
|
256
|
-
methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
|
257
|
-
methods.unshift(@options[:param_method]) if @options[:param_method].present?
|
258
|
-
methods
|
259
|
-
end
|
260
|
-
|
261
|
-
def namespace
|
262
|
-
@params[:controller].split(/::|\//)[0..-2]
|
263
|
-
end
|
264
|
-
|
265
|
-
def namespaced_name
|
266
|
-
[namespace, name.camelize].flatten.map(&:camelize).join('::').singularize.constantize
|
267
|
-
rescue NameError
|
268
|
-
name
|
269
|
-
end
|
270
|
-
|
271
|
-
def name_from_controller
|
272
|
-
@params[:controller].sub("Controller", "").underscore.split('/').last.singularize
|
273
|
-
end
|
274
|
-
|
275
115
|
def instance_name
|
276
116
|
@options[:instance_name] || name
|
277
117
|
end
|
@@ -280,18 +120,22 @@ module CanCan
|
|
280
120
|
[:index] + Array(@options[:collection])
|
281
121
|
end
|
282
122
|
|
283
|
-
def new_actions
|
284
|
-
[:new, :create] + Array(@options[:new])
|
285
|
-
end
|
286
|
-
|
287
123
|
def save_actions
|
288
|
-
[
|
124
|
+
%i[create update]
|
289
125
|
end
|
290
126
|
|
291
127
|
private
|
292
128
|
|
293
|
-
def
|
294
|
-
|
129
|
+
def action_exists_in?(options)
|
130
|
+
Array(options).include?(@params[:action].to_sym)
|
131
|
+
end
|
132
|
+
|
133
|
+
def adapter
|
134
|
+
ModelAdapters::AbstractAdapter.adapter_class(resource_class)
|
135
|
+
end
|
136
|
+
|
137
|
+
def current_ability
|
138
|
+
@controller.send(:current_ability)
|
295
139
|
end
|
296
140
|
end
|
297
141
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceBuilder
|
5
|
+
protected
|
6
|
+
|
7
|
+
def build_resource
|
8
|
+
resource = resource_base.new(resource_params || {})
|
9
|
+
assign_attributes(resource)
|
10
|
+
end
|
11
|
+
|
12
|
+
def assign_attributes(resource)
|
13
|
+
resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
|
14
|
+
initial_attributes.each do |attr_name, value|
|
15
|
+
resource.send("#{attr_name}=", value)
|
16
|
+
end
|
17
|
+
resource
|
18
|
+
end
|
19
|
+
|
20
|
+
def initial_attributes
|
21
|
+
current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, _value|
|
22
|
+
resource_params && resource_params.include?(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceFinder
|
5
|
+
protected
|
6
|
+
|
7
|
+
def find_resource
|
8
|
+
if @options[:singleton] && parent_resource.respond_to?(name)
|
9
|
+
parent_resource.send(name)
|
10
|
+
elsif @options[:find_by]
|
11
|
+
find_resource_using_find_by
|
12
|
+
else
|
13
|
+
adapter.find(resource_base, id_param)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_resource_using_find_by
|
18
|
+
find_by_dynamic_finder || find_by_find_by_finder || resource_base.send(@options[:find_by], id_param)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_by_dynamic_finder
|
22
|
+
method_name = "find_by_#{@options[:find_by]}!"
|
23
|
+
resource_base.send(method_name, id_param) if resource_base.respond_to? method_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_by_find_by_finder
|
27
|
+
resource_base.find_by(@options[:find_by].to_sym => id_param) if resource_base.respond_to? :find_by
|
28
|
+
end
|
29
|
+
|
30
|
+
def id_param
|
31
|
+
@params[id_param_key].to_s if @params[id_param_key].present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def id_param_key
|
35
|
+
if @options[:id_param]
|
36
|
+
@options[:id_param]
|
37
|
+
else
|
38
|
+
parent? ? :"#{name}_id" : :id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'controller_resource_finder.rb'
|
4
|
+
require_relative 'controller_resource_name_finder.rb'
|
5
|
+
require_relative 'controller_resource_builder.rb'
|
6
|
+
require_relative 'controller_resource_sanitizer.rb'
|
7
|
+
module CanCan
|
8
|
+
module ControllerResourceLoader
|
9
|
+
include CanCan::ControllerResourceNameFinder
|
10
|
+
include CanCan::ControllerResourceFinder
|
11
|
+
include CanCan::ControllerResourceBuilder
|
12
|
+
include CanCan::ControllerResourceSanitizer
|
13
|
+
|
14
|
+
def load_resource
|
15
|
+
return if skip?(:load)
|
16
|
+
|
17
|
+
if load_instance?
|
18
|
+
self.resource_instance ||= load_resource_instance
|
19
|
+
elsif load_collection?
|
20
|
+
self.collection_instance ||= load_collection
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def new_actions
|
27
|
+
%i[new create] + Array(@options[:new])
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource_params_by_key(key)
|
31
|
+
return unless @options[key] && @params.key?(extract_key(@options[key]))
|
32
|
+
|
33
|
+
@params[extract_key(@options[key])]
|
34
|
+
end
|
35
|
+
|
36
|
+
def resource_params_by_namespaced_name
|
37
|
+
resource_params_by_key(:instance_name) || resource_params_by_key(:class) || (
|
38
|
+
params = @params[extract_key(namespaced_name)]
|
39
|
+
params.respond_to?(:to_h) ? params : nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def resource_params
|
43
|
+
if parameters_require_sanitizing? && params_method.present?
|
44
|
+
sanitize_parameters
|
45
|
+
else
|
46
|
+
resource_params_by_namespaced_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_parent(name)
|
51
|
+
if @controller.instance_variable_defined? "@#{name}"
|
52
|
+
@controller.instance_variable_get("@#{name}")
|
53
|
+
elsif @controller.respond_to?(name, true)
|
54
|
+
@controller.send(name)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# The object to load this resource through.
|
59
|
+
def parent_resource
|
60
|
+
parent_name && fetch_parent(parent_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def parent_name
|
64
|
+
@options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def resource_base_through_parent_resource
|
68
|
+
if @options[:singleton]
|
69
|
+
resource_class
|
70
|
+
else
|
71
|
+
parent_resource.send(@options[:through_association] || name.to_s.pluralize)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def resource_base_through
|
76
|
+
if parent_resource
|
77
|
+
resource_base_through_parent_resource
|
78
|
+
elsif @options[:shallow]
|
79
|
+
resource_class
|
80
|
+
else
|
81
|
+
# maybe this should be a record not found error instead?
|
82
|
+
raise AccessDenied.new(nil, authorization_action, resource_class)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# The object that methods (such as "find", "new" or "build") are called on.
|
87
|
+
# If the :through option is passed it will go through an association on that instance.
|
88
|
+
# If the :shallow option is passed it will use the resource_class if there's no parent
|
89
|
+
# If the :singleton option is passed it won't use the association because it needs to be handled later.
|
90
|
+
def resource_base
|
91
|
+
@options[:through] ? resource_base_through : resource_class
|
92
|
+
end
|
93
|
+
|
94
|
+
def parent_authorization_action
|
95
|
+
@options[:parent_action] || :show
|
96
|
+
end
|
97
|
+
|
98
|
+
def authorization_action
|
99
|
+
parent? ? parent_authorization_action : @params[:action].to_sym
|
100
|
+
end
|
101
|
+
|
102
|
+
def load_collection
|
103
|
+
resource_base.accessible_by(current_ability, authorization_action)
|
104
|
+
end
|
105
|
+
|
106
|
+
def load_resource_instance
|
107
|
+
if !parent? && new_actions.include?(@params[:action].to_sym)
|
108
|
+
build_resource
|
109
|
+
elsif id_param || @options[:singleton]
|
110
|
+
find_resource
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def extract_key(value)
|
117
|
+
value.to_s.underscore.tr('/', '_')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceNameFinder
|
5
|
+
protected
|
6
|
+
|
7
|
+
def name_from_controller
|
8
|
+
@params[:controller].split('/').last.singularize
|
9
|
+
end
|
10
|
+
|
11
|
+
def namespaced_name
|
12
|
+
[namespace, name].join('/').singularize.camelize.safe_constantize || name
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@name || name_from_controller
|
17
|
+
end
|
18
|
+
|
19
|
+
def namespace
|
20
|
+
@params[:controller].split('/')[0..-2]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ControllerResourceSanitizer
|
5
|
+
protected
|
6
|
+
|
7
|
+
def sanitize_parameters
|
8
|
+
case params_method
|
9
|
+
when Symbol
|
10
|
+
@controller.send(params_method)
|
11
|
+
when String
|
12
|
+
@controller.instance_eval(params_method)
|
13
|
+
when Proc
|
14
|
+
params_method.call(@controller)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def params_methods
|
19
|
+
methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
|
20
|
+
methods.unshift(@options[:param_method]) if @options[:param_method].present?
|
21
|
+
methods
|
22
|
+
end
|
23
|
+
|
24
|
+
def params_method
|
25
|
+
params_methods.each do |method|
|
26
|
+
return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
|
27
|
+
method.is_a?(String) || method.is_a?(Proc)
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/cancan/exceptions.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
# A general CanCan exception
|
3
5
|
class Error < StandardError; end
|
@@ -8,9 +10,18 @@ module CanCan
|
|
8
10
|
# Raised when removed code is called, an alternative solution is provided in message.
|
9
11
|
class ImplementationRemoved < Error; end
|
10
12
|
|
11
|
-
# Raised when using check_authorization without calling
|
13
|
+
# Raised when using check_authorization without calling authorize!
|
12
14
|
class AuthorizationNotPerformed < Error; end
|
13
15
|
|
16
|
+
# Raised when a rule is created with both a block and a hash of conditions
|
17
|
+
class BlockAndConditionsError < Error; end
|
18
|
+
|
19
|
+
# Raised when an unexpected argument is passed as an attribute
|
20
|
+
class AttributeArgumentError < Error; end
|
21
|
+
|
22
|
+
# Raised when using a wrong association name
|
23
|
+
class WrongAssociationName < Error; end
|
24
|
+
|
14
25
|
# This error is raised when a user isn't allowed to access a given controller action.
|
15
26
|
# This usually happens within a call to ControllerAdditions#authorize! but can be
|
16
27
|
# raised manually.
|
@@ -30,21 +41,30 @@ module CanCan
|
|
30
41
|
# exception.default_message = "Default error message"
|
31
42
|
# exception.message # => "Default error message"
|
32
43
|
#
|
33
|
-
# See ControllerAdditions#
|
44
|
+
# See ControllerAdditions#authorize! for more information on rescuing from this exception
|
34
45
|
# and customizing the message using I18n.
|
35
46
|
class AccessDenied < Error
|
36
|
-
attr_reader :action, :subject
|
47
|
+
attr_reader :action, :subject, :conditions
|
37
48
|
attr_writer :default_message
|
38
49
|
|
39
|
-
def initialize(message = nil, action = nil, subject = nil)
|
50
|
+
def initialize(message = nil, action = nil, subject = nil, conditions = nil)
|
40
51
|
@message = message
|
41
52
|
@action = action
|
42
53
|
@subject = subject
|
43
|
-
@
|
54
|
+
@conditions = conditions
|
55
|
+
@default_message = I18n.t(:"unauthorized.default", default: 'You are not authorized to access this page.')
|
44
56
|
end
|
45
57
|
|
46
58
|
def to_s
|
47
59
|
@message || @default_message
|
48
60
|
end
|
61
|
+
|
62
|
+
def inspect
|
63
|
+
details = %i[action subject conditions message].map do |attribute|
|
64
|
+
value = instance_variable_get "@#{attribute}"
|
65
|
+
"#{attribute}: #{value.inspect}" if value.present?
|
66
|
+
end.compact.join(', ')
|
67
|
+
"#<#{self.class.name} #{details}>"
|
68
|
+
end
|
49
69
|
end
|
50
70
|
end
|
data/lib/cancan/matchers.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability
|
2
4
|
|
3
5
|
if rspec_module == 'RSpec'
|
@@ -9,13 +11,25 @@ end
|
|
9
11
|
|
10
12
|
Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
11
13
|
match do |ability|
|
12
|
-
|
14
|
+
actions = args.first
|
15
|
+
if actions.is_a? Array
|
16
|
+
if actions.empty?
|
17
|
+
false
|
18
|
+
else
|
19
|
+
actions.all? { |action| ability.can?(action, *args[1..-1]) }
|
20
|
+
end
|
21
|
+
else
|
22
|
+
ability.can?(*args)
|
23
|
+
end
|
13
24
|
end
|
14
25
|
|
15
26
|
# Check that RSpec is < 2.99
|
16
27
|
if !respond_to?(:failure_message) && respond_to?(:failure_message_for_should)
|
17
|
-
|
18
|
-
|
28
|
+
alias_method :failure_message, :failure_message_for_should
|
29
|
+
end
|
30
|
+
|
31
|
+
if !respond_to?(:failure_message_when_negated) && respond_to?(:failure_message_for_should_not)
|
32
|
+
alias_method :failure_message_when_negated, :failure_message_for_should_not
|
19
33
|
end
|
20
34
|
|
21
35
|
failure_message do
|