Fix getting live by anonymous user

pull/4935/head
Chocobozzz 2022-04-22 09:50:20 +02:00
parent 4ec52d04dc
commit 961cbe4269
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
7 changed files with 89 additions and 56 deletions

View File

@ -10,11 +10,11 @@ import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator
import { VideoLiveModel } from '@server/models/video/video-live' import { VideoLiveModel } from '@server/models/video/video-live'
import { MVideoDetails, MVideoFullLight } from '@server/types/models' import { MVideoDetails, MVideoFullLight } from '@server/types/models'
import { buildUUID, uuidToShort } from '@shared/extra-utils' import { buildUUID, uuidToShort } from '@shared/extra-utils'
import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, VideoState } from '@shared/models' import { HttpStatusCode, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, UserRight, VideoState } from '@shared/models'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers/database' import { sequelizeTypescript } from '../../../initializers/database'
import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail'
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, optionalAuthenticate } from '../../../middlewares'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
const liveRouter = express.Router() const liveRouter = express.Router()
@ -29,7 +29,7 @@ liveRouter.post('/live',
) )
liveRouter.get('/live/:videoId', liveRouter.get('/live/:videoId',
authenticate, optionalAuthenticate,
asyncMiddleware(videoLiveGetValidator), asyncMiddleware(videoLiveGetValidator),
getLiveVideo getLiveVideo
) )
@ -52,7 +52,17 @@ export {
function getLiveVideo (req: express.Request, res: express.Response) { function getLiveVideo (req: express.Request, res: express.Response) {
const videoLive = res.locals.videoLive const videoLive = res.locals.videoLive
return res.json(videoLive.toFormattedJSON()) return res.json(videoLive.toFormattedJSON(canSeePrivateLiveInformation(res)))
}
function canSeePrivateLiveInformation (res: express.Response) {
const user = res.locals.oauth?.token.User
if (!user) return false
if (user.hasRight(UserRight.GET_ANY_LIVE)) return true
const video = res.locals.videoAll
return video.VideoChannel.Account.userId === user.id
} }
async function updateLiveVideo (req: express.Request, res: express.Response) { async function updateLiveVideo (req: express.Request, res: express.Response) {

View File

@ -33,15 +33,11 @@ const videoLiveGetValidator = [
isValidVideoIdParam('videoId'), isValidVideoIdParam('videoId'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params, user: res.locals.oauth.token.User.username }) logger.debug('Checking videoLiveGetValidator parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'all')) return if (!await doesVideoExist(req.params.videoId, res, 'all')) return
// Check if the user who did the request is able to get the live info
const user = res.locals.oauth.token.User
if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.GET_ANY_LIVE, res, false)) return
const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id) const videoLive = await VideoLiveModel.loadByVideoId(res.locals.videoAll.id)
if (!videoLive) { if (!videoLive) {
return res.fail({ return res.fail({

View File

@ -101,21 +101,28 @@ export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>
return VideoLiveModel.findOne<MVideoLive>(query) return VideoLiveModel.findOne<MVideoLive>(query)
} }
toFormattedJSON (): LiveVideo { toFormattedJSON (canSeePrivateInformation: boolean): LiveVideo {
let rtmpUrl: string = null let privateInformation: Pick<LiveVideo, 'rtmpUrl' | 'rtmpsUrl' | 'streamKey'> | {} = {}
let rtmpsUrl: string = null
// If we don't have a stream key, it means this is a remote live so we don't specify the rtmp URL // If we don't have a stream key, it means this is a remote live so we don't specify the rtmp URL
if (this.streamKey) { // We also display these private information only to the live owne/moderators
if (CONFIG.LIVE.RTMP.ENABLED) rtmpUrl = WEBSERVER.RTMP_URL if (this.streamKey && canSeePrivateInformation === true) {
if (CONFIG.LIVE.RTMPS.ENABLED) rtmpsUrl = WEBSERVER.RTMPS_URL privateInformation = {
streamKey: this.streamKey,
rtmpUrl: CONFIG.LIVE.RTMP.ENABLED
? WEBSERVER.RTMP_URL
: null,
rtmpsUrl: CONFIG.LIVE.RTMPS.ENABLED
? WEBSERVER.RTMPS_URL
: null
}
} }
return { return {
rtmpUrl, ...privateInformation,
rtmpsUrl,
streamKey: this.streamKey,
permanentLive: this.permanentLive, permanentLive: this.permanentLive,
saveReplay: this.saveReplay, saveReplay: this.saveReplay,
latencyMode: this.latencyMode latencyMode: this.latencyMode

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import 'mocha' import 'mocha'
import { expect } from 'chai'
import { omit } from 'lodash' import { omit } from 'lodash'
import { buildAbsoluteFixturePath } from '@shared/core-utils' import { buildAbsoluteFixturePath } from '@shared/core-utils'
import { HttpStatusCode, LiveVideoLatencyMode, VideoCreateResult, VideoPrivacy } from '@shared/models' import { HttpStatusCode, LiveVideoLatencyMode, VideoCreateResult, VideoPrivacy } from '@shared/models'
@ -340,16 +341,33 @@ describe('Test video lives API validator', function () {
describe('When getting live information', function () { describe('When getting live information', function () {
it('Should fail without access token', async function () {
await command.get({ token: '', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
})
it('Should fail with a bad access token', async function () { it('Should fail with a bad access token', async function () {
await command.get({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) await command.get({ token: 'toto', videoId: video.id, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
}) })
it('Should fail with access token of another user', async function () { it('Should not display private information without access token', async function () {
await command.get({ token: userAccessToken, videoId: video.id, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) const live = await command.get({ token: '', videoId: video.id })
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
expect(live.latencyMode).to.exist
})
it('Should not display private information with token of another user', async function () {
const live = await command.get({ token: userAccessToken, videoId: video.id })
expect(live.rtmpUrl).to.not.exist
expect(live.streamKey).to.not.exist
expect(live.latencyMode).to.exist
})
it('Should display private information with appropriate token', async function () {
const live = await command.get({ videoId: video.id })
expect(live.rtmpUrl).to.exist
expect(live.streamKey).to.exist
expect(live.latencyMode).to.exist
}) })
it('Should fail with a bad video id', async function () { it('Should fail with a bad video id', async function () {

View File

@ -1,47 +1,47 @@
export const enum UserRight { export const enum UserRight {
ALL, ALL = 0,
MANAGE_USERS, MANAGE_USERS = 1,
MANAGE_SERVER_FOLLOW, MANAGE_SERVER_FOLLOW = 2,
MANAGE_LOGS, MANAGE_LOGS = 3,
MANAGE_DEBUG, MANAGE_DEBUG = 4,
MANAGE_SERVER_REDUNDANCY, MANAGE_SERVER_REDUNDANCY = 5,
MANAGE_ABUSES, MANAGE_ABUSES = 6,
MANAGE_JOBS, MANAGE_JOBS = 7,
MANAGE_CONFIGURATION, MANAGE_CONFIGURATION = 8,
MANAGE_INSTANCE_CUSTOM_PAGE, MANAGE_INSTANCE_CUSTOM_PAGE = 9,
MANAGE_ACCOUNTS_BLOCKLIST, MANAGE_ACCOUNTS_BLOCKLIST = 10,
MANAGE_SERVERS_BLOCKLIST, MANAGE_SERVERS_BLOCKLIST = 11,
MANAGE_VIDEO_BLACKLIST, MANAGE_VIDEO_BLACKLIST = 12,
MANAGE_ANY_VIDEO_CHANNEL, MANAGE_ANY_VIDEO_CHANNEL = 13,
REMOVE_ANY_VIDEO, REMOVE_ANY_VIDEO = 14,
REMOVE_ANY_VIDEO_PLAYLIST, REMOVE_ANY_VIDEO_PLAYLIST = 15,
REMOVE_ANY_VIDEO_COMMENT, REMOVE_ANY_VIDEO_COMMENT = 16,
UPDATE_ANY_VIDEO, UPDATE_ANY_VIDEO = 17,
UPDATE_ANY_VIDEO_PLAYLIST, UPDATE_ANY_VIDEO_PLAYLIST = 18,
GET_ANY_LIVE, GET_ANY_LIVE = 19,
SEE_ALL_VIDEOS, SEE_ALL_VIDEOS = 20,
SEE_ALL_COMMENTS, SEE_ALL_COMMENTS = 21,
CHANGE_VIDEO_OWNERSHIP, CHANGE_VIDEO_OWNERSHIP = 22,
MANAGE_PLUGINS, MANAGE_PLUGINS = 23,
MANAGE_VIDEOS_REDUNDANCIES, MANAGE_VIDEOS_REDUNDANCIES = 24,
MANAGE_VIDEO_FILES, MANAGE_VIDEO_FILES = 25,
RUN_VIDEO_TRANSCODING, RUN_VIDEO_TRANSCODING = 26,
MANAGE_VIDEO_IMPORTS MANAGE_VIDEO_IMPORTS = 27
} }

View File

@ -1,10 +1,10 @@
import { LiveVideoLatencyMode } from './live-video-latency-mode.enum' import { LiveVideoLatencyMode } from './live-video-latency-mode.enum'
export interface LiveVideo { export interface LiveVideo {
rtmpUrl: string // If owner
rtmpsUrl: string rtmpUrl?: string
rtmpsUrl?: string
streamKey: string streamKey?: string
saveReplay: boolean saveReplay: boolean
permanentLive: boolean permanentLive: boolean

View File

@ -7657,11 +7657,13 @@ components:
properties: properties:
rtmpUrl: rtmpUrl:
type: string type: string
description: Included in the response if an appropriate token is provided
rtmpsUrl: rtmpsUrl:
type: string type: string
description: Included in the response if an appropriate token is provided
streamKey: streamKey:
type: string type: string
description: RTMP stream key to use to stream into this live video description: RTMP stream key to use to stream into this live video. Included in the response if an appropriate token is provided
saveReplay: saveReplay:
type: boolean type: boolean
permanentLive: permanentLive: