Add filter hooks tests
authorChocobozzz <me@florianbigard.com>
Mon, 22 Jul 2019 09:14:58 +0000 (11:14 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Wed, 24 Jul 2019 08:58:16 +0000 (10:58 +0200)
server/controllers/api/videos/comment.ts
server/controllers/api/videos/import.ts
server/controllers/api/videos/index.ts
server/lib/activitypub/process/process-create.ts
server/lib/activitypub/videos.ts
server/lib/video-blacklist.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 feda71bdd769caf8529b76adec385f5e5e15be0a..39d521f5f9ea6966a04bb21593fba342db138937 100644 (file)
@@ -110,7 +110,7 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
     const apiOptions = await Hooks.wrapObject({
       videoId: video.id,
       threadId: res.locals.videoCommentThread.id,
-      user: user
+      user
     }, 'filter:api.video-thread-comments.list.params')
 
     resultList = await Hooks.wrapPromiseFun(
index dcba0e08f4a2372505de2d71615cd4eb408c2735..47c6f122cf698cb81ea6b9fa80b1c1b8a29c4298 100644 (file)
@@ -245,7 +245,7 @@ function insertIntoDB (parameters: {
     if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
     if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
 
-    await autoBlacklistVideoIfNeeded(video, user, t)
+    await autoBlacklistVideoIfNeeded({ video, user, isRemote: false, isNew: true, transaction: t })
 
     // Set tags to the video
     if (tags) {
index 11e468df283476a0d25074bbba021532bcc268ec..6a79a16c78de709b6897fb84ee62c2422bb286c2 100644 (file)
@@ -268,7 +268,13 @@ async function addVideo (req: express.Request, res: express.Response) {
       }, { transaction: t })
     }
 
-    const videoWasAutoBlacklisted = await autoBlacklistVideoIfNeeded(video, res.locals.oauth.token.User, t)
+    const videoWasAutoBlacklisted = await autoBlacklistVideoIfNeeded({
+      video,
+      user: res.locals.oauth.token.User,
+      isRemote: false,
+      isNew: true,
+      transaction: t
+    })
     if (!videoWasAutoBlacklisted) await federateVideoIfNeeded(video, true, t)
 
     auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
@@ -336,16 +342,16 @@ async function updateVideo (req: express.Request, res: express.Response) {
       const sequelizeOptions = { transaction: t }
       const oldVideoChannel = videoInstance.VideoChannel
 
-      if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
-      if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
-      if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
-      if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
-      if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
-      if (videoInfoToUpdate.waitTranscoding !== undefined) videoInstance.set('waitTranscoding', videoInfoToUpdate.waitTranscoding)
-      if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support)
-      if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
-      if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
-      if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.set('downloadEnabled', videoInfoToUpdate.downloadEnabled)
+      if (videoInfoToUpdate.name !== undefined) videoInstance.name = videoInfoToUpdate.name
+      if (videoInfoToUpdate.category !== undefined) videoInstance.category = videoInfoToUpdate.category
+      if (videoInfoToUpdate.licence !== undefined) videoInstance.licence = videoInfoToUpdate.licence
+      if (videoInfoToUpdate.language !== undefined) videoInstance.language = videoInfoToUpdate.language
+      if (videoInfoToUpdate.nsfw !== undefined) videoInstance.nsfw = videoInfoToUpdate.nsfw
+      if (videoInfoToUpdate.waitTranscoding !== undefined) videoInstance.waitTranscoding = videoInfoToUpdate.waitTranscoding
+      if (videoInfoToUpdate.support !== undefined) videoInstance.support = videoInfoToUpdate.support
+      if (videoInfoToUpdate.description !== undefined) videoInstance.description = videoInfoToUpdate.description
+      if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.commentsEnabled = videoInfoToUpdate.commentsEnabled
+      if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.downloadEnabled = videoInfoToUpdate.downloadEnabled
 
       if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
         videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt)
@@ -398,6 +404,14 @@ async function updateVideo (req: express.Request, res: express.Response) {
         await ScheduleVideoUpdateModel.deleteByVideoId(videoInstanceUpdated.id, t)
       }
 
+      await autoBlacklistVideoIfNeeded({
+        video: videoInstanceUpdated,
+        user: res.locals.oauth.token.User,
+        isRemote: false,
+        isNew: false,
+        transaction: t
+      })
+
       const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE
 
       // Don't send update if the video was unfederated
index daf8465136b5d29f11773cb9173b950b63dc70c2..b9f584aa572572f36b81606aa21789ef5cc7f705 100644 (file)
@@ -48,9 +48,9 @@ export {
 async function processCreateVideo (activity: ActivityCreate) {
   const videoToCreateData = activity.object as VideoTorrentObject
 
-  const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
+  const { video, created, autoBlacklisted } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
 
-  if (created) Notifier.Instance.notifyOnNewVideo(video)
+  if (created && !autoBlacklisted) Notifier.Instance.notifyOnNewVideo(video)
 
   return video
 }
index dade6b55f6a1e3ea2e625dfbaeaa5c712b2f30b8..67b433165b4c445735d26f45ff971aa078660c5a 100644 (file)
@@ -224,11 +224,11 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
   if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
 
   const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
-  const video = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail)
+  const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail)
 
-  await syncVideoExternalAttributes(video, fetchedVideo, syncParam)
+  await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
 
-  return { video, created: true }
+  return { video: videoCreated, created: true, autoBlacklisted }
 }
 
 async function updateVideoFromAP (options: {
@@ -354,7 +354,13 @@ async function updateVideoFromAP (options: {
       }
     })
 
-    const autoBlacklisted = await autoBlacklistVideoIfNeeded(video, undefined, undefined)
+    const autoBlacklisted = await autoBlacklistVideoIfNeeded({
+      video,
+      user: undefined,
+      isRemote: true,
+      isNew: false,
+      transaction: undefined
+    })
 
     if (autoBlacklisted) Notifier.Instance.notifyOnVideoAutoBlacklist(video)
     else if (!wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideo(video) // Notify our users?
@@ -467,7 +473,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
     thumbnailModel = await promiseThumbnail
   }
 
-  const videoCreated: VideoModel = await sequelizeTypescript.transaction(async t => {
+  const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
     const videoCreated = await video.save(sequelizeOptions)
@@ -506,9 +512,17 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
     })
     await Promise.all(videoCaptionsPromises)
 
+    const autoBlacklisted = await autoBlacklistVideoIfNeeded({
+      video,
+      user: undefined,
+      isRemote: true,
+      isNew: true,
+      transaction: t
+    })
+
     logger.info('Remote video with uuid %s inserted.', videoObject.uuid)
 
-    return videoCreated
+    return { autoBlacklisted, videoCreated }
   })
 
   if (waitThumbnail === false) {
@@ -519,7 +533,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
     })
   }
 
