1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { basename } from 'path'
5 import { checkVideoFileTokenReinjection, expectStartWith } from '@server/tests/shared'
6 import { areScalewayObjectStorageTestsDisabled, getAllFiles, getHLS } from '@shared/core-utils'
7 import { HttpStatusCode, LiveVideo, VideoDetails, VideoPrivacy } from '@shared/models'
11 findExternalSavedVideo,
16 setAccessTokensToServers,
17 setDefaultVideoChannel,
20 } from '@shared/server-commands'
22 function extractFilenameFromUrl (url: string) {
23 const parts = basename(url).split(':')
25 return parts[parts.length - 1]
28 describe('Object storage for video static file privacy', function () {
29 // We need real world object storage to check ACL
30 if (areScalewayObjectStorageTestsDisabled()) return
32 let server: PeerTubeServer
35 // ---------------------------------------------------------------------------
37 async function checkPrivateVODFiles (uuid: string) {
38 const video = await server.videos.getWithToken({ id: uuid })
40 for (const file of video.files) {
41 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/webseed/private/')
43 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
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 })
52 const hls = getHLS(video)
55 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
56 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
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 })
62 for (const file of hls.files) {
63 expectStartWith(file.fileUrl, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
65 await makeRawRequest({ url: file.fileUrl, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
70 async function checkPublicVODFiles (uuid: string) {
71 const video = await server.videos.getWithToken({ id: uuid })
73 for (const file of getAllFiles(video)) {
74 expectStartWith(file.fileUrl, ObjectStorageCommand.getScalewayBaseUrl())
76 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
79 const hls = getHLS(video)
82 expectStartWith(hls.playlistUrl, ObjectStorageCommand.getScalewayBaseUrl())
83 expectStartWith(hls.segmentsSha256Url, ObjectStorageCommand.getScalewayBaseUrl())
85 await makeRawRequest({ url: hls.playlistUrl, expectedStatus: HttpStatusCode.OK_200 })
86 await makeRawRequest({ url: hls.segmentsSha256Url, expectedStatus: HttpStatusCode.OK_200 })
90 // ---------------------------------------------------------------------------
92 before(async function () {
95 server = await createSingleServer(1, ObjectStorageCommand.getDefaultScalewayConfig({ serverNumber: 1 }))
96 await setAccessTokensToServers([ server ])
97 await setDefaultVideoChannel([ server ])
99 await server.config.enableMinimumTranscoding()
101 userToken = await server.users.generateUserAndToken('user1')
104 describe('VOD', function () {
105 let privateVideoUUID: string
106 let publicVideoUUID: string
107 let userPrivateVideoUUID: string
109 // ---------------------------------------------------------------------------
111 async function getSampleFileUrls (videoId: string) {
112 const video = await server.videos.getWithToken({ id: videoId })
115 webTorrentFile: video.files[0].fileUrl,
116 hlsFile: getHLS(video).files[0].fileUrl
120 // ---------------------------------------------------------------------------
122 it('Should upload a private video and have appropriate object storage ACL', async function () {
126 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
127 privateVideoUUID = uuid
131 const { uuid } = await server.videos.quickUpload({ name: 'user video', token: userToken, privacy: VideoPrivacy.PRIVATE })
132 userPrivateVideoUUID = uuid
135 await waitJobs([ server ])
137 await checkPrivateVODFiles(privateVideoUUID)
140 it('Should upload a public video and have appropriate object storage ACL', async function () {
143 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.UNLISTED })
144 await waitJobs([ server ])
146 publicVideoUUID = uuid
148 await checkPublicVODFiles(publicVideoUUID)
151 it('Should not get files without appropriate OAuth token', async function () {
154 const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
156 await makeRawRequest({ url: webTorrentFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
157 await makeRawRequest({ url: webTorrentFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
159 await makeRawRequest({ url: hlsFile, token: userToken, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
160 await makeRawRequest({ url: hlsFile, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
163 it('Should not get HLS file of another video', async function () {
166 const privateVideo = await server.videos.getWithToken({ id: privateVideoUUID })
167 const hlsFilename = basename(getHLS(privateVideo).files[0].fileUrl)
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
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 })
176 it('Should correctly check OAuth or video file token', async function () {
179 const badVideoFileToken = await server.videoToken.getVideoFileToken({ token: userToken, videoId: userPrivateVideoUUID })
180 const goodVideoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
182 const { webTorrentFile, hlsFile } = await getSampleFileUrls(privateVideoUUID)
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 })
189 await makeRawRequest({ url, query: { videoFileToken: badVideoFileToken }, expectedStatus: HttpStatusCode.FORBIDDEN_403 })
190 await makeRawRequest({ url, query: { videoFileToken: goodVideoFileToken }, expectedStatus: HttpStatusCode.OK_200 })
194 it('Should reinject video file token', async function () {
197 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: privateVideoUUID })
199 await checkVideoFileTokenReinjection({
201 videoUUID: privateVideoUUID,
203 resolutions: [ 240, 720 ],
208 it('Should update public video to private', async function () {
211 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.INTERNAL } })
213 await checkPrivateVODFiles(publicVideoUUID)
216 it('Should update private video to public', async function () {
219 await server.videos.update({ id: publicVideoUUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
221 await checkPublicVODFiles(publicVideoUUID)
225 describe('Live', function () {
226 let normalLiveId: string
227 let normalLive: LiveVideo
229 let permanentLiveId: string
230 let permanentLive: LiveVideo
232 let unrelatedFileToken: string
234 // ---------------------------------------------------------------------------
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 })
240 const video = await server.videos.getWithToken({ id: liveId })
241 const fileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
243 const hls = video.streamingPlaylists[0]
245 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
246 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
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 })
251 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
252 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
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 })
259 await stopFfmpeg(ffmpegCommand)
262 async function checkReplay (replay: VideoDetails) {
263 const fileToken = await server.videoToken.getVideoFileToken({ videoId: replay.uuid })
265 const hls = replay.streamingPlaylists[0]
266 expect(hls.files).to.not.have.lengthOf(0)
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 })
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({
276 query: { videoFileToken: unrelatedFileToken },
277 expectedStatus: HttpStatusCode.FORBIDDEN_403
281 for (const url of [ hls.playlistUrl, hls.segmentsSha256Url ]) {
282 expectStartWith(url, server.url + '/object-storage-proxy/streaming-playlists/hls/private/')
284 await makeRawRequest({ url, token: server.accessToken, expectedStatus: HttpStatusCode.OK_200 })
285 await makeRawRequest({ url, query: { videoFileToken: fileToken }, expectedStatus: HttpStatusCode.OK_200 })
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 })
293 // ---------------------------------------------------------------------------
295 before(async function () {
296 await server.config.enableMinimumTranscoding()
298 const { uuid } = await server.videos.quickUpload({ name: 'another video' })
299 unrelatedFileToken = await server.videoToken.getVideoFileToken({ videoId: uuid })
301 await server.config.enableLive({
308 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: false, privacy: VideoPrivacy.PRIVATE })
309 normalLiveId = video.uuid
314 const { video, live } = await server.live.quickCreate({ saveReplay: true, permanentLive: true, privacy: VideoPrivacy.PRIVATE })
315 permanentLiveId = video.uuid
320 it('Should create a private normal live and have a private static path', async function () {
323 await checkLiveFiles(normalLive, normalLiveId)
326 it('Should create a private permanent live and have a private static path', async function () {
329 await checkLiveFiles(permanentLive, permanentLiveId)
332 it('Should reinject video file token in permanent live', async function () {
335 const ffmpegCommand = sendRTMPStream({ rtmpBaseUrl: permanentLive.rtmpUrl, streamKey: permanentLive.streamKey })
336 await server.live.waitUntilPublished({ videoId: permanentLiveId })
338 const video = await server.videos.getWithToken({ id: permanentLiveId })
339 const videoFileToken = await server.videoToken.getVideoFileToken({ videoId: video.uuid })
341 await checkVideoFileTokenReinjection({
343 videoUUID: permanentLiveId,
345 resolutions: [ 720 ],
349 await stopFfmpeg(ffmpegCommand)
352 it('Should have created a replay of the normal live with a private static path', async function () {
355 await server.live.waitUntilReplacedByReplay({ videoId: normalLiveId })
357 const replay = await server.videos.getWithToken({ id: normalLiveId })
358 await checkReplay(replay)
361 it('Should have created a replay of the permanent live with a private static path', async function () {
364 await server.live.waitUntilWaiting({ videoId: permanentLiveId })
365 await waitJobs([ server ])
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 })
371 await checkReplay(replay)
375 describe('With private files proxy disabled and public ACL for private files', function () {
376 let videoUUID: string
378 before(async function () {
383 const config = ObjectStorageCommand.getDefaultScalewayConfig({
385 enablePrivateProxy: false,
386 privateACL: 'public-read'
388 await server.run(config)
390 const { uuid } = await server.videos.quickUpload({ name: 'video', privacy: VideoPrivacy.PRIVATE })
393 await waitJobs([ server ])
396 it('Should display object storage path for a private video and be able to access them', async function () {
399 await checkPublicVODFiles(videoUUID)
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)
407 await makeRawRequest({
408 url: server.url + '/object-storage-proxy/webseed/private/' + webtorrentFilename,
409 token: server.accessToken,
410 expectedStatus: HttpStatusCode.BAD_REQUEST_400
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
421 after(async function () {
424 const { data } = await server.videos.listAllForAdmin()
426 for (const v of data) {
427 await server.videos.remove({ id: v.uuid })
430 for (const v of data) {
431 await server.servers.waitUntilLog('Removed files of video ' + v.url)
434 await cleanupTests([ server ])