cancancan 2.1.0 → 2.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9115e4337e5f3acf25ada77c66a9e2eef4627cd8
4
- data.tar.gz: 530b77ec0c2f974624e814a65d798864b30324b8
3
+ metadata.gz: a754d91858c1b6c433c9b7c12d6b4e99440c0cc0
4
+ data.tar.gz: 31d2172742361e8a5ee8e7d74d2d5ca3a8e0e681
5
5
  SHA512:
6
- metadata.gz: 8e4091db3eb5acc0567e7d9c80df489974013021a5ee54010a4f9406727680d753c48d955b31e4285fb6903aedfda66089f9a12c2afc07e9e55b90545f31de69
7
- data.tar.gz: 438619dd7cde250eb3598ed34cdc28f514a7d9ce2fa18221cdc3c945312a855ab5cb3c378e5c7737367551353f39f2fdf4e37fd16371e4af3d18b88eb9985da3
6
+ metadata.gz: 6a15feefdba0ac03be2609772608006b2a4546d8ce5bcfd1e1a656a136ad87c837a2b3506bc57c8ae57747b47c6464d4e5912c5252c41b5acc4f625a3aaa01bc
7
+ data.tar.gz: 6492f1fd59db034738c473a7c25677037d7ab37aabb644b4cffb057ed97e53519e25c9aa90d7048f2485088859094824af9dad7926d0ec27d3e2ff20b331ba33
@@ -10,6 +10,6 @@ require 'cancan/model_adapters/abstract_adapter'
10
10
  require 'cancan/model_adapters/default_adapter'
11
11
 
12
12
  if defined? ActiveRecord
13
- require 'cancan/model_adapters/active_record_adapter'
13
+ require 'cancan/model_adapters/active_record_adapter'
14
14
  require 'cancan/model_adapters/active_record_4_adapter'
15
15
  end
@@ -1,3 +1,5 @@
1
+ require_relative 'ability/rules.rb'
2
+ require_relative 'ability/actions.rb'
1
3
  module CanCan
2
4
  # This module is designed to be included into an Ability class. This will
3
5
  # provide the "can" methods for defining and checking abilities.
@@ -15,6 +17,9 @@ module CanCan
15
17
  # end
16
18
  #
17
19
  module Ability
20
+ include CanCan::Ability::Rules
21
+ include CanCan::Ability::Actions
22
+
18
23
  # Check if the user has permission to perform a given action on an object.
19
24
  #
20
25
  # can? :destroy, @project
@@ -149,58 +154,12 @@ module CanCan
149
154
  add_rule(Rule.new(false, action, subject, conditions, block))
150
155
  end
151
156
 
152
- # Alias one or more actions into another one.
153
- #
154
- # alias_action :update, :destroy, :to => :modify
155
- # can :modify, Comment
156
- #
157
- # Then :modify permission will apply to both :update and :destroy requests.
158
- #
159
- # can? :update, Comment # => true
160
- # can? :destroy, Comment # => true
161
- #
162
- # This only works in one direction. Passing the aliased action into the "can?" call
163
- # will not work because aliases are meant to generate more generic actions.
164
- #
165
- # alias_action :update, :destroy, :to => :modify
166
- # can :update, Comment
167
- # can? :modify, Comment # => false
168
- #
169
- # Unless that exact alias is used.
170
- #
171
- # can :modify, Comment
172
- # can? :modify, Comment # => true
173
- #
174
- # The following aliases are added by default for conveniently mapping common controller actions.
175
- #
176
- # alias_action :index, :show, :to => :read
177
- # alias_action :new, :to => :create
178
- # alias_action :edit, :to => :update
179
- #
180
- # This way one can use params[:action] in the controller to determine the permission.
181
- def alias_action(*args)
182
- target = args.pop[:to]
183
- validate_target(target)
184
- aliased_actions[target] ||= []
185
- aliased_actions[target] += args
186
- end
187
-
188
157
  # User shouldn't specify targets with names of real actions or it will cause Seg fault
189
158
  def validate_target(target)
190
159
  error_message = "You can't specify target (#{target}) as alias because it is real action name"
191
160
  raise Error, error_message if aliased_actions.values.flatten.include? target
192
161
  end
193
162
 
194
- # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
195
- def aliased_actions
196
- @aliased_actions ||= default_alias_actions
197
- end
198
-
199
- # Removes previously aliased actions including the defaults.
200
- def clear_aliased_actions
201
- @aliased_actions = {}
202
- end
203
-
204
163
  def model_adapter(model_class, action)
205
164
  adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
206
165
  adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
@@ -262,29 +221,16 @@ module CanCan
262
221
  # }
263
222
  def permissions
264
223
  permissions_list = { can: {}, cannot: {} }
