]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add video abuse to activity pub
authorChocobozzz <florian.bigard@gmail.com>
Wed, 15 Nov 2017 14:12:23 +0000 (15:12 +0100)
committerChocobozzz <florian.bigard@gmail.com>
Mon, 27 Nov 2017 18:40:52 +0000 (19:40 +0100)
13 files changed:
client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html
server/controllers/api/videos/abuse.ts
server/helpers/activitypub.ts
server/helpers/custom-validators/videos.ts
server/lib/activitypub/process-create.ts
server/lib/activitypub/send-request.ts
server/models/video/video-abuse-interface.ts
server/models/video/video-abuse.ts
shared/models/activitypub/activity.ts
shared/models/activitypub/objects/index.ts
shared/models/activitypub/objects/video-abuse-object.ts [new file with mode: 0644]
shared/models/activitypub/objects/video-channel-object.ts
shared/models/videos/video-abuse.model.ts

index 1d8bb4d38ee903a92342185b5022f2c3e24422a4..ab0a9d99f23412e6400d1ad959ac49e6aabd8ba3 100644 (file)
@@ -11,6 +11,7 @@
     <p-column field="reason" header="Reason"></p-column>
     <p-column field="reporterServerHost" header="Reporter server host"></p-column>
     <p-column field="reporterUsername" header="Reporter username"></p-column>
+    <p-column field="videoName" header="Video name"></p-column>
     <p-column header="Video" styleClass="action-cell">
       <ng-template pTemplate="body" let-videoAbuse="rowData">
         <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a>
index 29f901f605bb90d3b83d8a131bd790afc2fa71e9..d9b4e8772fabfe766d72ce3970b18e1884b1bb9f 100644 (file)
@@ -18,6 +18,7 @@ import {
 } from '../../../middlewares'
 import { VideoInstance } from '../../../models'
 import { VideoAbuseCreate, UserRight } from '../../../../shared'
+import { sendVideoAbuse } from '../../../lib/index'
 
 const abuseVideoRouter = express.Router()
 
@@ -63,28 +64,21 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.
 
 async function reportVideoAbuse (req: express.Request, res: express.Response) {
   const videoInstance = res.locals.video as VideoInstance
-  const reporterUsername = res.locals.oauth.token.User.username
+  const reporterAccount = res.locals.oauth.token.User.Account
   const body: VideoAbuseCreate = req.body
 
   const abuseToCreate = {
-    reporterUsername,
+    reporterAccountId: reporterAccount.id,
     reason: body.reason,
-    videoId: videoInstance.id,
-    reporterServerId: null // This is our server that reported this abuse
+    videoId: videoInstance.id
   }
 
   await db.sequelize.transaction(async t => {
-    const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
-    // We send the information to the destination server
+    const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t })
+
+    // We send the video abuse to the origin server
     if (videoInstance.isOwned() === false) {
-      const reportData = {
-        reporterUsername,
-        reportReason: abuse.reason,
-        videoUUID: videoInstance.uuid
-      }
-
-      // await friends.reportAbuseVideoToFriend(reportData, videoInstance, t)
-      // TODO: send abuse to origin server
+      await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t)
     }
   })
 
index 3e77e05814a909a148dabcb2a22a9cdf7686ea24..de20ba55d28989ce3e19a0b651c7a02cca3e7079 100644 (file)
@@ -22,10 +22,11 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
   return doRequestAndSaveToFile(options, thumbnailPath)
 }
 
-function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account', id: string) {
+function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) {
   if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id
   else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id
   else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id
+  else if (type === 'videoAbuse') return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + id
 
   return ''
 }
@@ -134,7 +135,8 @@ function activityPubContextify <T> (data: T) {
         'nsfw': 'as:sensitive',
         'language': 'http://schema.org/inLanguage',
         'views': 'http://schema.org/Number',
-        'size': 'http://schema.org/Number'
+        'size': 'http://schema.org/Number',
+        'VideoChannel': 'https://peertu.be/ns/VideoChannel'
       }
     ]
   })
index d68de66092253e1f08bd88dacf28d0bc959d095f..1505632da8720a6baee20ff2077d17b0e4f1a8ed 100644 (file)
@@ -112,10 +112,6 @@ function isVideoAbuseReasonValid (value: string) {
   return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
 }
 
-function isVideoAbuseReporterUsernameValid (value: string) {
-  return isUserUsernameValid(value)
-}
-
 function isVideoViewsValid (value: string) {
   return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
 }
