/
application_spec.rb
352 lines (297 loc) · 11.9 KB
/
application_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# frozen_string_literal: true
require "spec_helper"
require "bcrypt"
module Doorkeeper
describe Application do
let(:clazz) { Doorkeeper::Application }
let(:require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", true) }
let(:unset_require_owner) { Doorkeeper.configuration.instance_variable_set("@confirm_application_owner", false) }
let(:new_application) { FactoryBot.build(:application) }
let(:uid) { SecureRandom.hex(8) }
let(:secret) { SecureRandom.hex(8) }
context "application_owner is enabled" do
before do
Doorkeeper.configure do
orm DOORKEEPER_ORM
enable_application_owner
end
end
context "application owner is not required" do
before(:each) do
unset_require_owner
end
it "is valid given valid attributes" do
expect(new_application).to be_valid
end
end
context "application owner is required" do
before(:each) do
require_owner
@owner = FactoryBot.build_stubbed(:doorkeeper_testing_user)
end
it "is invalid without an owner" do
expect(new_application).not_to be_valid
end
it "is valid with an owner" do
new_application.owner = @owner
expect(new_application).to be_valid
end
end
end
it "is invalid without a name" do
new_application.name = nil
expect(new_application).not_to be_valid
end
it "is invalid without determining confidentiality" do
new_application.confidential = nil
expect(new_application).not_to be_valid
end
it "generates uid on create" do
expect(new_application.uid).to be_nil
new_application.save
expect(new_application.uid).not_to be_nil
end
it "generates uid on create if an empty string" do
new_application.uid = ""
new_application.save
expect(new_application.uid).not_to be_blank
end
it "generates uid on create unless one is set" do
new_application.uid = uid
new_application.save
expect(new_application.uid).to eq(uid)
end
it "is invalid without uid" do
new_application.save
new_application.uid = nil
expect(new_application).not_to be_valid
end
context "redirect URI" do
context "when grant flows allow blank redirect URI" do
before do
Doorkeeper.configure do
grant_flows %w[password client_credentials]
end
end
it "is valid without redirect_uri" do
new_application.save
new_application.redirect_uri = nil
expect(new_application).to be_valid
end
end
context "when grant flows require redirect URI" do
before do
Doorkeeper.configure do
grant_flows %w[password client_credentials authorization_code]
end
end
it "is invalid without redirect_uri" do
new_application.save
new_application.redirect_uri = nil
expect(new_application).not_to be_valid
end
end
end
it "checks uniqueness of uid" do
app1 = FactoryBot.create(:application)
app2 = FactoryBot.create(:application)
app2.uid = app1.uid
expect(app2).not_to be_valid
end
it "expects database to throw an error when uids are the same" do
app1 = FactoryBot.create(:application)
app2 = FactoryBot.create(:application)
app2.uid = app1.uid
expect { app2.save!(validate: false) }.to raise_error(uniqueness_error)
end
it "generate secret on create" do
expect(new_application.secret).to be_nil
new_application.save
expect(new_application.secret).not_to be_nil
end
it "generate secret on create if is blank string" do
new_application.secret = ""
new_application.save
expect(new_application.secret).not_to be_blank
end
it "generate secret on create unless one is set" do
new_application.secret = secret
new_application.save
expect(new_application.secret).to eq(secret)
end
it "is invalid without secret" do
new_application.save
new_application.secret = nil
expect(new_application).not_to be_valid
end
context "with hashing enabled" do
include_context "with application hashing enabled"
let(:app) { FactoryBot.create :application }
let(:default_strategy) { Doorkeeper::SecretStoring::Sha256Hash }
it "uses SHA256 to avoid additional dependencies" do
# Ensure token was generated
app.validate
expect(app.secret).to eq(default_strategy.transform_secret(app.plaintext_secret))
end
context "when bcrypt strategy is configured" do
# In this text context, we have bcrypt loaded so `bcrypt_present?`
# will always be true
before do
Doorkeeper.configure do
hash_application_secrets using: "Doorkeeper::SecretStoring::BCrypt"
end
end
it "holds a volatile plaintext and BCrypt secret" do
expect(app.secret_strategy).to eq Doorkeeper::SecretStoring::BCrypt
expect(app.plaintext_secret).to be_a(String)
expect(app.secret).not_to eq(app.plaintext_secret)
expect { ::BCrypt::Password.create(app.secret) }.not_to raise_error
end
end
it "does not fallback to plain lookup by default" do
lookup = clazz.by_uid_and_secret(app.uid, app.secret)
expect(lookup).to eq(nil)
lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
expect(lookup).to eq(app)
end
context "with fallback enabled" do
include_context "with token hashing and fallback lookup enabled"
it "provides plain and hashed lookup" do
lookup = clazz.by_uid_and_secret(app.uid, app.secret)
expect(lookup).to eq(app)
lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
expect(lookup).to eq(app)
end
end
it "does not provide access to secret after loading" do
lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
expect(lookup.plaintext_secret).to be_nil
end
end
describe "destroy related models on cascade" do
before(:each) do
new_application.save
end
it "should destroy its access grants" do
FactoryBot.create(:access_grant, application: new_application)
expect { new_application.destroy }.to change { Doorkeeper::AccessGrant.count }.by(-1)
end
it "should destroy its access tokens" do
FactoryBot.create(:access_token, application: new_application)
FactoryBot.create(:access_token, application: new_application, revoked_at: Time.now.utc)
expect do
new_application.destroy
end.to change { Doorkeeper::AccessToken.count }.by(-2)
end
end
describe :ordered_by do
let(:applications) { FactoryBot.create_list(:application, 5) }
context "when a direction is not specified" do
it "calls order with a default order of asc" do
names = applications.map(&:name).sort
expect(Application.ordered_by(:name).map(&:name)).to eq(names)
end
end
context "when a direction is specified" do
it "calls order with specified direction" do
names = applications.map(&:name).sort.reverse
expect(Application.ordered_by(:name, :desc).map(&:name)).to eq(names)
end
end
end
describe "#redirect_uri=" do
context "when array of valid redirect_uris" do
it "should join by newline" do
new_application.redirect_uri = ["http://localhost/callback1", "http://localhost/callback2"]
expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
end
end
context "when string of valid redirect_uris" do
it "should store as-is" do
new_application.redirect_uri = "http://localhost/callback1\nhttp://localhost/callback2"
expect(new_application.redirect_uri).to eq("http://localhost/callback1\nhttp://localhost/callback2")
end
end
end
describe :authorized_for do
let(:resource_owner) { double(:resource_owner, id: 10) }
it "is empty if the application is not authorized for anyone" do
expect(Application.authorized_for(resource_owner)).to be_empty
end
it "returns only application for a specific resource owner" do
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id + 1)
token = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
expect(Application.authorized_for(resource_owner)).to eq([token.application])
end
it "excludes revoked tokens" do
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, revoked_at: 2.days.ago)
expect(Application.authorized_for(resource_owner)).to be_empty
end
it "returns all applications that have been authorized" do
token1 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
token2 = FactoryBot.create(:access_token, resource_owner_id: resource_owner.id)
expect(Application.authorized_for(resource_owner)).to eq([token1.application, token2.application])
end
it "returns only one application even if it has been authorized twice" do
application = FactoryBot.create(:application)
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
FactoryBot.create(:access_token, resource_owner_id: resource_owner.id, application: application)
expect(Application.authorized_for(resource_owner)).to eq([application])
end
end
describe :revoke_tokens_and_grants_for do
it "revokes all access tokens and access grants" do
application_id = 42
resource_owner = double
expect(Doorkeeper::AccessToken)
.to receive(:revoke_all_for).with(application_id, resource_owner)
expect(Doorkeeper::AccessGrant)
.to receive(:revoke_all_for).with(application_id, resource_owner)
Application.revoke_tokens_and_grants_for(application_id, resource_owner)
end
end
describe :by_uid_and_secret do
context "when application is private/confidential" do
it "finds the application via uid/secret" do
app = FactoryBot.create :application
authenticated = Application.by_uid_and_secret(app.uid, app.secret)
expect(authenticated).to eq(app)
end
context "when secret is wrong" do
it "should not find the application" do
app = FactoryBot.create :application
authenticated = Application.by_uid_and_secret(app.uid, "bad")
expect(authenticated).to eq(nil)
end
end
end
context "when application is public/non-confidential" do
context "when secret is blank" do
it "should find the application" do
app = FactoryBot.create :application, confidential: false
authenticated = Application.by_uid_and_secret(app.uid, nil)
expect(authenticated).to eq(app)
end
end
context "when secret is wrong" do
it "should not find the application" do
app = FactoryBot.create :application, confidential: false
authenticated = Application.by_uid_and_secret(app.uid, "bad")
expect(authenticated).to eq(nil)
end
end
end
end
describe :confidential? do
subject { FactoryBot.create(:application, confidential: confidential).confidential? }
context "when application is private/confidential" do
let(:confidential) { true }
it { expect(subject).to eq(true) }
end
context "when application is public/non-confidential" do
let(:confidential) { false }
it { expect(subject).to eq(false) }
end
end
end
end