aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--config/default.yaml2
-rw-r--r--server/controllers/api/videos/live.ts3
-rw-r--r--server/lib/live-manager.ts2
-rw-r--r--server/lib/moderation.ts11
-rw-r--r--server/middlewares/validators/videos/video-live.ts30
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js25
-rw-r--r--server/tests/plugins/action-hooks.ts43
-rw-r--r--server/tests/plugins/filter-hooks.ts23
-rw-r--r--shared/models/plugins/server-hook.model.ts6
9 files changed, 123 insertions, 22 deletions
diff --git a/config/default.yaml b/config/default.yaml
index af16f081f..120f03d5d 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -262,7 +262,7 @@ live:
262 # PeerTube will transcode segments in a video file 262 # PeerTube will transcode segments in a video file
263 # If the user daily/total quota is reached, PeerTube will stop the live 263 # If the user daily/total quota is reached, PeerTube will stop the live
264 # /!\ transcoding.enabled (and not live.transcoding.enabled) has to be true to create a replay 264 # /!\ transcoding.enabled (and not live.transcoding.enabled) has to be true to create a replay
265 allow_replay: true 265 allow_replay: false
266 266
267 rtmp: 267 rtmp:
268 port: 1935 268 port: 1935
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index f980c7730..d438b6f3a 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -5,6 +5,7 @@ import { CONFIG } from '@server/initializers/config'
5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' 5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants'
6import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' 6import { getVideoActivityPubUrl } from '@server/lib/activitypub/url'
7import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' 7import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
8import { Hooks } from '@server/lib/plugins/hooks'
8import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' 9import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
9import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live' 10import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live'
10import { VideoLiveModel } from '@server/models/video/video-live' 11import { VideoLiveModel } from '@server/models/video/video-live'
@@ -128,6 +129,8 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
128 return { videoCreated } 129 return { videoCreated }
129 }) 130 })
130 131
132 Hooks.runAction('action:api.live-video.created', { video: videoCreated })
133
131 return res.json({ 134 return res.json({
132 video: { 135 video: {
133 id: videoCreated.id, 136 id: videoCreated.id,
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts
index 9a2914cc5..ef9377e43 100644
--- a/server/lib/live-manager.ts
+++ b/server/lib/live-manager.ts
@@ -118,7 +118,7 @@ class LiveManager {
118 } 118 }
119 119
120 run () { 120 run () {
121 logger.info('Running RTMP server.') 121 logger.info('Running RTMP server on port %d', config.rtmp.port)
122 122
123 this.rtmpServer = new NodeRtmpServer(config) 123 this.rtmpServer = new NodeRtmpServer(config)
124 this.rtmpServer.run() 124 this.rtmpServer.run()
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index 0ef26d53d..0ace2d021 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -20,7 +20,7 @@ import {
20import { ActivityCreate } from '../../shared/models/activitypub' 20import { ActivityCreate } from '../../shared/models/activitypub'
21import { VideoObject } from '../../shared/models/activitypub/objects' 21import { VideoObject } from '../../shared/models/activitypub/objects'
22import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' 22import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object'
23import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' 23import { LiveVideoCreate, VideoCreate, VideoImportCreate } from '../../shared/models/videos'
24import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' 24import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model'
25import { UserModel } from '../models/account/user' 25import { UserModel } from '../models/account/user'
26import { ActorModel } from '../models/activitypub/actor' 26import { ActorModel } from '../models/activitypub/actor'
@@ -43,6 +43,13 @@ function isLocalVideoAccepted (object: {
43 return { accepted: true } 43 return { accepted: true }
44} 44}
45 45
46function isLocalLiveVideoAccepted (object: {
47 liveVideoBody: LiveVideoCreate
48 user: UserModel
49}): AcceptResult {
50 return { accepted: true }
51}
52
46function isLocalVideoThreadAccepted (_object: { 53function isLocalVideoThreadAccepted (_object: {
47 commentBody: VideoCommentCreate 54 commentBody: VideoCommentCreate
48 video: VideoModel 55 video: VideoModel
@@ -175,6 +182,8 @@ function createAccountAbuse (options: {
175} 182}
176 183
177export { 184export {
185 isLocalLiveVideoAccepted,
186
178 isLocalVideoAccepted, 187 isLocalVideoAccepted,
179 isLocalVideoThreadAccepted, 188 isLocalVideoThreadAccepted,
180 isRemoteVideoAccepted, 189 isRemoteVideoAccepted,
diff --git a/server/middlewares/validators/videos/video-live.ts b/server/middlewares/validators/videos/video-live.ts
index cbc48fe93..ff92db910 100644
--- a/server/middlewares/validators/videos/video-live.ts
+++ b/server/middlewares/validators/videos/video-live.ts
@@ -11,6 +11,8 @@ import { CONFIG } from '../../../initializers/config'
11import { areValidationErrors } from '../utils' 11import { areValidationErrors } from '../utils'
12import { getCommonVideoEditAttributes } from './videos' 12import { getCommonVideoEditAttributes } from './videos'
13import { VideoModel } from '@server/models/video/video' 13import { VideoModel } from '@server/models/video/video'
14import { Hooks } from '@server/lib/plugins/hooks'
15import { isLocalLiveVideoAccepted } from '@server/lib/moderation'
14 16
15const videoLiveGetValidator = [ 17const videoLiveGetValidator = [
16 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 18 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -97,6 +99,8 @@ const videoLiveAddValidator = getCommonVideoEditAttributes().concat([
97 } 99 }
98 } 100 }
99 101
102 if (!await isLiveVideoAccepted(req, res)) return cleanUpReqFiles(req)
103
100 return next() 104 return next()
101 } 105 }
102]) 106])
@@ -137,3 +141,29 @@ export {
137 videoLiveUpdateValidator, 141 videoLiveUpdateValidator,
138 videoLiveGetValidator 142 videoLiveGetValidator
139} 143}
144
145// ---------------------------------------------------------------------------
146
147async function isLiveVideoAccepted (req: express.Request, res: express.Response) {
148 // Check we accept this video
149 const acceptParameters = {
150 liveVideoBody: req.body,
151 user: res.locals.oauth.token.User
152 }
153 const acceptedResult = await Hooks.wrapFun(
154 isLocalLiveVideoAccepted,
155 acceptParameters,
156 'filter:api.live-video.create.accept.result'
157 )
158
159 if (!acceptedResult || acceptedResult.accepted !== true) {
160 logger.info('Refused local live video.', { acceptedResult, acceptParameters })
161
162 res.status(403)
163 .json({ error: acceptedResult.errorMessage || 'Refused local live video' })
164
165 return false
166 }
167
168 return true
169}
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index a45e98fb5..322c0610c 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -7,6 +7,8 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
7 'action:api.video.uploaded', 7 'action:api.video.uploaded',
8 'action:api.video.viewed', 8 'action:api.video.viewed',
9 9
10 'action:api.live-video.created',
11
10 'action:api.video-thread.created', 12 'action:api.video-thread.created',
11 'action:api.video-comment-reply.created', 13 'action:api.video-comment-reply.created',
12 'action:api.video-comment.deleted', 14 'action:api.video-comment.deleted',
@@ -46,15 +48,22 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
46 } 48 }
47 }) 49 })
48 50
49 registerHook({ 51 for (const hook of [ 'filter:api.video.upload.accept.result', 'filter:api.live-video.create.accept.result' ]) {
50 target: 'filter:api.video.upload.accept.result', 52 registerHook({
51 handler: ({ accepted }, { videoBody }) => { 53 target: hook,
52 if (!accepted) return { accepted: false } 54 handler: ({ accepted }, { videoBody, liveVideoBody }) => {
53 if (videoBody.name.indexOf('bad word') !== -1) return { accepted: false, errorMessage: 'bad word' } 55 if (!accepted) return { accepted: false }
54 56
55 return { accepted: true } 57 const name = videoBody
56 } 58 ? videoBody.name
57 }) 59 : liveVideoBody.name
60
61 if (name.indexOf('bad word') !== -1) return { accepted: false, errorMessage: 'bad word' }
62
63 return { accepted: true }
64 }
65 })
66 }
58 67
59 registerHook({ 68 registerHook({
60 target: 'filter:api.video.pre-import-url.accept.result', 69 target: 'filter:api.video.pre-import-url.accept.result',
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index ca57a4b51..ac9f2cea5 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -1,18 +1,12 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import { ServerHookName, VideoPrivacy } from '@shared/models'
5 cleanupTests,
6 flushAndRunMultipleServers,
7 killallServers,
8 reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import { 5import {
13 addVideoCommentReply, 6 addVideoCommentReply,
14 addVideoCommentThread, 7 addVideoCommentThread,
15 blockUser, 8 blockUser,
9 createLive,
16 createUser, 10 createUser,
17 deleteVideoComment, 11 deleteVideoComment,
18 getPluginTestPath, 12 getPluginTestPath,
@@ -20,6 +14,7 @@ import {
20 registerUser, 14 registerUser,
21 removeUser, 15 removeUser,
22 setAccessTokensToServers, 16 setAccessTokensToServers,
17 setDefaultVideoChannel,
23 unblockUser, 18 unblockUser,
24 updateUser, 19 updateUser,
25 updateVideo, 20 updateVideo,
@@ -27,13 +22,21 @@ import {
27 userLogin, 22 userLogin,
28 viewVideo 23 viewVideo
29} from '../../../shared/extra-utils' 24} from '../../../shared/extra-utils'
25import {
26 cleanupTests,
27 flushAndRunMultipleServers,
28 killallServers,
29 reRunServer,
30 ServerInfo,
31 waitUntilLog
32} from '../../../shared/extra-utils/server/servers'
30 33
31describe('Test plugin action hooks', function () { 34describe('Test plugin action hooks', function () {
32 let servers: ServerInfo[] 35 let servers: ServerInfo[]
33 let videoUUID: string 36 let videoUUID: string
34 let threadId: number 37 let threadId: number
35 38
36 function checkHook (hook: string) { 39 function checkHook (hook: ServerHookName) {
37 return waitUntilLog(servers[0], 'Run hook ' + hook) 40 return waitUntilLog(servers[0], 'Run hook ' + hook)
38 } 41 }
39 42
@@ -42,6 +45,7 @@ describe('Test plugin action hooks', function () {
42 45
43 servers = await flushAndRunMultipleServers(2) 46 servers = await flushAndRunMultipleServers(2)
44 await setAccessTokensToServers(servers) 47 await setAccessTokensToServers(servers)
48 await setDefaultVideoChannel(servers)
45 49
46 await installPlugin({ 50 await installPlugin({
47 url: servers[0].url, 51 url: servers[0].url,
@@ -51,7 +55,11 @@ describe('Test plugin action hooks', function () {
51 55
52 killallServers([ servers[0] ]) 56 killallServers([ servers[0] ])
53 57
54 await reRunServer(servers[0]) 58 await reRunServer(servers[0], {
59 live: {
60 enabled: true
61 }
62 })
55 }) 63 })
56 64
57 describe('Application hooks', function () { 65 describe('Application hooks', function () {
@@ -81,6 +89,21 @@ describe('Test plugin action hooks', function () {
81 }) 89 })
82 }) 90 })
83 91
92 describe('Live hooks', function () {
93
94 it('Should run action:api.live-video.created', async function () {
95 const attributes = {
96 name: 'live',
97 privacy: VideoPrivacy.PUBLIC,
98 channelId: servers[0].videoChannel.id
99 }
100
101 await createLive(servers[0].url, servers[0].accessToken, attributes)
102
103 await checkHook('action:api.live-video.created')
104 })
105 })
106
84 describe('Comments hooks', function () { 107 describe('Comments hooks', function () {
85 it('Should run action:api.video-thread.created', async function () { 108 it('Should run action:api.video-thread.created', async function () {
86 const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread') 109 const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread')
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 4d354b68e..9939b8e6e 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -6,6 +6,7 @@ import { ServerConfig } from '@shared/models'
6import { 6import {
7 addVideoCommentReply, 7 addVideoCommentReply,
8 addVideoCommentThread, 8 addVideoCommentThread,
9 createLive,
9 doubleFollow, 10 doubleFollow,
10 getConfig, 11 getConfig,
11 getPluginTestPath, 12 getPluginTestPath,
@@ -19,6 +20,7 @@ import {
19 registerUser, 20 registerUser,
20 setAccessTokensToServers, 21 setAccessTokensToServers,
21 setDefaultVideoChannel, 22 setDefaultVideoChannel,
23 updateCustomSubConfig,
22 updateVideo, 24 updateVideo,
23 uploadVideo, 25 uploadVideo,
24 waitJobs 26 waitJobs
@@ -61,6 +63,17 @@ describe('Test plugin filter hooks', function () {
61 63
62 const res = await getVideosList(servers[0].url) 64 const res = await getVideosList(servers[0].url)
63 videoUUID = res.body.data[0].uuid 65 videoUUID = res.body.data[0].uuid
66
67 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
68 live: { enabled: true },
69 signup: { enabled: true },
70 import: {
71 videos: {
72 http: { enabled: true },
73 torrent: { enabled: true }
74 }
75 }
76 })
64 }) 77 })
65 78
66 it('Should run filter:api.videos.list.params', async function () { 79 it('Should run filter:api.videos.list.params', async function () {
@@ -87,6 +100,16 @@ describe('Test plugin filter hooks', function () {
87 await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video with bad word' }, 403) 100 await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video with bad word' }, 403)
88 }) 101 })
89 102
103 it('Should run filter:api.live-video.create.accept.result', async function () {
104 const attributes = {
105 name: 'video with bad word',
106 privacy: VideoPrivacy.PUBLIC,
107 channelId: servers[0].videoChannel.id
108 }
109
110 await createLive(servers[0].url, servers[0].accessToken, attributes, 403)
111 })
112
90 it('Should run filter:api.video.pre-import-url.accept.result', async function () { 113 it('Should run filter:api.video.pre-import-url.accept.result', async function () {
91 const baseAttributes = { 114 const baseAttributes = {
92 name: 'normal title', 115 name: 'normal title',
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts
index 5f812904f..6609bc893 100644
--- a/shared/models/plugins/server-hook.model.ts
+++ b/shared/models/plugins/server-hook.model.ts
@@ -9,9 +9,10 @@ export const serverFilterHookObject = {
9 // Used to get detailed video information (video watch page for example) 9 // Used to get detailed video information (video watch page for example)
10 'filter:api.video.get.result': true, 10 'filter:api.video.get.result': true,
11 11
12 // Filter the result of the accept upload, import via torrent or url functions 12 // Filter the result of the accept upload/live, import via torrent/url functions
13 // If this function returns false then the upload is aborted with an error 13 // If this function returns false then the upload is aborted with an error
14 'filter:api.video.upload.accept.result': true, 14 'filter:api.video.upload.accept.result': true,
15 'filter:api.live-video.create.accept.result': true,
15 'filter:api.video.pre-import-url.accept.result': true, 16 'filter:api.video.pre-import-url.accept.result': true,
16 'filter:api.video.pre-import-torrent.accept.result': true, 17 'filter:api.video.pre-import-torrent.accept.result': true,
17 'filter:api.video.post-import-url.accept.result': true, 18 'filter:api.video.post-import-url.accept.result': true,
@@ -54,6 +55,9 @@ export const serverActionHookObject = {
54 // Fired when a local video is viewed 55 // Fired when a local video is viewed
55 'action:api.video.viewed': true, 56 'action:api.video.viewed': true,
56 57
58 // Fired when a live video is created
59 'action:api.live-video.created': true,
60
57 // Fired when a thread is created 61 // Fired when a thread is created
58 'action:api.video-thread.created': true, 62 'action:api.video-thread.created': true,
59 // Fired when a reply to a thread is created 63 // Fired when a reply to a thread is created