cancancan 3.1.0 → 3.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81afd3cec5dc78c4e4d9d14719482ae589ed43bf336cc1b4f9e5681dea56b99d
4
- data.tar.gz: fd23ce69481f9daf4b227b61e4e7e236abcd40d7b5f0dd01f70ca20a3706fae3
3
+ metadata.gz: f912e7e8ba7143a52b467949b374103edb0aae0b8b3dcbea4157400b6a70e7d6
4
+ data.tar.gz: 604a95ca8d9a794386810ad68a98eec86d1b6a8b73c07d64c0b8706ec5c52fa6
5
5
  SHA512:
6
- metadata.gz: 04ee2bfead0ce01e0bdc64e69fae219c221495c30950542323fc5e3d91e250e9a679863546c09db9f3a71a647cb414510bcbb92db41309d9b0b2d04f7d2a1b0e
7
- data.tar.gz: 79b4b11ef02ca50417c4e441dd8586569ed86caa4d3216fc54e1713bd09071e544b529db0babd429dd14b0efc90f59f2dfbd8a8d101a9e4d4332908f0487115b
6
+ metadata.gz: 5988dd5e020a13b020769f632a4c780f2b3a401c6064f9d4b1da7f4d3570cf18d52be603ed2fd55059b88f5b06e4dd6fb6708e893ff899e2ffbd793eddfec07a
7
+ data.tar.gz: 5c2338a84835d7d828739984925bd2906f044d8b0c0355a0a62a8fe763e2f203e579e4909ba37fdbf8f30bce26e36790a47793ab056fb39e7a6c1e683ce9e486
@@ -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,6 +17,7 @@ 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'
@@ -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) && 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,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
@@ -78,7 +78,7 @@ module CanCan
78
78
 
79
79
  def hash_condition_match?(attribute, value)
80
80
  if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
81
- attribute.any? { |element| matches_conditions_hash?(element, value) }
81
+ attribute.to_a.any? { |element| matches_conditions_hash?(element, value) }
82
82
  else
83
83
  attribute && matches_conditions_hash?(attribute, value)
84
84
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ def self.valid_accessible_by_strategies
5
+ strategies = [:left_join]
6
+ strategies << :subquery unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
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
+ @accessible_by_strategy || default_accessible_by_strategy
25
+ end
26
+
27
+ def self.default_accessible_by_strategy
28
+ if CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
29
+ # see https://github.com/CanCanCommunity/cancancan/pull/655 for where this was added
30
+ # the `subquery` strategy (from https://github.com/CanCanCommunity/cancancan/pull/619
31
+ # only works in Rails 5 and higher
32
+ :left_join
33
+ else
34
+ :subquery
35
+ end
36
+ end
37
+
38
+ def self.accessible_by_strategy=(value)
39
+ unless valid_accessible_by_strategies.include?(value)
40
+ raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}"
41
+ end
42
+
43
+ if value == :subquery && CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
44
+ raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer'
45
+ end
46
+
47
+ @accessible_by_strategy = value
48
+ end
49
+ end
@@ -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
@@ -5,7 +5,7 @@ module CanCan
5
5
  class AbstractAdapter
6
6
  def self.inherited(subclass)
7
7
  @subclasses ||= []
8
- @subclasses << subclass
8
+ @subclasses.insert(0, subclass)
9
9
  end
10
10
 
11
11
  def self.adapter_class(model_class)
@@ -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,19 @@ module CanCan
21
21
 
22
22
  private
23
23
 
24
- def build_relation(*where_conditions)
25
- if joins.present?
24
+ def build_joins_relation(relation, *where_conditions)
25
+ case CanCan.accessible_by_strategy
26
+ when :subquery
26
27
  inner = @model_class.unscoped do
27
28
  @model_class.left_joins(joins).where(*where_conditions)
28
29
  end
29
30
  @model_class.where(@model_class.primary_key => inner)
30
- else
31
- @model_class.where(*where_conditions)
31
+
32
+ when :left_join
33
+ relation.left_joins(joins).distinct
32
34
  end
33
35
  end
34
36
 
35
- # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
36
37
  def sanitize_sql(conditions)
37
38
  if conditions.is_a?(Hash)
38
39
  sanitize_sql_activerecord5(conditions)
@@ -46,11 +47,7 @@ module CanCan
46
47
  table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
47
48
  predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
48
49
 
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 ')
50
+ predicate_builder.build_from_hash(conditions.stringify_keys).map { |b| visit_nodes(b) }.join(' AND ')
54
51
  end
55
52
 
56
53
  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
@@ -16,6 +14,7 @@ module CanCan
16
14
  def initialize(model_class, rules)
17
15
  super
18
16
  @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
17
+ StiNormalizer.normalize(@compressed_rules)
19
18
  ConditionsNormalizer.normalize(model_class, @compressed_rules)
20
19
  end
21
20
 
@@ -60,6 +59,14 @@ module CanCan
60
59
  end
61
60
  end
62
61
 
62
+ def build_relation(*where_conditions)
63
+ relation = @model_class.where(*where_conditions)
64
+ return relation unless joins.present?
65
+
66
+ # subclasses must implement `build_joins_relation`
67
+ build_joins_relation(relation, *where_conditions)
68
+ end
69
+
63
70
  # Returns the associations used in conditions for the :joins option of a search.
64
71
  # See ModelAdditions#accessible_by
65
72
  def joins
@@ -0,0 +1,31 @@
1
+ # this class is responsible for detecting sti classes and creating new rules for the
2
+ # relevant subclasses, using the inheritance_column as a merger
3
+ module CanCan
4
+ module ModelAdapters
5
+ class StiNormalizer
6
+ class << self
7
+ def normalize(rules)
8
+ rules_cache = []
9
+ rules.delete_if.with_index do |rule, _index|
10
+ subjects = rule.subjects.select do |subject|
11
+ next if subject == :all || subject.descends_from_active_record?
12
+
13
+ rules_cache.push(build_rule_for_subclass(rule, subject))
14
+ true
15
+ end
16
+ subjects.length == rule.subjects.length
17
+ end
18
+ rules_cache.each { |rule| rules.push(rule) }
19
+ end
20
+
21
+ private
22
+
23
+ # create a new rule for the subclasses that links on the inheritance_column
24
+ def build_rule_for_subclass(rule, subject)
25
+ CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass,
26
+ rule.conditions.merge(subject.inheritance_column => subject.name), rule.block)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -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
@@ -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
@@ -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.2.0'.freeze
5
5
  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.1.0
4
+ version: 3.2.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: 2020-03-15 00:00:00.000000000 Z
14
+ date: 2020-12-12 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: appraisal
@@ -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,6 +134,7 @@ 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
135
138
  - lib/cancan/model_additions.rb
136
139
  - lib/cancan/parameter_validators.rb
137
140
  - lib/cancan/relevant.rb
@@ -147,7 +150,7 @@ homepage: https://github.com/CanCanCommunity/cancancan
147
150
  licenses:
148
151
  - MIT
149
152
  metadata: {}
150
- post_install_message:
153
+ post_install_message:
151
154
  rdoc_options: []
152
155
  require_paths:
153
156
  - lib
@@ -163,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
166
  version: '0'
164
167
  requirements: []
165
168
  rubygems_version: 3.0.6
166
- signing_key:
169
+ signing_key:
167
170
  specification_version: 4
168
171
  summary: Simple authorization solution for Rails.
169
172
  test_files: []