@@ -209,7 +205,6 @@ export {
   isVideoThumbnailDataValid,
   isVideoFileExtnameValid,
   isVideoAbuseReasonValid,
-  isVideoAbuseReporterUsernameValid,
   isVideoFile,
   isVideoViewsValid,
   isVideoLikesValid,
index 471674eada856f9788bf7bf98b1c9cbf6aa55ac6..8d842b8221589f5d791c00931191036ab1a684b4 100644 (file)
@@ -1,11 +1,9 @@
-import { ActivityCreate, VideoChannelObject, VideoTorrentObject } from '../../../shared'
-import { ActivityAdd } from '../../../shared/models/activitypub/activity'
-import { generateThumbnailFromUrl, logger, retryTransactionWrapper } from '../../helpers'
+import { ActivityCreate, VideoChannelObject } from '../../../shared'
+import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
+import { logger, retryTransactionWrapper } from '../../helpers'
+import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
 import { database as db } from '../../initializers'
-import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
-import Bluebird = require('bluebird')
 import { AccountInstance } from '../../models/account/account-interface'
-import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
 
 async function processCreateActivity (activity: ActivityCreate) {
   const activityObject = activity.object
@@ -14,6 +12,8 @@ async function processCreateActivity (activity: ActivityCreate) {
 
   if (activityType === 'VideoChannel') {
     return processCreateVideoChannel(account, activityObject as VideoChannelObject)
+  } else if (activityType === 'Flag') {
+    return processCreateVideoAbuse(account, activityObject as VideoAbuseObject)
   }
 
   logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -62,3 +62,34 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr
 
   logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
 }
+
+function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+  const options = {
+    arguments: [ account, videoAbuseToCreateData ],
+    errorMessage: 'Cannot insert the remote video abuse with many retries.'
+  }
+
+  return retryTransactionWrapper(addRemoteVideoAbuse, options)
+}
+
+async function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) {
+  logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
+
+  return db.sequelize.transaction(async t => {
+    const video = await db.Video.loadByUrl(videoAbuseToCreateData.object, t)
+    if (!video) {
+      logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
+      return
+    }
+
+    const videoAbuseData = {
+      reporterAccountId: account.id,
+      reason: videoAbuseToCreateData.content,
+      videoId: video.id
+    }
+
+    await db.VideoAbuse.create(videoAbuseData)
+
+    logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
+  })
+}
index f942a2ebad3fbb6e27b13be89ec7210f89478414..1a6cebc0391a931e9d0c2d1cc993cf2842770c02 100644 (file)
@@ -9,6 +9,8 @@ import {
 import { httpRequestJobScheduler } from '../jobs'
 import { signObject, activityPubContextify } from '../../helpers'
 import { Activity } from '../../../shared'
+import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
+import { getActivityPubUrl } from '../../helpers/activitypub'
 
 async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
   const videoChannelObject = videoChannel.toActivityPubObject()
@@ -56,16 +58,28 @@ async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transac
   return broadcastToFollowers(data, account, t)
 }
 
+async function sendVideoAbuse (
+  fromAccount: AccountInstance,
+  videoAbuse: VideoAbuseInstance,
+  video: VideoInstance,
+  t: Sequelize.Transaction
+) {
+  const url = getActivityPubUrl('videoAbuse', videoAbuse.id.toString())
+  const data = await createActivityData(url, fromAccount, video.url)
+
+  return unicastTo(data, video.VideoChannel.Account.sharedInboxUrl, t)
+}
+
 async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
   const data = await acceptActivityData(fromAccount)
 
-  return unicastTo(data, toAccount, t)
+  return unicastTo(data, toAccount.inboxUrl, t)
 }
 
 async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) {
   const data = await followActivityData(toAccount.url, fromAccount)
 
-  return unicastTo(data, toAccount, t)
+  return unicastTo(data, toAccount.inboxUrl, t)
 }
 
 // ---------------------------------------------------------------------------
@@ -79,7 +93,8 @@ export {
   sendDeleteVideo,
   sendDeleteAccount,
   sendAccept,
-  sendFollow
+  sendFollow,
+  sendVideoAbuse
 }
 
 // ---------------------------------------------------------------------------
@@ -95,9 +110,9 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t:
   return httpRequestJobScheduler.createJob(t, 'httpRequestBroadcastHandler', jobPayload)
 }
 
