diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts index 59c48f4fe..24fac1a04 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comment.component.ts @@ -132,7 +132,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { this.timestampClicked.emit(timestamp) } - canBeRemovedOrApprovedByUser () { + canBeRemovedUser () { return this.comment.account && this.isUserLoggedIn() && ( this.user.account.id === this.comment.account.id || @@ -141,6 +141,14 @@ export class VideoCommentComponent implements OnInit, OnChanges { ) } + canBeApprovedByUser () { + return this.comment.account && this.isUserLoggedIn() && + ( + this.user.account.id === this.video.account.id || + this.user.hasRight(UserRight.MANAGE_ANY_VIDEO_COMMENT) + ) + } + isRedraftableByUser () { return ( this.comment.account && @@ -201,7 +209,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { this.prependModerationActions = [] - if (this.canBeRemovedOrApprovedByUser() && this.comment.heldForReview) { + if (this.canBeApprovedByUser() && this.comment.heldForReview) { this.prependModerationActions.push({ label: $localize`Approve`, iconName: 'tick', @@ -217,7 +225,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { }) } - if (this.canBeRemovedOrApprovedByUser()) { + if (this.canBeRemovedUser()) { this.prependModerationActions.push({ label: $localize`Remove`, iconName: 'delete', diff --git a/packages/tests/src/api/check-params/video-comments.ts b/packages/tests/src/api/check-params/video-comments.ts index 19eec417c..e187f28e6 100644 --- a/packages/tests/src/api/check-params/video-comments.ts +++ b/packages/tests/src/api/check-params/video-comments.ts @@ -502,10 +502,14 @@ describe('Test video comments API validator', function () { let videoId: string let commentId: number let deletedCommentId: number + let userAccessToken3: string before(async function () { + userAccessToken3 = await server.users.generateUserAndToken('user3') + { const res = await server.videos.upload({ + token: userAccessToken, attributes: { name: 'review policy', commentsPolicy: VideoCommentPolicy.REQUIRES_APPROVAL @@ -516,12 +520,12 @@ describe('Test video comments API validator', function () { } { - const res = await server.comments.createThread({ text: 'thread', videoId, token: userAccessToken }) + const res = await server.comments.createThread({ text: 'thread', videoId, token: userAccessToken2 }) commentId = res.id } { - const res = await server.comments.createThread({ text: 'deleted', videoId, token: userAccessToken }) + const res = await server.comments.createThread({ text: 'deleted', videoId, token: userAccessToken2 }) deletedCommentId = res.id await server.comments.delete({ commentId: deletedCommentId, videoId }) @@ -533,15 +537,19 @@ describe('Test video comments API validator', function () { }) it('Should fail with another user', async function () { + await server.comments.approve({ token: userAccessToken3, commentId, videoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + }) + + it('Should fail with the owner', async function () { await server.comments.approve({ token: userAccessToken2, commentId, videoId, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) }) it('Should fail with an incorrect video', async function () { - await server.comments.approve({ token: userAccessToken2, commentId, videoId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + await server.comments.approve({ token: userAccessToken, commentId, videoId: 42, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should fail with an incorrect comment', async function () { - await server.comments.approve({ token: userAccessToken2, commentId: 42, videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) + await server.comments.approve({ token: userAccessToken, commentId: 42, videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) }) it('Should fail with a deleted comment', async function () { diff --git a/server/core/middlewares/validators/videos/video-comments.ts b/server/core/middlewares/validators/videos/video-comments.ts index 836a46854..a3f15dc99 100644 --- a/server/core/middlewares/validators/videos/video-comments.ts +++ b/server/core/middlewares/validators/videos/video-comments.ts @@ -237,7 +237,21 @@ function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MC return false } - return checkUserCanManageVideoComment(user, videoComment, res) + const userAccount = user.Account + + if ( + user.hasRight(UserRight.MANAGE_ANY_VIDEO_COMMENT) === false && // Not a moderator + videoComment.accountId !== userAccount.id && // Not the comment owner + videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner + ) { + res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot remove video comment of another user' + }) + return false + } + + return true } function checkUserCanApproveVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { @@ -257,20 +271,15 @@ function checkUserCanApproveVideoComment (user: MUserAccountUrl, videoComment: M return false } - return checkUserCanManageVideoComment(user, videoComment, res) -} - -function checkUserCanManageVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { const userAccount = user.Account if ( user.hasRight(UserRight.MANAGE_ANY_VIDEO_COMMENT) === false && // Not a moderator - videoComment.accountId !== userAccount.id && // Not the comment owner videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner ) { res.fail({ status: HttpStatusCode.FORBIDDEN_403, - message: 'Cannot remove video comment of another user' + message: 'Cannot approve video comment of another user' }) return false }