cancancan 1.13.1 → 3.1.0

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