-async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) {
+async function unicastTo (data: any, toAccountUrl: string, t: Sequelize.Transaction) {
   const jobPayload = {
-    uris: [ toAccount.inboxUrl ],
+    uris: [ toAccountUrl ],
     body: data
   }
 
index 16806cae2ee36378486f7bb99ad226c4a24e2bac..96f0fbe4a2a7f66dc2e068568db0afad3a0aebfa 100644 (file)
@@ -1,11 +1,10 @@
-import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
-
-import { ServerInstance } from '../server/server-interface'
+import * as Sequelize from 'sequelize'
 import { ResultList } from '../../../shared'
-
-// Don't use barrel, import just what we need
 import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model'
+import { AccountInstance } from '../account/account-interface'
+import { ServerInstance } from '../server/server-interface'
+import { VideoInstance } from './video-interface'
 
 export namespace VideoAbuseMethods {
   export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse
@@ -18,9 +17,12 @@ export interface VideoAbuseClass {
 }
 
 export interface VideoAbuseAttributes {
-  reporterUsername: string
   reason: string
   videoId: number
+  reporterAccountId: number
+
+  Account?: AccountInstance
+  Video?: VideoInstance
 }
 
 export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
index c1d070ec0de8ca3dacae91c8879ded9dfa84d91f..f3fdeab524807cc7fee5bb65cc191ca2c0ad934e 100644 (file)
@@ -1,7 +1,7 @@
 import * as Sequelize from 'sequelize'
 
 import { CONFIG } from '../../initializers'
-import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers'
+import { isVideoAbuseReasonValid } from '../../helpers'
 
 import { addMethodsToModel, getSort } from '../utils'
 import {
@@ -18,16 +18,6 @@ let listForApi: VideoAbuseMethods.ListForApi
 export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
   VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse',
     {
-      reporterUsername: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          reporterUsernameValid: value => {
-            const res = isVideoAbuseReporterUsernameValid(value)
-            if (res === false) throw new Error('Video abuse reporter username is not valid.')
-          }
-        }
-      },
       reason: {
         type: DataTypes.STRING,
         allowNull: false,
@@ -45,7 +35,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
           fields: [ 'videoId' ]
         },
         {
-          fields: [ 'reporterServerId' ]
+          fields: [ 'reporterAccountId' ]
         }
       ]
     }
@@ -69,8 +59,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
 toFormattedJSON = function (this: VideoAbuseInstance) {
   let reporterServerHost
 
-  if (this.Server) {
-    reporterServerHost = this.Server.host
+  if (this.Account.Server) {
+    reporterServerHost = this.Account.Server.host
   } else {
     // It means it's our video
     reporterServerHost = CONFIG.WEBSERVER.HOST
@@ -78,10 +68,12 @@ toFormattedJSON = function (this: VideoAbuseInstance) {
 
   const json = {
     id: this.id,
-    reporterServerHost,
     reason: this.reason,
-    reporterUsername: this.reporterUsername,
-    videoId: this.videoId,
+    reporterUsername: this.Account.name,
+    reporterServerHost,
+    videoId: this.Video.id,
+    videoUUID: this.Video.uuid,
+    videoName: this.Video.name,
     createdAt: this.createdAt
   }
 
@@ -91,9 +83,9 @@ toFormattedJSON = function (this: VideoAbuseInstance) {
 // ------------------------------ STATICS ------------------------------
 
 function associate (models) {
-  VideoAbuse.belongsTo(models.Server, {
+  VideoAbuse.belongsTo(models.Account, {
     foreignKey: {
-      name: 'reporterServerId',
+      name: 'reporterAccountId',
       allowNull: true
     },
     onDelete: 'CASCADE'
@@ -115,8 +107,18 @@ listForApi = function (start: number, count: number, sort: string) {
     order: [ getSort(sort) ],
     include: [
       {
-        model: VideoAbuse['sequelize'].models.Server,
-        required: false
+        model: VideoAbuse['sequelize'].models.Account,
+        required: true,
+        include: [
+          {
+            model: VideoAbuse['sequelize'].models.Server,
+            required: false
+          }
+        ]
+      },
+      {
+        model: VideoAbuse['sequelize'].models.Video,
+        required: true
       }
     ]
   }
index 254daf11894869ffe20d66d931d1ffba85bd7e20..506e64effb46068b8abb68a23a8884f6f7e652d4 100644 (file)
@@ -1,8 +1,6 @@
-import {
-  VideoChannelObject,
-  VideoTorrentObject
-} from './objects'
+import { VideoChannelObject, VideoTorrentObject } from './objects'
 import { ActivityPubSignature } from './activitypub-signature'
+import { VideoAbuseObject } from './objects/video-abuse-object'
 
 export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag |
   ActivityDelete | ActivityFollow | ActivityAccept
@@ -21,7 +19,7 @@ export interface BaseActivity {
 
 export interface ActivityCreate extends BaseActivity {
   type: 'Create'
-  object: VideoChannelObject
+  object: VideoChannelObject | VideoAbuseObject
 }
 
 export interface ActivityAdd extends BaseActivity {
index 8c2e2daca29b1af3e0014132b6a2a72de517b722..cd772b28def73367e022d1338930c0d01fccdc1b 100644 (file)
@@ -1,3 +1,4 @@
 export * from './common-objects'
+export * from './video-abuse-object'
 export * from './video-channel-object'
 export * from './video-torrent-object'
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts
new file mode 100644 (file)
index 0000000..40e7abd
--- /dev/null
@@ -0,0 +1,5 @@
+export interface VideoAbuseObject {
+  type: 'Flag',
+  content: string
+  object: string
+}
index 72efe42b31504419391200c06d8c60bd046cabf6..de504d84c05eab8b9fe4f2e78db3326f1877495b 100644 (file)
@@ -1,5 +1,3 @@
-import { ActivityIdentifierObject } from './common-objects'
-
 export interface VideoChannelObject {
   type: 'VideoChannel'
   id: string
index 38041e491b006c2dd363f0dc6ee0a86d2beaf419..aaedd00d4c1b1ceb16d9df0c74016468ae9f07d0 100644 (file)
@@ -1,8 +1,10 @@
 export interface VideoAbuse {
   id: number
-  reporterServerHost: string
   reason: string
   reporterUsername: string
+  reporterServerHost: string
   videoId: number
+  videoUUID: string
+  videoName: string
   createdAt: Date
 }