Check video rights before providing AP information

pull/6358/head
Chocobozzz 2024-04-26 08:58:35 +02:00
parent b8635c2606
commit afb28272f5
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
31 changed files with 254 additions and 233 deletions

View File

@ -3,12 +3,12 @@
import { expect } from 'chai'
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
import { sortObjectComparator } from '@peertube/peertube-core-utils'
import { UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
import { HttpStatusCode, UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
import {
BlacklistCommand,
cleanupTests,
createMultipleServers,
doubleFollow, PeerTubeServer,
doubleFollow, makeActivityPubGetRequest, PeerTubeServer,
setAccessTokensToServers,
setDefaultChannelAvatar,
waitJobs
@ -298,6 +298,13 @@ describe('Test video blacklist', function () {
expect(video4Blacklisted.unfederated).to.be.true
})
it('Should not have AP comments/announces/likes/dislikes', async function () {
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/comments`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/announces`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/likes`, HttpStatusCode.UNAUTHORIZED_401)
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/dislikes`, HttpStatusCode.UNAUTHORIZED_401)
})
it('Should remove the video from blacklist and refederate the video', async function () {
await command.remove({ videoId: video4UUID })

View File

@ -120,7 +120,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
activityPubClientRouter.get('/videos/watch/:id/announces',
executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(videoAnnouncesController)
)
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
@ -132,19 +132,19 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
activityPubClientRouter.get('/videos/watch/:id/likes',
executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(videoLikesController)
)
activityPubClientRouter.get('/videos/watch/:id/dislikes',
executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(videoDislikesController)
)
activityPubClientRouter.get('/videos/watch/:id/comments',
executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(videoCommentsController)
)
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
@ -175,7 +175,7 @@ activityPubClientRouter.get('/videos/watch/:id/chapters',
activityPubRateLimiter,
apVideoChaptersSetCacheKey,
chaptersCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(videosCustomGetValidator('only-video')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(videoChaptersController)
)
@ -330,7 +330,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
}
async function videoAnnouncesController (req: express.Request, res: express.Response) {
const video = res.locals.onlyImmutableVideo
const video = res.locals.onlyVideo
if (redirectIfNotOwned(video.url, res)) return
@ -347,7 +347,7 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
}
async function videoLikesController (req: express.Request, res: express.Response) {
const video = res.locals.onlyImmutableVideo
const video = res.locals.onlyVideo
if (redirectIfNotOwned(video.url, res)) return
@ -357,7 +357,7 @@ async function videoLikesController (req: express.Request, res: express.Response
}
async function videoDislikesController (req: express.Request, res: express.Response) {
const video = res.locals.onlyImmutableVideo
const video = res.locals.onlyVideo
if (redirectIfNotOwned(video.url, res)) return
@ -367,7 +367,7 @@ async function videoDislikesController (req: express.Request, res: express.Respo
}
async function videoCommentsController (req: express.Request, res: express.Response) {
const video = res.locals.onlyImmutableVideo
const video = res.locals.onlyVideo
if (redirectIfNotOwned(video.url, res)) return

View File

@ -11,7 +11,7 @@ import { replaceChapters } from '@server/lib/video-chapters.js'
const videoChaptersRouter = express.Router()
videoChaptersRouter.get('/:id/chapters',
asyncMiddleware(videosCustomGetValidator('only-video')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
asyncMiddleware(listVideoChapters)
)

View File

@ -1,6 +1,7 @@
import express from 'express'
import { HttpStatusCode, VideoChangeOwnershipStatus, VideoState } from '@peertube/peertube-models'
import { HttpStatusCode, VideoChangeOwnershipStatus } from '@peertube/peertube-models'
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
import { MVideoFullLight } from '@server/types/models/index.js'
import express from 'express'
import { logger } from '../../../helpers/logger.js'
import { getFormattedObjects } from '../../../helpers/utils.js'
import { sequelizeTypescript } from '../../../initializers/database.js'
@ -113,7 +114,7 @@ function acceptOwnership (req: express.Request, res: express.Response) {
const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
targetVideoUpdated.VideoChannel = channel
if (targetVideoUpdated.hasPrivacyForFederation() && targetVideoUpdated.state === VideoState.PUBLISHED) {
if (canVideoBeFederated(targetVideoUpdated)) {
await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
}

View File

@ -7,7 +7,7 @@ const tokenRouter = express.Router()
tokenRouter.post('/:id/token',
optionalAuthenticate,
asyncMiddleware(videosCustomGetValidator('only-video')),
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
videoFileTokenValidator,
generateToken
)

View File

@ -1,9 +1,11 @@
import express, { UploadFiles } from 'express'
import { Transaction } from 'sequelize'
import { forceNumber } from '@peertube/peertube-core-utils'
import { HttpStatusCode, ThumbnailType, VideoPrivacy, VideoPrivacyType, VideoUpdate } from '@peertube/peertube-models'
import { exists } from '@server/helpers/custom-validators/misc.js'
import { changeVideoChannelShare } from '@server/lib/activitypub/share.js'
import { isNewVideoPrivacyForFederation, isPrivacyForFederation } from '@server/lib/activitypub/videos/federate.js'
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
import { VideoPathManager } from '@server/lib/video-path-manager.js'
import { setVideoPrivacy } from '@server/lib/video-privacy.js'
import { setVideoTags } from '@server/lib/video.js'
@ -11,7 +13,9 @@ import { openapiOperationDoc } from '@server/middlewares/doc.js'
import { VideoPasswordModel } from '@server/models/video/video-password.js'
import { FilteredModelAttributes } from '@server/types/index.js'
import { MVideoFullLight, MVideoThumbnail } from '@server/types/models/index.js'
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
import express, { UploadFiles } from 'express'
import { Transaction } from 'sequelize'
import { VideoAuditView, auditLoggerFactory, getAuditIdFromRes } from '../../../helpers/audit-logger.js'
import { resetSequelizeInstance } from '../../../helpers/database-utils.js'
import { createReqFiles } from '../../../helpers/express-utils.js'
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
@ -22,9 +26,6 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist.js'
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares/index.js'
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update.js'
import { VideoModel } from '../../../models/video/video.js'
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
const lTags = loggerTagsFactory('api', 'video')
const auditLogger = auditLoggerFactory('videos')
@ -53,7 +54,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON())
const videoInfoToUpdate: VideoUpdate = req.body
const hadPrivacyForFederation = videoFromReq.hasPrivacyForFederation()
const hadPrivacyForFederation = isPrivacyForFederation(videoFromReq.privacy)
const oldPrivacy = videoFromReq.privacy
const thumbnails = await buildVideoThumbnailsFromReq(videoFromReq, req.files)
@ -191,7 +192,7 @@ async function updateVideoPrivacy (options: {
transaction: Transaction
}) {
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
const isNewVideoForFederation = videoInstance.isNewVideoForFederation(videoInfoToUpdate.privacy)
const isNewVideoForFederation = isNewVideoPrivacyForFederation(videoInfoToUpdate.privacy, videoInfoToUpdate.privacy)
const newPrivacy = forceNumber(videoInfoToUpdate.privacy) as VideoPrivacyType
setVideoPrivacy(videoInstance, newPrivacy)
@ -207,7 +208,7 @@ async function updateVideoPrivacy (options: {
}
// Unfederate the video if the new privacy is not compatible with federation
if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
if (hadPrivacyForFederation && !isPrivacyForFederation(videoInstance.privacy)) {
await VideoModel.sendDelete(videoInstance, { transaction })
}

View File

@ -1,51 +1,28 @@
import { Response } from 'express'
import { forceNumber } from '@peertube/peertube-core-utils'
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
import { VideoPrivacy } from '@peertube/peertube-models'
import { CONFIG } from '@server/initializers/config.js'
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models/index.js'
import { Response } from 'express'
function getVideoWithAttributes (res: Response) {
export function getVideoWithAttributes (res: Response) {
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
}
function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
export function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
return isStreamingPlaylist(videoOrPlaylist)
? videoOrPlaylist.Video
: videoOrPlaylist
}
function isPrivacyForFederation (privacy: VideoPrivacyType) {
const castedPrivacy = forceNumber(privacy)
return castedPrivacy === VideoPrivacy.PUBLIC ||
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
}
function isStateForFederation (state: VideoStateType) {
const castedState = forceNumber(state)
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
}
function getPrivaciesForFederation () {
export function getPrivaciesForFederation () {
return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true)
? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ]
: [ { privacy: VideoPrivacy.PUBLIC } ]
}
function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
export function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
const value = mimeTypes[mimeType]
if (Array.isArray(value)) return value[0]
return value
}
export {
getVideoWithAttributes,
extractVideo,
getExtFromMimetype,
isStateForFederation,
isPrivacyForFederation,
getPrivaciesForFederation
}

View File

@ -145,7 +145,7 @@ async function buildElementsDBAttributes (elementUrls: string[], playlist: MVide
try {
const { elementObject } = await fetchRemotePlaylistElement(elementUrl)
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video' })
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video-and-blacklist' })
elementsToCreate.push(playlistElementObjectToDBAttributes(elementObject, playlist, video))
} catch (err) {

View File

@ -24,7 +24,7 @@ import { createOrUpdateLocalVideoViewer } from '../local-video-viewer.js'
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
import { resolveThread } from '../video-comments.js'
import { getOrCreateAPVideo } from '../videos/index.js'
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
const { activity, byActor } = options
@ -87,6 +87,11 @@ async function processCreateCacheFile (
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
if (video.isOwned() && !canVideoBeFederated(video)) {
logger.warn(`Do not process create cache file ${cacheFile.object} on a video that cannot be federated`)
return
}
await sequelizeTypescript.transaction(async t => {
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
})

View File

@ -1,11 +1,12 @@
import { VideoModel } from '@server/models/video/video.js'
import { ActivityDislike } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import { VideoModel } from '@server/models/video/video.js'
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
import { sequelizeTypescript } from '../../../initializers/database.js'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
import { MActorSignature } from '../../../types/models/index.js'
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
const { activity, byActor } = options
@ -21,14 +22,19 @@ export {
// ---------------------------------------------------------------------------
async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
const dislikeObject = activity.object
const videoUrl = activity.object
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: dislikeObject, fetchType: 'only-video' })
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
if (!onlyVideo?.isOwned()) return
if (!canVideoBeFederated(onlyVideo)) {
logger.warn(`Do not process dislike on video ${videoUrl} that cannot be federated`)
return
}
return sequelizeTypescript.transaction(async t => {
const video = await VideoModel.loadFull(onlyVideo.id, t)

View File

@ -1,4 +1,5 @@
import { ActivityLike } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import { VideoModel } from '@server/models/video/video.js'
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
import { sequelizeTypescript } from '../../../initializers/database.js'
@ -6,7 +7,7 @@ import { getAPId } from '../../../lib/activitypub/activity.js'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
import { MActorSignature } from '../../../types/models/index.js'
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
const { activity, byActor } = options
@ -28,9 +29,14 @@ async function processLikeVideo (byActor: MActorSignature, activity: ActivityLik
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video' })
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
if (!onlyVideo?.isOwned()) return
if (!canVideoBeFederated(onlyVideo)) {
logger.warn(`Do not process like on video ${videoUrl} that cannot be federated`)
return
}
return sequelizeTypescript.transaction(async t => {
const video = await VideoModel.loadFull(onlyVideo.id, t)

View File

@ -20,7 +20,7 @@ import { APActorUpdater } from '../actors/updater.js'
import { createOrUpdateCacheFile } from '../cache-file.js'
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
import { APVideoUpdater, getOrCreateAPVideo } from '../videos/index.js'
import { APVideoUpdater, canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
const { activity, byActor } = options
@ -93,6 +93,11 @@ async function processUpdateCacheFile (
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
if (video.isOwned() && !canVideoBeFederated(video)) {
logger.warn(`Do not process update cache file on video ${activity.object} that cannot be federated`)
return
}
await sequelizeTypescript.transaction(async t => {
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
})

View File

@ -24,7 +24,7 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
const { video } = await getOrCreateAPVideo({
videoObject,
fetchType: 'only-video',
fetchType: 'only-video-and-blacklist',
allowRefresh: false
})

View File

@ -1,5 +1,3 @@
import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application.js'
import {
ActivityAudience,
ActivityCreate,
@ -9,19 +7,24 @@ import {
VideoPlaylistPrivacy,
VideoPrivacy
} from '@peertube/peertube-models'
import { AccountModel } from '@server/models/account/account.js'
import { getServerActor } from '@server/models/application/application.js'
import { VideoModel } from '@server/models/video/video.js'
import { Transaction } from 'sequelize'
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
import { VideoCommentModel } from '../../../models/video/video-comment.js'
import {
MActorLight,
MCommentOwnerVideo,
MLocalVideoViewerWithWatchSections,
MVideoAccountLight,
MVideoAP,
MVideoAccountLight,
MVideoPlaylistFull,
MVideoRedundancyFileVideo,
MVideoRedundancyStreamingPlaylistVideo
} from '../../../types/models/index.js'
import { audiencify, getAudience } from '../audience.js'
import { canVideoBeFederated } from '../videos/federate.js'
import {
broadcastToActors,
broadcastToFollowers,
@ -32,12 +35,11 @@ import {
sendVideoRelatedActivity,
unicastTo
} from './shared/index.js'
import { AccountModel } from '@server/models/account/account.js'
const lTags = loggerTagsFactory('ap', 'create')
async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
if (!video.hasPrivacyForFederation()) return undefined
export async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
if (!canVideoBeFederated(video)) return undefined
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
@ -56,7 +58,7 @@ async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
})
}
async function sendCreateCacheFile (
export async function sendCreateCacheFile (
byActor: MActorLight,
video: MVideoAccountLight,
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
@ -72,7 +74,7 @@ async function sendCreateCacheFile (
})
}
async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
export async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid))
const byActor = await getServerActor()
@ -84,7 +86,7 @@ async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections,
return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' })
}
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
export async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
@ -109,11 +111,20 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transactio
})
}
async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
logger.info('Creating job to send comment %s.', comment.url)
export async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
const isOrigin = comment.Video.isOwned()
if (isOrigin) {
const videoWithBlacklist = await VideoModel.loadWithBlacklist(comment.Video.id)
if (!canVideoBeFederated(videoWithBlacklist)) {
logger.debug(`Do not send comment ${comment.url} on a video that cannot be federated`)
return undefined
}
}
logger.info('Creating job to send comment %s.', comment.url)
const byActor = comment.Account.Actor
const videoAccount = await AccountModel.load(comment.Video.VideoChannel.Account.id, transaction)
@ -179,7 +190,7 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction:
})
}
function buildCreateActivity <T extends ActivityCreateObject> (
export function buildCreateActivity <T extends ActivityCreateObject> (
url: string,
byActor: MActorLight,
object: T,
@ -201,16 +212,7 @@ function buildCreateActivity <T extends ActivityCreateObject> (
}
// ---------------------------------------------------------------------------
export {
sendCreateVideo,
buildCreateActivity,
sendCreateVideoComment,
sendCreateVideoPlaylist,
sendCreateCacheFile,
sendCreateWatchAction
}
// Private
// ---------------------------------------------------------------------------
async function sendVideoRelatedCreateActivity (options: {

View File

@ -1,10 +1,10 @@
import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application.js'
import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models'
import { getServerActor } from '@server/models/application/application.js'
import { Transaction } from 'sequelize'
import { logger } from '../../../helpers/logger.js'
import { AccountModel } from '../../../models/account/account.js'
import { VideoModel } from '../../../models/video/video.js'
import { VideoShareModel } from '../../../models/video/video-share.js'
import { VideoModel } from '../../../models/video/video.js'
import {
MAccountDefault,
MActor,
@ -16,11 +16,12 @@ import {
} from '../../../types/models/index.js'
import { audiencify, getAudience } from '../audience.js'
import { getUpdateActivityPubUrl } from '../url.js'
import { canVideoBeFederated } from '../videos/federate.js'
import { getActorsInvolvedInVideo } from './shared/index.js'
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils.js'
async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
if (!videoArg.hasPrivacyForFederation()) return undefined
export async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
if (!canVideoBeFederated(videoArg)) return undefined
const video = await videoArg.lightAPToFullAP(transaction)
@ -47,7 +48,7 @@ async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transactio
})
}
async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
export async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
const byActor = accountOrChannel.Actor
logger.info('Creating job to update actor %s.', byActor.url)
@ -77,7 +78,7 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
})
}
async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
export async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
logger.info('Creating job to update cache file %s.', redundancyModel.url)
const associatedVideo = redundancyModel.getVideo()
@ -98,7 +99,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
}
async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
export async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
const byActor = videoPlaylist.OwnerAccount.Actor
@ -127,14 +128,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, trans
}
// ---------------------------------------------------------------------------
export {
sendUpdateActor,
sendUpdateVideo,
sendUpdateCacheFile,
sendUpdateVideoPlaylist
}
// Private
// ---------------------------------------------------------------------------
function buildUpdateActivity (

View File

@ -1,6 +1,6 @@
import { getServerActor } from '@server/models/application/application.js'
import Bluebird from 'bluebird'
import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application.js'
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants.js'
import { VideoShareModel } from '../../models/video/video-share.js'
@ -12,16 +12,7 @@ import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url.js
const lTags = loggerTagsFactory('share')
async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
if (!video.hasPrivacyForFederation()) return undefined
return Promise.all([
shareByServer(video, t),
shareByVideoChannel(video, t)
])
}
async function changeVideoChannelShare (
export async function changeVideoChannelShare (
video: MVideoAccountLight,
oldVideoChannel: MChannelActorLight,
t: Transaction
@ -36,7 +27,7 @@ async function changeVideoChannelShare (
await shareByVideoChannel(video, t)
}
async function addVideoShares (shareUrls: string[], video: MVideoId) {
export async function addVideoShares (shareUrls: string[], video: MVideoId) {
await Bluebird.map(shareUrls, async shareUrl => {
try {
await addVideoShare(shareUrl, video)
@ -46,12 +37,44 @@ async function addVideoShares (shareUrls: string[], video: MVideoId) {
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
}
export {
changeVideoChannelShare,
addVideoShares,
shareVideoByServerAndChannel
export async function shareByServer (video: MVideo, t: Transaction) {
const serverActor = await getServerActor()
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
const [ serverShare ] = await VideoShareModel.findOrCreate({
defaults: {
actorId: serverActor.id,
videoId: video.id,
url: serverShareUrl
},
where: {
url: serverShareUrl
},
transaction: t
})
return sendVideoAnnounce(serverActor, serverShare, video, t)
}
export async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
defaults: {
actorId: video.VideoChannel.actorId,
videoId: video.id,
url: videoChannelShareUrl
},
where: {
url: videoChannelShareUrl
},
transaction: t
})
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
async function addVideoShare (shareUrl: string, video: MVideoId) {
@ -74,42 +97,6 @@ async function addVideoShare (shareUrl: string, video: MVideoId) {
await VideoShareModel.upsert(entry)
}
async function shareByServer (video: MVideo, t: Transaction) {
const serverActor = await getServerActor()
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
const [ serverShare ] = await VideoShareModel.findOrCreate({
defaults: {
actorId: serverActor.id,
videoId: video.id,
url: serverShareUrl
},
where: {
url: serverShareUrl
},
transaction: t
})
return sendVideoAnnounce(serverActor, serverShare, video, t)
}
async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
defaults: {
actorId: video.VideoChannel.actorId,
videoId: video.id,
url: videoChannelShareUrl
},
where: {
url: videoChannelShareUrl
},
transaction: t
})
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
}
async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
// Load old share
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)

View File

@ -9,7 +9,7 @@ import { Hooks } from '../plugins/hooks.js'
import { fetchAP } from './activity.js'
import { getOrCreateAPActor } from './actors/index.js'
import { checkUrlsSameHost } from './url.js'
import { getOrCreateAPVideo } from './videos/index.js'
import { canVideoBeFederated, getOrCreateAPVideo } from './videos/index.js'
type ResolveThreadParams = {
url: string
@ -92,8 +92,8 @@ async function tryToResolveThreadFromVideo (params: ResolveThreadParams) {
const syncParam = { rates: true, shares: true, comments: false, refreshVideo: false }
const { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
if (video.isOwned() && !video.hasPrivacyForFederation()) {
throw new Error('Cannot resolve thread of video with privacy that is not compatible with federation')
if (video.isOwned() && !canVideoBeFederated(video)) {
throw new Error('Cannot resolve thread of video that is not compatible with federation')
}
let resultComment: MCommentOwnerVideo

View File

@ -1,29 +1,53 @@
import { forceNumber } from '@peertube/peertube-core-utils'
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
import { CONFIG } from '@server/initializers/config.js'
import { MVideoAPLight, MVideoWithBlacklistRights } from '@server/types/models/index.js'
import { Transaction } from 'sequelize'
import { MVideoAP, MVideoAPLight } from '@server/types/models/index.js'
import { sendCreateVideo, sendUpdateVideo } from '../send/index.js'
import { shareVideoByServerAndChannel } from '../share.js'
import { shareByServer, shareByVideoChannel } from '../share.js'
async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
const video = videoArg as MVideoAP
export async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
if (!canVideoBeFederated(videoArg, isNewVideo)) return
if (
// Check this is not a blacklisted video, or unfederated blacklisted video
(video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
// Check the video is public/unlisted and published
video.hasPrivacyForFederation() && video.hasStateForFederation()
) {
const video = await videoArg.lightAPToFullAP(transaction)
const video = await videoArg.lightAPToFullAP(transaction)
if (isNewVideo) {
// Now we'll add the video's meta data to our followers
await sendCreateVideo(video, transaction)
await shareVideoByServerAndChannel(video, transaction)
} else {
await sendUpdateVideo(video, transaction)
}
if (isNewVideo) {
// Now we'll add the video's meta data to our followers
await sendCreateVideo(video, transaction)
await Promise.all([
shareByServer(video, transaction),
shareByVideoChannel(video, transaction)
])
} else {
await sendUpdateVideo(video, transaction)
}
}
export {
federateVideoIfNeeded
export function canVideoBeFederated (video: MVideoWithBlacklistRights, isNewVideo = false) {
// Check this is not a blacklisted video
if (video.isBlacklisted() === true) {
if (isNewVideo === false) return false
if (video.VideoBlacklist.unfederated === true) return false
}
// Check the video is public/unlisted and published
return isPrivacyForFederation(video.privacy) && isStateForFederation(video.state)
}
export function isNewVideoPrivacyForFederation (currentPrivacy: VideoPrivacyType, newPrivacy: VideoPrivacyType) {
return !isPrivacyForFederation(currentPrivacy) && isPrivacyForFederation(newPrivacy)
}
export function isPrivacyForFederation (privacy: VideoPrivacyType) {
const castedPrivacy = forceNumber(privacy)
return castedPrivacy === VideoPrivacy.PUBLIC ||
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
}
export function isStateForFederation (state: VideoStateType) {
const castedState = forceNumber(state)
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
}

View File

@ -1,9 +1,14 @@
import { APObjectId } from '@peertube/peertube-models'
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
import { logger } from '@server/helpers/logger.js'
import { JobQueue } from '@server/lib/job-queue/index.js'
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders/index.js'
import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models/index.js'
import { APObjectId } from '@peertube/peertube-models'
import {
MVideoAccountLightBlacklistAllFiles,
MVideoImmutable,
MVideoThumbnail,
MVideoThumbnailBlacklist
} from '@server/types/models/index.js'
import { getAPId } from '../activity.js'
import { refreshVideoIfNeeded } from './refresh.js'
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared/index.js'
@ -24,23 +29,25 @@ type GetVideoParamAll = {
type GetVideoParamImmutable = {
videoObject: APObjectId
syncParam?: SyncParam
fetchType: 'only-immutable-attributes'
fetchType: 'unsafe-only-immutable-attributes'
allowRefresh: false
}
type GetVideoParamOther = {
videoObject: APObjectId
syncParam?: SyncParam
fetchType?: 'all' | 'only-video'
fetchType?: 'all' | 'only-video-and-blacklist'
allowRefresh?: boolean
}
export function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
export function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
export function getOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
export function getOrCreateAPVideo (
options: GetVideoParamOther
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
export async function getOrCreateAPVideo (
options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
// Default params
const syncParam = options.syncParam || { rates: true, shares: true, comments: true, refreshVideo: false }
const fetchType = options.fetchType || 'all'
@ -52,7 +59,7 @@ export async function getOrCreateAPVideo (
if (videoFromDatabase) {
if (allowRefresh === true) {
// Typings ensure allowRefresh === false in only-immutable-attributes fetch type
// Typings ensure allowRefresh === false in unsafe-only-immutable-attributes fetch type
videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
}
@ -87,7 +94,9 @@ export async function getOrCreateAPVideo (
export function maybeGetOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
export function maybeGetOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
export function maybeGetOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
export function maybeGetOrCreateAPVideo (
options: GetVideoParamOther
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
export async function maybeGetOrCreateAPVideo (options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther) {
try {
const result = await getOrCreateAPVideo(options as any)

View File

@ -6,57 +6,57 @@ import {
MVideoFullLight,
MVideoId,
MVideoImmutable,
MVideoThumbnail
MVideoThumbnailBlacklist
} from '@server/types/models/index.js'
import { getOrCreateAPVideo } from '../activitypub/videos/get.js'
type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes'
type VideoLoadType = 'for-api' | 'all' | 'only-video-and-blacklist' | 'id' | 'none' | 'unsafe-only-immutable-attributes'
function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
function loadVideo (id: number | string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
function loadVideo (id: number | string, fetchType: 'only-video', userId?: number): Promise<MVideoThumbnail>
function loadVideo (id: number | string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
function loadVideo (id: number | string, fetchType: 'only-video-and-blacklist', userId?: number): Promise<MVideoThumbnailBlacklist>
function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
function loadVideo (
id: number | string,
fetchType: VideoLoadType,
userId?: number
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable>
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable>
function loadVideo (
id: number | string,
fetchType: VideoLoadType,
userId?: number
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> {
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable> {
if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId })
if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId)
if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
if (fetchType === 'only-video') return VideoModel.load(id)
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadWithBlacklist(id)
if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
}
type VideoLoadByUrlType = 'all' | 'only-video' | 'only-immutable-attributes'
type VideoLoadByUrlType = 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes'
function loadVideoByUrl (url: string, fetchType: 'all'): Promise<MVideoAccountLightBlacklistAllFiles>
function loadVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
function loadVideoByUrl (url: string, fetchType: 'only-video'): Promise<MVideoThumbnail>
function loadVideoByUrl (url: string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
function loadVideoByUrl (url: string, fetchType: 'only-video-and-blacklist'): Promise<MVideoThumbnailBlacklist>
function loadVideoByUrl (
url: string,
fetchType: VideoLoadByUrlType
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable>
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable>
function loadVideoByUrl (
url: string,
fetchType: VideoLoadByUrlType
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccountAndFiles(url)
if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadByUrlWithBlacklist(url)
}
async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
@ -64,7 +64,7 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
try {
const res = await getOrCreateAPVideo({
videoObject: videoUrl,
fetchType: 'only-immutable-attributes',
fetchType: 'unsafe-only-immutable-attributes',
allowRefresh: false
})
@ -78,10 +78,8 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
}
export {
type VideoLoadType,
type VideoLoadByUrlType,
loadVideo,
loadOrCreateVideoIfAllowedForUser, loadVideo,
loadVideoByUrl,
loadOrCreateVideoIfAllowedForUser
type VideoLoadByUrlType,
type VideoLoadType
}

View File

@ -5,10 +5,11 @@ import { logger } from '../../helpers/logger.js'
import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants.js'
import { sequelizeTypescript } from '../../initializers/database.js'
import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update.js'
import { isNewVideoPrivacyForFederation } from '../activitypub/videos/federate.js'
import { Notifier } from '../notifier/index.js'
import { addVideoJobsAfterUpdate } from '../video-jobs.js'
import { VideoPathManager } from '../video-path-manager.js'
import { setVideoPrivacy } from '../video-privacy.js'
import { addVideoJobsAfterUpdate } from '../video-jobs.js'
import { AbstractScheduler } from './abstract-scheduler.js'
export class UpdateVideosScheduler extends AbstractScheduler {
@ -58,7 +59,7 @@ export class UpdateVideosScheduler extends AbstractScheduler {
logger.info('Executing scheduled video update on %s.', video.uuid)
if (schedule.privacy) {
isNewVideoForFederation = video.isNewVideoForFederation(schedule.privacy)
isNewVideoForFederation = isNewVideoPrivacyForFederation(video.privacy, schedule.privacy)
oldPrivacy = video.privacy
setVideoPrivacy(video, schedule.privacy)

View File

@ -47,7 +47,7 @@ const addPlaybackMetricValidator = [
const body: PlaybackMetricCreate = req.body
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(body.videoId, res, 'only-immutable-attributes')) return
if (!await doesVideoExist(body.videoId, res, 'unsafe-only-immutable-attributes')) return
return next()
}

View File

@ -143,7 +143,7 @@ const addVideoRedundancyValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
if (!await doesVideoExist(req.body.videoId, res, 'only-video-and-blacklist')) return
if (res.locals.onlyVideo.remote === false) {
return res.fail({ message: 'Cannot create a redundancy on a local video' })

View File

@ -47,7 +47,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
res.locals.videoAll = video as MVideoFullLight
break
case 'only-immutable-attributes':
case 'unsafe-only-immutable-attributes':
res.locals.onlyImmutableVideo = video as MVideoImmutable
break
@ -55,7 +55,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
res.locals.videoId = video as MVideoId
break
case 'only-video':
case 'only-video-and-blacklist':
res.locals.onlyVideo = video as MVideoThumbnail
break
}

View File

@ -67,7 +67,7 @@ const listVideoCaptionsValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
const video = res.locals.onlyVideo
if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.videoId })) return

View File

@ -56,7 +56,7 @@ const listVideoCommentThreadsValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
@ -73,7 +73,7 @@ const listVideoThreadCommentsValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return

View File

@ -205,7 +205,7 @@ const videoPlaylistsAddVideoValidator = [
if (areValidationErrors(req, res)) return
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
if (!await doesVideoExist(req.body.videoId, res, 'only-video-and-blacklist')) return
const videoPlaylist = getPlaylist(res)

View File

@ -37,7 +37,7 @@ export const videoViewValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return
if (!await doesVideoExist(req.params.videoId, res, 'unsafe-only-immutable-attributes')) return
const video = res.locals.onlyImmutableVideo
const { duration } = await getCachedVideoDuration(video.id)

View File

@ -243,7 +243,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
})
}
const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes') => {
const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes') => {
return [
isValidVideoIdParam('id'),
@ -254,7 +254,7 @@ const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' |
if (!await doesVideoExist(req.params.id, res, fetchType)) return
// Controllers does not need to check video rights
if (fetchType === 'only-immutable-attributes') return next()
if (fetchType === 'unsafe-only-immutable-attributes') return next()
const video = getVideoWithAttributes(res) as MVideoFullLight

View File

@ -19,7 +19,7 @@ import {
type VideoStateType
} from '@peertube/peertube-models'
import { uuidToShort } from '@peertube/peertube-node-utils'
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video.js'
import { getPrivaciesForFederation } from '@server/helpers/video.js'
import { InternalEventEmitter } from '@server/lib/internal-event-emitter.js'
import { LiveManager } from '@server/lib/live/live-manager.js'
import {
@ -1448,6 +1448,12 @@ export class VideoModel extends SequelizeModel<VideoModel> {
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
}
static loadByUrlWithBlacklist (url: string, transaction?: Transaction): Promise<MVideoThumbnailBlacklist> {
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails-blacklist' })
}
static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLight> {
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
@ -2045,18 +2051,6 @@ export class VideoModel extends SequelizeModel<VideoModel> {
return isOutdated(this, ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL)
}
hasPrivacyForFederation () {
return isPrivacyForFederation(this.privacy)
}
hasStateForFederation () {
return isStateForFederation(this.state)
}
isNewVideoForFederation (newPrivacy: VideoPrivacyType) {
return this.hasPrivacyForFederation() === false && isPrivacyForFederation(newPrivacy) === true
}
setAsRefreshed (transaction?: Transaction) {
return setAsUpdated({ sequelize: this.sequelize, table: 'video', id: this.id, transaction })
}

View File

@ -102,6 +102,10 @@ export type MVideoWithBlacklistLight =
MVideo &
Use<'VideoBlacklist', MVideoBlacklistLight>
export type MVideoWithBlacklistRights =
MVideo &
Use<'VideoBlacklist', MVideoBlacklistUnfederated>
export type MVideoAccountLight =
MVideo &
Use<'VideoChannel', MChannelAccountLight>