cancancan 3.1.0 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/cancancan.gemspec +2 -1
  3. data/lib/cancan/ability/rules.rb +8 -5
  4. data/lib/cancan/ability/strong_parameter_support.rb +1 -1
  5. data/lib/cancan/ability.rb +5 -1
  6. data/lib/cancan/class_matcher.rb +30 -0
  7. data/lib/cancan/conditions_matcher.rb +50 -9
  8. data/lib/cancan/config.rb +101 -0
  9. data/lib/cancan/controller_additions.rb +6 -1
  10. data/lib/cancan/controller_resource.rb +1 -1
  11. data/lib/cancan/exceptions.rb +8 -0
  12. data/lib/cancan/matchers.rb +5 -3
  13. data/lib/cancan/model_adapters/abstract_adapter.rb +20 -1
  14. data/lib/cancan/model_adapters/active_record_4_adapter.rb +2 -4
  15. data/lib/cancan/model_adapters/active_record_5_adapter.rb +8 -15
  16. data/lib/cancan/model_adapters/active_record_adapter.rb +90 -4
  17. data/lib/cancan/model_adapters/conditions_extractor.rb +4 -4
  18. data/lib/cancan/model_adapters/conditions_normalizer.rb +1 -1
  19. data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  20. data/lib/cancan/model_adapters/strategies/base.rb +40 -0
  21. data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  22. data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  23. data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  24. data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  25. data/lib/cancan/model_additions.rb +4 -2
  26. data/lib/cancan/rule.rb +16 -3
  27. data/lib/cancan/rules_compressor.rb +18 -0
  28. data/lib/cancan/sti_detector.rb +12 -0
  29. data/lib/cancan/version.rb +1 -1
  30. data/lib/cancan.rb +7 -0
  31. data/lib/generators/cancan/ability/templates/ability.rb +7 -9
  32. metadata +31 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81afd3cec5dc78c4e4d9d14719482ae589ed43bf336cc1b4f9e5681dea56b99d
4
- data.tar.gz: fd23ce69481f9daf4b227b61e4e7e236abcd40d7b5f0dd01f70ca20a3706fae3
3
+ metadata.gz: e0d4a5b11aba155764cf54465d5d8bf88872fed61f4a33736d45a531c619ad6e
4
+ data.tar.gz: 12b3b41403f3fdaba3fdb97c30ce17e319d1bfdef05fcb7700d41ba754c75a55
5
5
  SHA512:
6
- metadata.gz: 04ee2bfead0ce01e0bdc64e69fae219c221495c30950542323fc5e3d91e250e9a679863546c09db9f3a71a647cb414510bcbb92db41309d9b0b2d04f7d2a1b0e
7
- data.tar.gz: 79b4b11ef02ca50417c4e441dd8586569ed86caa4d3216fc54e1713bd09071e544b529db0babd429dd14b0efc90f59f2dfbd8a8d101a9e4d4332908f0487115b
6
+ metadata.gz: beb6989dbb2554678fa17626b6138aab396419b6d01ed8ce3ee3781030086da48985837a01bef50ab111fb47273454df8c7e3d6ced11b10a036ffe2e494feb15
7
+ data.tar.gz: 405b0e6eb4ab73f04964651ab27e106701c014cd706f74012e5f30aada5796de6541f812593b57e058031af0991890884701136229f0405900e3bc99ce3747ee
data/cancancan.gemspec CHANGED
@@ -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
@@ -24,5 +25,5 @@ Gem::Specification.new do |s|
24
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 'rubocop', '~> 0.63.1'
28
+ s.add_development_dependency 'rubocop', '~> 1.31.1'
28
29
  end
@@ -19,12 +19,13 @@ module CanCan
19
19
  end
20
20
 
21
21
  def add_rule_to_index(rule, position)
22
- @rules_index ||= Hash.new { |h, k| h[k] = [] }
22
+ @rules_index ||= {}
23
23
 
24
24
  subjects = rule.subjects.compact
25
25
  subjects << :all if subjects.empty?
26
26
 
27
27
  subjects.each do |subject|
28
+ @rules_index[subject] ||= []
28
29
  @rules_index[subject] << position
29
30
  end
30
31
  end
@@ -48,7 +49,9 @@ module CanCan
48
49
  rules
49
50
  else
50
51
  positions = @rules_index.values_at(subject, *alternative_subjects(subject))
51
- positions.flatten!.sort!
52
+ positions.compact!
53
+ positions.flatten!
54
+ positions.sort!
52
55
  positions.map { |i| @rules[i] }
53
56
  end
54
57
  end
@@ -58,8 +61,8 @@ module CanCan
58
61
  next unless rule.only_raw_sql?
59
62
 
60
63
  raise Error,
61
- "The can? and cannot? call cannot be used with a raw sql 'can' definition."\
62
- " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
64
+ "The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
65
+ "The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
63
66
  end
64
67
  end
65
68
 
@@ -69,7 +72,7 @@ module CanCan
69
72
  rule.base_behavior == false && rule.attributes.present?
70
73
  end
71
74
  if rules.any?(&:only_block?)
72
- raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
75
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
73
76
  "The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
74
77
  end
75
78
  rules
@@ -31,7 +31,7 @@ module CanCan
31
31
  klass = subject_class?(subject) ? subject : subject.class
32
32
  # empty attributes is an 'all'
33
33
  if rule.attributes.empty? && klass < ActiveRecord::Base
34
- klass.column_names.map(&:to_sym) - Array(klass.primary_key)
34
+ klass.attribute_names.map(&:to_sym) - Array(klass.primary_key)
35
35
  else
36
36
  rule.attributes
37
37
  end
@@ -302,7 +302,11 @@ module CanCan
302
302
 
303
303
  def alternative_subjects(subject)
304
304
  subject = subject.class unless subject.is_a?(Module)
305
- [: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
306
310
  end
307
311
  end
308
312
  end
@@ -0,0 +1,30 @@
1
+ require_relative 'sti_detector'
2
+
3
+ # This class is responsible for matching classes and their subclasses as well as
4
+ # upmatching classes to their ancestors.
5
+ # This is used to generate sti connections
6
+ class SubjectClassMatcher
7
+ def self.matches_subject_class?(subjects, subject)
8
+ subjects.any? do |sub|
9
+ has_subclasses = subject.respond_to?(:subclasses)
10
+ matching_class_check(subject, sub, has_subclasses)
11
+ end
12
+ end
13
+
14
+ def self.matching_class_check(subject, sub, has_subclasses)
15
+ matches = matches_class_or_is_related(subject, sub)
16
+ if has_subclasses
17
+ return matches unless StiDetector.sti_class?(sub)
18
+
19
+ matches || subject.subclasses.include?(sub)
20
+ else
21
+ matches
22
+ end
23
+ end
24
+
25
+ def self.matches_class_or_is_related(subject, sub)
26
+ sub.is_a?(Module) && (subject.is_a?(sub) ||
27
+ subject.class.to_s == sub.to_s ||
28
+ (subject.is_a?(Module) && subject.ancestors.include?(sub)))
29
+ end
30
+ end
@@ -18,10 +18,14 @@ module CanCan
18
18
  [Class, Module].include? klass
19
19
  end
20
20
 
21
- def matches_block_conditions(subject, *extra_args)
21
+ def matches_block_conditions(subject, attribute, *extra_args)
22
22
  return @base_behavior if subject_class?(subject)
23
23
 
24
- @block.call(subject, *extra_args.compact)
24
+ if attribute
25
+ @block.call(subject, attribute, *extra_args)
26
+ else
27
+ @block.call(subject, *extra_args)
28
+ end
25
29
  end
26
30
 
27
31
  def matches_non_block_conditions(subject)
@@ -33,16 +37,26 @@ module CanCan
33
37
  end
34
38
 
35
39
  def nested_subject_matches_conditions?(subject_hash)
36
- parent, _child = subject_hash.first
37
- matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
40
+ parent, child = subject_hash.first
41
+
42
+ adapter = model_adapter(parent)
43
+
44
+ parent_condition_name = adapter.parent_condition_name(parent, child)
45
+
46
+ matches_base_parent_conditions = matches_conditions_hash?(parent,
47
+ @conditions[parent_condition_name] || {})
48
+
49
+ matches_base_parent_conditions &&
50
+ (!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) ||
51
+ adapter.nested_subject_matches_conditions?(parent, child, @conditions))
38
52
  end
39
53
 
40
54
  # Checks if the given subject matches the given conditions hash.
41
- # This behavior can be overriden by a model adapter by defining two class methods:
55
+ # This behavior can be overridden by a model adapter by defining two class methods:
42
56
  # override_matching_for_conditions?(subject, conditions) and
43
57
  # matches_conditions_hash?(subject, conditions)
44
58
  def matches_conditions_hash?(subject, conditions = @conditions)
45
- return true if conditions.empty?
59
+ return true if conditions.is_a?(Hash) && conditions.empty?
46
60
 
47
61
  adapter = model_adapter(subject)
48
62
 
@@ -50,10 +64,20 @@ module CanCan
50
64
  return adapter.matches_conditions_hash?(subject, conditions)
51
65
  end
52
66
 
53
- matches_all_conditions?(adapter, conditions, subject)
67
+ matches_all_conditions?(adapter, subject, conditions)
68
+ end
69
+
70
+ def matches_all_conditions?(adapter, subject, conditions)
71
+ if conditions.is_a?(Hash)
72
+ matches_hash_conditions?(adapter, subject, conditions)
73
+ elsif conditions.respond_to?(:include?)
74
+ conditions.include?(subject)
75
+ else
76
+ subject == conditions
77
+ end
54
78
  end
55
79
 
56
- def matches_all_conditions?(adapter, conditions, subject)
80
+ def matches_hash_conditions?(adapter, subject, conditions)
57
81
  conditions.all? do |name, value|
58
82
  if adapter.override_condition_matching?(subject, name, value)
59
83
  adapter.matches_condition?(subject, name, value)
@@ -78,12 +102,29 @@ module CanCan
78
102
 
79
103
  def hash_condition_match?(attribute, value)
80
104
  if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
81
- attribute.any? { |element| matches_conditions_hash?(element, value) }
105
+ array_like_matches_condition_hash?(attribute, value)
82
106
  else
83
107
  attribute && matches_conditions_hash?(attribute, value)
84
108
  end
85
109
  end
86
110
 
111
+ def array_like_matches_condition_hash?(attribute, value)
112
+ if attribute.any?
113
+ attribute.any? { |element| matches_conditions_hash?(element, value) }
114
+ else
115
+ # you can use `nil`s in your ability definition to tell cancancan to find
116
+ # objects that *don't* have any children in a has_many relationship.
117
+ #
118
+ # for example, given ability:
119
+ # => can :read, Article, comments: { id: nil }
120
+ # cancancan will return articles where `article.comments == []`
121
+ #
122
+ # this is implemented here. `attribute` is `article.comments`, and it's an empty array.
123
+ # the expression below returns true if this was expected.
124
+ !value.values.empty? && value.values.all?(&:nil?)
125
+ end
126
+ end
127
+
87
128
  def call_block_with_all(action, subject, *extra_args)
