Protect all video related AP endpoints

This commit is contained in:
Chocobozzz 2024-04-26 15:20:47 +02:00
parent d72ef2a2b9
commit 1642c5b9e7
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
7 changed files with 37 additions and 32 deletions

View File

@ -16,6 +16,7 @@ import { isHostValid } from '../../helpers/custom-validators/servers.js'
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js' import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
import { ServerModel } from '../../models/server/server.js' import { ServerModel } from '../../models/server/server.js'
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared/index.js' import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared/index.js'
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
const videoFileRedundancyGetValidator = [ const videoFileRedundancyGetValidator = [
isValidVideoIdParam('videoId'), isValidVideoIdParam('videoId'),
@ -31,6 +32,7 @@ const videoFileRedundancyGetValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return if (!await doesVideoExist(req.params.videoId, res)) return
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
const video = res.locals.videoAll const video = res.locals.videoAll
@ -72,6 +74,7 @@ const videoPlaylistRedundancyGetValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return if (!await doesVideoExist(req.params.videoId, res)) return
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
const video = res.locals.videoAll const video = res.locals.videoAll

View File

@ -18,7 +18,7 @@ import {
MVideoFullLight, MVideoFullLight,
MVideoId, MVideoId,
MVideoImmutable, MVideoImmutable,
MVideoThumbnail, MVideoThumbnailBlacklist,
MVideoUUID, MVideoUUID,
MVideoWithRights MVideoWithRights
} from '@server/types/models/index.js' } from '@server/types/models/index.js'
@ -56,7 +56,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
break break
case 'only-video-and-blacklist': case 'only-video-and-blacklist':
res.locals.onlyVideo = video as MVideoThumbnail res.locals.onlyVideo = video as MVideoThumbnailBlacklist
break break
} }

View File

