cancancan 1.17.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/cancancan.gemspec +10 -11
- data/init.rb +2 -0
- data/lib/cancan/ability/actions.rb +93 -0
- data/lib/cancan/ability/rules.rb +96 -0
- data/lib/cancan/ability/strong_parameter_support.rb +41 -0
- data/lib/cancan/ability.rb +87 -198
- data/lib/cancan/class_matcher.rb +30 -0
- data/lib/cancan/conditions_matcher.rb +147 -0
- data/lib/cancan/config.rb +101 -0
- data/lib/cancan/controller_additions.rb +13 -30
- data/lib/cancan/controller_resource.rb +33 -225
- data/lib/cancan/controller_resource_builder.rb +26 -0
- data/lib/cancan/controller_resource_finder.rb +42 -0
- data/lib/cancan/controller_resource_loader.rb +120 -0
- data/lib/cancan/controller_resource_name_finder.rb +23 -0
- data/lib/cancan/controller_resource_sanitizer.rb +32 -0
- data/lib/cancan/exceptions.rb +24 -4
- data/lib/cancan/matchers.rb +12 -1
- data/lib/cancan/model_adapters/abstract_adapter.rb +22 -1
- data/lib/cancan/model_adapters/active_record_4_adapter.rb +25 -44
- data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +157 -83
- 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 +67 -90
- 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 +15 -10
- 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 +64 -86
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop.yml +0 -39
- data/.rubocop_todo.yml +0 -54
- data/.travis.yml +0 -39
- data/Appraisals +0 -105
- data/CHANGELOG.rdoc +0 -536
- data/CONTRIBUTING.md +0 -23
- data/Gemfile +0 -3
- data/LICENSE +0 -22
- data/README.md +0 -234
- data/Rakefile +0 -13
- data/gemfiles/activerecord_3.2.gemfile +0 -18
- data/gemfiles/activerecord_4.0.gemfile +0 -19
- data/gemfiles/activerecord_4.1.gemfile +0 -19
- data/gemfiles/activerecord_4.2.gemfile +0 -21
- data/gemfiles/activerecord_5.0.gemfile +0 -20
- data/gemfiles/mongoid_2.x.gemfile +0 -18
- data/gemfiles/sequel_3.x.gemfile +0 -18
- data/lib/cancan/inherited_resource.rb +0 -20
- data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
- data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -80
- data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
- data/spec/README.rdoc +0 -27
- data/spec/cancan/ability_spec.rb +0 -553
- data/spec/cancan/controller_additions_spec.rb +0 -164
- data/spec/cancan/controller_resource_spec.rb +0 -645
- data/spec/cancan/exceptions_spec.rb +0 -58
- data/spec/cancan/inherited_resource_spec.rb +0 -71
- data/spec/cancan/matchers_spec.rb +0 -29
- data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -160
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -415
- data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -246
- data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -129
- data/spec/cancan/rule_spec.rb +0 -52
- data/spec/matchers.rb +0 -13
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -27
- data/spec/support/ability.rb +0 -6
@@ -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
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CanCan
|
4
|
+
module ConditionsMatcher
|
5
|
+
# Matches the block or conditions hash
|
6
|
+
def matches_conditions?(action, subject, attribute = nil, *extra_args)
|
7
|
+
return call_block_with_all(action, subject, extra_args) if @match_all
|
8
|
+
return matches_block_conditions(subject, attribute, *extra_args) if @block
|
9
|
+
return matches_non_block_conditions(subject) unless conditions_empty?
|
10
|
+
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def subject_class?(subject)
|
17
|
+
klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
|
18
|
+
[Class, Module].include? klass
|
19
|
+
end
|
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)
|
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
|
+
|
35
|
+
# Don't stop at "cannot" definitions when there are conditions.
|
36
|
+
@base_behavior
|
37
|
+
end
|
38
|
+
|
39
|
+
def nested_subject_matches_conditions?(subject_hash)
|
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))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Checks if the given subject matches the given conditions hash.
|
55
|
+
# This behavior can be overridden by a model adapter by defining two class methods:
|
56
|
+
# override_matching_for_conditions?(subject, conditions) and
|
57
|
+
# matches_conditions_hash?(subject, conditions)
|
58
|
+
def matches_conditions_hash?(subject, conditions = @conditions)
|
59
|
+
return true if conditions.is_a?(Hash) && conditions.empty?
|
60
|
+
|
61
|
+
adapter = model_adapter(subject)
|
62
|
+
|
63
|
+
if adapter.override_conditions_hash_matching?(subject, conditions)
|
64
|
+
return adapter.matches_conditions_hash?(subject, conditions)
|
65
|
+
end
|
66
|
+
|
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
|
78
|
+
end
|
79
|
+
|
80
|
+
def matches_hash_conditions?(adapter, subject, conditions)
|
81
|
+
conditions.all? do |name, value|
|
82
|
+
if adapter.override_condition_matching?(subject, name, value)
|
83
|
+
adapter.matches_condition?(subject, name, value)
|
84
|
+
else
|
85
|
+
condition_match?(subject.send(name), value)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def condition_match?(attribute, value)
|
91
|
+
case value
|
92
|
+
when Hash
|
93
|
+
hash_condition_match?(attribute, value)
|
94
|
+
when Range
|
95
|
+
value.cover?(attribute)
|
96
|
+
when Enumerable
|
97
|
+
value.include?(attribute)
|
98
|
+
else
|
99
|
+
attribute == value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def hash_condition_match?(attribute, value)
|
104
|
+
if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
|
105
|
+
array_like_matches_condition_hash?(attribute, value)
|
106
|
+
else
|
107
|
+
attribute && matches_conditions_hash?(attribute, value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
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)
|
129
|
+
if subject.class == Class
|
130
|
+
@block.call(action, subject, nil, *extra_args)
|
131
|
+
else
|
132
|
+
@block.call(action, subject.class, subject, *extra_args)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def model_adapter(subject)
|
137
|
+
CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
|
138
|
+
end
|
139
|
+
|
140
|
+
def conditions_empty?
|
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?
|
145
|
+
end
|
146
|
+
end
|
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.
|
@@ -40,7 +42,7 @@ module CanCan
|
|
40
42
|
# private
|
41
43
|
#
|
42
44
|
# def find_book_by_permalink
|
43
|
-
# @book = Book.find_by_permalink!(params[:id)
|
45
|
+
# @book = Book.find_by_permalink!(params[:id])
|
44
46
|
# end
|
45
47
|
# end
|
46
48
|
#
|
@@ -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.
|
@@ -256,18 +258,17 @@ module CanCan
|
|
256
258
|
# check_authorization :unless => :devise_controller?
|
257
259
|
#
|
258
260
|
def check_authorization(options = {})
|
259
|
-
method_name = active_support_4? ? :after_action : :after_filter
|
260
|
-
|
261
261
|
block = proc do |controller|
|
262
262
|
next if controller.instance_variable_defined?(:@_authorized)
|
263
263
|
next if options[:if] && !controller.send(options[:if])
|
264
264
|
next if options[:unless] && controller.send(options[:unless])
|
265
|
+
|
265
266
|
raise AuthorizationNotPerformed,
|
266
|
-
'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. ' \
|
267
268
|
'Add skip_authorization_check to bypass this check.'
|
268
269
|
end
|
269
270
|
|
270
|
-
send(
|
271
|
+
send(:after_action, options.slice(:only, :except), &block)
|
271
272
|
end
|
272
273
|
|
273
274
|
# Call this in the class of a controller to skip the check_authorization behavior on the actions.
|
@@ -278,37 +279,23 @@ module CanCan
|
|
278
279
|
#
|
279
280
|
# Any arguments are passed to the +before_action+ it triggers.
|
280
281
|
def skip_authorization_check(*args)
|
281
|
-
method_name = active_support_4? ? :before_action : :before_filter
|
282
282
|
block = proc { |controller| controller.instance_variable_set(:@_authorized, true) }
|
283
|
-
send(
|
284
|
-
end
|
285
|
-
|
286
|
-
def skip_authorization(*_args)
|
287
|
-
raise ImplementationRemoved,
|
288
|
-
'The CanCan skip_authorization method has been renamed to skip_authorization_check. '\
|
289
|
-
'Please update your code.'
|
283
|
+
send(:before_action, *args, &block)
|
290
284
|
end
|
291
285
|
|
292
286
|
def cancan_resource_class
|
293
|
-
|
294
|
-
InheritedResource
|
295
|
-
else
|
296
|
-
ControllerResource
|
297
|
-
end
|
287
|
+
ControllerResource
|
298
288
|
end
|
299
289
|
|
300
290
|
def cancan_skipper
|
301
|
-
|
302
|
-
end
|
303
|
-
|
304
|
-
def active_support_4?
|
305
|
-
ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new('4')
|
291
|
+
self._cancan_skipper ||= { authorize: {}, load: {} }
|
306
292
|
end
|
307
293
|
end
|
308
294
|
|
309
295
|
def self.included(base)
|
310
296
|
base.extend ClassMethods
|
311
297
|
base.helper_method :can?, :cannot?, :current_ability if base.respond_to? :helper_method
|
298
|
+
base.class_attribute :_cancan_skipper
|
312
299
|
end
|
313
300
|
|
314
301
|
# Raises a CanCan::AccessDenied exception if the current_ability cannot
|
@@ -352,10 +339,6 @@ module CanCan
|
|
352
339
|
current_ability.authorize!(*args)
|
353
340
|
end
|
354
341
|
|
355
|
-
def unauthorized!(_message = nil)
|
356
|
-
raise ImplementationRemoved, 'The unauthorized! method has been removed from CanCan, use authorize! instead.'
|
357
|
-
end
|
358
|
-
|
359
342
|
# Creates and returns the current user's ability and caches it. If you
|
360
343
|
# want to override how the Ability is defined then this is the place.
|
361
344
|
# Just define the method in the controller to change behavior.
|
@@ -404,8 +387,8 @@ module CanCan
|
|
404
387
|
end
|
405
388
|
end
|
406
389
|
|
407
|
-
if defined?
|
408
|
-
|
390
|
+
if defined? ActiveSupport
|
391
|
+
ActiveSupport.on_load(:action_controller) do
|
409
392
|
include CanCan::ControllerAdditions
|
410
393
|
end
|
411
394
|
end
|