cancancan 2.3.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/cancancan.gemspec +6 -5
  3. data/init.rb +2 -0
  4. data/lib/cancan/ability/actions.rb +2 -0
  5. data/lib/cancan/ability/rules.rb +19 -8
  6. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/lib/cancan/ability.rb +54 -24
  8. data/lib/cancan/class_matcher.rb +26 -0
  9. data/lib/cancan/conditions_matcher.rb +25 -12
  10. data/lib/cancan/config.rb +74 -0
  11. data/lib/cancan/controller_additions.rb +4 -1
  12. data/lib/cancan/controller_resource.rb +6 -0
  13. data/lib/cancan/controller_resource_builder.rb +2 -0
  14. data/lib/cancan/controller_resource_finder.rb +2 -0
  15. data/lib/cancan/controller_resource_loader.rb +4 -0
  16. data/lib/cancan/controller_resource_name_finder.rb +2 -0
  17. data/lib/cancan/controller_resource_sanitizer.rb +2 -0
  18. data/lib/cancan/exceptions.rb +18 -2
  19. data/lib/cancan/matchers.rb +3 -0
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +3 -1
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +26 -25
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +21 -26
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +56 -14
  24. data/lib/cancan/model_adapters/conditions_extractor.rb +3 -3
  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 +39 -0
  28. data/lib/cancan/model_additions.rb +6 -2
  29. data/lib/cancan/parameter_validators.rb +9 -0
  30. data/lib/cancan/relevant.rb +29 -0
  31. data/lib/cancan/rule.rb +67 -23
  32. data/lib/cancan/rules_compressor.rb +3 -0
  33. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  34. data/lib/cancan/version.rb +3 -1
  35. data/lib/cancan.rb +6 -0
  36. data/lib/cancancan.rb +2 -0
  37. data/lib/generators/cancan/ability/ability_generator.rb +3 -1
  38. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  39. metadata +37 -30
  40. data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f63f1d6ab266068caab6e7baf2b628ae72a7e03a134116eda91473861b9df359
4
- data.tar.gz: fbf6337f058ece9a2f01c990a55398489e0ea56888f0a5ce3b9f8d5e203c31ae
3
+ metadata.gz: c4498ac94e1994faa4da80dc957d8c9564433d991048774f9ac2f051e60de580
4
+ data.tar.gz: 74209123c4c49adcd1d2d81df1de61c5f8cc2f243fdcdb4d01d3e41731e4c266
5
5
  SHA512:
6
- metadata.gz: 24fcc98ce0592b263add65cf0bc75a7020d75777fea7c6902216f97bbc9c13a36b4d379194a142cd8a5e5a24dc1ae82c82a61d6d99143c30a9afe11133048528
7
- data.tar.gz: 8873b440698a941314f67a748db86c2abe33e89417ae54d9f35769c29f904edc9eff5736d0bf55d53badc494b9bca9e0ac1761c1920067238628d23dc8e0c643
6
+ metadata.gz: eb7774650d12a7073d09bb713f7eedfd7d376689f6d6bb842620b325a814720172f4fec900705bcc0dd3bd90818a88d2ab7e3904ea3a5d004533e13b4bed1c4c
7
+ data.tar.gz: a7a6fff07fbd7d52816dd960d00ae0baea7d50c5ed41d0abf32ac3a0c2b6bc3c557a4309944717b57aad785ad7dc394870c079b6a820b62798a373a9d9f8c2b0
data/cancancan.gemspec CHANGED
@@ -1,6 +1,6 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'cancan/version'
6
6
 
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.authors = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
11
11
  s.email = 'alessandro.rodi@renuo.ch'
12
12
  s.homepage = 'https://github.com/CanCanCommunity/cancancan'
13
+ s.metadata = { 'funding_uri' => 'https://github.com/sponsors/coorasse' }
13
14
  s.summary = 'Simple authorization solution for Rails.'
14
15
  s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.'
15
16
  s.platform = Gem::Platform::RUBY
@@ -20,9 +21,9 @@ Gem::Specification.new do |s|
20
21
 
21
22
  s.required_ruby_version = '>= 2.2.0'
22
23
 
23
- s.add_development_dependency 'bundler', '~> 1.3'
24
- s.add_development_dependency 'rubocop', '~> 0.48.1'
24
+ s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
25
+ s.add_development_dependency 'bundler', '~> 2.0'
25
26
  s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
