cancancan 1.11.0 → 2.3.0

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 (70) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +15 -19
  3. data/lib/cancan/ability/actions.rb +91 -0
  4. data/lib/cancan/ability/rules.rb +85 -0
  5. data/lib/cancan/ability.rb +74 -136
  6. data/lib/cancan/conditions_matcher.rb +93 -0
  7. data/lib/cancan/controller_additions.rb +34 -40
  8. data/lib/cancan/controller_resource.rb +47 -212
  9. data/lib/cancan/controller_resource_builder.rb +24 -0
  10. data/lib/cancan/controller_resource_finder.rb +40 -0
  11. data/lib/cancan/controller_resource_loader.rb +116 -0
  12. data/lib/cancan/controller_resource_name_finder.rb +21 -0
  13. data/lib/cancan/controller_resource_sanitizer.rb +30 -0
  14. data/lib/cancan/exceptions.rb +7 -3
  15. data/lib/cancan/matchers.rb +12 -3
  16. data/lib/cancan/model_adapters/abstract_adapter.rb +8 -8
  17. data/lib/cancan/model_adapters/active_record_4_adapter.rb +33 -10
  18. data/lib/cancan/model_adapters/active_record_5_adapter.rb +70 -0
  19. data/lib/cancan/model_adapters/active_record_adapter.rb +41 -81
  20. data/lib/cancan/model_adapters/can_can/model_adapters/active_record_adapter/joins.rb +39 -0
  21. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  22. data/lib/cancan/model_additions.rb +0 -1
  23. data/lib/cancan/rule.rb +36 -92
  24. data/lib/cancan/rules_compressor.rb +20 -0
  25. data/lib/cancan/version.rb +1 -1
  26. data/lib/cancan.rb +5 -12
  27. data/lib/generators/cancan/ability/ability_generator.rb +1 -1
  28. metadata +54 -65
  29. data/.gitignore +0 -15
  30. data/.rspec +0 -1
  31. data/.travis.yml +0 -55
  32. data/Appraisals +0 -136
  33. data/CHANGELOG.rdoc +0 -503
  34. data/CONTRIBUTING.md +0 -23
  35. data/Gemfile +0 -3
  36. data/LICENSE +0 -22
  37. data/README.md +0 -188
  38. data/Rakefile +0 -9
  39. data/gemfiles/activerecord_3.0.gemfile +0 -18
  40. data/gemfiles/activerecord_3.1.gemfile +0 -20
  41. data/gemfiles/activerecord_3.2.gemfile +0 -20
  42. data/gemfiles/activerecord_4.0.gemfile +0 -17
  43. data/gemfiles/activerecord_4.1.gemfile +0 -17
  44. data/gemfiles/activerecord_4.2.gemfile +0 -18
  45. data/gemfiles/datamapper_1.x.gemfile +0 -14
  46. data/gemfiles/mongoid_2.x.gemfile +0 -20
  47. data/gemfiles/sequel_3.x.gemfile +0 -20
  48. data/lib/cancan/inherited_resource.rb +0 -20
  49. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -47
  50. data/lib/cancan/model_adapters/data_mapper_adapter.rb +0 -34
  51. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -54
  52. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  53. data/spec/README.rdoc +0 -27
  54. data/spec/cancan/ability_spec.rb +0 -487
  55. data/spec/cancan/controller_additions_spec.rb +0 -141
  56. data/spec/cancan/controller_resource_spec.rb +0 -632
  57. data/spec/cancan/exceptions_spec.rb +0 -58
  58. data/spec/cancan/inherited_resource_spec.rb +0 -71
  59. data/spec/cancan/matchers_spec.rb +0 -29
  60. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -85
  61. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -446
  62. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +0 -119
  63. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  64. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -227
  65. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  66. data/spec/cancan/rule_spec.rb +0 -52
  67. data/spec/matchers.rb +0 -13
  68. data/spec/spec.opts +0 -2
  69. data/spec/spec_helper.rb +0 -28
  70. data/spec/support/ability.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 31c960036e585081371697da5c478888f7f5f03d
4
- data.tar.gz: c0ecbe3789eae4ec649037153b750749bc5c6279
2
+ SHA256:
3
+ metadata.gz: f63f1d6ab266068caab6e7baf2b628ae72a7e03a134116eda91473861b9df359
4
+ data.tar.gz: fbf6337f058ece9a2f01c990a55398489e0ea56888f0a5ce3b9f8d5e203c31ae
5
5
  SHA512:
