cancancan 1.15.0 → 3.1.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.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +18 -18
  3. data/init.rb +2 -0
  4. data/lib/cancan.rb +9 -11
  5. data/lib/cancan/ability.rb +90 -203
  6. data/lib/cancan/ability/actions.rb +93 -0
  7. data/lib/cancan/ability/rules.rb +93 -0
  8. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  9. data/lib/cancan/conditions_matcher.rb +106 -0
  10. data/lib/cancan/controller_additions.rb +29 -36
  11. data/lib/cancan/controller_resource.rb +46 -211
  12. data/lib/cancan/controller_resource_builder.rb +26 -0
  13. data/lib/cancan/controller_resource_finder.rb +42 -0
  14. data/lib/cancan/controller_resource_loader.rb +120 -0
  15. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  16. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  17. data/lib/cancan/exceptions.rb +17 -5
  18. data/lib/cancan/matchers.rb +12 -3
  19. data/lib/cancan/model_adapters/abstract_adapter.rb +10 -8
  20. data/lib/cancan/model_adapters/active_record_4_adapter.rb +39 -43
  21. data/lib/cancan/model_adapters/active_record_5_adapter.rb +68 -0
  22. data/lib/cancan/model_adapters/active_record_adapter.rb +77 -82
  23. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  24. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  25. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  26. data/lib/cancan/model_additions.rb +2 -1
  27. data/lib/cancan/parameter_validators.rb +9 -0
  28. data/lib/cancan/relevant.rb +29 -0
  29. data/lib/cancan/rule.rb +76 -106
  30. data/lib/cancan/rules_compressor.rb +23 -0
  31. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  32. data/lib/cancan/version.rb +3 -1
  33. data/lib/cancancan.rb +2 -0
  34. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  35. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  36. metadata +66 -57
  37. data/.gitignore +0 -15
  38. data/.rspec +0 -1
  39. data/.travis.yml +0 -33
  40. data/Appraisals +0 -104
  41. data/CHANGELOG.rdoc +0 -527
  42. data/CONTRIBUTING.md +0 -23
  43. data/Gemfile +0 -3
  44. data/LICENSE +0 -22
  45. data/README.md +0 -217
  46. data/Rakefile +0 -9
  47. data/gemfiles/activerecord_3.2.gemfile +0 -17
  48. data/gemfiles/activerecord_4.0.gemfile +0 -18
  49. data/gemfiles/activerecord_4.1.gemfile +0 -18
  50. data/gemfiles/activerecord_4.2.gemfile +0 -19
  51. data/gemfiles/activerecord_5.0.gemfile +0 -19
  52. data/gemfiles/mongoid_2.x.gemfile +0 -17
  53. data/gemfiles/sequel_3.x.gemfile +0 -17
  54. data/lib/cancan/inherited_resource.rb +0 -20
  55. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
  56. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -75
  57. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  58. data/spec/README.rdoc +0 -27
  59. data/spec/cancan/ability_spec.rb +0 -544
  60. data/spec/cancan/controller_additions_spec.rb +0 -151
  61. data/spec/cancan/controller_resource_spec.rb +0 -643
  62. data/spec/cancan/exceptions_spec.rb +0 -58
  63. data/spec/cancan/inherited_resource_spec.rb +0 -71
  64. data/spec/cancan/matchers_spec.rb +0 -29
  65. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -154
  66. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -405
  67. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  68. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -247
  69. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  70. data/spec/cancan/rule_spec.rb +0 -52
  71. data/spec/matchers.rb +0 -13
  72. data/spec/spec.opts +0 -2
  73. data/spec/spec_helper.rb +0 -27
  74. data/spec/support/ability.rb +0 -7
@@ -1,22 +1,25 @@
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 so we don't clutter up all controllers with non-interface methods.
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:
9
+ include ControllerResourceLoader
10
+
5
11
  def self.add_before_action(controller_class, method, *args)
6
12
  options = args.extract_options!
7
13
  resource_name = args.first
8
14
  before_action_method = before_callback_name(options)
9
15
  controller_class.send(before_action_method, options.slice(:only, :except, :if, :unless)) do |controller|
10
- controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except, :if, :unless)).send(method)
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
 
14
21
  def self.before_callback_name(options)
15
- if ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new("4")
16
- options.delete(:prepend) ? :prepend_before_action : :before_action
17
- else
18
- options.delete(:prepend) ? :prepend_before_filter : :before_filter
19
- end
22
+ options.delete(:prepend) ? :prepend_before_action : :before_action
20
23
  end
21
24
 
