diff options
author | Chocobozzz <me@florianbigard.com> | 2021-03-23 17:18:18 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-03-24 18:18:41 +0100 |
commit | eebd9838f067369031af9770b899f75f30810549 (patch) | |
tree | b7ce9668e6a653a097b6790ac944eaa1d78d2032 | |
parent | 4bc45da342597fb49593fc14c40f8dc5a97bb64e (diff) | |
download | PeerTube-eebd9838f067369031af9770b899f75f30810549.tar.gz PeerTube-eebd9838f067369031af9770b899f75f30810549.tar.zst PeerTube-eebd9838f067369031af9770b899f75f30810549.zip |
Add filter hook to forbid embed access
-rw-r--r-- | server/controllers/client.ts | 28 | ||||
-rw-r--r-- | server/controllers/download.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-comments.ts | 2 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test/main.js | 20 | ||||
-rw-r--r-- | server/tests/plugins/filter-hooks.ts | 54 | ||||
-rw-r--r-- | shared/models/plugins/server-hook.model.ts | 6 |
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' | |||
2 | import { constants, promises as fs } from 'fs' | 2 | import { constants, promises as fs } from 'fs' |
3 | import { readFile } from 'fs-extra' | 3 | import { readFile } from 'fs-extra' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import { logger } from '@server/helpers/logger' | ||
5 | import { CONFIG } from '@server/initializers/config' | 6 | import { CONFIG } from '@server/initializers/config' |
7 | import { Hooks } from '@server/lib/plugins/hooks' | ||
6 | import { HttpStatusCode } from '@shared/core-utils' | 8 | import { HttpStatusCode } from '@shared/core-utils' |
7 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' | 9 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' |
8 | import { root } from '../helpers/core-utils' | 10 | import { 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 | ||
107 | async function generateEmbedHtmlPage (req: express.Request, res: express.Response) { | 110 | async 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 | |||
183 | type AllowedResult = { allowed: boolean, html?: string } | ||
184 | function 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 | ||
215 | async function unregister () { | 235 | async 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 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { ServerConfig } from '@shared/models' | 5 | import { ServerConfig } from '@shared/models' |
6 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
6 | import { | 7 | import { |
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' |
33 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers' | 36 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers' |
34 | import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' | 37 | import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports' |
35 | import { VideoDetails, VideoImport, VideoImportState, VideoPrivacy } from '../../../shared/models/videos' | 38 | import { |
39 | VideoDetails, | ||
40 | VideoImport, | ||
41 | VideoImportState, | ||
42 | VideoPlaylist, | ||
43 | VideoPlaylistPrivacy, | ||
44 | VideoPrivacy | ||
45 | } from '../../../shared/models/videos' | ||
36 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | 46 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' |
37 | import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' | ||
38 | 47 | ||
39 | const expect = chai.expect | 48 | const 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 | ||
60 | export type ServerFilterHookName = keyof typeof serverFilterHookObject | 64 | export type ServerFilterHookName = keyof typeof serverFilterHookObject |