cancancan 3.0.1 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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.rb +5 -1
  5. data/lib/cancan/class_matcher.rb +30 -0
  6. data/lib/cancan/conditions_matcher.rb +54 -10
  7. data/lib/cancan/config.rb +101 -0
  8. data/lib/cancan/controller_additions.rb +1 -1
  9. data/lib/cancan/controller_resource.rb +1 -1
  10. data/lib/cancan/exceptions.rb +8 -0
  11. data/lib/cancan/matchers.rb +5 -3
  12. data/lib/cancan/model_adapters/abstract_adapter.rb +20 -1
  13. data/lib/cancan/model_adapters/active_record_4_adapter.rb +2 -4
  14. data/lib/cancan/model_adapters/active_record_5_adapter.rb +8 -10
  15. data/lib/cancan/model_adapters/active_record_adapter.rb +84 -4
  16. data/lib/cancan/model_adapters/conditions_extractor.rb +4 -4
  17. data/lib/cancan/model_adapters/conditions_normalizer.rb +6 -2
  18. data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  19. data/lib/cancan/model_adapters/strategies/base.rb +40 -0
  20. data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  21. data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  22. data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  23. data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  24. data/lib/cancan/model_additions.rb +4 -2
  25. data/lib/cancan/relevant.rb +29 -0
  26. data/lib/cancan/rule.rb +28 -22
  27. data/lib/cancan/sti_detector.rb +12 -0
  28. data/lib/cancan/unauthorized_message_resolver.rb +4 -2
  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 +20 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec5d008dea72d99ee804c69a710c9b474e39c8085cf23317cfa031f0ef3acd95
4
- data.tar.gz: dfc2e52da361d4405ced63b2a229dbb34e80885abebad71bb531df4999d25fa1
3
+ metadata.gz: bebbba60e68460ec234fc11e8d3cf0414e578a56c0347862c673396eb917dff9
4
+ data.tar.gz: bb07244a17dcf45d1852cf6677864084c2f0db5630ea9b72bdcc0c6055b5c4b6
5
5
  SHA512:
6
- metadata.gz: 43d51d793d5dad4d3e0b21f106c09ff3abcbb8862bbb6377b479b9748191af470bdd45d6005126a5622626c4d3a16e7f3242889a5ddf2a6da1b74c3f8f553edf
7
- data.tar.gz: ad7df527d7267c980a229a81fde2d6e5d03129260786f808ffc8c0d4016db7d29d9fd275e18a608c7cf1d474108638d4e3bdf4ac2ab56c18bc946270f39f830e
6
+ metadata.gz: be9f2b03ae43651ea70a451b97a44fd6ec6e0a09ca444ddf625b91ae3815a245e0669bb80b4d3b0687ca327bc0c4fe81028f7736cb6493ef636b43a4140f4f49
7
+ data.tar.gz: db75441929e737d12699f57324d031e894b5d2cdbe1555451857977c42b0ef28148cc63bb718981c32ac08bafd2873623d2151ba8e98c442f217b3f4affecda9
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
@@ -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)
54
68
  end
55
69
 
56
- def matches_all_conditions?(adapter, conditions, subject)
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
78
+ end
79
+
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)
@@ -97,7 +138,10 @@ module CanCan
97
138
  end
98
139
 
99
140
  def conditions_empty?
100
- @conditions == {} || @conditions.nil?
141
+ # @conditions might be an ActiveRecord::Associations::CollectionProxy
142
+ # which it's `==` implementation will fetch all records for comparison
143
+
144
+ (@conditions.is_a?(Hash) && @conditions == {}) || @conditions.nil?
101
145
  end
102
146
  end
103
147
  end
@@ -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
@@ -264,7 +264,7 @@ module CanCan
264
264
  next if options[:unless] && controller.send(options[:unless])
265
265
 
266
266
  raise AuthorizationNotPerformed,
267
- 'This action failed the check_authorization because it does not authorize_resource. '\
267
+ 'This action failed the check_authorization because it does not authorize_resource. ' \
268
268
  'Add skip_authorization_check to bypass this check.'
269
269
  end
270
270
 
@@ -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,13 +21,15 @@ module CanCan
21
21
 
22
22
  private
23
23
 
24
- def build_relation(*where_conditions)
25
- relation = @model_class.where(*where_conditions)
26
- relation = relation.left_joins(joins).distinct if joins.present?
27
- relation
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)
28
31
  end
