cancancan 1.10.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +19 -21
  3. data/init.rb +2 -0
  4. data/lib/cancan/ability/actions.rb +93 -0
  5. data/lib/cancan/ability/rules.rb +96 -0
  6. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/lib/cancan/ability.rb +114 -146
  8. data/lib/cancan/class_matcher.rb +30 -0
  9. data/lib/cancan/conditions_matcher.rb +147 -0
  10. data/lib/cancan/config.rb +101 -0
  11. data/lib/cancan/controller_additions.rb +38 -41
  12. data/lib/cancan/controller_resource.rb +59 -215
  13. data/lib/cancan/controller_resource_builder.rb +26 -0
  14. data/lib/cancan/controller_resource_finder.rb +42 -0
  15. data/lib/cancan/controller_resource_loader.rb +120 -0
  16. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  17. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  18. data/lib/cancan/exceptions.rb +25 -5
  19. data/lib/cancan/matchers.rb +17 -3
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +30 -9
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +43 -15
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +157 -82
  24. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  25. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  26. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  27. data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  28. data/lib/cancan/model_adapters/strategies/base.rb +40 -0
  29. data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  30. data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  31. data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  32. data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  33. data/lib/cancan/model_additions.rb +6 -3
  34. data/lib/cancan/parameter_validators.rb +9 -0
  35. data/lib/cancan/relevant.rb +29 -0
  36. data/lib/cancan/rule.rb +79 -91
  37. data/lib/cancan/rules_compressor.rb +23 -0
  38. data/lib/cancan/sti_detector.rb +12 -0
  39. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  40. data/lib/cancan/version.rb +3 -1
  41. data/lib/cancan.rb +16 -12
  42. data/lib/cancancan.rb +2 -0
  43. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  44. data/lib/generators/cancan/ability/templates/ability.rb +9 -9
  45. metadata +82 -93
  46. data/.gitignore +0 -15
  47. data/.rspec +0 -1
  48. data/.travis.yml +0 -48
  49. data/Appraisals +0 -135
  50. data/CHANGELOG.rdoc +0 -495
  51. data/CONTRIBUTING.md +0 -23
  52. data/Gemfile +0 -3
  53. data/LICENSE +0 -22
  54. data/README.md +0 -197
  55. data/Rakefile +0 -9
  56. data/gemfiles/activerecord_3.0.gemfile +0 -18
  57. data/gemfiles/activerecord_3.1.gemfile +0 -20
  58. data/gemfiles/activerecord_3.2.gemfile +0 -20
  59. data/gemfiles/activerecord_4.0.gemfile +0 -17
  60. data/gemfiles/activerecord_4.1.gemfile +0 -17
  61. data/gemfiles/activerecord_4.2.gemfile +0 -17
  62. data/gemfiles/datamapper_1.x.gemfile +0 -14
  63. data/gemfiles/mongoid_2.x.gemfile +0 -20
  64. data/gemfiles/sequel_3.x.gemfile +0 -20
  65. data/lib/cancan/inherited_resource.rb +0 -20
  66. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -47
  67. data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
  68. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  69. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  70. data/spec/README.rdoc +0 -27
  71. data/spec/cancan/ability_spec.rb +0 -487
  72. data/spec/cancan/controller_additions_spec.rb +0 -141
  73. data/spec/cancan/controller_resource_spec.rb +0 -648
  74. data/spec/cancan/exceptions_spec.rb +0 -58
  75. data/spec/cancan/inherited_resource_spec.rb +0 -71
  76. data/spec/cancan/matchers_spec.rb +0 -29
  77. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -40
  78. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -446
  79. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
  80. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  81. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  82. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  83. data/spec/cancan/rule_spec.rb +0 -52
  84. data/spec/matchers.rb +0 -13
  85. data/spec/spec.opts +0 -2
  86. data/spec/spec_helper.rb +0 -27
  87. 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,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
- 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
- def skip?(behavior) # This could probably use some refactoring
50
- options = @controller.class.cancan_skipper[behavior][@name]
51
- if options.nil?
52
- false
53
- elsif options == {}
54
- true
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
- def load_resource_instance
65
- if !parent? && new_actions.include?(@params[:action].to_sym)
66
- build_resource
67
- elsif id_param || @options[:singleton]
68
- find_resource
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] || ( (@params[:id] || @params[@options[:id_param]]) && !collection_actions.include?(@params[:action].to_sym))
143
- end
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.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}")
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.instance_variable_get("@#{instance_name.to_s.pluralize}")
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
- def name
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
- [:create, :update]
124
+ %i[create update]
289
125
  end
290
126
 
291
127
  private
292
128
 
293
- def extract_key(value)
294
- value.to_s.underscore.gsub('/', '_')
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
@@ -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 authorized!
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#authorized! for more information on rescuing from this exception
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
- @default_message = I18n.t(:"unauthorized.default", :default => "You are not authorized to access this page.")
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
@@ -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
- ability.can?(*args)
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
- alias :failure_message :failure_message_for_should
18
- alias :failure_message_when_negated :failure_message_for_should_not
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