cancancan 1.17.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +10 -11
  3. data/init.rb +2 -0
  4. data/lib/cancan/ability/actions.rb +93 -0
  5. data/lib/cancan/ability/rules.rb +96 -0
  6. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  7. data/lib/cancan/ability.rb +87 -198
  8. data/lib/cancan/class_matcher.rb +30 -0
  9. data/lib/cancan/conditions_matcher.rb +147 -0
  10. data/lib/cancan/config.rb +101 -0
  11. data/lib/cancan/controller_additions.rb +13 -30
  12. data/lib/cancan/controller_resource.rb +33 -225
  13. data/lib/cancan/controller_resource_builder.rb +26 -0
  14. data/lib/cancan/controller_resource_finder.rb +42 -0
  15. data/lib/cancan/controller_resource_loader.rb +120 -0
  16. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  17. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  18. data/lib/cancan/exceptions.rb +24 -4
  19. data/lib/cancan/matchers.rb +12 -1
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +22 -1
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +25 -44
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +157 -83
  24. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  25. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  26. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  27. data/lib/cancan/model_adapters/sti_normalizer.rb +47 -0
  28. data/lib/cancan/model_adapters/strategies/base.rb +40 -0
  29. data/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +93 -0
  30. data/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +31 -0
  31. data/lib/cancan/model_adapters/strategies/left_join.rb +11 -0
  32. data/lib/cancan/model_adapters/strategies/subquery.rb +18 -0
  33. data/lib/cancan/model_additions.rb +6 -2
  34. data/lib/cancan/parameter_validators.rb +9 -0
  35. data/lib/cancan/relevant.rb +29 -0
  36. data/lib/cancan/rule.rb +67 -90
  37. data/lib/cancan/rules_compressor.rb +23 -0
  38. data/lib/cancan/sti_detector.rb +12 -0
  39. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  40. data/lib/cancan/version.rb +3 -1
  41. data/lib/cancan.rb +15 -10
  42. data/lib/cancancan.rb +2 -0
  43. data/lib/generators/cancan/ability/ability_generator.rb +3 -1
  44. data/lib/generators/cancan/ability/templates/ability.rb +9 -9
  45. metadata +64 -86
  46. data/.gitignore +0 -15
  47. data/.rspec +0 -1
  48. data/.rubocop.yml +0 -39
  49. data/.rubocop_todo.yml +0 -54
  50. data/.travis.yml +0 -39
  51. data/Appraisals +0 -105
  52. data/CHANGELOG.rdoc +0 -536
  53. data/CONTRIBUTING.md +0 -23
  54. data/Gemfile +0 -3
  55. data/LICENSE +0 -22
  56. data/README.md +0 -234
  57. data/Rakefile +0 -13
  58. data/gemfiles/activerecord_3.2.gemfile +0 -18
  59. data/gemfiles/activerecord_4.0.gemfile +0 -19
  60. data/gemfiles/activerecord_4.1.gemfile +0 -19
  61. data/gemfiles/activerecord_4.2.gemfile +0 -21
  62. data/gemfiles/activerecord_5.0.gemfile +0 -20
  63. data/gemfiles/mongoid_2.x.gemfile +0 -18
  64. data/gemfiles/sequel_3.x.gemfile +0 -18
  65. data/lib/cancan/inherited_resource.rb +0 -20
  66. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
  67. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -80
  68. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  69. data/spec/README.rdoc +0 -27
  70. data/spec/cancan/ability_spec.rb +0 -553
  71. data/spec/cancan/controller_additions_spec.rb +0 -164
  72. data/spec/cancan/controller_resource_spec.rb +0 -645
  73. data/spec/cancan/exceptions_spec.rb +0 -58
  74. data/spec/cancan/inherited_resource_spec.rb +0 -71
  75. data/spec/cancan/matchers_spec.rb +0 -29
  76. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -160
  77. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -415
  78. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  79. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -246
  80. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -129
  81. data/spec/cancan/rule_spec.rb +0 -52
  82. data/spec/matchers.rb +0 -13
  83. data/spec/spec.opts +0 -2
  84. data/spec/spec_helper.rb +0 -27
  85. 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 +authorized+! or +authorize_resource+ call.
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(method_name, options.slice(:only, :except), &block)
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(method_name, *args, &block)
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
- if ancestors.map(&:to_s).include? 'InheritedResources::Actions'
294
- InheritedResource
295
- else
296
- ControllerResource
297
- end
287
+ ControllerResource
298
288
  end
299
289
 
300
290
  def cancan_skipper
301
- @_cancan_skipper ||= { authorize: {}, load: {} }
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? ActionController::Base
408
- ActionController::Base.class_eval do
390
+ if defined? ActiveSupport
391
+ ActiveSupport.on_load(:action_controller) do
409
392
  include CanCan::ControllerAdditions
410
393
  end
411
394
  end