From d95d15598847c7f020aa056e7e6e0c02d2bbf732 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 1 Jul 2020 16:05:30 +0200 Subject: Use 3 tables to represent abuses --- server/lib/activitypub/process/process-flag.ts | 117 +++++++++------ server/lib/activitypub/send/send-flag.ts | 31 ++-- server/lib/activitypub/url.ts | 10 +- server/lib/emailer.ts | 110 +++++++++----- server/lib/emails/account-abuse-new/html.pug | 14 ++ server/lib/emails/common/mixins.pug | 6 +- server/lib/emails/video-abuse-new/html.pug | 8 +- server/lib/emails/video-comment-abuse-new/html.pug | 15 ++ server/lib/moderation.ts | 164 +++++++++++++++++++-- server/lib/notifier.ts | 43 +++--- 10 files changed, 374 insertions(+), 144 deletions(-) create mode 100644 server/lib/emails/account-abuse-new/html.pug create mode 100644 server/lib/emails/video-comment-abuse-new/html.pug (limited to 'server/lib') diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1d7132a3a..6350cee12 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts @@ -1,24 +1,19 @@ -import { - ActivityCreate, - ActivityFlag, - VideoAbuseState, - videoAbusePredefinedReasonsMap -} from '../../../../shared' -import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' +import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' +import { AccountModel } from '@server/models/account/account' +import { VideoModel } from '@server/models/video/video' +import { VideoCommentModel } from '@server/models/video/video-comment' +import { AbuseObject, abusePredefinedReasonsMap, AbuseState, ActivityCreate, ActivityFlag } from '../../../../shared' +import { getAPId } from '../../../helpers/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' import { sequelizeTypescript } from '../../../initializers/database' -import { VideoAbuseModel } from '../../../models/video/video-abuse' -import { getOrCreateVideoAndAccountAndChannel } from '../videos' -import { Notifier } from '../../notifier' -import { getAPId } from '../../../helpers/activitypub' import { APProcessorOptions } from '../../../types/activitypub-processor.model' -import { MActorSignature, MVideoAbuseAccountVideo } from '../../../types/models' -import { AccountModel } from '@server/models/account/account' +import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models' async function processFlagActivity (options: APProcessorOptions) { const { activity, byActor } = options - return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor) + + return retryTransactionWrapper(processCreateAbuse, activity, byActor) } // --------------------------------------------------------------------------- @@ -29,55 +24,79 @@ export { // --------------------------------------------------------------------------- -async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { - const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) +async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { + const flag = activity.type === 'Flag' ? activity : (activity.object as AbuseObject) const account = byActor.Account - if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) + if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url) + + const reporterAccount = await AccountModel.load(account.id) const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] + const tags = Array.isArray(flag.tag) ? flag.tag : [] + const predefinedReasons = tags.map(tag => abusePredefinedReasonsMap[tag.name]) + .filter(v => !isNaN(v)) + + const startAt = flag.startAt + const endAt = flag.endAt + for (const object of objects) { try { - logger.debug('Reporting remote abuse for video %s.', getAPId(object)) - - const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) - const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t)) - const tags = Array.isArray(flag.tag) ? flag.tag : [] - const predefinedReasons = tags.map(tag => videoAbusePredefinedReasonsMap[tag.name]) - .filter(v => !isNaN(v)) - const startAt = flag.startAt - const endAt = flag.endAt - - const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { - const videoAbuseData = { - reporterAccountId: account.id, - reason: flag.content, - videoId: video.id, - state: VideoAbuseState.PENDING, - predefinedReasons, - startAt, - endAt - } + const uri = getAPId(object) - const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) - videoAbuseInstance.Video = video - videoAbuseInstance.Account = reporterAccount + logger.debug('Reporting remote abuse for object %s.', uri) - logger.info('Remote abuse for video uuid %s created', flag.object) + await sequelizeTypescript.transaction(async t => { - return videoAbuseInstance - }) + const video = await VideoModel.loadByUrlAndPopulateAccount(uri) + let videoComment: MCommentOwnerVideo + let flaggedAccount: MAccountDefault + + if (!video) videoComment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(uri) + if (!videoComment) flaggedAccount = await AccountModel.loadByUrl(uri) + + if (!video && !videoComment && !flaggedAccount) { + logger.warn('Cannot flag unknown entity %s.', object) + return + } + + const baseAbuse = { + reporterAccountId: reporterAccount.id, + reason: flag.content, + state: AbuseState.PENDING, + predefinedReasons + } - const videoAbuseJSON = videoAbuseInstance.toFormattedJSON() + if (video) { + return createVideoAbuse({ + baseAbuse, + startAt, + endAt, + reporterAccount, + transaction: t, + videoInstance: video + }) + } + + if (videoComment) { + return createVideoCommentAbuse({ + baseAbuse, + reporterAccount, + transaction: t, + commentInstance: videoComment + }) + } - Notifier.Instance.notifyOnNewVideoAbuse({ - videoAbuse: videoAbuseJSON, - videoAbuseInstance, - reporter: reporterAccount.Actor.getIdentifier() + return await createAccountAbuse({ + baseAbuse, + reporterAccount, + transaction: t, + accountInstance: flaggedAccount + }) }) } catch (err) { - logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) + logger.debug('Cannot process report of %s', getAPId(object), { err }) } } } diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 3a1fe0812..821637ec8 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts @@ -1,32 +1,31 @@ -import { getVideoAbuseActivityPubUrl } from '../url' -import { unicastTo } from './utils' -import { logger } from '../../../helpers/logger' +import { Transaction } from 'sequelize' import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' +import { logger } from '../../../helpers/logger' +import { MAbuseAP, MAccountLight, MActor } from '../../../types/models' import { audiencify, getAudience } from '../audience' -import { Transaction } from 'sequelize' -import { MActor, MVideoFullLight } from '../../../types/models' -import { MVideoAbuseVideo } from '../../../types/models/video' +import { getAbuseActivityPubUrl } from '../url' +import { unicastTo } from './utils' -function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { - if (!video.VideoChannel.Account.Actor.serverId) return // Local user +function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLight, t: Transaction) { + if (!flaggedAccount.Actor.serverId) return // Local user - const url = getVideoAbuseActivityPubUrl(videoAbuse) + const url = getAbuseActivityPubUrl(abuse) - logger.info('Creating job to send video abuse %s.', url) + logger.info('Creating job to send abuse %s.', url) // Custom audience, we only send the abuse to the origin instance - const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } - const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience) + const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } + const flagActivity = buildFlagActivity(url, byActor, abuse, audience) - t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.getSharedInbox())) + t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) } -function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { +function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { if (!audience) audience = getAudience(byActor) const activity = Object.assign( { id: url, actor: byActor.url }, - videoAbuse.toActivityPubObject() + abuse.toActivityPubObject() ) return audiencify(activity, audience) @@ -35,5 +34,5 @@ function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbus // --------------------------------------------------------------------------- export { - sendVideoAbuse + sendAbuse } diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index 7f98751a1..b54e038a4 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts @@ -5,10 +5,10 @@ import { MActorId, MActorUrl, MCommentId, - MVideoAbuseId, MVideoId, MVideoUrl, - MVideoUUID + MVideoUUID, + MAbuseId } from '../../types/models' import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' import { MVideoFileVideoUUID } from '../../types/models/video/video-file' @@ -48,8 +48,8 @@ function getAccountActivityPubUrl (accountName: string) { return WEBSERVER.URL + '/accounts/' + accountName } -function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { - return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id +function getAbuseActivityPubUrl (abuse: MAbuseId) { + return WEBSERVER.URL + '/admin/abuses/' + abuse.id } function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { @@ -118,7 +118,7 @@ export { getVideoCacheStreamingPlaylistActivityPubUrl, getVideoChannelActivityPubUrl, getAccountActivityPubUrl, - getVideoAbuseActivityPubUrl, + getAbuseActivityPubUrl, getActorFollowActivityPubUrl, getActorFollowAcceptActivityPubUrl, getVideoAnnounceActivityPubUrl, diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index c08732b48..e821aea5f 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts @@ -1,26 +1,20 @@ +import { readFileSync } from 'fs-extra' +import { merge } from 'lodash' import { createTransport, Transporter } from 'nodemailer' +import { join } from 'path' +import { VideoChannelModel } from '@server/models/video/video-channel' +import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' +import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' +import { Abuse, EmailPayload } from '@shared/models' +import { SendEmailOptions } from '../../shared/models/server/emailer.model' import { isTestInstance, root } from '../helpers/core-utils' import { bunyanLogger, logger } from '../helpers/logger' import { CONFIG, isEmailEnabled } from '../initializers/config' -import { JobQueue } from './job-queue' -import { readFileSync } from 'fs-extra' import { WEBSERVER } from '../initializers/constants' -import { - MCommentOwnerVideo, - MVideo, - MVideoAbuseVideo, - MVideoAccountLight, - MVideoBlacklistLightVideo, - MVideoBlacklistVideo -} from '../types/models/video' -import { MActorFollowActors, MActorFollowFull, MUser } from '../types/models' -import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' -import { EmailPayload } from '@shared/models' -import { join } from 'path' -import { VideoAbuse } from '../../shared/models/videos' -import { SendEmailOptions } from '../../shared/models/server/emailer.model' -import { merge } from 'lodash' -import { VideoChannelModel } from '@server/models/video/video-channel' +import { MAbuseFull, MActorFollowActors, MActorFollowFull, MUser } from '../types/models' +import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' +import { JobQueue } from './job-queue' + const Email = require('email-templates') class Emailer { @@ -288,28 +282,70 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoAbuseModeratorsNotification (to: string[], parameters: { - videoAbuse: VideoAbuse - videoAbuseInstance: MVideoAbuseVideo + addAbuseModeratorsNotification (to: string[], parameters: { + abuse: Abuse + abuseInstance: MAbuseFull reporter: string }) { - const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id - const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() + const { abuse, abuseInstance, reporter } = parameters - const emailPayload: EmailPayload = { - template: 'video-abuse-new', - to, - subject: `New video abuse report from ${parameters.reporter}`, - locals: { - videoUrl, - videoAbuseUrl, - videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), - videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), - videoAbuse: parameters.videoAbuse, - reporter: parameters.reporter, - action: { - text: 'View report #' + parameters.videoAbuse.id, - url: videoAbuseUrl + const action = { + text: 'View report #' + abuse.id, + url: WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id + } + + let emailPayload: EmailPayload + + if (abuseInstance.VideoAbuse) { + const video = abuseInstance.VideoAbuse.Video + const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() + + emailPayload = { + template: 'video-abuse-new', + to, + subject: `New video abuse report from ${reporter}`, + locals: { + videoUrl, + isLocal: video.remote === false, + videoCreatedAt: new Date(video.createdAt).toLocaleString(), + videoPublishedAt: new Date(video.publishedAt).toLocaleString(), + videoName: video.name, + reason: abuse.reason, + videoChannel: video.VideoChannel, + action + } + } + } else if (abuseInstance.VideoCommentAbuse) { + const comment = abuseInstance.VideoCommentAbuse.VideoComment + const commentUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() + ';threadId=' + comment.getThreadId() + + emailPayload = { + template: 'comment-abuse-new', + to, + subject: `New comment abuse report from ${reporter}`, + locals: { + commentUrl, + isLocal: comment.isOwned(), + commentCreatedAt: new Date(comment.createdAt).toLocaleString(), + reason: abuse.reason, + flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(), + action + } + } + } else { + const account = abuseInstance.FlaggedAccount + const accountUrl = account.getClientUrl() + + emailPayload = { + template: 'account-abuse-new', + to, + subject: `New account abuse report from ${reporter}`, + locals: { + accountUrl, + accountDisplayName: account.getDisplayName(), + isLocal: account.isOwned(), + reason: abuse.reason, + action } } } diff --git a/server/lib/emails/account-abuse-new/html.pug b/server/lib/emails/account-abuse-new/html.pug new file mode 100644 index 000000000..06be8025b --- /dev/null +++ b/server/lib/emails/account-abuse-new/html.pug @@ -0,0 +1,14 @@ +extends ../common/greetings +include ../common/mixins.pug + +block title + | An account is pending moderation + +block content + p + | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account " + a(href=accountUrl) #{accountDisplayName} + + p The reporter, #{reporter}, cited the following reason(s): + blockquote #{reason} + br(style="display: none;") diff --git a/server/lib/emails/common/mixins.pug b/server/lib/emails/common/mixins.pug index 76b805a24..831211864 100644 --- a/server/lib/emails/common/mixins.pug +++ b/server/lib/emails/common/mixins.pug @@ -1,3 +1,7 @@ mixin channel(channel) - var handle = `${channel.name}@${channel.host}` - | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] \ No newline at end of file + | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] + +mixin account(account) + - var handle = `${account.name}@${account.host}` + | #[a(href=`${WEBSERVER.URL}/accounts/${handle}` title=handle) #{account.displayName}] diff --git a/server/lib/emails/video-abuse-new/html.pug b/server/lib/emails/video-abuse-new/html.pug index 999c89d26..a1acdabdc 100644 --- a/server/lib/emails/video-abuse-new/html.pug +++ b/server/lib/emails/video-abuse-new/html.pug @@ -6,13 +6,13 @@ block title block content p - | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video " - a(href=videoUrl) #{videoAbuse.video.name} - | " by #[+channel(videoAbuse.video.channel)] + | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}video " + a(href=videoUrl) #{videoName} + | " by #[+channel(videoChannel)] if videoPublishedAt | , published the #{videoPublishedAt}. else | , uploaded the #{videoCreatedAt} but not yet published. p The reporter, #{reporter}, cited the following reason(s): - blockquote #{videoAbuse.reason} + blockquote #{reason} br(style="display: none;") diff --git a/server/lib/emails/video-comment-abuse-new/html.pug b/server/lib/emails/video-comment-abuse-new/html.pug new file mode 100644 index 000000000..170b79576 --- /dev/null +++ b/server/lib/emails/video-comment-abuse-new/html.pug @@ -0,0 +1,15 @@ +extends ../common/greetings +include ../common/mixins.pug + +block title + | A comment is pending moderation + +block content + p + | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment " + a(href=commentUrl) of #{flaggedAccount} + | created on #{commentCreatedAt} + + p The reporter, #{reporter}, cited the following reason(s): + blockquote #{reason} + br(style="display: none;") diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index 60d1b4053..4fc9cd747 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts @@ -1,15 +1,33 @@ -import { VideoModel } from '../models/video/video' -import { VideoCommentModel } from '../models/video/video-comment' -import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' +import { PathLike } from 'fs-extra' +import { Transaction } from 'sequelize/types' +import { AbuseAuditView, auditLoggerFactory } from '@server/helpers/audit-logger' +import { logger } from '@server/helpers/logger' +import { AbuseModel } from '@server/models/abuse/abuse' +import { VideoAbuseModel } from '@server/models/abuse/video-abuse' +import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' +import { VideoFileModel } from '@server/models/video/video-file' +import { FilteredModelAttributes } from '@server/types' +import { + MAbuseFull, + MAccountDefault, + MAccountLight, + MCommentAbuseAccountVideo, + MCommentOwnerVideo, + MUser, + MVideoAbuseVideoFull, + MVideoAccountLightBlacklistAllFiles +} from '@server/types/models' +import { ActivityCreate } from '../../shared/models/activitypub' +import { VideoTorrentObject } from '../../shared/models/activitypub/objects' +import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' +import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' import { UserModel } from '../models/account/user' -import { VideoTorrentObject } from '../../shared/models/activitypub/objects' -import { ActivityCreate } from '../../shared/models/activitypub' import { ActorModel } from '../models/activitypub/actor' -import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' -import { VideoFileModel } from '@server/models/video/video-file' -import { PathLike } from 'fs-extra' -import { MUser } from '@server/types/models' +import { VideoModel } from '../models/video/video' +import { VideoCommentModel } from '../models/video/video-comment' +import { sendAbuse } from './activitypub/send/send-flag' +import { Notifier } from './notifier' export type AcceptResult = { accepted: boolean @@ -73,6 +91,89 @@ function isPostImportVideoAccepted (object: { return { accepted: true } } +async function createVideoAbuse (options: { + baseAbuse: FilteredModelAttributes + videoInstance: MVideoAccountLightBlacklistAllFiles + startAt: number + endAt: number + transaction: Transaction + reporterAccount: MAccountDefault +}) { + const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount } = options + + const associateFun = async (abuseInstance: MAbuseFull) => { + const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({ + abuseId: abuseInstance.id, + videoId: videoInstance.id, + startAt: startAt, + endAt: endAt + }, { transaction }) + + videoAbuseInstance.Video = videoInstance + abuseInstance.VideoAbuse = videoAbuseInstance + + return { isOwned: videoInstance.isOwned() } + } + + return createAbuse({ + base: baseAbuse, + reporterAccount, + flaggedAccount: videoInstance.VideoChannel.Account, + transaction, + associateFun + }) +} + +function createVideoCommentAbuse (options: { + baseAbuse: FilteredModelAttributes + commentInstance: MCommentOwnerVideo + transaction: Transaction + reporterAccount: MAccountDefault +}) { + const { baseAbuse, commentInstance, transaction, reporterAccount } = options + + const associateFun = async (abuseInstance: MAbuseFull) => { + const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({ + abuseId: abuseInstance.id, + videoCommentId: commentInstance.id + }, { transaction }) + + commentAbuseInstance.VideoComment = commentInstance + abuseInstance.VideoCommentAbuse = commentAbuseInstance + + return { isOwned: commentInstance.isOwned() } + } + + return createAbuse({ + base: baseAbuse, + reporterAccount, + flaggedAccount: commentInstance.Account, + transaction, + associateFun + }) +} + +function createAccountAbuse (options: { + baseAbuse: FilteredModelAttributes + accountInstance: MAccountDefault + transaction: Transaction + reporterAccount: MAccountDefault +}) { + const { baseAbuse, accountInstance, transaction, reporterAccount } = options + + const associateFun = async () => { + return { isOwned: accountInstance.isOwned() } + } + + return createAbuse({ + base: baseAbuse, + reporterAccount, + flaggedAccount: accountInstance, + transaction, + associateFun + }) +} + export { isLocalVideoAccepted, isLocalVideoThreadAccepted, @@ -80,5 +181,48 @@ export { isRemoteVideoCommentAccepted, isLocalVideoCommentReplyAccepted, isPreImportVideoAccepted, - isPostImportVideoAccepted + isPostImportVideoAccepted, + + createAbuse, + createVideoAbuse, + createVideoCommentAbuse, + createAccountAbuse +} + +// --------------------------------------------------------------------------- + +async function createAbuse (options: { + base: FilteredModelAttributes + reporterAccount: MAccountDefault + flaggedAccount: MAccountLight + associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean} > + transaction: Transaction +}) { + const { base, reporterAccount, flaggedAccount, associateFun, transaction } = options + const auditLogger = auditLoggerFactory('abuse') + + const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id }) + const abuseInstance: MAbuseFull = await AbuseModel.create(abuseAttributes, { transaction }) + + abuseInstance.ReporterAccount = reporterAccount + abuseInstance.FlaggedAccount = flaggedAccount + + const { isOwned } = await associateFun(abuseInstance) + + if (isOwned === false) { + await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) + } + + const abuseJSON = abuseInstance.toFormattedJSON() + auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) + + Notifier.Instance.notifyOnNewAbuse({ + abuse: abuseJSON, + abuseInstance, + reporter: reporterAccount.Actor.getIdentifier() + }) + + logger.info('Abuse report %d created.', abuseInstance.id) + + return abuseJSON } diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 943a087d2..40cff66d2 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts @@ -8,23 +8,18 @@ import { MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/types/models/user' +import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' import { MVideoImportVideo } from '@server/types/models/video/video-import' +import { Abuse } from '@shared/models' import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' -import { VideoAbuse, VideoPrivacy, VideoState } from '../../shared/models/videos' +import { VideoPrivacy, VideoState } from '../../shared/models/videos' import { logger } from '../helpers/logger' import { CONFIG } from '../initializers/config' import { AccountBlocklistModel } from '../models/account/account-blocklist' import { UserModel } from '../models/account/user' import { UserNotificationModel } from '../models/account/user-notification' -import { MAccountServer, MActorFollowFull } from '../types/models' -import { - MCommentOwnerVideo, - MVideoAbuseVideo, - MVideoAccountLight, - MVideoBlacklistLightVideo, - MVideoBlacklistVideo, - MVideoFullLight -} from '../types/models/video' +import { MAbuseFull, MAbuseVideo, MAccountServer, MActorFollowFull } from '../types/models' +import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' import { isBlockedByServerOrAccount } from './blocklist' import { Emailer } from './emailer' import { PeerTubeSocket } from './peertube-socket' @@ -78,9 +73,9 @@ class Notifier { .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) } - notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void { - this.notifyModeratorsOfNewVideoAbuse(parameters) - .catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err })) + notifyOnNewAbuse (parameters: { abuse: Abuse, abuseInstance: MAbuseFull, reporter: string }): void { + this.notifyModeratorsOfNewAbuse(parameters) + .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err })) } notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { @@ -354,33 +349,37 @@ class Notifier { return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) } - private async notifyModeratorsOfNewVideoAbuse (parameters: { - videoAbuse: VideoAbuse - videoAbuseInstance: MVideoAbuseVideo + private async notifyModeratorsOfNewAbuse (parameters: { + abuse: Abuse + abuseInstance: MAbuseFull reporter: string }) { - const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) + const { abuse, abuseInstance } = parameters + + const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) if (moderators.length === 0) return - logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url) + const url = abuseInstance.VideoAbuse?.Video?.url || abuseInstance.VideoCommentAbuse?.VideoComment?.url + + logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url) function settingGetter (user: MUserWithNotificationSetting) { return user.NotificationSetting.videoAbuseAsModerator } async function notificationCreator (user: MUserWithNotificationSetting) { - const notification: UserNotificationModelForApi = await UserNotificationModel.create({ + const notification = await UserNotificationModel.create({ type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, userId: user.id, - videoAbuseId: parameters.videoAbuse.id + abuseId: abuse.id }) - notification.VideoAbuse = parameters.videoAbuseInstance + notification.Abuse = abuseInstance return notification } function emailSender (emails: string[]) { - return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters) + return Emailer.Instance.addAbuseModeratorsNotification(emails, parameters) } return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) -- cgit v1.2.3