88
129
  if subject.class == Class
89
130
  @block.call(action, subject, nil, *extra_args)
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ def self.valid_accessible_by_strategies
5
+ strategies = [:left_join]
6
+
7
+ unless does_not_support_subquery_strategy?
8
+ strategies.push(:joined_alias_exists_subquery, :joined_alias_each_rule_as_exists_subquery, :subquery)
9
+ end
10
+
11
+ strategies
12
+ end
13
+
14
+ # You can disable the rules compressor if it's causing unexpected issues.
15
+ def self.rules_compressor_enabled
16
+ return @rules_compressor_enabled if defined?(@rules_compressor_enabled)
17
+
18
+ @rules_compressor_enabled = true
19
+ end
20
+
21
+ def self.rules_compressor_enabled=(value)
22
+ @rules_compressor_enabled = value
23
+ end
24
+
25
+ def self.with_rules_compressor_enabled(value)
26
+ return yield if value == rules_compressor_enabled
27
+
28
+ begin
29
+ rules_compressor_enabled_was = rules_compressor_enabled
30
+ @rules_compressor_enabled = value
31
+ yield
32
+ ensure
33
+ @rules_compressor_enabled = rules_compressor_enabled_was
34
+ end
35
+ end
36
+
37
+ # Determines how CanCan should build queries when calling accessible_by,
38
+ # if the query will contain a join. The default strategy is `:subquery`.
39
+ #
40
+ # # config/initializers/cancan.rb
41
+ # CanCan.accessible_by_strategy = :subquery
42
+ #
43
+ # Valid strategies are:
44
+ # - :subquery - Creates a nested query with all joins, wrapped by a
45
+ # WHERE IN query.
46
+ # - :left_join - Calls the joins directly using `left_joins`, and
47
+ # ensures records are unique using `distinct`. Note that
48
+ # `distinct` is not reliable in some cases. See
49
+ # https://github.com/CanCanCommunity/cancancan/pull/605
50
+ def self.accessible_by_strategy
51
+ return @accessible_by_strategy if @accessible_by_strategy
52
+
53
+ @accessible_by_strategy = default_accessible_by_strategy
54
+ end
55
+
56
+ def self.default_accessible_by_strategy
57
+ if does_not_support_subquery_strategy?
58
+ # see https://github.com/CanCanCommunity/cancancan/pull/655 for where this was added
59
+ # the `subquery` strategy (from https://github.com/CanCanCommunity/cancancan/pull/619
60
+ # only works in Rails 5 and higher
61
+ :left_join
62
+ else
63
+ :subquery
64
+ end
65
+ end
66
+
67
+ def self.accessible_by_strategy=(value)
68
+ validate_accessible_by_strategy!(value)
69
+
70
+ if value == :subquery && does_not_support_subquery_strategy?
71
+ raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer'
72
+ end
73
+
74
+ @accessible_by_strategy = value
75
+ end
76
+
77
+ def self.with_accessible_by_strategy(value)
78
+ return yield if value == accessible_by_strategy
79
+
80
+ validate_accessible_by_strategy!(value)
81
+
82
+ begin
83
+ strategy_was = accessible_by_strategy
84
+ @accessible_by_strategy = value
85
+ yield
86
+ ensure
87
+ @accessible_by_strategy = strategy_was
88
+ end
89
+ end
90
+
91
+ def self.validate_accessible_by_strategy!(value)
92
+ return if valid_accessible_by_strategies.include?(value)
93
+
94
+ raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}"
95
+ end
96
+
97
+ def self.does_not_support_subquery_strategy?
98
+ !defined?(CanCan::ModelAdapters::ActiveRecordAdapter) ||
99
+ CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
100
+ end
101
+ end
@@ -171,6 +171,11 @@ module CanCan
171
171
  # [:+instance_name+]
172
172
  # The name of the instance variable for this resource.
173
173
  #
174
+ # [:+id_param+]
175
+ # Find using a param key other than :id. For example:
176
+ #
177
+ # load_resource :id_param => :url # will use find(params[:url])
178
+ #
174
179
  # [:+through+]
175
180
  # Authorize conditions on this parent resource when instance isn't available.
176
181
  #
@@ -264,7 +269,7 @@ module CanCan
264
269
  next if options[:unless] && controller.send(options[:unless])
265
270
 
266
271
  raise AuthorizationNotPerformed,
267
- 'This action failed the check_authorization because it does not authorize_resource. '\
272
+ 'This action failed the check_authorization because it does not authorize_resource. ' \
268
273
  'Add skip_authorization_check to bypass this check.'
269
274
  end
270
275
 
@@ -54,7 +54,7 @@ module CanCan
54
54
 
55
55
  protected
56
56
 
57
- # Returns the class used for this resource. This can be overriden by the :class option.
57
+ # Returns the class used for this resource. This can be overridden by the :class option.
58
58
  # If +false+ is passed in it will use the resource name as a symbol in which case it should
59
59
  # only be used for authorization, not loading since there's no class to load through.
60
60
  def resource_class
@@ -58,5 +58,13 @@ module CanCan
58
58
  def to_s
59
59
  @message || @default_message
60
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
61
69
  end
62
70
  end
@@ -13,9 +13,11 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
13
13
  match do |ability|
14
14
  actions = args.first
15
15
  if actions.is_a? Array
