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