6
- metadata.gz: 3bf8dcc830c31f9b2b0658dd25f3995358d7ef2150a054a85646b766eb1e202c1c949a75ef618bdb0f772a8b5d9a10a3b35c9c392865092e1761f2a1693237cd
7
- data.tar.gz: 7eebcdd0f7a211f958279848dde7a076f2a9c048f110096ecf939bd85731750dda57a5e6b50589fbe1c3708b107117f047049bdce651756955a23fad9a247c58
6
+ metadata.gz: 24fcc98ce0592b263add65cf0bc75a7020d75777fea7c6902216f97bbc9c13a36b4d379194a142cd8a5e5a24dc1ae82c82a61d6d99143c30a9afe11133048528
7
+ data.tar.gz: 8873b440698a941314f67a748db86c2abe33e89417ae54d9f35769c29f904edc9eff5736d0bf55d53badc494b9bca9e0ac1761c1920067238628d23dc8e0c643
data/cancancan.gemspec CHANGED
@@ -1,32 +1,28 @@
1
1
  # coding: utf-8
2
+
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'cancan/version'
5
6
 
6
7
  Gem::Specification.new do |s|
7
- s.name = "cancancan"
8
+ s.name = 'cancancan'
8
9
  s.version = CanCan::VERSION
9
- s.authors = ["Bryan Rite", "Ryan Bates"]
10
- s.email = "bryan@bryanrite.com"
11
- s.homepage = "https://github.com/CanCanCommunity/cancancan"
12
- s.summary = "Simple authorization solution for Rails."
13
- s.description = "Continuation of the simple authorization solution for Rails which is decoupled from user roles. All permissions are stored in a single location."
10
+ s.authors = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
11
+ s.email = 'alessandro.rodi@renuo.ch'
12
+ s.homepage = 'https://github.com/CanCanCommunity/cancancan'
13
+ s.summary = 'Simple authorization solution for Rails.'
14
+ s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.'
14
15
  s.platform = Gem::Platform::RUBY
15
- s.license = "MIT"
16
+ s.license = 'MIT'
16
17
 
17
- s.files = `git ls-files`.split($/)
18
- s.test_files = `git ls-files -- Appraisals {spec,features,gemfiles}/*`.split($/)
19
- s.executables = `git ls-files -- bin/*`.split($/).map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
18
+ s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR)
19
+ s.require_paths = ['lib']
21
20
 
22
- s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
23
- s.required_rubygems_version = ">= 1.3.4"
21
+ s.required_ruby_version = '>= 2.2.0'
24
22
 
25
23
  s.add_development_dependency 'bundler', '~> 1.3'
26
- s.add_development_dependency 'rake', '~> 10.1.1'
27
- s.add_development_dependency 'rspec', '~> 3.0.0'
28
- s.add_development_dependency 'appraisal', '>= 1.0.0'
29
- s.add_development_dependency 'pry', '~> 0.10.0'
30
-
31
- s.rubyforge_project = s.name
24
+ s.add_development_dependency 'rubocop', '~> 0.48.1'
25
+ s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
26
+ s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
27
+ s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0'
32
28
  end
