Give moderators access to edit channels (#4608)

* give admins access to edit all channels

closes #4598

* test(channels): +admin update another users channel

* Fix tests

* fix(server): delete another users channel

Since the channel owner isn't necessary the auth user we need to check
the right account whether it's the last video or not.

* REMOVE_ANY_VIDEO_CHANNEL > MANAGE_ANY_VIDEO_CHANNEL

Merge REMOVE_ANY_VIDEO_CHANNEL and MANY_VIDEO_CHANNELS to
MANAGE_ANY_VIDEO_CHANNEL.

* user-right: moderator can't manage admins channel

* client: MyVideoChannelCreateComponent > VideoChannelCreateComponent

* client: MyVideoChannelEdit > VideoChannelEdit

* Revert "user-right: moderator can't manage admins channel"

This reverts commit 2c627c154e.

* server: clean dupl validator functionality

* fix ensureUserCanManageChannel usage

It's not async anymore.

* server: merge channel validator middleares

ensureAuthUserOwnsChannelValidator & ensureUserCanManageChannel gets
merged into one middleware.

* client(VideoChannelEdit): redirect to prev route

* fix(VideoChannels): handle anon users

* client: new routes for create/update channel

* Refactor channel validators

Co-authored-by: Chocobozzz <me@florianbigard.com>
This commit is contained in:
kontrollanten
2021-12-13 15:29:13 +01:00
committed by GitHub
co-authored by Chocobozzz
parent 11e520b50d
commit a37e9e74ff
27 changed files with 316 additions and 246 deletions
@@ -3,12 +3,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel'
import { MChannelBannerAccountDefault } from '@server/types/models'
import { HttpStatusCode } from '@shared/models'
async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
return processVideoChannelExist(videoChannel, res)
}
async function doesVideoChannelIdExist (id: number, res: express.Response) {
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
@@ -24,7 +18,6 @@ async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: e
// ---------------------------------------------------------------------------
export {
doesLocalVideoChannelNameExist,
doesVideoChannelIdExist,
doesVideoChannelNameWithHostExist
}
+10 -7
View File
@@ -3,7 +3,7 @@ import { body, param, query } from 'express-validator'
import { omit } from 'lodash'
import { Hooks } from '@server/lib/plugins/hooks'
import { MUserDefault } from '@server/types/models'
import { HttpStatusCode, UserRegister, UserRole } from '@shared/models'
import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
import {
@@ -490,14 +490,17 @@ const ensureAuthUserOwnsAccountValidator = [
}
]
const ensureAuthUserOwnsChannelValidator = [
const ensureCanManageChannel = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
const user = res.locals.oauth.token.User
const user = res.locals.oauth.token.user
const isUserOwner = res.locals.videoChannel.Account.userId === user.id
if (!isUserOwner && user.hasRight(UserRight.MANAGE_ANY_VIDEO_CHANNEL) === false) {
const message = `User ${user.username} does not have right to manage channel ${req.params.nameWithHost}.`
if (res.locals.videoChannel.Account.userId !== user.id) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Only owner of this video channel can access this ressource'
message
})
}
@@ -542,8 +545,8 @@ export {
usersVerifyEmailValidator,
userAutocompleteValidator,
ensureAuthUserOwnsAccountValidator,
ensureAuthUserOwnsChannelValidator,
ensureCanManageUser
ensureCanManageUser,
ensureCanManageChannel
}
// ---------------------------------------------------------------------------
@@ -1,7 +1,7 @@
import express from 'express'
import { body, param, query } from 'express-validator'
import { MChannelAccountDefault, MUser } from '@server/types/models'
import { UserRight } from '../../../../shared'
import { CONFIG } from '@server/initializers/config'
import { MChannelAccountDefault } from '@server/types/models'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
import {
@@ -13,8 +13,7 @@ import {
import { logger } from '../../../helpers/logger'
import { ActorModel } from '../../../models/actor/actor'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared'
import { CONFIG } from '@server/initializers/config'
import { areValidationErrors, doesVideoChannelNameWithHostExist } from '../shared'
const videoChannelsAddValidator = [
body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'),
@@ -71,16 +70,10 @@ const videoChannelsUpdateValidator = [
]
const videoChannelsRemoveValidator = [
param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
if (!await checkVideoChannelIsNotTheLastOne(res)) return
if (!await checkVideoChannelIsNotTheLastOne(res.locals.videoChannel, res)) return
return next()
}
@@ -100,14 +93,14 @@ const videoChannelsNameWithHostValidator = [
}
]
const localVideoChannelValidator = [
param('name').custom(isVideoChannelDisplayNameValid).withMessage('Should have a valid video channel name'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await doesLocalVideoChannelNameExist(req.params.name, res)) return
const ensureIsLocalChannel = [
(req: express.Request, res: express.Response, next: express.NextFunction) => {
if (res.locals.videoChannel.Actor.isOwned() === false) {
return res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'This channel is not owned.'
})
}
return next()
}
@@ -144,38 +137,15 @@ export {
videoChannelsUpdateValidator,
videoChannelsRemoveValidator,
videoChannelsNameWithHostValidator,
ensureIsLocalChannel,
videoChannelsListValidator,
localVideoChannelValidator,
videoChannelStatsValidator
}
// ---------------------------------------------------------------------------
function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) {
if (videoChannel.Actor.isOwned() === false) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Cannot remove video channel of another server.'
})
return false
}
// Check if the user can delete the video channel
// The user can delete it if s/he is an admin
// Or if s/he is the video channel's account
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
res.fail({
status: HttpStatusCode.FORBIDDEN_403,
message: 'Cannot remove video channel of another user'
})
return false
}
return true
}
async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
async function checkVideoChannelIsNotTheLastOne (videoChannel: MChannelAccountDefault, res: express.Response) {
const count = await VideoChannelModel.countByAccount(videoChannel.Account.id)
if (count <= 1) {
res.fail({