Handle reports from mastodon
authorChocobozzz <me@florianbigard.com>
Fri, 30 Aug 2019 07:40:21 +0000 (09:40 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 30 Aug 2019 07:40:32 +0000 (09:40 +0200)
server/helpers/custom-validators/activitypub/actor.ts
server/initializers/constants.ts
server/initializers/migrations/0425-nullable-actor-fields.ts [new file with mode: 0644]
server/lib/activitypub/process/process-flag.ts
server/models/activitypub/actor.ts
shared/models/activitypub/activity.ts
shared/models/activitypub/objects/video-abuse-object.ts

index deb331abbb8efb304d1a443c98a6fe71ff3047f8..55bc8cc96c381b22b5349405f5340db9656a7729 100644 (file)
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) {
     validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
 }
 
-const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]'
+const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
 const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
 function isActorPreferredUsernameValid (preferredUsername: string) {
   return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) {
   return exists(actor) &&
     isActivityPubUrlValid(actor.id) &&
     isActorTypeValid(actor.type) &&
-    isActivityPubUrlValid(actor.following) &&
-    isActivityPubUrlValid(actor.followers) &&
     isActivityPubUrlValid(actor.inbox) &&
-    isActivityPubUrlValid(actor.outbox) &&
     isActorPreferredUsernameValid(actor.preferredUsername) &&
     isActivityPubUrlValid(actor.url) &&
     isActorPublicKeyObjectValid(actor.publicKey) &&
     isActorEndpointsObjectValid(actor.endpoints) &&
-    setValidAttributedTo(actor) &&
 
-    // If this is not an account, it should be attributed to an account
+    (!actor.outbox || isActivityPubUrlValid(actor.outbox)) &&
+    (!actor.following || isActivityPubUrlValid(actor.following)) &&
+    (!actor.followers || isActivityPubUrlValid(actor.followers)) &&
+
+    setValidAttributedTo(actor) &&
+    // If this is a group (a channel), it should be attributed to an account
     // In PeerTube we use this to attach a video channel to a specific account
-    (actor.type === 'Person' || actor.attributedTo.length !== 0)
+    (actor.type !== 'Group' || actor.attributedTo.length !== 0)
 }
 
 function isActorFollowingCountValid (value: string) {
index 3dc178b117c9d63288eeaa49789c047556367be5..908231a88551a3ff8a75d664daef5a63dc94b246 100644 (file)
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 420
+const LAST_MIGRATION_VERSION = 425
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts
new file mode 100644 (file)
index 0000000..4e5f9e6
--- /dev/null
@@ -0,0 +1,26 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  const data = {
+    type: Sequelize.STRING,
+    allowNull: true
+  }
+
+  await utils.queryInterface.changeColumn('actor', 'outboxUrl', data)
+  await utils.queryInterface.changeColumn('actor', 'followersUrl', data)
+  await utils.queryInterface.changeColumn('actor', 'followingUrl', data)
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 422386540fc635184a8eadb6053726344d098193..e6e9084de2aa23edde37192b2b38e29d9fc1c062 100644 (file)
@@ -26,28 +26,36 @@ export {
 async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) {
   const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
 
-  logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
-
   const account = byActor.Account
   if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url)
 
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object })
+  const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ]
 
-  const videoAbuse = await sequelizeTypescript.transaction(async t => {
-    const videoAbuseData = {
-      reporterAccountId: account.id,
-      reason: flag.content,
-      videoId: video.id,
-      state: VideoAbuseState.PENDING
-    }
+  for (const object of objects) {
+    try {
+      logger.debug('Reporting remote abuse for video %s.', getAPId(object))
+
+      const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
 
-    const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
-    videoAbuseInstance.Video = video
+      const videoAbuse = await sequelizeTypescript.transaction(async t => {
+        const videoAbuseData = {
+          reporterAccountId: account.id,
+          reason: flag.content,
+          videoId: video.id,
+          state: VideoAbuseState.PENDING
+        }
 
-    logger.info('Remote abuse for video uuid %s created', flag.object)
+        const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
+        videoAbuseInstance.Video = video
 
-    return videoAbuseInstance
-  })
+        logger.info('Remote abuse for video uuid %s created', flag.object)
 
-  Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
+        return videoAbuseInstance
+      })
+
+      Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
+    } catch (err) {
+      logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
+    }
+  }
 }
index fb4327e4f6f44fe5efb95d350852bd80bbcd2337..67a1b5bc1b55b9607da7f29708123cdab31f6260 100644 (file)
@@ -175,8 +175,8 @@ export class ActorModel extends Model<ActorModel> {
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   inboxUrl: string
 
-  @AllowNull(false)
-  @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
+  @AllowNull(true)
+  @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true))
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   outboxUrl: string
 
@@ -185,13 +185,13 @@ export class ActorModel extends Model<ActorModel> {
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   sharedInboxUrl: string
 
-  @AllowNull(false)
-  @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
+  @AllowNull(true)
+  @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true))
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   followersUrl: string
 
-  @AllowNull(false)
-  @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
+  @AllowNull(true)
+  @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true))
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
   followingUrl: string
 
index 95801190d83d07223e0b92c699e0b1a5bdaaf787..492b672c7b4a7087864bfa0d8a705a7b4bbb5be3 100644 (file)
@@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity {
 export interface ActivityFlag extends BaseActivity {
   type: 'Flag',
   content: string,
-  object: APObject
+  object: APObject | APObject[]
 }
index 40e7abd576a6a7248585ca49d4694f0c685594a1..5f1264a76b68e373beea13070584a8e86758c411 100644 (file)
@@ -1,5 +1,5 @@
 export interface VideoAbuseObject {
   type: 'Flag',
   content: string
-  object: string
+  object: string | string[]
 }