16
- break false if actions.empty?
17
-
18
- actions.all? { |action| ability.can?(action, *args[1..-1]) }
16
+ if actions.empty?
17
+ false
18
+ else
19
+ actions.all? { |action| ability.can?(action, *args[1..-1]) }
20
+ end
19
21
  else
20
22
  ability.can?(*args)
21
23
  end
@@ -3,9 +3,11 @@
3
3
  module CanCan
4
4
  module ModelAdapters
5
5
  class AbstractAdapter
6
+ attr_reader :model_class
7
+
6
8
  def self.inherited(subclass)
7
9
  @subclasses ||= []
8
- @subclasses << subclass
10
+ @subclasses.insert(0, subclass)
9
11
  end
10
12
 
11
13
  def self.adapter_class(model_class)
@@ -33,6 +35,23 @@ module CanCan
33
35
  raise NotImplemented, 'This model adapter does not support matching on a conditions hash.'
34
36
  end
35
37
 
38
+ # Override if parent condition could be under a different key in conditions
39
+ def self.parent_condition_name(parent, _child)
40
+ parent.class.name.downcase.to_sym
41
+ end
42
+
43
+ # Used above override_conditions_hash_matching to determine if this model adapter will override the
44
+ # matching behavior for nested subject.
45
+ # If this returns true then nested_subject_matches_conditions? will be called.
46
+ def self.override_nested_subject_conditions_matching?(_parent, _child, _all_conditions)
47
+ false
48
+ end
49
+
50
+ # Override if override_nested_subject_conditions_matching? returns true
51
+ def self.nested_subject_matches_conditions?(_parent, _child, _all_conditions)
52
+ raise NotImplemented, 'This model adapter does not support matching on a nested subject.'
53
+ end
54
+
36
55
  # Used to determine if this model adapter will override the matching behavior for a specific condition.
37
56
  # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
38
57
  def self.override_condition_matching?(_subject, _name, _value)
@@ -34,10 +34,8 @@ module CanCan
34
34
  # look inside the where clause to decide to outer join tables
35
35
  # you're using in the where. Instead, `references()` is required
36
36
  # in addition to `includes()` to force the outer join.
37
- def build_relation(*where_conditions)
38
- relation = @model_class.where(*where_conditions)
39
- relation = relation.includes(joins).references(joins) if joins.present?
40
- relation
37
+ def build_joins_relation(relation, *_where_conditions)
38
+ relation.includes(joins).references(joins)
41
39
  end
42
40
 
43
41
  # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
@@ -21,18 +21,15 @@ module CanCan
21
21
 
22
22
  private
23
23
 
24
- def build_relation(*where_conditions)
25
- if joins.present?
26
- inner = @model_class.unscoped do
27
- @model_class.left_joins(joins).where(*where_conditions)
28
- end
29
- @model_class.where(@model_class.primary_key => inner)
30
- else
31
- @model_class.where(*where_conditions)
32
- end
24
+ def build_joins_relation(relation, *where_conditions)
25
+ strategy_class.new(adapter: self, relation: relation, where_conditions: where_conditions).execute!
26
+ end
27
+
28
+ def strategy_class
29
+ strategy_class_name = CanCan.accessible_by_strategy.to_s.camelize
30
+ CanCan::ModelAdapters::Strategies.const_get(strategy_class_name)
33
31
  end
34
32
 
35
- # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
36
33
  def sanitize_sql(conditions)
37
34
  if conditions.is_a?(Hash)
38
35
  sanitize_sql_activerecord5(conditions)
@@ -46,11 +43,7 @@ module CanCan
46
43
  table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
47
44
  predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
48
45
 
49
- conditions = predicate_builder.resolve_column_aliases(conditions)
50
-
51
- conditions.stringify_keys!
52
-
53
- predicate_builder.build_from_hash(conditions).map { |b| visit_nodes(b) }.join(' AND ')
46
+ predicate_builder.build_from_hash(conditions.stringify_keys).map { |b| visit_nodes(b) }.join(' AND ')
54
47
  end
55
48
 
56
49
  def visit_nodes(node)
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'conditions_extractor.rb'
4
- require 'cancan/rules_compressor'
3
+ # rubocop:disable Metrics/AbcSize
4
+ # rubocop:disable Metrics/CyclomaticComplexity
5
+ # rubocop:disable Metrics/PerceivedComplexity
5
6
  module CanCan
6
7
  module ModelAdapters
7
8
  class ActiveRecordAdapter < AbstractAdapter
@@ -13,12 +14,86 @@ module CanCan
13
14
  Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
14
15
  end
15
16
 
17
+ attr_reader :compressed_rules
18
+
16
19
  def initialize(model_class, rules)
17
20
  super
18
- @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
21
+ @compressed_rules = if CanCan.rules_compressor_enabled
22
+ RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
23
+ else
24
+ @rules
25
+ end
26
+ StiNormalizer.normalize(@compressed_rules)
19
27
  ConditionsNormalizer.normalize(model_class, @compressed_rules)
20
28
  end
21
29
 