@@ -0,0 +1,91 @@
1
+ module CanCan
2
+ module Ability
3
+ module Actions
4
+ # Alias one or more actions into another one.
5
+ #
6
+ # alias_action :update, :destroy, :to => :modify
7
+ # can :modify, Comment
8
+ #
9
+ # Then :modify permission will apply to both :update and :destroy requests.
10
+ #
11
+ # can? :update, Comment # => true
12
+ # can? :destroy, Comment # => true
13
+ #
14
+ # This only works in one direction. Passing the aliased action into the "can?" call
15
+ # will not work because aliases are meant to generate more generic actions.
16
+ #
17
+ # alias_action :update, :destroy, :to => :modify
18
+ # can :update, Comment
19
+ # can? :modify, Comment # => false
20
+ #
21
+ # Unless that exact alias is used.
22
+ #
23
+ # can :modify, Comment
24
+ # can? :modify, Comment # => true
25
+ #
26
+ # The following aliases are added by default for conveniently mapping common controller actions.
27
+ #
28
+ # alias_action :index, :show, :to => :read
29
+ # alias_action :new, :to => :create
30
+ # alias_action :edit, :to => :update
31
+ #
32
+ # This way one can use params[:action] in the controller to determine the permission.
33
+ def alias_action(*args)
34
+ target = args.pop[:to]
35
+ validate_target(target)
36
+ aliased_actions[target] ||= []
37
+ aliased_actions[target] += args
38
+ end
39
+
40
+ # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
41
+ def aliased_actions
42
+ @aliased_actions ||= default_alias_actions
43
+ end
44
+
45
+ # Removes previously aliased actions including the defaults.
46
+ def clear_aliased_actions
47
+ @aliased_actions = {}
48
+ end
49
+
50
+ private
51
+
52
+ def default_alias_actions
53
+ {
54
+ read: %i[index show],
55
+ create: [:new],
56
+ update: [:edit]
57
+ }
58
+ end
59
+
60
+ # Given an action, it will try to find all of the actions which are aliased to it.
61
+ # This does the opposite kind of lookup as expand_actions.
62
+ def aliases_for_action(action)
63
+ results = [action]
64
+ aliased_actions.each do |aliased_action, actions|
65
+ results += aliases_for_action(aliased_action) if actions.include? action
66
+ end
67
+ results
68
+ end
69
+
70
+ def expanded_actions
71
+ @expanded_actions ||= {}
72
+ end
73
+
74
+ # Accepts an array of actions and returns an array of actions which match.
75
+ # This should be called before "matches?" and other checking methods since they
76
+ # rely on the actions to be expanded.
77
+ def expand_actions(actions)
78
+ expanded_actions[actions] ||= begin
79
+ expanded = []
80
+ actions.each do |action|
81
+ expanded << action
82
+ if (aliases = aliased_actions[action])
83
+ expanded += expand_actions(aliases)
84
+ end
85
+ end
86
+ expanded
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,85 @@
1
+ module CanCan
2
+ module Ability
3
+ module Rules
4
+ protected
5
+
6
+ # Must be protected as an ability can merge with other abilities.
7
+ # This means that an ability must expose their rules with another ability.
8
+ def rules
9
+ @rules ||= []
10
+ end
11
+
12
+ private
13
+
14
+ def add_rule(rule)
15
+ rules << rule
16
+ add_rule_to_index(rule, rules.size - 1)
17
+ end
18
+
19
+ def add_rule_to_index(rule, position)
20
+ @rules_index ||= Hash.new { |h, k| h[k] = [] }
21
+
22
+ subjects = rule.subjects.compact
23
+ subjects << :all if subjects.empty?
24
+
25
+ subjects.each do |subject|
26
+ @rules_index[subject] << position
27
+ end
28
+ end
29
+
30
+ # Returns an array of Rule instances which match the action and subject
31
+ # This does not take into consideration any hash conditions or block statements
32
+ def relevant_rules(action, subject)
33
+ return [] unless @rules
34
+ relevant = possible_relevant_rules(subject).select do |rule|
35
+ rule.expanded_actions = expand_actions(rule.actions)
36
+ rule.relevant? action, subject
37
+ end
38
+ relevant.reverse!.uniq!
39
+ optimize_order! relevant
40
+ relevant
41
+ end
42
+
43
+ def possible_relevant_rules(subject)
44
+ if subject.is_a?(Hash)
45
+ rules
46
+ else
47
+ positions = @rules_index.values_at(subject, *alternative_subjects(subject))
48
+ positions.flatten!.sort!
49
+ positions.map { |i| @rules[i] }
50
+ end
51
+ end
52
+
53
+ def relevant_rules_for_match(action, subject)
54
+ relevant_rules(action, subject).each do |rule|
55
+ next unless rule.only_raw_sql?
56
+ raise Error,
57
+ "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}"
59
+ end
60
+ end
61
+
62
+ 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
68
+ end
69
+ end
70
+
71
+ # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
72
+ def optimize_order!(rules)
73
+ first_can_in_group = -1
74
+ rules.each_with_index do |rule, i|
75
+ (first_can_in_group = -1) && next unless rule.base_behavior
76
+ (first_can_in_group = i) && next if first_can_in_group == -1
77
+ next unless rule.subjects == [:all]
78
+ rules[i] = rules[first_can_in_group]
79
+ rules[first_can_in_group] = rule
80
+ first_can_in_group += 1
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,5 +1,6 @@
1
+ require_relative 'ability/rules.rb'
2
+ require_relative 'ability/actions.rb'
1
3
  module CanCan
2
-
3
4
  # This module is designed to be included into an Ability class. This will
4
5
  # provide the "can" methods for defining and checking abilities.
5
6
  #
@@ -16,6 +17,9 @@ module CanCan
16
17
  # end
17
18
  #
18
19
  module Ability
