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