22
25
  def initialize(controller, *args)
@@ -24,9 +27,6 @@ module CanCan
24
27
  @params = controller.params
25
28
  @options = args.extract_options!
26
29
  @name = args.first
27
- raise CanCan::ImplementationRemoved, "The :nested option is no longer supported, instead use :through with separate load/authorize call." if @options[:nested]
28
- raise CanCan::ImplementationRemoved, "The :name option is no longer supported, instead pass the name as the first argument." if @options[:name]
29
- raise CanCan::ImplementationRemoved, "The :resource option has been renamed back to :class, use false if no class." if @options[:resource]
30
30
  end
31
31
 
32
32
  def load_and_authorize_resource
@@ -34,41 +34,39 @@ module CanCan
34
34
  authorize_resource
35
35
  end
36
36
 
37
- def load_resource
38
- unless skip?(:load)
39
- if load_instance?
40
- self.resource_instance ||= load_resource_instance
41
- elsif load_collection?
42
- self.collection_instance ||= load_collection
43
- end
44
- end
45
- end
46
-
47
37
  def authorize_resource
48
- unless skip?(:authorize)
49
- @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
50
- end
38
+ return if skip?(:authorize)
39
+
40
+ @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
51
41
  end
52
42
 
53
43
  def parent?
54
- @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
44
+ @options.key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
55
45
  end
56
46
 
57
47
  def skip?(behavior)
58
- return false unless options = @controller.class.cancan_skipper[behavior][@name]
48
+ return false unless (options = @controller.class.cancan_skipper[behavior][@name])
59
49
 
60
50
  options == {} ||
61
- options[:except] && !action_exists_in?(options[:except]) ||
62
- action_exists_in?(options[:only])
51
+ options[:except] && !action_exists_in?(options[:except]) ||
52
+ action_exists_in?(options[:only])
63
53
  end
64
54
 
65
55
  protected
66
56
 
67
- def load_resource_instance
68
- if !parent? && new_actions.include?(@params[:action].to_sym)
69
- build_resource
70
- elsif id_param || @options[:singleton]
71
- find_resource
57
+ # Returns the class used for this resource. This can be overriden 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]
72
70
  end
73
71
  end
74
72
 
@@ -80,89 +78,14 @@ module CanCan
80
78
  resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
81
79
  end
82
80
 
83
- def load_collection
84
- resource_base.accessible_by(current_ability, authorization_action)
85
- end
86
-
87
- def build_resource
88
- resource = resource_base.new(resource_params || {})
89
- assign_attributes(resource)
90
- end
91
-
92
- def assign_attributes(resource)
93
- resource.send("#{parent_name}=", parent_resource) if @options[:singleton] && parent_resource
94
- initial_attributes.each do |attr_name, value|
95
- resource.send("#{attr_name}=", value)
96
- end
97
- resource
98
- end
99
-
100
- def initial_attributes
101
- current_ability.attributes_for(@params[:action].to_sym, resource_class).delete_if do |key, value|
102
- resource_params && resource_params.include?(key)
103
- end
104
- end
105
-
106
- def find_resource
107
- if @options[:singleton] && parent_resource.respond_to?(name)
108
- parent_resource.send(name)
109
- else
110
- if @options[:find_by]
111
- if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
112
- resource_base.send("find_by_#{@options[:find_by]}!", id_param)
113
- elsif resource_base.respond_to? "find_by"
114
- resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
115
- else
116
- resource_base.send(@options[:find_by], id_param)
117
- end
118
- else
119
- adapter.find(resource_base, id_param)
120
- end
121
- end
122
- end
123
-
124
- def adapter
125
- ModelAdapters::AbstractAdapter.adapter_class(resource_class)
126
- end
127
-
128
- def authorization_action
129
- parent? ? parent_authorization_action : @params[:action].to_sym
130
- end
131
-
132
- def parent_authorization_action
133
- @options[:parent_action] || :show
134
- end
135
-
136
- def id_param
137
- @params[id_param_key].to_s if @params[id_param_key]
138
- end
139
-
140
- def id_param_key
141
- if @options[:id_param]
142
- @options[:id_param]
143
- else
144
- parent? ? :"#{name}_id" : :id
145
- end
146
- end
147
-
148
81
  def member_action?
149
- new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
150
- end
151
-
152
- # Returns the class used for this resource. This can be overriden by the :class option.
153
- # If +false+ is passed in it will use the resource name as a symbol in which case it should
154
- # only be used for authorization, not loading since there's no class to load through.
155
- def resource_class
156
- case @options[:class]
157
- when false then name.to_sym
158
- when nil then namespaced_name.to_s.camelize.constantize
159
- when String then @options[:class].constantize
160
- else @options[:class]
161
- 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))
162
85
  end