26
27
  s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
27
- s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
28
+ s.add_development_dependency 'rubocop', '~> 0.63.1'
28
29
  end
data/init.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cancan'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module Ability
3
5
  module Actions
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module Ability
3
5
  module Rules
@@ -17,12 +19,13 @@ module CanCan
17
19
  end
18
20
 
19
21
  def add_rule_to_index(rule, position)
20
- @rules_index ||= Hash.new { |h, k| h[k] = [] }
22
+ @rules_index ||= {}
21
23
 
22
24
  subjects = rule.subjects.compact
23
25
  subjects << :all if subjects.empty?
24
26
 
25
27
  subjects.each do |subject|
28
+ @rules_index[subject] ||= []
26
29
  @rules_index[subject] << position
27
30
  end
28
31
  end
@@ -31,6 +34,7 @@ module CanCan
31
34
  # This does not take into consideration any hash conditions or block statements
32
35
  def relevant_rules(action, subject)
33
36
  return [] unless @rules
37
+
34
38
  relevant = possible_relevant_rules(subject).select do |rule|
35
39
  rule.expanded_actions = expand_actions(rule.actions)
36
40
  rule.relevant? action, subject
@@ -45,7 +49,9 @@ module CanCan
45
49
  rules
46
50
  else
47
51
  positions = @rules_index.values_at(subject, *alternative_subjects(subject))
48
- positions.flatten!.sort!
52
+ positions.compact!
53
+ positions.flatten!
54
+ positions.sort!
49
55
  positions.map { |i| @rules[i] }
50
56
  end
51
57
  end
@@ -53,19 +59,23 @@ module CanCan
53
59
  def relevant_rules_for_match(action, subject)
54
60
  relevant_rules(action, subject).each do |rule|
55
61
  next unless rule.only_raw_sql?
62
+
56
63
  raise Error,
57
64
  "The can? and cannot? call cannot be used with a raw sql 'can' definition."\
58
- " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
65
+ " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
59
66
  end
60
67
  end
61
68
 
62
69
  def relevant_rules_for_query(action, subject)
63
- relevant_rules(action, subject).each do |rule|
64
- if rule.only_block?
65
- raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
66
- " The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
67
- end
70
+ rules = relevant_rules(action, subject).reject do |rule|
71
+ # reject 'cannot' rules with attributes when doing queries
72
+ rule.base_behavior == false && rule.attributes.present?
73
+ end
74
+ if rules.any?(&:only_block?)
75
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
76
+ "The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
68
77
  end
78
+ rules
69
79
  end
70
80
 
71
81
  # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
@@ -75,6 +85,7 @@ module CanCan
75
85
  (first_can_in_group = -1) && next unless rule.base_behavior
76
86
  (first_can_in_group = i) && next if first_can_in_group == -1
77
87
  next unless rule.subjects == [:all]
88
+
78
89
  rules[i] = rules[first_can_in_group]
79
90
  rules[first_can_in_group] = rule
80
91
  first_can_in_group += 1
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module Ability
5
+ module StrongParameterSupport
6
+ # Returns an array of attributes suitable for use with strong parameters
7
+ #
8
+ # Note: reversing the relevant rules is important. Normal order means that 'cannot'
9
+ # rules will come before 'can' rules. However, you can't remove attributes before
10
+ # they are added. The 'reverse' is so that attributes will be added before the
11
+ # 'cannot' rules remove them.
12
+ def permitted_attributes(action, subject)
13
+ relevant_rules(action, subject)
14
+ .reverse
15
+ .select { |rule| rule.matches_conditions? action, subject }
16
+ .each_with_object(Set.new) do |rule, set|
17
+ attributes = get_attributes(rule, subject)
18
+ # add attributes for 'can', remove them for 'cannot'
19
+ rule.base_behavior ? set.merge(attributes) : set.subtract(attributes)
20
+ end.to_a
21
+ end
22
+
23
+ private
24
+
25
+ def subject_class?(subject)
26
+ klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
27
+ [Class, Module].include? klass
28
+ end
29
+
30
+ def get_attributes(rule, subject)
31
+ klass = subject_class?(subject) ? subject : subject.class
32
+ # empty attributes is an 'all'
33
+ if rule.attributes.empty? && klass < ActiveRecord::Base
34
+ klass.column_names.map(&:to_sym) - Array(klass.primary_key)
35
+ else
36
+ rule.attributes
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'ability/rules.rb'
2
4
  require_relative 'ability/actions.rb'
