diff options
author | Chocobozzz <me@florianbigard.com> | 2022-01-06 13:31:37 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-01-06 13:31:37 +0100 |
commit | c3edc5b074aa4bb1861ed0a94d3713808e87170f (patch) | |
tree | 328af78334a13d0d20ca53b0d88c13128e0f1244 | |
parent | 75b7117f078461d2507572ba9da6527894e1b734 (diff) | |
parent | 795212f7acc690c88c86d0fab8772f6564d59cb8 (diff) | |
download | PeerTube-c3edc5b074aa4bb1861ed0a94d3713808e87170f.tar.gz PeerTube-c3edc5b074aa4bb1861ed0a94d3713808e87170f.tar.zst PeerTube-c3edc5b074aa4bb1861ed0a94d3713808e87170f.zip |
Merge branch 'release/4.0.0' into develop
-rw-r--r-- | server/controllers/api/videos/captions.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/videos/files.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/shared/videos.ts | 33 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-captions.ts | 22 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-imports.ts | 18 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 19 | ||||
-rw-r--r-- | server/tests/api/check-params/video-captions.ts | 28 | ||||
-rw-r--r-- | server/tests/api/check-params/video-imports.ts | 28 |
8 files changed, 132 insertions, 22 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 | ||
51 | async function listVideoCaptions (req: express.Request, res: express.Response) { | 51 | async 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 | ||
16 | const lTags = loggerTagsFactory('api', 'video') | 17 | const lTags = loggerTagsFactory('api', 'video') |
17 | const filesRouter = express.Router() | 18 | const filesRouter = express.Router() |
18 | 19 | ||
19 | filesRouter.get('/:id/metadata/:videoFileId', | 20 | filesRouter.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/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 @@ | |||
1 | import { Response } from 'express' | 1 | import { Request, Response } from 'express' |
2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' | 2 | import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' |
3 | import { authenticatePromiseIfNeeded } from '@server/middlewares/auth' | ||
4 | import { VideoModel } from '@server/models/video/video' | ||
3 | import { VideoChannelModel } from '@server/models/video/video-channel' | 5 | import { VideoChannelModel } from '@server/models/video/video-channel' |
4 | import { VideoFileModel } from '@server/models/video/video-file' | 6 | import { VideoFileModel } from '@server/models/video/video-file' |
5 | import { | 7 | import { |
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' |
15 | import { HttpStatusCode, UserRight } from '@shared/models' | 19 | import { 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 | ||
96 | async 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 | |||
104 | async 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 | |||
92 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { | 117 | function 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 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param } from 'express-validator' |
3 | import { UserRight } from '@shared/models' | 3 | import { HttpStatusCode, UserRight } from '@shared/models' |
4 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' | 4 | import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' |
5 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 5 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' |
8 | import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared' | 8 | import { |
9 | areValidationErrors, | ||
10 | checkCanSeeVideoIfPrivate, | ||
11 | checkUserCanManageVideo, | ||
12 | doesVideoCaptionExist, | ||
13 | doesVideoExist, | ||
14 | isValidVideoIdParam | ||
15 | } from '../shared' | ||
9 | 16 | ||
10 | const addVideoCaptionValidator = [ | 17 | const 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' | |||
13 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 13 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
14 | import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' | 14 | import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' |
15 | import { getCommonVideoEditAttributes } from './videos' | 15 | import { getCommonVideoEditAttributes } from './videos' |
16 | import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js' | ||
16 | 17 | ||
17 | const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | 18 | const 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' | |||
49 | import { isLocalVideoAccepted } from '../../../lib/moderation' | 49 | import { isLocalVideoAccepted } from '../../../lib/moderation' |
50 | import { Hooks } from '../../../lib/plugins/hooks' | 50 | import { Hooks } from '../../../lib/plugins/hooks' |
51 | import { VideoModel } from '../../../models/video/video' | 51 | import { VideoModel } from '../../../models/video/video' |
52 | import { authenticatePromiseIfNeeded } from '../../auth' | ||
53 | import { | 52 | import { |
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 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { buildAbsoluteFixturePath } from '@shared/core-utils' | 4 | import { buildAbsoluteFixturePath } from '@shared/core-utils' |
5 | import { HttpStatusCode, VideoCreateResult } from '@shared/models' | 5 | import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models' |
6 | import { | 6 | import { |
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 | ||