cancancan 2.3.0 → 3.0.0.rc1
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 +5 -5
- data/init.rb +2 -0
- data/lib/cancan.rb +4 -0
- data/lib/cancan/ability.rb +49 -23
- data/lib/cancan/ability/actions.rb +2 -0
- data/lib/cancan/ability/rules.rb +14 -6
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/conditions_matcher.rb +20 -10
- data/lib/cancan/controller_additions.rb +4 -1
- data/lib/cancan/controller_resource.rb +6 -0
- 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 +10 -2
- data/lib/cancan/matchers.rb +3 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +2 -0
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +24 -21
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +11 -18
- data/lib/cancan/model_adapters/active_record_adapter.rb +47 -12
- data/lib/cancan/model_adapters/conditions_extractor.rb +3 -3
- data/lib/cancan/model_adapters/conditions_normalizer.rb +45 -0
- data/lib/cancan/model_adapters/default_adapter.rb +2 -0
- data/lib/cancan/model_additions.rb +2 -0
- data/lib/cancan/parameter_validators.rb +9 -0
- data/lib/cancan/rule.rb +50 -12
- data/lib/cancan/rules_compressor.rb +3 -0
- data/lib/cancan/unauthorized_message_resolver.rb +22 -0
- data/lib/cancan/version.rb +3 -1
- data/lib/cancancan.rb +2 -0
- data/lib/generators/cancan/ability/ability_generator.rb +3 -1
- data/lib/generators/cancan/ability/templates/ability.rb +2 -0
- metadata +24 -21
- 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: d745990a81dbfa8dfaf8802b76bbe6a09d11291562383b429320c1db96355e20
|
|
4
|
+
data.tar.gz: 579e1848a8c8a5c096a747306c7aa08b2a156bcd404f2c6b6d5b418c02026324
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91d38cda2159ce1c061101d599aae4abf8bb1358bfd1fe43b4432e7eba0ce9e4002b52d055a1e693138a05e185b70fbc504b6f696e2d02d6e8086b8a6f9976ac
|
|
7
|
+
data.tar.gz: eb23bb3c5b8a3feaa55d27226443fa8e3fd15175a23d7fa6810c4253ca8646fdbd88150138e3bc81e473d7a27302584ec5ebdb5e31e0fd1dfdf54458b52b3fea
|
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
|
|
|
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
|
|
21
21
|
s.required_ruby_version = '>= 2.2.0'
|
|
22
22
|
|
|
23
|
-
s.add_development_dependency '
|
|
24
|
-
s.add_development_dependency '
|
|
23
|
+
s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
|
|
24
|
+
s.add_development_dependency 'bundler', '~> 2.0'
|
|
25
25
|
s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
|
|
26
26
|
s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
|
|
27
|
-
s.add_development_dependency '
|
|
27
|
+
s.add_development_dependency 'rubocop', '~> 0.63.1'
|
|
28
28
|
end
|
data/init.rb
CHANGED
data/lib/cancan.rb
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'cancan/version'
|
|
4
|
+
require 'cancan/parameter_validators'
|
|
2
5
|
require 'cancan/ability'
|
|
3
6
|
require 'cancan/rule'
|
|
4
7
|
require 'cancan/controller_resource'
|
|
@@ -12,6 +15,7 @@ require 'cancan/rules_compressor'
|
|
|
12
15
|
|
|
13
16
|
if defined? ActiveRecord
|
|
14
17
|
require 'cancan/model_adapters/conditions_extractor'
|
|
18
|
+
require 'cancan/model_adapters/conditions_normalizer'
|
|
15
19
|
require 'cancan/model_adapters/active_record_adapter'
|
|
16
20
|
require 'cancan/model_adapters/active_record_4_adapter'
|
|
17
21
|
require 'cancan/model_adapters/active_record_5_adapter'
|
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.
|
|
228
|
+
#
|
|
229
|
+
# class ReadAbility
|
|
230
|
+
# include CanCan::Ability
|
|
226
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
|
|
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
|
|
@@ -31,6 +33,7 @@ module CanCan
|
|
|
31
33
|
# This does not take into consideration any hash conditions or block statements
|
|
32
34
|
def relevant_rules(action, subject)
|
|
33
35
|
return [] unless @rules
|
|
36
|
+
|
|
34
37
|
relevant = possible_relevant_rules(subject).select do |rule|
|
|
35
38
|
rule.expanded_actions = expand_actions(rule.actions)
|
|
36
39
|
rule.relevant? action, subject
|
|
@@ -53,19 +56,23 @@ module CanCan
|
|
|
53
56
|
def relevant_rules_for_match(action, subject)
|
|
54
57
|
relevant_rules(action, subject).each do |rule|
|
|
55
58
|
next unless rule.only_raw_sql?
|
|
59
|
+
|
|
56
60
|
raise Error,
|
|
57
61
|
"The can? and cannot? call cannot be used with a raw sql 'can' definition."\
|
|
58
|
-
|
|
62
|
+
" The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
59
63
|
end
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
def relevant_rules_for_query(action, subject)
|
|
63
|
-
relevant_rules(action, subject).
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
rules = relevant_rules(action, subject).reject do |rule|
|
|
68
|
+
# reject 'cannot' rules with attributes when doing queries
|
|
69
|
+
rule.base_behavior == false && rule.attributes.present?
|
|
70
|
+
end
|
|
71
|
+
if rules.any?(&:only_block?)
|
|
72
|
+
raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
|
|
73
|
+
"The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
|
|
68
74
|
end
|
|
75
|
+
rules
|
|
69
76
|
end
|
|
70
77
|
|
|
71
78
|
# Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
|
|
@@ -75,6 +82,7 @@ module CanCan
|
|
|
75
82
|
(first_can_in_group = -1) && next unless rule.base_behavior
|
|
76
83
|
(first_can_in_group = i) && next if first_can_in_group == -1
|
|
77
84
|
next unless rule.subjects == [:all]
|
|
85
|
+
|
|
78
86
|
rules[i] = rules[first_can_in_group]
|
|
79
87
|
rules[first_can_in_group] = rule
|
|
80
88
|
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
|
|
@@ -1,26 +1,35 @@
|
|
|
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
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def matches_block_conditions(subject, *extra_args)
|
|
22
|
+
return @base_behavior if subject_class?(subject)
|
|
23
|
+
|
|
24
|
+
@block.call(subject, *extra_args)
|
|
15
25
|
end
|
|
16
26
|
|
|
17
27
|
def matches_non_block_conditions(subject)
|
|
18
|
-
if
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end
|
|
28
|
+
return nested_subject_matches_conditions?(subject) if subject.class == Hash
|
|
29
|
+
return matches_conditions_hash?(subject) unless subject_class?(subject)
|
|
30
|
+
|
|
22
31
|
# Don't stop at "cannot" definitions when there are conditions.
|
|
23
|
-
|
|
32
|
+
@base_behavior
|
|
24
33
|
end
|
|
25
34
|
|
|
26
35
|
def nested_subject_matches_conditions?(subject_hash)
|
|
@@ -34,6 +43,7 @@ module CanCan
|
|
|
34
43
|
# matches_conditions_hash?(subject, conditions)
|
|
35
44
|
def matches_conditions_hash?(subject, conditions = @conditions)
|
|
36
45
|
return true if conditions.empty?
|
|
46
|
+
|
|
37
47
|
adapter = model_adapter(subject)
|
|
38
48
|
|
|
39
49
|
if adapter.override_conditions_hash_matching?(subject, conditions)
|
|
@@ -74,7 +84,7 @@ module CanCan
|
|
|
74
84
|
end
|
|
75
85
|
end
|
|
76
86
|
|
|
77
|
-
def call_block_with_all(action, subject, extra_args)
|
|
87
|
+
def call_block_with_all(action, subject, *extra_args)
|
|
78
88
|
if subject.class == Class
|
|
79
89
|
@block.call(action, subject, nil, *extra_args)
|
|
80
90
|
else
|
|
@@ -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,6 +262,7 @@ 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
267
|
'This action failed the check_authorization because it does not authorize_resource. '\
|
|
265
268
|
'Add skip_authorization_check to bypass this check.'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'controller_resource_loader.rb'
|
|
2
4
|
module CanCan
|
|
3
5
|
# Handle the load and authorization controller logic
|
|
@@ -34,6 +36,7 @@ module CanCan
|
|
|
34
36
|
|
|
35
37
|
def authorize_resource
|
|
36
38
|
return if skip?(:authorize)
|
|
39
|
+
|
|
37
40
|
@controller.authorize!(authorization_action, resource_instance || resource_class_with_parent)
|
|
38
41
|
end
|
|
39
42
|
|
|
@@ -43,6 +46,7 @@ module CanCan
|
|
|
43
46
|
|
|
44
47
|
def skip?(behavior)
|
|
45
48
|
return false unless (options = @controller.class.cancan_skipper[behavior][@name])
|
|
49
|
+
|
|
46
50
|
options == {} ||
|
|
47
51
|
options[:except] && !action_exists_in?(options[:except]) ||
|
|
48
52
|
action_exists_in?(options[:only])
|
|
@@ -90,6 +94,7 @@ module CanCan
|
|
|
90
94
|
|
|
91
95
|
def resource_instance
|
|
92
96
|
return unless load_instance? && @controller.instance_variable_defined?("@#{instance_name}")
|
|
97
|
+
|
|
93
98
|
@controller.instance_variable_get("@#{instance_name}")
|
|
94
99
|
end
|
|
95
100
|
|
|
@@ -99,6 +104,7 @@ module CanCan
|
|
|
99
104
|
|
|
100
105
|
def collection_instance
|
|
101
106
|
return unless @controller.instance_variable_defined?("@#{instance_name.to_s.pluralize}")
|
|
107
|
+
|
|
102
108
|
@controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
|
|
103
109
|
end
|
|
104
110
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'controller_resource_finder.rb'
|
|
2
4
|
require_relative 'controller_resource_name_finder.rb'
|
|
3
5
|
require_relative 'controller_resource_builder.rb'
|
|
@@ -11,6 +13,7 @@ module CanCan
|
|
|
11
13
|
|
|
12
14
|
def load_resource
|
|
13
15
|
return if skip?(:load)
|
|
16
|
+
|
|
14
17
|
if load_instance?
|
|
15
18
|
self.resource_instance ||= load_resource_instance
|
|
16
19
|
elsif load_collection?
|
|
@@ -26,6 +29,7 @@ module CanCan
|
|
|
26
29
|
|
|
27
30
|
def resource_params_by_key(key)
|
|
28
31
|
return unless @options[key] && @params.key?(extract_key(@options[key]))
|
|
32
|
+
|
|
29
33
|
@params[extract_key(@options[key])]
|
|
30
34
|
end
|
|
31
35
|
|
data/lib/cancan/exceptions.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module CanCan
|
|
2
4
|
# A general CanCan exception
|
|
3
5
|
class Error < StandardError; end
|
|
@@ -8,9 +10,15 @@ module CanCan
|
|
|
8
10
|
# Raised when removed code is called, an alternative solution is provided in message.
|
|
9
11
|
class ImplementationRemoved < Error; end
|
|
10
12
|
|
|
11
|
-
# Raised when using check_authorization without calling
|
|
13
|
+
# Raised when using check_authorization without calling authorize!
|
|
12
14
|
class AuthorizationNotPerformed < Error; end
|
|
13
15
|
|
|
16
|
+
# Raised when a rule is created with both a block and a hash of conditions
|
|
17
|
+
class BlockAndConditionsError < Error; end
|
|
18
|
+
|
|
19
|
+
# Raised when an unexpected argument is passed as an attribute
|
|
20
|
+
class AttributeArgumentError < Error; end
|
|
21
|
+
|
|
14
22
|
# Raised when using a wrong association name
|
|
15
23
|
class WrongAssociationName < Error; end
|
|
16
24
|
|
|
@@ -33,7 +41,7 @@ module CanCan
|
|
|
33
41
|
# exception.default_message = "Default error message"
|
|
34
42
|
# exception.message # => "Default error message"
|
|
35
43
|
#
|
|
36
|
-
# See ControllerAdditions#
|
|
44
|
+
# See ControllerAdditions#authorize! for more information on rescuing from this exception
|
|
37
45
|
# and customizing the message using I18n.
|
|
38
46
|
class AccessDenied < Error
|
|
39
47
|
attr_reader :action, :subject, :conditions
|
data/lib/cancan/matchers.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
rspec_module = defined?(RSpec::Core) ? 'RSpec' : 'Spec' # RSpec 1 compatability
|
|
2
4
|
|
|
3
5
|
if rspec_module == 'RSpec'
|
|
@@ -12,6 +14,7 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
|
|
|
12
14
|
actions = args.first
|
|
13
15
|
if actions.is_a? Array
|
|
14
16
|
break false if actions.empty?
|
|
17
|
+
|
|
15
18
|
actions.all? { |action| ability.can?(action, *args[1..-1]) }
|
|
16
19
|
else
|
|
17
20
|
ability.can?(*args)
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module CanCan
|
|
2
4
|
module ModelAdapters
|
|
3
|
-
class ActiveRecord4Adapter <
|
|
4
|
-
|
|
5
|
-
def self.for_class?(model_class)
|
|
6
|
-
ActiveRecord::VERSION::MAJOR == 4 && model_class <= ActiveRecord::Base
|
|
7
|
-
end
|
|
5
|
+
class ActiveRecord4Adapter < ActiveRecordAdapter
|
|
6
|
+
AbstractAdapter.inherited(self)
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
class << self
|
|
9
|
+
def for_class?(model_class)
|
|
10
|
+
version_lower?('5.0.0') && model_class <= ActiveRecord::Base
|
|
11
|
+
end
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
def override_condition_matching?(subject, name, _value)
|
|
14
|
+
subject.class.defined_enums.include?(name.to_s)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def matches_condition?(subject, name, value)
|
|
18
|
+
# Get the mapping from enum strings to values.
|
|
19
|
+
enum = subject.class.send(name.to_s.pluralize)
|
|
20
|
+
# Get the value of the attribute as an integer.
|
|
21
|
+
attribute = enum[subject.send(name)]
|
|
22
|
+
# Check to see if the value matches the condition.
|
|
23
|
+
if value.is_a?(Enumerable)
|
|
24
|
+
value.include? attribute
|
|
25
|
+
else
|
|
26
|
+
attribute == value
|
|
27
|
+
end
|
|
25
28
|
end
|
|
26
29
|
end
|
|
27
30
|
|
|
@@ -39,7 +42,7 @@ module CanCan
|
|
|
39
42
|
|
|
40
43
|
# Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
|
|
41
44
|
def sanitize_sql(conditions)
|
|
42
|
-
if
|
|
45
|
+
if self.class.version_greater_or_equal?('4.2.0') && conditions.is_a?(Hash)
|
|
43
46
|
sanitize_sql_activerecord4(conditions)
|
|
44
47
|
else
|
|
45
48
|
@model_class.send(:sanitize_sql, conditions)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module CanCan
|
|
2
4
|
module ModelAdapters
|
|
3
5
|
class ActiveRecord5Adapter < ActiveRecord4Adapter
|
|
4
6
|
AbstractAdapter.inherited(self)
|
|
5
7
|
|
|
6
8
|
def self.for_class?(model_class)
|
|
7
|
-
|
|
9
|
+
version_greater_or_equal?('5.0.0') && model_class <= ActiveRecord::Base
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
# rails 5 is capable of using strings in enum
|
|
@@ -13,22 +15,15 @@ module CanCan
|
|
|
13
15
|
return super if Array.wrap(value).all? { |x| x.is_a? Integer }
|
|
14
16
|
|
|
15
17
|
attribute = subject.send(name)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
else
|
|
19
|
-
attribute == value.to_s
|
|
20
|
-
end
|
|
18
|
+
raw_attribute = subject.class.send(name.to_s.pluralize)[attribute]
|
|
19
|
+
!(Array(value).map(&:to_s) & [attribute, raw_attribute]).empty?
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
private
|
|
24
23
|
|
|
25
|
-
# As of rails 4, `includes()` no longer causes active record to
|
|
26
|
-
# look inside the where clause to decide to outer join tables
|
|
27
|
-
# you're using in the where. Instead, `references()` is required
|
|
28
|
-
# in addition to `includes()` to force the outer join.
|
|
29
24
|
def build_relation(*where_conditions)
|
|
30
25
|
relation = @model_class.where(*where_conditions)
|
|
31
|
-
relation = relation.
|
|
26
|
+
relation = relation.left_joins(joins).distinct if joins.present?
|
|
32
27
|
relation
|
|
33
28
|
end
|
|
34
29
|
|
|
@@ -50,19 +45,17 @@ module CanCan
|
|
|
50
45
|
|
|
51
46
|
conditions.stringify_keys!
|
|
52
47
|
|
|
53
|
-
predicate_builder.build_from_hash(conditions).map
|
|
54
|
-
visit_nodes(b)
|
|
55
|
-
end.join(' AND ')
|
|
48
|
+
predicate_builder.build_from_hash(conditions).map { |b| visit_nodes(b) }.join(' AND ')
|
|
56
49
|
end
|
|
57
50
|
|
|
58
|
-
def visit_nodes(
|
|
51
|
+
def visit_nodes(node)
|
|
59
52
|
# Rails 5.2 adds a BindParam node that prevents the visitor method from properly compiling the SQL query
|
|
60
|
-
if
|
|
53
|
+
if self.class.version_greater_or_equal?('5.2.0')
|
|
61
54
|
connection = @model_class.send(:connection)
|
|
62
55
|
collector = Arel::Collectors::SubstituteBinds.new(connection, Arel::Collectors::SQLString.new)
|
|
63
|
-
connection.visitor.accept(
|
|
56
|
+
connection.visitor.accept(node, collector).value
|
|
64
57
|
else
|
|
65
|
-
@model_class.send(:connection).visitor.compile(
|
|
58
|
+
@model_class.send(:connection).visitor.compile(node)
|
|
66
59
|
end
|
|
67
60
|
end
|
|
68
61
|
end
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require_relative 'conditions_extractor.rb'
|
|
3
4
|
require 'cancan/rules_compressor'
|
|
4
5
|
module CanCan
|
|
5
6
|
module ModelAdapters
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
class ActiveRecordAdapter < AbstractAdapter
|
|
8
|
+
def self.version_greater_or_equal?(version)
|
|
9
|
+
Gem::Version.new(ActiveRecord.version).release >= Gem::Version.new(version)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.version_lower?(version)
|
|
13
|
+
Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(model_class, rules)
|
|
17
|
+
super
|
|
18
|
+
@compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
|
|
19
|
+
ConditionsNormalizer.normalize(model_class, @compressed_rules)
|
|
20
|
+
end
|
|
8
21
|
|
|
9
22
|
# Returns conditions intended to be used inside a database query. Normally you will not call this
|
|
10
23
|
# method directly, but instead go through ModelAdditions#accessible_by.
|
|
@@ -22,13 +35,12 @@ module CanCan
|
|
|
22
35
|
# query(:manage, User).conditions # => "not (self_managed = 't') AND ((manager_id = 1) OR (id = 1))"
|
|
23
36
|
#
|
|
24
37
|
def conditions
|
|
25
|
-
compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
|
|
26
38
|
conditions_extractor = ConditionsExtractor.new(@model_class)
|
|
27
|
-
if compressed_rules.size == 1 && compressed_rules.first.base_behavior
|
|
39
|
+
if @compressed_rules.size == 1 && @compressed_rules.first.base_behavior
|
|
28
40
|
# Return the conditions directly if there's just one definition
|
|
29
|
-
conditions_extractor.tableize_conditions(compressed_rules.first.conditions).dup
|
|
41
|
+
conditions_extractor.tableize_conditions(@compressed_rules.first.conditions).dup
|
|
30
42
|
else
|
|
31
|
-
extract_multiple_conditions(conditions_extractor, compressed_rules)
|
|
43
|
+
extract_multiple_conditions(conditions_extractor, @compressed_rules)
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
@@ -42,27 +54,50 @@ module CanCan
|
|
|
42
54
|
if override_scope
|
|
43
55
|
@model_class.where(nil).merge(override_scope)
|
|
44
56
|
elsif @model_class.respond_to?(:where) && @model_class.respond_to?(:joins)
|
|
45
|
-
|
|
57
|
+
build_relation(conditions)
|
|
46
58
|
else
|
|
47
59
|
@model_class.all(conditions: conditions, joins: joins)
|
|
48
60
|
end
|
|
49
61
|
end
|
|
50
62
|
|
|
63
|
+
# Returns the associations used in conditions for the :joins option of a search.
|
|
64
|
+
# See ModelAdditions#accessible_by
|
|
65
|
+
def joins
|
|
66
|
+
joins_hash = {}
|
|
67
|
+
@compressed_rules.reverse_each do |rule|
|
|
68
|
+
deep_merge(joins_hash, rule.associations_hash)
|
|
69
|
+
end
|
|
70
|
+
deep_clean(joins_hash) unless joins_hash.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
51
73
|
private
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
# Removes empty hashes and moves everything into arrays.
|
|
76
|
+
def deep_clean(joins_hash)
|
|
77
|
+
joins_hash.map { |name, nested| nested.empty? ? name : { name => deep_clean(nested) } }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Takes two hashes and does a deep merge.
|
|
81
|
+
def deep_merge(base_hash, added_hash)
|
|
82
|
+
added_hash.each do |key, value|
|
|
83
|
+
if base_hash[key].is_a?(Hash)
|
|
84
|
+
deep_merge(base_hash[key], value) unless value.empty?
|
|
85
|
+
else
|
|
86
|
+
base_hash[key] = value
|
|
87
|
+
end
|
|
88
|
+
end
|
|
55
89
|
end
|
|
56
90
|
|
|
57
91
|
def override_scope
|
|
58
|
-
conditions = @
|
|
92
|
+
conditions = @compressed_rules.map(&:conditions).compact
|
|
59
93
|
return unless conditions.any? { |c| c.is_a?(ActiveRecord::Relation) }
|
|
60
94
|
return conditions.first if conditions.size == 1
|
|
95
|
+
|
|
61
96
|
raise_override_scope_error
|
|
62
97
|
end
|
|
63
98
|
|
|
64
99
|
def raise_override_scope_error
|
|
65
|
-
rule_found = @
|
|
100
|
+
rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
|
|
66
101
|
raise Error,
|
|
67
102
|
'Unable to merge an Active Record scope with other conditions. '\
|
|
68
103
|
"Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# this class is responsible of converting the hash of conditions
|
|
2
4
|
# in "where conditions" to generate the sql query
|
|
3
5
|
# it consists of a names_cache that helps calculating the next name given to the association
|
|
@@ -12,6 +14,7 @@ module CanCan
|
|
|
12
14
|
|
|
13
15
|
def tableize_conditions(conditions, model_class = @root_model_class, path_to_key = 0)
|
|
14
16
|
return conditions unless conditions.is_a? Hash
|
|
17
|
+
|
|
15
18
|
conditions.each_with_object({}) do |(key, value), result_hash|
|
|
16
19
|
if value.is_a? Hash
|
|
17
20
|
result_hash.merge!(calculate_result_hash(key, model_class, path_to_key, result_hash, value))
|
|
@@ -26,9 +29,6 @@ module CanCan
|
|
|
26
29
|
|
|
27
30
|
def calculate_result_hash(key, model_class, path_to_key, result_hash, value)
|
|
28
31
|
reflection = model_class.reflect_on_association(key)
|
|
29
|
-
unless reflection
|
|
30
|
-
raise WrongAssociationName, "association #{key} not defined in model #{model_class.name}"
|
|
31
|
-
end
|
|
32
32
|
nested_resulted = calculate_nested(model_class, result_hash, key, value.dup, path_to_key)
|
|
33
33
|
association_class = reflection.klass.name.constantize
|
|
34
34
|
tableize_conditions(nested_resulted, association_class, "#{path_to_key}_#{key}")
|
|
@@ -0,0 +1,45 @@
|
|
|
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 thorugh 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 reflection.options[:through].present?
|
|
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
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/cancan/rule.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'conditions_matcher.rb'
|
|
2
4
|
module CanCan
|
|
3
5
|
# This class is used internally and should only be called through Ability.
|
|
@@ -5,25 +7,41 @@ module CanCan
|
|
|
5
7
|
# helpful methods to determine permission checking and conditions hash generation.
|
|
6
8
|
class Rule # :nodoc:
|
|
7
9
|
include ConditionsMatcher
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
include ParameterValidators
|
|
11
|
+
attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
|
|
12
|
+
attr_writer :expanded_actions, :conditions
|
|
10
13
|
|
|
11
14
|
# The first argument when initializing is the base_behavior which is a true/false
|
|
12
15
|
# value. True for "can" and false for "cannot". The next two arguments are the action
|
|
13
16
|
# and subject respectively (such as :read, @project). The third argument is a hash
|
|
14
17
|
# of conditions and the last one is the block passed to the "can" call.
|
|
15
|
-
def initialize(base_behavior, action, subject,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
def initialize(base_behavior, action, subject, *extra_args, &block)
|
|
19
|
+
# for backwards compatibility, attributes are an optional parameter. Check if
|
|
20
|
+
# attributes were passed or are actually conditions
|
|
21
|
+
attributes, extra_args = parse_attributes_from_extra_args(extra_args)
|
|
22
|
+
condition_and_block_check(extra_args, block, action, subject)
|
|
19
23
|
@match_all = action.nil? && subject.nil?
|
|
24
|
+
raise Error, "Subject is required for #{action}" if action && subject.nil?
|
|
25
|
+
|
|
20
26
|
@base_behavior = base_behavior
|
|
21
27
|
@actions = Array(action)
|
|
22
28
|
@subjects = Array(subject)
|
|
23
|
-
@
|
|
29
|
+
@attributes = Array(attributes)
|
|
30
|
+
@conditions = extra_args || {}
|
|
24
31
|
@block = block
|
|
25
32
|
end
|
|
26
33
|
|
|
34
|
+
def inspect
|
|
35
|
+
repr = "#<#{self.class.name}"
|
|
36
|
+
repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
|
|
37
|
+
repr += if with_scope?
|
|
38
|
+
", #{@conditions.where_values_hash}"
|
|
39
|
+
elsif [Hash, String].include?(@conditions.class)
|
|
40
|
+
", #{@conditions.inspect}"
|
|
41
|
+
end
|
|
42
|
+
repr + '>'
|
|
43
|
+
end
|
|
44
|
+
|
|
27
45
|
def can_rule?
|
|
28
46
|
base_behavior
|
|
29
47
|
end
|
|
@@ -33,10 +51,11 @@ module CanCan
|
|
|
33
51
|
end
|
|
34
52
|
|
|
35
53
|
def catch_all?
|
|
36
|
-
|
|
54
|
+
(with_scope? && @conditions.where_values_hash.empty?) ||
|
|
55
|
+
(!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
|
|
37
56
|
end
|
|
38
57
|
|
|
39
|
-
# Matches both the subject and
|
|
58
|
+
# Matches both the action, subject, and attribute, not necessarily the conditions
|
|
40
59
|
def relevant?(action, subject)
|
|
41
60
|
subject = subject.values.first if subject.class == Hash
|
|
42
61
|
@match_all || (matches_action?(action) && matches_subject?(subject))
|
|
@@ -50,9 +69,8 @@ module CanCan
|
|
|
50
69
|
@block.nil? && !conditions_empty? && !@conditions.is_a?(Hash)
|
|
51
70
|
end
|
|
52
71
|
|
|
53
|
-
def
|
|
54
|
-
@conditions.
|
|
55
|
-
(!@conditions.keys.first.is_a? Symbol)
|
|
72
|
+
def with_scope?
|
|
73
|
+
@conditions.is_a?(ActiveRecord::Relation)
|
|
56
74
|
end
|
|
57
75
|
|
|
58
76
|
def associations_hash(conditions = @conditions)
|
|
@@ -75,6 +93,13 @@ module CanCan
|
|
|
75
93
|
attributes
|
|
76
94
|
end
|
|
77
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
|
+
|
|
78
103
|
private
|
|
79
104
|
|
|
80
105
|
def matches_action?(action)
|
|
@@ -92,5 +117,18 @@ module CanCan
|
|
|
92
117
|
(subject.is_a?(Module) && subject.ancestors.include?(sub)))
|
|
93
118
|
end
|
|
94
119
|
end
|
|
120
|
+
|
|
121
|
+
def parse_attributes_from_extra_args(args)
|
|
122
|
+
attributes = args.shift if valid_attribute_param?(args.first)
|
|
123
|
+
extra_args = args.shift
|
|
124
|
+
[attributes, extra_args]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def condition_and_block_check(conditions, block, action, subject)
|
|
128
|
+
return unless conditions.is_a?(Hash) && block
|
|
129
|
+
|
|
130
|
+
raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\
|
|
131
|
+
"Check \":#{action} #{subject}\" ability."
|
|
132
|
+
end
|
|
95
133
|
end
|
|
96
134
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'conditions_matcher.rb'
|
|
2
4
|
module CanCan
|
|
3
5
|
class RulesCompressor
|
|
@@ -11,6 +13,7 @@ module CanCan
|
|
|
11
13
|
def compress(array)
|
|
12
14
|
idx = array.rindex(&:catch_all?)
|
|
13
15
|
return array unless idx
|
|
16
|
+
|
|
14
17
|
value = array[idx]
|
|
15
18
|
array[idx..-1]
|
|
16
19
|
.drop_while { |n| n.base_behavior == value.base_behavior }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CanCan
|
|
4
|
+
module UnauthorizedMessageResolver
|
|
5
|
+
def unauthorized_message(action, subject)
|
|
6
|
+
keys = unauthorized_message_keys(action, subject)
|
|
7
|
+
variables = { action: action.to_s }
|
|
8
|
+
variables[:subject] = translate_subject(subject)
|
|
9
|
+
message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
|
|
10
|
+
message.blank? ? nil : message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def translate_subject(subject)
|
|
14
|
+
klass = (subject.class == Class ? subject : subject.class)
|
|
15
|
+
if klass.respond_to?(:model_name)
|
|
16
|
+
klass.model_name.human
|
|
17
|
+
else
|
|
18
|
+
klass.to_s.underscore.humanize.downcase
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/cancan/version.rb
CHANGED
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'
|
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:
|
|
4
|
+
version: 3.0.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessandro Rodi (Renuo AG)
|
|
@@ -11,36 +11,42 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date:
|
|
14
|
+
date: 2019-03-17 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
|
-
name:
|
|
17
|
+
name: appraisal
|
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
|
19
19
|
requirements:
|
|
20
20
|
- - "~>"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
22
|
+
version: '2.0'
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 2.0.0
|
|
23
26
|
type: :development
|
|
24
27
|
prerelease: false
|
|
25
28
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
29
|
requirements:
|
|
27
30
|
- - "~>"
|
|
28
31
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
32
|
+
version: '2.0'
|
|
33
|
+
- - ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: 2.0.0
|
|
30
36
|
- !ruby/object:Gem::Dependency
|
|
31
|
-
name:
|
|
37
|
+
name: bundler
|
|
32
38
|
requirement: !ruby/object:Gem::Requirement
|
|
33
39
|
requirements:
|
|
34
40
|
- - "~>"
|
|
35
41
|
- !ruby/object:Gem::Version
|
|
36
|
-
version: 0
|
|
42
|
+
version: '2.0'
|
|
37
43
|
type: :development
|
|
38
44
|
prerelease: false
|
|
39
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
46
|
requirements:
|
|
41
47
|
- - "~>"
|
|
42
48
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: 0
|
|
49
|
+
version: '2.0'
|
|
44
50
|
- !ruby/object:Gem::Dependency
|
|
45
51
|
name: rake
|
|
46
52
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -82,25 +88,19 @@ dependencies:
|
|
|
82
88
|
- !ruby/object:Gem::Version
|
|
83
89
|
version: 3.2.0
|
|
84
90
|
- !ruby/object:Gem::Dependency
|
|
85
|
-
name:
|
|
91
|
+
name: rubocop
|
|
86
92
|
requirement: !ruby/object:Gem::Requirement
|
|
87
93
|
requirements:
|
|
88
94
|
- - "~>"
|
|
89
95
|
- !ruby/object:Gem::Version
|
|
90
|
-
version:
|
|
91
|
-
- - ">="
|
|
92
|
-
- !ruby/object:Gem::Version
|
|
93
|
-
version: 2.0.0
|
|
96
|
+
version: 0.63.1
|
|
94
97
|
type: :development
|
|
95
98
|
prerelease: false
|
|
96
99
|
version_requirements: !ruby/object:Gem::Requirement
|
|
97
100
|
requirements:
|
|
98
101
|
- - "~>"
|
|
99
102
|
- !ruby/object:Gem::Version
|
|
100
|
-
version:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: 2.0.0
|
|
103
|
+
version: 0.63.1
|
|
104
104
|
description: Simple authorization solution for Rails. All permissions are stored in
|
|
105
105
|
a single location.
|
|
106
106
|
email: alessandro.rodi@renuo.ch
|
|
@@ -114,6 +114,7 @@ files:
|
|
|
114
114
|
- lib/cancan/ability.rb
|
|
115
115
|
- lib/cancan/ability/actions.rb
|
|
116
116
|
- lib/cancan/ability/rules.rb
|
|
117
|
+
- lib/cancan/ability/strong_parameter_support.rb
|
|
117
118
|
- lib/cancan/conditions_matcher.rb
|
|
118
119
|
- lib/cancan/controller_additions.rb
|
|
119
120
|
- lib/cancan/controller_resource.rb
|
|
@@ -128,12 +129,14 @@ files:
|
|
|
128
129
|
- lib/cancan/model_adapters/active_record_4_adapter.rb
|
|
129
130
|
- lib/cancan/model_adapters/active_record_5_adapter.rb
|
|
130
131
|
- lib/cancan/model_adapters/active_record_adapter.rb
|
|
131
|
-
- lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb
|
|
132
132
|
- lib/cancan/model_adapters/conditions_extractor.rb
|
|
133
|
+
- lib/cancan/model_adapters/conditions_normalizer.rb
|
|
133
134
|
- lib/cancan/model_adapters/default_adapter.rb
|
|
134
135
|
- lib/cancan/model_additions.rb
|
|
136
|
+
- lib/cancan/parameter_validators.rb
|
|
135
137
|
- lib/cancan/rule.rb
|
|
136
138
|
- lib/cancan/rules_compressor.rb
|
|
139
|
+
- lib/cancan/unauthorized_message_resolver.rb
|
|
137
140
|
- lib/cancan/version.rb
|
|
138
141
|
- lib/cancancan.rb
|
|
139
142
|
- lib/generators/cancan/ability/USAGE
|
|
@@ -154,12 +157,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
154
157
|
version: 2.2.0
|
|
155
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
159
|
requirements:
|
|
157
|
-
- - "
|
|
160
|
+
- - ">"
|
|
158
161
|
- !ruby/object:Gem::Version
|
|
159
|
-
version:
|
|
162
|
+
version: 1.3.1
|
|
160
163
|
requirements: []
|
|
161
164
|
rubyforge_project:
|
|
162
|
-
rubygems_version: 2.7.
|
|
165
|
+
rubygems_version: 2.7.6
|
|
163
166
|
signing_key:
|
|
164
167
|
specification_version: 4
|
|
165
168
|
summary: Simple authorization solution for Rails.
|
|
@@ -1,39 +0,0 @@
|
|
|
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.reverse.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
|