163
86
 
164
87
  def resource_class_with_parent
165
- parent_resource ? {parent_resource => resource_class} : resource_class
88
+ parent_resource ? { parent_resource => resource_class } : resource_class
166
89
  end
167
90
 
168
91
  def resource_instance=(instance)
@@ -170,7 +93,9 @@ module CanCan
170
93
  end
171
94
 
172
95
  def resource_instance
173
- @controller.instance_variable_get("@#{instance_name}") if load_instance?
96
+ return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
97
+
98
+ @controller.instance_variable_get("@#{instance_name}")
174
99
  end
175
100
 
176
101
  def collection_instance=(instance)
@@ -178,105 +103,15 @@ module CanCan
178
103
  end
179
104
 
180
105
  def collection_instance
181
- @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
182
- end
183
-
184
- # The object that methods (such as "find", "new" or "build") are called on.
185
- # If the :through option is passed it will go through an association on that instance.
186
- # If the :shallow option is passed it will use the resource_class if there's no parent
187
- # If the :singleton option is passed it won't use the association because it needs to be handled later.
188
- def resource_base
189
- if @options[:through]
190
- if parent_resource
191
- base = @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
192
- base = base.scoped if base.respond_to?(:scoped) && defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
193
- base
194
- elsif @options[:shallow]
195
- resource_class
196
- else
197
- raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
198
- end
199
- else
200
- resource_class
201
- end
202
- end
203
-
204
- def parent_name
205
- @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
206
- end
207
-
208
- # The object to load this resource through.
209
- def parent_resource
210
- parent_name && fetch_parent(parent_name)
211
- end
212
-
213
- def fetch_parent(name)
214
- if @controller.instance_variable_defined? "@#{name}"
215
- @controller.instance_variable_get("@#{name}")
216
- elsif @controller.respond_to?(name, true)
217
- @controller.send(name)
218
- end
219
- end
220
-
221
- def current_ability
222
- @controller.send(:current_ability)
223
- end
224
-
225
- def name
226
- @name || name_from_controller
227
- end
106
+ return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
228
107
 
229
- def resource_params
230
- if parameters_require_sanitizing? && params_method.present?
231
- return case params_method
232
- when Symbol then @controller.send(params_method)
233
- when String then @controller.instance_eval(params_method)
234
- when Proc then params_method.call(@controller)
235
- end
236
- else
237
- resource_params_by_namespaced_name
238
- end
108
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
239
109
  end
240
110
 
241
111
  def parameters_require_sanitizing?
242
112
  save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
243
113
  end
244
114
 
245
- def resource_params_by_namespaced_name
246
- if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
247
- @params[extract_key(@options[:instance_name])]
248
- elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
249
- @params[extract_key(@options[:class])]
250
- else
251
- @params[extract_key(namespaced_name)]
252
- end
253
- end
254
-
255
- def params_method
256
- params_methods.each do |method|
257
- return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc)
258
- end
259
- nil
260
- end
261
-
262
- def params_methods
263
- methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
264
- methods.unshift(@options[:param_method]) if @options[:param_method].present?
265
- methods
266
- end
267
-
268
- def namespace
269
- @params[:controller].split('/')[0..-2]
270
- end
271
-
272
- def namespaced_name
273
- ([namespace, name] * '/').singularize.camelize.safe_constantize || name
274
- end
275
-
276
- def name_from_controller
277
- @params[:controller].split('/').last.singularize
278
- end
279
-
280
115
  def instance_name
281
116
  @options[:instance_name] || name
282
117
  end
@@ -285,12 +120,8 @@ module CanCan
285
120
  [:index] + Array(@options[:collection])
286
121
  end
287
122
 
288
- def new_actions
289
- [:new, :create] + Array(@options[:new])
290
- end
291
-
292
123
  def save_actions
293
- [:create, :update]
124
+ %i[create update]
294
125
  end
295
126
 
296
127
  private
@@ -299,8 +130,12 @@ module CanCan
299
130
  Array(options).include?(@params[:action].to_sym)
300
131
  end
301
132
 
302
- def extract_key(value)
303
- value.to_s.underscore.gsub('/', '_')
133
+ def adapter
134
+ ModelAdapters::AbstractAdapter.adapter_class(resource_class)
135
+ end
136
+
137
+ def current_ability
138
+ @controller.send(:current_ability)
304
139
  end
305
140
  end
306
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