cancancan 1.13.1 → 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 (73) 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 +93 -194
  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 +38 -41
  11. data/lib/cancan/controller_resource.rb +52 -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 -13
  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 -105
  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 -56
  37. data/.gitignore +0 -15
  38. data/.rspec +0 -1
  39. data/.travis.yml +0 -28
  40. data/Appraisals +0 -81
  41. data/CHANGELOG.rdoc +0 -518
  42. data/CONTRIBUTING.md +0 -23
  43. data/Gemfile +0 -3
  44. data/LICENSE +0 -22
  45. data/README.md +0 -214
  46. data/Rakefile +0 -9
  47. data/gemfiles/activerecord_3.2.gemfile +0 -16
  48. data/gemfiles/activerecord_4.0.gemfile +0 -17
  49. data/gemfiles/activerecord_4.1.gemfile +0 -17
  50. data/gemfiles/activerecord_4.2.gemfile +0 -18
  51. data/gemfiles/mongoid_2.x.gemfile +0 -16
  52. data/gemfiles/sequel_3.x.gemfile +0 -16
  53. data/lib/cancan/inherited_resource.rb +0 -20
  54. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
  55. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  56. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  57. data/spec/README.rdoc +0 -27
  58. data/spec/cancan/ability_spec.rb +0 -521
  59. data/spec/cancan/controller_additions_spec.rb +0 -141
  60. data/spec/cancan/controller_resource_spec.rb +0 -632
  61. data/spec/cancan/exceptions_spec.rb +0 -58
  62. data/spec/cancan/inherited_resource_spec.rb +0 -71
  63. data/spec/cancan/matchers_spec.rb +0 -29
  64. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -85
  65. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -384
  66. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  67. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  68. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  69. data/spec/cancan/rule_spec.rb +0 -52
  70. data/spec/matchers.rb +0 -13
  71. data/spec/spec.opts +0 -2
  72. data/spec/spec_helper.rb +0 -27
  73. 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 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:
5
- def self.add_before_filter(controller_class, method, *args)
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
- before_filter_method = options.delete(:prepend) ? :prepend_before_filter : :before_filter
9
- controller_class.send(before_filter_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)
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,41 +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
- unless skip?(:authorize)
41
- @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
42
- end
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.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
47
45
  end
48
46
 
49
47
  def skip?(behavior)
50
- return false unless options = @controller.class.cancan_skipper[behavior][@name]
48
+ return false unless (options = @controller.class.cancan_skipper[behavior][@name])
51
49
 
52
50
  options == {} ||
53
- options[:except] && !action_exists_in?(options[:except]) ||
54
- action_exists_in?(options[:only])
51
+ options[:except] && !action_exists_in?(options[:except]) ||
52
+ action_exists_in?(options[:only])
55
53
  end
56
54
 
57
55
  protected
58
56
 
59
- def load_resource_instance
60
- if !parent? && new_actions.include?(@params[:action].to_sym)
61
- build_resource
62
- elsif id_param || @options[:singleton]
63
- 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]
64
70
  end
65
71
  end
66
72
 
@@ -72,89 +78,14 @@ module CanCan
72
78
  resource_base.respond_to?(:accessible_by) && !current_ability.has_block?(authorization_action, resource_class)
73
79
  end
74
80
 
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
- else
102
- if @options[:find_by]
103
- if resource_base.respond_to? "find_by_#{@options[:find_by]}!"
104
- resource_base.send("find_by_#{@options[:find_by]}!", id_param)
105
- elsif resource_base.respond_to? "find_by"
106
- resource_base.send("find_by", { @options[:find_by].to_sym => id_param })
107
- else
108
- resource_base.send(@options[:find_by], id_param)
109
- end
110
- else
111
- adapter.find(resource_base, id_param)
112
- end
113
- end
114
- end
115
-
116
- def adapter
117
- ModelAdapters::AbstractAdapter.adapter_class(resource_class)
118
- end
119
-
120
- def authorization_action
121
- parent? ? parent_authorization_action : @params[:action].to_sym
122
- end
123
-
124
- def parent_authorization_action
125
- @options[:parent_action] || :show
126
- end
127
-
128
- def id_param
129
- @params[id_param_key].to_s if @params[id_param_key]
130
- end
131
-
132
- def id_param_key
133
- if @options[:id_param]
134
- @options[:id_param]
135
- else
136
- parent? ? :"#{name}_id" : :id
137
- end
138
- end
139
-
140
81
  def member_action?
