cancancan 1.10.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +19 -21
  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 +114 -146
  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 +38 -41
  12. data/lib/cancan/controller_resource.rb +59 -215
  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 +25 -5
  19. data/lib/cancan/matchers.rb +17 -3
  20. data/lib/cancan/model_adapters/abstract_adapter.rb +30 -9
  21. data/lib/cancan/model_adapters/active_record_4_adapter.rb +43 -15
  22. data/lib/cancan/model_adapters/active_record_5_adapter.rb +61 -0
  23. data/lib/cancan/model_adapters/active_record_adapter.rb +157 -82
  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 -3
  34. data/lib/cancan/parameter_validators.rb +9 -0
  35. data/lib/cancan/relevant.rb +29 -0
  36. data/lib/cancan/rule.rb +79 -91
  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 +16 -12
  42. data/lib/cancancan.rb +2 -0
  43. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  44. data/lib/generators/cancan/ability/templates/ability.rb +9 -9
  45. metadata +82 -93
  46. data/.gitignore +0 -15
  47. data/.rspec +0 -1
  48. data/.travis.yml +0 -48
  49. data/Appraisals +0 -135
  50. data/CHANGELOG.rdoc +0 -495
  51. data/CONTRIBUTING.md +0 -23
  52. data/Gemfile +0 -3
  53. data/LICENSE +0 -22
  54. data/README.md +0 -197
  55. data/Rakefile +0 -9
  56. data/gemfiles/activerecord_3.0.gemfile +0 -18
  57. data/gemfiles/activerecord_3.1.gemfile +0 -20
  58. data/gemfiles/activerecord_3.2.gemfile +0 -20
  59. data/gemfiles/activerecord_4.0.gemfile +0 -17
  60. data/gemfiles/activerecord_4.1.gemfile +0 -17
  61. data/gemfiles/activerecord_4.2.gemfile +0 -17
  62. data/gemfiles/datamapper_1.x.gemfile +0 -14
  63. data/gemfiles/mongoid_2.x.gemfile +0 -20
  64. data/gemfiles/sequel_3.x.gemfile +0 -20
  65. data/lib/cancan/inherited_resource.rb +0 -20
  66. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -47
  67. data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
  68. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  69. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  70. data/spec/README.rdoc +0 -27
  71. data/spec/cancan/ability_spec.rb +0 -487
  72. data/spec/cancan/controller_additions_spec.rb +0 -141
  73. data/spec/cancan/controller_resource_spec.rb +0 -648
  74. data/spec/cancan/exceptions_spec.rb +0 -58
  75. data/spec/cancan/inherited_resource_spec.rb +0 -71
  76. data/spec/cancan/matchers_spec.rb +0 -29
  77. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -40
  78. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -446
  79. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
  80. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  81. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  82. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  83. data/spec/cancan/rule_spec.rb +0 -52
  84. data/spec/matchers.rb +0 -13
  85. data/spec/spec.opts +0 -2
  86. data/spec/spec_helper.rb +0 -27
  87. data/spec/support/ability.rb +0 -7
@@ -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,5 +1,6 @@
1
- module CanCan
1
+ # frozen_string_literal: true
2
2
 
3
+ module CanCan
3
4
  # This module is automatically included into all controllers.
4
5
  # It also makes the "can?" and "cannot?" methods available to all views.
5
6
  module ControllerAdditions
@@ -12,7 +13,7 @@ module CanCan
12
13
  # end
13
14
  #
14
15
  def load_and_authorize_resource(*args)
15
- cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
16
+ cancan_resource_class.add_before_action(self, :load_and_authorize_resource, *args)
16
17
  end
17
18
 
18
19
  # Sets up a before filter which loads the model resource into an instance variable.
@@ -32,16 +33,16 @@ module CanCan
32
33
  # end
33
34
  #
34
35
  # A resource is not loaded if the instance variable is already set. This makes it easy to override