30
+ class << self
31
+ # When belongs_to parent_id is a condition for a model,
32
+ # we want to check the parent when testing ability for a hash {parent => model}
33
+ def override_nested_subject_conditions_matching?(parent, child, all_conditions)
34
+ parent_child_conditions(parent, child, all_conditions).present?
35
+ end
36
+
37
+ # parent_id condition can be an array of integer or one integer, we check the parent against this
38
+ def nested_subject_matches_conditions?(parent, child, all_conditions)
39
+ id_condition = parent_child_conditions(parent, child, all_conditions)
40
+ return id_condition.include?(parent.id) if id_condition.is_a? Array
41
+ return id_condition == parent.id if id_condition.is_a? Integer
42
+
43
+ false
44
+ end
45
+
46
+ def parent_child_conditions(parent, child, all_conditions)
47
+ child_class = child.is_a?(Class) ? child : child.class
48
+ parent_class = parent.is_a?(Class) ? parent : parent.class
49
+
50
+ foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association|
51
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
52
+ !association.polymorphic? && association.klass == parent.class
53
+ end&.foreign_key&.to_sym
54
+
55
+ # Search again in case of polymorphic associations, this time matching on the :has_many side
56
+ # via the :as option, as well as klass
57
+ foreign_key ||= parent_class.reflect_on_all_associations(:has_many).find do |has_many_assoc|
58
+ matching_parent_child_polymorphic_association(has_many_assoc, child_class)
59
+ end&.foreign_key&.to_sym
60
+
61
+ foreign_key.nil? ? nil : all_conditions[foreign_key]
62
+ end
63
+
64
+ def matching_parent_child_polymorphic_association(parent_assoc, child_class)
65
+ return nil unless parent_assoc.klass == child_class
66
+ return nil if parent_assoc&.options[:as].nil?
67
+
68
+ child_class.reflect_on_all_associations(:belongs_to).find do |child_assoc|
69
+ # Only match this way for polymorphic associations
70
+ child_assoc.polymorphic? && child_assoc.name == parent_assoc.options[:as]
71
+ end
72
+ end
73
+
74
+ def child_association_to_parent(parent, child)
75
+ child_class = child.is_a?(Class) ? child : child.class
76
+ parent_class = parent.is_a?(Class) ? parent : parent.class
77
+
78
+ association = child_class.reflect_on_all_associations(:belongs_to).find do |belongs_to_assoc|
79
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
80
+ !belongs_to_assoc.polymorphic? && belongs_to_assoc.klass == parent.class
81
+ end
82
+
83
+ return association if association
84
+
85
+ parent_class.reflect_on_all_associations(:has_many).each do |has_many_assoc|
86
+ association ||= matching_parent_child_polymorphic_association(has_many_assoc, child_class)
87
+ end
88
+
89
+ association
90
+ end
91
+
92
+ def parent_condition_name(parent, child)
93
+ child_association_to_parent(parent, child)&.name || parent.class.name.downcase.to_sym
94
+ end
95
+ end
96
+
22
97
  # Returns conditions intended to be used inside a database query. Normally you will not call this
23
98
  # method directly, but instead go through ModelAdditions#accessible_by.
24
99
  #
@@ -60,6 +135,14 @@ module CanCan
60
135
  end
61
136
  end
62
137
 
138
+ def build_relation(*where_conditions)
139
+ relation = @model_class.where(*where_conditions)
140
+ return relation unless joins.present?
141
+
142
+ # subclasses must implement `build_joins_relation`
143
+ build_joins_relation(relation, *where_conditions)
144
+ end
145
+
63
146
  # Returns the associations used in conditions for the :joins option of a search.
64
147
  # See ModelAdditions#accessible_by
65
148
  def joins
@@ -99,7 +182,7 @@ module CanCan
99
182
  def raise_override_scope_error
100
183
  rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
101
184
  raise Error,
102
- 'Unable to merge an Active Record scope with other conditions. '\
185
+ 'Unable to merge an Active Record scope with other conditions. ' \
103
186
  "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
104
187
  end
105
188
 
@@ -137,6 +220,9 @@ module CanCan
137
220
  end
138
221
  end
139
222
  end
223
+ # rubocop:enable Metrics/PerceivedComplexity
224
+ # rubocop:enable Metrics/CyclomaticComplexity
225
+ # rubocop:enable Metrics/AbcSize
140
226
 
141
227
  ActiveSupport.on_load(:active_record) do
142
228
  send :include, CanCan::ModelAdditions
@@ -3,7 +3,7 @@
3
3
  # this class is responsible of converting the hash of conditions
4
4
  # in "where conditions" to generate the sql query
5
5
  # it consists of a names_cache that helps calculating the next name given to the association
6
- # it tries to reflect the bahavior of ActiveRecord when generating aliases for tables.
6
+ # it tries to reflect the behavior of ActiveRecord when generating aliases for tables.
7
7
  module CanCan
8
8
  module ModelAdapters
9
9
  class ConditionsExtractor
@@ -50,18 +50,18 @@ module CanCan
50
50
  def generate_table_alias(model_class, relation_name, path_to_key)
51
51
  table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym
52
52
 
53
- if alredy_used?(table_alias, relation_name, path_to_key)
53
+ if already_used?(table_alias, relation_name, path_to_key)
54
54
  table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym
55
55
 
56
56
  index = 1
57
- while alredy_used?(table_alias, relation_name, path_to_key)
57
+ while already_used?(table_alias, relation_name, path_to_key)
58
58
  table_alias = "#{table_alias}_#{index += 1}".to_sym
59
59
  end
60
60
  end
61
61
  add_to_cache(table_alias, relation_name, path_to_key)
62
62
  end
63
63
 
64
- def alredy_used?(table_alias, relation_name, path_to_key)
64
+ def already_used?(table_alias, relation_name, path_to_key)
65
65
  @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}")
66
66
  end
67
67
 
@@ -1,6 +1,6 @@
1
1
  # this class is responsible of normalizing the hash of conditions