20
+ include CanCan::Ability::Rules
21
+ include CanCan::Ability::Actions
22
+
19
23
  # Check if the user has permission to perform a given action on an object.
20
24
  #
21
25
  # can? :destroy, @project
@@ -61,16 +65,14 @@ module CanCan
61
65
  #
62
66
  # Also see the RSpec Matchers to aid in testing.
63
67
  def can?(action, subject, *extra_args)
64
- subject = extract_subjects(subject)
65
-
66
- match = subject.map do |subject|
67
- relevant_rules_for_match(action, subject).detect do |rule|
68
- rule.matches_conditions?(action, subject, extra_args)
68
+ match = extract_subjects(subject).lazy.map do |a_subject|
69
+ relevant_rules_for_match(action, a_subject).detect do |rule|
70
+ rule.matches_conditions?(action, a_subject, extra_args)
69
71
  end
70
- end.compact.first
71
-
72
+ end.reject(&:nil?).first
72
73
  match ? match.base_behavior : false
73
74
  end
75
+
74
76
  # Convenience method which works the same as "can?" but returns the opposite value.
75
77
  #
76
78
  # cannot? :destroy, @project
@@ -119,7 +121,7 @@ module CanCan
119
121
  # can :read, :stats
120
122
  # can? :read, :stats # => true
121
123
  #
122
- # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
124
+ # IMPORTANT: Neither a hash of conditions nor a block will be used when checking permission on a class.
123
125
  #
124
126
  # can :update, Project, :priority => 3
125
127
  # can? :update, Project # => true
@@ -133,7 +135,7 @@ module CanCan
133
135
  # end
134
136
  #
135
137
  def can(action = nil, subject = nil, conditions = nil, &block)
136
- rules << Rule.new(true, action, subject, conditions, block)
138
+ add_rule(Rule.new(true, action, subject, conditions, block))
137
139
  end
138
140
 
139
141
  # Defines an ability which cannot be done. Accepts the same arguments as "can".
@@ -149,58 +151,13 @@ module CanCan
149
151
  # end
150
152
  #
151
153
  def cannot(action = nil, subject = nil, conditions = nil, &block)
152
- rules << Rule.new(false, action, subject, conditions, block)
153
- end
154
-
155
- # Alias one or more actions into another one.
156
- #
157
- # alias_action :update, :destroy, :to => :modify
158
- # can :modify, Comment
159
- #
160
- # Then :modify permission will apply to both :update and :destroy requests.
161
- #
162
- # can? :update, Comment # => true
163
- # can? :destroy, Comment # => true
164
- #
165
- # This only works in one direction. Passing the aliased action into the "can?" call
166
- # will not work because aliases are meant to generate more generic actions.
167
- #
168
- # alias_action :update, :destroy, :to => :modify
169
- # can :update, Comment
170
- # can? :modify, Comment # => false
171
- #
172
- # Unless that exact alias is used.
173
- #
174
- # can :modify, Comment
175
- # can? :modify, Comment # => true
176
- #
177
- # The following aliases are added by default for conveniently mapping common controller actions.
178
- #
179
- # alias_action :index, :show, :to => :read
180
- # alias_action :new, :to => :create
181
- # alias_action :edit, :to => :update
182
- #
183
- # This way one can use params[:action] in the controller to determine the permission.
184
- def alias_action(*args)
185
- target = args.pop[:to]
186
- validate_target(target)
187
- aliased_actions[target] ||= []
188
- aliased_actions[target] += args
154
+ add_rule(Rule.new(false, action, subject, conditions, block))
189
155
  end
190
156
 
191
157
  # User shouldn't specify targets with names of real actions or it will cause Seg fault
192
158
  def validate_target(target)
193
- raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
194
- end
195
-
196
- # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
197
- def aliased_actions
198
- @aliased_actions ||= default_alias_actions
199
- end
200
-
201
- # Removes previously aliased actions including the defaults.
202
- def clear_aliased_actions
203
- @aliased_actions = {}
159
+ error_message = "You can't specify target (#{target}) as alias because it is real action name"
160
+ raise Error, error_message if aliased_actions.values.flatten.include? target
204
161
  end
205
162
 
206
163
  def model_adapter(model_class, action)
@@ -211,21 +168,21 @@ module CanCan
211
168
  # See ControllerAdditions#authorize! for documentation.
212
169
  def authorize!(action, subject, *args)
213
170
  message = nil