5
+ require_relative 'unauthorized_message_resolver.rb'
6
+ require_relative 'ability/strong_parameter_support'
7
+
3
8
  module CanCan
4
9
  # This module is designed to be included into an Ability class. This will
5
10
  # provide the "can" methods for defining and checking abilities.
@@ -19,6 +24,8 @@ module CanCan
19
24
  module Ability
20
25
  include CanCan::Ability::Rules
21
26
  include CanCan::Ability::Actions
27
+ include CanCan::UnauthorizedMessageResolver
28
+ include StrongParameterSupport
22
29
 
23
30
  # Check if the user has permission to perform a given action on an object.
24
31
  #
@@ -64,10 +71,10 @@ module CanCan
64
71
  # end
65
72
  #
66
73
  # Also see the RSpec Matchers to aid in testing.
67
- def can?(action, subject, *extra_args)
74
+ def can?(action, subject, attribute = nil, *extra_args)
68
75
  match = extract_subjects(subject).lazy.map do |a_subject|
69
76
  relevant_rules_for_match(action, a_subject).detect do |rule|
70
- rule.matches_conditions?(action, a_subject, extra_args)
77
+ rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute)
71
78
  end
72
79
  end.reject(&:nil?).first
73
80
  match ? match.base_behavior : false
@@ -134,8 +141,8 @@ module CanCan
134
141
  # # check the database and return true/false
135
142
  # end
136
143
  #
137
- def can(action = nil, subject = nil, conditions = nil, &block)
138
- add_rule(Rule.new(true, action, subject, conditions, block))
144
+ def can(action = nil, subject = nil, *attributes_and_conditions, &block)
145
+ add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
139
146
  end
140
147
 
141
148
  # Defines an ability which cannot be done. Accepts the same arguments as "can".
@@ -150,8 +157,8 @@ module CanCan
150
157
  # product.invisible?
151
158
  # end
152
159
  #
153
- def cannot(action = nil, subject = nil, conditions = nil, &block)
154
- add_rule(Rule.new(false, action, subject, conditions, block))
160
+ def cannot(action = nil, subject = nil, *attributes_and_conditions, &block)
161
+ add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block))
155
162
  end
156
163
 
157
164
  # User shouldn't specify targets with names of real actions or it will cause Seg fault
@@ -167,10 +174,7 @@ module CanCan
167
174
 
168
175
  # See ControllerAdditions#authorize! for documentation.
169
176
  def authorize!(action, subject, *args)
170
- message = nil
171
- if args.last.is_a?(Hash) && args.last.key?(:message)
172
- message = args.pop[:message]
173
- end
177
+ message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
174
178
  if cannot?(action, subject, *args)
175
179
  message ||= unauthorized_message(action, subject)
176
180
  raise AccessDenied.new(message, action, subject, args)
@@ -178,14 +182,6 @@ module CanCan
178
182
  subject
179
183
  end
180
184
 
181
- def unauthorized_message(action, subject)
182
- keys = unauthorized_message_keys(action, subject)
183
- variables = { action: action.to_s }
184
- variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
185
- message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
186
- message.blank? ? nil : message
187
- end
188
-
189
185
  def attributes_for(action, subject)
190
186
  attributes = {}
191
187
  relevant_rules(action, subject).map do |rule|
@@ -202,12 +198,13 @@ module CanCan
202
198
  relevant_rules(action, subject).any?(&:only_raw_sql?)
203
199
  end
204
200
 
205
- # Copies all rules of the given +CanCan::Ability+ and adds them to +self+.
201
+ # Copies all rules and aliased actions of the given +CanCan::Ability+ and adds them to +self+.
206
202
  # class ReadAbility
207
203
  # include CanCan::Ability
208
204
  #
209
205
  # def initialize
210
206
  # can :read, User
207
+ # alias_action :show, :index, to: :see
211
208
  # end
212
209
  # end
213
210
  #
@@ -216,6 +213,7 @@ module CanCan
216
213
  #
217
214
  # def initialize
218
215
  # can :edit, User
216
+ # alias_action :create, :update, to: :modify
219
217
  # end
