From 8e13fa7d09e9925b4559cbba6c5d72c5ff1bd391 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 15 Nov 2017 15:12:23 +0100 Subject: [PATCH] Add video abuse to activity pub --- .../video-abuse-list.component.html | 1 + server/controllers/api/videos/abuse.ts | 22 ++++------ server/helpers/activitypub.ts | 6 ++- server/helpers/custom-validators/videos.ts | 5 --- server/lib/activitypub/process-create.ts | 43 +++++++++++++++--- server/lib/activitypub/send-request.ts | 25 ++++++++--- server/models/video/video-abuse-interface.ts | 14 +++--- server/models/video/video-abuse.ts | 44 ++++++++++--------- shared/models/activitypub/activity.ts | 8 ++-- shared/models/activitypub/objects/index.ts | 1 + .../activitypub/objects/video-abuse-object.ts | 5 +++ .../objects/video-channel-object.ts | 2 - shared/models/videos/video-abuse.model.ts | 4 +- 13 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 shared/models/activitypub/objects/video-abuse-object.ts diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html index 1d8bb4d38..ab0a9d99f 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html @@ -11,6 +11,7 @@ + {{ videoAbuse.videoId }} diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 29f901f60..d9b4e8772 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -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) } }) diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 3e77e0581..de20ba55d 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -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 (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' } ] }) diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index d68de6609..1505632da 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -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, diff --git a/server/lib/activitypub/process-create.ts b/server/lib/activitypub/process-create.ts index 471674ead..8d842b822 100644 --- a/server/lib/activitypub/process-create.ts +++ b/server/lib/activitypub/process-create.ts @@ -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) + }) +} diff --git a/server/lib/activitypub/send-request.ts b/server/lib/activitypub/send-request.ts index f942a2eba..1a6cebc03 100644 --- a/server/lib/activitypub/send-request.ts +++ b/server/lib/activitypub/send-request.ts @@ -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 } diff --git a/server/models/video/video-abuse-interface.ts b/server/models/video/video-abuse-interface.ts index 16806cae2..96f0fbe4a 100644 --- a/server/models/video/video-abuse-interface.ts +++ b/server/models/video/video-abuse-interface.ts @@ -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 { diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index c1d070ec0..f3fdeab52 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -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('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 } ] } diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 254daf118..506e64eff 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts @@ -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 { diff --git a/shared/models/activitypub/objects/index.ts b/shared/models/activitypub/objects/index.ts index 8c2e2daca..cd772b28d 100644 --- a/shared/models/activitypub/objects/index.ts +++ b/shared/models/activitypub/objects/index.ts @@ -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 index 000000000..40e7abd57 --- /dev/null +++ b/shared/models/activitypub/objects/video-abuse-object.ts @@ -0,0 +1,5 @@ +export interface VideoAbuseObject { + type: 'Flag', + content: string + object: string +} diff --git a/shared/models/activitypub/objects/video-channel-object.ts b/shared/models/activitypub/objects/video-channel-object.ts index 72efe42b3..de504d84c 100644 --- a/shared/models/activitypub/objects/video-channel-object.ts +++ b/shared/models/activitypub/objects/video-channel-object.ts @@ -1,5 +1,3 @@ -import { ActivityIdentifierObject } from './common-objects' - export interface VideoChannelObject { type: 'VideoChannel' id: string diff --git a/shared/models/videos/video-abuse.model.ts b/shared/models/videos/video-abuse.model.ts index 38041e491..aaedd00d4 100644 --- a/shared/models/videos/video-abuse.model.ts +++ b/shared/models/videos/video-abuse.model.ts @@ -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 } -- 2.41.0