]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add filter hook to forbid embed access
authorChocobozzz <me@florianbigard.com>
Tue, 23 Mar 2021 16:18:18 +0000 (17:18 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 24 Mar 2021 17:18:41 +0000 (18:18 +0100)
server/controllers/client.ts
server/controllers/download.ts
server/middlewares/validators/videos/video-comments.ts
server/tests/fixtures/peertube-plugin-test/main.js
server/tests/plugins/filter-hooks.ts
shared/models/plugins/server-hook.model.ts

index 557cbfdfb278afde68b50b816138a2f6c4f58594..022a17ff47c8f7921bdbde3d11bf5f38ba43f040 100644 (file)
@@ -2,7 +2,9 @@ import * as express from 'express'
 import { constants, promises as fs } from 'fs'
 import { readFile } from 'fs-extra'
 import { join } from 'path'
+import { logger } from '@server/helpers/logger'
 import { CONFIG } from '@server/initializers/config'
+import { Hooks } from '@server/lib/plugins/hooks'
 import { HttpStatusCode } from '@shared/core-utils'
 import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
 import { root } from '../helpers/core-utils'
@@ -27,6 +29,7 @@ const embedMiddlewares = [
     ? embedCSP
     : (req: express.Request, res: express.Response, next: express.NextFunction) => next(),
 
+  // Set headers
   (req: express.Request, res: express.Response, next: express.NextFunction) => {
     res.removeHeader('X-Frame-Options')
 
@@ -105,6 +108,24 @@ function serveServerTranslations (req: express.Request, res: express.Response) {
 }
 
 async function generateEmbedHtmlPage (req: express.Request, res: express.Response) {
+  const hookName = req.originalUrl.startsWith('/video-playlists/')
+    ? 'filter:html.embed.video-playlist.allowed.result'
+    : 'filter:html.embed.video.allowed.result'
+
+  const allowParameters = { req }
+
+  const allowedResult = await Hooks.wrapFun(
+    isEmbedAllowed,
+    allowParameters,
+    hookName
+  )
+
+  if (!allowedResult || allowedResult.allowed !== true) {
+    logger.info('Embed is not allowed.', { allowedResult })
+
+    return sendHTML(allowedResult?.html || '', res)
+  }
+
   const html = await ClientHtml.getEmbedHTML()
 
   return sendHTML(html, res)
@@ -158,3 +179,10 @@ function serveClientOverride (path: string) {
     }
   }
 }
+
+type AllowedResult = { allowed: boolean, html?: string }
+function isEmbedAllowed (_object: {
+  req: express.Request
+}): AllowedResult {
+  return { allowed: true }
+}
index fd44f10e91c2e4fe3d2c8882cd246b3c531b32db..9a8194c5c4b3affe8083c64d326e68ab3f99e3f3 100644 (file)
@@ -132,7 +132,7 @@ function checkAllowResult (res: express.Response, allowParameters: any, result?:
   if (!result || result.allowed !== true) {
     logger.info('Download is not allowed.', { result, allowParameters })
     res.status(HttpStatusCode.FORBIDDEN_403)
-       .json({ error: result.errorMessage || 'Refused download' })
+       .json({ error: result?.errorMessage || 'Refused download' })
 
     return false
   }
index 226c9d43683a2857ea833d592b233a99d09d4906..1afacfed878e508391749815cfc2237c0e43bf89 100644 (file)
@@ -216,7 +216,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
   if (!acceptedResult || acceptedResult.accepted !== true) {
     logger.info('Refused local comment.', { acceptedResult, acceptParameters })
     res.status(HttpStatusCode.FORBIDDEN_403)
-       .json({ error: acceptedResult.errorMessage || 'Refused local comment' })
+       .json({ error: acceptedResult?.errorMessage || 'Refused local comment' })
 
     return false
   }
index 9913d0846dc18dc8e428b8bcc712628addfa95d6..dfcc874d41281585f5d23d58a666b4a568b7c370 100644 (file)
@@ -210,6 +210,26 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
       return result
     }
   })