220
218
  # end
221
219
  #
@@ -223,11 +221,35 @@ module CanCan
223
221
  # read_ability.can? :edit, User.new #=> false
224
222
  # read_ability.merge(WritingAbility.new)
225
223
  # read_ability.can? :edit, User.new #=> true
224
+ # read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]]
225
+ #
226
+ # If there are collisions when merging the +aliased_actions+, the actions on +self+ will be
227
+ # overwritten.
226
228
  #
229
+ # class ReadAbility
230
+ # include CanCan::Ability
231
+ #
232
+ # def initialize
233
+ # alias_action :show, :index, to: :see
234
+ # end
235
+ # end
236
+ #
237
+ # class ShowAbility
238
+ # include CanCan::Ability
239
+ #
240
+ # def initialize
241
+ # alias_action :show, to: :see
242
+ # end
243
+ # end
244
+ #
245
+ # read_ability = ReadAbility.new
246
+ # read_ability.merge(ShowAbility)
247
+ # read_ability.aliased_actions #=> [:see => [:show]]
227
248
  def merge(ability)
228
249
  ability.rules.each do |rule|
229
250
  add_rule(rule.dup)
230
251
  end
252
+ @aliased_actions = aliased_actions.merge(ability.aliased_actions)
231
253
  self
232
254
  end
233
255
 
@@ -239,10 +261,13 @@ module CanCan
239
261
  #
240
262
  # Where can_hash and cannot_hash are formatted thusly:
241
263
  # {
242
- # action: array_of_objects
264
+ # action: { subject: [attributes] }
243
265
  # }
244
266
  def permissions
245
- permissions_list = { can: {}, cannot: {} }
267
+ permissions_list = {
268
+ can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } },
269
+ cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }
270
+ }
246
271
  rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
247
272
  permissions_list
248
273
  end
@@ -250,8 +275,9 @@ module CanCan
250
275
  def extract_rule_in_permissions(permissions_list, rule)
251
276
  expand_actions(rule.actions).each do |action|
252
277
  container = rule.base_behavior ? :can : :cannot
253
- permissions_list[container][action] ||= []
254
- permissions_list[container][action] += rule.subjects.map(&:to_s)
278
+ rule.subjects.each do |subject|
279
+ permissions_list[container][action][subject.to_s] += rule.attributes
280
+ end
255
281
  end
256
282
  end
257
283
 
@@ -276,7 +302,11 @@ module CanCan
276
302
 
277
303
  def alternative_subjects(subject)
278
304
  subject = subject.class unless subject.is_a?(Module)
279
- [:all, *subject.ancestors, subject.class.to_s]
305
+ if subject.respond_to?(:subclasses) && defined?(ActiveRecord::Base) && subject < ActiveRecord::Base
306
+ [:all, *(subject.ancestors + subject.subclasses), subject.class.to_s]
307
+ else
308
+ [:all, *subject.ancestors, subject.class.to_s]
309
+ end
280
310
  end
281
311
  end
282
312
  end
@@ -0,0 +1,26 @@
1
+ # This class is responsible for matching classes and their subclasses as well as
2
+ # upmatching classes to their ancestors.
3
+ # This is used to generate sti connections
4
+ class SubjectClassMatcher
5
+ def self.matches_subject_class?(subjects, subject)
6
+ subjects.any? do |sub|
7
+ has_subclasses = subject.respond_to?(:subclasses)
8
+ matching_class_check(subject, sub, has_subclasses)
9
+ end
10
+ end
11
+
12
+ def self.matching_class_check(subject, sub, has_subclasses)
13
+ matches = matches_class_or_is_related(subject, sub)
14
+ if has_subclasses
15
+ matches || subject.subclasses.include?(sub)
16
+ else
17
+ matches
18
+ end
19
+ end
20
+
21
+ def self.matches_class_or_is_related(subject, sub)
22
+ sub.is_a?(Module) && (subject.is_a?(sub) ||
23
+ subject.class.to_s == sub.to_s ||
24
+ (subject.is_a?(Module) && subject.ancestors.include?(sub)))
25
+ end
26
+ end
@@ -1,26 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ConditionsMatcher
3
5
  # Matches the block or conditions hash
4
- def matches_conditions?(action, subject, extra_args)
6
+ def matches_conditions?(action, subject, attribute = nil, *extra_args)
5
7
  return call_block_with_all(action, subject, extra_args) if @match_all