2
2
  # by exploding has_many through associations
3
- # when a condition is defined with an has_many thorugh association this is exploded in all its parts
3
+ # when a condition is defined with an has_many through association this is exploded in all its parts
4
4
  # TODO: it could identify STI and normalize it
5
5
  module CanCan
6
6
  module ModelAdapters
@@ -0,0 +1,47 @@
1
+ require_relative '../sti_detector'
2
+
3
+ # this class is responsible for detecting sti classes and creating new rules for the
4
+ # relevant subclasses, using the inheritance_column as a merger
5
+ module CanCan
6
+ module ModelAdapters
7
+ class StiNormalizer
8
+ class << self
9
+ def normalize(rules)
10
+ rules_cache = []
11
+ return unless defined?(ActiveRecord::Base)
12
+
13
+ rules.delete_if do |rule|
14
+ subjects = rule.subjects.select do |subject|
15
+ update_rule(subject, rule, rules_cache)
16
+ end
17
+ subjects.length == rule.subjects.length
18
+ end
19
+ rules_cache.each { |rule| rules.push(rule) }
20
+ end
21
+
22
+ private
23
+
24
+ def update_rule(subject, rule, rules_cache)
25
+ return false unless StiDetector.sti_class?(subject)
26
+
27
+ rules_cache.push(build_rule_for_subclass(rule, subject))
28
+ true
29
+ end
30
+
31
+ # create a new rule for the subclasses that links on the inheritance_column
32
+ def build_rule_for_subclass(rule, subject)
33
+ sti_conditions = { subject.inheritance_column => subject.sti_name }
34
+ new_rule_conditions =
35
+ if rule.with_scope?
36
+ rule.conditions.where(sti_conditions)
37
+ else
38
+ rule.conditions.merge(sti_conditions)
39
+ end
40
+
41
+ CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass,
42
+ new_rule_conditions, rule.block)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Base
5
+ attr_reader :adapter, :relation, :where_conditions
6
+
7
+ delegate(
8
+ :compressed_rules,
9
+ :extract_multiple_conditions,
10
+ :joins,
11
+ :model_class,
12
+ :quoted_primary_key,
13
+ :quoted_aliased_table_name,
14
+ :quoted_table_name,
15
+ to: :adapter
16
+ )
17
+ delegate :connection, :quoted_primary_key, to: :model_class
18
+ delegate :quote_table_name, to: :connection
19
+
20
+ def initialize(adapter:, relation:, where_conditions:)
21
+ @adapter = adapter
22
+ @relation = relation
23
+ @where_conditions = where_conditions
24
+ end
25
+
26
+ def aliased_table_name
27
+ @aliased_table_name ||= "#{model_class.table_name}_alias"
28
+ end
29
+
30
+ def quoted_aliased_table_name
31
+ @quoted_aliased_table_name ||= quote_table_name(aliased_table_name)
32
+ end
33
+
34
+ def quoted_table_name
35
+ @quoted_table_name ||= quote_table_name(model_class.table_name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: false
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasEachRuleAsExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where(double_exists_sql)
14
+ end
15
+
16
+ def double_exists_sql
17
+ double_exists_sql = ''
18
+
19
+ compressed_rules.each_with_index do |rule, index|
20
+ double_exists_sql << ' OR ' if index.positive?
21
+ double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})"
22
+ end
23
+
24
+ double_exists_sql
25
+ end
26
+
27
+ def sub_query_for_rule(rule)
28
+ conditions_extractor = ConditionsExtractor.new(model_class)
29
+ rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule])
30
+ joins_hash, left_joins_hash = extract_joins_from_rule(rule)
31
+ sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
32
+ end
33
+
34
+ def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
35
+ model_class
36
+ .select('1')
37
+ .joins(joins_hash)
38
+ .left_joins(left_joins_hash)
39
+ .where(
40
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
41
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
42
+ )
43
+ .where(rule_where_conditions)
44
+ .limit(1)
45
+ end
46
+
47
+ def extract_joins_from_rule(rule)
48
+ joins = {}
49
+ left_joins = {}
50
+
51
+ extra_joins_recursive([], rule.conditions, joins, left_joins)
52
+ [joins, left_joins]
53
+ end
54
+
55
+ def extra_joins_recursive(current_path, conditions, joins, left_joins)
56
+ conditions.each do |key, value|
57
+ if value.is_a?(Hash)
58
+ current_path << key
59
+ extra_joins_recursive(current_path, value, joins, left_joins)
60
+ current_path.pop
61
+ else
62
+ extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
63
+ end
64
+ end
65
+ end
66
+
67
+ def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
68
+ hash_joins = current_path_to_hash(current_path)
69
+
70
+ if value.nil?
71
+ left_joins.deep_merge!(hash_joins)
72
+ else
73
+ joins.deep_merge!(hash_joins)
74
+ end
75
+ end
76
+
77
+ # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}}
78
+ def current_path_to_hash(current_path)
79
+ hash_joins = {}
80
+ current_hash_joins = hash_joins
81
+
82
+ current_path.each do |path_part|
83
+ new_hash = {}
84
+ current_hash_joins[path_part] = new_hash
85
+ current_hash_joins = new_hash
86
+ end
87
+
88
+ hash_joins
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ModelAdapters
5
+ class Strategies
6
+ class JoinedAliasExistsSubquery < Base
7
+ def execute!
8
+ model_class
9
+ .joins(
10
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
11
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
12
+ )
13
+ .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})")
14
+ end
15
+
16
+ def joined_alias_exists_subquery_inner_query
17
+ model_class
18
+ .unscoped
19
+ .select('1')
20
+ .left_joins(joins)
21
+ .where(*where_conditions)
22
+ .where(
23
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
24
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
25
+ )
26
+ .limit(1)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class LeftJoin < Base
5
+ def execute!
6
+ relation.left_joins(joins).distinct
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ module ModelAdapters
3
+ class Strategies
4
+ class Subquery < Base
5
+ def execute!
6
+ build_joins_relation_subquery(where_conditions)
7
+ end
8
+
9
+ def build_joins_relation_subquery(where_conditions)
10
+ inner = model_class.unscoped do
11
+ model_class.left_joins(joins).where(*where_conditions)
12
+ end
13
+ model_class.where(model_class.primary_key => inner)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -20,8 +20,10 @@ module CanCan
20
20
  # @articles = Article.accessible_by(current_ability, :update)
