diff options
author | Aurélien Bertron <aurelienbertron@gmail.com> | 2018-07-31 14:04:26 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-31 15:40:29 +0200 |
commit | 80e36cd9facb56b330be3e4f1c5ba253cc78c308 (patch) | |
tree | 807d8a642ae99ec3f05597e19ebe1ca5dc849582 /server | |
parent | 59390818384baa0ffc0cb71af2e67350c6b39172 (diff) | |
download | PeerTube-80e36cd9facb56b330be3e4f1c5ba253cc78c308.tar.gz PeerTube-80e36cd9facb56b330be3e4f1c5ba253cc78c308.tar.zst PeerTube-80e36cd9facb56b330be3e4f1c5ba253cc78c308.zip |
Add audit logs in various modules
- Videos
- Videos comments
- Users
- Videos channels
- Videos abuses
- Custom config
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/config.ts | 16 | ||||
-rw-r--r-- | server/controllers/api/users.ts | 49 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 29 | ||||
-rw-r--r-- | server/controllers/api/videos/abuse.ts | 8 | ||||
-rw-r--r-- | server/controllers/api/videos/comment.ts | 10 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 14 | ||||
-rw-r--r-- | server/helpers/audit-logger.ts | 138 | ||||
-rw-r--r-- | server/lib/user.ts | 1 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 4 |
9 files changed, 249 insertions, 20 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 9c1b2818c..411b13539 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -9,10 +9,13 @@ import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers' | |||
9 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' | 9 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' |
10 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | 10 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' |
11 | import { ClientHtml } from '../../lib/client-html' | 11 | import { ClientHtml } from '../../lib/client-html' |
12 | import { CustomConfigAuditView, auditLoggerFactory } from '../../helpers/audit-logger' | ||
12 | 13 | ||
13 | const packageJSON = require('../../../../package.json') | 14 | const packageJSON = require('../../../../package.json') |
14 | const configRouter = express.Router() | 15 | const configRouter = express.Router() |
15 | 16 | ||
17 | const auditLogger = auditLoggerFactory('config') | ||
18 | |||
16 | configRouter.get('/about', getAbout) | 19 | configRouter.get('/about', getAbout) |
17 | configRouter.get('/', | 20 | configRouter.get('/', |
18 | asyncMiddleware(getConfig) | 21 | asyncMiddleware(getConfig) |
@@ -119,6 +122,11 @@ async function getCustomConfig (req: express.Request, res: express.Response, nex | |||
119 | async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 122 | async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { |
120 | await unlinkPromise(CONFIG.CUSTOM_FILE) | 123 | await unlinkPromise(CONFIG.CUSTOM_FILE) |
121 | 124 | ||
125 | auditLogger.delete( | ||
126 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
127 | new CustomConfigAuditView(customConfig()) | ||
128 | ) | ||
129 | |||
122 | reloadConfig() | 130 | reloadConfig() |
123 | ClientHtml.invalidCache() | 131 | ClientHtml.invalidCache() |
124 | 132 | ||
@@ -129,6 +137,7 @@ async function deleteCustomConfig (req: express.Request, res: express.Response, | |||
129 | 137 | ||
130 | async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 138 | async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { |
131 | const toUpdate: CustomConfig = req.body | 139 | const toUpdate: CustomConfig = req.body |
140 | const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig()) | ||
132 | 141 | ||
133 | // Force number conversion | 142 | // Force number conversion |
134 | toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) | 143 | toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10) |
@@ -150,6 +159,13 @@ async function updateCustomConfig (req: express.Request, res: express.Response, | |||
150 | ClientHtml.invalidCache() | 159 | ClientHtml.invalidCache() |
151 | 160 | ||
152 | const data = customConfig() | 161 | const data = customConfig() |
162 | |||
163 | auditLogger.update( | ||
164 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
165 | new CustomConfigAuditView(data), | ||
166 | oldCustomConfigAuditKeys | ||
167 | ) | ||
168 | |||
153 | return res.json(data).end() | 169 | return res.json(data).end() |
154 | } | 170 | } |
155 | 171 | ||
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index c80f27a23..dbe736bff 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -39,6 +39,9 @@ import { createReqFiles } from '../../helpers/express-utils' | |||
39 | import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model' | 39 | import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.model' |
40 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' | 40 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' |
41 | import { updateActorAvatarFile } from '../../lib/avatar' | 41 | import { updateActorAvatarFile } from '../../lib/avatar' |
42 | import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger' | ||
43 | |||
44 | const auditLogger = auditLoggerFactory('users') | ||
42 | 45 | ||
43 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) | 46 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) |
44 | const loginRateLimiter = new RateLimit({ | 47 | const loginRateLimiter = new RateLimit({ |
@@ -189,6 +192,7 @@ async function createUser (req: express.Request, res: express.Response) { | |||
189 | 192 | ||
190 | const { user, account } = await createUserAccountAndChannel(userToCreate) | 193 | const { user, account } = await createUserAccountAndChannel(userToCreate) |
191 | 194 | ||
195 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON())) | ||
192 | logger.info('User %s with its channel and account created.', body.username) | 196 | logger.info('User %s with its channel and account created.', body.username) |
193 | 197 | ||
194 | return res.json({ | 198 | return res.json({ |
@@ -205,7 +209,7 @@ async function createUser (req: express.Request, res: express.Response) { | |||
205 | async function registerUser (req: express.Request, res: express.Response) { | 209 | async function registerUser (req: express.Request, res: express.Response) { |
206 | const body: UserCreate = req.body | 210 | const body: UserCreate = req.body |
207 | 211 | ||
208 | const user = new UserModel({ | 212 | const userToCreate = new UserModel({ |
209 | username: body.username, | 213 | username: body.username, |
210 | password: body.password, | 214 | password: body.password, |
211 | email: body.email, | 215 | email: body.email, |
@@ -215,8 +219,9 @@ async function registerUser (req: express.Request, res: express.Response) { | |||
215 | videoQuota: CONFIG.USER.VIDEO_QUOTA | 219 | videoQuota: CONFIG.USER.VIDEO_QUOTA |
216 | }) | 220 | }) |
217 | 221 | ||
218 | await createUserAccountAndChannel(user) | 222 | const { user } = await createUserAccountAndChannel(userToCreate) |
219 | 223 | ||
224 | auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) | ||
220 | logger.info('User %s with its channel and account registered.', body.username) | 225 | logger.info('User %s with its channel and account registered.', body.username) |
221 | 226 | ||
222 | return res.type('json').status(204).end() | 227 | return res.type('json').status(204).end() |
@@ -269,6 +274,8 @@ async function removeUser (req: express.Request, res: express.Response, next: ex | |||
269 | 274 | ||
270 | await user.destroy() | 275 | await user.destroy() |
271 | 276 | ||
277 | auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new UserAuditView(user.toFormattedJSON())) | ||
278 | |||
272 | return res.sendStatus(204) | 279 | return res.sendStatus(204) |
273 | } | 280 | } |
274 | 281 | ||
@@ -276,6 +283,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr | |||
276 | const body: UserUpdateMe = req.body | 283 | const body: UserUpdateMe = req.body |
277 | 284 | ||
278 | const user: UserModel = res.locals.oauth.token.user | 285 | const user: UserModel = res.locals.oauth.token.user |
286 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | ||
279 | 287 | ||
280 | if (body.password !== undefined) user.password = body.password | 288 | if (body.password !== undefined) user.password = body.password |
281 | if (body.email !== undefined) user.email = body.email | 289 | if (body.email !== undefined) user.email = body.email |
@@ -290,6 +298,12 @@ async function updateMe (req: express.Request, res: express.Response, next: expr | |||
290 | await user.Account.save({ transaction: t }) | 298 | await user.Account.save({ transaction: t }) |
291 | 299 | ||
292 | await sendUpdateActor(user.Account, t) | 300 | await sendUpdateActor(user.Account, t) |
301 | |||
302 | auditLogger.update( | ||
303 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
304 | new UserAuditView(user.toFormattedJSON()), | ||
305 | oldUserAuditView | ||
306 | ) | ||
293 | }) | 307 | }) |
294 | 308 | ||
295 | return res.sendStatus(204) | 309 | return res.sendStatus(204) |
@@ -297,10 +311,18 @@ async function updateMe (req: express.Request, res: express.Response, next: expr | |||
297 | 311 | ||
298 | async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { | 312 | async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { |
299 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 313 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] |
300 | const account = res.locals.oauth.token.user.Account | 314 | const user: UserModel = res.locals.oauth.token.user |
315 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | ||
316 | const account = user.Account | ||
301 | 317 | ||
302 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account) | 318 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, account.Actor, account) |
303 | 319 | ||
320 | auditLogger.update( | ||
321 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
322 | new UserAuditView(user.toFormattedJSON()), | ||
323 | oldUserAuditView | ||
324 | ) | ||
325 | |||
304 | return res | 326 | return res |
305 | .json({ | 327 | .json({ |
306 | avatar: avatar.toFormattedJSON() | 328 | avatar: avatar.toFormattedJSON() |
@@ -310,20 +332,27 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next | |||
310 | 332 | ||
311 | async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 333 | async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
312 | const body: UserUpdate = req.body | 334 | const body: UserUpdate = req.body |
313 | const user = res.locals.user as UserModel | 335 | const userToUpdate = res.locals.user as UserModel |
314 | const roleChanged = body.role !== undefined && body.role !== user.role | 336 | const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) |
337 | const roleChanged = body.role !== undefined && body.role !== userToUpdate.role | ||
315 | 338 | ||
316 | if (body.email !== undefined) user.email = body.email | 339 | if (body.email !== undefined) userToUpdate.email = body.email |
317 | if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota | 340 | if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota |
318 | if (body.role !== undefined) user.role = body.role | 341 | if (body.role !== undefined) userToUpdate.role = body.role |
319 | 342 | ||
320 | await user.save() | 343 | const user = await userToUpdate.save() |
321 | 344 | ||
322 | // Destroy user token to refresh rights | 345 | // Destroy user token to refresh rights |
323 | if (roleChanged) { | 346 | if (roleChanged) { |
324 | await OAuthTokenModel.deleteUserToken(user.id) | 347 | await OAuthTokenModel.deleteUserToken(userToUpdate.id) |
325 | } | 348 | } |
326 | 349 | ||
350 | auditLogger.update( | ||
351 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
352 | new UserAuditView(user.toFormattedJSON()), | ||
353 | oldUserAuditView | ||
354 | ) | ||
355 | |||
327 | // Don't need to send this update to followers, these attributes are not propagated | 356 | // Don't need to send this update to followers, these attributes are not propagated |
328 | 357 | ||
329 | return res.sendStatus(204) | 358 | return res.sendStatus(204) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 0488ba8f5..3a444547b 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -27,7 +27,9 @@ import { logger } from '../../helpers/logger' | |||
27 | import { VideoModel } from '../../models/video/video' | 27 | import { VideoModel } from '../../models/video/video' |
28 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' | 28 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' |
29 | import { updateActorAvatarFile } from '../../lib/avatar' | 29 | import { updateActorAvatarFile } from '../../lib/avatar' |
30 | import { auditLoggerFactory, VideoChannelAuditView } from '../../helpers/audit-logger' | ||
30 | 31 | ||
32 | const auditLogger = auditLoggerFactory('channels') | ||
31 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) | 33 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR }) |
32 | 34 | ||
33 | const videoChannelRouter = express.Router() | 35 | const videoChannelRouter = express.Router() |
@@ -99,10 +101,17 @@ async function listVideoChannels (req: express.Request, res: express.Response, n | |||
99 | 101 | ||
100 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { | 102 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { |
101 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 103 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] |
102 | const videoChannel = res.locals.videoChannel | 104 | const videoChannel = res.locals.videoChannel as VideoChannelModel |
105 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) | ||
103 | 106 | ||
104 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel) | 107 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel.Actor, videoChannel) |
105 | 108 | ||
109 | auditLogger.update( | ||
110 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
111 | new VideoChannelAuditView(videoChannel.toFormattedJSON()), | ||
112 | oldVideoChannelAuditKeys | ||
113 | ) | ||
114 | |||
106 | return res | 115 | return res |
107 | .json({ | 116 | .json({ |
108 | avatar: avatar.toFormattedJSON() | 117 | avatar: avatar.toFormattedJSON() |
@@ -121,6 +130,10 @@ async function addVideoChannel (req: express.Request, res: express.Response) { | |||
121 | setAsyncActorKeys(videoChannelCreated.Actor) | 130 | setAsyncActorKeys(videoChannelCreated.Actor) |
122 | .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err })) | 131 | .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err })) |
123 | 132 | ||
133 | auditLogger.create( | ||
134 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
135 | new VideoChannelAuditView(videoChannelCreated.toFormattedJSON()) | ||
136 | ) | ||
124 | logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) | 137 | logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) |
125 | 138 | ||
126 | return res.json({ | 139 | return res.json({ |
@@ -134,6 +147,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) { | |||
134 | async function updateVideoChannel (req: express.Request, res: express.Response) { | 147 | async function updateVideoChannel (req: express.Request, res: express.Response) { |
135 | const videoChannelInstance = res.locals.videoChannel as VideoChannelModel | 148 | const videoChannelInstance = res.locals.videoChannel as VideoChannelModel |
136 | const videoChannelFieldsSave = videoChannelInstance.toJSON() | 149 | const videoChannelFieldsSave = videoChannelInstance.toJSON() |
150 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) | ||
137 | const videoChannelInfoToUpdate = req.body as VideoChannelUpdate | 151 | const videoChannelInfoToUpdate = req.body as VideoChannelUpdate |
138 | 152 | ||
139 | try { | 153 | try { |
@@ -148,9 +162,14 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
148 | 162 | ||
149 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) | 163 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) |
150 | await sendUpdateActor(videoChannelInstanceUpdated, t) | 164 | await sendUpdateActor(videoChannelInstanceUpdated, t) |
151 | }) | ||
152 | 165 | ||
153 | logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) | 166 | auditLogger.update( |
167 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
168 | new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()), | ||
169 | oldVideoChannelAuditKeys | ||
170 | ) | ||
171 | logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) | ||
172 | }) | ||
154 | } catch (err) { | 173 | } catch (err) { |
155 | logger.debug('Cannot update the video channel.', { err }) | 174 | logger.debug('Cannot update the video channel.', { err }) |
156 | 175 | ||
@@ -171,6 +190,10 @@ async function removeVideoChannel (req: express.Request, res: express.Response) | |||
171 | await sequelizeTypescript.transaction(async t => { | 190 | await sequelizeTypescript.transaction(async t => { |
172 | await videoChannelInstance.destroy({ transaction: t }) | 191 | await videoChannelInstance.destroy({ transaction: t }) |
173 | 192 | ||
193 | auditLogger.delete( | ||
194 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
195 | new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) | ||
196 | ) | ||
174 | logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) | 197 | logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) |
175 | }) | 198 | }) |
176 | 199 | ||
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 3413ae894..7782fc639 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -18,7 +18,9 @@ import { | |||
18 | import { AccountModel } from '../../../models/account/account' | 18 | import { AccountModel } from '../../../models/account/account' |
19 | import { VideoModel } from '../../../models/video/video' | 19 | import { VideoModel } from '../../../models/video/video' |
20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | ||
21 | 22 | ||
23 | const auditLogger = auditLoggerFactory('abuse') | ||
22 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
23 | 25 | ||
24 | abuseVideoRouter.get('/abuse', | 26 | abuseVideoRouter.get('/abuse', |
@@ -64,14 +66,16 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
64 | await sequelizeTypescript.transaction(async t => { | 66 | await sequelizeTypescript.transaction(async t => { |
65 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | 67 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) |
66 | videoAbuseInstance.Video = videoInstance | 68 | videoAbuseInstance.Video = videoInstance |
69 | videoAbuseInstance.Account = reporterAccount | ||
67 | 70 | ||
68 | // We send the video abuse to the origin server | 71 | // We send the video abuse to the origin server |
69 | if (videoInstance.isOwned() === false) { | 72 | if (videoInstance.isOwned() === false) { |
70 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) | 73 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) |
71 | } | 74 | } |
72 | }) | ||
73 | 75 | ||
74 | logger.info('Abuse report for video %s created.', videoInstance.name) | 76 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) |
77 | logger.info('Abuse report for video %s created.', videoInstance.name) | ||
78 | }) | ||
75 | 79 | ||
76 | return res.type('json').status(204).end() | 80 | return res.type('json').status(204).end() |
77 | } | 81 | } |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index bbeb0d557..e35247829 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -23,7 +23,9 @@ import { | |||
23 | } from '../../../middlewares/validators/video-comments' | 23 | } from '../../../middlewares/validators/video-comments' |
24 | import { VideoModel } from '../../../models/video/video' | 24 | import { VideoModel } from '../../../models/video/video' |
25 | import { VideoCommentModel } from '../../../models/video/video-comment' | 25 | import { VideoCommentModel } from '../../../models/video/video-comment' |
26 | import { auditLoggerFactory, CommentAuditView } from '../../../helpers/audit-logger' | ||
26 | 27 | ||
28 | const auditLogger = auditLoggerFactory('comments') | ||
27 | const videoCommentRouter = express.Router() | 29 | const videoCommentRouter = express.Router() |
28 | 30 | ||
29 | videoCommentRouter.get('/:videoId/comment-threads', | 31 | videoCommentRouter.get('/:videoId/comment-threads', |
@@ -107,6 +109,8 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons | |||
107 | }, t) | 109 | }, t) |
108 | }) | 110 | }) |
109 | 111 | ||
112 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON())) | ||
113 | |||
110 | return res.json({ | 114 | return res.json({ |
111 | comment: comment.toFormattedJSON() | 115 | comment: comment.toFormattedJSON() |
112 | }).end() | 116 | }).end() |
@@ -124,6 +128,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response | |||
124 | }, t) | 128 | }, t) |
125 | }) | 129 | }) |
126 | 130 | ||
131 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new CommentAuditView(comment.toFormattedJSON())) | ||
132 | |||
127 | return res.json({ | 133 | return res.json({ |
128 | comment: comment.toFormattedJSON() | 134 | comment: comment.toFormattedJSON() |
129 | }).end() | 135 | }).end() |
@@ -136,6 +142,10 @@ async function removeVideoComment (req: express.Request, res: express.Response) | |||
136 | await videoCommentInstance.destroy({ transaction: t }) | 142 | await videoCommentInstance.destroy({ transaction: t }) |
137 | }) | 143 | }) |
138 | 144 | ||
145 | auditLogger.delete( | ||
146 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
147 | new CommentAuditView(videoCommentInstance.toFormattedJSON()) | ||
148 | ) | ||
139 | logger.info('Video comment %d deleted.', videoCommentInstance.id) | 149 | logger.info('Video comment %d deleted.', videoCommentInstance.id) |
140 | 150 | ||
141 | return res.type('json').status(204).end() | 151 | return res.type('json').status(204).end() |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 101183eab..e396ee6be 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -5,6 +5,7 @@ import { renamePromise } from '../../../helpers/core-utils' | |||
5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
6 | import { processImage } from '../../../helpers/image-utils' | 6 | import { processImage } from '../../../helpers/image-utils' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { auditLoggerFactory, VideoAuditView } from '../../../helpers/audit-logger' | ||
8 | import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' | 9 | import { getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' |
9 | import { | 10 | import { |
10 | CONFIG, | 11 | CONFIG, |
@@ -54,6 +55,7 @@ import { createReqFiles, buildNSFWFilter } from '../../../helpers/express-utils' | |||
54 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | 55 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' |
55 | import { videoCaptionsRouter } from './captions' | 56 | import { videoCaptionsRouter } from './captions' |
56 | 57 | ||
58 | const auditLogger = auditLoggerFactory('videos') | ||
57 | const videosRouter = express.Router() | 59 | const videosRouter = express.Router() |
58 | 60 | ||
59 | const reqVideoFileAdd = createReqFiles( | 61 | const reqVideoFileAdd = createReqFiles( |
@@ -247,6 +249,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
247 | 249 | ||
248 | await federateVideoIfNeeded(video, true, t) | 250 | await federateVideoIfNeeded(video, true, t) |
249 | 251 | ||
252 | auditLogger.create(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) | ||
250 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) | 253 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) |
251 | 254 | ||
252 | return videoCreated | 255 | return videoCreated |
@@ -273,6 +276,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
273 | async function updateVideo (req: express.Request, res: express.Response) { | 276 | async function updateVideo (req: express.Request, res: express.Response) { |
274 | const videoInstance: VideoModel = res.locals.video | 277 | const videoInstance: VideoModel = res.locals.video |
275 | const videoFieldsSave = videoInstance.toJSON() | 278 | const videoFieldsSave = videoInstance.toJSON() |
279 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) | ||
276 | const videoInfoToUpdate: VideoUpdate = req.body | 280 | const videoInfoToUpdate: VideoUpdate = req.body |
277 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE | 281 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE |
278 | 282 | ||
@@ -344,9 +348,14 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
344 | 348 | ||
345 | const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE | 349 | const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE |
346 | await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) | 350 | await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) |
347 | }) | ||
348 | 351 | ||
349 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) | 352 | auditLogger.update( |
353 | res.locals.oauth.token.User.Account.Actor.getIdentifier(), | ||
354 | new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), | ||
355 | oldVideoAuditView | ||
356 | ) | ||
357 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) | ||
358 | }) | ||
350 | } catch (err) { | 359 | } catch (err) { |
351 | // Force fields we want to update | 360 | // Force fields we want to update |
352 | // If the transaction is retried, sequelize will think the object has not changed | 361 | // If the transaction is retried, sequelize will think the object has not changed |
@@ -423,6 +432,7 @@ async function removeVideo (req: express.Request, res: express.Response) { | |||
423 | await videoInstance.destroy({ transaction: t }) | 432 | await videoInstance.destroy({ transaction: t }) |
424 | }) | 433 | }) |
425 | 434 | ||
435 | auditLogger.delete(res.locals.oauth.token.User.Account.Actor.getIdentifier(), new VideoAuditView(videoInstance.toFormattedDetailsJSON())) | ||
426 | logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid) | 436 | logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid) |
427 | 437 | ||
428 | return res.type('json').status(204).end() | 438 | return res.type('json').status(204).end() |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 4b237316f..f6eea7d90 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -5,7 +5,9 @@ import * as flatten from 'flat' | |||
5 | import * as winston from 'winston' | 5 | import * as winston from 'winston' |
6 | import { CONFIG } from '../initializers' | 6 | import { CONFIG } from '../initializers' |
7 | import { jsonLoggerFormat, labelFormatter } from './logger' | 7 | import { jsonLoggerFormat, labelFormatter } from './logger' |
8 | import { VideoDetails } from '../../shared' | 8 | import { VideoDetails, User, VideoChannel, VideoAbuse } from '../../shared' |
9 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | ||
10 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | ||
9 | 11 | ||
10 | enum AUDIT_TYPE { | 12 | enum AUDIT_TYPE { |
11 | CREATE = 'create', | 13 | CREATE = 'create', |
@@ -111,13 +113,143 @@ const videoKeysToKeep = [ | |||
111 | 'support', | 113 | 'support', |
112 | 'commentsEnabled' | 114 | 'commentsEnabled' |
113 | ] | 115 | ] |
114 | class VideoAuditView extends AuditEntity { | 116 | class VideoAuditView extends EntityAuditView { |
115 | constructor (private video: VideoDetails) { | 117 | constructor (private video: VideoDetails) { |
116 | super(videoKeysToKeep, 'video', video) | 118 | super(videoKeysToKeep, 'video', video) |
117 | } | 119 | } |
118 | } | 120 | } |
119 | 121 | ||
122 | const commentKeysToKeep = [ | ||
123 | 'id', | ||
124 | 'text', | ||
125 | 'threadId', | ||
126 | 'inReplyToCommentId', | ||
127 | 'videoId', | ||
128 | 'createdAt', | ||
129 | 'updatedAt', | ||
130 | 'totalReplies', | ||
131 | 'account-id', | ||
132 | 'account-uuid', | ||
133 | 'account-name' | ||
134 | ] | ||
135 | class CommentAuditView extends EntityAuditView { | ||
136 | constructor (private comment: VideoComment) { | ||
137 | super(commentKeysToKeep, 'comment', comment) | ||
138 | } | ||
139 | } | ||
140 | |||
141 | const userKeysToKeep = [ | ||
142 | 'id', | ||
143 | 'username', | ||
144 | 'email', | ||
145 | 'nsfwPolicy', | ||
146 | 'autoPlayVideo', | ||
147 | 'role', | ||
148 | 'videoQuota', | ||
149 | 'createdAt', | ||
150 | 'account-id', | ||
151 | 'account-uuid', | ||
152 | 'account-name', | ||
153 | 'account-followingCount', | ||
154 | 'account-followersCount', | ||
155 | 'account-createdAt', | ||
156 | 'account-updatedAt', | ||
157 | 'account-avatar-path', | ||
158 | 'account-avatar-createdAt', | ||
159 | 'account-avatar-updatedAt', | ||
160 | 'account-displayName', | ||
161 | 'account-description', | ||
162 | 'videoChannels' | ||
163 | ] | ||
164 | class UserAuditView extends EntityAuditView { | ||
165 | constructor (private user: User) { | ||
166 | super(userKeysToKeep, 'user', user) | ||
167 | } | ||
168 | } | ||
169 | |||
170 | const channelKeysToKeep = [ | ||
171 | 'id', | ||
172 | 'uuid', | ||
173 | 'name', | ||
174 | 'followingCount', | ||
175 | 'followersCount', | ||
176 | 'createdAt', | ||
177 | 'updatedAt', | ||
178 | 'avatar-path', | ||
179 | 'avatar-createdAt', | ||
180 | 'avatar-updatedAt', | ||
181 | 'displayName', | ||
182 | 'description', | ||
183 | 'support', | ||
184 | 'isLocal', | ||
185 | 'ownerAccount-id', | ||
186 | 'ownerAccount-uuid', | ||
187 | 'ownerAccount-name', | ||
188 | 'ownerAccount-displayedName' | ||
189 | ] | ||
190 | class VideoChannelAuditView extends EntityAuditView { | ||
191 | constructor (private channel: VideoChannel) { | ||
192 | super(channelKeysToKeep, 'channel', channel) | ||
193 | } | ||
194 | } | ||
195 | |||
196 | const videoAbuseKeysToKeep = [ | ||
197 | 'id', | ||
198 | 'reason', | ||
199 | 'reporterAccount', | ||
200 | 'video-id', | ||
201 | 'video-name', | ||
202 | 'video-uuid', | ||
203 | 'createdAt' | ||
204 | ] | ||
205 | class VideoAbuseAuditView extends EntityAuditView { | ||
206 | constructor (private videoAbuse: VideoAbuse) { | ||
207 | super(videoAbuseKeysToKeep, 'abuse', videoAbuse) | ||
208 | } | ||
209 | } | ||
210 | |||
211 | const customConfigKeysToKeep = [ | ||
212 | 'instance-name', | ||
213 | 'instance-shortDescription', | ||
214 | 'instance-description', | ||
215 | 'instance-terms', | ||
216 | 'instance-defaultClientRoute', | ||
217 | 'instance-defaultNSFWPolicy', | ||
218 | 'instance-customizations-javascript', | ||
219 | 'instance-customizations-css', | ||
220 | 'services-twitter-username', | ||
221 | 'services-twitter-whitelisted', | ||
222 | 'cache-previews-size', | ||
223 | 'cache-captions-size', | ||
224 | 'signup-enabled', | ||
225 | 'signup-limit', | ||
226 | 'admin-email', | ||
227 | 'user-videoQuota', | ||
228 | 'transcoding-enabled', | ||
229 | 'transcoding-threads', | ||
230 | 'transcoding-resolutions' | ||
231 | ] | ||
232 | class CustomConfigAuditView extends EntityAuditView { | ||
233 | constructor (customConfig: CustomConfig) { | ||
234 | const infos: any = customConfig | ||
235 | const resolutionsDict = infos.transcoding.resolutions | ||
236 | const resolutionsArray = [] | ||
237 | Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => { | ||
238 | if (isEnabled) { | ||
239 | resolutionsArray.push(resolution) | ||
240 | } | ||
241 | }) | ||
242 | infos.transcoding.resolutions = resolutionsArray | ||
243 | super(customConfigKeysToKeep, 'config', infos) | ||
244 | } | ||
245 | } | ||
246 | |||
120 | export { | 247 | export { |
121 | auditLoggerFactory, | 248 | auditLoggerFactory, |
122 | VideoAuditView | 249 | VideoChannelAuditView, |
250 | CommentAuditView, | ||
251 | UserAuditView, | ||
252 | VideoAuditView, | ||
253 | VideoAbuseAuditView, | ||
254 | CustomConfigAuditView | ||
123 | } | 255 | } |
diff --git a/server/lib/user.ts b/server/lib/user.ts index ac5f55260..e7a45f5aa 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -17,6 +17,7 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse | |||
17 | 17 | ||
18 | const userCreated = await userToCreate.save(userOptions) | 18 | const userCreated = await userToCreate.save(userOptions) |
19 | const accountCreated = await createLocalAccountWithoutKeys(userToCreate.username, userToCreate.id, null, t) | 19 | const accountCreated = await createLocalAccountWithoutKeys(userToCreate.username, userToCreate.id, null, t) |
20 | userCreated.Account = accountCreated | ||
20 | 21 | ||
21 | const videoChannelDisplayName = `Default ${userCreated.username} channel` | 22 | const videoChannelDisplayName = `Default ${userCreated.username} channel` |
22 | const videoChannelInfo = { | 23 | const videoChannelInfo = { |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 267032e2a..aeb69e7b4 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -454,6 +454,10 @@ export class ActorModel extends Model<ActorModel> { | |||
454 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | 454 | return 'acct:' + this.preferredUsername + '@' + this.getHost() |
455 | } | 455 | } |
456 | 456 | ||
457 | getIdentifier () { | ||
458 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | ||
459 | } | ||
460 | |||
457 | getHost () { | 461 | getHost () { |
458 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST | 462 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST |
459 | } | 463 | } |