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