29
32
 
30
- # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
31
33
  def sanitize_sql(conditions)
32
34
  if conditions.is_a?(Hash)
33
35
  sanitize_sql_activerecord5(conditions)
@@ -41,11 +43,7 @@ module CanCan
41
43
  table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
42
44
  predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
43
45
 
44
- conditions = predicate_builder.resolve_column_aliases(conditions)
45
-
46
- conditions.stringify_keys!
47
-
48
- 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 ')
49
47
  end
50
48
 
51
49
  def visit_nodes(node)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'conditions_extractor.rb'
4
- require 'cancan/rules_compressor'
5
3
  module CanCan
6
4
  module ModelAdapters
7
5
  class ActiveRecordAdapter < AbstractAdapter
@@ -13,12 +11,86 @@ module CanCan
13
11
  Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
14
12
  end
15
13
 
14
+ attr_reader :compressed_rules
15
+
16
16
  def initialize(model_class, rules)
17
17
  super
18
- @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
18
+ @compressed_rules = if CanCan.rules_compressor_enabled
19
+ RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
20
+ else
21
+ @rules
22
+ end
23
+ StiNormalizer.normalize(@compressed_rules)
19
24
  ConditionsNormalizer.normalize(model_class, @compressed_rules)
20
25
  end
21
26
 
27
+ class << self
28
+ # When belongs_to parent_id is a condition for a model,
29
+ # we want to check the parent when testing ability for a hash {parent => model}
30
+ def override_nested_subject_conditions_matching?(parent, child, all_conditions)
31
+ parent_child_conditions(parent, child, all_conditions).present?
32
+ end
33
+
34
+ # parent_id condition can be an array of integer or one integer, we check the parent against this
35
+ def nested_subject_matches_conditions?(parent, child, all_conditions)
36
+ id_condition = parent_child_conditions(parent, child, all_conditions)
37
+ return id_condition.include?(parent.id) if id_condition.is_a? Array
38
+ return id_condition == parent.id if id_condition.is_a? Integer
39
+
40
+ false
41
+ end
42
+
43
+ def parent_child_conditions(parent, child, all_conditions)
44
+ child_class = child.is_a?(Class) ? child : child.class
45
+ parent_class = parent.is_a?(Class) ? parent : parent.class
46
+
47
+ foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association|
48
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
49
+ !association.polymorphic? && association.klass == parent.class
50
+ end&.foreign_key&.to_sym
51
+
52
+ # Search again in case of polymorphic associations, this time matching on the :has_many side
53
+ # via the :as option, as well as klass
54
+ foreign_key ||= parent_class.reflect_on_all_associations(:has_many).find do |has_many_assoc|
55
+ !matching_parent_child_polymorphic_association(has_many_assoc, child_class).nil?
56
+ end&.foreign_key&.to_sym
57
+
58
+ foreign_key.nil? ? nil : all_conditions[foreign_key]
59
+ end
60
+
61
+ def matching_parent_child_polymorphic_association(parent_assoc, child_class)
62
+ return nil unless parent_assoc.klass == child_class
63
+ return nil if parent_assoc&.options[:as].nil?
64
+
65
+ child_class.reflect_on_all_associations(:belongs_to).find do |child_assoc|
66
+ # Only match this way for polymorphic associations
67
+ child_assoc.polymorphic? && child_assoc.name == parent_assoc.options[:as]
68
+ end
69
+ end
70
+
71
+ def child_association_to_parent(parent, child)
72
+ child_class = child.is_a?(Class) ? child : child.class
73
+ parent_class = parent.is_a?(Class) ? parent : parent.class
74
+
75
+ association = child_class.reflect_on_all_associations(:belongs_to).find do |association|
76
+ # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
77
+ !association.polymorphic? && association.klass == parent.class
78
+ end
79
+
80
+ return association unless association.nil?
81
+
82
+ parent_class.reflect_on_all_associations(:has_many).each do |has_many_assoc|
83
+ association ||= matching_parent_child_polymorphic_association(has_many_assoc, child_class)
84
+ end
85
+
86
+ association
87
+ end
88
+
89
+ def parent_condition_name(parent, child)
90
+ child_association_to_parent(parent, child)&.name || parent.class.name.downcase.to_sym
91
+ end
92
+ end
93
+
22
94
  # Returns conditions intended to be used inside a database query. Normally you will not call this
