cancancan 1.11.0 → 2.3.0

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