aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/captions.ts2
-rw-r--r--server/controllers/api/videos/files.ts4
-rw-r--r--server/lib/live/shared/muxing-session.ts2
-rw-r--r--server/middlewares/validators/shared/videos.ts33
-rw-r--r--server/middlewares/validators/videos/video-captions.ts22
-rw-r--r--server/middlewares/validators/videos/video-imports.ts18
-rw-r--r--server/middlewares/validators/videos/videos.ts19
-rw-r--r--server/tests/api/check-params/video-captions.ts28
-rw-r--r--server/tests/api/check-params/video-imports.ts28
9 files changed, 133 insertions, 23 deletions
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index c0e60848b..2a9a9d233 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -49,7 +49,7 @@ export {
49// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
50 50
51async function listVideoCaptions (req: express.Request, res: express.Response) { 51async function listVideoCaptions (req: express.Request, res: express.Response) {
52 const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id) 52 const data = await VideoCaptionModel.listVideoCaptions(res.locals.onlyVideo.id)
53 53
54 return res.json(getFormattedObjects(data, data.length)) 54 return res.json(getFormattedObjects(data, data.length))
55} 55}
diff --git a/server/controllers/api/videos/files.ts b/server/controllers/api/videos/files.ts
index a8b32411d..0fbda280e 100644
--- a/server/controllers/api/videos/files.ts
+++ b/server/controllers/api/videos/files.ts
@@ -10,13 +10,15 @@ import {
10 ensureUserHasRight, 10 ensureUserHasRight,
11 videoFileMetadataGetValidator, 11 videoFileMetadataGetValidator,
12 videoFilesDeleteHLSValidator, 12 videoFilesDeleteHLSValidator,
13 videoFilesDeleteWebTorrentValidator 13 videoFilesDeleteWebTorrentValidator,
14 videosGetValidator
14} from '../../../middlewares' 15} from '../../../middlewares'
15 16
16const lTags = loggerTagsFactory('api', 'video') 17const lTags = loggerTagsFactory('api', 'video')
17const filesRouter = express.Router() 18const filesRouter = express.Router()
18 19
19filesRouter.get('/:id/metadata/:videoFileId', 20filesRouter.get('/:id/metadata/:videoFileId',
21 asyncMiddleware(videosGetValidator),
20 asyncMiddleware(videoFileMetadataGetValidator), 22 asyncMiddleware(videoFileMetadataGetValidator),
21 asyncMiddleware(getVideoFileMetadata) 23 asyncMiddleware(getVideoFileMetadata)
22) 24)
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts
index eccaefcfa..22a47942a 100644
--- a/server/lib/live/shared/muxing-session.ts
+++ b/server/lib/live/shared/muxing-session.ts
@@ -229,7 +229,7 @@ class MuxingSession extends EventEmitter {
229 229
230 const playlistIdMatcher = /^([\d+])-/ 230 const playlistIdMatcher = /^([\d+])-/
231 231
232 const addHandler = async segmentPath => { 232 const addHandler = async (segmentPath: string) => {
233 logger.debug('Live add handler of %s.', segmentPath, this.lTags()) 233 logger.debug('Live add handler of %s.', segmentPath, this.lTags())
234 234
235 const playlistId = basename(segmentPath).match(playlistIdMatcher)[0] 235 const playlistId = basename(segmentPath).match(playlistIdMatcher)[0]
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts
index 71b81654f..fc978b63a 100644
--- a/server/middlewares/validators/shared/videos.ts
+++ b/server/middlewares/validators/shared/videos.ts
@@ -1,16 +1,20 @@
1import { Response } from 'express' 1import { Request, Response } from 'express'
2import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' 2import { loadVideo, VideoLoadType } from '@server/lib/model-loaders'
3import { authenticatePromiseIfNeeded } from '@server/middlewares/auth'
4import { VideoModel } from '@server/models/video/video'
3import { VideoChannelModel } from '@server/models/video/video-channel' 5import { VideoChannelModel } from '@server/models/video/video-channel'
4import { VideoFileModel } from '@server/models/video/video-file' 6import { VideoFileModel } from '@server/models/video/video-file'
5import { 7import {
6 MUser, 8 MUser,
7 MUserAccountId, 9 MUserAccountId,
10 MVideo,
8 MVideoAccountLight, 11 MVideoAccountLight,
9 MVideoFormattableDetails, 12 MVideoFormattableDetails,
10 MVideoFullLight, 13 MVideoFullLight,
11 MVideoId, 14 MVideoId,
12 MVideoImmutable, 15 MVideoImmutable,
13 MVideoThumbnail 16 MVideoThumbnail,
17 MVideoWithRights
14} from '@server/types/models' 18} from '@server/types/models'
15import { HttpStatusCode, UserRight } from '@shared/models' 19import { HttpStatusCode, UserRight } from '@shared/models'
16 20
@@ -89,6 +93,27 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
89 return true 93 return true
90} 94}
91 95
96async function checkCanSeeVideoIfPrivate (req: Request, res: Response, video: MVideo, authenticateInQuery = false) {
97 if (!video.requiresAuth()) return true
98
99 const videoWithRights = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)
100
101 return checkCanSeePrivateVideo(req, res, videoWithRights, authenticateInQuery)
102}
103
104async function checkCanSeePrivateVideo (req: Request, res: Response, video: MVideoWithRights, authenticateInQuery = false) {
105 await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
106
107 const user = res.locals.oauth ? res.locals.oauth.token.User : null
108
109 // Only the owner or a user that have blocklist rights can see the video
110 if (!user || !user.canGetVideo(video)) {
111 return false
112 }
113
114 return true
115}
116
92function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { 117function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
93 // Retrieve the user who did the request 118 // Retrieve the user who did the request
94 if (onlyOwned && video.isOwned() === false) { 119 if (onlyOwned && video.isOwned() === false) {
@@ -120,5 +145,7 @@ export {
120 doesVideoChannelOfAccountExist, 145 doesVideoChannelOfAccountExist,
121 doesVideoExist, 146 doesVideoExist,
122 doesVideoFileOfVideoExist, 147 doesVideoFileOfVideoExist,
123 checkUserCanManageVideo 148 checkUserCanManageVideo,
149 checkCanSeeVideoIfPrivate,
150 checkCanSeePrivateVideo
124} 151}
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 47c491b4c..a399871e1 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -1,11 +1,18 @@
1import express from 'express' 1import express from 'express'
2import { body, param } from 'express-validator' 2import { body, param } from 'express-validator'
3import { UserRight } from '@shared/models' 3import { HttpStatusCode, UserRight } from '@shared/models'
4import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' 4import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions'
5import { cleanUpReqFiles } from '../../../helpers/express-utils' 5import { cleanUpReqFiles } from '../../../helpers/express-utils'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' 7import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants'
8import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared' 8import {
9 areValidationErrors,
10 checkCanSeeVideoIfPrivate,
11 checkUserCanManageVideo,
12 doesVideoCaptionExist,
13 doesVideoExist,
14 isValidVideoIdParam
15} from '../shared'
9 16
10const addVideoCaptionValidator = [ 17const addVideoCaptionValidator = [
11 isValidVideoIdParam('videoId'), 18 isValidVideoIdParam('videoId'),
@@ -64,7 +71,16 @@ const listVideoCaptionsValidator = [
64 logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) 71 logger.debug('Checking listVideoCaptions parameters', { parameters: req.params })
65 72
66 if (areValidationErrors(req, res)) return 73 if (areValidationErrors(req, res)) return
67 if (!await doesVideoExist(req.params.videoId, res, 'id')) return 74 if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
75
76 const video = res.locals.onlyVideo
77
78 if (!await checkCanSeeVideoIfPrivate(req, res, video)) {
79 return res.fail({
80 status: HttpStatusCode.FORBIDDEN_403,
81 message: 'Cannot list captions of private/internal/blocklisted video'
82 })
83 }
68 84
69 return next() 85 return next()
70 } 86 }
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 640139c73..e4b54283f 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -13,6 +13,7 @@ import { CONFIG } from '../../../initializers/config'
13import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' 13import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
14import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' 14import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared'
15import { getCommonVideoEditAttributes } from './videos' 15import { getCommonVideoEditAttributes } from './videos'
16import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js'
16 17
17const videoImportAddValidator = getCommonVideoEditAttributes().concat([ 18const videoImportAddValidator = getCommonVideoEditAttributes().concat([
18 body('channelId') 19 body('channelId')
@@ -71,6 +72,23 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
71 return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' }) 72 return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' })
72 } 73 }
73 74
75 if (req.body.targetUrl) {
76 const hostname = new URL(req.body.targetUrl).hostname
77
78 if (isIPValid(hostname)) {
79 const parsed = parseIP(hostname)
80
81 if (parsed.range() !== 'unicast') {
82 cleanUpReqFiles(req)
83
84 return res.fail({
85 status: HttpStatusCode.FORBIDDEN_403,
86 message: 'Cannot use non unicast IP as targetUrl.'
87 })
88 }
89 }
90 }
91
74 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) 92 if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req)
75 93
76 return next() 94 return next()
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index bf5f1c97b..3a1a905f3 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -49,9 +49,9 @@ import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
49import { isLocalVideoAccepted } from '../../../lib/moderation' 49import { isLocalVideoAccepted } from '../../../lib/moderation'
50import { Hooks } from '../../../lib/plugins/hooks' 50import { Hooks } from '../../../lib/plugins/hooks'
51import { VideoModel } from '../../../models/video/video' 51import { VideoModel } from '../../../models/video/video'
52import { authenticatePromiseIfNeeded } from '../../auth'
53import { 52import {
54 areValidationErrors, 53 areValidationErrors,
54 checkCanSeePrivateVideo,
55 checkUserCanManageVideo, 55 checkUserCanManageVideo,
56 doesVideoChannelOfAccountExist, 56 doesVideoChannelOfAccountExist,
57 doesVideoExist, 57 doesVideoExist,
@@ -315,19 +315,12 @@ const videosCustomGetValidator = (
315 315
316 // Video private or blacklisted 316 // Video private or blacklisted
317 if (video.requiresAuth()) { 317 if (video.requiresAuth()) {
318 await authenticatePromiseIfNeeded(req, res, authenticateInQuery) 318 if (await checkCanSeePrivateVideo(req, res, video, authenticateInQuery)) return next()
319 319
320 const user = res.locals.oauth ? res.locals.oauth.token.User : null 320 return res.fail({
321 321 status: HttpStatusCode.FORBIDDEN_403,
322 // Only the owner or a user that have blocklist rights can see the video 322 message: 'Cannot get this private/internal or blocklisted video'
323 if (!user || !user.canGetVideo(video)) { 323 })
324 return res.fail({
325 status: HttpStatusCode.FORBIDDEN_403,
326 message: 'Cannot get this private/internal or blocklisted video'
327 })
328 }
329
330 return next()
331 } 324 }
332 325
333 // Video is public, anyone can access it 326 // Video is public, anyone can access it
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts
index 8a8840793..9881df80c 100644
--- a/server/tests/api/check-params/video-captions.ts
+++ b/server/tests/api/check-params/video-captions.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import { buildAbsoluteFixturePath } from '@shared/core-utils' 4import { buildAbsoluteFixturePath } from '@shared/core-utils'
5import { HttpStatusCode, VideoCreateResult } from '@shared/models' 5import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createSingleServer, 8 createSingleServer,
@@ -19,6 +19,7 @@ describe('Test video captions API validator', function () {
19 let server: PeerTubeServer 19 let server: PeerTubeServer
20 let userAccessToken: string 20 let userAccessToken: string
21 let video: VideoCreateResult 21 let video: VideoCreateResult
22 let privateVideo: VideoCreateResult
22 23
23 // --------------------------------------------------------------- 24 // ---------------------------------------------------------------
24 25
@@ -30,6 +31,7 @@ describe('Test video captions API validator', function () {
30 await setAccessTokensToServers([ server ]) 31 await setAccessTokensToServers([ server ])
31 32
32 video = await server.videos.upload() 33 video = await server.videos.upload()
34 privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
33 35
34 { 36 {
35 const user = { 37 const user = {
@@ -204,8 +206,32 @@ describe('Test video captions API validator', function () {
204 }) 206 })
205 }) 207 })
206 208
209 it('Should fail with a private video without token', async function () {
210 await makeGetRequest({
211 url: server.url,
212 path: path + privateVideo.shortUUID + '/captions',
213 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
214 })
215 })
216
217 it('Should fail with another user token', async function () {
218 await makeGetRequest({
219 url: server.url,
220 token: userAccessToken,
221 path: path + privateVideo.shortUUID + '/captions',
222 expectedStatus: HttpStatusCode.FORBIDDEN_403
223 })
224 })
225
207 it('Should success with the correct parameters', async function () { 226 it('Should success with the correct parameters', async function () {
208 await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 }) 227 await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 })
228
229 await makeGetRequest({
230 url: server.url,
231 path: path + privateVideo.shortUUID + '/captions',
232 token: server.accessToken,
233 expectedStatus: HttpStatusCode.OK_200
234 })
209 }) 235 })
210 }) 236 })
211 237
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts
index ddea68db4..da05793a0 100644
--- a/server/tests/api/check-params/video-imports.ts
+++ b/server/tests/api/check-params/video-imports.ts
@@ -105,6 +105,34 @@ describe('Test video imports API validator', function () {
105 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 105 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
106 }) 106 })
107 107
108 it('Should fail with localhost', async function () {
109 const fields = { ...baseCorrectParams, targetUrl: 'http://localhost:8000' }
110
111 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
112 })
113
114 it('Should fail with a private IP target urls', async function () {
115 const targetUrls = [
116 'http://127.0.0.1:8000',
117 'http://127.0.0.1',
118 'http://127.0.0.1/hello',
119 'https://192.168.1.42',
120 'http://192.168.1.42'
121 ]
122
123 for (const targetUrl of targetUrls) {
124 const fields = { ...baseCorrectParams, targetUrl }
125
126 await makePostBodyRequest({
127 url: server.url,
128 path,
129 token: server.accessToken,
130 fields,
131 expectedStatus: HttpStatusCode.FORBIDDEN_403
132 })
133 }
134 })
135
108 it('Should fail with a long name', async function () { 136 it('Should fail with a long name', async function () {
109 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) } 137 const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }
110 138