23
95
  # method directly, but instead go through ModelAdditions#accessible_by.
24
96
  #
@@ -60,6 +132,14 @@ module CanCan
60
132
  end
61
133
  end
62
134
 
135
+ def build_relation(*where_conditions)
136
+ relation = @model_class.where(*where_conditions)
137
+ return relation unless joins.present?
138
+
139
+ # subclasses must implement `build_joins_relation`
140
+ build_joins_relation(relation, *where_conditions)
141
+ end
142
+
63
143
  # Returns the associations used in conditions for the :joins option of a search.
64
144
  # See ModelAdditions#accessible_by
65
145
  def joins
@@ -99,7 +179,7 @@ module CanCan
99
179
  def raise_override_scope_error
100
180
  rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
101
181
  raise Error,
102
- 'Unable to merge an Active Record scope with other conditions. '\
182
+ 'Unable to merge an Active Record scope with other conditions. ' \
103
183
  "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
104
184
  end
105
185
 
@@ -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
@@ -31,7 +31,7 @@ module CanCan
31
31
  raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'"
32
32
  end
33
33
 
34
- if reflection.options[:through].present?
34
+ if normalizable_association? reflection
35
35
  key = reflection.options[:through]
36
36
  value = { reflection.source_reflection_name => value }
37
37
  reflection = model_class.reflect_on_association(key)
@@ -39,6 +39,10 @@ module CanCan
39
39
 
40
40
  { key => normalize_conditions(reflection.klass.name.constantize, value) }
41
41
  end
42
+
43
+ def normalizable_association?(reflection)
44
+ reflection.options[:through].present? && !reflection.options[:source_type].present?
45
+ end
42
46
  end
43
47
  end
44
48
  end
@@ -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
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module Relevant
5
+ # Matches both the action, subject, and attribute, not necessarily the conditions
6
+ def relevant?(action, subject)
7
+ subject = subject.values.first if subject.class == Hash
8
+ @match_all || (matches_action?(action) && matches_subject?(subject))
9
+ end
10
+
11
+ private
12
+
13
+ def matches_action?(action)
14
+ @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
15
+ end
16
+
17
+ def matches_subject?(subject)
18
+ @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
19
+ end
20
+
21
+ def matches_subject_class?(subject)
22
+ @subjects.any? do |sub|
23
+ sub.is_a?(Module) && (subject.is_a?(sub) ||
24
+ subject.class.to_s == sub.to_s ||
25
+ (subject.is_a?(Module) && subject.ancestors.include?(sub)))
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/cancan/rule.rb CHANGED
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'conditions_matcher.rb'
4
+ require_relative 'class_matcher.rb'
5
+ require_relative 'relevant.rb'
6
+
4
7
  module CanCan
5
8
  # This class is used internally and should only be called through Ability.
6
9
  # it holds the information about a "can" call made on Ability and provides
7
10
  # helpful methods to determine permission checking and conditions hash generation.
8
11
  class Rule # :nodoc:
9
12
  include ConditionsMatcher
13
+ include Relevant
10
14
  include ParameterValidators
11
- attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
15
+ attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
12
16
  attr_writer :expanded_actions, :conditions
13
17
 
14
18
  # The first argument when initializing is the base_behavior which is a true/false
@@ -24,9 +28,9 @@ module CanCan
24
28
  raise Error, "Subject is required for #{action}" if action && subject.nil?
25
29
 
26
30
  @base_behavior = base_behavior
27
- @actions = Array(action)
28
- @subjects = Array(subject)
29
- @attributes = Array(attributes)
31
+ @actions = wrap(action)
32
+ @subjects = wrap(subject)
33
+ @attributes = wrap(attributes)
30
34
  @conditions = extra_args || {}
31
35
  @block = block
32
36
  end
@@ -34,11 +38,13 @@ module CanCan
34
38
  def inspect
35
39
  repr = "#<#{self.class.name}"
36
40
  repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
37
- repr += if with_scope?
38
- ", #{@conditions.where_values_hash}"
39
- elsif [Hash, String].include?(@conditions.class)
40
- ", #{@conditions.inspect}"
41
- end
41
+
42
+ if with_scope?
43
+ repr += ", #{@conditions.where_values_hash}"
44
+ elsif [Hash, String].include?(@conditions.class)
45
+ repr += ", #{@conditions.inspect}"
46
+ end
47
+
42
48
  repr + '>'
43
49
  end
44
50
 
@@ -55,12 +61,6 @@ module CanCan
55
61
  (!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
56
62
  end
57
63
 
58
- # Matches both the action, subject, and attribute, not necessarily the conditions
59
- def relevant?(action, subject)
60
- subject = subject.values.first if subject.class == Hash
61
- @match_all || (matches_action?(action) && matches_subject?(subject))
62
- end
63
-
64
64
  def only_block?
65
65
  conditions_empty? && @block
66
66
  end
@@ -70,7 +70,7 @@ module CanCan
70
70
  end
71
71
 
72
72
  def with_scope?
73
- @conditions.is_a?(ActiveRecord::Relation)
73
+ defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
74
74
  end
75
75
 
76
76
  def associations_hash(conditions = @conditions)
@@ -111,11 +111,7 @@ module CanCan
111
111
  end
112
112
 
113
113
  def matches_subject_class?(subject)
114
- @subjects.any? do |sub|
115
- sub.is_a?(Module) && (subject.is_a?(sub) ||
116
- subject.class.to_s == sub.to_s ||
117
- (subject.is_a?(Module) && subject.ancestors.include?(sub)))
118
- end
114
+ SubjectClassMatcher.matches_subject_class?(@subjects, subject)
119
115
  end
120
116
 
121
117
  def parse_attributes_from_extra_args(args)
@@ -127,8 +123,18 @@ module CanCan
127
123
  def condition_and_block_check(conditions, block, action, subject)
128
124
  return unless conditions.is_a?(Hash) && block
129
125
 
130
- 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. ' \
131
127
  "Check \":#{action} #{subject}\" ability."
132
128
  end
129
+
130
+ def wrap(object)
131
+ if object.nil?
132
+ []
133
+ elsif object.respond_to?(:to_ary)
134
+ object.to_ary || [object]
135
+ else
136
+ [object]
137
+ end
138
+ end
133
139
  end
134
140
  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
@@ -3,10 +3,12 @@
3
3
  module CanCan
4
4
  module UnauthorizedMessageResolver
5
5
  def unauthorized_message(action, subject)
6
+ subject = subject.values.last if subject.is_a?(Hash)
6
7
  keys = unauthorized_message_keys(action, subject)
7
- variables = { action: action.to_s }
8
+ variables = {}
9
+ variables[:action] = I18n.translate("actions.#{action}", default: action.to_s)
8
10
  variables[:subject] = translate_subject(subject)
9
- message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
11
+ message = I18n.translate(keys.shift, **variables.merge(scope: :unauthorized, default: keys + ['']))
10
12
  message.blank? ? nil : message
11
13
  end
12
14
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CanCan
4
- VERSION = '3.0.1'.freeze
4
+ VERSION = '3.5.0'.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,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancancan
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.5.0
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: 2019-04-16 00:00:00.000000000 Z
14
+ date: 2023-03-05 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: appraisal
@@ -93,14 +93,14 @@ dependencies:
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,10 +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
145
+ - lib/cancan/relevant.rb
137
146
  - lib/cancan/rule.rb
138
147
  - lib/cancan/rules_compressor.rb
148
+ - lib/cancan/sti_detector.rb
139
149
  - lib/cancan/unauthorized_message_resolver.rb
140
150
  - lib/cancan/version.rb
141
151
  - lib/cancancan.rb
@@ -145,8 +155,9 @@ files:
145
155
  homepage: https://github.com/CanCanCommunity/cancancan
146
156
  licenses:
147
157
  - MIT
148
- metadata: {}
149
- post_install_message:
158
+ metadata:
159
+ funding_uri: https://github.com/sponsors/coorasse
160
+ post_install_message:
150
161
  rdoc_options: []
151
162
  require_paths:
152
163
  - lib
@@ -161,9 +172,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
172
  - !ruby/object:Gem::Version
162
173
  version: '0'
163
174
  requirements: []
164
- rubyforge_project:
165
- rubygems_version: 2.7.6
166
- signing_key:
175
+ rubygems_version: 3.3.7
176
+ signing_key:
167
177
  specification_version: 4
168
178
  summary: Simple authorization solution for Rails.
169
179
  test_files: []