]>
Commit | Line | Data |
---|---|---|
9ab330b9 C |
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | ||
3 | import { expect } from 'chai' | |
4 | import { basename } from 'path' | |
d102de1b | 5 | import { checkVideoFileTokenReinjection, expectStartWith, SQLCommand } from '@server/tests/shared' |
9ab330b9 C |
6 | import { areScalewayObjectStorageTestsDisabled, getAllFiles, getHLS } from '@shared/core-utils' |
7 | import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models' | |
8 | import { | |
9 | cleanupTests, | |
10 | createSingleServer, | |
11 | findExternalSavedVideo, | |
12 | makeRawRequest, | |
13 | ObjectStorageCommand, | |
14 | PeerTubeServer, | |
15 | sendRTMPStream, | |
16 | setAccessTokensToServers, | |
17 | setDefaultVideoChannel, | |
18 | stopFfmpeg, | |
19 | waitJobs | |
20 | } from '@shared/server-commands' | |
21 | ||
5a122ddd C |
22 | function extractFilenameFromUrl (url: string) { |
23 | const parts = basename(url).split(':') | |
24 | ||
25 | return parts[parts.length - 1] | |
26 | } | |
27 | ||
9ab330b9 C |
28 | describe('Object storage for video static file privacy', function () { |
29 | // We need real world object storage to check ACL | |
30 | if (areScalewayObjectStorageTestsDisabled()) return | |
31 | ||
32 | let server: PeerTubeServer | |
d102de1b | 33 | let sqlCommand: SQLCommand |
9ab330b9 C |
34 | let userToken: string |
35 | ||
5a122ddd | 36 | // --------------------------------------------------------------------------- |
9ab330b9 | 37 | |
5a122ddd C |
38 | async function checkPrivateVODFiles (uuid: string) { |
39 | const video = await server.videos.getWithToken({ id: uuid }) | |
9ab330b9 | 40 | |
5a122ddd C |
41 | for (const file of video.files) { |
42 | expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/') | |
9ab330b9 | 43 | |
5a122ddd C |
44 | await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) |
45 | } | |
9ab330b9 | 46 | |
5a122ddd | 47 | for (const file of getAllFiles(video)) { |
d102de1b | 48 | const internalFileUrl = await sqlCommand.getInternalFileUrl(file.id) |
5a122ddd C |
49 | expectStartWith(internalFileUrl, ObjectStorageCommand.getScalewayBaseUrl()) |
50 | await makeRawRequest({ url: internalFileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
51 | } | |
52 | ||
53 | const hls = getHLS(video) | |
54 | ||
55 | if (hls) { | |
56 | for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { | |
57 | expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
58 | } | |
9ab330b9 | 59 | |
5a122ddd C |
60 | await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) |
61 | await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
9ab330b9 | 62 | |
5a122ddd C |
63 | for (const file of hls.files) { |
64 | expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
9ab330b9 C |
65 | |
66 | await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
67 | } | |
5a122ddd C |
68 | } |
69 | } | |
9ab330b9 | 70 | |
5a122ddd C |
71 | async function checkPublicVODFiles (uuid: string) { |
72 | const video = await server.videos.getWithToken({ id: uuid }) | |
9ab330b9 | 73 | |
5a122ddd C |
74 | for (const file of getAllFiles(video)) { |
75 | expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl()) | |
9ab330b9 | 76 | |
5a122ddd C |
77 | await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 }) |
78 | } | |
9ab330b9 | 79 | |
5a122ddd | 80 | const hls = getHLS(video) |
9ab330b9 | 81 | |
5a122ddd C |
82 | if (hls) { |
83 | expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl()) | |
84 | expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl()) | |
9ab330b9 | 85 | |
5a122ddd C |
86 | await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 }) |
87 | await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 }) | |
9ab330b9 | 88 | } |
5a122ddd | 89 | } |
9ab330b9 | 90 | |
5a122ddd | 91 | // --------------------------------------------------------------------------- |
9ab330b9 | 92 | |
5a122ddd C |
93 | before(async function () { |
94 | this.timeout(120000) | |
9ab330b9 | 95 | |
5a122ddd C |
96 | server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig({ serverNumber: 1 })) |
97 | await setAccessTokensToServers([ server ]) | |
98 | await setDefaultVideoChannel([ server ]) | |
9ab330b9 | 99 | |
5a122ddd | 100 | await server.config.enableMinimumTranscoding() |
9ab330b9 | 101 | |
5a122ddd | 102 | userToken = await server.users.generateUserAndToken('user1') |
d102de1b C |
103 | |
104 | sqlCommand = new SQLCommand(server) | |
5a122ddd | 105 | }) |
9ab330b9 | 106 | |
5a122ddd C |
107 | describe('VOD', function () { |
108 | let privateVideoUUID: string | |
109 | let publicVideoUUID: string | |
110 | let userPrivateVideoUUID: string | |
111 | ||
112 | // --------------------------------------------------------------------------- | |
9ab330b9 C |
113 | |
114 | async function getSampleFileUrls (videoId: string) { | |
115 | const video = await server.videos.getWithToken({ id: videoId }) | |
116 | ||
117 | return { | |
118 | webTorrentFile: video.files[0].fileUrl, | |
119 | hlsFile: getHLS(video).files[0].fileUrl | |
120 | } | |
121 | } | |
122 | ||
5a122ddd C |
123 | // --------------------------------------------------------------------------- |
124 | ||
9ab330b9 | 125 | it('Should upload a private video and have appropriate object storage ACL', async function () { |
4fe7cde2 | 126 | this.timeout(120000) |
9ab330b9 C |
127 | |
128 | { | |
129 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) | |
130 | privateVideoUUID = uuid | |
131 | } | |
132 | ||
133 | { | |
134 | const { uuid } = await server.videos.quickUpload({ name: 'user video', token: userToken, privacy: VideoPrivacy.PRIVATE }) | |
135 | userPrivateVideoUUID = uuid | |
136 | } | |
137 | ||
138 | await waitJobs([ server ]) | |
139 | ||
5a122ddd | 140 | await checkPrivateVODFiles(privateVideoUUID) |
9ab330b9 C |
141 | }) |
142 | ||
143 | it('Should upload a public video and have appropriate object storage ACL', async function () { | |
4fe7cde2 | 144 | this.timeout(120000) |
9ab330b9 C |
145 | |
146 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED }) | |
147 | await waitJobs([ server ]) | |
148 | ||
149 | publicVideoUUID = uuid | |
150 | ||
5a122ddd | 151 | await checkPublicVODFiles(publicVideoUUID) |
9ab330b9 C |
152 | }) |
153 | ||
154 | it('Should not get files without appropriate OAuth token', async function () { | |
155 | this.timeout(60000) | |
156 | ||
157 | const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID) | |
158 | ||
159 | await makeRawRequest({ url: webTorrentFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
160 | await makeRawRequest({ url: webTorrentFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
161 | ||
162 | await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
163 | await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
164 | }) | |
165 | ||
166 | it('Should not get HLS file of another video', async function () { | |
167 | this.timeout(60000) | |
168 | ||
169 | const privateVideo = await server.videos.getWithToken({ id: privateVideoUUID }) | |
170 | const hlsFilename = basename(getHLS(privateVideo).files[0].fileUrl) | |
171 | ||
172 | const badUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + userPrivateVideoUUID + '/' + hlsFilename | |
173 | const goodUrl = server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + privateVideoUUID + '/' + hlsFilename | |
174 | ||
175 | await makeRawRequest({ url: badUrl, token: server.accessToken, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) | |
176 | await makeRawRequest({ url: goodUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
177 | }) | |
178 | ||
179 | it('Should correctly check OAuth or video file token', async function () { | |
180 | this.timeout(60000) | |
181 | ||
182 | const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID }) | |
183 | const goodVideoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID }) | |
184 | ||
185 | const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID) | |
186 | ||
187 | for (const url of [ webTorrentFile, hlsFile ]) { | |
188 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
189 | await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
190 | await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
191 | ||
192 | await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
193 | await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
194 | } | |
195 | }) | |
196 | ||
71e3e879 C |
197 | it('Should reinject video file token', async function () { |
198 | this.timeout(120000) | |
199 | ||
200 | const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID }) | |
201 | ||
202 | await checkVideoFileTokenReinjection({ | |
203 | server, | |
204 | videoUUID: privateVideoUUID, | |
205 | videoFileToken, | |
206 | resolutions: [ 240, 720 ], | |
207 | isLive: false | |
208 | }) | |
209 | }) | |
210 | ||
9ab330b9 C |
211 | it('Should update public video to private', async function () { |
212 | this.timeout(60000) | |
213 | ||
214 | await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } }) | |
215 | ||
5a122ddd | 216 | await checkPrivateVODFiles(publicVideoUUID) |
9ab330b9 C |
217 | }) |
218 | ||
219 | it('Should update private video to public', async function () { | |
220 | this.timeout(60000) | |
221 | ||
222 | await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } }) | |
223 | ||
5a122ddd | 224 | await checkPublicVODFiles(publicVideoUUID) |
9ab330b9 | 225 | }) |
9ab330b9 C |
226 | }) |
227 | ||
228 | describe('Live', function () { | |
229 | let normalLiveId: string | |
230 | let normalLive: LiveVideo | |
231 | ||
232 | let permanentLiveId: string | |
233 | let permanentLive: LiveVideo | |
234 | ||
235 | let unrelatedFileToken: string | |
236 | ||
5a122ddd C |
237 | // --------------------------------------------------------------------------- |
238 | ||
9ab330b9 C |
239 | async function checkLiveFiles (live: LiveVideo, liveId: string) { |
240 | const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey }) | |
241 | await server.live.waitUntilPublished({ videoId: liveId }) | |
242 | ||
243 | const video = await server.videos.getWithToken({ id: liveId }) | |
244 | const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid }) | |
245 | ||
246 | const hls = video.streamingPlaylists[0] | |
247 | ||
248 | for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { | |
249 | expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
250 | ||
251 | await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
252 | await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
253 | ||
254 | await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
255 | await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
256 | ||
257 | await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
258 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
259 | await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
260 | } | |
261 | ||
262 | await stopFfmpeg(ffmpegCommand) | |
263 | } | |
264 | ||
265 | async function checkReplay (replay: VideoDetails) { | |
266 | const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid }) | |
267 | ||
268 | const hls = replay.streamingPlaylists[0] | |
269 | expect(hls.files).to.not.have.lengthOf(0) | |
270 | ||
271 | for (const file of hls.files) { | |
272 | await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
273 | await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
274 | ||
275 | await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
276 | await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
277 | await makeRawRequest({ | |
278 | url: file.fileUrl, | |
279 | query: { videoFileToken: unrelatedFileToken }, | |
280 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | |
281 | }) | |
282 | } | |
283 | ||
284 | for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { | |
285 | expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
286 | ||
287 | await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
288 | await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
289 | ||
290 | await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
291 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
292 | await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
293 | } | |
294 | } | |
295 | ||
5a122ddd C |
296 | // --------------------------------------------------------------------------- |
297 | ||
9ab330b9 C |
298 | before(async function () { |
299 | await server.config.enableMinimumTranscoding() | |
300 | ||
301 | const { uuid } = await server.videos.quickUpload({ name: 'another video' }) | |
302 | unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid }) | |
303 | ||
304 | await server.config.enableLive({ | |
305 | allowReplay: true, | |
306 | transcoding: true, | |
307 | resolutions: 'min' | |
308 | }) | |
309 | ||
310 | { | |
05a60d85 W |
311 | const { video, live } = await server.live.quickCreate({ |
312 | saveReplay: true, | |
313 | permanentLive: false, | |
314 | privacy: VideoPrivacy.PRIVATE | |
315 | }) | |
9ab330b9 C |
316 | normalLiveId = video.uuid |
317 | normalLive = live | |
318 | } | |
319 | ||
320 | { | |
05a60d85 W |
321 | const { video, live } = await server.live.quickCreate({ |
322 | saveReplay: true, | |
323 | permanentLive: true, | |
324 | privacy: VideoPrivacy.PRIVATE | |
325 | }) | |
9ab330b9 C |
326 | permanentLiveId = video.uuid |
327 | permanentLive = live | |
328 | } | |
329 | }) | |
330 | ||
331 | it('Should create a private normal live and have a private static path', async function () { | |
332 | this.timeout(240000) | |
333 | ||
334 | await checkLiveFiles(normalLive, normalLiveId) | |
335 | }) | |
336 | ||
337 | it('Should create a private permanent live and have a private static path', async function () { | |
338 | this.timeout(240000) | |
339 | ||
340 | await checkLiveFiles(permanentLive, permanentLiveId) | |
341 | }) | |
342 | ||
71e3e879 C |
343 | it('Should reinject video file token in permanent live', async function () { |
344 | this.timeout(240000) | |
345 | ||
346 | const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: permanentLive.rtmpUrl, streamKey: permanentLive.streamKey }) | |
347 | await server.live.waitUntilPublished({ videoId: permanentLiveId }) | |
348 | ||
349 | const video = await server.videos.getWithToken({ id: permanentLiveId }) | |
350 | const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid }) | |
351 | ||
352 | await checkVideoFileTokenReinjection({ | |
353 | server, | |
354 | videoUUID: permanentLiveId, | |
355 | videoFileToken, | |
356 | resolutions: [ 720 ], | |
357 | isLive: true | |
358 | }) | |
359 | ||
360 | await stopFfmpeg(ffmpegCommand) | |
361 | }) | |
362 | ||
9ab330b9 C |
363 | it('Should have created a replay of the normal live with a private static path', async function () { |
364 | this.timeout(240000) | |
365 | ||
366 | await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId }) | |
367 | ||
368 | const replay = await server.videos.getWithToken({ id: normalLiveId }) | |
369 | await checkReplay(replay) | |
370 | }) | |
371 | ||
372 | it('Should have created a replay of the permanent live with a private static path', async function () { | |
373 | this.timeout(240000) | |
374 | ||
375 | await server.live.waitUntilWaiting({ videoId: permanentLiveId }) | |
376 | await waitJobs([ server ]) | |
377 | ||
378 | const live = await server.videos.getWithToken({ id: permanentLiveId }) | |
379 | const replayFromList = await findExternalSavedVideo(server, live) | |
380 | const replay = await server.videos.getWithToken({ id: replayFromList.id }) | |
381 | ||
382 | await checkReplay(replay) | |
383 | }) | |
384 | }) | |
385 | ||
5a122ddd C |
386 | describe('With private files proxy disabled and public ACL for private files', function () { |
387 | let videoUUID: string | |
388 | ||
389 | before(async function () { | |
390 | this.timeout(240000) | |
391 | ||
392 | await server.kill() | |
393 | ||
394 | const config = ObjectStorageCommand.getDefaultScalewayConfig({ | |
e8299615 | 395 | serverNumber: 1, |
5a122ddd C |
396 | enablePrivateProxy: false, |
397 | privateACL: 'public-read' | |
398 | }) | |
399 | await server.run(config) | |
400 | ||
401 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) | |
402 | videoUUID = uuid | |
403 | ||
404 | await waitJobs([ server ]) | |
405 | }) | |
406 | ||
407 | it('Should display object storage path for a private video and be able to access them', async function () { | |
408 | this.timeout(60000) | |
409 | ||
410 | await checkPublicVODFiles(videoUUID) | |
411 | }) | |
412 | ||
413 | it('Should not be able to access object storage proxy', async function () { | |
414 | const privateVideo = await server.videos.getWithToken({ id: videoUUID }) | |
415 | const webtorrentFilename = extractFilenameFromUrl(privateVideo.files[0].fileUrl) | |
416 | const hlsFilename = extractFilenameFromUrl(getHLS(privateVideo).files[0].fileUrl) | |
417 | ||
418 | await makeRawRequest({ | |
419 | url: server.url + '/object-storage-proxy/webseed/private/' + webtorrentFilename, | |
420 | token: server.accessToken, | |
421 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
422 | }) | |
423 | ||
424 | await makeRawRequest({ | |
425 | url: server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + videoUUID + '/' + hlsFilename, | |
426 | token: server.accessToken, | |
427 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
428 | }) | |
429 | }) | |
430 | }) | |
431 | ||
9ab330b9 | 432 | after(async function () { |
1ebe2c2b | 433 | this.timeout(240000) |
508c1b1e C |
434 | |
435 | const { data } = await server.videos.listAllForAdmin() | |
436 | ||
437 | for (const v of data) { | |
438 | await server.videos.remove({ id: v.uuid }) | |
439 | } | |
440 | ||
441 | for (const v of data) { | |
e8299615 | 442 | await server.servers.waitUntilLog('Removed files of video ' + v.url) |
508c1b1e C |
443 | } |
444 | ||
d102de1b | 445 | await sqlCommand.cleanup() |
9ab330b9 C |
446 | await cleanupTests([ server ]) |
447 | }) | |
448 | }) |