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.
- checksums.yaml +15 -0
- data/CHANGELOG.rdoc +427 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +23 -0
- data/LICENSE +20 -0
- data/README.rdoc +161 -0
- data/Rakefile +18 -0
- data/init.rb +1 -0
- data/lib/cancan.rb +13 -0
- data/lib/cancan/ability.rb +324 -0
- data/lib/cancan/controller_additions.rb +397 -0
- data/lib/cancan/controller_resource.rb +286 -0
- data/lib/cancan/exceptions.rb +50 -0
- data/lib/cancan/inherited_resource.rb +20 -0
- data/lib/cancan/matchers.rb +14 -0
- data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
- data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
- data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
- data/lib/cancan/model_adapters/default_adapter.rb +7 -0
- data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
- data/lib/cancan/model_additions.rb +31 -0
- data/lib/cancan/rule.rb +147 -0
- data/lib/cancancan.rb +1 -0
- data/lib/generators/cancan/ability/USAGE +4 -0
- data/lib/generators/cancan/ability/ability_generator.rb +11 -0
- data/lib/generators/cancan/ability/templates/ability.rb +32 -0
- data/spec/README.rdoc +28 -0
- data/spec/cancan/ability_spec.rb +455 -0
- data/spec/cancan/controller_additions_spec.rb +141 -0
- data/spec/cancan/controller_resource_spec.rb +553 -0
- data/spec/cancan/exceptions_spec.rb +58 -0
- data/spec/cancan/inherited_resource_spec.rb +60 -0
- data/spec/cancan/matchers_spec.rb +29 -0
- data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
- data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
- data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
- data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
- data/spec/cancan/rule_spec.rb +52 -0
- data/spec/matchers.rb +13 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +77 -0
- 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
|