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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/cancancan.gemspec +5 -5
  3. data/init.rb +2 -0
  4. data/lib/cancan.rb +4 -0
  5. data/lib/cancan/ability.rb +49 -23
  6. data/lib/cancan/ability/actions.rb +2 -0
  7. data/lib/cancan/ability/rules.rb +14 -6
  8. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  9. data/lib/cancan/conditions_matcher.rb +20 -10
  10. data/lib/cancan/controller_additions.rb +4 -1
  11. data/lib/cancan/controller_resource.rb +6 -0
  12. data/lib/cancan/controller_resource_builder.rb +2 -0
  13. data/lib/cancan/controller_resource_finder.rb +2 -0
  14. data/lib/cancan/controller_resource_loader.rb +4 -0
  15. data/lib/cancan/controller_resource_name_finder.rb +2 -0
  16. data/lib/cancan/controller_resource_sanitizer.rb +2 -0
  17. data/lib/cancan/exceptions.rb +10 -2
  18. data/lib/cancan/matchers.rb +3 -0
  19. data/lib/cancan/model_adapters/abstract_adapter.rb +2 -0
  20. data/lib/cancan/model_adapters/active_record_4_adapter.rb +24 -21
  21. data/lib/cancan/model_adapters/active_record_5_adapter.rb +11 -18
  22. data/lib/cancan/model_adapters/active_record_adapter.rb +47 -12
  23. data/lib/cancan/model_adapters/conditions_extractor.rb +3 -3
  24. data/lib/cancan/model_adapters/conditions_normalizer.rb +45 -0
  25. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  26. data/lib/cancan/model_additions.rb +2 -0
  27. data/lib/cancan/parameter_validators.rb +9 -0
  28. data/lib/cancan/rule.rb +50 -12
  29. data/lib/cancan/rules_compressor.rb +3 -0
  30. data/lib/cancan/unauthorized_message_resolver.rb +22 -0
  31. data/lib/cancan/version.rb +3 -1
  32. data/lib/cancancan.rb +2 -0
  33. data/lib/generators/cancan/ability/ability_generator.rb +3 -1
  34. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  35. metadata +24 -21
  36. 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: f63f1d6ab266068caab6e7baf2b628ae72a7e03a134116eda91473861b9df359
4
- data.tar.gz: fbf6337f058ece9a2f01c990a55398489e0ea56888f0a5ce3b9f8d5e203c31ae
3
+ metadata.gz: d745990a81dbfa8dfaf8802b76bbe6a09d11291562383b429320c1db96355e20
4
+ data.tar.gz: 579e1848a8c8a5c096a747306c7aa08b2a156bcd404f2c6b6d5b418c02026324
5
5
  SHA512:
6
- metadata.gz: 24fcc98ce0592b263add65cf0bc75a7020d75777fea7c6902216f97bbc9c13a36b4d379194a142cd8a5e5a24dc1ae82c82a61d6d99143c30a9afe11133048528
7
- data.tar.gz: 8873b440698a941314f67a748db86c2abe33e89417ae54d9f35769c29f904edc9eff5736d0bf55d53badc494b9bca9e0ac1761c1920067238628d23dc8e0c643
6
+ metadata.gz: 91d38cda2159ce1c061101d599aae4abf8bb1358bfd1fe43b4432e7eba0ce9e4002b52d055a1e693138a05e185b70fbc504b6f696e2d02d6e8086b8a6f9976ac
7
+ data.tar.gz: eb23bb3c5b8a3feaa55d27226443fa8e3fd15175a23d7fa6810c4253ca8646fdbd88150138e3bc81e473d7a27302584ec5ebdb5e31e0fd1dfdf54458b52b3fea
@@ -1,6 +1,6 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
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 'bundler', '~> 1.3'
24
- s.add_development_dependency 'rubocop', '~> 0.48.1'
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 'appraisal', '~> 2.0', '>= 2.0.0'
27
+ s.add_development_dependency 'rubocop', '~> 0.63.1'
28
28
  end
data/init.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cancan'
@@ -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'
@@ -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, conditions = nil, &block)
138
- add_rule(Rule.new(true, action, subject, conditions, block))
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, conditions = nil, &block)
154
- add_rule(Rule.new(false, action, subject, conditions, block))
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: array_of_objects
264
+ # action: { subject: [attributes] }
243
265
  # }
244
266
  def permissions
245
- permissions_list = { can: {}, cannot: {} }
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
- permissions_list[container][action] ||= []
254
- permissions_list[container][action] += rule.subjects.map(&:to_s)
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module Ability
3
5
  module Actions
@@ -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
- " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
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).each do |rule|
64
- if rule.only_block?
65
- raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
66
- " The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
67
- end
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 @block.call(subject, *extra_args) if @block && !subject_class?(subject)
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
- klass == Class || klass == Module
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 @conditions.is_a?(Hash)
19
- return nested_subject_matches_conditions?(subject) if subject.class == Hash
20
- return matches_conditions_hash?(subject) unless subject_class?(subject)
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
- conditions_empty? ? true : @base_behavior
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 +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.
@@ -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
  module CanCan