214
- if args.last.kind_of?(Hash) && args.last.has_key?(:message)
171
+ if args.last.is_a?(Hash) && args.last.key?(:message)
215
172
  message = args.pop[:message]
216
173
  end
217
174
  if cannot?(action, subject, *args)
218
175
  message ||= unauthorized_message(action, subject)
219
- raise AccessDenied.new(message, action, subject)
176
+ raise AccessDenied.new(message, action, subject, args)
220
177
  end
221
178
  subject
222
179
  end
223
180
 
224
181
  def unauthorized_message(action, subject)
225
182
  keys = unauthorized_message_keys(action, subject)
226
- variables = {:action => action.to_s}
183
+ variables = { action: action.to_s }
227
184
  variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
228
- message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
185
+ message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
229
186
  message.blank? ? nil : message
230
187
  end
231
188
 
@@ -245,100 +202,81 @@ module CanCan
245
202
  relevant_rules(action, subject).any?(&:only_raw_sql?)
246
203
  end
247
204
 
205
+ # Copies all rules of the given +CanCan::Ability+ and adds them to +self+.
206
+ # class ReadAbility
207
+ # include CanCan::Ability
208
+ #
209
+ # def initialize
210
+ # can :read, User
211
+ # end
212
+ # end
213
+ #
214
+ # class WritingAbility
215
+ # include CanCan::Ability
216
+ #
217
+ # def initialize
218
+ # can :edit, User
219
+ # end
220
+ # end
221
+ #
222
+ # read_ability = ReadAbility.new
223
+ # read_ability.can? :edit, User.new #=> false
224
+ # read_ability.merge(WritingAbility.new)
225
+ # read_ability.can? :edit, User.new #=> true
226
+ #
248
227
  def merge(ability)
249
- ability.send(:rules).each do |rule|
250
- rules << rule.dup
228
+ ability.rules.each do |rule|
229
+ add_rule(rule.dup)
251
230
  end
252
231
  self
253
232
  end
254
233
 
255
- private
256
-
257
- def unauthorized_message_keys(action, subject)
258
- subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
259
- [subject, :all].map do |try_subject|
260
- [aliases_for_action(action), :manage].flatten.map do |try_action|
261
- :"#{try_action}.#{try_subject}"
262
- end
263
- end.flatten
234
+ # Return a hash of permissions for the user in the format of:
235
+ # {
236
+ # can: can_hash,
237
+ # cannot: cannot_hash
238
+ # }
239
+ #
240
+ # Where can_hash and cannot_hash are formatted thusly:
241
+ # {
242
+ # action: array_of_objects
243
+ # }
244
+ def permissions
245
+ permissions_list = { can: {}, cannot: {} }
246
+ rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
247
+ permissions_list
264
248
  end
265
249
 
266
- # Accepts an array of actions and returns an array of actions which match.
267
- # This should be called before "matches?" and other checking methods since they
268
- # rely on the actions to be expanded.
269
- def expand_actions(actions)
270
- expanded_actions[actions] ||= begin
271
- expanded = []
272
- actions.each do |action|
273
- expanded << action
274
- if aliases = aliased_actions[action]
275
- expanded += expand_actions(aliases)
276
- end
277
- end
278
- expanded
250
+ def extract_rule_in_permissions(permissions_list, rule)
251
+ expand_actions(rule.actions).each do |action|
252
+ container = rule.base_behavior ? :can : :cannot
253
+ permissions_list[container][action] ||= []
254
+ permissions_list[container][action] += rule.subjects.map(&:to_s)
279
255
  end
280
256
  end
281
257
 
282
- def expanded_actions
283
- @expanded_actions ||= {}
258
+ private
259
+
260
+ def unauthorized_message_keys(action, subject)
261
+ subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.is_a? Symbol
262
+ aliases = aliases_for_action(action)
263
+ [subject, :all].product([*aliases, :manage]).map do |try_subject, try_action|
264
+ :"#{try_action}.#{try_subject}"
265
+ end
284
266
  end
285
267
 
286
268
  # It translates to an array the subject or the hash with multiple subjects given to can?.
287
269
  def extract_subjects(subject)
288
- subject = if subject.kind_of?(Hash) && subject.key?(:any)
270
+ if subject.is_a?(Hash) && subject.key?(:any)
289
271
  subject[:any]
290
272
  else
291
273
  [subject]
292
274
  end
293
275
  end
294
276
 
