]>
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' | |
5 | import { expectStartWith } from '@server/tests/shared' | |
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 C |
122 | it('Should upload a private video and have appropriate object storage ACL', async function () { |
123 | this.timeout(60000) | |
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 () { | |
141 | this.timeout(60000) | |
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 | ||
194 | it('Should update public video to private', async function () { | |
195 | this.timeout(60000) | |
196 | ||
197 | await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } }) | |
198 | ||
5a122ddd | 199 | await checkPrivateVODFiles(publicVideoUUID) |
9ab330b9 C |
200 | }) |
201 | ||
202 | it('Should update private video to public', async function () { | |
203 | this.timeout(60000) | |
204 | ||
205 | await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } }) | |
206 | ||
5a122ddd | 207 | await checkPublicVODFiles(publicVideoUUID) |
9ab330b9 | 208 | }) |
9ab330b9 C |
209 | }) |
210 | ||
211 | describe('Live', function () { | |
212 | let normalLiveId: string | |
213 | let normalLive: LiveVideo | |
214 | ||
215 | let permanentLiveId: string | |
216 | let permanentLive: LiveVideo | |
217 | ||
218 | let unrelatedFileToken: string | |
219 | ||
5a122ddd C |
220 | // --------------------------------------------------------------------------- |
221 | ||
9ab330b9 C |
222 | async function checkLiveFiles (live: LiveVideo, liveId: string) { |
223 | const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: live.rtmpUrl, streamKey: live.streamKey }) | |
224 | await server.live.waitUntilPublished({ videoId: liveId }) | |
225 | ||
226 | const video = await server.videos.getWithToken({ id: liveId }) | |
227 | const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid }) | |
228 | ||
229 | const hls = video.streamingPlaylists[0] | |
230 | ||
231 | for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { | |
232 | expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
233 | ||
234 | await makeRawRequest({ url: hls.playlistUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
235 | await makeRawRequest({ url: hls.segmentsSha256Url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
236 | ||
237 | await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
238 | await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
239 | ||
240 | await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
241 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
242 | await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
243 | } | |
244 | ||
245 | await stopFfmpeg(ffmpegCommand) | |
246 | } | |
247 | ||
248 | async function checkReplay (replay: VideoDetails) { | |
249 | const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid }) | |
250 | ||
251 | const hls = replay.streamingPlaylists[0] | |
252 | expect(hls.files).to.not.have.lengthOf(0) | |
253 | ||
254 | for (const file of hls.files) { | |
255 | await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
256 | await makeRawRequest({ url: file.fileUrl, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
257 | ||
258 | await makeRawRequest({ url: file.fileUrl, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
259 | await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
260 | await makeRawRequest({ | |
261 | url: file.fileUrl, | |
262 | query: { videoFileToken: unrelatedFileToken }, | |
263 | expectedStatus: HttpStatusCode.FORBIDDEN_403 | |
264 | }) | |
265 | } | |
266 | ||
267 | for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) { | |
268 | expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/') | |
269 | ||
270 | await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 }) | |
271 | await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 }) | |
272 | ||
273 | await makeRawRequest({ url, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
274 | await makeRawRequest({ url, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
275 | await makeRawRequest({ url, query: { videoFileToken: unrelatedFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) | |
276 | } | |
277 | } | |
278 | ||
5a122ddd C |
279 | // --------------------------------------------------------------------------- |
280 | ||
9ab330b9 C |
281 | before(async function () { |
282 | await server.config.enableMinimumTranscoding() | |
283 | ||
284 | const { uuid } = await server.videos.quickUpload({ name: 'another video' }) | |
285 | unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid }) | |
286 | ||
287 | await server.config.enableLive({ | |
288 | allowReplay: true, | |
289 | transcoding: true, | |
290 | resolutions: 'min' | |
291 | }) | |
292 | ||
293 | { | |
294 | const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: false, privacy: VideoPrivacy.PRIVATE }) | |
295 | normalLiveId = video.uuid | |
296 | normalLive = live | |
297 | } | |
298 | ||
299 | { | |
300 | const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: true, privacy: VideoPrivacy.PRIVATE }) | |
301 | permanentLiveId = video.uuid | |
302 | permanentLive = live | |
303 | } | |
304 | }) | |
305 | ||
306 | it('Should create a private normal live and have a private static path', async function () { | |
307 | this.timeout(240000) | |
308 | ||
309 | await checkLiveFiles(normalLive, normalLiveId) | |
310 | }) | |
311 | ||
312 | it('Should create a private permanent live and have a private static path', async function () { | |
313 | this.timeout(240000) | |
314 | ||
315 | await checkLiveFiles(permanentLive, permanentLiveId) | |
316 | }) | |
317 | ||
318 | it('Should have created a replay of the normal live with a private static path', async function () { | |
319 | this.timeout(240000) | |
320 | ||
321 | await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId }) | |
322 | ||
323 | const replay = await server.videos.getWithToken({ id: normalLiveId }) | |
324 | await checkReplay(replay) | |
325 | }) | |
326 | ||
327 | it('Should have created a replay of the permanent live with a private static path', async function () { | |
328 | this.timeout(240000) | |
329 | ||
330 | await server.live.waitUntilWaiting({ videoId: permanentLiveId }) | |
331 | await waitJobs([ server ]) | |
332 | ||
333 | const live = await server.videos.getWithToken({ id: permanentLiveId }) | |
334 | const replayFromList = await findExternalSavedVideo(server, live) | |
335 | const replay = await server.videos.getWithToken({ id: replayFromList.id }) | |
336 | ||
337 | await checkReplay(replay) | |
338 | }) | |
339 | }) | |
340 | ||
5a122ddd C |
341 | describe('With private files proxy disabled and public ACL for private files', function () { |
342 | let videoUUID: string | |
343 | ||
344 | before(async function () { | |
345 | this.timeout(240000) | |
346 | ||
347 | await server.kill() | |
348 | ||
349 | const config = ObjectStorageCommand.getDefaultScalewayConfig({ | |
350 | serverNumber: server.internalServerNumber, | |
351 | enablePrivateProxy: false, | |
352 | privateACL: 'public-read' | |
353 | }) | |
354 | await server.run(config) | |
355 | ||
356 | const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE }) | |
357 | videoUUID = uuid | |
358 | ||
359 | await waitJobs([ server ]) | |
360 | }) | |
361 | ||
362 | it('Should display object storage path for a private video and be able to access them', async function () { | |
363 | this.timeout(60000) | |
364 | ||
365 | await checkPublicVODFiles(videoUUID) | |
366 | }) | |
367 | ||
368 | it('Should not be able to access object storage proxy', async function () { | |
369 | const privateVideo = await server.videos.getWithToken({ id: videoUUID }) | |
370 | const webtorrentFilename = extractFilenameFromUrl(privateVideo.files[0].fileUrl) | |
371 | const hlsFilename = extractFilenameFromUrl(getHLS(privateVideo).files[0].fileUrl) | |
372 | ||
373 | await makeRawRequest({ | |
374 | url: server.url + '/object-storage-proxy/webseed/private/' + webtorrentFilename, | |
375 | token: server.accessToken, | |
376 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
377 | }) | |
378 | ||
379 | await makeRawRequest({ | |
380 | url: server.url + '/object-storage-proxy/streaming-playlists/hls/private/' + videoUUID + '/' + hlsFilename, | |
381 | token: server.accessToken, | |
382 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | |
383 | }) | |
384 | }) | |
385 | }) | |
386 | ||
9ab330b9 | 387 | after(async function () { |
1ebe2c2b | 388 | this.timeout(240000) |
508c1b1e C |
389 | |
390 | const { data } = await server.videos.listAllForAdmin() | |
391 | ||
392 | for (const v of data) { | |
393 | await server.videos.remove({ id: v.uuid }) | |
394 | } | |
395 | ||
396 | for (const v of data) { | |
397 | await server.servers.waitUntilLog('Removed files of video ' + v.url, 1, true) | |
398 | } | |
399 | ||
9ab330b9 C |
400 | await cleanupTests([ server ]) |
401 | }) | |
402 | }) |