aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-15 15:12:23 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:52 +0100
commit8e13fa7d09e9925b4559cbba6c5d72c5ff1bd391 (patch)
treea59070f4dc6b52a5b422bd31a8eb8ea3bff831a0 /server
parent59c857da5961e2bcddcfd07832783c1e4afcd01a (diff)
downloadPeerTube-8e13fa7d09e9925b4559cbba6c5d72c5ff1bd391.tar.gz
PeerTube-8e13fa7d09e9925b4559cbba6c5d72c5ff1bd391.tar.zst
PeerTube-8e13fa7d09e9925b4559cbba6c5d72c5ff1bd391.zip
Add video abuse to activity pub
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/abuse.ts22
-rw-r--r--server/helpers/activitypub.ts6
-rw-r--r--server/helpers/custom-validators/videos.ts5
-rw-r--r--server/lib/activitypub/process-create.ts43
-rw-r--r--server/lib/activitypub/send-request.ts25
-rw-r--r--server/models/video/video-abuse-interface.ts14
-rw-r--r--server/models/video/video-abuse.ts44
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'
19import { VideoInstance } from '../../../models' 19import { VideoInstance } from '../../../models'
20import { VideoAbuseCreate, UserRight } from '../../../../shared' 20import { VideoAbuseCreate, UserRight } from '../../../../shared'
21import { sendVideoAbuse } from '../../../lib/index'
21 22
22const abuseVideoRouter = express.Router() 23const abuseVideoRouter = express.Router()
23 24
@@ -63,28 +64,21 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.
63 64
64async function reportVideoAbuse (req: express.Request, res: express.Response) { 65async 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
25function getActivityPubUrl (type: 'video' | 'videoChannel' | 'account', id: string) { 25function 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
115function isVideoAbuseReporterUsernameValid (value: string) {
116 return isUserUsernameValid(value)
117}
118
119function isVideoViewsValid (value: string) { 115function 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 @@
1import { ActivityCreate, VideoChannelObject, VideoTorrentObject } from '../../../shared' 1import { ActivityCreate, VideoChannelObject } from '../../../shared'
2import { ActivityAdd } from '../../../shared/models/activitypub/activity' 2import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
3import { generateThumbnailFromUrl, logger, retryTransactionWrapper } from '../../helpers' 3import { logger, retryTransactionWrapper } from '../../helpers'
4import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
4import { database as db } from '../../initializers' 5import { database as db } from '../../initializers'
5import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
6import Bluebird = require('bluebird')
7import { AccountInstance } from '../../models/account/account-interface' 6import { AccountInstance } from '../../models/account/account-interface'
8import { getActivityPubUrl, getOrCreateAccount } from '../../helpers/activitypub'
9 7
10async function processCreateActivity (activity: ActivityCreate) { 8async 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
66function 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
75async 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 {
9import { httpRequestJobScheduler } from '../jobs' 9import { httpRequestJobScheduler } from '../jobs'
10import { signObject, activityPubContextify } from '../../helpers' 10import { signObject, activityPubContextify } from '../../helpers'
11import { Activity } from '../../../shared' 11import { Activity } from '../../../shared'
12import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
13import { getActivityPubUrl } from '../../helpers/activitypub'
12 14
13async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { 15async 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
61async 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
59async function sendAccept (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { 73async 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
65async function sendFollow (fromAccount: AccountInstance, toAccount: AccountInstance, t: Sequelize.Transaction) { 79async 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
98async function unicastTo (data: any, toAccount: AccountInstance, t: Sequelize.Transaction) { 113async 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 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 1import * as Promise from 'bluebird'
3 2import * as Sequelize from 'sequelize'
4import { ServerInstance } from '../server/server-interface'
5import { ResultList } from '../../../shared' 3import { ResultList } from '../../../shared'
6
7// Don't use barrel, import just what we need
8import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model' 4import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model'
5import { AccountInstance } from '../account/account-interface'
6import { ServerInstance } from '../server/server-interface'
7import { VideoInstance } from './video-interface'
9 8
10export namespace VideoAbuseMethods { 9export 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
20export interface VideoAbuseAttributes { 19export 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
26export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> { 28export 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 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3import { CONFIG } from '../../initializers' 3import { CONFIG } from '../../initializers'
4import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers' 4import { isVideoAbuseReasonValid } from '../../helpers'
5 5
6import { addMethodsToModel, getSort } from '../utils' 6import { addMethodsToModel, getSort } from '../utils'
7import { 7import {
@@ -18,16 +18,6 @@ let listForApi: VideoAbuseMethods.ListForApi
18export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 18export 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
69toFormattedJSON = function (this: VideoAbuseInstance) { 59toFormattedJSON = 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
93function associate (models) { 85function 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 }