2
4
  module ControllerResourceBuilder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceFinder
3
5
  protected
@@ -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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceNameFinder
3
5
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ControllerResourceSanitizer
3
5
  protected
@@ -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 authorized!
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#authorized! for more information on rescuing from this exception
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
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ModelAdapters
3
5
  class AbstractAdapter
@@ -1,27 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ModelAdapters
3
- class ActiveRecord4Adapter < AbstractAdapter
4
- include ActiveRecordAdapter
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
- # TODO: this should be private
10
- def self.override_condition_matching?(subject, name, _value)
11
- subject.class.defined_enums.include?(name.to_s)
12
- end
8
+ class << self
9
+ def for_class?(model_class)
10
+ version_lower?('5.0.0') && model_class <= ActiveRecord::Base
11
+ end
13
12
 
14
- # TODO: this should be private
15
- def self.matches_condition?(subject, name, value)
16
- # Get the mapping from enum strings to values.
17
- enum = subject.class.send(name.to_s.pluralize)
18
- # Get the value of the attribute as an integer.
19
- attribute = enum[subject.send(name)]
20
- # Check to see if the value matches the condition.
21
- if value.is_a?(Enumerable)
22
- value.include? attribute
23
- else
24
- attribute == value
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 ActiveRecord::VERSION::MINOR >= 2 && conditions.is_a?(Hash)
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
- ActiveRecord::VERSION::MAJOR == 5 && model_class <= ActiveRecord::Base
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
- if value.is_a?(Enumerable)
17
- value.map(&:to_s).include? attribute
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.includes(joins).references(joins) if joins.present?
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 do |b|
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(b)
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 ActiveRecord::VERSION::MINOR >= 2
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(b, collector).value
56
+ connection.visitor.accept(node, collector).value
64
57
  else
65
- @model_class.send(:connection).visitor.compile(b)
58
+ @model_class.send(:connection).visitor.compile(node)
66
59
  end
67
60
  end
68
61
  end
@@ -1,10 +1,23 @@
1
- require_relative 'can_can/model_adapters/active_record_adapter/joins.rb'
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
- module ActiveRecordAdapter
7
- include CanCan::ModelAdapters::ActiveRecordAdapter::Joins
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
- mergeable_conditions? ? build_relation(conditions) : build_relation(*@rules.map(&:conditions))
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
- def mergeable_conditions?
54
- @rules.find(&:unmergeable?).blank?
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 = @rules.map(&:conditions).compact
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 = @rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  module ModelAdapters
3
5
  class DefaultAdapter < AbstractAdapter
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
4
  # This module adds the accessible_by class method to a model. It is included in the model adapters.
3
5
  module ModelAdditions
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CanCan
4
+ module ParameterValidators
5
+ def valid_attribute_param?(attribute)
6
+ attribute.nil? || attribute.is_a?(Symbol) || (attribute.is_a?(Array) && attribute.all? { |a| a.is_a?(Symbol) })
7
+ end
8
+ end
9
+ end
@@ -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
- attr_reader :base_behavior, :subjects, :actions, :conditions
9
- attr_writer :expanded_actions
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, conditions, block)
16
- both_block_and_hash_error = 'You are not able to supply a block with a hash of conditions in '\
17
- "#{action} #{subject} ability. Use either one."
18
- raise Error, both_block_and_hash_error if conditions.is_a?(Hash) && block
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
- @conditions = conditions || {}
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
- [nil, false, [], {}, '', ' '].include? @conditions
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 action, not necessarily the conditions
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 unmergeable?
54
- @conditions.respond_to?(:keys) && @conditions.present? &&
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CanCan
2
- VERSION = '2.3.0'.freeze
4
+ VERSION = '3.0.0.rc1'.freeze
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'cancan'
2
4
 
3
5
  module CanCanCan
@@ -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('../templates', __FILE__)
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'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Ability
2
4
  include CanCan::Ability
3
5
 
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: 2.3.0
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: 2018-09-16 00:00:00.000000000 Z
14
+ date: 2019-03-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
- name: bundler
17
+ name: appraisal
18
18
  requirement: !ruby/object:Gem::Requirement
19
19
  requirements:
20
20
  - - "~>"
21
21
  - !ruby/object:Gem::Version
22
- version: '1.3'
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: '1.3'
32
+ version: '2.0'
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 2.0.0
30
36
  - !ruby/object:Gem::Dependency
31
- name: rubocop
37
+ name: bundler
32
38
  requirement: !ruby/object:Gem::Requirement
33
39
  requirements:
34
40
  - - "~>"
35
41
  - !ruby/object:Gem::Version
36
- version: 0.48.1
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.48.1
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: appraisal
91
+ name: rubocop
86
92
  requirement: !ruby/object:Gem::Requirement
87
93
  requirements:
88
94
  - - "~>"
89
95
  - !ruby/object:Gem::Version
90
- version: '2.0'
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: '2.0'
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: '0'
162
+ version: 1.3.1
160
163
  requirements: []
161
164
  rubyforge_project:
162
- rubygems_version: 2.7.4
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