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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bebbba60e68460ec234fc11e8d3cf0414e578a56c0347862c673396eb917dff9
|
4
|
+
data.tar.gz: bb07244a17dcf45d1852cf6677864084c2f0db5630ea9b72bdcc0c6055b5c4b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be9f2b03ae43651ea70a451b97a44fd6ec6e0a09ca444ddf625b91ae3815a245e0669bb80b4d3b0687ca327bc0c4fe81028f7736cb6493ef636b43a4140f4f49
|
7
|
+
data.tar.gz: db75441929e737d12699f57324d031e894b5d2cdbe1555451857977c42b0ef28148cc63bb718981c32ac08bafd2873623d2151ba8e98c442f217b3f4affecda9
|
data/cancancan.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
5
|
require 'cancan/version'
|
6
6
|
|
@@ -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
|
@@ -20,9 +21,9 @@ Gem::Specification.new do |s|
|
|
20
21
|
|
21
22
|
s.required_ruby_version = '>= 2.2.0'
|
22
23
|
|
23
|
-
s.add_development_dependency '
|
24
|
-
s.add_development_dependency '
|
24
|
+
s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
|
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 '
|
28
|
+
s.add_development_dependency 'rubocop', '~> 1.31.1'
|
28
29
|
end
|
data/init.rb
CHANGED
data/lib/cancan/ability/rules.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
module Ability
|
3
5
|
module Rules
|
@@ -17,12 +19,13 @@ module CanCan
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def add_rule_to_index(rule, position)
|
20
|
-
@rules_index ||=
|
22
|
+
@rules_index ||= {}
|
21
23
|
|
22
24
|
subjects = rule.subjects.compact
|
23
25
|
subjects << :all if subjects.empty?
|
24
26
|
|
25
27
|
subjects.each do |subject|
|
28
|
+
@rules_index[subject] ||= []
|
26
29
|
@rules_index[subject] << position
|
27
30
|
end
|
28
31
|
end
|
@@ -31,6 +34,7 @@ module CanCan
|
|
31
34
|
# This does not take into consideration any hash conditions or block statements
|
32
35
|
def relevant_rules(action, subject)
|
33
36
|
return [] unless @rules
|
37
|
+
|
34
38
|
relevant = possible_relevant_rules(subject).select do |rule|
|
35
39
|
rule.expanded_actions = expand_actions(rule.actions)
|
36
40
|
rule.relevant? action, subject
|
@@ -45,7 +49,9 @@ module CanCan
|
|
45
49
|
rules
|
46
50
|
else
|
47
51
|
positions = @rules_index.values_at(subject, *alternative_subjects(subject))
|
48
|
-
positions.
|
52
|
+
positions.compact!
|
53
|
+
positions.flatten!
|
54
|
+
positions.sort!
|
49
55
|
positions.map { |i| @rules[i] }
|
50
56
|
end
|
51
57
|
end
|
@@ -53,19 +59,23 @@ module CanCan
|
|
53
59
|
def relevant_rules_for_match(action, subject)
|
54
60
|
relevant_rules(action, subject).each do |rule|
|
55
61
|
next unless rule.only_raw_sql?
|
62
|
+
|
56
63
|
raise Error,
|
57
|
-
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
|
58
|
-
|
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}"
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
69
|
def relevant_rules_for_query(action, subject)
|
63
|
-
relevant_rules(action, subject).
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
70
|
+
rules = relevant_rules(action, subject).reject do |rule|
|
71
|
+
# reject 'cannot' rules with attributes when doing queries
|
72
|
+
rule.base_behavior == false && rule.attributes.present?
|
73
|
+
end
|
74
|
+
if rules.any?(&:only_block?)
|
75
|
+
raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
|
76
|
+
"The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
68
77
|
end
|
78
|
+
rules
|
69
79
|
end
|
70
80
|
|
71
81
|
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
@@ -75,6 +85,7 @@ module CanCan
|
|
75
85
|
(first_can_in_group = -1) && next unless rule.base_behavior
|
76
86
|
(first_can_in_group = i) && next if first_can_in_group == -1
|
77
87
|
next unless rule.subjects == [:all]
|
88
|
+
|
78
89
|
rules[i] = rules[first_can_in_group]
|
79
90
|
rules[first_can_in_group] = rule
|
80
91
|
first_can_in_group += 1
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module Ability
|
5
|
+
module StrongParameterSupport
|
6
|
+
# Returns an array of attributes suitable for use with strong parameters
|
7
|
+
#
|
8
|
+
# Note: reversing the relevant rules is important. Normal order means that 'cannot'
|
9
|
+
# rules will come before 'can' rules. However, you can't remove attributes before
|
10
|
+
# they are added. The 'reverse' is so that attributes will be added before the
|
11
|
+
# 'cannot' rules remove them.
|
12
|
+
def permitted_attributes(action, subject)
|
13
|
+
relevant_rules(action, subject)
|
14
|
+
.reverse
|
15
|
+
.select { |rule| rule.matches_conditions? action, subject }
|
16
|
+
.each_with_object(Set.new) do |rule, set|
|
17
|
+
attributes = get_attributes(rule, subject)
|
18
|
+
# add attributes for 'can', remove them for 'cannot'
|
19
|
+
rule.base_behavior ? set.merge(attributes) : set.subtract(attributes)
|
20
|
+
end.to_a
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def subject_class?(subject)
|
26
|
+
klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
|
27
|
+
[Class, Module].include? klass
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_attributes(rule, subject)
|
31
|
+
klass = subject_class?(subject) ? subject : subject.class
|
32
|
+
# empty attributes is an 'all'
|
33
|
+
if rule.attributes.empty? && klass < ActiveRecord::Base
|
34
|
+
klass.column_names.map(&:to_sym) - Array(klass.primary_key)
|
35
|
+
else
|
36
|
+
rule.attributes
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/cancan/ability.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'ability/rules.rb'
|
2
4
|
require_relative 'ability/actions.rb'
|
5
|
+
require_relative 'unauthorized_message_resolver.rb'
|
6
|
+
require_relative 'ability/strong_parameter_support'
|
7
|
+
|
3
8
|
module CanCan
|
4
9
|
# This module is designed to be included into an Ability class. This will
|
5
10
|
# provide the "can" methods for defining and checking abilities.
|
@@ -19,6 +24,8 @@ module CanCan
|
|
19
24
|
module Ability
|
20
25
|
include CanCan::Ability::Rules
|
21
26
|
include CanCan::Ability::Actions
|
27
|
+
include CanCan::UnauthorizedMessageResolver
|
28
|
+
include StrongParameterSupport
|
22
29
|
|
23
30
|
# Check if the user has permission to perform a given action on an object.
|
24
31
|
#
|
@@ -64,10 +71,10 @@ module CanCan
|
|
64
71
|
# end
|
65
72
|
#
|
66
73
|
# Also see the RSpec Matchers to aid in testing.
|
67
|
-
def can?(action, subject, *extra_args)
|
74
|
+
def can?(action, subject, attribute = nil, *extra_args)
|
68
75
|
match = extract_subjects(subject).lazy.map do |a_subject|
|
69
76
|
relevant_rules_for_match(action, a_subject).detect do |rule|
|
70
|
-
rule.matches_conditions?(action, a_subject, extra_args)
|
77
|
+
rule.matches_conditions?(action, a_subject, attribute, *extra_args) && rule.matches_attributes?(attribute)
|
71
78
|
end
|
72
79
|
end.reject(&:nil?).first
|
73
80
|
match ? match.base_behavior : false
|
@@ -134,8 +141,8 @@ module CanCan
|
|
134
141
|
# # check the database and return true/false
|
135
142
|
# end
|
136
143
|
#
|
137
|
-
def can(action = nil, subject = nil,
|
138
|
-
add_rule(Rule.new(true, action, subject,
|
144
|
+
def can(action = nil, subject = nil, *attributes_and_conditions, &block)
|
145
|
+
add_rule(Rule.new(true, action, subject, *attributes_and_conditions, &block))
|
139
146
|
end
|
140
147
|
|
141
148
|
# Defines an ability which cannot be done. Accepts the same arguments as "can".
|
@@ -150,8 +157,8 @@ module CanCan
|
|
150
157
|
# product.invisible?
|
151
158
|
# end
|
152
159
|
#
|
153
|
-
def cannot(action = nil, subject = nil,
|
154
|
-
add_rule(Rule.new(false, action, subject,
|
160
|
+
def cannot(action = nil, subject = nil, *attributes_and_conditions, &block)
|
161
|
+
add_rule(Rule.new(false, action, subject, *attributes_and_conditions, &block))
|
155
162
|
end
|
156
163
|
|
157
164
|
# User shouldn't specify targets with names of real actions or it will cause Seg fault
|
@@ -167,10 +174,7 @@ module CanCan
|
|
167
174
|
|
168
175
|
# See ControllerAdditions#authorize! for documentation.
|
169
176
|
def authorize!(action, subject, *args)
|
170
|
-
message = nil
|
171
|
-
if args.last.is_a?(Hash) && args.last.key?(:message)
|
172
|
-
message = args.pop[:message]
|
173
|
-
end
|
177
|
+
message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
|
174
178
|
if cannot?(action, subject, *args)
|
175
179
|
message ||= unauthorized_message(action, subject)
|
176
180
|
raise AccessDenied.new(message, action, subject, args)
|
@@ -178,14 +182,6 @@ module CanCan
|
|
178
182
|
subject
|
179
183
|
end
|
180
184
|
|
181
|
-
def unauthorized_message(action, subject)
|
182
|
-
keys = unauthorized_message_keys(action, subject)
|
183
|
-
variables = { action: action.to_s }
|
184
|
-
variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
|
185
|
-
message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
|
186
|
-
message.blank? ? nil : message
|
187
|
-
end
|
188
|
-
|
189
185
|
def attributes_for(action, subject)
|
190
186
|
attributes = {}
|
191
187
|
relevant_rules(action, subject).map do |rule|
|
@@ -202,12 +198,13 @@ module CanCan
|
|
202
198
|
relevant_rules(action, subject).any?(&:only_raw_sql?)
|
203
199
|
end
|
204
200
|
|
205
|
-
# Copies all rules of the given +CanCan::Ability+ and adds them to +self+.
|
201
|
+
# Copies all rules and aliased actions of the given +CanCan::Ability+ and adds them to +self+.
|
206
202
|
# class ReadAbility
|
207
203
|
# include CanCan::Ability
|
208
204
|
#
|
209
205
|
# def initialize
|
210
206
|
# can :read, User
|
207
|
+
# alias_action :show, :index, to: :see
|
211
208
|
# end
|
212
209
|
# end
|
213
210
|
#
|
@@ -216,6 +213,7 @@ module CanCan
|
|
216
213
|
#
|
217
214
|
# def initialize
|
218
215
|
# can :edit, User
|
216
|
+
# alias_action :create, :update, to: :modify
|
219
217
|
# end
|
220
218
|
# end
|
221
219
|
#
|
@@ -223,11 +221,35 @@ module CanCan
|
|
223
221
|
# read_ability.can? :edit, User.new #=> false
|
224
222
|
# read_ability.merge(WritingAbility.new)
|
225
223
|
# read_ability.can? :edit, User.new #=> true
|
224
|
+
# read_ability.aliased_actions #=> [:see => [:show, :index], :modify => [:create, :update]]
|
225
|
+
#
|
226
|
+
# If there are collisions when merging the +aliased_actions+, the actions on +self+ will be
|
227
|
+
# overwritten.
|
226
228
|
#
|
229
|
+
# class ReadAbility
|
230
|
+
# include CanCan::Ability
|
231
|
+
#
|
232
|
+
# def initialize
|
233
|
+
# alias_action :show, :index, to: :see
|
234
|
+
# end
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# class ShowAbility
|
238
|
+
# include CanCan::Ability
|
239
|
+
#
|
240
|
+
# def initialize
|
241
|
+
# alias_action :show, to: :see
|
242
|
+
# end
|
243
|
+
# end
|
244
|
+
#
|
245
|
+
# read_ability = ReadAbility.new
|
246
|
+
# read_ability.merge(ShowAbility)
|
247
|
+
# read_ability.aliased_actions #=> [:see => [:show]]
|
227
248
|
def merge(ability)
|
228
249
|
ability.rules.each do |rule|
|
229
250
|
add_rule(rule.dup)
|
230
251
|
end
|
252
|
+
@aliased_actions = aliased_actions.merge(ability.aliased_actions)
|
231
253
|
self
|
232
254
|
end
|
233
255
|
|
@@ -239,10 +261,13 @@ module CanCan
|
|
239
261
|
#
|
240
262
|
# Where can_hash and cannot_hash are formatted thusly:
|
241
263
|
# {
|
242
|
-
# action:
|
264
|
+
# action: { subject: [attributes] }
|
243
265
|
# }
|
244
266
|
def permissions
|
245
|
-
permissions_list = {
|
267
|
+
permissions_list = {
|
268
|
+
can: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } },
|
269
|
+
cannot: Hash.new { |actions, k1| actions[k1] = Hash.new { |subjects, k2| subjects[k2] = [] } }
|
270
|
+
}
|
246
271
|
rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
|
247
272
|
permissions_list
|
248
273
|
end
|
@@ -250,8 +275,9 @@ module CanCan
|
|
250
275
|
def extract_rule_in_permissions(permissions_list, rule)
|
251
276
|
expand_actions(rule.actions).each do |action|
|
252
277
|
container = rule.base_behavior ? :can : :cannot
|
253
|
-
|
254
|
-
|
278
|
+
rule.subjects.each do |subject|
|
279
|
+
permissions_list[container][action][subject.to_s] += rule.attributes
|
280
|
+
end
|
255
281
|
end
|
256
282
|
end
|
257
283
|
|
@@ -276,7 +302,11 @@ module CanCan
|
|
276
302
|
|
277
303
|
def alternative_subjects(subject)
|
278
304
|
subject = subject.class unless subject.is_a?(Module)
|
279
|
-
|
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
|
280
310
|
end
|
281
311
|
end
|
282
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
|
@@ -1,49 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
module ConditionsMatcher
|
3
5
|
# Matches the block or conditions hash
|
4
|
-
def matches_conditions?(action, subject, extra_args)
|
6
|
+
def matches_conditions?(action, subject, attribute = nil, *extra_args)
|
5
7
|
return call_block_with_all(action, subject, extra_args) if @match_all
|
6
|
-
return
|
7
|
-
matches_non_block_conditions(subject)
|
8
|
+
return matches_block_conditions(subject, attribute, *extra_args) if @block
|
9
|
+
return matches_non_block_conditions(subject) unless conditions_empty?
|
10
|
+
|
11
|
+
true
|
8
12
|
end
|
9
13
|
|
10
14
|
private
|
11
15
|
|
12
16
|
def subject_class?(subject)
|
13
17
|
klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
|
14
|
-
|
18
|
+
[Class, Module].include? klass
|
15
19
|
end
|
16
20
|
|
17
|
-
def
|
18
|
-
if
|
19
|
-
|
20
|
-
|
21
|
+
def matches_block_conditions(subject, attribute, *extra_args)
|
22
|
+
return @base_behavior if subject_class?(subject)
|
23
|
+
|
24
|
+
if attribute
|
25
|
+
@block.call(subject, attribute, *extra_args)
|
26
|
+
else
|
27
|
+
@block.call(subject, *extra_args)
|
21
28
|
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches_non_block_conditions(subject)
|
32
|
+
return nested_subject_matches_conditions?(subject) if subject.class == Hash
|
33
|
+
return matches_conditions_hash?(subject) unless subject_class?(subject)
|
34
|
+
|
22
35
|
# Don't stop at "cannot" definitions when there are conditions.
|
23
|
-
|
36
|
+
@base_behavior
|
24
37
|
end
|
25
38
|
|
26
39
|
def nested_subject_matches_conditions?(subject_hash)
|
27
|
-
parent,
|
28
|
-
|
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))
|
29
52
|
end
|
30
53
|
|
31
54
|
# Checks if the given subject matches the given conditions hash.
|
32
|
-
# This behavior can be
|
55
|
+
# This behavior can be overridden by a model adapter by defining two class methods:
|
33
56
|
# override_matching_for_conditions?(subject, conditions) and
|
34
57
|
# matches_conditions_hash?(subject, conditions)
|
35
58
|
def matches_conditions_hash?(subject, conditions = @conditions)
|
36
|
-
return true if conditions.empty?
|
59
|
+
return true if conditions.is_a?(Hash) && conditions.empty?
|
60
|
+
|
37
61
|
adapter = model_adapter(subject)
|
38
62
|
|
39
63
|
if adapter.override_conditions_hash_matching?(subject, conditions)
|
40
64
|
return adapter.matches_conditions_hash?(subject, conditions)
|
41
65
|
end
|
42
66
|
|
43
|
-
matches_all_conditions?(adapter,
|
67
|
+
matches_all_conditions?(adapter, subject, conditions)
|
68
|
+
end
|
69
|
+
|
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
|
44
78
|
end
|
45
79
|
|
46
|
-
def
|
80
|
+
def matches_hash_conditions?(adapter, subject, conditions)
|
47
81
|
conditions.all? do |name, value|
|
48
82
|
if adapter.override_condition_matching?(subject, name, value)
|
49
83
|
adapter.matches_condition?(subject, name, value)
|
@@ -68,13 +102,30 @@ module CanCan
|
|
68
102
|
|
69
103
|
def hash_condition_match?(attribute, value)
|
70
104
|
if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
|
71
|
-
|
105
|
+
array_like_matches_condition_hash?(attribute, value)
|
72
106
|
else
|
73
107
|
attribute && matches_conditions_hash?(attribute, value)
|
74
108
|
end
|
75
109
|
end
|
76
110
|
|
77
|
-
def
|
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
|
+
|
128
|
+
def call_block_with_all(action, subject, *extra_args)
|
78
129
|
if subject.class == Class
|
79
130
|
@block.call(action, subject, nil, *extra_args)
|
80
131
|
else
|
@@ -87,7 +138,10 @@ module CanCan
|
|
87
138
|
end
|
88
139
|
|
89
140
|
def conditions_empty?
|
90
|
-
@conditions
|
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?
|
91
145
|
end
|
92
146
|
end
|
93
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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CanCan
|
2
4
|
# This module is automatically included into all controllers.
|
3
5
|
# It also makes the "can?" and "cannot?" methods available to all views.
|
@@ -225,7 +227,7 @@ module CanCan
|
|
225
227
|
cancan_skipper[:authorize][name] = options
|
226
228
|
end
|
227
229
|
|
228
|
-
# Add this to a controller to ensure it performs authorization through +
|
230
|
+
# Add this to a controller to ensure it performs authorization through +authorize+! or +authorize_resource+ call.
|
229
231
|
# If neither of these authorization methods are called,
|
230
232
|
# a CanCan::AuthorizationNotPerformed exception will be raised.
|
231
233
|
# This is normally added to the ApplicationController to ensure all controller actions do authorization.
|
@@ -260,8 +262,9 @@ module CanCan
|
|
260
262
|
next if controller.instance_variable_defined?(:@_authorized)
|
261
263
|
next if options[:if] && !controller.send(options[:if])
|
262
264
|
next if options[:unless] && controller.send(options[:unless])
|
265
|
+
|
263
266
|
raise AuthorizationNotPerformed,
|
264
|
-
'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. ' \
|
265
268
|
'Add skip_authorization_check to bypass this check.'
|
266
269
|
end
|
267
270
|
|
@@ -384,6 +387,8 @@ module CanCan
|
|
384
387
|
end
|
385
388
|
end
|
386
389
|
|
387
|
-
ActiveSupport
|
388
|
-
|
390
|
+
if defined? ActiveSupport
|
391
|
+
ActiveSupport.on_load(:action_controller) do
|
392
|
+
include CanCan::ControllerAdditions
|
393
|
+
end
|
389
394
|
end
|