@ -1,19 +1,19 @@
import { HttpStatusCode } from '@peertube/peertube-models'
import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
import { logger } from '@server/helpers/logger.js'
import { LRU_CACHE } from '@server/initializers/constants.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoModel } from '@server/models/video/video.js'
import { MStreamingPlaylist, MVideoFile, MVideoThumbnailBlacklist } from '@server/types/models/index.js'
import express from 'express' import express from 'express'
import { query } from 'express-validator' import { query } from 'express-validator'
import { LRUCache } from 'lru-cache' import { LRUCache } from 'lru-cache'
import { basename, dirname } from 'path' import { basename, dirname } from 'path'
import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
import { logger } from '@server/helpers/logger.js'
import { LRU_CACHE } from '@server/initializers/constants.js'
import { VideoModel } from '@server/models/video/video.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { MStreamingPlaylist, MVideoFile, MVideoThumbnail } from '@server/types/models/index.js'
import { HttpStatusCode } from '@peertube/peertube-models'
import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared/index.js' import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared/index.js'
type LRUValue = { type LRUValue = {
allowed: boolean allowed: boolean
video?: MVideoThumbnail video?: MVideoThumbnailBlacklist
file?: MVideoFile file?: MVideoFile
playlist?: MStreamingPlaylist } playlist?: MStreamingPlaylist }
@ -122,8 +122,7 @@ const ensureCanAccessPrivateVideoHLSFiles = [
] ]
export { export {
ensureCanAccessVideoPrivateWebVideoFiles, ensureCanAccessPrivateVideoHLSFiles, ensureCanAccessVideoPrivateWebVideoFiles
ensureCanAccessPrivateVideoHLSFiles
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -139,7 +138,7 @@ async function isWebVideoAllowed (req: express.Request, res: express.Response) {
return { allowed: false } return { allowed: false }
} }
const video = await VideoModel.load(file.getVideo().id) const video = await VideoModel.loadWithBlacklist(file.getVideo().id)
return { return {
file, file,
@ -151,7 +150,7 @@ async function isWebVideoAllowed (req: express.Request, res: express.Response) {
async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) { async function isHLSAllowed (req: express.Request, res: express.Response, videoUUID: string) {
const filename = basename(req.path) const filename = basename(req.path)
const video = await VideoModel.loadWithFiles(videoUUID) const video = await VideoModel.loadAndPopulateAccountAndFiles(videoUUID)
if (!video) { if (!video) {
logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID }) logger.debug('Unknown static file %s to serve', req.originalUrl, { videoUUID })

View File

@ -17,6 +17,7 @@ import {
isValidVideoIdParam, isValidVideoIdParam,
isValidVideoPasswordHeader isValidVideoPasswordHeader
} from '../shared/index.js' } from '../shared/index.js'
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
const listVideoCommentsValidator = [ const listVideoCommentsValidator = [
query('isLocal') query('isLocal')
@ -132,8 +133,11 @@ const videoCommentGetValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
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-and-blacklist')) return
if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
if (!await doesVideoCommentExist(req.params.commentId, res.locals.onlyVideo, res)) return
return next() return next()
} }

View File

@ -1,11 +1,12 @@
import { HttpStatusCode } from '@peertube/peertube-models'
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
import express from 'express' import express from 'express'
import { param } from 'express-validator' import { param } from 'express-validator'
import { HttpStatusCode } from '@peertube/peertube-models'
import { isIdValid } from '../../../helpers/custom-validators/misc.js' import { isIdValid } from '../../../helpers/custom-validators/misc.js'
import { VideoShareModel } from '../../../models/video/video-share.js' import { VideoShareModel } from '../../../models/video/video-share.js'
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared/index.js' import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared/index.js'
const videosShareValidator = [ export const videosShareValidator = [
isValidVideoIdParam('id'), isValidVideoIdParam('id'),
param('actorId') param('actorId')
@ -16,20 +17,12 @@ const videosShareValidator = [
if (!await doesVideoExist(req.params.id, res)) return if (!await doesVideoExist(req.params.id, res)) return
const video = res.locals.videoAll const video = res.locals.videoAll
if (!canVideoBeFederated(video)) res.sendStatus(HttpStatusCode.NOT_FOUND_404)
const share = await VideoShareModel.load(req.params.actorId, video.id) const share = await VideoShareModel.load(req.params.actorId, video.id)
if (!share) { if (!share) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
return res.status(HttpStatusCode.NOT_FOUND_404)
.end()
}
res.locals.videoShare = share res.locals.videoShare = share
return next() return next()
} }
] ]
// ---------------------------------------------------------------------------
export {
videosShareValidator
}

View File

@ -1392,6 +1392,12 @@ export class VideoModel extends SequelizeModel<VideoModel> {
return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' }) return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
} }
static loadAndPopulateAccountAndFiles (id: number | string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
return queryBuilder.queryVideo({ id, transaction, type: 'account-blacklist-files' })
}
static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> { static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> {
const fun = () => { const fun = () => {
const query = { const query = {

View File

@ -20,7 +20,8 @@ import {
MVideoLiveFormattable, MVideoLiveFormattable,
MVideoPassword, MVideoPassword,
MVideoPlaylistFull, MVideoPlaylistFull,
MVideoPlaylistFullSummary MVideoPlaylistFullSummary,
MVideoThumbnailBlacklist
} from '@server/types/models/index.js' } from '@server/types/models/index.js'
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token.js' import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token.js'
import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server.js' import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server.js'
@ -44,8 +45,7 @@ import {
MVideoCaptionVideo, MVideoCaptionVideo,
MVideoFullLight, MVideoFullLight,
MVideoRedundancyVideo, MVideoRedundancyVideo,
MVideoShareActor, MVideoShareActor
MVideoThumbnail
} from './models/index.js' } from './models/index.js'
import { MRunner, MRunnerJobRunner, MRunnerRegistrationToken } from './models/runners/index.js' import { MRunner, MRunnerJobRunner, MRunnerRegistrationToken } from './models/runners/index.js'
import { MVideoSource } from './models/video/video-source.js' import { MVideoSource } from './models/video/video-source.js'
@ -135,7 +135,7 @@ declare module 'express' {
videoAPI?: MVideoFormattableDetails videoAPI?: MVideoFormattableDetails
videoAll?: MVideoFullLight videoAll?: MVideoFullLight
onlyImmutableVideo?: MVideoImmutable onlyImmutableVideo?: MVideoImmutable
onlyVideo?: MVideoThumbnail onlyVideo?: MVideoThumbnailBlacklist
videoId?: MVideoId videoId?: MVideoId
videoLive?: MVideoLiveFormattable videoLive?: MVideoLiveFormattable