1
0
Fork 0

Merge branch 'release/4.0.0' into develop

This commit is contained in:
Chocobozzz 2022-01-06 13:31:37 +01:00
commit c3edc5b074
No known key found for this signature in database
GPG key ID: 583A612D890159BE
8 changed files with 132 additions and 22 deletions

View file

@ -49,7 +49,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function listVideoCaptions (req: express.Request, res: express.Response) { async function listVideoCaptions (req: express.Request, res: express.Response) {
const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id) const data = await VideoCaptionModel.listVideoCaptions(res.locals.onlyVideo.id)
return res.json(getFormattedObjects(data, data.length)) return res.json(getFormattedObjects(data, data.length))
} }

View file

@ -10,13 +10,15 @@ import {
ensureUserHasRight, ensureUserHasRight,
videoFileMetadataGetValidator, videoFileMetadataGetValidator,
videoFilesDeleteHLSValidator, videoFilesDeleteHLSValidator,
videoFilesDeleteWebTorrentValidator videoFilesDeleteWebTorrentValidator,
videosGetValidator
} from '../../../middlewares' } from '../../../middlewares'
const lTags = loggerTagsFactory('api', 'video') const lTags = loggerTagsFactory('api', 'video')
const filesRouter = express.Router() const filesRouter = express.Router()
filesRouter.get('/:id/metadata/:videoFileId', filesRouter.get('/:id/metadata/:videoFileId',
asyncMiddleware(videosGetValidator),
asyncMiddleware(videoFileMetadataGetValidator), asyncMiddleware(videoFileMetadataGetValidator),
asyncMiddleware(getVideoFileMetadata) asyncMiddleware(getVideoFileMetadata)
) )

View file

@ -1,16 +1,20 @@
import { Response } from 'express' import { Request, Response } from 'express'
import { loadVideo, VideoLoadType } from '@server/lib/model-loaders' import { loadVideo, VideoLoadType } from '@server/lib/model-loaders'
import { authenticatePromiseIfNeeded } from '@server/middlewares/auth'
import { VideoModel } from '@server/models/video/video'
import { VideoChannelModel } from '@server/models/video/video-channel' import { VideoChannelModel } from '@server/models/video/video-channel'
import { VideoFileModel } from '@server/models/video/video-file' import { VideoFileModel } from '@server/models/video/video-file'
import { import {
MUser, MUser,
MUserAccountId, MUserAccountId,
MVideo,
MVideoAccountLight, MVideoAccountLight,
MVideoFormattableDetails, MVideoFormattableDetails,
MVideoFullLight, MVideoFullLight,
MVideoId, MVideoId,
MVideoImmutable, MVideoImmutable,
MVideoThumbnail MVideoThumbnail,
MVideoWithRights
} from '@server/types/models' } from '@server/types/models'
import { HttpStatusCode, UserRight } from '@shared/models' import { HttpStatusCode, UserRight } from '@shared/models'
@ -89,6 +93,27 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAcc
return true return true
} }
async function checkCanSeeVideoIfPrivate (req: Request, res: Response, video: MVideo, authenticateInQuery = false) {
if (!video.requiresAuth()) return true
const videoWithRights = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)
return checkCanSeePrivateVideo(req, res, videoWithRights, authenticateInQuery)
}
async function checkCanSeePrivateVideo (req: Request, res: Response, video: MVideoWithRights, authenticateInQuery = false) {
await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
const user = res.locals.oauth ? res.locals.oauth.token.User : null
// Only the owner or a user that have blocklist rights can see the video
if (!user || !user.canGetVideo(video)) {
return false
}
return true
}
function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) { function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response, onlyOwned = true) {
// Retrieve the user who did the request // Retrieve the user who did the request
if (onlyOwned && video.isOwned() === false) { if (onlyOwned && video.isOwned() === false) {
@ -120,5 +145,7 @@ export {
doesVideoChannelOfAccountExist, doesVideoChannelOfAccountExist,
doesVideoExist, doesVideoExist,
doesVideoFileOfVideoExist, doesVideoFileOfVideoExist,
checkUserCanManageVideo checkUserCanManageVideo,
checkCanSeeVideoIfPrivate,
checkCanSeePrivateVideo
} }

View file

@ -1,11 +1,18 @@
import express from 'express' import express from 'express'
import { body, param } from 'express-validator' import { body, param } from 'express-validator'
import { UserRight } from '@shared/models' import { HttpStatusCode, UserRight } from '@shared/models'
import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions'
import { cleanUpReqFiles } from '../../../helpers/express-utils' import { cleanUpReqFiles } from '../../../helpers/express-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants' import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants'
import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared' import {
areValidationErrors,
checkCanSeeVideoIfPrivate,
checkUserCanManageVideo,
doesVideoCaptionExist,
doesVideoExist,
isValidVideoIdParam
} from '../shared'
const addVideoCaptionValidator = [ const addVideoCaptionValidator = [
isValidVideoIdParam('videoId'), isValidVideoIdParam('videoId'),
@ -64,7 +71,16 @@ const listVideoCaptionsValidator = [
logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) logger.debug('Checking listVideoCaptions parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'id')) return if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
const video = res.locals.onlyVideo
if (!await checkCanSeeVideoIfPrivate(req, res, video)) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Cannot list captions of private/internal/blocklisted video'
})
}
return next() return next()
} }

