cancancan 2.2.0 → 3.5.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 +4 -4
- data/cancancan.gemspec +6 -5
- data/init.rb +2 -0
- data/lib/cancan/ability/actions.rb +2 -0
- data/lib/cancan/ability/rules.rb +20 -9
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/ability.rb +54 -24
- data/lib/cancan/class_matcher.rb +30 -0
- data/lib/cancan/conditions_matcher.rb +72 -18
- data/lib/cancan/config.rb +101 -0
- data/lib/cancan/controller_additions.rb +9 -4
- data/lib/cancan/controller_resource.rb +7 -1
- data/lib/cancan/controller_resource_builder.rb +2 -0
- data/lib/cancan/controller_resource_finder.rb +2 -0
- data/lib/cancan/controller_resource_loader.rb +4 -0
- data/lib/cancan/controller_resource_name_finder.rb +2 -0
- data/lib/cancan/controller_resource_sanitizer.rb +2 -0
- data/lib/cancan/exceptions.rb +21 -2
- data/lib/cancan/matchers.rb +7 -2
- data/lib/cancan/model_adapters/abstract_adapter.rb +22 -1
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +26 -25
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +17 -26
- data/lib/cancan/model_adapters/active_record_adapter.rb +134 -45
- data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
- data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
- data/lib/cancan/model_adapters/strategies/base.rb +40 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
- data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
- data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
- data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
- data/lib/cancan/model_additions.rb +6 -2
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/relevant.rb +29 -0
- data/lib/cancan/rule.rb +76 -20
- data/lib/cancan/rules_compressor.rb +23 -0
- data/lib/cancan/sti_detector.rb +12 -0
- data/lib/cancan/unauthorized_message_resolver.rb +24 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancan.rb +13 -0
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +3 -1
- data/lib/generators/cancan/ability/templates/ability.rb +9 -9
- metadata +39 -24
- data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +0 -39
@@ -0,0 +1,49 @@
|
|
1
|
+
# this class is responsible of normalizing the hash of conditions
|
2
|
+
# by exploding has_many through associations
|
3
|
+
# when a condition is defined with an has_many through association this is exploded in all its parts
|
4
|
+
# TODO: it could identify STI and normalize it
|
5
|
+
module CanCan
|
6
|
+
module ModelAdapters
|
7
|
+
class ConditionsNormalizer
|
8
|
+
class << self
|
9
|
+
def normalize(model_class, rules)
|
10
|
+
rules.each { |rule| rule.conditions = normalize_conditions(model_class, rule.conditions) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def normalize_conditions(model_class, conditions)
|
14
|
+
return conditions unless conditions.is_a? Hash
|
15
|
+
|
16
|
+
conditions.each_with_object({}) do |(key, value), result_hash|
|
17
|
+
if value.is_a? Hash
|
18
|
+
result_hash.merge!(calculate_result_hash(model_class, key, value))
|
19
|
+
else
|
20
|
+
result_hash[key] = value
|
21
|
+
end
|
22
|
+
result_hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def calculate_result_hash(model_class, key, value)
|
29
|
+
reflection = model_class.reflect_on_association(key)
|
30
|
+
unless reflection
|
31
|
+
raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'"
|
32
|
+
end
|
33
|
+
|
34
|
+
if normalizable_association? reflection
|
35
|
+
key = reflection.options[:through]
|
36
|
+
value = { reflection.source_reflection_name => value }
|
37
|
+
reflection = model_class.reflect_on_association(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
{ key => normalize_conditions(reflection.klass.name.constantize, value) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalizable_association?(reflection)
|
44
|
+
reflection.options[:through].present? && !reflection.options[:source_type].present?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
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,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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
# This module adds the accessible_by class method to a model. It is included in the model adapters.
|
3
5
|
module ModelAdditions
|
@@ -18,8 +20,10 @@ module CanCan
|
|
18
20
|
# @articles = Article.accessible_by(current_ability, :update)
|
19
21
|
#
|
20
22
|
# Here only the articles which the user can update are returned.
|
21
|
-
def accessible_by(ability, action = :index)
|
22
|
-
|
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
|
23
27
|
end
|
24
28
|
end
|
25
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,33 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'conditions_matcher.rb'
|
4
|
+
require_relative 'class_matcher.rb'
|
5
|
+
require_relative 'relevant.rb'
|
6
|
+
|
2
7
|
module CanCan
|
3
8
|
# This class is used internally and should only be called through Ability.
|
4
9
|
# it holds the information about a "can" call made on Ability and provides
|
5
10
|
# helpful methods to determine permission checking and conditions hash generation.
|
6
11
|
class Rule # :nodoc:
|
7
12
|
include ConditionsMatcher
|
8
|
-
|
9
|
-
|
13
|
+
include Relevant
|
14
|
+
include ParameterValidators
|
15
|
+
attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
|
16
|
+
attr_writer :expanded_actions, :conditions
|
10
17
|
|
11
18
|
# The first argument when initializing is the base_behavior which is a true/false
|
12
19
|
# value. True for "can" and false for "cannot". The next two arguments are the action
|
13
20
|
# and subject respectively (such as :read, @project). The third argument is a hash
|
14
21
|
# of conditions and the last one is the block passed to the "can" call.
|
15
|
-
def initialize(base_behavior, action, subject,
|
16
|
-
|
17
|
-
|
18
|
-
|
22
|
+
def initialize(base_behavior, action, subject, *extra_args, &block)
|
23
|
+
# for backwards compatibility, attributes are an optional parameter. Check if
|
24
|
+
# attributes were passed or are actually conditions
|
25
|
+
attributes, extra_args = parse_attributes_from_extra_args(extra_args)
|
26
|
+
condition_and_block_check(extra_args, block, action, subject)
|
19
27
|
@match_all = action.nil? && subject.nil?
|
28
|
+
raise Error, "Subject is required for #{action}" if action && subject.nil?
|
29
|
+
|
20
30
|
@base_behavior = base_behavior
|
21
|
-
@actions =
|
22
|
-
@subjects =
|
23
|
-
@
|
31
|
+
@actions = wrap(action)
|
32
|
+
@subjects = wrap(subject)
|
33
|
+
@attributes = wrap(attributes)
|
34
|
+
@conditions = extra_args || {}
|
24
35
|
@block = block
|
25
36
|
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
def inspect
|
39
|
+
repr = "#<#{self.class.name}"
|
40
|
+
repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
|
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
|
+
|
48
|
+
repr + '>'
|
49
|
+
end
|
50
|
+
|
51
|
+
def can_rule?
|
52
|
+
base_behavior
|
53
|
+
end
|
54
|
+
|
55
|
+
def cannot_catch_all?
|
56
|
+
!can_rule? && catch_all?
|
57
|
+
end
|
58
|
+
|
59
|
+
def catch_all?
|
60
|
+
(with_scope? && @conditions.where_values_hash.empty?) ||
|
61
|
+
(!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
|
31
62
|
end
|
32
63
|
|
33
64
|
def only_block?
|
@@ -38,9 +69,8 @@ module CanCan
|
|
38
69
|
@block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
|
39
70
|
end
|
40
71
|
|
41
|
-
def
|
42
|
-
|
43
|
-
(!@conditions.keys.first.is_a? Symbol)
|
72
|
+
def with_scope?
|
73
|
+
defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
|
44
74
|
end
|
45
75
|
|
46
76
|
def associations_hash(conditions = @conditions)
|
@@ -63,6 +93,13 @@ module CanCan
|
|
63
93
|
attributes
|
64
94
|
end
|
65
95
|
|
96
|
+
def matches_attributes?(attribute)
|
97
|
+
return true if @attributes.empty?
|
98
|
+
return @base_behavior if attribute.nil?
|
99
|
+
|
100
|
+
@attributes.include?(attribute.to_sym)
|
101
|
+
end
|
102
|
+
|
66
103
|
private
|
67
104
|
|
68
105
|
def matches_action?(action)
|
@@ -74,10 +111,29 @@ module CanCan
|
|
74
111
|
end
|
75
112
|
|
76
113
|
def matches_subject_class?(subject)
|
77
|
-
@subjects
|
78
|
-
|
79
|
-
|
80
|
-
|
114
|
+
SubjectClassMatcher.matches_subject_class?(@subjects, subject)
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_attributes_from_extra_args(args)
|
118
|
+
attributes = args.shift if valid_attribute_param?(args.first)
|
119
|
+
extra_args = args.shift
|
120
|
+
[attributes, extra_args]
|
121
|
+
end
|
122
|
+
|
123
|
+
def condition_and_block_check(conditions, block, action, subject)
|
124
|
+
return unless conditions.is_a?(Hash) && block
|
125
|
+
|
126
|
+
raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
|
127
|
+
"Check \":#{action} #{subject}\" ability."
|
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]
|
81
137
|
end
|
82
138
|
end
|
83
139
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'conditions_matcher.rb'
|
4
|
+
module CanCan
|
5
|
+
class RulesCompressor
|
6
|
+
attr_reader :initial_rules, :rules_collapsed
|
7
|
+
|
8
|
+
def initialize(rules)
|
9
|
+
@initial_rules = rules
|
10
|
+
@rules_collapsed = compress(@initial_rules)
|
11
|
+
end
|
12
|
+
|
13
|
+
def compress(array)
|
14
|
+
idx = array.rindex(&:catch_all?)
|
15
|
+
return array unless idx
|
16
|
+
|
17
|
+
value = array[idx]
|
18
|
+
array[idx..-1]
|
19
|
+
.drop_while { |n| n.base_behavior == value.base_behavior }
|
20
|
+
.tap { |a| a.unshift(value) unless value.cannot_catch_all? }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module UnauthorizedMessageResolver
|
5
|
+
def unauthorized_message(action, subject)
|
6
|
+
subject = subject.values.last if subject.is_a?(Hash)
|
7
|
+
keys = unauthorized_message_keys(action, subject)
|
8
|
+
variables = {}
|
9
|
+
variables[:action] = I18n.translate("actions.#{action}", default: action.to_s)
|
10
|
+
variables[:subject] = translate_subject(subject)
|
11
|
+
message = I18n.translate(keys.shift, **variables.merge(scope: :unauthorized, default: keys + ['']))
|
12
|
+
message.blank? ? nil : message
|
13
|
+
end
|
14
|
+
|
15
|
+
def translate_subject(subject)
|
16
|
+
klass = (subject.class == Class ? subject : subject.class)
|
17
|
+
if klass.respond_to?(:model_name)
|
18
|
+
klass.model_name.human
|
19
|
+
else
|
20
|
+
klass.to_s.underscore.humanize.downcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cancan/version.rb
CHANGED
data/lib/cancan.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cancan/version'
|
4
|
+
require 'cancan/config'
|
5
|
+
require 'cancan/parameter_validators'
|
2
6
|
require 'cancan/ability'
|
3
7
|
require 'cancan/rule'
|
4
8
|
require 'cancan/controller_resource'
|
@@ -8,9 +12,18 @@ require 'cancan/exceptions'
|
|
8
12
|
|
9
13
|
require 'cancan/model_adapters/abstract_adapter'
|
10
14
|
require 'cancan/model_adapters/default_adapter'
|
15
|
+
require 'cancan/rules_compressor'
|
11
16
|
|
12
17
|
if defined? ActiveRecord
|
18
|
+
require 'cancan/model_adapters/conditions_extractor'
|
19
|
+
require 'cancan/model_adapters/conditions_normalizer'
|
20
|
+
require 'cancan/model_adapters/sti_normalizer'
|
13
21
|
require 'cancan/model_adapters/active_record_adapter'
|
14
22
|
require 'cancan/model_adapters/active_record_4_adapter'
|
15
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'
|
16
29
|
end
|
data/lib/cancancan.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Cancan
|
2
4
|
module Generators
|
3
5
|
class AbilityGenerator < Rails::Generators::Base
|
4
|
-
source_root File.expand_path('
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
5
7
|
|
6
8
|
def generate_ability
|
7
9
|
copy_file 'ability.rb', 'app/models/ability.rb'
|
@@ -1,15 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Ability
|
2
4
|
include CanCan::Ability
|
3
5
|
|
4
6
|
def initialize(user)
|
5
|
-
# Define abilities for the
|
7
|
+
# Define abilities for the user here. For example:
|
6
8
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# can :read, :all
|
12
|
-
# end
|
9
|
+
# return unless user.present?
|
10
|
+
# can :read, :all
|
11
|
+
# return unless user.admin?
|
12
|
+
# can :manage, :all
|
13
13
|
#
|
14
14
|
# The first argument to `can` is the action you are giving the user
|
15
15
|
# permission to do.
|
@@ -24,9 +24,9 @@ class Ability
|
|
24
24
|
# objects.
|
25
25
|
# For example, here the user can only update published articles.
|
26
26
|
#
|
27
|
-
# can :update, Article, :
|
27
|
+
# can :update, Article, published: true
|
28
28
|
#
|
29
29
|
# See the wiki for details:
|
30
|
-
# https://github.com/CanCanCommunity/cancancan/
|
30
|
+
# https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md
|
31
31
|
end
|
32
32
|
end
|