cancancan 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.rdoc +427 -0
  3. data/CONTRIBUTING.md +11 -0
  4. data/Gemfile +23 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +161 -0
  7. data/Rakefile +18 -0
  8. data/init.rb +1 -0
  9. data/lib/cancan.rb +13 -0
  10. data/lib/cancan/ability.rb +324 -0
  11. data/lib/cancan/controller_additions.rb +397 -0
  12. data/lib/cancan/controller_resource.rb +286 -0
  13. data/lib/cancan/exceptions.rb +50 -0
  14. data/lib/cancan/inherited_resource.rb +20 -0
  15. data/lib/cancan/matchers.rb +14 -0
  16. data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
  17. data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
  18. data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
  19. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  20. data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
  21. data/lib/cancan/model_additions.rb +31 -0
  22. data/lib/cancan/rule.rb +147 -0
  23. data/lib/cancancan.rb +1 -0
  24. data/lib/generators/cancan/ability/USAGE +4 -0
  25. data/lib/generators/cancan/ability/ability_generator.rb +11 -0
  26. data/lib/generators/cancan/ability/templates/ability.rb +32 -0
  27. data/spec/README.rdoc +28 -0
  28. data/spec/cancan/ability_spec.rb +455 -0
  29. data/spec/cancan/controller_additions_spec.rb +141 -0
  30. data/spec/cancan/controller_resource_spec.rb +553 -0
  31. data/spec/cancan/exceptions_spec.rb +58 -0
  32. data/spec/cancan/inherited_resource_spec.rb +60 -0
  33. data/spec/cancan/matchers_spec.rb +29 -0
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
  35. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
  36. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  37. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
  38. data/spec/cancan/rule_spec.rb +52 -0
  39. data/spec/matchers.rb +13 -0
  40. data/spec/spec.opts +2 -0
  41. data/spec/spec_helper.rb +77 -0
  42. metadata +126 -0
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::ControllerAdditions do
4
+ before(:each) do
5
+ @controller_class = Class.new
6
+ @controller = @controller_class.new
7
+ allow(@controller).to receive(:params) { {} }
8
+ allow(@controller).to receive(:current_user) { :current_user }
9
+ expect(@controller_class).to receive(:helper_method).with(:can?, :cannot?, :current_ability)
10
+ @controller_class.send(:include, CanCan::ControllerAdditions)
11
+ end
12
+
13
+ it "raises ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do
14
+ expect { @controller.unauthorized! }.to raise_error(CanCan::ImplementationRemoved)
15
+ end
16
+
17
+ it "authorize! assigns @_authorized instance variable and pass args to current ability" do
18
+ allow(@controller.current_ability).to receive(:authorize!).with(:foo, :bar)
19
+ @controller.authorize!(:foo, :bar)
20
+ expect(@controller.instance_variable_get(:@_authorized)).to be_true
21
+ end
22
+
23
+ it "has a current_ability method which generates an ability for the current user" do
24
+ expect(@controller.current_ability).to be_kind_of(Ability)
25
+ end
26
+
27
+ it "provides a can? and cannot? methods which go through the current ability" do
28
+ expect(@controller.current_ability).to be_kind_of(Ability)
29
+ expect(@controller.can?(:foo, :bar)).to be_false
30
+ expect(@controller.cannot?(:foo, :bar)).to be_true
31
+ end
32
+
33
+ it "load_and_authorize_resource setups a before filter which passes call to ControllerResource" do
34
+ expect(cancan_resource_class = double).to receive(:load_and_authorize_resource)
35
+ allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, :foo => :bar) {cancan_resource_class }
36
+ expect(@controller_class).to receive(:before_filter).with({}) { |options, &block| block.call(@controller) }
37
+ @controller_class.load_and_authorize_resource :foo => :bar
38
+ end
39
+
40
+ it "load_and_authorize_resource properly passes first argument as the resource name" do
41
+ expect(cancan_resource_class = double).to receive(:load_and_authorize_resource)
42
+ allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, :foo => :bar) {cancan_resource_class}
43
+ expect(@controller_class).to receive(:before_filter).with({}) { |options, &block| block.call(@controller) }
44
+ @controller_class.load_and_authorize_resource :project, :foo => :bar
45
+ end
46
+
47
+ it "load_and_authorize_resource with :prepend prepends the before filter" do
48
+ expect(@controller_class).to receive(:prepend_before_filter).with({})
49
+ @controller_class.load_and_authorize_resource :foo => :bar, :prepend => true
50
+ end
51
+
52
+ it "authorize_resource setups a before filter which passes call to ControllerResource" do
53
+ expect(cancan_resource_class = double).to receive(:authorize_resource)
54
+ allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, :foo => :bar) {cancan_resource_class}
55
+ expect(@controller_class).to receive(:before_filter).with(:except => :show, :if => true) { |options, &block| block.call(@controller) }
56
+ @controller_class.authorize_resource :foo => :bar, :except => :show, :if => true
57
+ end
58
+
59
+ it "load_resource setups a before filter which passes call to ControllerResource" do
60
+ expect(cancan_resource_class = double).to receive(:load_resource)
61
+ allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, :foo => :bar) {cancan_resource_class}
62
+ expect(@controller_class).to receive(:before_filter).with(:only => [:show, :index], :unless => false) { |options, &block| block.call(@controller) }
63
+ @controller_class.load_resource :foo => :bar, :only => [:show, :index], :unless => false
64
+ end
65
+
66
+ it "skip_authorization_check setups a before filter which sets @_authorized to true" do
67
+ expect(@controller_class).to receive(:before_filter).with(:filter_options) { |options, &block| block.call(@controller) }
68
+ @controller_class.skip_authorization_check(:filter_options)
69
+ expect(@controller.instance_variable_get(:@_authorized)).to be_true
70
+ end
71
+
72
+ it "check_authorization triggers AuthorizationNotPerformed in after filter" do
73
+ expect(@controller_class).to receive(:after_filter).with(:only => [:test]) { |options, &block| block.call(@controller) }
74
+ expect {
75
+ @controller_class.check_authorization(:only => [:test])
76
+ }.to raise_error(CanCan::AuthorizationNotPerformed)
77
+ end
78
+
79
+ it "check_authorization does not trigger AuthorizationNotPerformed when :if is false" do
80
+ allow(@controller).to receive(:check_auth?) { false }
81
+ allow(@controller_class).to receive(:after_filter).with({}) { |options, &block| block.call(@controller) }
82
+ expect {
83
+ @controller_class.check_authorization(:if => :check_auth?)
84
+ }.not_to raise_error
85
+ end
86
+
87
+ it "check_authorization does not trigger AuthorizationNotPerformed when :unless is true" do
88
+ allow(@controller).to receive(:engine_controller?) { true }
89
+ expect(@controller_class).to receive(:after_filter).with({}) { |options, &block| block.call(@controller) }
90
+ expect {
91
+ @controller_class.check_authorization(:unless => :engine_controller?)
92
+ }.not_to raise_error
93
+ end
94
+
95
+ it "check_authorization does not raise error when @_authorized is set" do
96
+ @controller.instance_variable_set(:@_authorized, true)
97
+ expect(@controller_class).to receive(:after_filter).with(:only => [:test]) { |options, &block| block.call(@controller) }
98
+ expect {
99
+ @controller_class.check_authorization(:only => [:test])
100
+ }.not_to raise_error
101
+ end
102
+
103
+ it "cancan_resource_class is ControllerResource by default" do
104
+ expect(@controller.class.cancan_resource_class).to eq(CanCan::ControllerResource)
105
+ end
106
+
107
+ it "cancan_resource_class is InheritedResource when class includes InheritedResources::Actions" do
108
+ allow(@controller.class).to receive(:ancestors) { ["InheritedResources::Actions"] }
109
+ expect(@controller.class.cancan_resource_class).to eq(CanCan::InheritedResource)
110
+ end
111
+
112
+ it "cancan_skipper is an empty hash with :authorize and :load options and remember changes" do
113
+ expect(@controller_class.cancan_skipper).to eq({:authorize => {}, :load => {}})
114
+ @controller_class.cancan_skipper[:load] = true
115
+ expect(@controller_class.cancan_skipper[:load]).to be_true
116
+ end
117
+
118
+ it "skip_authorize_resource adds itself to the cancan skipper with given model name and options" do
119
+ @controller_class.skip_authorize_resource(:project, :only => [:index, :show])
120
+ expect(@controller_class.cancan_skipper[:authorize][:project]).to eq({:only => [:index, :show]})
121
+ @controller_class.skip_authorize_resource(:only => [:index, :show])
122
+ expect(@controller_class.cancan_skipper[:authorize][nil]).to eq({:only => [:index, :show]})
123
+ @controller_class.skip_authorize_resource(:article)
124
+ expect(@controller_class.cancan_skipper[:authorize][:article]).to eq({})
125
+ end
126
+
127
+ it "skip_load_resource adds itself to the cancan skipper with given model name and options" do
128
+ @controller_class.skip_load_resource(:project, :only => [:index, :show])
129
+ expect(@controller_class.cancan_skipper[:load][:project]).to eq({:only => [:index, :show]})
130
+ @controller_class.skip_load_resource(:only => [:index, :show])
131
+ expect(@controller_class.cancan_skipper[:load][nil]).to eq({:only => [:index, :show]})
132
+ @controller_class.skip_load_resource(:article)
133
+ expect(@controller_class.cancan_skipper[:load][:article]).to eq({})
134
+ end
135
+
136
+ it "skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options" do
137
+ @controller_class.skip_load_and_authorize_resource(:project, :only => [:index, :show])
138
+ expect(@controller_class.cancan_skipper[:load][:project]).to eq({:only => [:index, :show]})
139
+ expect(@controller_class.cancan_skipper[:authorize][:project]).to eq({:only => [:index, :show]})
140
+ end
141
+ end
@@ -0,0 +1,553 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::ControllerResource do
4
+ before(:each) do
5
+ @params = HashWithIndifferentAccess.new(:controller => "projects")
6
+ @controller_class = Class.new
7
+ @controller = @controller_class.new
8
+ @ability = Ability.new(nil)
9
+ allow(@controller).to receive(:params) { @params }
10
+ allow(@controller).to receive(:current_ability) { @ability }
11
+ allow(@controller_class).to receive(:cancan_skipper) { {:authorize => {}, :load => {}} }
12
+ end
13
+
14
+ it "loads the resource into an instance variable if params[:id] is specified" do
15
+ project = Project.create!
16
+ @params.merge!(:action => "show", :id => project.id)
17
+ resource = CanCan::ControllerResource.new(@controller)
18
+ resource.load_resource
19
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
20
+ end
21
+
22
+ it "does not load resource into an instance variable if already set" do
23
+ @params.merge!(:action => "show", :id => "123")
24
+ @controller.instance_variable_set(:@project, :some_project)
25
+ resource = CanCan::ControllerResource.new(@controller)
26
+ resource.load_resource
27
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
28
+ end
29
+
30
+ it "loads resource for namespaced controller" do
31
+ project = Project.create!
32
+ @params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
33
+ resource = CanCan::ControllerResource.new(@controller)
34
+ resource.load_resource
35
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
36
+ end
37
+
38
+ it "attempts to load a resource with the same namespace as the controller when using :: for namespace" do
39
+ module MyEngine
40
+ class Project < ::Project; end
41
+ end
42
+
43
+ project = MyEngine::Project.create!
44
+ @params.merge!(:controller => "MyEngine::ProjectsController", :action => "show", :id => project.id)
45
+ resource = CanCan::ControllerResource.new(@controller)
46
+ resource.load_resource
47
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
48
+ end
49
+
50
+ # Rails includes namespace in params, see issue #349
51
+ it "creates through the namespaced params" do
52
+ module MyEngine
53
+ class Project < ::Project; end
54
+ end
55
+
56
+ @params.merge!(:controller => "MyEngine::ProjectsController", :action => "create", :my_engine_project => {:name => "foobar"})
57
+ resource = CanCan::ControllerResource.new(@controller)
58
+ resource.load_resource
59
+ expect(@controller.instance_variable_get(:@project).name).to eq("foobar")
60
+ end
61
+
62
+ it "loads resource for namespaced controller when using '::' for namespace" do
63
+ project = Project.create!
64
+ @params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id)
65
+ resource = CanCan::ControllerResource.new(@controller)
66
+ resource.load_resource
67
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
68
+ end
69
+
70
+ it "has the specified nested resource_class when using / for namespace" do
71
+ module Admin
72
+ class Dashboard; end
73
+ end
74
+ @ability.can(:index, "admin/dashboard")
75
+ @params.merge!(:controller => "admin/dashboard", :action => "index")
76
+ resource = CanCan::ControllerResource.new(@controller, :authorize => true)
77
+ expect(resource.send(:resource_class)).to eq(Admin::Dashboard)
78
+ end
79
+
80
+ it "builds a new resource with hash if params[:id] is not specified" do
81
+ @params.merge!(:action => "create", :project => {:name => "foobar"})
82
+ resource = CanCan::ControllerResource.new(@controller)
83
+ resource.load_resource
84
+ expect(@controller.instance_variable_get(:@project).name).to eq("foobar")
85
+ end
86
+
87
+ it "builds a new resource for namespaced model with hash if params[:id] is not specified" do
88
+ @params.merge!(:action => "create", 'sub_project' => {:name => "foobar"})
89
+ resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
90
+ resource.load_resource
91
+ expect(@controller.instance_variable_get(:@project).name).to eq("foobar")
92
+ end
93
+
94
+ it "builds a new resource for namespaced controller and namespaced model with hash if params[:id] is not specified" do
95
+ @params.merge!(:controller => "Admin::SubProjectsController", :action => "create", 'sub_project' => {:name => "foobar"})
96
+ resource = CanCan::ControllerResource.new(@controller, :class => Project)
97
+ resource.load_resource
98
+ expect(@controller.instance_variable_get(:@sub_project).name).to eq("foobar")
99
+ end
100
+
101
+ it "builds a new resource with attributes from current ability" do
102
+ @params.merge!(:action => "new")
103
+ @ability.can(:create, Project, :name => "from conditions")
104
+ resource = CanCan::ControllerResource.new(@controller)
105
+ resource.load_resource
106
+ expect(@controller.instance_variable_get(:@project).name).to eq("from conditions")
107
+ end
108
+
109
+ it "overrides initial attributes with params" do
110
+ @params.merge!(:action => "new", :project => {:name => "from params"})
111
+ @ability.can(:create, Project, :name => "from conditions")
112
+ resource = CanCan::ControllerResource.new(@controller)
113
+ resource.load_resource
114
+ expect(@controller.instance_variable_get(:@project).name).to eq("from params")
115
+ end
116
+
117
+ it "builds a collection when on index action when class responds to accessible_by" do
118
+ allow(Project).to receive(:accessible_by).with(@ability, :index) { :found_projects }
119
+ @params[:action] = "index"
120
+ resource = CanCan::ControllerResource.new(@controller, :project)
121
+ resource.load_resource
122
+ expect(@controller.instance_variable_get(:@project)).to be_nil
123
+ expect(@controller.instance_variable_get(:@projects)).to eq(:found_projects)
124
+ end
125
+
126
+ it "does not build a collection when on index action when class does not respond to accessible_by" do
127
+ @params[:action] = "index"
128
+ resource = CanCan::ControllerResource.new(@controller)
129
+ resource.load_resource
130
+ expect(@controller.instance_variable_get(:@project)).to be_nil
131
+ expect(@controller.instance_variable_defined?(:@projects)).to be_false
132
+ end
133
+
134
+ it "does not use accessible_by when defining abilities through a block" do
135
+ allow(Project).to receive(:accessible_by).with(@ability) { :found_projects }
136
+ @params[:action] = "index"
137
+ @ability.can(:read, Project) { |p| false }
138
+ resource = CanCan::ControllerResource.new(@controller)
139
+ resource.load_resource
140
+ expect(@controller.instance_variable_get(:@project)).to be_nil
141
+ expect(@controller.instance_variable_defined?(:@projects)).to be_false
142
+ end
143
+
144
+ it "does not authorize single resource in collection action" do
145
+ @params[:action] = "index"
146
+ @controller.instance_variable_set(:@project, :some_project)
147
+ allow(@controller).to receive(:authorize!).with(:index, Project) { raise CanCan::AccessDenied }
148
+ resource = CanCan::ControllerResource.new(@controller)
149
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
150
+ end
151
+
152
+ it "authorizes parent resource in collection action" do
153
+ @params[:action] = "index"
154
+ @controller.instance_variable_set(:@category, :some_category)
155
+ allow(@controller).to receive(:authorize!).with(:show, :some_category) { raise CanCan::AccessDenied }
156
+ resource = CanCan::ControllerResource.new(@controller, :category, :parent => true)
157
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
158
+ end
159
+
160
+ it "performs authorization using controller action and loaded model" do
161
+ @params.merge!(:action => "show", :id => "123")
162
+ @controller.instance_variable_set(:@project, :some_project)
163
+ allow(@controller).to receive(:authorize!).with(:show, :some_project) { raise CanCan::AccessDenied }
164
+ resource = CanCan::ControllerResource.new(@controller)
165
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
166
+ end
167
+
168
+ it "performs authorization using controller action and non loaded model" do
169
+ @params.merge!(:action => "show", :id => "123")
170
+ allow(@controller).to receive(:authorize!).with(:show, Project) { raise CanCan::AccessDenied }
171
+ resource = CanCan::ControllerResource.new(@controller)
172
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
173
+ end
174
+
175
+ it "calls load_resource and authorize_resource for load_and_authorize_resource" do
176
+ @params.merge!(:action => "show", :id => "123")
177
+ resource = CanCan::ControllerResource.new(@controller)
178
+ expect(resource).to receive(:load_resource)
179
+ expect(resource).to receive(:authorize_resource)
180
+ resource.load_and_authorize_resource
181
+ end
182
+
183
+ it "does not build a single resource when on custom collection action even with id" do
184
+ @params.merge!(:action => "sort", :id => "123")
185
+ resource = CanCan::ControllerResource.new(@controller, :collection => [:sort, :list])
186
+ resource.load_resource
187
+ expect(@controller.instance_variable_get(:@project)).to be_nil
188
+ end
189
+
190
+ it "loads a collection resource when on custom action with no id param" do
191
+ allow(Project).to receive(:accessible_by).with(@ability, :sort) { :found_projects }
192
+ @params[:action] = "sort"
193
+ resource = CanCan::ControllerResource.new(@controller)
194
+ resource.load_resource
195
+ expect(@controller.instance_variable_get(:@project)).to be_nil
196
+ expect(@controller.instance_variable_get(:@projects)).to eq(:found_projects)
197
+ end
198
+
199
+ it "builds a resource when on custom new action even when params[:id] exists" do
200
+ @params.merge!(:action => "build", :id => "123")
201
+ allow(Project).to receive(:new) { :some_project }
202
+ resource = CanCan::ControllerResource.new(@controller, :new => :build)
203
+ resource.load_resource
204
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
205
+ end
206
+
207
+ it "does not try to load resource for other action if params[:id] is undefined" do
208
+ @params[:action] = "list"
209
+ resource = CanCan::ControllerResource.new(@controller)
210
+ resource.load_resource
211
+ expect(@controller.instance_variable_get(:@project)).to be_nil
212
+ end
213
+
214
+ it "is a parent resource when name is provided which doesn't match controller" do
215
+ resource = CanCan::ControllerResource.new(@controller, :category)
216
+ expect(resource).to be_parent
217
+ end
218
+
219
+ it "does not be a parent resource when name is provided which matches controller" do
220
+ resource = CanCan::ControllerResource.new(@controller, :project)
221
+ expect(resource).to_not be_parent
222
+ end
223
+
224
+ it "is parent if specified in options" do
225
+ resource = CanCan::ControllerResource.new(@controller, :project, {:parent => true})
226
+ expect(resource).to be_parent
227
+ end
228
+
229
+ it "does not be parent if specified in options" do
230
+ resource = CanCan::ControllerResource.new(@controller, :category, {:parent => false})
231
+ expect(resource).to_not be_parent
232
+ end
233
+
234
+ it "has the specified resource_class if 'name' is passed to load_resource" do
235
+ class Section
236
+ end
237
+
238
+ resource = CanCan::ControllerResource.new(@controller, :section)
239
+ expect(resource.send(:resource_class)).to eq(Section)
240
+ end
241
+
242
+ it "loads parent resource through proper id parameter" do
243
+ project = Project.create!
244
+ @params.merge!(:controller => "categories", :action => "index", :project_id => project.id)
245
+ resource = CanCan::ControllerResource.new(@controller, :project)
246
+ resource.load_resource
247
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
248
+ end
249
+
250
+ it "loads resource through the association of another parent resource using instance variable" do
251
+ @params.merge!(:action => "show", :id => "123")
252
+ category = double(:projects => {})
253
+ @controller.instance_variable_set(:@category, category)
254
+ allow(category.projects).to receive(:find).with("123") { :some_project }
255
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
256
+ resource.load_resource
257
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
258
+ end
259
+
260
+ it "loads resource through the custom association name" do
261
+ @params.merge!(:action => "show", :id => "123")
262
+ category = double(:custom_projects => {})
263
+ @controller.instance_variable_set(:@category, category)
264
+ allow(category.custom_projects).to receive(:find).with("123") { :some_project }
265
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :through_association => :custom_projects)
266
+ resource.load_resource
267
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
268
+ end
269
+
270
+ it "loads resource through the association of another parent resource using method" do
271
+ @params.merge!(:action => "show", :id => "123")
272
+ category = double(:projects => {})
273
+ allow(@controller).to receive(:category) { category }
274
+ allow(category.projects).to receive(:find).with("123") { :some_project }
275
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
276
+ resource.load_resource
277
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
278
+ end
279
+
280
+ it "does not load through parent resource if instance isn't loaded when shallow" do
281
+ project = Project.create!
282
+ @params.merge!(:action => "show", :id => project.id)
283
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
284
+ resource.load_resource
285
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
286
+ end
287
+
288
+ it "raises AccessDenied when attempting to load resource through nil" do
289
+ project = Project.create!
290
+ @params.merge!(:action => "show", :id => project.id)
291
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
292
+ expect {
293
+ resource.load_resource
294
+ }.to raise_error(CanCan::AccessDenied) { |exception|
295
+ expect(exception.action).to eq(:show)
296
+ expect(exception.subject).to eq(Project)
297
+ }
298
+ expect(@controller.instance_variable_get(:@project)).to be_nil
299
+ end
300
+
301
+ it "authorizes nested resource through parent association on index action" do
302
+ @params.merge!(:action => "index")
303
+ @controller.instance_variable_set(:@category, category = double)
304
+ allow(@controller).to receive(:authorize!).with(:index, category => Project) { raise CanCan::AccessDenied }
305
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
306
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
307
+ end
308
+
309
+ it "loads through first matching if multiple are given" do
310
+ @params.merge!(:action => "show", :id => "123")
311
+ category = double(:projects => {})
312
+ @controller.instance_variable_set(:@category, category)
313
+ allow(category.projects).to receive(:find).with("123") { :some_project }
314
+ resource = CanCan::ControllerResource.new(@controller, :through => [:category, :user])
315
+ resource.load_resource
316
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
317
+ end
318
+
319
+ it "finds record through has_one association with :singleton option without id param" do
320
+ @params.merge!(:action => "show", :id => nil)
321
+ category = double(:project => :some_project)
322
+ @controller.instance_variable_set(:@category, category)
323
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
324
+ resource.load_resource
325
+ expect(@controller.instance_variable_get(:@project)).to eq(:some_project)
326
+ end
327
+
328
+ it "does not build record through has_one association with :singleton option because it can cause it to delete it in the database" do
329
+ @params.merge!(:action => "create", :project => {:name => "foobar"})
330
+ category = Category.new
331
+ @controller.instance_variable_set(:@category, category)
332
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true)
333
+ resource.load_resource
334
+ expect(@controller.instance_variable_get(:@project).name).to eq("foobar")
335
+ expect(@controller.instance_variable_get(:@project).category).to eq(category)
336
+ end
337
+
338
+ it "finds record through has_one association with :singleton and :shallow options" do
339
+ project = Project.create!
340
+ @params.merge!(:action => "show", :id => project.id)
341
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
342
+ resource.load_resource
343
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
344
+ end
345
+
346
+ it "builds record through has_one association with :singleton and :shallow options" do
347
+ @params.merge!(:action => "create", :project => {:name => "foobar"})
348
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
349
+ resource.load_resource
350
+ expect(@controller.instance_variable_get(:@project).name).to eq("foobar")
351
+ end
352
+
353
+ it "only authorizes :show action on parent resource" do
354
+ project = Project.create!
355
+ @params.merge!(:action => "new", :project_id => project.id)
356
+ allow(@controller).to receive(:authorize!).with(:show, project) { raise CanCan::AccessDenied }
357
+ resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
358
+ expect { resource.load_and_authorize_resource }.to raise_error(CanCan::AccessDenied)
359
+ end
360
+
361
+ it "loads the model using a custom class" do
362
+ project = Project.create!
363
+ @params.merge!(:action => "show", :id => project.id)
364
+ resource = CanCan::ControllerResource.new(@controller, :class => Project)
365
+ resource.load_resource
366
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
367
+ end
368
+
369
+ it "loads the model using a custom namespaced class" do
370
+ project = Sub::Project.create!
371
+ @params.merge!(:action => "show", :id => project.id)
372
+ resource = CanCan::ControllerResource.new(@controller, :class => ::Sub::Project)
373
+ resource.load_resource
374
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
375
+ end
376
+
377
+ it "authorizes based on resource name if class is false" do
378
+ @params.merge!(:action => "show", :id => "123")
379
+ allow(@controller).to receive(:authorize!).with(:show, :project) { raise CanCan::AccessDenied }
380
+ resource = CanCan::ControllerResource.new(@controller, :class => false)
381
+ expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
382
+ end
383
+
384
+ it "loads and authorize using custom instance name" do
385
+ project = Project.create!
386
+ @params.merge!(:action => "show", :id => project.id)
387
+ allow(@controller).to receive(:authorize!).with(:show, project) { raise CanCan::AccessDenied }
388
+ resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project)
389
+ expect { resource.load_and_authorize_resource }.to raise_error(CanCan::AccessDenied)
390
+ expect(@controller.instance_variable_get(:@custom_project)).to eq(project)
391
+ end
392
+
393
+ it "loads resource using custom ID param" do
394
+ project = Project.create!
395
+ @params.merge!(:action => "show", :the_project => project.id)
396
+ resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
397
+ resource.load_resource
398
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
399
+ end
400
+
401
+ # CVE-2012-5664
402
+ it "always converts id param to string" do
403
+ @params.merge!(:action => "show", :the_project => { :malicious => "I am" })
404
+ resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
405
+ expect(resource.send(:id_param).class).to eq(String)
406
+ end
407
+
408
+ it "should id param return nil if param is nil" do
409
+ @params.merge!(:action => "show", :the_project => nil)
410
+ resource = CanCan::ControllerResource.new(@controller, :id_param => :the_project)
411
+ expect(resource.send(:id_param)).to be_nil
412
+ end
413
+
414
+ it "loads resource using custom find_by attribute" do
415
+ project = Project.create!(:name => "foo")
416
+ @params.merge!(:action => "show", :id => "foo")
417
+ resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
418
+ resource.load_resource
419
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
420
+ end
421
+
422
+ it "allows full find method to be passed into find_by option" do
423
+ project = Project.create!(:name => "foo")
424
+ @params.merge!(:action => "show", :id => "foo")
425
+ resource = CanCan::ControllerResource.new(@controller, :find_by => :find_by_name)
426
+ resource.load_resource
427
+ expect(@controller.instance_variable_get(:@project)).to eq(project)
428
+ end
429
+
430
+ it "raises ImplementationRemoved when adding :name option" do
431
+ expect {
432
+ CanCan::ControllerResource.new(@controller, :name => :foo)
433
+ }.to raise_error(CanCan::ImplementationRemoved)
434
+ end
435
+
436
+ it "raises ImplementationRemoved exception when specifying :resource option since it is no longer used" do
437
+ expect {
438
+ CanCan::ControllerResource.new(@controller, :resource => Project)
439
+ }.to raise_error(CanCan::ImplementationRemoved)
440
+ end
441
+
442
+ it "raises ImplementationRemoved exception when passing :nested option" do
443
+ expect {
444
+ CanCan::ControllerResource.new(@controller, :nested => :project)
445
+ }.to raise_error(CanCan::ImplementationRemoved)
446
+ end
447
+
448
+ it "skips resource behavior for :only actions in array" do
449
+ allow(@controller_class).to receive(:cancan_skipper) { {:load => {nil => {:only => [:index, :show]}}} }
450
+ @params.merge!(:action => "index")
451
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_true
452
+ expect(CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load)).to be_false
453
+ @params.merge!(:action => "show")
454
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_true
455
+ @params.merge!(:action => "other_action")
456
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_false
457
+ end
458
+
459
+ it "skips resource behavior for :only one action on resource" do
460
+ allow(@controller_class).to receive(:cancan_skipper) { {:authorize => {:project => {:only => :index}}} }
461
+ @params.merge!(:action => "index")
462
+ expect(CanCan::ControllerResource.new(@controller).skip?(:authorize)).to be_false
463
+ expect(CanCan::ControllerResource.new(@controller, :project).skip?(:authorize)).to be_true
464
+ @params.merge!(:action => "other_action")
465
+ expect(CanCan::ControllerResource.new(@controller, :project).skip?(:authorize)).to be_false
466
+ end
467
+
468
+ it "skips resource behavior :except actions in array" do
469
+ allow(@controller_class).to receive(:cancan_skipper) { {:load => {nil => {:except => [:index, :show]}}} }
470
+ @params.merge!(:action => "index")
471
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_false
472
+ @params.merge!(:action => "show")
473
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_false
474
+ @params.merge!(:action => "other_action")
475
+ expect(CanCan::ControllerResource.new(@controller).skip?(:load)).to be_true
476
+ expect(CanCan::ControllerResource.new(@controller, :some_resource).skip?(:load)).to be_false
477
+ end
478
+
479
+ it "skips resource behavior :except one action on resource" do
480
+ allow(@controller_class).to receive(:cancan_skipper) { {:authorize => {:project => {:except => :index}}} }
481
+ @params.merge!(:action => "index")
482
+ expect(CanCan::ControllerResource.new(@controller, :project).skip?(:authorize)).to be_false
483
+ @params.merge!(:action => "other_action")
484
+ expect(CanCan::ControllerResource.new(@controller).skip?(:authorize)).to be_false
485
+ expect(CanCan::ControllerResource.new(@controller, :project).skip?(:authorize)).to be_true
486
+ end
487
+
488
+ it "skips loading and authorization" do
489
+ allow(@controller_class).to receive(:cancan_skipper) { {:authorize => {nil => {}}, :load => {nil => {}}} }
490
+ @params.merge!(:action => "new")
491
+ resource = CanCan::ControllerResource.new(@controller)
492
+ expect { resource.load_and_authorize_resource }.not_to raise_error
493
+ expect(@controller.instance_variable_get(:@project)).to be_nil
494
+ end
495
+
496
+ context "with a strong parameters method" do
497
+
498
+ it "only calls the santitize method with actions matching param_actions" do
499
+ @params.merge!(:controller => "project", :action => "update")
500
+ @controller.stub(:resource_params).and_return(:resource => 'params')
501
+ resource = CanCan::ControllerResource.new(@controller)
502
+ resource.stub(:param_actions => [:create])
503
+
504
+ @controller.should_not_receive(:send).with(:resource_params)
505
+ resource.send("resource_params")
506
+ end
507
+
508
+ it "uses the specified option for santitizing input" do
509
+ @params.merge!(:controller => "project", :action => "create")
510
+ @controller.stub(:resource_params).and_return(:resource => 'params')
511
+ @controller.stub(:project_params).and_return(:model => 'params')
512
+ @controller.stub(:create_params).and_return(:create => 'params')
513
+ @controller.stub(:custom_params).and_return(:custom => 'params')
514
+ resource = CanCan::ControllerResource.new(@controller, {:param_method => :custom_params})
515
+ expect(resource.send("resource_params")).to eq(:custom => 'params')
516
+ end
517
+
518
+ it "prefers to use the create_params method for santitizing input" do
519
+ @params.merge!(:controller => "project", :action => "create")
520
+ @controller.stub(:resource_params).and_return(:resource => 'params')
521
+ @controller.stub(:project_params).and_return(:model => 'params')
522
+ @controller.stub(:create_params).and_return(:create => 'params')
523
+ @controller.stub(:custom_params).and_return(:custom => 'params')
524
+ resource = CanCan::ControllerResource.new(@controller)
525
+ expect(resource.send("resource_params")).to eq(:create => 'params')
526
+ end
527
+
528
+ it "uses the proper action param based on the action" do
529
+ @params.merge!(:controller => "project", :action => "update")
530
+ @controller.stub(:create_params).and_return(:create => 'params')
531
+ @controller.stub(:update_params).and_return(:update => 'params')
532
+ resource = CanCan::ControllerResource.new(@controller)
533
+ expect(resource.send("resource_params")).to eq(:update => 'params')
534
+ end
535
+
536
+ it "prefers to use the <model_name>_params method for santitizing input if create is not found" do
537
+ @params.merge!(:controller => "project", :action => "create")
538
+ @controller.stub(:resource_params).and_return(:resource => 'params')
539
+ @controller.stub(:project_params).and_return(:model => 'params')
540
+ @controller.stub(:custom_params).and_return(:custom => 'params')
541
+ resource = CanCan::ControllerResource.new(@controller)
542
+ expect(resource.send("resource_params")).to eq(:model => 'params')
543
+ end
544
+
545
+ it "prefers to use the resource_params method for santitizing input if create or model is not found" do
546
+ @params.merge!(:controller => "project", :action => "create")
547
+ @controller.stub(:resource_params).and_return(:resource => 'params')
548
+ @controller.stub(:custom_params).and_return(:custom => 'params')
549
+ resource = CanCan::ControllerResource.new(@controller)
550
+ expect(resource.send("resource_params")).to eq(:resource => 'params')
551
+ end
552
+ end
553
+ end