21
21
  #
22
22
  # Here only the articles which the user can update are returned.
23
- def accessible_by(ability, action = :index)
24
- ability.model_adapter(self, action).database_records
23
+ def accessible_by(ability, action = :index, strategy: CanCan.accessible_by_strategy)
24
+ CanCan.with_accessible_by_strategy(strategy) do
25
+ ability.model_adapter(self, action).database_records
26
+ end
25
27
  end
26
28
  end
27
29
 
data/lib/cancan/rule.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'conditions_matcher.rb'
4
+ require_relative 'class_matcher.rb'
4
5
  require_relative 'relevant.rb'
5
6
 
6
7
  module CanCan
@@ -11,7 +12,7 @@ module CanCan
11
12
  include ConditionsMatcher
12
13
  include Relevant
13
14
  include ParameterValidators
14
- attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
15
+ attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
15
16
  attr_writer :expanded_actions, :conditions
16
17
 
17
18
  # The first argument when initializing is the base_behavior which is a true/false
@@ -69,7 +70,7 @@ module CanCan
69
70
  end
70
71
 
71
72
  def with_scope?
72
- @conditions.is_a?(ActiveRecord::Relation)
73
+ defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
73
74
  end
74
75
 
75
76
  def associations_hash(conditions = @conditions)
@@ -101,6 +102,18 @@ module CanCan
101
102
 
102
103
  private
103
104
 
105
+ def matches_action?(action)
106
+ @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
107
+ end
108
+
109
+ def matches_subject?(subject)
110
+ @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
111
+ end
112
+
113
+ def matches_subject_class?(subject)
114
+ SubjectClassMatcher.matches_subject_class?(@subjects, subject)
115
+ end
116
+
104
117
  def parse_attributes_from_extra_args(args)
105
118
  attributes = args.shift if valid_attribute_param?(args.first)
106
119
  extra_args = args.shift
@@ -110,7 +123,7 @@ module CanCan
110
123
  def condition_and_block_check(conditions, block, action, subject)
111
124
  return unless conditions.is_a?(Hash) && block
112
125
 
113
- raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\
126
+ raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
114
127
  "Check \":#{action} #{subject}\" ability."
115
128
  end
116
129
 
@@ -11,6 +11,7 @@ module CanCan
11
11
  end
12
12
 
13
13
  def compress(array)
14
+ array = simplify(array)
14
15
  idx = array.rindex(&:catch_all?)
15
16
  return array unless idx
16
17
 
@@ -19,5 +20,22 @@ module CanCan
19
20
  .drop_while { |n| n.base_behavior == value.base_behavior }
20
21
  .tap { |a| a.unshift(value) unless value.cannot_catch_all? }
21
22
  end
23
+
24
+ # If we have A OR (!A AND anything ), then we can simplify to A OR anything
25
+ # If we have A OR (A OR anything ), then we can simplify to A OR anything
26
+ # If we have !A AND (A OR something), then we can simplify it to !A AND something
27
+ # If we have !A AND (!A AND something), then we can simplify it to !A AND something
28
+ #
29
+ # So as soon as we see a condition that is the same as the previous one,
30
+ # we can skip it, no matter of the base_behavior
31
+ def simplify(rules)
32
+ seen = Set.new
33
+ rules.reverse_each.filter_map do |rule|
34
+ next if seen.include?(rule.conditions)
35
+
36
+ seen.add(rule.conditions)
37
+ rule
38
+ end.reverse
39
+ end
22
40
  end
23
41
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StiDetector
4
+ def self.sti_class?(subject)
5
+ return false unless defined?(ActiveRecord::Base)
6
+ return false unless subject.respond_to?(:descends_from_active_record?)
7
+ return false if subject == :all || subject.descends_from_active_record?
8
+ return false unless subject < ActiveRecord::Base
9
+
10
+ true
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CanCan
4
- VERSION = '3.1.0'.freeze
4
+ VERSION = '3.6.1'.freeze
5
5
  end
data/lib/cancan.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cancan/version'
4
+ require 'cancan/config'
4
5
  require 'cancan/parameter_validators'
5
6
  require 'cancan/ability'
6
7
  require 'cancan/rule'
@@ -16,7 +17,13 @@ require 'cancan/rules_compressor'
16
17
  if defined? ActiveRecord
17
18
  require 'cancan/model_adapters/conditions_extractor'
18
19
  require 'cancan/model_adapters/conditions_normalizer'
20
+ require 'cancan/model_adapters/sti_normalizer'
19
21
  require 'cancan/model_adapters/active_record_adapter'
20
22
  require 'cancan/model_adapters/active_record_4_adapter'
21
23
  require 'cancan/model_adapters/active_record_5_adapter'
