diff options
author | Chocobozzz <me@florianbigard.com> | 2020-07-01 16:05:30 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-07-10 14:02:41 +0200 |
commit | d95d15598847c7f020aa056e7e6e0c02d2bbf732 (patch) | |
tree | a8a593f1269688caf9e5f99559996f346290fec5 /server/controllers/api | |
parent | 72493e44e9b455a04c4f093ed6c6ffa300b98d8b (diff) | |
download | PeerTube-d95d15598847c7f020aa056e7e6e0c02d2bbf732.tar.gz PeerTube-d95d15598847c7f020aa056e7e6e0c02d2bbf732.tar.zst PeerTube-d95d15598847c7f020aa056e7e6e0c02d2bbf732.zip |
Use 3 tables to represent abuses
Diffstat (limited to 'server/controllers/api')
-rw-r--r-- | server/controllers/api/abuse.ts | 168 | ||||
-rw-r--r-- | server/controllers/api/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/videos/abuse.ts | 109 |
3 files changed, 199 insertions, 80 deletions
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts new file mode 100644 index 000000000..ee046cb3a --- /dev/null +++ b/server/controllers/api/abuse.ts | |||
@@ -0,0 +1,168 @@ | |||
1 | import * as express from 'express' | ||
2 | import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' | ||
3 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared' | ||
6 | import { getFormattedObjects } from '../../helpers/utils' | ||
7 | import { sequelizeTypescript } from '../../initializers/database' | ||
8 | import { | ||
9 | abuseGetValidator, | ||
10 | abuseListValidator, | ||
11 | abuseReportValidator, | ||
12 | abusesSortValidator, | ||
13 | abuseUpdateValidator, | ||
14 | asyncMiddleware, | ||
15 | asyncRetryTransactionMiddleware, | ||
16 | authenticate, | ||
17 | ensureUserHasRight, | ||
18 | paginationValidator, | ||
19 | setDefaultPagination, | ||
20 | setDefaultSort | ||
21 | } from '../../middlewares' | ||
22 | import { AccountModel } from '../../models/account/account' | ||
23 | |||
24 | const abuseRouter = express.Router() | ||
25 | |||
26 | abuseRouter.get('/abuse', | ||
27 | authenticate, | ||
28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
29 | paginationValidator, | ||
30 | abusesSortValidator, | ||
31 | setDefaultSort, | ||
32 | setDefaultPagination, | ||
33 | abuseListValidator, | ||
34 | asyncMiddleware(listAbuses) | ||
35 | ) | ||
36 | abuseRouter.put('/:videoId/abuse/:id', | ||
37 | authenticate, | ||
38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
39 | asyncMiddleware(abuseUpdateValidator), | ||
40 | asyncRetryTransactionMiddleware(updateAbuse) | ||
41 | ) | ||
42 | abuseRouter.post('/:videoId/abuse', | ||
43 | authenticate, | ||
44 | asyncMiddleware(abuseReportValidator), | ||
45 | asyncRetryTransactionMiddleware(reportAbuse) | ||
46 | ) | ||
47 | abuseRouter.delete('/:videoId/abuse/:id', | ||
48 | authenticate, | ||
49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
50 | asyncMiddleware(abuseGetValidator), | ||
51 | asyncRetryTransactionMiddleware(deleteAbuse) | ||
52 | ) | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | export { | ||
57 | abuseRouter, | ||
58 | |||
59 | // FIXME: deprecated in 2.3. Remove these exports | ||
60 | listAbuses, | ||
61 | updateAbuse, | ||
62 | deleteAbuse, | ||
63 | reportAbuse | ||
64 | } | ||
65 | |||
66 | // --------------------------------------------------------------------------- | ||
67 | |||
68 | async function listAbuses (req: express.Request, res: express.Response) { | ||
69 | const user = res.locals.oauth.token.user | ||
70 | const serverActor = await getServerActor() | ||
71 | |||
72 | const resultList = await AbuseModel.listForApi({ | ||
73 | start: req.query.start, | ||
74 | count: req.query.count, | ||
75 | sort: req.query.sort, | ||
76 | id: req.query.id, | ||
77 | filter: 'video', | ||
78 | predefinedReason: req.query.predefinedReason, | ||
79 | search: req.query.search, | ||
80 | state: req.query.state, | ||
81 | videoIs: req.query.videoIs, | ||
82 | searchReporter: req.query.searchReporter, | ||
83 | searchReportee: req.query.searchReportee, | ||
84 | searchVideo: req.query.searchVideo, | ||
85 | searchVideoChannel: req.query.searchVideoChannel, | ||
86 | serverAccountId: serverActor.Account.id, | ||
87 | user | ||
88 | }) | ||
89 | |||
90 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
91 | } | ||
92 | |||
93 | async function updateAbuse (req: express.Request, res: express.Response) { | ||
94 | const abuse = res.locals.abuse | ||
95 | |||
96 | if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment | ||
97 | if (req.body.state !== undefined) abuse.state = req.body.state | ||
98 | |||
99 | await sequelizeTypescript.transaction(t => { | ||
100 | return abuse.save({ transaction: t }) | ||
101 | }) | ||
102 | |||
103 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
104 | |||
105 | return res.type('json').status(204).end() | ||
106 | } | ||
107 | |||
108 | async function deleteAbuse (req: express.Request, res: express.Response) { | ||
109 | const abuse = res.locals.abuse | ||
110 | |||
111 | await sequelizeTypescript.transaction(t => { | ||
112 | return abuse.destroy({ transaction: t }) | ||
113 | }) | ||
114 | |||
115 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
116 | |||
117 | return res.type('json').status(204).end() | ||
118 | } | ||
119 | |||
120 | async function reportAbuse (req: express.Request, res: express.Response) { | ||
121 | const videoInstance = res.locals.videoAll | ||
122 | const commentInstance = res.locals.videoCommentFull | ||
123 | const accountInstance = res.locals.account | ||
124 | |||
125 | const body: AbuseCreate = req.body | ||
126 | |||
127 | const { id } = await sequelizeTypescript.transaction(async t => { | ||
128 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
129 | const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r]) | ||
130 | |||
131 | const baseAbuse = { | ||
132 | reporterAccountId: reporterAccount.id, | ||
133 | reason: body.reason, | ||
134 | state: AbuseState.PENDING, | ||
135 | predefinedReasons | ||
136 | } | ||
137 | |||
138 | if (body.video) { | ||
139 | return createVideoAbuse({ | ||
140 | baseAbuse, | ||
141 | videoInstance, | ||
142 | reporterAccount, | ||
143 | transaction: t, | ||
144 | startAt: body.video.startAt, | ||
145 | endAt: body.video.endAt | ||
146 | }) | ||
147 | } | ||
148 | |||
149 | if (body.comment) { | ||
150 | return createVideoCommentAbuse({ | ||
151 | baseAbuse, | ||
152 | commentInstance, | ||
153 | reporterAccount, | ||
154 | transaction: t | ||
155 | }) | ||
156 | } | ||
157 | |||
158 | // Account report | ||
159 | return createAccountAbuse({ | ||
160 | baseAbuse, | ||
161 | accountInstance, | ||
162 | reporterAccount, | ||
163 | transaction: t | ||
164 | }) | ||
165 | }) | ||
166 | |||
167 | return res.json({ abuse: { id } }) | ||
168 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index c334a26b4..eda9e04d1 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -3,6 +3,7 @@ import * as express from 'express' | |||
3 | import * as RateLimit from 'express-rate-limit' | 3 | import * as RateLimit from 'express-rate-limit' |
4 | import { badRequest } from '../../helpers/express-utils' | 4 | import { badRequest } from '../../helpers/express-utils' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { CONFIG } from '../../initializers/config' |
6 | import { abuseRouter } from './abuse' | ||
6 | import { accountsRouter } from './accounts' | 7 | import { accountsRouter } from './accounts' |
7 | import { bulkRouter } from './bulk' | 8 | import { bulkRouter } from './bulk' |
8 | import { configRouter } from './config' | 9 | import { configRouter } from './config' |
@@ -32,6 +33,7 @@ const apiRateLimiter = RateLimit({ | |||
32 | apiRouter.use(apiRateLimiter) | 33 | apiRouter.use(apiRateLimiter) |
33 | 34 | ||
34 | apiRouter.use('/server', serverRouter) | 35 | apiRouter.use('/server', serverRouter) |
36 | apiRouter.use('/abuses', abuseRouter) | ||
35 | apiRouter.use('/bulk', bulkRouter) | 37 | apiRouter.use('/bulk', bulkRouter) |
36 | apiRouter.use('/oauth-clients', oauthClientsRouter) | 38 | apiRouter.use('/oauth-clients', oauthClientsRouter) |
37 | apiRouter.use('/config', configRouter) | 39 | apiRouter.use('/config', configRouter) |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index ab2074459..b92a66360 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse, videoAbusePredefinedReasonsMap } from '../../../../shared' | 2 | import { AbuseModel } from '@server/models/abuse/abuse' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared' | ||
4 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers/database' | ||
6 | import { | 6 | import { |
7 | abusesSortValidator, | ||
7 | asyncMiddleware, | 8 | asyncMiddleware, |
8 | asyncRetryTransactionMiddleware, | 9 | asyncRetryTransactionMiddleware, |
9 | authenticate, | 10 | authenticate, |
@@ -12,28 +13,21 @@ import { | |||
12 | setDefaultPagination, | 13 | setDefaultPagination, |
13 | setDefaultSort, | 14 | setDefaultSort, |
14 | videoAbuseGetValidator, | 15 | videoAbuseGetValidator, |
16 | videoAbuseListValidator, | ||
15 | videoAbuseReportValidator, | 17 | videoAbuseReportValidator, |
16 | videoAbusesSortValidator, | 18 | videoAbuseUpdateValidator |
17 | videoAbuseUpdateValidator, | ||
18 | videoAbuseListValidator | ||
19 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
20 | import { AccountModel } from '../../../models/account/account' | 20 | import { deleteAbuse, reportAbuse, updateAbuse } from '../abuse' |
21 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 21 | |
22 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 22 | // FIXME: deprecated in 2.3. Remove this controller |
23 | import { Notifier } from '../../../lib/notifier' | ||
24 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | ||
25 | import { MVideoAbuseAccountVideo } from '../../../types/models/video' | ||
26 | import { getServerActor } from '@server/models/application/application' | ||
27 | import { MAccountDefault } from '@server/types/models' | ||
28 | 23 | ||
29 | const auditLogger = auditLoggerFactory('abuse') | ||
30 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
31 | 25 | ||
32 | abuseVideoRouter.get('/abuse', | 26 | abuseVideoRouter.get('/abuse', |
33 | authenticate, | 27 | authenticate, |
34 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
35 | paginationValidator, | 29 | paginationValidator, |
36 | videoAbusesSortValidator, | 30 | abusesSortValidator, |
37 | setDefaultSort, | 31 | setDefaultSort, |
38 | setDefaultPagination, | 32 | setDefaultPagination, |
39 | videoAbuseListValidator, | 33 | videoAbuseListValidator, |
@@ -41,7 +35,7 @@ abuseVideoRouter.get('/abuse', | |||
41 | ) | 35 | ) |
42 | abuseVideoRouter.put('/:videoId/abuse/:id', | 36 | abuseVideoRouter.put('/:videoId/abuse/:id', |
43 | authenticate, | 37 | authenticate, |
44 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
45 | asyncMiddleware(videoAbuseUpdateValidator), | 39 | asyncMiddleware(videoAbuseUpdateValidator), |
46 | asyncRetryTransactionMiddleware(updateVideoAbuse) | 40 | asyncRetryTransactionMiddleware(updateVideoAbuse) |
47 | ) | 41 | ) |
@@ -52,7 +46,7 @@ abuseVideoRouter.post('/:videoId/abuse', | |||
52 | ) | 46 | ) |
53 | abuseVideoRouter.delete('/:videoId/abuse/:id', | 47 | abuseVideoRouter.delete('/:videoId/abuse/:id', |
54 | authenticate, | 48 | authenticate, |
55 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
56 | asyncMiddleware(videoAbuseGetValidator), | 50 | asyncMiddleware(videoAbuseGetValidator), |
57 | asyncRetryTransactionMiddleware(deleteVideoAbuse) | 51 | asyncRetryTransactionMiddleware(deleteVideoAbuse) |
58 | ) | 52 | ) |
@@ -69,11 +63,12 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
69 | const user = res.locals.oauth.token.user | 63 | const user = res.locals.oauth.token.user |
70 | const serverActor = await getServerActor() | 64 | const serverActor = await getServerActor() |
71 | 65 | ||
72 | const resultList = await VideoAbuseModel.listForApi({ | 66 | const resultList = await AbuseModel.listForApi({ |
73 | start: req.query.start, | 67 | start: req.query.start, |
74 | count: req.query.count, | 68 | count: req.query.count, |
75 | sort: req.query.sort, | 69 | sort: req.query.sort, |
76 | id: req.query.id, | 70 | id: req.query.id, |
71 | filter: 'video', | ||
77 | predefinedReason: req.query.predefinedReason, | 72 | predefinedReason: req.query.predefinedReason, |
78 | search: req.query.search, | 73 | search: req.query.search, |
79 | state: req.query.state, | 74 | state: req.query.state, |
@@ -90,74 +85,28 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
90 | } | 85 | } |
91 | 86 | ||
92 | async function updateVideoAbuse (req: express.Request, res: express.Response) { | 87 | async function updateVideoAbuse (req: express.Request, res: express.Response) { |
93 | const videoAbuse = res.locals.videoAbuse | 88 | return updateAbuse(req, res) |
94 | |||
95 | if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment | ||
96 | if (req.body.state !== undefined) videoAbuse.state = req.body.state | ||
97 | |||
98 | await sequelizeTypescript.transaction(t => { | ||
99 | return videoAbuse.save({ transaction: t }) | ||
100 | }) | ||
101 | |||
102 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
103 | |||
104 | return res.type('json').status(204).end() | ||
105 | } | 89 | } |
106 | 90 | ||
107 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { | 91 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { |
108 | const videoAbuse = res.locals.videoAbuse | 92 | return deleteAbuse(req, res) |
109 | |||
110 | await sequelizeTypescript.transaction(t => { | ||
111 | return videoAbuse.destroy({ transaction: t }) | ||
112 | }) | ||
113 | |||
114 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
115 | |||
116 | return res.type('json').status(204).end() | ||
117 | } | 93 | } |
118 | 94 | ||
119 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 95 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
120 | const videoInstance = res.locals.videoAll | 96 | const oldBody = req.body as VideoAbuseCreate |
121 | const body: VideoAbuseCreate = req.body | ||
122 | let reporterAccount: MAccountDefault | ||
123 | let videoAbuseJSON: VideoAbuse | ||
124 | |||
125 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { | ||
126 | reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
127 | const predefinedReasons = body.predefinedReasons?.map(r => videoAbusePredefinedReasonsMap[r]) | ||
128 | |||
129 | const abuseToCreate = { | ||
130 | reporterAccountId: reporterAccount.id, | ||
131 | reason: body.reason, | ||
132 | videoId: videoInstance.id, | ||
133 | state: VideoAbuseState.PENDING, | ||
134 | predefinedReasons, | ||
135 | startAt: body.startAt, | ||
136 | endAt: body.endAt | ||
137 | } | ||
138 | |||
139 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | ||
140 | videoAbuseInstance.Video = videoInstance | ||
141 | videoAbuseInstance.Account = reporterAccount | ||
142 | |||
143 | // We send the video abuse to the origin server | ||
144 | if (videoInstance.isOwned() === false) { | ||
145 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) | ||
146 | } | ||
147 | 97 | ||
148 | videoAbuseJSON = videoAbuseInstance.toFormattedJSON() | 98 | req.body = { |
149 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON)) | 99 | accountId: res.locals.videoAll.VideoChannel.accountId, |
150 | 100 | ||
151 | return videoAbuseInstance | 101 | reason: oldBody.reason, |
152 | }) | 102 | predefinedReasons: oldBody.predefinedReasons, |
153 | 103 | ||
154 | Notifier.Instance.notifyOnNewVideoAbuse({ | 104 | video: { |
155 | videoAbuse: videoAbuseJSON, | 105 | id: res.locals.videoAll.id, |
156 | videoAbuseInstance, | 106 | startAt: oldBody.startAt, |
157 | reporter: reporterAccount.Actor.getIdentifier() | 107 | endAt: oldBody.endAt |
158 | }) | 108 | } |
159 | 109 | } as AbuseCreate | |
160 | logger.info('Abuse report for video "%s" created.', videoInstance.name) | ||
161 | 110 | ||
162 | return res.json({ videoAbuse: videoAbuseJSON }).end() | 111 | return reportAbuse(req, res) |
163 | } | 112 | } |