View file

@ -13,6 +13,7 @@ import { CONFIG } from '../../../initializers/config'
import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared'
import { getCommonVideoEditAttributes } from './videos' import { getCommonVideoEditAttributes } from './videos'
import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js'
const videoImportAddValidator = getCommonVideoEditAttributes().concat([ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
body('channelId') body('channelId')
@ -71,6 +72,23 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' }) return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' })
} }
if (req.body.targetUrl) {
const hostname = new URL(req.body.targetUrl).hostname
if (isIPValid(hostname)) {
const parsed = parseIP(hostname)
if (parsed.range() !== 'unicast') {
cleanUpReqFiles(req)
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Cannot use non unicast IP as targetUrl.'
})
}
}
}
if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req)
return next() return next()

View file

@ -49,9 +49,9 @@ import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
import { isLocalVideoAccepted } from '../../../lib/moderation' import { isLocalVideoAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks' import { Hooks } from '../../../lib/plugins/hooks'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { authenticatePromiseIfNeeded } from '../../auth'
import { import {
areValidationErrors, areValidationErrors,
checkCanSeePrivateVideo,
checkUserCanManageVideo, checkUserCanManageVideo,
doesVideoChannelOfAccountExist, doesVideoChannelOfAccountExist,
doesVideoExist, doesVideoExist,
@ -315,19 +315,12 @@ const videosCustomGetValidator = (
// Video private or blacklisted // Video private or blacklisted
if (video.requiresAuth()) { if (video.requiresAuth()) {
await authenticatePromiseIfNeeded(req, res, authenticateInQuery) if (await checkCanSeePrivateVideo(req, res, video, authenticateInQuery)) return next()
const user = res.locals.oauth ? res.locals.oauth.token.User : null return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
// Only the owner or a user that have blocklist rights can see the video message: 'Cannot get this private/internal or blocklisted video'
if (!user || !user.canGetVideo(video)) { })
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Cannot get this private/internal or blocklisted video'
})
}
return next()
} }
// Video is public, anyone can access it // Video is public, anyone can access it

View file

@ -2,7 +2,7 @@
import 'mocha' import 'mocha'
import { buildAbsoluteFixturePath } from '@shared/core-utils' import { buildAbsoluteFixturePath } from '@shared/core-utils'
import { HttpStatusCode, VideoCreateResult } from '@shared/models' import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
import { import {
cleanupTests, cleanupTests,
createSingleServer, createSingleServer,
@ -19,6 +19,7 @@ describe('Test video captions API validator', function () {
let server: PeerTubeServer let server: PeerTubeServer
let userAccessToken: string let userAccessToken: string
let video: VideoCreateResult let video: VideoCreateResult
let privateVideo: VideoCreateResult
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -30,6 +31,7 @@ describe('Test video captions API validator', function () {
await setAccessTokensToServers([ server ]) await setAccessTokensToServers([ server ])
video = await server.videos.upload() video = await server.videos.upload()
privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
{ {
const user = { const user = {
@ -204,8 +206,32 @@ describe('Test video captions API validator', function () {
}) })
}) })
it('Should fail with a private video without token', async function () {
await makeGetRequest({
url: server.url,
path: path + privateVideo.shortUUID + '/captions',
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with another user token', async function () {
await makeGetRequest({
url: server.url,
token: userAccessToken,
path: path + privateVideo.shortUUID + '/captions',
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
})
it('Should success with the correct parameters', async function () { it('Should success with the correct parameters', async function () {
await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 }) await makeGetRequest({ url: server.url, path: path + video.shortUUID + '/captions', expectedStatus: HttpStatusCode.OK_200 })
await makeGetRequest({
url: server.url,
path: path + privateVideo.shortUUID + '/captions',
token: server.accessToken,
expectedStatus: HttpStatusCode.OK_200
})
}) })
}) })

View file

@ -105,6 +105,34 @@ describe('Test video imports API validator', function () {
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
}) })
it('Should fail with localhost', async function () {
const fields = { ...baseCorrectParams, targetUrl: 'http://localhost:8000' }
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a private IP target urls', async function () {
const targetUrls = [
'http://127.0.0.1:8000',
'http://127.0.0.1',
'http://127.0.0.1/hello',
'https://192.168.1.42',
'http://192.168.1.42'
]
for (const targetUrl of targetUrls) {
const fields = { ...baseCorrectParams, targetUrl }
await makePostBodyRequest({
url: server.url,
path,
token: server.accessToken,
fields,
expectedStatus: HttpStatusCode.FORBIDDEN_403
})
}
})
it('Should fail with a long name', async function () { it('Should fail with a long name', async function () {
const fields = { ...baseCorrectParams, name: 'super'.repeat(65) } const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }