pundit 2.0.0 → 2.2.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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +21 -52
- data/.travis.yml +19 -14
- data/CHANGELOG.md +68 -3
- data/Gemfile +2 -11
- data/LICENSE.txt +1 -1
- data/README.md +60 -26
- data/Rakefile +2 -0
- data/lib/generators/pundit/install/install_generator.rb +4 -2
- data/lib/generators/pundit/install/templates/application_policy.rb +7 -3
- data/lib/generators/pundit/policy/policy_generator.rb +4 -2
- data/lib/generators/pundit/policy/templates/policy.rb +4 -3
- data/lib/generators/rspec/policy_generator.rb +4 -2
- data/lib/generators/rspec/templates/policy_spec.rb +1 -1
- data/lib/generators/test_unit/policy_generator.rb +4 -2
- data/lib/pundit/authorization.rb +168 -0
- data/lib/pundit/policy_finder.rb +3 -1
- data/lib/pundit/rspec.rb +6 -14
- data/lib/pundit/version.rb +1 -1
- data/lib/pundit.rb +45 -184
- data/pundit.gemspec +14 -2
- data/spec/authorization_spec.rb +258 -0
- data/spec/generators_spec.rb +43 -0
- data/spec/policies/post_policy_spec.rb +3 -1
- data/spec/policy_finder_spec.rb +82 -17
- data/spec/pundit_spec.rb +63 -216
- data/spec/spec_helper.rb +44 -26
- metadata +152 -8
@@ -0,0 +1,258 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Pundit::Authorization do
|
6
|
+
let(:controller) { Controller.new(user, "update", {}) }
|
7
|
+
let(:user) { double }
|
8
|
+
let(:post) { Post.new(user) }
|
9
|
+
let(:customer_post) { Customer::Post.new(user) }
|
10
|
+
let(:comment) { Comment.new }
|
11
|
+
let(:article) { Article.new }
|
12
|
+
let(:article_tag) { ArticleTag.new }
|
13
|
+
let(:wiki) { Wiki.new }
|
14
|
+
|
15
|
+
describe "#verify_authorized" do
|
16
|
+
it "does nothing when authorized" do
|
17
|
+
controller.authorize(post)
|
18
|
+
controller.verify_authorized
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an exception when not authorized" do
|
22
|
+
expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#verify_policy_scoped" do
|
27
|
+
it "does nothing when policy_scope is used" do
|
28
|
+
controller.policy_scope(Post)
|
29
|
+
controller.verify_policy_scoped
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an exception when policy_scope is not used" do
|
33
|
+
expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#pundit_policy_authorized?" do
|
38
|
+
it "is true when authorized" do
|
39
|
+
controller.authorize(post)
|
40
|
+
expect(controller.pundit_policy_authorized?).to be true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is false when not authorized" do
|
44
|
+
expect(controller.pundit_policy_authorized?).to be false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#pundit_policy_scoped?" do
|
49
|
+
it "is true when policy_scope is used" do
|
50
|
+
controller.policy_scope(Post)
|
51
|
+
expect(controller.pundit_policy_scoped?).to be true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is false when policy scope is not used" do
|
55
|
+
expect(controller.pundit_policy_scoped?).to be false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#authorize" do
|
60
|
+
it "infers the policy name and authorizes based on it" do
|
61
|
+
expect(controller.authorize(post)).to be_truthy
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns the record on successful authorization" do
|
65
|
+
expect(controller.authorize(post)).to eq(post)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns the record when passed record with namespace " do
|
69
|
+
expect(controller.authorize([:project, comment], :update?)).to eq(comment)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns the record when passed record with nested namespace " do
|
73
|
+
expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the policy name symbol when passed record with headless policy" do
|
77
|
+
expect(controller.authorize(:publication, :create?)).to eq(:publication)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns the class when passed record not a particular instance" do
|
81
|
+
expect(controller.authorize(Post, :show?)).to eq(Post)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "can be given a different permission to check" do
|
85
|
+
expect(controller.authorize(post, :show?)).to be_truthy
|
86
|
+
expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "can be given a different policy class" do
|
90
|
+
expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy
|
91
|
+
end
|
92
|
+
|
93
|
+
it "works with anonymous class policies" do
|
94
|
+
expect(controller.authorize(article_tag, :show?)).to be_truthy
|
95
|
+
expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "throws an exception when the permission check fails" do
|
99
|
+
expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "throws an exception when a policy cannot be found" do
|
103
|
+
expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "caches the policy" do
|
107
|
+
expect(controller.policies[post]).to be_nil
|
108
|
+
controller.authorize(post)
|
109
|
+
expect(controller.policies[post]).not_to be_nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it "raises an error when the given record is nil" do
|
113
|
+
expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotAuthorizedError)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "raises an error with a invalid policy constructor" do
|
117
|
+
expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#skip_authorization" do
|
122
|
+
it "disables authorization verification" do
|
123
|
+
controller.skip_authorization
|
124
|
+
expect { controller.verify_authorized }.not_to raise_error
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#skip_policy_scope" do
|
129
|
+
it "disables policy scope verification" do
|
130
|
+
controller.skip_policy_scope
|
131
|
+
expect { controller.verify_policy_scoped }.not_to raise_error
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#pundit_user" do
|
136
|
+
it "returns the same thing as current_user" do
|
137
|
+
expect(controller.pundit_user).to eq controller.current_user
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#policy" do
|
142
|
+
it "returns an instantiated policy" do
|
143
|
+
policy = controller.policy(post)
|
144
|
+
expect(policy.user).to eq user
|
145
|
+
expect(policy.post).to eq post
|
146
|
+
end
|
147
|
+
|
148
|
+
it "throws an exception if the given policy can't be found" do
|
149
|
+
expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "raises an error with a invalid policy constructor" do
|
153
|
+
expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "allows policy to be injected" do
|
157
|
+
new_policy = OpenStruct.new
|
158
|
+
controller.policies[post] = new_policy
|
159
|
+
|
160
|
+
expect(controller.policy(post)).to eq new_policy
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "#policy_scope" do
|
165
|
+
it "returns an instantiated policy scope" do
|
166
|
+
expect(controller.policy_scope(Post)).to eq :published
|
167
|
+
end
|
168
|
+
|
169
|
+
it "allows policy scope class to be overriden" do
|
170
|
+
expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published
|
171
|
+
end
|
172
|
+
|
173
|
+
it "throws an exception if the given policy can't be found" do
|
174
|
+
expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "raises an error with a invalid policy scope constructor" do
|
178
|
+
expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "allows policy_scope to be injected" do
|
182
|
+
new_scope = OpenStruct.new
|
183
|
+
controller.policy_scopes[Post] = new_scope
|
184
|
+
|
185
|
+
expect(controller.policy_scope(Post)).to eq new_scope
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "#permitted_attributes" do
|
190
|
+
it "checks policy for permitted attributes" do
|
191
|
+
params = ActionController::Parameters.new(
|
192
|
+
post: {
|
193
|
+
title: "Hello",
|
194
|
+
votes: 5,
|
195
|
+
admin: true
|
196
|
+
}
|
197
|
+
)
|
198
|
+
|
199
|
+
action = "update"
|
200
|
+
|
201
|
+
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq(
|
202
|
+
"title" => "Hello",
|
203
|
+
"votes" => 5
|
204
|
+
)
|
205
|
+
expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "checks policy for permitted attributes for record of a ActiveModel type" do
|
209
|
+
params = ActionController::Parameters.new(
|
210
|
+
customer_post: {
|
211
|
+
title: "Hello",
|
212
|
+
votes: 5,
|
213
|
+
admin: true
|
214
|
+
}
|
215
|
+
)
|
216
|
+
|
217
|
+
action = "update"
|
218
|
+
|
219
|
+
expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq(
|
220
|
+
"title" => "Hello",
|
221
|
+
"votes" => 5
|
222
|
+
)
|
223
|
+
expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq(
|
224
|
+
"votes" => 5
|
225
|
+
)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#permitted_attributes_for_action" do
|
230
|
+
it "is checked if it is defined in the policy" do
|
231
|
+
params = ActionController::Parameters.new(
|
232
|
+
post: {
|
233
|
+
title: "Hello",
|
234
|
+
body: "blah",
|
235
|
+
votes: 5,
|
236
|
+
admin: true
|
237
|
+
}
|
238
|
+
)
|
239
|
+
|
240
|
+
action = "revise"
|
241
|
+
expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah")
|
242
|
+
end
|
243
|
+
|
244
|
+
it "can be explicitly set" do
|
245
|
+
params = ActionController::Parameters.new(
|
246
|
+
post: {
|
247
|
+
title: "Hello",
|
248
|
+
body: "blah",
|
249
|
+
votes: 5,
|
250
|
+
admin: true
|
251
|
+
}
|
252
|
+
)
|
253
|
+
|
254
|
+
action = "update"
|
255
|
+
expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "tmpdir"
|
5
|
+
|
6
|
+
require "rails/generators"
|
7
|
+
require "generators/pundit/install/install_generator"
|
8
|
+
require "generators/pundit/policy/policy_generator"
|
9
|
+
|
10
|
+
RSpec.describe "generators" do
|
11
|
+
before(:all) do
|
12
|
+
@tmpdir = Dir.mktmpdir
|
13
|
+
|
14
|
+
Dir.chdir(@tmpdir) do
|
15
|
+
Pundit::Generators::InstallGenerator.new([], { quiet: true }).invoke_all
|
16
|
+
Pundit::Generators::PolicyGenerator.new(%w[Widget], { quiet: true }).invoke_all
|
17
|
+
|
18
|
+
require "./app/policies/application_policy"
|
19
|
+
require "./app/policies/widget_policy"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:all) do
|
24
|
+
FileUtils.remove_entry(@tmpdir)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "WidgetPolicy", type: :policy do
|
28
|
+
permissions :index?, :show?, :create?, :new?, :update?, :edit?, :destroy? do
|
29
|
+
it "has safe defaults" do
|
30
|
+
expect(WidgetPolicy).not_to permit(double("User"), double("Widget"))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "WidgetPolicy::Scope" do
|
35
|
+
describe "#resolve" do
|
36
|
+
it "raises a descriptive error" do
|
37
|
+
scope = WidgetPolicy::Scope.new(double("User"), double("User.all"))
|
38
|
+
expect { scope.resolve }.to raise_error(NotImplementedError, /WidgetPolicy::Scope/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/policy_finder_spec.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "spec_helper"
|
2
4
|
|
3
|
-
|
5
|
+
class Foo; end
|
6
|
+
RSpec.describe Pundit::PolicyFinder do
|
4
7
|
let(:user) { double }
|
5
8
|
let(:post) { Post.new(user) }
|
6
9
|
let(:comment) { CommentFourFiveSix.new }
|
@@ -22,37 +25,99 @@ describe Pundit::PolicyFinder do
|
|
22
25
|
end
|
23
26
|
|
24
27
|
describe "#policy" do
|
25
|
-
|
28
|
+
context "with an instance" do
|
29
|
+
it "returns the associated policy" do
|
30
|
+
object = described_class.new(post)
|
31
|
+
|
32
|
+
expect(object.policy).to eq PostPolicy
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with an array of symbols" do
|
37
|
+
it "returns the associated namespaced policy" do
|
38
|
+
object = described_class.new(%i[project post])
|
39
|
+
|
40
|
+
expect(object.policy).to eq Project::PostPolicy
|
41
|
+
end
|
42
|
+
end
|
26
43
|
|
27
|
-
|
28
|
-
|
44
|
+
context "with an array of a symbol and an instance" do
|
45
|
+
it "returns the associated namespaced policy" do
|
46
|
+
object = described_class.new([:project, post])
|
47
|
+
|
48
|
+
expect(object.policy).to eq Project::PostPolicy
|
49
|
+
end
|
29
50
|
end
|
30
51
|
|
31
|
-
context "with a
|
32
|
-
it "returns
|
33
|
-
|
34
|
-
|
52
|
+
context "with an array of a symbol and a class with a specified policy class" do
|
53
|
+
it "returns the associated namespaced policy" do
|
54
|
+
object = described_class.new([:project, Customer::Post])
|
55
|
+
|
56
|
+
expect(object.policy).to eq Project::PostPolicy
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with an array of a symbol and a class with a specified model name" do
|
61
|
+
it "returns the associated namespaced policy" do
|
62
|
+
object = described_class.new([:project, CommentsRelation])
|
63
|
+
|
64
|
+
expect(object.policy).to eq Project::CommentPolicy
|
35
65
|
end
|
36
66
|
end
|
37
67
|
|
38
68
|
context "with a class" do
|
39
|
-
it "returns
|
40
|
-
|
41
|
-
|
69
|
+
it "returns the associated policy" do
|
70
|
+
object = described_class.new(Post)
|
71
|
+
|
72
|
+
expect(object.policy).to eq PostPolicy
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "with a class which has a specified policy class" do
|
77
|
+
it "returns the associated policy" do
|
78
|
+
object = described_class.new(Customer::Post)
|
79
|
+
|
80
|
+
expect(object.policy).to eq PostPolicy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with an instance which has a specified policy class" do
|
85
|
+
it "returns the associated policy" do
|
86
|
+
object = described_class.new(Customer::Post.new(user))
|
87
|
+
|
88
|
+
expect(object.policy).to eq PostPolicy
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with a class which has a specified model name" do
|
93
|
+
it "returns the associated policy" do
|
94
|
+
object = described_class.new(CommentsRelation)
|
95
|
+
|
96
|
+
expect(object.policy).to eq CommentPolicy
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with an instance which has a specified policy class" do
|
101
|
+
it "returns the associated policy" do
|
102
|
+
object = described_class.new(CommentsRelation.new)
|
103
|
+
|
104
|
+
expect(object.policy).to eq CommentPolicy
|
42
105
|
end
|
43
106
|
end
|
44
107
|
|
45
108
|
context "with nil" do
|
46
|
-
it "returns
|
47
|
-
|
48
|
-
|
109
|
+
it "returns a NilClassPolicy" do
|
110
|
+
object = described_class.new(nil)
|
111
|
+
|
112
|
+
expect(object.policy).to eq NilClassPolicy
|
49
113
|
end
|
50
114
|
end
|
51
115
|
|
52
|
-
context "with a
|
116
|
+
context "with a class that doesn't have an associated policy" do
|
53
117
|
it "returns nil" do
|
54
|
-
|
55
|
-
|
118
|
+
object = described_class.new(Foo)
|
119
|
+
|
120
|
+
expect(object.policy).to eq nil
|
56
121
|
end
|
57
122
|
end
|
58
123
|
end
|