265
-
266
- rules.each do |rule|
267
- subjects = rule.subjects
268
- expand_actions(rule.actions).each do |action|
269
- if rule.base_behavior
270
- permissions_list[:can][action] ||= []
271
- permissions_list[:can][action] += subjects.map(&:to_s)
272
- else
273
- permissions_list[:cannot][action] ||= []
274
- permissions_list[:cannot][action] += subjects.map(&:to_s)
275
- end
276
- end
277
- end
278
-
224
+ rules.each { |rule| extract_rule_in_permissions(permissions_list, rule) }
279
225
  permissions_list
280
226
  end
281
227
 
282
- protected
283
-
284
- # Must be protected as an ability can merge with other abilities.
285
- # This means that an ability must expose their rules with another ability.
286
- def rules
287
- @rules ||= []
228
+ def extract_rule_in_permissions(permissions_list, rule)
229
+ expand_actions(rule.actions).each do |action|
230
+ container = rule.base_behavior ? :can : :cannot
231
+ permissions_list[container][action] ||= []
232
+ permissions_list[container][action] += rule.subjects.map(&:to_s)
233
+ end
288
234
  end
289
235
 
290
236
  private
@@ -297,26 +243,6 @@ module CanCan
297
243
  end
298
244
  end
299
245
 
300
- # Accepts an array of actions and returns an array of actions which match.
301
- # This should be called before "matches?" and other checking methods since they
302
- # rely on the actions to be expanded.
303
- def expand_actions(actions)
304
- expanded_actions[actions] ||= begin
305
- expanded = []
306
- actions.each do |action|
307
- expanded << action
308
- if (aliases = aliased_actions[action])
309
- expanded += expand_actions(aliases)
310
- end
311
- end
312
- expanded
313
- end
314
- end
315
-
316
- def expanded_actions
317
- @expanded_actions ||= {}
318
- end
319
-
320
246
  # It translates to an array the subject or the hash with multiple subjects given to can?.
321
247
  def extract_subjects(subject)
322
248
  if subject.is_a?(Hash) && subject.key?(:any)
@@ -326,97 +252,9 @@ module CanCan
326
252
  end
327
253
  end
328
254
 
329
- # Given an action, it will try to find all of the actions which are aliased to it.
330
- # This does the opposite kind of lookup as expand_actions.
331
- def aliases_for_action(action)
332
- results = [action]
333
- aliased_actions.each do |aliased_action, actions|
334
- results += aliases_for_action(aliased_action) if actions.include? action
335
- end
336
- results
337
- end
338
-
339
- def add_rule(rule)
340
- rules << rule
341
- add_rule_to_index(rule, rules.size - 1)
342
- end
343
-
344
- def add_rule_to_index(rule, position)
345
- @rules_index ||= Hash.new { |h, k| h[k] = [] }
346
-
347
- subjects = rule.subjects.compact
348
- subjects << :all if subjects.empty?
349
-
350
- subjects.each do |subject|
351
- @rules_index[subject] << position
352
- end
353
- end
354
-
355
255
  def alternative_subjects(subject)
356
256
  subject = subject.class unless subject.is_a?(Module)
357
- [:all, *subject.ancestors, subject.class.to_s]
358
- end
359
-
360
- # Returns an array of Rule instances which match the action and subject
361
- # This does not take into consideration any hash conditions or block statements
362
- def relevant_rules(action, subject)
363
- return [] unless @rules
364
- relevant = possible_relevant_rules(subject).select do |rule|
365
- rule.expanded_actions = expand_actions(rule.actions)
366
- rule.relevant? action, subject
367
- end
368
- relevant.reverse!.uniq!
369
- optimize_order! relevant
370
- relevant
371
- end
372
-
373
- # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
374
- def optimize_order!(rules)
375
- first_can_in_group = -1
376
- rules.each_with_index do |rule, i|
377
- (first_can_in_group = -1) && next unless rule.base_behavior
378
- (first_can_in_group = i) && next if first_can_in_group == -1
379
- next unless rule.subjects == [:all]
380
- rules[i] = rules[first_can_in_group]
381
- rules[first_can_in_group] = rule
382
- first_can_in_group += 1
383
- end
384
- end
385
-
386
- def possible_relevant_rules(subject)
387
- if subject.is_a?(Hash)
388
- rules
389
- else
390
- positions = @rules_index.values_at(subject, *alternative_subjects(subject))
391
- positions.flatten!.sort!
392
- positions.map { |i| @rules[i] }
393
- end
394
- end
395
-
396
- def relevant_rules_for_match(action, subject)
397
- relevant_rules(action, subject).each do |rule|
398
- next unless rule.only_raw_sql?
399
- raise Error,
400
- "The can? and cannot? call cannot be used with a raw sql 'can' definition."\
401
- " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
402
- end
403
- end
404
-
405
- def relevant_rules_for_query(action, subject)
406
- relevant_rules(action, subject).each do |rule|
407
- if rule.only_block?
408
- raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
409
- " The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
410
- end
411
- end
412
- end
413
-
414
- def default_alias_actions
415
- {
416
- read: %i[index show],
417
- create: [:new],
418
- update: [:edit]
419
- }
257
+ [:all, *subject.ancestors, subject.class.to_s]
420
258
  end
421
259
  end
422
260
  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
@@ -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