cancancan 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cancan.rb +1 -1
- data/lib/cancan/ability.rb +13 -175
- data/lib/cancan/ability/actions.rb +91 -0
- data/lib/cancan/ability/rules.rb +85 -0
- data/lib/cancan/conditions_matcher.rb +93 -0
- data/lib/cancan/controller_resource.rb +22 -207
- data/lib/cancan/controller_resource_builder.rb +24 -0
- data/lib/cancan/controller_resource_finder.rb +35 -0
- data/lib/cancan/controller_resource_loader.rb +116 -0
- data/lib/cancan/controller_resource_name_finder.rb +21 -0
- data/lib/cancan/controller_resource_sanitizer.rb +30 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +55 -70
- data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +39 -0
- data/lib/cancan/rule.rb +4 -83
- data/lib/cancan/version.rb +1 -1
- metadata +11 -2
@@ -0,0 +1,21 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ControllerResourceNameFinder
|
3
|
+
protected
|
4
|
+
|
5
|
+
def name_from_controller
|
6
|
+
@params[:controller].split('/').last.singularize
|
7
|
+
end
|
8
|
+
|
9
|
+
def namespaced_name
|
10
|
+
[namespace, name].join('/').singularize.camelize.safe_constantize || name
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
@name || name_from_controller
|
15
|
+
end
|
16
|
+
|
17
|
+
def namespace
|
18
|
+
@params[:controller].split('/')[0..-2]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ControllerResourceSanitizer
|
3
|
+
protected
|
4
|
+
|
5
|
+
def sanitize_parameters
|
6
|
+
case params_method
|
7
|
+
when Symbol
|
8
|
+
@controller.send(params_method)
|
9
|
+
when String
|
10
|
+
@controller.instance_eval(params_method)
|
11
|
+
when Proc
|
12
|
+
params_method.call(@controller)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def params_methods
|
17
|
+
methods = ["#{@params[:action]}_params".to_sym, "#{name}_params".to_sym, :resource_params]
|
18
|
+
methods.unshift(@options[:param_method]) if @options[:param_method].present?
|
19
|
+
methods
|
20
|
+
end
|
21
|
+
|
22
|
+
def params_method
|
23
|
+
params_methods.each do |method|
|
24
|
+
return method if (method.is_a?(Symbol) && @controller.respond_to?(method, true)) ||
|
25
|
+
method.is_a?(String) || method.is_a?(Proc)
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require_relative 'can_can/model_adapters/active_record_adapter/joins.rb'
|
1
2
|
module CanCan
|
2
3
|
module ModelAdapters
|
3
4
|
module ActiveRecordAdapter
|
5
|
+
include CanCan::ModelAdapters::ActiveRecordAdapter::Joins
|
6
|
+
|
4
7
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
5
8
|
# method directly, but instead go through ModelAdditions#accessible_by.
|
6
9
|
#
|
@@ -21,54 +24,51 @@ module CanCan
|
|
21
24
|
# Return the conditions directly if there's just one definition
|
22
25
|
tableized_conditions(@rules.first.conditions).dup
|
23
26
|
else
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
extract_multiple_conditions
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_multiple_conditions
|
32
|
+
@rules.reverse.inject(false_sql) do |sql, rule|
|
33
|
+
merge_conditions(sql, tableized_conditions(rule.conditions).dup, rule.base_behavior)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
30
37
|
def tableized_conditions(conditions, model_class = @model_class)
|
31
38
|
return conditions unless conditions.is_a? Hash
|
32
39
|
conditions.each_with_object({}) do |(name, value), result_hash|
|
33
|
-
|
34
|
-
value = value.dup
|
35
|
-
association_class = model_class.reflect_on_association(name).klass.name.constantize
|
36
|
-
nested_resulted = value.each_with_object({}) do |(k, v), nested|
|
37
|
-
if v.is_a? Hash
|
38
|
-
value.delete(k)
|
39
|
-
nested[k] = v
|
40
|
-
else
|
41
|
-
result_hash[model_class.reflect_on_association(name).table_name.to_sym] = value
|
42
|
-
end
|
43
|
-
nested
|
44
|
-
end
|
45
|
-
result_hash.merge!(tableized_conditions(nested_resulted, association_class))
|
46
|
-
else
|
47
|
-
result_hash[name] = value
|
48
|
-
end
|
49
|
-
result_hash
|
40
|
+
calculate_result_hash(model_class, name, result_hash, value)
|
50
41
|
end
|
51
42
|
end
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
44
|
+
def calculate_result_hash(model_class, name, result_hash, value)
|
45
|
+
if value.is_a? Hash
|
46
|
+
association_class = model_class.reflect_on_association(name).klass.name.constantize
|
47
|
+
nested_resulted = calculate_nested(model_class, name, result_hash, value.dup)
|
48
|
+
result_hash.merge!(tableized_conditions(nested_resulted, association_class))
|
49
|
+
else
|
50
|
+
result_hash[name] = value
|
51
|
+
end
|
52
|
+
result_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def calculate_nested(model_class, name, result_hash, value)
|
56
|
+
value.each_with_object({}) do |(k, v), nested|
|
57
|
+
if v.is_a? Hash
|
58
|
+
value.delete(k)
|
59
|
+
nested[k] = v
|
60
|
+
else
|
61
|
+
result_hash[model_class.reflect_on_association(name).table_name.to_sym] = value
|
62
|
+
end
|
63
|
+
nested
|
59
64
|
end
|
60
|
-
clean_joins(joins_hash) unless joins_hash.empty?
|
61
65
|
end
|
62
66
|
|
63
67
|
def database_records
|
64
68
|
if override_scope
|
65
69
|
@model_class.where(nil).merge(override_scope)
|
66
70
|
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
67
|
-
|
68
|
-
build_relation(conditions)
|
69
|
-
else
|
70
|
-
build_relation(*@rules.map(&:conditions))
|
71
|
-
end
|
71
|
+
mergeable_conditions? ? build_relation(conditions) : build_relation(*@rules.map(&:conditions))
|
72
72
|
else
|
73
73
|
@model_class.all(conditions: conditions, joins: joins)
|
74
74
|
end
|
@@ -82,30 +82,35 @@ module CanCan
|
|
82
82
|
|
83
83
|
def override_scope
|
84
84
|
conditions = @rules.map(&:conditions).compact
|
85
|
-
return unless
|
86
|
-
if conditions.size == 1
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
85
|
+
return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
|
86
|
+
return conditions.first if conditions.size == 1
|
87
|
+
raise_override_scope_error
|
88
|
+
end
|
89
|
+
|
90
|
+
def raise_override_scope_error
|
91
|
+
rule_found = @rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
|
92
|
+
raise Error,
|
93
|
+
'Unable to merge an Active Record scope with other conditions. '\
|
94
|
+
"Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
|
94
95
|
end
|
95
96
|
|
96
97
|
def merge_conditions(sql, conditions_hash, behavior)
|
97
98
|
if conditions_hash.blank?
|
98
99
|
behavior ? true_sql : false_sql
|
99
100
|
else
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
101
|
+
merge_non_empty_conditions(behavior, conditions_hash, sql)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def merge_non_empty_conditions(behavior, conditions_hash, sql)
|
106
|
+
conditions = sanitize_sql(conditions_hash)
|
107
|
+
case sql
|
108
|
+
when true_sql
|
109
|
+
behavior ? true_sql : "not (#{conditions})"
|
110
|
+
when false_sql
|
111
|
+
behavior ? conditions : false_sql
|
112
|
+
else
|
113
|
+
behavior ? "(#{conditions}) OR (#{sql})" : "not (#{conditions}) AND (#{sql})"
|
109
114
|
end
|
110
115
|
end
|
111
116
|
|
@@ -120,26 +125,6 @@ module CanCan
|
|
120
125
|
def sanitize_sql(conditions)
|
121
126
|
@model_class.send(:sanitize_sql, conditions)
|
122
127
|
end
|
123
|
-
|
124
|
-
# Takes two hashes and does a deep merge.
|
125
|
-
def merge_joins(base, add)
|
126
|
-
add.each do |name, nested|
|
127
|
-
if base[name].is_a?(Hash)
|
128
|
-
merge_joins(base[name], nested) unless nested.empty?
|
129
|
-
else
|
130
|
-
base[name] = nested
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Removes empty hashes and moves everything into arrays.
|
136
|
-
def clean_joins(joins_hash)
|
137
|
-
joins = []
|
138
|
-
joins_hash.each do |name, nested|
|
139
|
-
joins << (nested.empty? ? name : { name => clean_joins(nested) })
|
140
|
-
end
|
141
|
-
joins
|
142
|
-
end
|
143
128
|
end
|
144
129
|
end
|
145
130
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module CanCan
|
2
|
+
module ModelAdapters
|
3
|
+
module ActiveRecordAdapter
|
4
|
+
module Joins
|
5
|
+
# Returns the associations used in conditions for the :joins option of a search.
|
6
|
+
# See ModelAdditions#accessible_by
|
7
|
+
def joins
|
8
|
+
joins_hash = {}
|
9
|
+
@rules.each do |rule|
|
10
|
+
merge_joins(joins_hash, rule.associations_hash)
|
11
|
+
end
|
12
|
+
clean_joins(joins_hash) unless joins_hash.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Removes empty hashes and moves everything into arrays.
|
18
|
+
def clean_joins(joins_hash)
|
19
|
+
joins = []
|
20
|
+
joins_hash.each do |name, nested|
|
21
|
+
joins << (nested.empty? ? name : { name => clean_joins(nested) })
|
22
|
+
end
|
23
|
+
joins
|
24
|
+
end
|
25
|
+
|
26
|
+
# Takes two hashes and does a deep merge.
|
27
|
+
def merge_joins(base, add)
|
28
|
+
add.each do |name, nested|
|
29
|
+
if base[name].is_a?(Hash)
|
30
|
+
merge_joins(base[name], nested) unless nested.empty?
|
31
|
+
else
|
32
|
+
base[name] = nested
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/cancan/rule.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require_relative 'conditions_matcher.rb'
|
1
2
|
module CanCan
|
2
3
|
# This class is used internally and should only be called through Ability.
|
3
4
|
# it holds the information about a "can" call made on Ability and provides
|
4
5
|
# helpful methods to determine permission checking and conditions hash generation.
|
5
6
|
class Rule # :nodoc:
|
7
|
+
include ConditionsMatcher
|
6
8
|
attr_reader :base_behavior, :subjects, :actions, :conditions
|
7
9
|
attr_writer :expanded_actions
|
8
10
|
|
@@ -28,22 +30,6 @@ module CanCan
|
|
28
30
|
@match_all || (matches_action?(action) && matches_subject?(subject))
|
29
31
|
end
|
30
32
|
|
31
|
-
# Matches the block or conditions hash
|
32
|
-
def matches_conditions?(action, subject, extra_args)
|
33
|
-
if @match_all
|
34
|
-
call_block_with_all(action, subject, extra_args)
|
35
|
-
elsif @block && !subject_class?(subject)
|
36
|
-
@block.call(subject, *extra_args)
|
37
|
-
elsif @conditions.is_a?(Hash) && subject.class == Hash
|
38
|
-
nested_subject_matches_conditions?(subject)
|
39
|
-
elsif @conditions.is_a?(Hash) && !subject_class?(subject)
|
40
|
-
matches_conditions_hash?(subject)
|
41
|
-
else
|
42
|
-
# Don't stop at "cannot" definitions when there are conditions.
|
43
|
-
conditions_empty? ? true : @base_behavior
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
33
|
def only_block?
|
48
34
|
conditions_empty? && @block
|
49
35
|
end
|
@@ -52,10 +38,6 @@ module CanCan
|
|
52
38
|
@block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
|
53
39
|
end
|
54
40
|
|
55
|
-
def conditions_empty?
|
56
|
-
@conditions == {} || @conditions.nil?
|
57
|
-
end
|
58
|
-
|
59
41
|
def unmergeable?
|
60
42
|
@conditions.respond_to?(:keys) && @conditions.present? &&
|
61
43
|
(!@conditions.keys.first.is_a? Symbol)
|
@@ -83,11 +65,6 @@ module CanCan
|
|
83
65
|
|
84
66
|
private
|
85
67
|
|
86
|
-
def subject_class?(subject)
|
87
|
-
klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
|
88
|
-
klass == Class || klass == Module
|
89
|
-
end
|
90
|
-
|
91
68
|
def matches_action?(action)
|
92
69
|
@expanded_actions.include?(:manage) || @expanded_actions.include?(action)
|
93
70
|
end
|
@@ -99,64 +76,8 @@ module CanCan
|
|
99
76
|
def matches_subject_class?(subject)
|
100
77
|
@subjects.any? do |sub|
|
101
78
|
sub.is_a?(Module) && (subject.is_a?(sub) ||
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
# Checks if the given subject matches the given conditions hash.
|
108
|
-
# This behavior can be overriden by a model adapter by defining two class methods:
|
109
|
-
# override_matching_for_conditions?(subject, conditions) and
|
110
|
-
# matches_conditions_hash?(subject, conditions)
|
111
|
-
def matches_conditions_hash?(subject, conditions = @conditions)
|
112
|
-
return true if conditions.empty?
|
113
|
-
adapter = model_adapter(subject)
|
114
|
-
|
115
|
-
if adapter.override_conditions_hash_matching?(subject, conditions)
|
116
|
-
return adapter.matches_conditions_hash?(subject, conditions)
|
117
|
-
end
|
118
|
-
|
119
|
-
conditions.all? do |name, value|
|
120
|
-
if adapter.override_condition_matching?(subject, name, value)
|
121
|
-
adapter.matches_condition?(subject, name, value)
|
122
|
-
else
|
123
|
-
condition_match?(subject.send(name), value)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def nested_subject_matches_conditions?(subject_hash)
|
129
|
-
parent, _child = subject_hash.first
|
130
|
-
matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
|
131
|
-
end
|
132
|
-
|
133
|
-
def call_block_with_all(action, subject, extra_args)
|
134
|
-
if subject.class == Class
|
135
|
-
@block.call(action, subject, nil, *extra_args)
|
136
|
-
else
|
137
|
-
@block.call(action, subject.class, subject, *extra_args)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def model_adapter(subject)
|
142
|
-
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
143
|
-
end
|
144
|
-
|
145
|
-
def condition_match?(attribute, value)
|
146
|
-
case value
|
147
|
-
when Hash then hash_condition_match?(attribute, value)
|
148
|
-
when String then attribute == value
|
149
|
-
when Range then value.cover?(attribute)
|
150
|
-
when Enumerable then value.include?(attribute)
|
151
|
-
else attribute == value
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def hash_condition_match?(attribute, value)
|
156
|
-
if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
|
157
|
-
attribute.any? { |element| matches_conditions_hash?(element, value) }
|
158
|
-
else
|
159
|
-
attribute && matches_conditions_hash?(attribute, value)
|
79
|
+
subject.class.to_s == sub.to_s ||
|
80
|
+
(subject.is_a?(Module) && subject.ancestors.include?(sub)))
|
160
81
|
end
|
161
82
|
end
|
162
83
|
end
|
data/lib/cancan/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cancancan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Rodi (Renuo AG)
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2017-11-
|
14
|
+
date: 2017-11-13 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -112,13 +112,22 @@ files:
|
|
112
112
|
- init.rb
|
113
113
|
- lib/cancan.rb
|
114
114
|
- lib/cancan/ability.rb
|
115
|
+
- lib/cancan/ability/actions.rb
|
116
|
+
- lib/cancan/ability/rules.rb
|
117
|
+
- lib/cancan/conditions_matcher.rb
|
115
118
|
- lib/cancan/controller_additions.rb
|
116
119
|
- lib/cancan/controller_resource.rb
|
120
|
+
- lib/cancan/controller_resource_builder.rb
|
121
|
+
- lib/cancan/controller_resource_finder.rb
|
122
|
+
- lib/cancan/controller_resource_loader.rb
|
123
|
+
- lib/cancan/controller_resource_name_finder.rb
|
124
|
+
- lib/cancan/controller_resource_sanitizer.rb
|
117
125
|
- lib/cancan/exceptions.rb
|
118
126
|
- lib/cancan/matchers.rb
|
119
127
|
- lib/cancan/model_adapters/abstract_adapter.rb
|
120
128
|
- lib/cancan/model_adapters/active_record_4_adapter.rb
|
121
129
|
- lib/cancan/model_adapters/active_record_adapter.rb
|
130
|
+
- lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb
|
122
131
|
- lib/cancan/model_adapters/default_adapter.rb
|
123
132
|
- lib/cancan/model_additions.rb
|
124
133
|
- lib/cancan/rule.rb
|