cancancan 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []