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