diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/videos/abuse.ts | 22 | ||||
-rw-r--r-- | server/helpers/activitypub.ts | 6 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 5 | ||||
-rw-r--r-- | server/lib/activitypub/process-create.ts | 43 | ||||
-rw-r--r-- | server/lib/activitypub/send-request.ts | 25 | ||||
-rw-r--r-- | server/models/video/video-abuse-interface.ts | 14 | ||||
-rw-r--r-- | server/models/video/video-abuse.ts | 44 |
7 files changed, 100 insertions, 59 deletions
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 { | |||
18 | } from '../../../middlewares' | 18 | } from '../../../middlewares' |
19 | import { VideoInstance } from '../../../models' | 19 | import { VideoInstance } from '../../../models' |
20 | import { VideoAbuseCreate, UserRight } from '../../../../shared' | 20 | import { VideoAbuseCreate, UserRight } from '../../../../shared' |
21 | import { sendVideoAbuse } from '../../../lib/index' | ||
21 | 22 | ||
22 | const abuseVideoRouter = express.Router() | 23 | const abuseVideoRouter = express.Router() |
23 | 24 | ||
@@ -63,28 +64,21 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express. | |||
63 | 64 | ||
64 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 65 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
65 | const videoInstance = res.locals.video as VideoInstance | 66 | const videoInstance = res.locals.video as VideoInstance |
66 | const reporterUsername = res.locals.oauth.token.User.username | 67 | const reporterAccount = res.locals.oauth.token.User.Account |
67 | const body: VideoAbuseCreate = req.body | 68 | const body: VideoAbuseCreate = req.body |
68 | 69 | ||
69 | const abuseToCreate = { | 70 | const abuseToCreate = { |
70 | reporterUsername, | 71 | reporterAccountId: reporterAccount.id, |
71 | reason: body.reason, | 72 | reason: body.reason, |
72 | videoId: videoInstance.id, | 73 | videoId: videoInstance.id |
73 | reporterServerId: null // This is our server that reported this abuse | ||
74 | } | 74 | } |
75 | 75 | ||
76 | await db.sequelize.transaction(async t => { | 76 | await db.sequelize.transaction(async t => { |
77 | const abuse = await db.VideoAbuse.create(abuseToCreate, { transaction: t }) | 77 | const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t }) |
78 | // We send the information to the destination server | 78 | |
79 | // We send the video abuse to the origin server | ||
79 | if (videoInstance.isOwned() === false) { | 80 | if (videoInstance.isOwned() === false) { |
80 | const reportData = { | 81 | await sendVideoAbuse(reporterAccount, videoAbuseInstance, videoInstance, t) |
81 | reporterUsername, | ||
82 | reportReason: abuse.reason, | ||
83 | videoUUID: videoInstance.uuid | ||
84 | } | ||
85 | |||
86 | // await friends.reportAbuseVideoToFriend(reportData, videoInstance, t) | ||
87 | // TODO: send abuse to origin server | ||
88 | } | 82 | } |
89 | }) | 83 | }) |
90 | 84 | ||
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 | |||
22 | return doRequestAndSaveToFile(options, thumbnailPath) | 22 | return doRequestAndSaveToFile(options, thumbnailPath) |
23 | } | 23 | } |
24 | 24 | ||
25 | function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account', id: string) { | 25 | function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account' | 'videoAbuse', id: string) { |
26 | if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id | 26 | if (type === 'video') return CONFIG.WEBSERVER.URL + '/videos/watch/' + id |
27 | else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id | 27 | else if (type === 'videoChannel') return CONFIG.WEBSERVER.URL + '/video-channels/' + id |
28 | else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id | 28 | else if (type === 'account') return CONFIG.WEBSERVER.URL + '/account/' + id |
29 | else if (type === 'videoAbuse') return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + id | ||
29 | 30 | ||
30 | return '' | 31 | return '' |
31 | } | 32 | } |
@@ -134,7 +135,8 @@ function activityPubContextify <T> (data: T) { | |||
134 | 'nsfw': 'as:sensitive', | 135 | 'nsfw': 'as:sensitive', |
135 | 'language': 'http://schema.org/inLanguage', | 136 | 'language': 'http://schema.org/inLanguage', |
136 | 'views': 'http://schema.org/Number', | 137 | 'views': 'http://schema.org/Number', |
137 | 'size': 'http://schema.org/Number' | 138 | 'size': 'http://schema.org/Number', |
139 | 'VideoChannel': 'https://peertu.be/ns/VideoChannel' | ||
138 | } | 140 | } |
139 | ] | 141 | ] |
140 | }) | 142 | }) |
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) { | |||
112 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | 112 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) |
113 | } | 113 | } |
114 | 114 | ||
115 | function isVideoAbuseReporterUsernameValid (value: string) { | ||
116 | return isUserUsernameValid(value) | ||
117 | } | ||
118 | |||
119 | function isVideoViewsValid (value: string) { | 115 | function isVideoViewsValid (value: string) { |
120 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS) | 116 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS) |
121 | } | 117 | } |
@@ -209,7 +205,6 @@ export { | |||
209 | isVideoThumbnailDataValid, | 205 | isVideoThumbnailDataValid, |
210 | isVideoFileExtnameValid, | 206 | isVideoFileExtnameValid, |
211 | isVideoAbuseReasonValid, | 207 | isVideoAbuseReasonValid, |
212 | isVideoAbuseReporterUsernameValid, | ||
213 | isVideoFile, | 208 | isVideoFile, |
214 | isVideoViewsValid, | 209 | isVideoViewsValid, |
215 | isVideoLikesValid, | 210 | 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 @@ | |||
1 | import { ActivityCreate, VideoChannelObject, VideoTorrentObject } from '../../../shared' | 1 | import { ActivityCreate, VideoChannelObject } from '../../../shared' |
2 | import { ActivityAdd } from '../../../shared/models/activitypub/activity' | 2 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' |
3 | import { generateThumbnailFromUrl, logger, retryTransactionWrapper } from '../../helpers' | 3 | import { logger, retryTransactionWrapper } from '../../helpers' |
4 | import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub' | ||
4 | import { database as db } from '../../initializers' | 5 | import { database as db } from '../../initializers' |
5 | import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' | ||
6 | import Bluebird = require('bluebird') | ||
7 | import { AccountInstance } from '../../models/account/account-interface' | 6 | import { AccountInstance } from '../../models/account/account-interface' |
8 | import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub' | ||
9 | 7 | ||
10 | async function processCreateActivity (activity: ActivityCreate) { | 8 | async function processCreateActivity (activity: ActivityCreate) { |
11 | const activityObject = activity.object | 9 | const activityObject = activity.object |
@@ -14,6 +12,8 @@ async function processCreateActivity (activity: ActivityCreate) { | |||
14 | 12 | ||
15 | if (activityType === 'VideoChannel') { | 13 | if (activityType === 'VideoChannel') { |
16 | return processCreateVideoChannel(account, activityObject as VideoChannelObject) | 14 | return processCreateVideoChannel(account, activityObject as VideoChannelObject) |
15 | } else if (activityType === 'Flag') { | ||
16 | return processCreateVideoAbuse(account, activityObject as VideoAbuseObject) | ||
17 | } | 17 | } |
18 | 18 | ||
19 | logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) | 19 | logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) |
@@ -62,3 +62,34 @@ async function addRemoteVideoChannel (account: AccountInstance, videoChannelToCr | |||
62 | 62 | ||
63 | logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid) | 63 | logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid) |
64 | } | 64 | } |
65 | |||
66 | function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { | ||
67 | const options = { | ||
68 | arguments: [ account, videoAbuseToCreateData ], | ||
69 | errorMessage: 'Cannot insert the remote video abuse with many retries.' | ||
70 | } | ||
71 | |||
72 | return retryTransactionWrapper(addRemoteVideoAbuse, options) | ||
73 | } | ||
74 | |||
75 | async function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { | ||
76 | logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) | ||
77 | |||
78 | return db.sequelize.transaction(async t => { | ||
79 | const video = await db.Video.loadByUrl(videoAbuseToCreateData.object, t) | ||
80 | if (!video) { | ||
81 | logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object) | ||
82 | return | ||
83 | } | ||
84 | |||
85 | const videoAbuseData = { | ||
86 | reporterAccountId: account.id, | ||
87 | reason: videoAbuseToCreateData.content, | ||
88 | videoId: video.id | ||
89 | } | ||
90 | |||
91 | await db.VideoAbuse.create(videoAbuseData) | ||
92 | |||
93 | logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) | ||
94 | }) | ||
95 | } | ||
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 { | |||
9 | import { httpRequestJobScheduler } from '../jobs' | 9 | import { httpRequestJobScheduler } from '../jobs' |
10 | import { signObject, activityPubContextify } from '../../helpers' | 10 | import { signObject, activityPubContextify } from '../../helpers' |
11 | import { Activity } from '../../../shared' | 11 | import { Activity } from '../../../shared' |
12 | import { VideoAbuseInstance } from '../../models/video/video-abuse-interface' | ||
13 | import { getActivityPubUrl } from '../../helpers/activitypub' | ||
12 | 14 | ||
13 | async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { | 15 | async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { |
14 | const videoChannelObject = videoChannel.toActivityPubObject() | 16 | const videoChannelObject = videoChannel.toActivityPubObject() |
@@ -56,16 +58,28 @@ async function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transac | |||
56 | return broadcastToFollowers(data, account, t) | 58 | return broadcastToFollowers(data, account, t) |
57 | } | 59 | } |
58 | 60 | ||
61 | async function sendVideoAbuse ( | ||
62 | fromAccount: AccountInstance, | ||
63 | videoAbuse: VideoAbuseInstance, | ||
64 | video: VideoInstance, | ||
65 | t: Sequelize.Transaction | ||
66 | ) { | ||
67 | const url = getActivityPubUrl('videoAbuse', videoAbuse.id.toString()) | ||
68 | const data = await createActivityData(url, fromAccount, video.url) | ||
69 | |||
70 | return unicastTo(data, video.VideoChannel.Account.sharedInboxUrl, t) | ||
71 | } | ||
72 | |||
59 | async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { | 73 | async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { |
60 | const data = await acceptActivityData(fromAccount) | 74 | const data = await acceptActivityData(fromAccount) |
61 | 75 | ||
62 | return unicastTo(data, toAccount, t) | 76 | return unicastTo(data, toAccount.inboxUrl, t) |
63 | } | 77 | } |
64 | 78 | ||
65 | async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { | 79 | async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { |
66 | const data = await followActivityData(toAccount.url, fromAccount) | 80 | const data = await followActivityData(toAccount.url, fromAccount) |
67 | 81 | ||
68 | return unicastTo(data, toAccount, t) | 82 | return unicastTo(data, toAccount.inboxUrl, t) |
69 | } | 83 | } |
70 | 84 | ||
71 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
@@ -79,7 +93,8 @@ export { | |||
79 | sendDeleteVideo, | 93 | sendDeleteVideo, |
80 | sendDeleteAccount, | 94 | sendDeleteAccount, |
81 | sendAccept, | 95 | sendAccept, |
82 | sendFollow | 96 | sendFollow, |
97 | sendVideoAbuse | ||
83 | } | 98 | } |
84 | 99 | ||
85 | // --------------------------------------------------------------------------- | 100 | // --------------------------------------------------------------------------- |
@@ -95,9 +110,9 @@ async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: | |||
95 | return httpRequestJobScheduler.createJob(t, 'httpRequestBroadcastHandler', jobPayload) | 110 | return httpRequestJobScheduler.createJob(t, 'httpRequestBroadcastHandler', jobPayload) |
96 | } | 111 | } |
97 | 112 | ||
98 | async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) { | 113 | async function unicastTo (data: any, toAccountUrl: string, t: Sequelize.Transaction) { |
99 | const jobPayload = { | 114 | const jobPayload = { |
100 | uris: [ toAccount.inboxUrl ], | 115 | uris: [ toAccountUrl ], |
101 | body: data | 116 | body: data |
102 | } | 117 | } |
103 | 118 | ||
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | 1 | import * as Promise from 'bluebird' |
3 | 2 | import * as Sequelize from 'sequelize' | |
4 | import { ServerInstance } from '../server/server-interface' | ||
5 | import { ResultList } from '../../../shared' | 3 | import { ResultList } from '../../../shared' |
6 | |||
7 | // Don't use barrel, import just what we need | ||
8 | import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model' | 4 | import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model' |
5 | import { AccountInstance } from '../account/account-interface' | ||
6 | import { ServerInstance } from '../server/server-interface' | ||
7 | import { VideoInstance } from './video-interface' | ||
9 | 8 | ||
10 | export namespace VideoAbuseMethods { | 9 | export namespace VideoAbuseMethods { |
11 | export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse | 10 | export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse |
@@ -18,9 +17,12 @@ export interface VideoAbuseClass { | |||
18 | } | 17 | } |
19 | 18 | ||
20 | export interface VideoAbuseAttributes { | 19 | export interface VideoAbuseAttributes { |
21 | reporterUsername: string | ||
22 | reason: string | 20 | reason: string |
23 | videoId: number | 21 | videoId: number |
22 | reporterAccountId: number | ||
23 | |||
24 | Account?: AccountInstance | ||
25 | Video?: VideoInstance | ||
24 | } | 26 | } |
25 | 27 | ||
26 | export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> { | 28 | export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> { |
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | import { CONFIG } from '../../initializers' | 3 | import { CONFIG } from '../../initializers' |
4 | import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers' | 4 | import { isVideoAbuseReasonValid } from '../../helpers' |
5 | 5 | ||
6 | import { addMethodsToModel, getSort } from '../utils' | 6 | import { addMethodsToModel, getSort } from '../utils' |
7 | import { | 7 | import { |
@@ -18,16 +18,6 @@ let listForApi: VideoAbuseMethods.ListForApi | |||
18 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 18 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
19 | VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse', | 19 | VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse', |
20 | { | 20 | { |
21 | reporterUsername: { | ||
22 | type: DataTypes.STRING, | ||
23 | allowNull: false, | ||
24 | validate: { | ||
25 | reporterUsernameValid: value => { | ||
26 | const res = isVideoAbuseReporterUsernameValid(value) | ||
27 | if (res === false) throw new Error('Video abuse reporter username is not valid.') | ||
28 | } | ||
29 | } | ||
30 | }, | ||
31 | reason: { | 21 | reason: { |
32 | type: DataTypes.STRING, | 22 | type: DataTypes.STRING, |
33 | allowNull: false, | 23 | allowNull: false, |
@@ -45,7 +35,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
45 | fields: [ 'videoId' ] | 35 | fields: [ 'videoId' ] |
46 | }, | 36 | }, |
47 | { | 37 | { |
48 | fields: [ 'reporterServerId' ] | 38 | fields: [ 'reporterAccountId' ] |
49 | } | 39 | } |
50 | ] | 40 | ] |
51 | } | 41 | } |
@@ -69,8 +59,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
69 | toFormattedJSON = function (this: VideoAbuseInstance) { | 59 | toFormattedJSON = function (this: VideoAbuseInstance) { |
70 | let reporterServerHost | 60 | let reporterServerHost |
71 | 61 | ||
72 | if (this.Server) { | 62 | if (this.Account.Server) { |
73 | reporterServerHost = this.Server.host | 63 | reporterServerHost = this.Account.Server.host |
74 | } else { | 64 | } else { |
75 | // It means it's our video | 65 | // It means it's our video |
76 | reporterServerHost = CONFIG.WEBSERVER.HOST | 66 | reporterServerHost = CONFIG.WEBSERVER.HOST |
@@ -78,10 +68,12 @@ toFormattedJSON = function (this: VideoAbuseInstance) { | |||
78 | 68 | ||
79 | const json = { | 69 | const json = { |
80 | id: this.id, | 70 | id: this.id, |
81 | reporterServerHost, | ||
82 | reason: this.reason, | 71 | reason: this.reason, |
83 | reporterUsername: this.reporterUsername, | 72 | reporterUsername: this.Account.name, |
84 | videoId: this.videoId, | 73 | reporterServerHost, |
74 | videoId: this.Video.id, | ||
75 | videoUUID: this.Video.uuid, | ||
76 | videoName: this.Video.name, | ||
85 | createdAt: this.createdAt | 77 | createdAt: this.createdAt |
86 | } | 78 | } |
87 | 79 | ||
@@ -91,9 +83,9 @@ toFormattedJSON = function (this: VideoAbuseInstance) { | |||
91 | // ------------------------------ STATICS ------------------------------ | 83 | // ------------------------------ STATICS ------------------------------ |
92 | 84 | ||
93 | function associate (models) { | 85 | function associate (models) { |
94 | VideoAbuse.belongsTo(models.Server, { | 86 | VideoAbuse.belongsTo(models.Account, { |
95 | foreignKey: { | 87 | foreignKey: { |
96 | name: 'reporterServerId', | 88 | name: 'reporterAccountId', |
97 | allowNull: true | 89 | allowNull: true |
98 | }, | 90 | }, |
99 | onDelete: 'CASCADE' | 91 | onDelete: 'CASCADE' |
@@ -115,8 +107,18 @@ listForApi = function (start: number, count: number, sort: string) { | |||
115 | order: [ getSort(sort) ], | 107 | order: [ getSort(sort) ], |
116 | include: [ | 108 | include: [ |
117 | { | 109 | { |
118 | model: VideoAbuse['sequelize'].models.Server, | 110 | model: VideoAbuse['sequelize'].models.Account, |
119 | required: false | 111 | required: true, |
112 | include: [ | ||
113 | { | ||
114 | model: VideoAbuse['sequelize'].models.Server, | ||
115 | required: false | ||
116 | } | ||
117 | ] | ||
118 | }, | ||
119 | { | ||
120 | model: VideoAbuse['sequelize'].models.Video, | ||
121 | required: true | ||
120 | } | 122 | } |
121 | ] | 123 | ] |
122 | } | 124 | } |