aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-03-23 17:18:18 +0100
committerChocobozzz <me@florianbigard.com>2021-03-24 18:18:41 +0100
commiteebd9838f067369031af9770b899f75f30810549 (patch)
treeb7ce9668e6a653a097b6790ac944eaa1d78d2032
parent4bc45da342597fb49593fc14c40f8dc5a97bb64e (diff)
downloadPeerTube-eebd9838f067369031af9770b899f75f30810549.tar.gz
PeerTube-eebd9838f067369031af9770b899f75f30810549.tar.zst
PeerTube-eebd9838f067369031af9770b899f75f30810549.zip
Add filter hook to forbid embed access
-rw-r--r--server/controllers/client.ts28
-rw-r--r--server/controllers/download.ts2
-rw-r--r--server/middlewares/validators/videos/video-comments.ts2
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js20
-rw-r--r--server/tests/plugins/filter-hooks.ts54
-rw-r--r--shared/models/plugins/server-hook.model.ts6
6 files changed, 107 insertions, 5 deletions
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 557cbfdfb..022a17ff4 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -2,7 +2,9 @@ import * as express from 'express'
2import { constants, promises as fs } from 'fs' 2import { constants, promises as fs } from 'fs'
3import { readFile } from 'fs-extra' 3import { readFile } from 'fs-extra'
4import { join } from 'path' 4import { join } from 'path'
5import { logger } from '@server/helpers/logger'
5import { CONFIG } from '@server/initializers/config' 6import { CONFIG } from '@server/initializers/config'
7import { Hooks } from '@server/lib/plugins/hooks'
6import { HttpStatusCode } from '@shared/core-utils' 8import { HttpStatusCode } from '@shared/core-utils'
7import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' 9import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
8import { root } from '../helpers/core-utils' 10import { root } from '../helpers/core-utils'
@@ -27,6 +29,7 @@ const embedMiddlewares = [
27 ? embedCSP 29 ? embedCSP
28 : (req: express.Request, res: express.Response, next: express.NextFunction) => next(), 30 : (req: express.Request, res: express.Response, next: express.NextFunction) => next(),
29 31
32 // Set headers
30 (req: express.Request, res: express.Response, next: express.NextFunction) => { 33 (req: express.Request, res: express.Response, next: express.NextFunction) => {
31 res.removeHeader('X-Frame-Options') 34 res.removeHeader('X-Frame-Options')
32 35
@@ -105,6 +108,24 @@ function serveServerTranslations (req: express.Request, res: express.Response) {
105} 108}
106 109
107async function generateEmbedHtmlPage (req: express.Request, res: express.Response) { 110async function generateEmbedHtmlPage (req: express.Request, res: express.Response) {
111 const hookName = req.originalUrl.startsWith('/video-playlists/')
112 ? 'filter:html.embed.video-playlist.allowed.result'
113 : 'filter:html.embed.video.allowed.result'
114
115 const allowParameters = { req }
116
117 const allowedResult = await Hooks.wrapFun(
118 isEmbedAllowed,
119 allowParameters,
120 hookName
121 )
122
123 if (!allowedResult || allowedResult.allowed !== true) {
124 logger.info('Embed is not allowed.', { allowedResult })
125
126 return sendHTML(allowedResult?.html || '', res)
127 }
128
108 const html = await ClientHtml.getEmbedHTML() 129 const html = await ClientHtml.getEmbedHTML()
109 130
110 return sendHTML(html, res) 131 return sendHTML(html, res)
@@ -158,3 +179,10 @@ function serveClientOverride (path: string) {
158 } 179 }
159 } 180 }
160} 181}
182
183type AllowedResult = { allowed: boolean, html?: string }
184function isEmbedAllowed (_object: {
185 req: express.Request
186}): AllowedResult {
187 return { allowed: true }
188}
diff --git a/server/controllers/download.ts b/server/controllers/download.ts
index fd44f10e9..9a8194c5c 100644
--- a/server/controllers/download.ts
+++ b/server/controllers/download.ts
@@ -132,7 +132,7 @@ function checkAllowResult (res: express.Response, allowParameters: any, result?:
132 if (!result || result.allowed !== true) { 132 if (!result || result.allowed !== true) {
133 logger.info('Download is not allowed.', { result, allowParameters }) 133 logger.info('Download is not allowed.', { result, allowParameters })
134 res.status(HttpStatusCode.FORBIDDEN_403) 134 res.status(HttpStatusCode.FORBIDDEN_403)
135 .json({ error: result.errorMessage || 'Refused download' }) 135 .json({ error: result?.errorMessage || 'Refused download' })
136 136
137 return false 137 return false
138 } 138 }
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 226c9d436..1afacfed8 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -216,7 +216,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
216 if (!acceptedResult || acceptedResult.accepted !== true) { 216 if (!acceptedResult || acceptedResult.accepted !== true) {
217 logger.info('Refused local comment.', { acceptedResult, acceptParameters }) 217 logger.info('Refused local comment.', { acceptedResult, acceptParameters })
218 res.status(HttpStatusCode.FORBIDDEN_403) 218 res.status(HttpStatusCode.FORBIDDEN_403)
219 .json({ error: acceptedResult.errorMessage || 'Refused local comment' }) 219 .json({ error: acceptedResult?.errorMessage || 'Refused local comment' })
220 220
221 return false 221 return false
222 } 222 }
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index 9913d0846..dfcc874d4 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -210,6 +210,26 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
210 return result 210 return result
211 } 211 }
212 }) 212 })
213
214 registerHook({
215 target: 'filter:html.embed.video.allowed.result',
216 handler: (result, params) => {
217 return {
218 allowed: false,
219 html: 'Lu Bu'
220 }
221 }
222 })
223
224 registerHook({
225 target: 'filter:html.embed.video-playlist.allowed.result',
226 handler: (result, params) => {
227 return {
228 allowed: false,
229 html: 'Diao Chan'
230 }
231 }
232 })
213} 233}
214 234
215async function unregister () { 235async function unregister () {
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 6996ae788..be47b20ba 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -3,10 +3,12 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { ServerConfig } from '@shared/models' 5import { ServerConfig } from '@shared/models'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
6import { 7import {
7 addVideoCommentReply, 8 addVideoCommentReply,
8 addVideoCommentThread, 9 addVideoCommentThread,
9 createLive, 10 createLive,
11 createVideoPlaylist,
10 doubleFollow, 12 doubleFollow,
11 getAccountVideos, 13 getAccountVideos,
12 getConfig, 14 getConfig,
@@ -15,6 +17,7 @@ import {
15 getVideo, 17 getVideo,
16 getVideoChannelVideos, 18 getVideoChannelVideos,
17 getVideoCommentThreads, 19 getVideoCommentThreads,
20 getVideoPlaylist,
18 getVideosList, 21 getVideosList,
19 getVideosListPagination, 22 getVideosListPagination,
20 getVideoThreadComments, 23 getVideoThreadComments,
@@ -32,9 +35,15 @@ import {
32} from '../../../shared/extra-utils' 35} from '../../../shared/extra-utils'
33import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers' 36import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
34import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' 37import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports'
35import { VideoDetails, VideoImport, VideoImportState, VideoPrivacy } from '../../../shared/models/videos' 38import {
39 VideoDetails,
40 VideoImport,
41 VideoImportState,
42 VideoPlaylist,
43 VideoPlaylistPrivacy,
44 VideoPrivacy
45} from '../../../shared/models/videos'
36import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' 46import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
37import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
38 47
39const expect = chai.expect 48const expect = chai.expect
40 49
@@ -418,6 +427,47 @@ describe('Test plugin filter hooks', function () {
418 }) 427 })
419 }) 428 })
420 429
430 describe('Embed filters', function () {
431 const embedVideos: VideoDetails[] = []
432 const embedPlaylists: VideoPlaylist[] = []
433
434 before(async function () {
435 this.timeout(60000)
436
437 await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
438 transcoding: {
439 enabled: false
440 }
441 })
442
443 for (const name of [ 'bad embed', 'good embed' ]) {
444 {
445 const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid
446 const res = await getVideo(servers[0].url, uuid)
447 embedVideos.push(res.body)
448 }
449
450 {
451 const playlistAttrs = { displayName: name, videoChannelId: servers[0].videoChannel.id, privacy: VideoPlaylistPrivacy.PUBLIC }
452 const res = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
453
454 const resPlaylist = await getVideoPlaylist(servers[0].url, res.body.videoPlaylist.id)
455 embedPlaylists.push(resPlaylist.body)
456 }
457 }
458 })
459
460 it('Should run filter:html.embed.video.allowed.result', async function () {
461 const res = await makeRawRequest(servers[0].url + embedVideos[0].embedPath, 200)
462 expect(res.text).to.equal('Lu Bu')
463 })
464
465 it('Should run filter:html.embed.video-playlist.allowed.result', async function () {
466 const res = await makeRawRequest(servers[0].url + embedPlaylists[0].embedPath, 200)
467 expect(res.text).to.equal('Diao Chan')
468 })
469 })
470
421 after(async function () { 471 after(async function () {
422 await cleanupTests(servers) 472 await cleanupTests(servers)
423 }) 473 })
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts
index 1f7806d0d..d28f76dfe 100644
--- a/shared/models/plugins/server-hook.model.ts
+++ b/shared/models/plugins/server-hook.model.ts
@@ -54,7 +54,11 @@ export const serverFilterHookObject = {
54 54
55 // Filter result used to check if video/torrent download is allowed 55 // Filter result used to check if video/torrent download is allowed
56 'filter:api.download.video.allowed.result': true, 56 'filter:api.download.video.allowed.result': true,
57 'filter:api.download.torrent.allowed.result': true 57 'filter:api.download.torrent.allowed.result': true,
58
59 // Filter result to check if the embed is allowed for a particular request
60 'filter:html.embed.video.allowed.result': true,
61 'filter:html.embed.video-playlist.allowed.result': true
58} 62}
59 63
60export type ServerFilterHookName = keyof typeof serverFilterHookObject 64export type ServerFilterHookName = keyof typeof serverFilterHookObject