295
- # Given an action, it will try to find all of the actions which are aliased to it.
296
- # This does the opposite kind of lookup as expand_actions.
297
- def aliases_for_action(action)
298
- results = [action]
299
- aliased_actions.each do |aliased_action, actions|
300
- results += aliases_for_action(aliased_action) if actions.include? action
301
- end
302
- results
303
- end
304
-
305
- def rules
306
- @rules ||= []
307
- end
308
-
309
- # Returns an array of Rule instances which match the action and subject
310
- # This does not take into consideration any hash conditions or block statements
311
- def relevant_rules(action, subject)
312
- relevant = rules.select do |rule|
313
- rule.expanded_actions = expand_actions(rule.actions)
314
- rule.relevant? action, subject
315
- end
316
- relevant.reverse!
317
- relevant
318
- end
319
-
320
- def relevant_rules_for_match(action, subject)
321
- relevant_rules(action, subject).each do |rule|
322
- if rule.only_raw_sql?
323
- raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
324
- end
325
- end
326
- end
327
-
328
- def relevant_rules_for_query(action, subject)
329
- relevant_rules(action, subject).each do |rule|
330
- if rule.only_block?
331
- raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
332
- end
333
- end
334
- end
335
-
336
- def default_alias_actions
337
- {
338
- :read => [:index, :show],
339
- :create => [:new],
340
- :update => [:edit],
341
- }
277
+ def alternative_subjects(subject)
278
+ subject = subject.class unless subject.is_a?(Module)
279
+ [:all, *subject.ancestors, subject.class.to_s]
342
280
  end
343
281
  end
344
282
  end
@@ -0,0 +1,93 @@
1
+ module CanCan
2
+ module ConditionsMatcher
3
+ # Matches the block or conditions hash
4
+ def matches_conditions?(action, subject, extra_args)
5
+ 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
+ end
9
+
10
+ private
11
+
12
+ def subject_class?(subject)
13
+ klass = (subject.is_a?(Hash) ? subject.values.first : subject).class
14
+ klass == Class || klass == Module
15
+ end
16
+
17
+ 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
22
+ # Don't stop at "cannot" definitions when there are conditions.
23
+ conditions_empty? ? true : @base_behavior
24
+ end
25
+
26
+ def nested_subject_matches_conditions?(subject_hash)
27
+ parent, _child = subject_hash.first
28
+ matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
29
+ end
30
+
31
+ # Checks if the given subject matches the given conditions hash.
32
+ # This behavior can be overriden by a model adapter by defining two class methods:
33
+ # override_matching_for_conditions?(subject, conditions) and
34
+ # matches_conditions_hash?(subject, conditions)
35
+ def matches_conditions_hash?(subject, conditions = @conditions)
36
+ return true if conditions.empty?
37
+ adapter = model_adapter(subject)
38
+
39
+ if adapter.override_conditions_hash_matching?(subject, conditions)
40
+ return adapter.matches_conditions_hash?(subject, conditions)
41
+ end
42
+
43
+ matches_all_conditions?(adapter, conditions, subject)
44
+ end
45
+
46
+ def matches_all_conditions?(adapter, conditions, subject)
47
+ conditions.all? do |name, value|
48
+ if adapter.override_condition_matching?(subject, name, value)
49
+ adapter.matches_condition?(subject, name, value)
50
+ else
51
+ condition_match?(subject.send(name), value)
52
+ end
53
+ end
54
+ end
55
+
56
+ def condition_match?(attribute, value)
57
+ case value
58
+ when Hash
59
+ hash_condition_match?(attribute, value)
60
+ when Range
61
+ value.cover?(attribute)
62
+ when Enumerable
63
+ value.include?(attribute)
64
+ else
65
+ attribute == value
66
+ end
67
+ end
68
+
69
+ def hash_condition_match?(attribute, value)
70
+ if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
71
+ attribute.any? { |element| matches_conditions_hash?(element, value) }
72
+ else
73
+ attribute && matches_conditions_hash?(attribute, value)
74
+ end
75
+ end
76
+
77
+ def call_block_with_all(action, subject, extra_args)
78
+ if subject.class == Class
79
+ @block.call(action, subject, nil, *extra_args)
80
+ else
81
+ @block.call(action, subject.class, subject, *extra_args)
82
+ end
83
+ end
84
+
85
+ def model_adapter(subject)
86
+ CanCan::ModelAdapters::AbstractAdapter.adapter_class(subject_class?(subject) ? subject : subject.class)
87
+ end
88
+
89
+ def conditions_empty?
90
+ @conditions == {} || @conditions.nil?
91
+ end
92
+ end
93
+ end