141
- new_actions.include?(@params[:action].to_sym) || @options[:singleton] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
142
- end
143
-
144
- # Returns the class used for this resource. This can be overriden by the :class option.
145
- # If +false+ is passed in it will use the resource name as a symbol in which case it should
146
- # only be used for authorization, not loading since there's no class to load through.
147
- def resource_class
148
- case @options[:class]
149
- when false then name.to_sym
150
- when nil then namespaced_name.to_s.camelize.constantize
151
- when String then @options[:class].constantize
152
- else @options[:class]
153
- 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))
154
85
  end
155
86
 
156
87
  def resource_class_with_parent
157
- parent_resource ? {parent_resource => resource_class} : resource_class
88
+ parent_resource ? { parent_resource => resource_class } : resource_class
158
89
  end
159
90
 
160
91
  def resource_instance=(instance)
@@ -162,7 +93,9 @@ module CanCan
162
93
  end
163
94
 
164
95
  def resource_instance
165
- @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}")
166
99
  end
167
100
 
168
101
  def collection_instance=(instance)
@@ -170,107 +103,15 @@ module CanCan
170
103
  end
171
104
 
172
105
  def collection_instance
173
- @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
174
- end
175
-
176
- # The object that methods (such as "find", "new" or "build") are called on.
177
- # If the :through option is passed it will go through an association on that instance.
178
- # If the :shallow option is passed it will use the resource_class if there's no parent
179
- # If the :singleton option is passed it won't use the association because it needs to be handled later.
180
- def resource_base
181
- if @options[:through]
182
- if parent_resource
183
- base = @options[:singleton] ? resource_class : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
184
- base = base.scoped if base.respond_to?(:scoped) && defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3
185
- base
186
- elsif @options[:shallow]
187
- resource_class
188
- else
189
- raise AccessDenied.new(nil, authorization_action, resource_class) # maybe this should be a record not found error instead?
190
- end
191
- else
192
- resource_class
193
- end
194
- end
195
-
196
- def parent_name
197
- @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) }
198
- end
199
-
200
- # The object to load this resource through.
201
- def parent_resource
202
- parent_name && fetch_parent(parent_name)
203
- end
204
-
205
- def fetch_parent(name)
206
- if @controller.instance_variable_defined? "@#{name}"
207
- @controller.instance_variable_get("@#{name}")
208
- elsif @controller.respond_to?(name, true)
209
- @controller.send(name)
210
- end
211
- end
212
-
213
- def current_ability
214
- @controller.send(:current_ability)
215
- end
216
-
217
- def name
218
- @name || name_from_controller
219
- end
106
+ return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
220
107
 
221
- def resource_params
222
- if parameters_require_sanitizing? && params_method.present?
223
- return case params_method
224
- when Symbol then @controller.send(params_method)
225
- when String then @controller.instance_eval(params_method)
226
- when Proc then params_method.call(@controller)
227
- end
228
- else
229
- resource_params_by_namespaced_name
230
- end
108
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
231
109
  end
232
110
 
233
111
  def parameters_require_sanitizing?
234
112
  save_actions.include?(@params[:action].to_sym) || resource_params_by_namespaced_name.present?
235
113
  end
236
114
 
237
- def resource_params_by_namespaced_name
238
- if @options[:instance_name] && @params.has_key?(extract_key(@options[:instance_name]))
239
- @params[extract_key(@options[:instance_name])]
240
- elsif @options[:class] && @params.has_key?(extract_key(@options[:class]))
241
- @params[extract_key(@options[:class])]
242
- else
243
- @params[extract_key(namespaced_name)]
244
- end
245
- end
246
-
247
- def params_method
248
- params_methods.each do |method|
249
- return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) || method.is_a?(String) || method.is_a?(Proc)
250
- end
251
- nil
252
- end
253
-
254
- def params_methods
255
- methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
256
- methods.unshift(@options[:param_method]) if @options[:param_method].present?
257
- methods
258
- end
259
-
260
- def namespace
261
- @params[:controller].split('/')[0..-2]
262
- end
263
-
264
- def namespaced_name
265
- [namespace, name.camelize].flatten.map(&:camelize).join('::').singularize.constantize
266
- rescue NameError
267
- name
268
- end
269
-
270
- def name_from_controller
271
- @params[:controller].split('/').last.singularize
272
- end
273
-
274
115
  def instance_name
275
116
  @options[:instance_name] || name
276
117
  end
@@ -279,12 +120,8 @@ module CanCan
279
120
  [:index] + Array(@options[:collection])
280
121
  end
281
122
 
282
- def new_actions
283
- [:new, :create] + Array(@options[:new])
284
- end
285
-
286
123
  def save_actions
287
- [:create, :update]
124
+ %i[create update]
288
125
  end
289
126
 
290
127
  private
@@ -293,8 +130,12 @@ module CanCan
293
130
  Array(options).include?(@params[:action].to_sym)
294
131
  end
295
132
 
296
- def extract_key(value)
297
- 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)
298
139
  end
299
140
  end
300
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