-  return videoCreated
+  return { autoBlacklisted, videoCreated }
 }
 
 async function videoActivityObjectToDBAttributes (
index 9bc996f5a84d0f15cf2a29a9a257ba39f551e161..9749ce2f6cbc282e2bc69100be302fd954c78699 100644 (file)
@@ -8,10 +8,17 @@ import { logger } from '../helpers/logger'
 import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
 import { Hooks } from './plugins/hooks'
 
-async function autoBlacklistVideoIfNeeded (video: VideoModel, user?: UserModel, transaction?: Transaction) {
+async function autoBlacklistVideoIfNeeded (parameters: {
+  video: VideoModel,
+  user?: UserModel,
+  isRemote: boolean,
+  isNew: boolean,
+  transaction?: Transaction
+}) {
+  const { video, user, isRemote, isNew, transaction } = parameters
   const doAutoBlacklist = await Hooks.wrapPromiseFun(
     autoBlacklistNeeded,
-    { video, user },
+    { video, user, isRemote, isNew },
     'filter:video.auto-blacklist.result'
   )
 
@@ -23,17 +30,33 @@ async function autoBlacklistVideoIfNeeded (video: VideoModel, user?: UserModel,
     reason: 'Auto-blacklisted. Moderator review required.',
     type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
   }
-  await VideoBlacklistModel.create(videoBlacklistToCreate, { transaction })
+  const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({
+    where: {
+      videoId: video.id
+    },
+    defaults: videoBlacklistToCreate,
+    transaction
+  })
+
+  video.VideoBlacklist = videoBlacklist
 
   logger.info('Video %s auto-blacklisted.', video.uuid)
 
   return true
 }
 
-async function autoBlacklistNeeded (parameters: { video: VideoModel, user?: UserModel }) {
-  const { user } = parameters
+async function autoBlacklistNeeded (parameters: {
+  video: VideoModel,
+  isRemote: boolean,
+  isNew: boolean,
+  user?: UserModel
+}) {
+  const { user, video, isRemote, isNew } = parameters
 
+  // Already blacklisted
+  if (video.VideoBlacklist) return false
   if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED || !user) return false
+  if (isRemote || isNew) return false
 
   if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) || user.hasAdminFlag(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)) return false
 
index 9c1bfaeaaa307b68266ce073f4373943c587ea24..6b8e2f3180daeb0d02d53b13006c092be5562100 100644 (file)
@@ -210,13 +210,15 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon
   if (isReply) {
     const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment })
 
