aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages/tests/src/api/videos/video-static-file-privacy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tests/src/api/videos/video-static-file-privacy.ts')
-rw-r--r--packages/tests/src/api/videos/video-static-file-privacy.ts602
1 files changed, 602 insertions, 0 deletions
diff --git a/packages/tests/src/api/videos/video-static-file-privacy.ts b/packages/tests/src/api/videos/video-static-file-privacy.ts
new file mode 100644
index 000000000..7c8d14815
--- /dev/null
+++ b/packages/tests/src/api/videos/video-static-file-privacy.ts
@@ -0,0 +1,602 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { decode } from 'magnet-uri'
5import { getAllFiles, wait } from '@peertube/peertube-core-utils'
6import { HttpStatusCode, HttpStatusCodeType, LiveVideo, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
7import {
8 cleanupTests,
9 createSingleServer,
10 findExternalSavedVideo,
11 makeRawRequest,
12 PeerTubeServer,
13 sendRTMPStream,
14 setAccessTokensToServers,
15 setDefaultVideoChannel,
16 stopFfmpeg,
17 waitJobs
18} from '@peertube/peertube-server-commands'
19import { expectStartWith } from '@tests/shared/checks.js'
20import { checkVideoFileTokenReinjection } from '@tests/shared/streaming-playlists.js'
21import { parseTorrentVideo } from '@tests/shared/webtorrent.js'
22
23describe('Test video static file privacy', function () {
24 let server: PeerTubeServer
25 let userToken: string
26
27 before(async function () {
28 this.timeout(50000)
29
30 server = await createSingleServer(1)
31 await setAccessTokensToServers([ server ])
32 await setDefaultVideoChannel([ server ])
33
34 userToken = await server.users.generateUserAndToken('user1')
35 })
36
37 describe('VOD static file path', function () {
38
39 function runSuite () {
40
41 async function checkPrivateFiles (uuid: string) {
42 const video = await server.videos.getWithToken({ id: uuid })
43
44 for (const file of video.files) {
45 expect(file.fileDownloadUrl).to.not.include('/private/')
46 expectStartWith(file.fileUrl, server.url + '/static/web-videos/private/')
47
48 const torrent = await parseTorrentVideo(server, file)
49 expect(torrent.urlList).to.have.lengthOf(0)
50
51 const magnet = decode(file.magnetUri)
52 expect(magnet.urlList).to.have.lengthOf(0)
53
54 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
55 }
56
57 const hls = video.streamingPlaylists[0]
58 if (hls) {
59 expectStartWith(hls.playlistUrl, server.url + '/static/streaming-playlists/hls/private/')
60 expectStartWith(hls.segmentsSha256Url, server.url + '/static/streaming-playlists/hls/private/')
61
62 await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
63 await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
64 }
65 }
66
67 async function checkPublicFiles (uuid: string) {
68 const video = await server.videos.get({ id: uuid })
69
70 for (const file of getAllFiles(video)) {
71 expect(file.fileDownloadUrl).to.not.include('/private/')
72 expect(file.fileUrl).to.not.include('/private/')
73
74 const torrent = await parseTorrentVideo(server, file)
75 expect(torrent.urlList[0]).to.not.include('private')
76
77 const magnet = decode(file.magnetUri)
78 expect(magnet.urlList[0]).to.not.include('private')
79
80 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
81 await makeRawRequest({ url: torrent.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
82 await makeRawRequest({ url: magnet.urlList[0], expectedStatus: HttpStatusCode.OK_200 })
83 }
84
85 const hls = video.streamingPlaylists[0]
86 if (hls) {
87 expect(hls.playlistUrl).to.not.include('private')
88 expect(hls.segmentsSha256Url).to.not.include('private')
89
90 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
91 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
92 }
93 }
94
95 it('Should upload a private/internal/password protected video and have a private static path', async function () {
96 this.timeout(120000)
97
98 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
99 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy })
100 await waitJobs([ server ])
101
102 await checkPrivateFiles(uuid)
103 }
104
105 const { uuid } = await server.videos.quickUpload({
106 name: 'video',
107 privacy: VideoPrivacy.PASSWORD_PROTECTED,
108 videoPasswords: [ 'my super password' ]
109 })
110 await waitJobs([ server ])
111
112 await checkPrivateFiles(uuid)
113 })
114
115 it('Should upload a public video and update it as private/internal to have a private static path', async function () {
116 this.timeout(120000)
117
118 for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) {
119 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PUBLIC })
120 await waitJobs([ server ])
121
122 await server.videos.update({ id: uuid, attributes: { privacy } })
123 await waitJobs([ server ])
124
125 await checkPrivateFiles(uuid)
126 }
127 })
128
129 it('Should upload a private video and update it to unlisted to have a public static path', async function () {
130 this.timeout(120000)
131
132 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
133 await waitJobs([ server ])
134
135 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.UNLISTED } })
136 await waitJobs([ server ])
137
138 await checkPublicFiles(uuid)
139 })
140
141 it('Should upload an internal video and update it to public to have a public static path', async function () {
142 this.timeout(120000)
143
144 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
145 await waitJobs([ server ])
146
147 await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
148 await waitJobs([ server ])
149
150 await checkPublicFiles(uuid)
151 })
152
153 it('Should upload an internal video and schedule a public publish', async function () {
154 this.timeout(120000)
155
156 const attributes = {
157 name: 'video',
158 privacy: VideoPrivacy.PRIVATE,
159 scheduleUpdate: {
160 updateAt: new Date(Date.now() + 1000).toISOString(),
161 privacy: VideoPrivacy.PUBLIC
162 }
163 }
164
165 const { uuid } = await server.videos.upload({ attributes })
166
167 await waitJobs([ server ])
168 await wait(1000)
169 await server.debug.sendCommand({ body: { command: 'process-update-videos-scheduler' } })
170
171 await waitJobs([ server ])
172
173 await checkPublicFiles(uuid)
174 })
175 }
176
177 describe('Without transcoding', function () {
178 runSuite()
179 })
180
181 describe('With transcoding', function () {
182
183 before(async function () {
184 await server.config.enableMinimumTranscoding()
185 })
186
187 runSuite()
188 })
189 })
190
191 describe('VOD static file right check', function () {
192 let unrelatedFileToken: string
193
194 async function checkVideoFiles (options: {
195 id: string
196 expectedStatus: HttpStatusCodeType
197 token: string
198 videoFileToken: string
199 videoPassword?: string
200 }) {
201 const { id, expectedStatus, token, videoFileToken, videoPassword } = options
202
203 const video = await server.videos.getWithToken({ id })
204
205 for (const file of getAllFiles(video)) {
206 await makeRawRequest({ url: file.fileUrl, token, expectedStatus })
207 await makeRawRequest({ url: file.fileDownloadUrl, token, expectedStatus })
208
209 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken }, expectedStatus })
210 await makeRawRequest({ url: file.fileDownloadUrl, query: { videoFileToken }, expectedStatus })
211
212 if (videoPassword) {
213 const headers = { 'x-peertube-video-password': videoPassword }
214 await makeRawRequest({ url: file.fileUrl, headers, expectedStatus })
215 await makeRawRequest({ url: file.fileDownloadUrl, headers, expectedStatus })
216 }
217 }
218
219 const hls = video.streamingPlaylists[0]
220 await makeRawRequest({ url: hls.playlistUrl, token, expectedStatus })
221 await makeRawRequest({ url: hls.segmentsSha256Url, token, expectedStatus })
222
223 await makeRawRequest({ url: hls.playlistUrl, query: { videoFileToken }, expectedStatus })
224 await makeRawRequest({ url: hls.segmentsSha256Url, query: { videoFileToken }, expectedStatus })
225
226 if (videoPassword) {
227 const headers = { 'x-peertube-video-password': videoPassword }
228 await makeRawRequest({ url: hls.playlistUrl, token: null, headers, expectedStatus })
229 await makeRawRequest({ url: hls.segmentsSha256Url, token: null, headers, expectedStatus })
230 }
231 }
232
233 before(async function () {
234 await server.config.enableMinimumTranscoding()
235
236 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
237 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
238 })
239
240 it('Should not be able to access a private video files without OAuth token and file token', async function () {
241 this.timeout(120000)
242
243 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
244 await waitJobs([ server ])
245
246 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.FORBIDDEN_403, token: null, videoFileToken: null })
247 })
248
249 it('Should not be able to access password protected video files without OAuth token, file token and password', async function () {
250 this.timeout(120000)
251 const videoPassword = 'my super password'
252
253 const { uuid } = await server.videos.quickUpload({
254 name: 'password protected video',
255 privacy: VideoPrivacy.PASSWORD_PROTECTED,
256 videoPasswords: [ videoPassword ]
257 })
258 await waitJobs([ server ])
259
260 await checkVideoFiles({
261 id: uuid,
262 expectedStatus: HttpStatusCode.FORBIDDEN_403,
263 token: null,
264 videoFileToken: null,
265 videoPassword: null
266 })
267 })
268
269 it('Should not be able to access an password video files with incorrect OAuth token, file token and password', async function () {
270 this.timeout(120000)
271 const videoPassword = 'my super password'
272
273 const { uuid } = await server.videos.quickUpload({
274 name: 'password protected video',
275 privacy: VideoPrivacy.PASSWORD_PROTECTED,
276 videoPasswords: [ videoPassword ]
277 })
278 await waitJobs([ server ])
279
280 await checkVideoFiles({
281 id: uuid,
282 expectedStatus: HttpStatusCode.FORBIDDEN_403,
283 token: userToken,
284 videoFileToken: unrelatedFileToken,
285 videoPassword: 'incorrectPassword'
286 })
287 })
288
289 it('Should not be able to access an private video files without appropriate OAuth token and file token', async function () {
290 this.timeout(120000)
291
292 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
293 await waitJobs([ server ])
294
295 await checkVideoFiles({
296 id: uuid,
297 expectedStatus: HttpStatusCode.FORBIDDEN_403,
298 token: userToken,
299 videoFileToken: unrelatedFileToken
300 })
301 })
302
303 it('Should be able to access a private video files with appropriate OAuth token or file token', async function () {
304 this.timeout(120000)
305
306 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
307 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
308
309 await waitJobs([ server ])
310
311 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
312 })
313
314 it('Should be able to access a password protected video files with appropriate OAuth token or file token', async function () {
315 this.timeout(120000)
316 const videoPassword = 'my super password'
317
318 const { uuid } = await server.videos.quickUpload({
319 name: 'video',
320 privacy: VideoPrivacy.PASSWORD_PROTECTED,
321 videoPasswords: [ videoPassword ]
322 })
323
324 const videoFileToken = await server.videoToken.getVideoFileToken({ token: null, videoId: uuid, videoPassword })
325
326 await waitJobs([ server ])
327
328 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken, videoPassword })
329 })
330
331 it('Should reinject video file token', async function () {
332 this.timeout(120000)
333
334 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
335
336 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
337 await waitJobs([ server ])
338
339 {
340 const video = await server.videos.getWithToken({ id: uuid })
341 const hls = video.streamingPlaylists[0]
342 const query = { videoFileToken }
343 const { text } = await makeRawRequest({ url: hls.playlistUrl, query, expectedStatus: HttpStatusCode.OK_200 })
344
345 expect(text).to.not.include(videoFileToken)
346 }
347
348 {
349 await checkVideoFileTokenReinjection({
350 server,
351 videoUUID: uuid,
352 videoFileToken,
353 resolutions: [ 240, 720 ],
354 isLive: false
355 })
356 }
357 })
358
359 it('Should be able to access a private video of another user with an admin OAuth token or file token', async function () {
360 this.timeout(120000)
361
362 const { uuid } = await server.videos.quickUpload({ name: 'video', token: userToken, privacy: VideoPrivacy.PRIVATE })
363 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
364
365 await waitJobs([ server ])
366
367 await checkVideoFiles({ id: uuid, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken, videoFileToken })
368 })
369 })
370
371 describe('Live static file path and check', function () {
372 let normalLiveId: string
373 let normalLive: LiveVideo
374
375 let permanentLiveId: string
376 let permanentLive: LiveVideo
377
378 let passwordProtectedLiveId: string
379 let passwordProtectedLive: LiveVideo
380
381 const correctPassword = 'my super password'
382
383 let unrelatedFileToken: string
384
385 async function checkLiveFiles (options: { live: LiveVideo, liveId: string, videoPassword?: string }) {
386 const { live, liveId, videoPassword } = options
387 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey })
388 await server.live.waitUntilPublished({ videoId: liveId })
389
390 const video = await server.videos.getWithToken({ id: liveId })
391
392 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
393
394 const hls = video.streamingPlaylists[0]
395
396 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
397 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
398
399 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
400 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
401
402 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
403 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
404 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
405
406 if (videoPassword) {
407 await makeRawRequest({ url, headers: { 'x-peertube-video-password': videoPassword }, expectedStatus: HttpStatusCode.OK_200 })
408 await makeRawRequest({
409 url,
410 headers: { 'x-peertube-video-password': 'incorrectPassword' },
411 expectedStatus: HttpStatusCode.FORBIDDEN_403
412 })
413 }
414
415 }
416
417 await stopFfmpeg(ffmpegCommand)
418 }
419
420 async function checkReplay (replay: VideoDetails) {
421 const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
422
423 const hls = replay.streamingPlaylists[0]
424 expect(hls.files).to.not.have.lengthOf(0)
425
426 for (const file of hls.files) {
427 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
428 await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
429
430 await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
431 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
432 await makeRawRequest({
433 url: file.fileUrl,
434 query: { videoFileToken: unrelatedFileToken },
435 expectedStatus: HttpStatusCode.FORBIDDEN_403
436 })
437 }
438
439 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
440 expectStartWith(url, server.url + '/static/streaming-playlists/hls/private/')
441
442 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
443 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
444
445 await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
446 await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
447 await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
448 }
449 }
450
451 before(async function () {
452 await server.config.enableMinimumTranscoding()
453
454 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
455 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
456
457 await server.config.enableLive({
458 allowReplay: true,
459 transcoding: true,
460 resolutions: 'min'
461 })
462
463 {
464 const { video, live } = await server.live.quickCreate({
465 saveReplay: true,
466 permanentLive: false,
467 privacy: VideoPrivacy.PRIVATE
468 })
469 normalLiveId = video.uuid
470 normalLive = live
471 }
472
473 {
474 const { video, live } = await server.live.quickCreate({
475 saveReplay: true,
476 permanentLive: true,
477 privacy: VideoPrivacy.PRIVATE
478 })
479 permanentLiveId = video.uuid
480 permanentLive = live
481 }
482
483 {
484 const { video, live } = await server.live.quickCreate({
485 saveReplay: false,
486 permanentLive: false,
487 privacy: VideoPrivacy.PASSWORD_PROTECTED,
488 videoPasswords: [ correctPassword ]
489 })
490 passwordProtectedLiveId = video.uuid
491 passwordProtectedLive = live
492 }
493 })
494
495 it('Should create a private normal live and have a private static path', async function () {
496 this.timeout(240000)
497
498 await checkLiveFiles({ live: normalLive, liveId: normalLiveId })
499 })
500
501 it('Should create a private permanent live and have a private static path', async function () {
502 this.timeout(240000)
503
504 await checkLiveFiles({ live: permanentLive, liveId: permanentLiveId })
505 })
506
507 it('Should create a password protected live and have a private static path', async function () {
508 this.timeout(240000)
509
510 await checkLiveFiles({ live: passwordProtectedLive, liveId: passwordProtectedLiveId, videoPassword: correctPassword })
511 })
512
513 it('Should reinject video file token on permanent live', async function () {
514 this.timeout(240000)
515
516 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: permanentLive.rtmpUrl, streamKey: permanentLive.streamKey })
517 await server.live.waitUntilPublished({ videoId: permanentLiveId })
518
519 const video = await server.videos.getWithToken({ id: permanentLiveId })
520 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
521 const hls = video.streamingPlaylists[0]
522
523 {
524 const query = { videoFileToken }
525 const { text } = await makeRawRequest({ url: hls.playlistUrl, query, expectedStatus: HttpStatusCode.OK_200 })
526
527 expect(text).to.not.include(videoFileToken)
528 }
529
530 {
531 await checkVideoFileTokenReinjection({
532 server,
533 videoUUID: permanentLiveId,
534 videoFileToken,
535 resolutions: [ 720 ],
536 isLive: true
537 })
538 }
539
540 await stopFfmpeg(ffmpegCommand)
541 })
542
543 it('Should have created a replay of the normal live with a private static path', async function () {
544 this.timeout(240000)
545
546 await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
547
548 const replay = await server.videos.getWithToken({ id: normalLiveId })
549 await checkReplay(replay)
550 })
551
552 it('Should have created a replay of the permanent live with a private static path', async function () {
553 this.timeout(240000)
554
555 await server.live.waitUntilWaiting({ videoId: permanentLiveId })
556 await waitJobs([ server ])
557
558 const live = await server.videos.getWithToken({ id: permanentLiveId })
559 const replayFromList = await findExternalSavedVideo(server, live)
560 const replay = await server.videos.getWithToken({ id: replayFromList.id })
561
562 await checkReplay(replay)
563 })
564 })
565
566 describe('With static file right check disabled', function () {
567 let videoUUID: string
568
569 before(async function () {
570 this.timeout(240000)
571
572 await server.kill()
573
574 await server.run({
575 static_files: {
576 private_files_require_auth: false
577 }
578 })
579
580 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.INTERNAL })
581 videoUUID = uuid
582
583 await waitJobs([ server ])
584 })
585
586 it('Should not check auth for private static files', async function () {
587 const video = await server.videos.getWithToken({ id: videoUUID })
588
589 for (const file of getAllFiles(video)) {
590 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
591 }
592
593 const hls = video.streamingPlaylists[0]
594 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
595 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
596 })
597 })
598
599 after(async function () {
600 await cleanupTests([ server ])
601 })
602})