cancancan 1.7.0

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.
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