+
+  registerHook({
+    target: 'filter:html.embed.video.allowed.result',
+    handler: (result, params) => {
+      return {
+        allowed: false,
+        html: 'Lu Bu'
+      }
+    }
+  })
+
+  registerHook({
+    target: 'filter:html.embed.video-playlist.allowed.result',
+    handler: (result, params) => {
+      return {
+        allowed: false,
+        html: 'Diao Chan'
+      }
+    }
+  })
 }
 
 async function unregister () {
index 6996ae7882fb61f220e8e5e57df3b799f85d00b1..be47b20bafc51e14b82856f0da778fc4d3f627df 100644 (file)
@@ -3,10 +3,12 @@
 import 'mocha'
 import * as chai from 'chai'
 import { ServerConfig } from '@shared/models'
+import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   createLive,
+  createVideoPlaylist,
   doubleFollow,
   getAccountVideos,
   getConfig,
@@ -15,6 +17,7 @@ import {
   getVideo,
   getVideoChannelVideos,
   getVideoCommentThreads,
+  getVideoPlaylist,
   getVideosList,
   getVideosListPagination,
   getVideoThreadComments,
@@ -32,9 +35,15 @@ import {
 } from '../../../shared/extra-utils'
 import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
 import { getGoodVideoUrl, getMyVideoImports, importVideo } from '../../../shared/extra-utils/videos/video-imports'
-import { VideoDetails, VideoImport, VideoImportState, VideoPrivacy } from '../../../shared/models/videos'
+import {
+  VideoDetails,
+  VideoImport,
+  VideoImportState,
+  VideoPlaylist,
+  VideoPlaylistPrivacy,
+  VideoPrivacy
+} from '../../../shared/models/videos'
 import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
-import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
 
 const expect = chai.expect
 
@@ -418,6 +427,47 @@ describe('Test plugin filter hooks', function () {
     })
   })
 
+  describe('Embed filters', function () {
+    const embedVideos: VideoDetails[] = []
+    const embedPlaylists: VideoPlaylist[] = []
+
+    before(async function () {
+      this.timeout(60000)
+
+      await updateCustomSubConfig(servers[0].url, servers[0].accessToken, {
+        transcoding: {
+          enabled: false
+        }
+      })
+
+      for (const name of [ 'bad embed', 'good embed' ]) {
+        {
+          const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: name })).uuid
+          const res = await getVideo(servers[0].url, uuid)
+          embedVideos.push(res.body)
+        }
+
+        {
+          const playlistAttrs = { displayName: name, videoChannelId: servers[0].videoChannel.id, privacy: VideoPlaylistPrivacy.PUBLIC }
+          const res = await createVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistAttrs })
+
+          const resPlaylist = await getVideoPlaylist(servers[0].url, res.body.videoPlaylist.id)
+          embedPlaylists.push(resPlaylist.body)
+        }
+      }
+    })
+
+    it('Should run filter:html.embed.video.allowed.result', async function () {
+      const res = await makeRawRequest(servers[0].url + embedVideos[0].embedPath, 200)
+      expect(res.text).to.equal('Lu Bu')
+    })
+
+    it('Should run filter:html.embed.video-playlist.allowed.result', async function () {
+      const res = await makeRawRequest(servers[0].url + embedPlaylists[0].embedPath, 200)
+      expect(res.text).to.equal('Diao Chan')
+    })
+  })
+
   after(async function () {
     await cleanupTests(servers)
   })
index 1f7806d0db645c2ecc5b874f7d0e9b9eb18119b5..d28f76dfe9e4c024ddd3b246c796c25501fbd0fa 100644 (file)
@@ -54,7 +54,11 @@ export const serverFilterHookObject = {
 
   // Filter result used to check if video/torrent download is allowed
   'filter:api.download.video.allowed.result': true,
-  'filter:api.download.torrent.allowed.result': true
+  'filter:api.download.torrent.allowed.result': true,
+
+  // Filter result to check if the embed is allowed for a particular request
+  'filter:html.embed.video.allowed.result': true,
+  'filter:html.embed.video-playlist.allowed.result': true
 }
 
 export type ServerFilterHookName = keyof typeof serverFilterHookObject