35
- # the behavior through a before_filter on certain actions.
36
+ # the behavior through a before_action on certain actions.
36
37
  #
37
38
  # class BooksController < ApplicationController
38
- # before_filter :find_book_by_permalink, :only => :show
39
+ # before_action :find_book_by_permalink, :only => :show
39
40
  # load_resource
40
41
  #
41
42
  # private
42
43
  #
43
44
  # def find_book_by_permalink
44
- # @book = Book.find_by_permalink!(params[:id)
45
+ # @book = Book.find_by_permalink!(params[:id])
45
46
  # end
46
47
  # end
47
48
  #
@@ -72,8 +73,8 @@ module CanCan
72
73
  # Load this resource through another one. This should match the name of the parent instance variable or method.
73
74
  #
74
75
  # [:+through_association+]
75
- # The name of the association to fetch the child records through the parent resource. This is normally not needed
76
- # because it defaults to the pluralized resource name.
76
+ # The name of the association to fetch the child records through the parent resource.
77
+ # This is normally not needed because it defaults to the pluralized resource name.
77
78
  #
78
79
  # [:+shallow+]
79
80
  # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
@@ -82,8 +83,8 @@ module CanCan
82
83
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
83
84
  #
84
85
  # [:+parent+]
85
- # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
86
- # name is given which does not match the controller.
86
+ # True or false depending on if the resource is considered a parent resource.
87
+ # This defaults to +true+ if a resource name is given which does not match the controller.
87
88
  #
88
89
  # [:+class+]
89
90
  # The class to use for the model (string or constant).
@@ -115,10 +116,10 @@ module CanCan
115
116
  # load_resource :new => :build
116
117
  #
117
118
  # [:+prepend+]
118
- # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
119
+ # Passing +true+ will use prepend_before_action instead of a normal before_action.
119
120
  #
120
121
  def load_resource(*args)
121
- cancan_resource_class.add_before_filter(self, :load_resource, *args)
122
+ cancan_resource_class.add_before_action(self, :load_resource, *args)
122
123
  end
123
124
 
124
125
  # Sets up a before filter which authorizes the resource using the instance variable.
@@ -160,8 +161,8 @@ module CanCan
160
161
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
161
162
  #
162
163
  # [:+parent+]
163
- # True or false depending on if the resource is considered a parent resource. This defaults to +true+ if a resource
164
- # name is given which does not match the controller.
164
+ # True or false depending on if the resource is considered a parent resource.
165
+ # This defaults to +true+ if a resource name is given which does not match the controller.
165
166
  #
166
167
  # [:+class+]
167
168
  # The class to use for the model (string or constant). This passed in when the instance variable is not set.
@@ -174,10 +175,10 @@ module CanCan
174
175
  # Authorize conditions on this parent resource when instance isn't available.
175
176
  #
176
177
  # [:+prepend+]
177
- # Passing +true+ will use prepend_before_filter instead of a normal before_filter.
178
+ # Passing +true+ will use prepend_before_action instead of a normal before_action.
178
179
  #
179
180
  def authorize_resource(*args)
180
- cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
181
+ cancan_resource_class.add_before_action(self, :authorize_resource, *args)
181
182
  end
182
183
 
183
184
  # Skip both the loading and authorization behavior of CanCan for this given controller. This is primarily
@@ -226,8 +227,9 @@ module CanCan
226
227
  cancan_skipper[:authorize][name] = options
227
228
  end
228
229
 
229
- # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
230
- # If neither of these authorization methods are called, a CanCan::AuthorizationNotPerformed exception will be raised.
230
+ # Add this to a controller to ensure it performs authorization through +authorize+! or +authorize_resource+ call.
231
+ # If neither of these authorization methods are called,
232
+ # a CanCan::AuthorizationNotPerformed exception will be raised.
231
233
  # This is normally added to the ApplicationController to ensure all controller actions do authorization.
232
234
  #
233
235
  # class ApplicationController < ActionController::Base
