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