Fix ownership change with a live video

This commit is contained in:
Chocobozzz 2021-06-28 11:54:40 +02:00
parent 50cb778ee6
commit 21b5c2982f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
4 changed files with 208 additions and 101 deletions

View File

@ -3,6 +3,8 @@ export * from './video-captions'
export * from './video-channels'
export * from './video-comments'
export * from './video-imports'
export * from './video-live'
export * from './video-ownership-changes'
export * from './video-watch'
export * from './video-rates'
export * from './video-shares'

View File

@ -0,0 +1,119 @@
import * as express from 'express'
import { param } from 'express-validator'
import { isIdOrUUIDValid } from '@server/helpers/custom-validators/misc'
import { checkUserCanTerminateOwnershipChange } from '@server/helpers/custom-validators/video-ownership'
import { logger } from '@server/helpers/logger'
import { isAbleToUploadVideo } from '@server/lib/user'
import { AccountModel } from '@server/models/account/account'
import { MVideoWithAllFiles } from '@server/types/models'
import { HttpStatusCode } from '@shared/core-utils'
import { ServerErrorCode, UserRight, VideoChangeOwnershipAccept, VideoChangeOwnershipStatus, VideoState } from '@shared/models'
import {
areValidationErrors,
checkUserCanManageVideo,
doesChangeVideoOwnershipExist,
doesVideoChannelOfAccountExist,
doesVideoExist
} from '../shared'
const videosChangeOwnershipValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking changeOwnership parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
// Check if the user who did the request is able to change the ownership of the video
if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
const nextOwner = await AccountModel.loadLocalByName(req.body.username)
if (!nextOwner) {
res.fail({ message: 'Changing video ownership to a remote account is not supported yet' })
return
}
res.locals.nextOwner = nextOwner
return next()
}
]
const videosTerminateChangeOwnershipValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking changeOwnership parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
// Check if the user who did the request is able to change the ownership of the video
if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
const videoChangeOwnership = res.locals.videoChangeOwnership
if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Ownership already accepted or refused'
})
return
}
return next()
}
]
const videosAcceptChangeOwnershipValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const body = req.body as VideoChangeOwnershipAccept
if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
const videoChangeOwnership = res.locals.videoChangeOwnership
const video = videoChangeOwnership.Video
if (!await checkCanAccept(video, res)) return
return next()
}
]
export {
videosChangeOwnershipValidator,
videosTerminateChangeOwnershipValidator,
videosAcceptChangeOwnershipValidator
}
// ---------------------------------------------------------------------------
async function checkCanAccept (video: MVideoWithAllFiles, res: express.Response): Promise<boolean> {
if (video.isLive) {
if (video.state !== VideoState.WAITING_FOR_LIVE) {
res.fail({
status: HttpStatusCode.BAD_REQUEST_400,
message: 'You can accept an ownership change of a published live.'
})
return false
}
return true
}
const user = res.locals.oauth.token.User
if (!await isAbleToUploadVideo(user.id, video.getMaxQualityFile().size)) {
res.fail({
status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
message: 'The user video quota is exceeded with this video.',
type: ServerErrorCode.QUOTA_REACHED
})
return false
}
return true
}

View File