6
- return @block.call(subject, *extra_args) if @block && !subject_class?(subject)
7
- matches_non_block_conditions(subject)
8
+ return matches_block_conditions(subject, attribute, *extra_args) if @block
9
+ return matches_non_block_conditions(subject) unless conditions_empty?
10
+
11
+ true
8
12
  end
9
13
 
10
14
  private
11
15
 
12
16
  def subject_class?(subject)
13
17
  klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
14
- klass == Class || klass == Module
18
+ [Class, Module].include? klass
19
+ end
20
+
21
+ def matches_block_conditions(subject, *extra_args)
22
+ return @base_behavior if subject_class?(subject)
23
+
24
+ @block.call(subject, *extra_args.compact)
15
25
  end
16
26
 
17
27
  def matches_non_block_conditions(subject)
18
- if @conditions.is_a?(Hash)
19
- return nested_subject_matches_conditions?(subject) if subject.class == Hash
20
- return matches_conditions_hash?(subject) unless subject_class?(subject)
21
- end
28
+ return nested_subject_matches_conditions?(subject) if subject.class == Hash
29
+ return matches_conditions_hash?(subject) unless subject_class?(subject)
30
+
22
31
  # Don't stop at "cannot" definitions when there are conditions.
23
- conditions_empty? ? true : @base_behavior
32
+ @base_behavior
24
33
  end
25
34
 
26
35
  def nested_subject_matches_conditions?(subject_hash)
@@ -34,6 +43,7 @@ module CanCan
34
43
  # matches_conditions_hash?(subject, conditions)
35
44
  def matches_conditions_hash?(subject, conditions = @conditions)
36
45
  return true if conditions.empty?
46
+
37
47
  adapter = model_adapter(subject)
38
48
 
39
49
  if adapter.override_conditions_hash_matching?(subject, conditions)
@@ -68,13 +78,13 @@ module CanCan
68
78
 
69
79
  def hash_condition_match?(attribute, value)
70
80
  if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
71
- attribute.any? { |element| matches_conditions_hash?(element, value) }
81
+ attribute.to_a.any? { |element| matches_conditions_hash?(element, value) }
72
82
  else
73
83
  attribute && matches_conditions_hash?(attribute, value)
74
84
  end
75
85
  end
76
86
 
77
- def call_block_with_all(action, subject, extra_args)
87
+ def call_block_with_all(action, subject, *extra_args)
78
88
  if subject.class == Class
79
89
  @block.call(action, subject, nil, *extra_args)
80
90
  else
@@ -87,7 +97,10 @@ module CanCan
87
97
  end
88
98
 
89
99
  def conditions_empty?
90
- @conditions == {} || @conditions.nil?
100
+ # @conditions might be an ActiveRecord::Associations::CollectionProxy
101
+ # which it's `==` implementation will fetch all records for comparison
102
+
103
+ (@conditions.is_a?(Hash) && @conditions == {}) || @conditions.nil?
91
104
  end
92
105
  end
