cancancan 1.15.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/cancancan.gemspec +18 -18
  3. data/init.rb +2 -0
  4. data/lib/cancan.rb +9 -11
  5. data/lib/cancan/ability.rb +90 -203
  6. data/lib/cancan/ability/actions.rb +93 -0
  7. data/lib/cancan/ability/rules.rb +93 -0
  8. data/lib/cancan/ability/strong_parameter_support.rb +41 -0
  9. data/lib/cancan/conditions_matcher.rb +106 -0
  10. data/lib/cancan/controller_additions.rb +29 -36
  11. data/lib/cancan/controller_resource.rb +46 -211
  12. data/lib/cancan/controller_resource_builder.rb +26 -0
  13. data/lib/cancan/controller_resource_finder.rb +42 -0
  14. data/lib/cancan/controller_resource_loader.rb +120 -0
  15. data/lib/cancan/controller_resource_name_finder.rb +23 -0
  16. data/lib/cancan/controller_resource_sanitizer.rb +32 -0
  17. data/lib/cancan/exceptions.rb +17 -5
  18. data/lib/cancan/matchers.rb +12 -3
  19. data/lib/cancan/model_adapters/abstract_adapter.rb +10 -8
  20. data/lib/cancan/model_adapters/active_record_4_adapter.rb +39 -43
  21. data/lib/cancan/model_adapters/active_record_5_adapter.rb +68 -0
  22. data/lib/cancan/model_adapters/active_record_adapter.rb +77 -82
  23. data/lib/cancan/model_adapters/conditions_extractor.rb +75 -0
  24. data/lib/cancan/model_adapters/conditions_normalizer.rb +49 -0
  25. data/lib/cancan/model_adapters/default_adapter.rb +2 -0
  26. data/lib/cancan/model_additions.rb +2 -1
  27. data/lib/cancan/parameter_validators.rb +9 -0
  28. data/lib/cancan/relevant.rb +29 -0
  29. data/lib/cancan/rule.rb +76 -106
  30. data/lib/cancan/rules_compressor.rb +23 -0
  31. data/lib/cancan/unauthorized_message_resolver.rb +24 -0
  32. data/lib/cancan/version.rb +3 -1
  33. data/lib/cancancan.rb +2 -0
  34. data/lib/generators/cancan/ability/ability_generator.rb +4 -2
  35. data/lib/generators/cancan/ability/templates/ability.rb +2 -0
  36. metadata +66 -57
  37. data/.gitignore +0 -15
  38. data/.rspec +0 -1
  39. data/.travis.yml +0 -33
  40. data/Appraisals +0 -104
  41. data/CHANGELOG.rdoc +0 -527
  42. data/CONTRIBUTING.md +0 -23
  43. data/Gemfile +0 -3
  44. data/LICENSE +0 -22
  45. data/README.md +0 -217
  46. data/Rakefile +0 -9
  47. data/gemfiles/activerecord_3.2.gemfile +0 -17
  48. data/gemfiles/activerecord_4.0.gemfile +0 -18
  49. data/gemfiles/activerecord_4.1.gemfile +0 -18
  50. data/gemfiles/activerecord_4.2.gemfile +0 -19
  51. data/gemfiles/activerecord_5.0.gemfile +0 -19
  52. data/gemfiles/mongoid_2.x.gemfile +0 -17
  53. data/gemfiles/sequel_3.x.gemfile +0 -17
  54. data/lib/cancan/inherited_resource.rb +0 -20
  55. data/lib/cancan/model_adapters/active_record_3_adapter.rb +0 -16
  56. data/lib/cancan/model_adapters/mongoid_adapter.rb +0 -75
  57. data/lib/cancan/model_adapters/sequel_adapter.rb +0 -87
  58. data/spec/README.rdoc +0 -27
  59. data/spec/cancan/ability_spec.rb +0 -544
  60. data/spec/cancan/controller_additions_spec.rb +0 -151
  61. data/spec/cancan/controller_resource_spec.rb +0 -643
  62. data/spec/cancan/exceptions_spec.rb +0 -58
  63. data/spec/cancan/inherited_resource_spec.rb +0 -71
  64. data/spec/cancan/matchers_spec.rb +0 -29
  65. data/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +0 -154
  66. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +0 -405
  67. data/spec/cancan/model_adapters/default_adapter_spec.rb +0 -7
  68. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +0 -247
  69. data/spec/cancan/model_adapters/sequel_adapter_spec.rb +0 -132
  70. data/spec/cancan/rule_spec.rb +0 -52
  71. data/spec/matchers.rb +0 -13
  72. data/spec/spec.opts +0 -2
  73. data/spec/spec_helper.rb +0 -27
  74. data/spec/support/ability.rb +0 -7
