cancancan 2.1.0 → 2.1.1

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