93
106
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ def self.valid_accessible_by_strategies
5
+ strategies = [:left_join]
6
+ strategies << :subquery unless does_not_support_subquery_strategy?
7
+ strategies
8
+ end
9
+
10
+ # Determines how CanCan should build queries when calling accessible_by,
11
+ # if the query will contain a join. The default strategy is `:subquery`.
12
+ #
13
+ # # config/initializers/cancan.rb
14
+ # CanCan.accessible_by_strategy = :subquery
15
+ #
16
+ # Valid strategies are:
17
+ # - :subquery - Creates a nested query with all joins, wrapped by a
18
+ # WHERE IN query.
19
+ # - :left_join - Calls the joins directly using `left_joins`, and
20
+ # ensures records are unique using `distinct`. Note that
21
+ # `distinct` is not reliable in some cases. See
22
+ # https://github.com/CanCanCommunity/cancancan/pull/605
23
+ def self.accessible_by_strategy
24
+ return @accessible_by_strategy if @accessible_by_strategy
25
+
26
+ @accessible_by_strategy = default_accessible_by_strategy
27
+ end
28
+
29
+ def self.default_accessible_by_strategy
30
+ if does_not_support_subquery_strategy?
31
+ # see https://github.com/CanCanCommunity/cancancan/pull/655 for where this was added
32
+ # the `subquery` strategy (from https://github.com/CanCanCommunity/cancancan/pull/619
33
+ # only works in Rails 5 and higher
34
+ :left_join
35
+ else
36
+ :subquery
37
+ end
38
+ end
39
+
40
+ def self.accessible_by_strategy=(value)
41
+ validate_accessible_by_strategy!(value)
42
+
43
+ if value == :subquery && does_not_support_subquery_strategy?
44
+ raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer'
45
+ end
46
+
47
+ @accessible_by_strategy = value
48
+ end
49
+
50
+ def self.with_accessible_by_strategy(value)
51
+ return yield if value == accessible_by_strategy
52
+
53
+ validate_accessible_by_strategy!(value)
54
+
55
+ begin
56
+ strategy_was = accessible_by_strategy
57
+ @accessible_by_strategy = value
58
+ yield
59
+ ensure
60
+ @accessible_by_strategy = strategy_was
61
+ end
62
+ end
63
+
64
+ def self.validate_accessible_by_strategy!(value)
65
+ return if valid_accessible_by_strategies.include?(value)
66
+
67
+ raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}"
68
+ end
69
+
70
+ def self.does_not_support_subquery_strategy?
71
+ !defined?(CanCan::ModelAdapters::ActiveRecordAdapter) ||
72
+ CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
73
+ end
74
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  # This module is automatically included into all controllers.
3
5
  # It also makes the "can?" and "cannot?" methods available to all views.
@@ -225,7 +227,7 @@ module CanCan
225
227
  cancan_skipper[:authorize][name] = options
226
228
  end
227
229
 
228
- # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
230
+ # Add this to a controller to ensure it performs authorization through +authorize+! or +authorize_resource+ call.
229
231
  # If neither of these authorization methods are called,
230
232
  # a CanCan::AuthorizationNotPerformed exception will be raised.
231
233
  # This is normally added to the ApplicationController to ensure all controller actions do authorization.
@@ -260,6 +262,7 @@ module CanCan
260
262
  next if controller.instance_variable_defined?(:@_authorized)
261
263
  next if options[:if] && !controller.send(options[:if])
262
264
  next if options[:unless] && controller.send(options[:unless])
265
+
263
266
  raise AuthorizationNotPerformed,
264
267
  'This action failed the check_authorization because it does not authorize_resource. '\
265
268
  'Add skip_authorization_check to bypass this check.'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'controller_resource_loader.rb'
2
4
  module CanCan
3
5
  # Handle the load and authorization controller logic
@@ -34,6 +36,7 @@ module CanCan
34
36
 
35
37
  def authorize_resource
36
38
  return if skip?(:authorize)
39
+
37
40
  @controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
38
41
  end
39
42
 
@@ -43,6 +46,7 @@ module CanCan
43
46
 
44
47
  def skip?(behavior)
45
48
  return false unless (options = @controller.class.cancan_skipper[behavior][@name])
49
+
46
50
  options == {} ||
47
51
  options[:except] && !action_exists_in?(options[:except]) ||
48
52
  action_exists_in?(options[:only])
@@ -90,6 +94,7 @@ module CanCan
90
94
 
91
95
  def resource_instance
92
96
  return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
97
+
93
98
  @controller.instance_variable_get("@#{instance_name}")
94
99
  end
95
100
 
@@ -99,6 +104,7 @@ module CanCan
99
104
 
100
105
  def collection_instance
101
106
  return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
107
+
102
108
  @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
103
109
  end
104
110
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceBuilder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceFinder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'controller_resource_finder.rb'
2
4
  require_relative 'controller_resource_name_finder.rb'
3
5
  require_relative 'controller_resource_builder.rb'
@@ -11,6 +13,7 @@ module CanCan
11
13
 
12
14
  def load_resource
13
15
  return if skip?(:load)
16
+
14
17
  if load_instance?
15
18
  self.resource_instance ||= load_resource_instance
16
19
  elsif load_collection?
@@ -26,6 +29,7 @@ module CanCan
26
29
 
27
30
  def resource_params_by_key(key)
28
31
  return unless @options[key] && @params.key?(extract_key(@options[key]))
32
+
29
33
  @params[extract_key(@options[key])]
30
34
  end
31
35
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceNameFinder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceSanitizer
3
5
  protected