@@ -1,75 +0,0 @@
1
- module CanCan
2
- module ModelAdapters
3
- class MongoidAdapter < AbstractAdapter
4
- def self.for_class?(model_class)
5
- model_class <= Mongoid::Document
6
- end
7
-
8
- def self.override_conditions_hash_matching?(subject, conditions)
9
- conditions.any? do |k,v|
10
- key_is_not_symbol = lambda { !k.kind_of?(Symbol) }
11
- subject_value_is_array = lambda do
12
- subject.respond_to?(k) && subject.send(k).is_a?(Array)
13
- end
14
-
15
- key_is_not_symbol.call || subject_value_is_array.call
16
- end
17
- end
18
-
19
- def self.matches_conditions_hash?(subject, conditions)
20
- # To avoid hitting the db, retrieve the raw Mongo selector from
21
- # the Mongoid Criteria and use Mongoid::Matchers#matches?
22
- subject.matches?( subject.class.where(conditions).selector )
23
- end
24
-
25
- def database_records
26
- if @rules.size == 0
27
- @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
28
- elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria)
29
- @rules[0].conditions
30
- else
31
- # we only need to process can rules if
32
- # there are no rules with empty conditions
33
- rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior }
34
- process_can_rules = @rules.count == rules.count
35
-
36
- rules.inject(@model_class.all) do |records, rule|
37
- if process_can_rules && rule.base_behavior
38
- records.or simplify_relations(@model_class, rule.conditions)
39
- elsif !rule.base_behavior
40
- records.excludes simplify_relations(@model_class, rule.conditions)
41
- else
42
- records
43
- end
44
- end
45
- end
46
- end
47
-
48
- private
49
- # Look for criteria on relations and replace with simple id queries
50
- # eg.
51
- # {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}}
52
- # {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }}
53
- def simplify_relations model_class, conditions
54
- model_relations = model_class.relations.with_indifferent_access
55
- Hash[
56
- conditions.map {|k,v|
57
- if relation = model_relations[k]
58
- relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name]
59
- v = simplify_relations(relation_class_name.constantize, v)
60
- relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id)
61
- k = "#{k}_id"
62
- v = { "$in" => relation_ids }
63
- end
64
- [k,v]
65
- }
66
- ]
67
- end
68
- end
69
- end
70
- end
71
-
72
- # simplest way to add `accessible_by` to all Mongoid Documents
73
- module Mongoid::Document::ClassMethods
74
- include CanCan::ModelAdditions::ClassMethods
75
- end
@@ -1,87 +0,0 @@
1
- module CanCan
2
- module ModelAdapters
3
- class SequelAdapter < AbstractAdapter
4
- def self.for_class?(model_class)
5
- model_class <= Sequel::Model
6
- end
7
-
8
- def self.find(model_class, id)
9
- model_class[id]
10
- end
11
-
12
- def self.override_condition_matching?(subject, name, value)
13
- value.kind_of?(Hash)
14
- end
15
-
16
- def self.matches_condition?(subject, name, value)
17
- obj = subject.send(name)
18
- if obj.nil?
19
- false
20
- else
21
- value.each do |k, v|
22
- if v.kind_of?(Hash)
23
- return false unless self.matches_condition?(obj, k, v)
24
- elsif obj.send(k) != v
25
- return false
26
- end
27
- end
28
- end
29
- end
30
-
31
- def database_records
32
- if @rules.size == 0
33
- @model_class.where('1=0')
34
- else
35
- # only need to process can rules if there are no can rule with empty conditions
36
- rules = @rules.reject { |rule| rule.base_behavior && rule.conditions.empty? }
37
- rules.reject! { |rule| rule.base_behavior } if rules.count < @rules.count
38
-
39
- can_condition_added = false
40
- rules.reverse.inject(@model_class.dataset) do |records, rule|
41
- normalized_conditions = normalize_conditions(rule.conditions)
42
- if rule.base_behavior
43
- if can_condition_added
44
- records.or normalized_conditions
45
- else
46
- can_condition_added = true
47
- records.where normalized_conditions
48
- end
49
- else
50
- records.exclude normalized_conditions
51
- end
52
- end
53
- end
54
- end
55
-
56
- private
57
-
58
- def normalize_conditions(conditions, model_class = @model_class)
59
- return conditions unless conditions.kind_of? Hash
60
- conditions.inject({}) do |result_hash, (name, value)|
61
- if value.kind_of? Hash
62
- value = value.dup
63
- association_class = model_class.association_reflection(name).associated_class
64
- nested = value.inject({}) do |nested, (k, v)|
65
- if v.kind_of?(Hash)
66
- value.delete(k)
67
- nested_class = association_class.association_reflection(k).associated_class
68
- nested[k] = nested_class.where(normalize_conditions(v, association_class))
69
- else
70
- nested[k] = v
71
- end
72
- nested
73
- end
74
- result_hash[name] = association_class.where(nested)
75
- else
76
- result_hash[name] = value
77
- end
78
- result_hash
79
- end
80
- end
81
- end
82
- end
83
- end
84
-
85
- Sequel::Model.class_eval do
86
- include CanCan::ModelAdditions
87
- end
@@ -1,27 +0,0 @@
1
- = CanCan Specs
2
-
3
- == Running the specs
4
-
5
- To run the specs first run the +bundle+ command to install the necessary gems and the +rake+ command to run the specs.
6
-
7
- bundle
8
-
9
- Then run the appraisal command to install all the necessary test sets:
10
-
11
- appraisal install
12
-
13
- You can then run all test sets:
14
-
15
- appraisal rake
16
-
17
- Or individual ones:
18
-
19
- appraisal activerecord_3.2 rake
20
-
21
- A list of the tests is in the +Appraisal+ file.
22
-
23
- The specs support Ruby 1.8.7+
24
-
25
- == Model Adapters
26
-
27
- The model adapter ENV setting has been removed and replaced with the +Appraisal+ file.
@@ -1,544 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe CanCan::Ability do
4
- before(:each) do
5
- (@ability = double).extend(CanCan::Ability)
6
- end
7
-
8
- it "is able to :read anything" do
9
- @ability.can :read, :all
10
- expect(@ability.can?(:read, String)).to be(true)
11
- expect(@ability.can?(:read, 123)).to be(true)
12
- end
13
-
14
- it "does not have permission to do something it doesn't know about" do
15
- expect(@ability.can?(:foodfight, String)).to be(false)
16
- end
17
-
18
- it "passes true to `can?` when non false/nil is returned in block" do
19
- @ability.can :read, :all
20
- @ability.can :read, Symbol do |sym|
21
- "foo" # TODO test that sym is nil when no instance is passed
22
- end
23
- expect(@ability.can?(:read, :some_symbol)).to be(true)
24
- end
25
-
26
- it "passes nil to a block when no instance is passed" do
27
- @ability.can :read, Symbol do |sym|
28
- expect(sym).to be_nil
29
- true
30
- end
31
- expect(@ability.can?(:read, Symbol)).to be(true)
32
- end
33
-
34
- it "passes to previous rule, if block returns false or nil" do
35
- @ability.can :read, Symbol
36
- @ability.can :read, Integer do |i|
37
- i < 5
38
- end
39
- @ability.can :read, Integer do |i|
40
- i > 10
41
- end
42
- expect(@ability.can?(:read, Symbol)).to be(true)
43
- expect(@ability.can?(:read, 11)).to be(true)
44
- expect(@ability.can?(:read, 1)).to be(true)
45
- expect(@ability.can?(:read, 6)).to be(false)
46
- end
47
-
48
- it "overrides earlier rules with later ones (even if a different exact subject)" do
49
- @ability.cannot :read, Numeric
50
- @ability.can :read, Integer
51
-
52
- expect(@ability.can?(:read, 6)).to be(true)
53
- end
54
-
55
- it "performs can(_, :all) before other checks when can(_, :all) is defined before" do
56
- @ability.can :manage, :all
57
- @ability.can :edit, String do |_string|
58
- fail 'Performed a check for :edit before the check for :all'
59
- end
60
- expect { @ability.can? :edit, 'a' }.to_not raise_exception
61
- end
62
-
63
- it "performs can(_, :all) before other checks when can(_, :all) is defined after" do
64
- @ability.can :edit, String do |_string|
65
- fail 'Performed a check for :edit before the check for :all'
66
- end
67
- @ability.can :manage, :all
68
- expect { @ability.can? :edit, 'a' }.to_not raise_exception
69
- end
70
-
71
- it "does not pass class with object if :all objects are accepted" do
72
- @ability.can :preview, :all do |object|
73
- expect(object).to eq(123)
74
- @block_called = true
75
- end
76
- @ability.can?(:preview, 123)
77
- expect(@block_called).to be(true)
78
- end
79
-
80
- it "does not call block when only class is passed, only return true" do
81
- @block_called = false
82
- @ability.can :preview, :all do |object|
83
- @block_called = true
84
- end
85
- expect(@ability.can?(:preview, Hash)).to be(true)
86
- expect(@block_called).to be(false)
87
- end
88
-
89
- it "passes only object for global manage actions" do
90
- @ability.can :manage, String do |object|
91
- expect(object).to eq("foo")
92
- @block_called = true
93
- end
94
- expect(@ability.can?(:stuff, "foo")).to be(true)
95
- expect(@block_called).to be(true)
96
- end
97
-
98
- it "makes alias for update or destroy actions to modify action" do
99
- @ability.alias_action :update, :destroy, :to => :modify
100
- @ability.can :modify, :all
101
- expect(@ability.can?(:update, 123)).to be(true)
102
- expect(@ability.can?(:destroy, 123)).to be(true)
103
- end
104
-
105
- it "allows deeply nested aliased actions" do
106
- @ability.alias_action :increment, :to => :sort
107
- @ability.alias_action :sort, :to => :modify
108
- @ability.can :modify, :all
109
- expect(@ability.can?(:increment, 123)).to be(true)
110
- end
111
-
112
- it "raises an Error if alias target is an exist action" do
113
- expect { @ability.alias_action :show, :to => :show }.to raise_error(CanCan::Error, "You can't specify target (show) as alias because it is real action name")
114
- end
115
-
116
- it "always calls block with arguments when passing no arguments to can" do
117
- @ability.can do |action, object_class, object|
118
- expect(action).to eq(:foo)
119
- expect(object_class).to eq(123.class)
120
- expect(object).to eq(123)
121
- @block_called = true
122
- end
123
- @ability.can?(:foo, 123)
124
- expect(@block_called).to be(true)
125
- end
126
-
127
- it "passes nil to object when comparing class with can check" do
128
- @ability.can do |action, object_class, object|
129
- expect(action).to eq(:foo)
130
- expect(object_class).to eq(Hash)
131
- expect(object).to be_nil
132
- @block_called = true
133
- end
134
- @ability.can?(:foo, Hash)
135
- expect(@block_called).to be(true)
136
- end
137
-
138
- it "automatically makes alias for index and show into read calls" do
139
- @ability.can :read, :all
140
- expect(@ability.can?(:index, 123)).to be(true)
141
- expect(@ability.can?(:show, 123)).to be(true)
142
- end
143
-
144
- it "automatically makes alias for new and edit into create and update respectively" do
145
- @ability.can :create, :all
146
- @ability.can :update, :all
147
- expect(@ability.can?(:new, 123)).to be(true)
148
- expect(@ability.can?(:edit, 123)).to be(true)
149
- end
150
-
151
- it "does not respond to prepare (now using initialize)" do
152
- expect(@ability).to_not respond_to(:prepare)
153
- end
154
-
155
- it "offers cannot? method which is simply invert of can?" do
156
- expect(@ability.cannot?(:tie, String)).to be(true)
157
- end
158
-
159
- it "is able to specify multiple actions and match any" do
160
- @ability.can [:read, :update], :all
161
- expect(@ability.can?(:read, 123)).to be(true)
162
- expect(@ability.can?(:update, 123)).to be(true)
163
- expect(@ability.can?(:count, 123)).to be(false)
164
- end
165
-
166
- it "is able to specify multiple classes and match any" do
167
- @ability.can :update, [String, Range]
168
- expect(@ability.can?(:update, "foo")).to be(true)
169
- expect(@ability.can?(:update, 1..3)).to be(true)
170
- expect(@ability.can?(:update, 123)).to be(false)
171
- end
172
-
173
- it "checks if there is a permission for any of given subjects" do
174
- @ability.can :update, [String, Range]
175
- expect(@ability.can?(:update, {:any => ["foo", 1..3]})).to be(true)
176
- expect(@ability.can?(:update, {:any => [1..3, "foo"]})).to be(true)
177
- expect(@ability.can?(:update, {:any => [123, "foo"]})).to be(true)
178
- expect(@ability.can?(:update, {:any => [123, 1.0]})).to be(false)
179
- end
180
-
181
- it "lists all permissions" do
182
- @ability.can :manage, :all
183
- @ability.can :learn, Range
184
- @ability.cannot :read, String
185
- @ability.cannot :read, Hash
186
- @ability.cannot :preview, Array
187
-
188
- expected_list = {:can => {:manage => ["all"],
189
- :learn => ["Range"]
190
- },
191
- :cannot => {:read => ["String", "Hash"],
192
- :index => ["String", "Hash"],
193
- :show => ["String", "Hash"],
194
- :preview => ["Array"]
195
- }
196
- }
197
-
198
- expect(@ability.permissions).to eq(expected_list)
199
- end
200
-
201
- it "supports custom objects in the rule" do
202
- @ability.can :read, :stats
203
- expect(@ability.can?(:read, :stats)).to be(true)
204
- expect(@ability.can?(:update, :stats)).to be(false)
205
- expect(@ability.can?(:read, :nonstats)).to be(false)
206
- expect(@ability.can?(:read, {:any => [:stats, :nonstats]})).to be(true)
207
- expect(@ability.can?(:read, {:any => [:nonstats, :neitherstats]})).to be(false)
208
- end
209
-
210
- it "checks ancestors of class" do
211
- @ability.can :read, Numeric
212
- expect(@ability.can?(:read, Integer)).to be(true)
213
- expect(@ability.can?(:read, 1.23)).to be(true)
214
- expect(@ability.can?(:read, "foo")).to be(false)
215
- expect(@ability.can?(:read, {:any => [Integer, String]})).to be(true)
216
- end
217
-
218
- it "supports 'cannot' method to define what user cannot do" do
219
- @ability.can :read, :all
220
- @ability.cannot :read, Integer
221
- expect(@ability.can?(:read, "foo")).to be(true)
222
- expect(@ability.can?(:read, 123)).to be(false)
223
- expect(@ability.can?(:read, {:any => ["foo", "bar"]})).to be(true)
224
- expect(@ability.can?(:read, {:any => [123, "foo"]})).to be(false)
225
- expect(@ability.can?(:read, {:any => [123, 456]})).to be(false)
226
- end
227
-
228
- it "passes to previous rule, if block returns false or nil" do
229
- @ability.can :read, :all
230
- @ability.cannot :read, Integer do |int|
231
- int > 10 ? nil : ( int > 5 )
232
- end
233
-
234
- expect(@ability.can?(:read, "foo")).to be(true)
235
- expect(@ability.can?(:read, 3)).to be(true)
236
- expect(@ability.can?(:read, 8)).to be(false)
237
- expect(@ability.can?(:read, 123)).to be(true)
238
- expect(@ability.can?(:read, {:any => [123, 8]})).to be(true)
239
- expect(@ability.can?(:read, {:any => [8, 9]})).to be(false)
240
- end
241
-
242
- it "always returns `false` for single cannot definition" do
243
- @ability.cannot :read, Integer do |int|
244
- int > 10 ? nil : ( int > 5 )
245
- end
246
- expect(@ability.can?(:read, "foo")).to be(false)
247
- expect(@ability.can?(:read, 3)).to be(false)
248
- expect(@ability.can?(:read, 8)).to be(false)
249
- expect(@ability.can?(:read, 123)).to be(false)
250
- end
251
-
252
- it "passes to previous cannot definition, if block returns false or nil" do
253
- @ability.cannot :read, :all
254
- @ability.can :read, Integer do |int|
255
- int > 10 ? nil : ( int > 5 )
256
- end
257
- expect(@ability.can?(:read, "foo")).to be(false)
258
- expect(@ability.can?(:read, 3)).to be(false)
259
- expect(@ability.can?(:read, 10)).to be(true)
260
- expect(@ability.can?(:read, 123)).to be(false)
261
- end
262
-
263
- it "appends aliased actions" do
264
- @ability.alias_action :update, :to => :modify
265
- @ability.alias_action :destroy, :to => :modify
266
- expect(@ability.aliased_actions[:modify]).to eq([:update, :destroy])
267
- end
268
-
269
- it "clears aliased actions" do
270
- @ability.alias_action :update, :to => :modify
271
- @ability.clear_aliased_actions
272
- expect(@ability.aliased_actions[:modify]).to be_nil
273
- end
274
-
275
- it "passes additional arguments to block from can?" do
276
- @ability.can :read, Integer do |int, x|
277
- int > x
278
- end
279
-
280
- expect(@ability.can?(:read, 2, 1)).to be(true)
281
- expect(@ability.can?(:read, 2, 3)).to be(false)
282
- expect(@ability.can?(:read, {:any => [4, 5]}, 3)).to be(true)
283
- expect(@ability.can?(:read, {:any => [2, 3]}, 3)).to be(false)
284
- end
285
-
286
- it "uses conditions as third parameter and determine abilities from it" do
287
- @ability.can :read, Range, :begin => 1, :end => 3
288
-
289
- expect(@ability.can?(:read, 1..3)).to be(true)
290
- expect(@ability.can?(:read, 1..4)).to be(false)
291
- expect(@ability.can?(:read, Range)).to be(true)
292
- expect(@ability.can?(:read, {:any => [1..3, 1..4]})).to be(true)
293
- expect(@ability.can?(:read, {:any => [1..4, 2..4]})).to be(false)
294
- end
295
-
296
- it "allows an array of options in conditions hash" do
297
- @ability.can :read, Range, :begin => [1, 3, 5]
298
-
299
- expect(@ability.can?(:read, 1..3)).to be(true)
300
- expect(@ability.can?(:read, 2..4)).to be(false)
301
- expect(@ability.can?(:read, 3..5)).to be(true)
302
- expect(@ability.can?(:read, {:any => [2..4, 3..5]})).to be(true)
303
- expect(@ability.can?(:read, {:any => [2..4, 2..5]})).to be(false)
304
- end
305
-
306
- it "allows a range of options in conditions hash" do
307
- @ability.can :read, Range, :begin => 1..3
308
- expect(@ability.can?(:read, 1..10)).to be(true)
309
- expect(@ability.can?(:read, 3..30)).to be(true)
310
- expect(@ability.can?(:read, 4..40)).to be(false)
311
- end
312
-
313
- it "allows a range of time in conditions hash" do
314
- @ability.can :read, Range, :begin => 1.day.from_now..3.days.from_now
315
- expect(@ability.can?(:read, 1.day.from_now..10.days.from_now)).to be(true)
316
- expect(@ability.can?(:read, 2.days.from_now..20.days.from_now)).to be(true)
317
- expect(@ability.can?(:read, 4.days.from_now..40.days.from_now)).to be(false)
318
- end
319
-
320
- it "allows nested hashes in conditions hash" do
321
- @ability.can :read, Range, :begin => { :to_i => 5 }
322
- expect(@ability.can?(:read, 5..7)).to be(true)
323
- expect(@ability.can?(:read, 6..8)).to be(false)
324
- end
325
-
326
- it "matches any element passed in to nesting if it's an array (for has_many associations)" do
327
- @ability.can :read, Range, :to_a => { :to_i => 3 }
328
- expect(@ability.can?(:read, 1..5)).to be(true)
329
- expect(@ability.can?(:read, 4..6)).to be(false)
330
- end
331
-
332
- it "accepts a set as a condition value" do
333
- expect(object_with_foo_2 = double(:foo => 2)).to receive(:foo)
334
- expect(object_with_foo_3 = double(:foo => 3)).to receive(:foo)
335
- @ability.can :read, Object, :foo => [1, 2, 5].to_set
336
- expect(@ability.can?(:read, object_with_foo_2)).to be(true)
337
- expect(@ability.can?(:read, object_with_foo_3)).to be(false)
338
- end
339
-
340
- it "does not match subjects return nil for methods that must match nested a nested conditions hash" do
341
- expect(object_with_foo = double(:foo => :bar)).to receive(:foo)
342
- @ability.can :read, Array, :first => { :foo => :bar }
343
- expect(@ability.can?(:read, [object_with_foo])).to be(true)
344
- expect(@ability.can?(:read, [])).to be(false)
345
- end
346
-
347
- it "matches strings but not substrings specified in a conditions hash" do
348
- @ability.can :read, String, :presence => "declassified"
349
- expect(@ability.can?(:read, "declassified")).to be(true)
350
- expect(@ability.can?(:read, "classified")).to be(false)
351
- end
352
-
353
- it "does not stop at cannot definition when comparing class" do
354
- @ability.can :read, Range
355
- @ability.cannot :read, Range, :begin => 1
356
- expect(@ability.can?(:read, 2..5)).to be(true)
357
- expect(@ability.can?(:read, 1..5)).to be(false)
358
- expect(@ability.can?(:read, Range)).to be(true)
359
- end
360
-
361
- it "stops at cannot definition when no hash is present" do
362
- @ability.can :read, :all
363
- @ability.cannot :read, Range
364
- expect(@ability.can?(:read, 1..5)).to be(false)
365
- expect(@ability.can?(:read, Range)).to be(false)
366
- end
367
-
368
- it "allows to check ability for Module" do
369
- module B; end
370
- class A; include B; end
371
- @ability.can :read, B
372
- expect(@ability.can?(:read, A)).to be(true)
373
- expect(@ability.can?(:read, A.new)).to be(true)
374
- end
375
-
376
- it "passes nil to a block for ability on Module when no instance is passed" do
377
- module B; end
378
- class A; include B; end
379
- @ability.can :read, B do |sym|
380
- expect(sym).to be_nil
381
- true
382
- end
383
- expect(@ability.can?(:read, B)).to be(true)
384
- expect(@ability.can?(:read, A)).to be(true)
385
- end
386
-
387
- it "checks permissions through association when passing a hash of subjects" do
388
- @ability.can :read, Range, :string => {:length => 3}
389
-
390
- expect(@ability.can?(:read, "foo" => Range)).to be(true)
391
- expect(@ability.can?(:read, "foobar" => Range)).to be(false)
392
- expect(@ability.can?(:read, 123 => Range)).to be(true)
393
- expect(@ability.can?(:read, {:any => [{"foo" => Range}, {"foobar" => Range}]})).to be(true)
394
- expect(@ability.can?(:read, {:any => [{"food" => Range}, {"foobar" => Range}]})).to be(false)
395
- end
396
-
397
- it "checks permissions correctly when passing a hash of subjects with multiple definitions" do
398
- @ability.can :read, Range, :string => {:length => 4}
399
- @ability.can [:create, :read], Range, :string => {:upcase => 'FOO'}
400
-
401
- expect(@ability.can?(:read, "foo" => Range)).to be(true)
402
- expect(@ability.can?(:read, "foobar" => Range)).to be(false)
403
- expect(@ability.can?(:read, 1234 => Range)).to be(true)
404
- expect(@ability.can?(:read, {:any => [{"foo" => Range}, {"foobar" => Range}]})).to be(true)
405
- expect(@ability.can?(:read, {:any => [{"foo.bar" => Range}, {"foobar" => Range}]})).to be(false)
406
- end
407
-
408
- it "allows to check ability on Hash-like object" do
409
- class Container < Hash; end
410
- @ability.can :read, Container
411
- expect(@ability.can?(:read, Container.new)).to be(true)
412
- end
413
-
414
- it "has initial attributes based on hash conditions of 'new' action" do
415
- @ability.can :manage, Range, :foo => "foo", :hash => {:skip => "hashes"}
416
- @ability.can :create, Range, :bar => 123, :array => %w[skip arrays]
417
- @ability.can :new, Range, :baz => "baz", :range => 1..3
418
- @ability.cannot :new, Range, :ignore => "me"
419
- expect(@ability.attributes_for(:new, Range)).to eq({:foo => "foo", :bar => 123, :baz => "baz"})
420
- end
421
-
422
- it "raises access denied exception if ability us unauthorized to perform a certain action" do
423
- begin
424
- @ability.authorize! :read, :foo, 1, 2, 3, :message => "Access denied!"
425
- rescue CanCan::AccessDenied => e
426
- expect(e.message).to eq("Access denied!")
427
- expect(e.action).to eq(:read)
428
- expect(e.subject).to eq(:foo)
429
- else
430
- fail "Expected CanCan::AccessDenied exception to be raised"
431
- end
432
- end
433
-
434
- it "does not raise access denied exception if ability is authorized to perform an action and return subject" do
435
- @ability.can :read, :foo
436
- expect {
437
- expect(@ability.authorize!(:read, :foo)).to eq(:foo)
438
- }.to_not raise_error
439
- end
440
-
441
- it "knows when block is used in conditions" do
442
- @ability.can :read, :foo
443
- expect(@ability).to_not have_block(:read, :foo)
444
- @ability.can :read, :foo do |foo|
445
- false
446
- end
447
- expect(@ability).to have_block(:read, :foo)
448
- end
449
-
450
- it "knows when raw sql is used in conditions" do
451
- @ability.can :read, :foo
452
- expect(@ability).to_not have_raw_sql(:read, :foo)
453
- @ability.can :read, :foo, 'false'
454
- expect(@ability).to have_raw_sql(:read, :foo)
455
- end
456
-
457
- it "raises access denied exception with default message if not specified" do
458
- begin
459
- @ability.authorize! :read, :foo
460
- rescue CanCan::AccessDenied => e
461
- e.default_message = "Access denied!"
462
- expect(e.message).to eq("Access denied!")
463
- else
464
- fail "Expected CanCan::AccessDenied exception to be raised"
465
- end
466
- end
467
-
468
- it "determines model adapterO class by asking AbstractAdapter" do
469
- adapter_class, model_class = double, double
470
- allow(CanCan::ModelAdapters::AbstractAdapter).to receive(:adapter_class).with(model_class) { adapter_class }
471
- allow(adapter_class).to receive(:new).with(model_class, []) { :adapter_instance }
472
- expect(@ability.model_adapter(model_class, :read)).to eq(:adapter_instance)
473
- end
474
-
475
- it "raises an error when attempting to use a block with a hash condition since it's not likely what they want" do
476
- expect {
477
- @ability.can :read, Array, :published => true do
478
- false
479
- end
480
- }.to raise_error(CanCan::Error, "You are not able to supply a block with a hash of conditions in read Array ability. Use either one.")
481
- end
482
-
483
- describe "unauthorized message" do
484
- after(:each) do
485
- I18n.backend = nil
486
- end
487
-
488
- it "uses action/subject in i18n" do
489
- I18n.backend.store_translations :en, :unauthorized => {:update => {:array => "foo"}}
490
- expect(@ability.unauthorized_message(:update, Array)).to eq("foo")
491
- expect(@ability.unauthorized_message(:update, [1, 2, 3])).to eq("foo")
492
- expect(@ability.unauthorized_message(:update, :missing)).to be_nil
493
- end
494
-
495
- it "uses symbol as subject directly" do
496
- I18n.backend.store_translations :en, :unauthorized => {:has => {:cheezburger => "Nom nom nom. I eated it."}}
497
- expect(@ability.unauthorized_message(:has, :cheezburger)).to eq("Nom nom nom. I eated it.")
498
- end
499
-
500
- it "falls back to 'manage' and 'all'" do
501
- I18n.backend.store_translations :en, :unauthorized => {
502
- :manage => {:all => "manage all", :array => "manage array"},
503
- :update => {:all => "update all", :array => "update array"}
504
- }
505
- expect(@ability.unauthorized_message(:update, Array)).to eq("update array")
506
- expect(@ability.unauthorized_message(:update, Hash)).to eq("update all")
507
- expect(@ability.unauthorized_message(:foo, Array)).to eq("manage array")
508
- expect(@ability.unauthorized_message(:foo, Hash)).to eq("manage all")
509
- end
510
-
511
- it "follows aliased actions" do
512
- I18n.backend.store_translations :en, :unauthorized => {:modify => {:array => "modify array"}}
513
- @ability.alias_action :update, :to => :modify
514
- expect(@ability.unauthorized_message(:update, Array)).to eq("modify array")
515
- expect(@ability.unauthorized_message(:edit, Array)).to eq("modify array")
516
- end
517
-
518
- it "has variables for action and subject" do
519
- I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
520
- expect(@ability.unauthorized_message(:update, Array)).to eq("update array")
521
- expect(@ability.unauthorized_message(:update, ArgumentError)).to eq("update argument error")
522
- expect(@ability.unauthorized_message(:edit, 1..3)).to eq("edit range")
523
- end
524
- end
525
-
526
- describe "#merge" do
527
- it "adds the rules from the given ability" do
528
- @ability.can :use, :tools
529
- (another_ability = double).extend(CanCan::Ability)
530
- another_ability.can :use, :search
531
-
532
- @ability.merge(another_ability)
533
- expect(@ability.can?(:use, :search)).to be(true)
534
- expect(@ability.send(:rules).size).to eq(2)
535
- end
536
-
537
- it "can add an empty ability" do
538
- (another_ability = double).extend(CanCan::Ability)
539
-
540
- @ability.merge(another_ability)
541
- expect(@ability.send(:rules).size).to eq(0)
542
- end
543
- end
544
- end