cancancan 2.3.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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