@@ -244,22 +246,29 @@ module CanCan
244
246
  # Does not apply to given actions.
245
247
  #
246
248
  # [:+if+]
247
- # Supply the name of a controller method to be called. The authorization check only takes place if this returns true.
249
+ # Supply the name of a controller method to be called.
250
+ # The authorization check only takes place if this returns true.
248
251
  #
249
252
  # check_authorization :if => :admin_controller?
250
253
  #
251
254
  # [:+unless+]
252
- # Supply the name of a controller method to be called. The authorization check only takes place if this returns false.
255
+ # Supply the name of a controller method to be called.
256
+ # The authorization check only takes place if this returns false.
253
257
  #
254
258
  # check_authorization :unless => :devise_controller?
255
259
  #
256
260
  def check_authorization(options = {})
257
- self.after_filter(options.slice(:only, :except)) do |controller|
261
+ block = proc do |controller|
258
262
  next if controller.instance_variable_defined?(:@_authorized)
259
263
  next if options[:if] && !controller.send(options[:if])
260
264
  next if options[:unless] && controller.send(options[:unless])
261
- raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
265
+
266
+ raise AuthorizationNotPerformed,
267
+ 'This action failed the check_authorization because it does not authorize_resource. ' \
268
+ 'Add skip_authorization_check to bypass this check.'
262
269
  end
270
+
271
+ send(:after_action, options.slice(:only, :except), &block)
263
272
  end
264
273
 
265
274
  # Call this in the class of a controller to skip the check_authorization behavior on the actions.
@@ -268,33 +277,25 @@ module CanCan
268
277
  # skip_authorization_check :only => :index
269
278
  # end
270
279
  #
271
- # Any arguments are passed to the +before_filter+ it triggers.
280
+ # Any arguments are passed to the +before_action+ it triggers.
272
281
  def skip_authorization_check(*args)
273
- self.before_filter(*args) do |controller|
274
- controller.instance_variable_set(:@_authorized, true)
275
- end
276
- end
277
-
278
- def skip_authorization(*args)
279
- raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
282
+ block = proc { |controller| controller.instance_variable_set(:@_authorized, true) }
283
+ send(:before_action, *args, &block)
280
284
  end
281
285
 
282
286
  def cancan_resource_class
283
- if ancestors.map(&:to_s).include? "InheritedResources::Actions"
284
- InheritedResource
285
- else
286
- ControllerResource
287
- end
287
+ ControllerResource
288
288
  end
289
289
 
290
290
  def cancan_skipper
291
- @_cancan_skipper ||= {:authorize => {}, :load => {}}
291
+ self._cancan_skipper ||= { authorize: {}, load: {} }
292
292
  end
293
293
  end
294
294
 
295
295
  def self.included(base)
296
296
  base.extend ClassMethods
297
297
  base.helper_method :can?, :cannot?, :current_ability if base.respond_to? :helper_method
298
+ base.class_attribute :_cancan_skipper
298
299
  end
299
300
 
300
301
  # Raises a CanCan::AccessDenied exception if the current_ability cannot
@@ -338,10 +339,6 @@ module CanCan
338
339
  current_ability.authorize!(*args)
339
340
  end
340
341
 
341
- def unauthorized!(message = nil)
342
- raise ImplementationRemoved, "The unauthorized! method has been removed from CanCan, use authorize! instead."
343
- end
344
-
345
342
  # Creates and returns the current user's ability and caches it. If you
346
343
  # want to override how the Ability is defined then this is the place.
347
344
  # Just define the method in the controller to change behavior.
@@ -390,8 +387,8 @@ module CanCan
390
387
  end
391
388
  end
392
389
 
393
- if defined? ActionController::Base
394
- ActionController::Base.class_eval do
390
+ if defined? ActiveSupport
391
+ ActiveSupport.on_load(:action_controller) do
395
392
  include CanCan::ControllerAdditions
396
393
  end
397
394
  end