]>
Commit | Line | Data |
---|---|---|
9107d791 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | ||
3 | import 'mocha' | |
4 | import { expect } from 'chai' | |
5 | import { ServerConfig, User, UserRole } from '@shared/models' | |
6 | import { | |
7 | decodeQueryString, | |
8 | getConfig, | |
9 | getExternalAuth, | |
10 | getMyUserInformation, | |
11 | getPluginTestPath, | |
12 | installPlugin, | |
13 | loginUsingExternalToken, | |
14 | logout, | |
15 | refreshToken, | |
16 | setAccessTokensToServers, | |
17 | uninstallPlugin, | |
18 | updateMyUser, | |
a4995eb7 C |
19 | wait, |
20 | userLogin, | |
d253bfaa C |
21 | updatePluginSettings, |
22 | createUser | |
9107d791 C |
23 | } from '../../../shared/extra-utils' |
24 | import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | |
2d53be02 | 25 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' |
9107d791 C |
26 | |
27 | async function loginExternal (options: { | |
28 | server: ServerInfo | |
29 | npmName: string | |
30 | authName: string | |
31 | username: string | |
32 | query?: any | |
2d53be02 RK |
33 | statusCodeExpected?: HttpStatusCode |
34 | statusCodeExpectedStep2?: HttpStatusCode | |
9107d791 C |
35 | }) { |
36 | const res = await getExternalAuth({ | |
37 | url: options.server.url, | |
38 | npmName: options.npmName, | |
39 | npmVersion: '0.0.1', | |
40 | authName: options.authName, | |
41 | query: options.query, | |
2d53be02 | 42 | statusCodeExpected: options.statusCodeExpected || HttpStatusCode.FOUND_302 |
9107d791 C |
43 | }) |
44 | ||
2d53be02 | 45 | if (res.status !== HttpStatusCode.FOUND_302) return |
9107d791 C |
46 | |
47 | const location = res.header.location | |
48 | const { externalAuthToken } = decodeQueryString(location) | |
49 | ||
50 | const resLogin = await loginUsingExternalToken( | |
51 | options.server, | |
52 | options.username, | |
d253bfaa C |
53 | externalAuthToken as string, |
54 | options.statusCodeExpectedStep2 | |
9107d791 C |
55 | ) |
56 | ||
57 | return resLogin.body | |
58 | } | |
59 | ||
60 | describe('Test external auth plugins', function () { | |
61 | let server: ServerInfo | |
62 | ||
63 | let cyanAccessToken: string | |
64 | let cyanRefreshToken: string | |
65 | ||
66 | let kefkaAccessToken: string | |
67 | let kefkaRefreshToken: string | |
68 | ||
69 | let externalAuthToken: string | |
70 | ||
71 | before(async function () { | |
72 | this.timeout(30000) | |
73 | ||
74 | server = await flushAndRunServer(1) | |
75 | await setAccessTokensToServers([ server ]) | |
76 | ||
74fd2643 | 77 | for (const suffix of [ 'one', 'two', 'three' ]) { |
9107d791 C |
78 | await installPlugin({ |
79 | url: server.url, | |
80 | accessToken: server.accessToken, | |
81 | path: getPluginTestPath('-external-auth-' + suffix) | |
82 | }) | |
83 | } | |
84 | }) | |
85 | ||
86 | it('Should display the correct configuration', async function () { | |
87 | const res = await getConfig(server.url) | |
88 | ||
89 | const config: ServerConfig = res.body | |
90 | ||
91 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 92 | expect(auths).to.have.lengthOf(8) |
9107d791 C |
93 | |
94 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | |
95 | expect(auth2).to.exist | |
96 | expect(auth2.authDisplayName).to.equal('External Auth 2') | |
97 | expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one') | |
98 | }) | |
99 | ||
100 | it('Should redirect for a Cyan login', async function () { | |
101 | const res = await getExternalAuth({ | |
102 | url: server.url, | |
103 | npmName: 'test-external-auth-one', | |
104 | npmVersion: '0.0.1', | |
105 | authName: 'external-auth-1', | |
106 | query: { | |
107 | username: 'cyan' | |
108 | }, | |
2d53be02 | 109 | statusCodeExpected: HttpStatusCode.FOUND_302 |
9107d791 C |
110 | }) |
111 | ||
112 | const location = res.header.location | |
113 | expect(location.startsWith('/login?')).to.be.true | |
114 | ||
115 | const searchParams = decodeQueryString(location) | |
116 | ||
117 | expect(searchParams.externalAuthToken).to.exist | |
118 | expect(searchParams.username).to.equal('cyan') | |
119 | ||
120 | externalAuthToken = searchParams.externalAuthToken as string | |
121 | }) | |
122 | ||
123 | it('Should reject auto external login with a missing or invalid token', async function () { | |
2d53be02 RK |
124 | await loginUsingExternalToken(server, 'cyan', '', HttpStatusCode.BAD_REQUEST_400) |
125 | await loginUsingExternalToken(server, 'cyan', 'blabla', HttpStatusCode.BAD_REQUEST_400) | |
9107d791 C |
126 | }) |
127 | ||
128 | it('Should reject auto external login with a missing or invalid username', async function () { | |
2d53be02 RK |
129 | await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) |
130 | await loginUsingExternalToken(server, '', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) | |
9107d791 C |
131 | }) |
132 | ||
133 | it('Should reject auto external login with an expired token', async function () { | |
134 | this.timeout(15000) | |
135 | ||
136 | await wait(5000) | |
137 | ||
2d53be02 | 138 | await loginUsingExternalToken(server, 'cyan', externalAuthToken, HttpStatusCode.BAD_REQUEST_400) |
9107d791 C |
139 | |
140 | await waitUntilLog(server, 'expired external auth token') | |
141 | }) | |
142 | ||
143 | it('Should auto login Cyan, create the user and use the token', async function () { | |
144 | { | |
145 | const res = await loginExternal({ | |
146 | server, | |
147 | npmName: 'test-external-auth-one', | |
148 | authName: 'external-auth-1', | |
149 | query: { | |
150 | username: 'cyan' | |
151 | }, | |
152 | username: 'cyan' | |
153 | }) | |
154 | ||
155 | cyanAccessToken = res.access_token | |
156 | cyanRefreshToken = res.refresh_token | |
157 | } | |
158 | ||
159 | { | |
160 | const res = await getMyUserInformation(server.url, cyanAccessToken) | |
161 | ||
162 | const body: User = res.body | |
163 | expect(body.username).to.equal('cyan') | |
164 | expect(body.account.displayName).to.equal('cyan') | |
165 | expect(body.email).to.equal('cyan@example.com') | |
166 | expect(body.role).to.equal(UserRole.USER) | |
167 | } | |
168 | }) | |
169 | ||
170 | it('Should auto login Kefka, create the user and use the token', async function () { | |
171 | { | |
172 | const res = await loginExternal({ | |
173 | server, | |
174 | npmName: 'test-external-auth-one', | |
175 | authName: 'external-auth-2', | |
176 | username: 'kefka' | |
177 | }) | |
178 | ||
179 | kefkaAccessToken = res.access_token | |
180 | kefkaRefreshToken = res.refresh_token | |
181 | } | |
182 | ||
183 | { | |
184 | const res = await getMyUserInformation(server.url, kefkaAccessToken) | |
185 | ||
186 | const body: User = res.body | |
187 | expect(body.username).to.equal('kefka') | |
188 | expect(body.account.displayName).to.equal('Kefka Palazzo') | |
189 | expect(body.email).to.equal('kefka@example.com') | |
190 | expect(body.role).to.equal(UserRole.ADMINISTRATOR) | |
191 | } | |
192 | }) | |
193 | ||
194 | it('Should refresh Cyan token, but not Kefka token', async function () { | |
195 | { | |
196 | const resRefresh = await refreshToken(server, cyanRefreshToken) | |
197 | cyanAccessToken = resRefresh.body.access_token | |
198 | cyanRefreshToken = resRefresh.body.refresh_token | |
199 | ||
200 | const res = await getMyUserInformation(server.url, cyanAccessToken) | |
201 | const user: User = res.body | |
202 | expect(user.username).to.equal('cyan') | |
203 | } | |
204 | ||
205 | { | |
2d53be02 | 206 | await refreshToken(server, kefkaRefreshToken, HttpStatusCode.BAD_REQUEST_400) |
9107d791 C |
207 | } |
208 | }) | |
209 | ||
210 | it('Should update Cyan profile', async function () { | |
211 | await updateMyUser({ | |
212 | url: server.url, | |
213 | accessToken: cyanAccessToken, | |
214 | displayName: 'Cyan Garamonde', | |
215 | description: 'Retainer to the king of Doma' | |
216 | }) | |
217 | ||
218 | const res = await getMyUserInformation(server.url, cyanAccessToken) | |
219 | ||
220 | const body: User = res.body | |
221 | expect(body.account.displayName).to.equal('Cyan Garamonde') | |
222 | expect(body.account.description).to.equal('Retainer to the king of Doma') | |
223 | }) | |
224 | ||
225 | it('Should logout Cyan', async function () { | |
226 | await logout(server.url, cyanAccessToken) | |
227 | }) | |
228 | ||
229 | it('Should have logged out Cyan', async function () { | |
230 | await waitUntilLog(server, 'On logout cyan') | |
231 | ||
2d53be02 | 232 | await getMyUserInformation(server.url, cyanAccessToken, HttpStatusCode.UNAUTHORIZED_401) |
9107d791 C |
233 | }) |
234 | ||
235 | it('Should login Cyan and keep the old existing profile', async function () { | |
236 | { | |
237 | const res = await loginExternal({ | |
238 | server, | |
239 | npmName: 'test-external-auth-one', | |
240 | authName: 'external-auth-1', | |
241 | query: { | |
242 | username: 'cyan' | |
243 | }, | |
244 | username: 'cyan' | |
245 | }) | |
246 | ||
247 | cyanAccessToken = res.access_token | |
248 | } | |
249 | ||
250 | const res = await getMyUserInformation(server.url, cyanAccessToken) | |
251 | ||
252 | const body: User = res.body | |
253 | expect(body.username).to.equal('cyan') | |
254 | expect(body.account.displayName).to.equal('Cyan Garamonde') | |
255 | expect(body.account.description).to.equal('Retainer to the king of Doma') | |
256 | expect(body.role).to.equal(UserRole.USER) | |
257 | }) | |
258 | ||
9a7fd960 C |
259 | it('Should not update an external auth email', async function () { |
260 | await updateMyUser({ | |
261 | url: server.url, | |
262 | accessToken: cyanAccessToken, | |
263 | email: 'toto@example.com', | |
264 | currentPassword: 'toto', | |
2d53be02 | 265 | statusCodeExpected: HttpStatusCode.BAD_REQUEST_400 |
9a7fd960 C |
266 | }) |
267 | }) | |
268 | ||
9107d791 C |
269 | it('Should reject token of Kefka by the plugin hook', async function () { |
270 | this.timeout(10000) | |
271 | ||
272 | await wait(5000) | |
273 | ||
2d53be02 | 274 | await getMyUserInformation(server.url, kefkaAccessToken, HttpStatusCode.UNAUTHORIZED_401) |
9107d791 C |
275 | }) |
276 | ||
a4995eb7 C |
277 | it('Should unregister external-auth-2 and do not login existing Kefka', async function () { |
278 | await updatePluginSettings({ | |
279 | url: server.url, | |
280 | accessToken: server.accessToken, | |
281 | npmName: 'peertube-plugin-test-external-auth-one', | |
282 | settings: { disableKefka: true } | |
283 | }) | |
284 | ||
2d53be02 | 285 | await userLogin(server, { username: 'kefka', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400) |
a4995eb7 C |
286 | |
287 | await loginExternal({ | |
288 | server, | |
289 | npmName: 'test-external-auth-one', | |
290 | authName: 'external-auth-2', | |
291 | query: { | |
292 | username: 'kefka' | |
293 | }, | |
294 | username: 'kefka', | |
2d53be02 | 295 | statusCodeExpected: HttpStatusCode.NOT_FOUND_404 |
a4995eb7 C |
296 | }) |
297 | }) | |
298 | ||
299 | it('Should have disabled this auth', async function () { | |
300 | const res = await getConfig(server.url) | |
301 | ||
302 | const config: ServerConfig = res.body | |
303 | ||
304 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 305 | expect(auths).to.have.lengthOf(7) |
a4995eb7 C |
306 | |
307 | const auth1 = auths.find(a => a.authName === 'external-auth-2') | |
308 | expect(auth1).to.not.exist | |
309 | }) | |
310 | ||
9107d791 C |
311 | it('Should uninstall the plugin one and do not login Cyan', async function () { |
312 | await uninstallPlugin({ | |
313 | url: server.url, | |
314 | accessToken: server.accessToken, | |
315 | npmName: 'peertube-plugin-test-external-auth-one' | |
316 | }) | |
317 | ||
318 | await loginExternal({ | |
319 | server, | |
320 | npmName: 'test-external-auth-one', | |
321 | authName: 'external-auth-1', | |
322 | query: { | |
323 | username: 'cyan' | |
324 | }, | |
325 | username: 'cyan', | |
2d53be02 | 326 | statusCodeExpected: HttpStatusCode.NOT_FOUND_404 |
9107d791 | 327 | }) |
d253bfaa | 328 | |
2d53be02 RK |
329 | await userLogin(server, { username: 'cyan', password: null }, HttpStatusCode.BAD_REQUEST_400) |
330 | await userLogin(server, { username: 'cyan', password: '' }, HttpStatusCode.BAD_REQUEST_400) | |
331 | await userLogin(server, { username: 'cyan', password: 'fake' }, HttpStatusCode.BAD_REQUEST_400) | |
d253bfaa C |
332 | }) |
333 | ||
334 | it('Should not login kefka with another plugin', async function () { | |
335 | await loginExternal({ | |
336 | server, | |
337 | npmName: 'test-external-auth-two', | |
338 | authName: 'external-auth-4', | |
339 | username: 'kefka2', | |
2d53be02 | 340 | statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa C |
341 | }) |
342 | ||
343 | await loginExternal({ | |
344 | server, | |
345 | npmName: 'test-external-auth-two', | |
346 | authName: 'external-auth-4', | |
347 | username: 'kefka', | |
2d53be02 | 348 | statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa C |
349 | }) |
350 | }) | |
351 | ||
352 | it('Should not login an existing user', async function () { | |
353 | await createUser({ | |
354 | url: server.url, | |
355 | accessToken: server.accessToken, | |
356 | username: 'existing_user', | |
357 | password: 'super_password' | |
358 | }) | |
359 | ||
360 | await loginExternal({ | |
361 | server, | |
362 | npmName: 'test-external-auth-two', | |
363 | authName: 'external-auth-6', | |
364 | username: 'existing_user', | |
2d53be02 | 365 | statusCodeExpectedStep2: HttpStatusCode.BAD_REQUEST_400 |
d253bfaa | 366 | }) |
9107d791 C |
367 | }) |
368 | ||
369 | it('Should display the correct configuration', async function () { | |
370 | const res = await getConfig(server.url) | |
371 | ||
372 | const config: ServerConfig = res.body | |
373 | ||
374 | const auths = config.plugin.registeredExternalAuths | |
74fd2643 | 375 | expect(auths).to.have.lengthOf(6) |
9107d791 C |
376 | |
377 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | |
378 | expect(auth2).to.not.exist | |
379 | }) | |
380 | ||
381 | after(async function () { | |
382 | await cleanupTests([ server ]) | |
383 | }) | |
74fd2643 C |
384 | |
385 | it('Should forward the redirectUrl if the plugin returns one', async function () { | |
386 | const resLogin = await loginExternal({ | |
387 | server, | |
388 | npmName: 'test-external-auth-three', | |
389 | authName: 'external-auth-7', | |
390 | username: 'cid' | |
391 | }) | |
392 | ||
393 | const resLogout = await logout(server.url, resLogin.access_token) | |
394 | ||
395 | expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl') | |
396 | }) | |
397 | ||
398 | it('Should call the plugin\'s onLogout method with the request', async function () { | |
399 | const resLogin = await loginExternal({ | |
400 | server, | |
401 | npmName: 'test-external-auth-three', | |
402 | authName: 'external-auth-8', | |
403 | username: 'cid' | |
404 | }) | |
405 | ||
406 | const resLogout = await logout(server.url, resLogin.access_token) | |
407 | ||
408 | expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token) | |
409 | }) | |
9107d791 | 410 | }) |