cancancan 1.15.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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