-    acceptedResult = await Hooks.wrapObject(
-      isLocalVideoCommentReplyAccepted(acceptReplyParameters),
+    acceptedResult = await Hooks.wrapFun(
+      isLocalVideoCommentReplyAccepted,
+      acceptReplyParameters,
       'filter:api.video-comment-reply.create.accept.result'
     )
   } else {
-    acceptedResult = await Hooks.wrapObject(
-      isLocalVideoThreadAccepted(acceptParameters),
+    acceptedResult = await Hooks.wrapFun(
+      isLocalVideoThreadAccepted,
+      acceptParameters,
       'filter:api.video-thread.create.accept.result'
     )
   }
index c5317ab41aacadbad39f37f99c1dd94e27231b3d..7c53f6afe49dcafb78804525d376c1e9d6d6f55a 100644 (file)
@@ -26,7 +26,7 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
 
   registerHook({
     target: 'filter:api.videos.list.result',
-    handler: obj => ({ data: obj.data, total: obj.total + 1 })
+    handler: obj => addToTotal(obj)
   })
 
   registerHook({
@@ -41,12 +41,51 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
   registerHook({
     target: 'filter:api.video.upload.accept.result',
     handler: ({ accepted }, { videoBody }) => {
-      if (accepted !== false) return { accepted: true }
+      if (!accepted) return { accepted: false }
       if (videoBody.name.indexOf('bad word') !== -1) return { accepted: false, errorMessage: 'bad word '}
 
       return { accepted: true }
     }
   })
+
+  registerHook({
+    target: 'filter:api.video-thread.create.accept.result',
+    handler: ({ accepted }, { commentBody }) => checkCommentBadWord(accepted, commentBody)
+  })
+
+  registerHook({
+    target: 'filter:api.video-comment-reply.create.accept.result',
+    handler: ({ accepted }, { commentBody }) => checkCommentBadWord(accepted, commentBody)
+  })
+
+  registerHook({
+    target: 'filter:api.video-threads.list.params',
+    handler: obj => addToCount(obj)
+  })
+
+  registerHook({
+    target: 'filter:api.video-threads.list.result',
+    handler: obj => addToTotal(obj)
+  })
+
+  registerHook({
+    target: 'filter:api.video-thread-comments.list.result',
+    handler: obj => {
+      obj.data.forEach(c => c.text += ' <3')
+
+      return obj
+    }
+  })
+
+  registerHook({
+    target: 'filter:video.auto-blacklist.result',
+    handler: (blacklisted, { video }) => {
+      if (blacklisted) return true
+      if (video.name.includes('please blacklist me')) return true
+
+      return false
+    }
+  })
 }
 
 async function unregister () {
@@ -63,3 +102,17 @@ module.exports = {
 function addToCount (obj) {
   return Object.assign({}, obj, { count: obj.count + 1 })
 }
+
+function addToTotal (result) {
+  return {
+    data: result.data,
+    total: result.total + 1
+  }
+}
+
+function checkCommentBadWord (accepted, commentBody) {
+  if (!accepted) return { accepted: false }
+  if (commentBody.text.indexOf('bad word') !== -1) return { accepted: false, errorMessage: 'bad word '}
+
+  return { accepted: true }
+}
index 4fc2c437b9153d7d8b24095e13cf1a4a584c32e9..ec0679b04f3d1d8f607ea89610d4fa7f85e6e659 100644 (file)
@@ -11,15 +11,28 @@ import {
 } from '../../../shared/extra-utils/server/servers'
 import {
   addVideoCommentReply,
-  addVideoCommentThread, deleteVideoComment,
-  getPluginTestPath, getVideosList,
-  installPlugin, removeVideo,
+  addVideoCommentThread,
+  deleteVideoComment,
+  getPluginTestPath,
+  getVideosList,
+  installPlugin,
+  removeVideo,
   setAccessTokensToServers,
   updateVideo,
   uploadVideo,
   viewVideo,
-  getVideosListPagination, getVideo
+  getVideosListPagination,
+  getVideo,
+  getVideoCommentThreads,
+  getVideoThreadComments,
+  getVideoWithToken,
+  setDefaultVideoChannel,
+  waitJobs,
+  doubleFollow
 } from '../../../shared/extra-utils'
+import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
+import { VideoDetails } from '../../../shared/models/videos'
+import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
 
 const expect = chai.expect
 
@@ -33,6 +46,8 @@ describe('Test plugin filter hooks', function () {
 
     servers = await flushAndRunMultipleServers(2)
     await setAccessTokensToServers(servers)
+    await setDefaultVideoChannel(servers)
+    await doubleFollow(servers[0], servers[1])
 
     await installPlugin({
       url: servers[0].url,
@@ -54,7 +69,7 @@ describe('Test plugin filter hooks', function () {
     videoUUID = res.body.data[0].uuid
   })
 
-  it('Should run filter:api.videos.list.params hook', async function () {
+  it('Should run filter:api.videos.list.params', async function () {
     const res = await getVideosListPagination(servers[0].url, 0, 2)
 
     // 2 plugins do +1 to the count parameter
@@ -74,6 +89,104 @@ describe('Test plugin filter hooks', function () {
     expect(res.body.name).to.contain('<3')
   })
 
+  it('Should run filter:api.video.upload.accept.result', async function () {
+    await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video with bad word' }, 403)
+  })
+
+  it('Should run filter:api.video-thread.create.accept.result', async function () {
+    await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'comment with bad word', 403)
+  })
+
+  it('Should run filter:api.video-comment-reply.create.accept.result', async function () {
+    const res = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'thread')
+    threadId = res.body.comment.id
+
+    await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'comment with bad word', 403)
+    await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID, threadId, 'comment with good word', 200)
+  })
+
+  it('Should run filter:api.video-threads.list.params', async function () {
+    const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 0)
+
+    // our plugin do +1 to the count parameter
+    expect(res.body.data).to.have.lengthOf(1)
+  })
+
+  it('Should run filter:api.video-threads.list.result', async function () {
+    const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 0)
+
+    // Plugin do +1 to the total result
+    expect(res.body.total).to.equal(2)
+  })
+
+  it('Should run filter:api.video-thread-comments.list.params')
+
+  it('Should run filter:api.video-thread-comments.list.result', async function () {
+    const res = await getVideoThreadComments(servers[0].url, videoUUID, threadId)
+
+    const thread = res.body as VideoCommentThreadTree
+    expect(thread.comment.text.endsWith(' <3')).to.be.true
+  })
+
+  describe('Should run filter:video.auto-blacklist.result', function () {
+
+    async function checkIsBlacklisted (oldRes: any, value: boolean) {
+      const videoId = oldRes.body.video.uuid
+
+      const res = await getVideoWithToken(servers[0].url, servers[0].accessToken, videoId)
+      const video: VideoDetails = res.body
+      expect(video.blacklisted).to.equal(value)
+    }
+
+    it('Should blacklist on upload', async function () {
+      const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video please blacklist me' })
+      await checkIsBlacklisted(res, true)
+    })
+
+    it('Should blacklist on import', async function () {
+      const attributes = {
+        name: 'video please blacklist me',
+        targetUrl: getYoutubeVideoUrl(),
+        channelId: servers[0].videoChannel.id
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      await checkIsBlacklisted(res, true)
+    })
+
+    it('Should blacklist on update', async function () {
+      const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' })
+      const videoId = res.body.video.uuid
+      await checkIsBlacklisted(res, false)
+
+      await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, { name: 'please blacklist me' })
+      await checkIsBlacklisted(res, true)
+    })
+
+    it('Should blacklist on remote upload', async function () {
+      this.timeout(45000)
+
+      const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'remote please blacklist me' })
+      await waitJobs(servers)
+
+      await checkIsBlacklisted(res, true)
+    })
+
+    it('Should blacklist on remote update', async function () {
+      this.timeout(45000)
+
+      const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video' })
+      await waitJobs(servers)
+
+      const videoId = res.body.video.uuid
+      await checkIsBlacklisted(res, false)
+
+      await updateVideo(servers[1].url, servers[1].accessToken, videoId, { name: 'please blacklist me' })
+      await waitJobs(servers)
+
+      await checkIsBlacklisted(res, true)
+    })
+  })
+
   after(async function () {
     await cleanupTests(servers)
   })
index 30469856cd7ff971016009d406e0e7155c3ed68f..a7f88f3c40f377ef9dc70c6881b4a0410488bdb9 100644 (file)
@@ -7,12 +7,12 @@ export type ServerFilterHookName =
   'filter:api.video-thread.create.accept.result' |
   'filter:api.video-comment-reply.create.accept.result' |
 
-  'filter:api.video-thread-comments.list.params' |
-  'filter:api.video-thread-comments.list.result' |
-
   'filter:api.video-threads.list.params' |
   'filter:api.video-threads.list.result' |
 
+  'filter:api.video-thread-comments.list.params' |
+  'filter:api.video-thread-comments.list.result' |
+
   'filter:video.auto-blacklist.result'
 
 export type ServerActionHookName =