@ -5,9 +5,8 @@ import { isAbleToUploadVideo } from '@server/lib/user'
import { getServerActor } from '@server/models/application/application'
import { ExpressPromiseHandler } from '@server/types/express'
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/change-ownership/video-change-ownership-accept.model'
import {
exists,
isBooleanValid,
@ -22,7 +21,6 @@ import {
toValueOrNull
} from '../../../helpers/custom-validators/misc'
import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
import { checkUserCanTerminateOwnershipChange } from '../../../helpers/custom-validators/video-ownership'
import {
isScheduleVideoUpdatePrivacyValid,
isVideoCategoryValid,
@ -48,13 +46,11 @@ import { CONFIG } from '../../../initializers/config'
import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants'
import { isLocalVideoAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks'
import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video'
import { authenticatePromiseIfNeeded } from '../../auth'
import {
areValidationErrors,
checkUserCanManageVideo,
doesChangeVideoOwnershipExist,
doesVideoChannelOfAccountExist,
doesVideoExist,
doesVideoFileOfVideoExist
@ -342,76 +338,6 @@ const videosRemoveValidator = [
}
]
const videosChangeOwnershipValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking changeOwnership parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
// Check if the user who did the request is able to change the ownership of the video
if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
const nextOwner = await AccountModel.loadLocalByName(req.body.username)
if (!nextOwner) {
res.fail({ message: 'Changing video ownership to a remote account is not supported yet' })
return
}
res.locals.nextOwner = nextOwner
return next()
}
]
const videosTerminateChangeOwnershipValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking changeOwnership parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesChangeVideoOwnershipExist(req.params.id, res)) return
// Check if the user who did the request is able to change the ownership of the video
if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
const videoChangeOwnership = res.locals.videoChangeOwnership
if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Ownership already accepted or refused'
})
return
}
return next()
}
]
const videosAcceptChangeOwnershipValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const body = req.body as VideoChangeOwnershipAccept
if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return
const user = res.locals.oauth.token.User
const videoChangeOwnership = res.locals.videoChangeOwnership
const isAble = await isAbleToUploadVideo(user.id, videoChangeOwnership.Video.getMaxQualityFile().size)
if (isAble === false) {
res.fail({
status: HttpStatusCode.PAYLOAD_TOO_LARGE_413,
message: 'The user video quota is exceeded with this video.',
type: ServerErrorCode.QUOTA_REACHED
})
return
}
return next()
}
]
const videosOverviewValidator = [
query('page')
.optional()
@ -578,10 +504,6 @@ export {
videosCustomGetValidator,
videosRemoveValidator,
videosChangeOwnershipValidator,
videosTerminateChangeOwnershipValidator,
videosAcceptChangeOwnershipValidator,
getCommonVideoEditAttributes,
commonVideosFiltersValidator,

View File

@ -1,11 +1,13 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import * as chai from 'chai'
import 'mocha'
import * as chai from 'chai'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import {
acceptChangeOwnership,
changeVideoOwnership,
cleanupTests,
createLive,
createUser,
doubleFollow,
flushAndRunMultipleServers,
@ -17,13 +19,14 @@ import {
refuseChangeOwnership,
ServerInfo,
setAccessTokensToServers,
setDefaultVideoChannel,
updateCustomSubConfig,
uploadVideo,
userLogin
} from '../../../../shared/extra-utils'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
import { User } from '../../../../shared/models/users'
import { VideoDetails } from '../../../../shared/models/videos'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
const expect = chai.expect
@ -37,15 +40,32 @@ describe('Test video change ownership - nominal', function () {
username: 'second',
password: 'My other password'
}
let firstUserAccessToken = ''
let firstUserChannelId: number
let secondUserAccessToken = ''
let secondUserChannelId: number
let lastRequestChangeOwnershipId = ''
let liveId: number
before(async function () {
this.timeout(50000)
servers = await flushAndRunMultipleServers(2)
await setAccessTokensToServers(servers)
await setDefaultVideoChannel(servers)
await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
transcoding: {
enabled: false
},
live: {
enabled: true
}
})
const videoQuota = 42000000
await createUser({
@ -66,22 +86,35 @@ describe('Test video change ownership - nominal', function () {
firstUserAccessToken = await userLogin(servers[0], firstUser)
secondUserAccessToken = await userLogin(servers[0], secondUser)
{
const res = await getMyUserInformation(servers[0].url, firstUserAccessToken)
const firstUserInformation: User = res.body
firstUserChannelId = firstUserInformation.videoChannels[0].id
}
{
const res = await getMyUserInformation(servers[0].url, secondUserAccessToken)
const secondUserInformation: User = res.body
secondUserChannelId = secondUserInformation.videoChannels[0].id
}
{
const videoAttributes = {
name: 'my super name',
description: 'my super description'
}
await uploadVideo(servers[0].url, firstUserAccessToken, videoAttributes)
const res = await uploadVideo(servers[0].url, firstUserAccessToken, videoAttributes)
await waitJobs(servers)
const resVideo = await getVideo(servers[0].url, res.body.video.id)
servers[0].video = resVideo.body
}
const res = await getVideosList(servers[0].url)
const videos = res.body.data
{
const attributes = { name: 'live', channelId: firstUserChannelId, privacy: VideoPrivacy.PUBLIC }
const res = await createLive(servers[0].url, firstUserAccessToken, attributes)
expect(videos.length).to.equal(1)
const video = videos.find(video => video.name === 'my super name')
expect(video.channel.name).to.equal('first_channel')
servers[0].video = video
liveId = res.body.video.id
}
await doubleFollow(servers[0], servers[1])
})
@ -175,19 +208,19 @@ describe('Test video change ownership - nominal', function () {
it('Should not be possible to accept the change of ownership from first user', async function () {
this.timeout(10000)
const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
const secondUserInformation: User = secondUserInformationResponse.body
const channelId = secondUserInformation.videoChannels[0].id
await acceptChangeOwnership(servers[0].url, firstUserAccessToken, lastRequestChangeOwnershipId, channelId, HttpStatusCode.FORBIDDEN_403)
await acceptChangeOwnership(
servers[0].url,
firstUserAccessToken,
lastRequestChangeOwnershipId,
secondUserChannelId,
HttpStatusCode.FORBIDDEN_403
)
})
it('Should be possible to accept the change of ownership from second user', async function () {
this.timeout(10000)
const secondUserInformationResponse = await getMyUserInformation(servers[0].url, secondUserAccessToken)
const secondUserInformation: User = secondUserInformationResponse.body
const channelId = secondUserInformation.videoChannels[0].id
await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, channelId)
await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, secondUserChannelId)
await waitJobs(servers)
})
@ -204,6 +237,37 @@ describe('Test video change ownership - nominal', function () {
}
})
it('Should send a request to change ownership of a live', async function () {
this.timeout(15000)
await changeVideoOwnership(servers[0].url, firstUserAccessToken, liveId, secondUser.username)
const resSecondUser = await getVideoChangeOwnershipList(servers[0].url, secondUserAccessToken)
expect(resSecondUser.body.total).to.equal(3)
expect(resSecondUser.body.data.length).to.equal(3)
lastRequestChangeOwnershipId = resSecondUser.body.data[0].id
})
it('Should accept a live ownership change', async function () {
this.timeout(20000)
await acceptChangeOwnership(servers[0].url, secondUserAccessToken, lastRequestChangeOwnershipId, secondUserChannelId)
await waitJobs(servers)
for (const server of servers) {
const res = await getVideo(server.url, servers[0].video.uuid)
const video: VideoDetails = res.body
expect(video.name).to.equal('my super name')
expect(video.channel.displayName).to.equal('Main second channel')
expect(video.channel.name).to.equal('second_channel')
}
})
after(async function () {
await cleanupTests(servers)
})