24
+ require 'cancan/model_adapters/strategies/base'
25
+ require 'cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery'
26
+ require 'cancan/model_adapters/strategies/joined_alias_exists_subquery'
27
+ require 'cancan/model_adapters/strategies/left_join'
28
+ require 'cancan/model_adapters/strategies/subquery'
22
29
  end
@@ -4,14 +4,12 @@ class Ability
4
4
  include CanCan::Ability
5
5
 
6
6
  def initialize(user)
7
- # Define abilities for the passed in user here. For example:
7
+ # Define abilities for the user here. For example:
8
8
  #
9
- # user ||= User.new # guest user (not logged in)
10
- # if user.admin?
11
- # can :manage, :all
12
- # else
13
- # can :read, :all
14
- # end
9
+ # return unless user.present?
10
+ # can :read, :all
11
+ # return unless user.admin?
12
+ # can :manage, :all
15
13
  #
16
14
  # The first argument to `can` is the action you are giving the user
17
15
  # permission to do.
@@ -26,9 +24,9 @@ class Ability
26
24
  # objects.
27
25
  # For example, here the user can only update published articles.
28
26
  #
29
- # can :update, Article, :published => true
27
+ # can :update, Article, published: true
30
28
  #
31
29
  # See the wiki for details:
32
- # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
30
+ # https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md
33
31
  end
34
32
  end
metadata CHANGED
@@ -1,38 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancancan
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessandro Rodi (Renuo AG)
8
8
  - Bryan Rite
9
9
  - Ryan Bates
10
10
  - Richard Wilson
11
- autorequire:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2020-03-15 00:00:00.000000000 Z
14
+ date: 2024-05-28 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: appraisal
18
18
  requirement: !ruby/object:Gem::Requirement
19
19
  requirements:
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 2.0.0
23
20
  - - "~>"
24
21
  - !ruby/object:Gem::Version
25
22
  version: '2.0'
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 2.0.0
26
26
  type: :development
27
27
  prerelease: false
28
28
  version_requirements: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 2.0.0
33
30
  - - "~>"
34
31
  - !ruby/object:Gem::Version
35
32
  version: '2.0'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.0.0
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bundler
38
38
  requirement: !ruby/object:Gem::Requirement
@@ -71,36 +71,36 @@ dependencies:
71
71
  name: rspec
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 3.2.0
77
74
  - - "~>"
78
75
  - !ruby/object:Gem::Version
79
76
  version: '3.2'
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 3.2.0
80
80
  type: :development
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: 3.2.0
87
84
  - - "~>"
88
85
  - !ruby/object:Gem::Version
89
86
  version: '3.2'
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.2.0
90
90
  - !ruby/object:Gem::Dependency
91
91
  name: rubocop
92
92
  requirement: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.63.1
96
+ version: 1.31.1
97
97
  type: :development
98
98
  prerelease: false
99
99
  version_requirements: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.63.1
103
+ version: 1.31.1
104
104
  description: Simple authorization solution for Rails. All permissions are stored in
105
105
  a single location.
106
106
  email: alessandro.rodi@renuo.ch
@@ -115,7 +115,9 @@ files:
115
115
  - lib/cancan/ability/actions.rb
116
116
  - lib/cancan/ability/rules.rb
117
117
  - lib/cancan/ability/strong_parameter_support.rb
118
+ - lib/cancan/class_matcher.rb
118
119
  - lib/cancan/conditions_matcher.rb
120
+ - lib/cancan/config.rb
119
121
  - lib/cancan/controller_additions.rb
120
122
  - lib/cancan/controller_resource.rb
121
123
  - lib/cancan/controller_resource_builder.rb
@@ -132,11 +134,18 @@ files:
132
134
  - lib/cancan/model_adapters/conditions_extractor.rb
133
135
  - lib/cancan/model_adapters/conditions_normalizer.rb
134
136
  - lib/cancan/model_adapters/default_adapter.rb
137
+ - lib/cancan/model_adapters/sti_normalizer.rb
138
+ - lib/cancan/model_adapters/strategies/base.rb
139
+ - lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb
140
+ - lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
141
+ - lib/cancan/model_adapters/strategies/left_join.rb
142
+ - lib/cancan/model_adapters/strategies/subquery.rb
135
143
  - lib/cancan/model_additions.rb
136
144
  - lib/cancan/parameter_validators.rb
137
145
  - lib/cancan/relevant.rb
138
146
  - lib/cancan/rule.rb
139
147
  - lib/cancan/rules_compressor.rb
148
+ - lib/cancan/sti_detector.rb
140
149
  - lib/cancan/unauthorized_message_resolver.rb
141
150
  - lib/cancan/version.rb
142
151
  - lib/cancancan.rb
@@ -146,8 +155,9 @@ files:
146
155
  homepage: https://github.com/CanCanCommunity/cancancan
147
156
  licenses:
148
157
  - MIT
149
- metadata: {}
150
- post_install_message:
158
+ metadata:
159
+ funding_uri: https://github.com/sponsors/coorasse
160
+ post_install_message:
151
161
  rdoc_options: []
152
162
  require_paths:
153
163
  - lib
@@ -162,8 +172,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
172
  - !ruby/object:Gem::Version
163
173
  version: '0'
164
174
  requirements: []
165
- rubygems_version: 3.0.6
166
- signing_key:
175
+ rubygems_version: 3.3.3
176
+ signing_key:
167
177
  specification_version: 4
168
178
  summary: Simple authorization solution for Rails.
169
179
  test_files: []