Skip to content

Commit a0e424d

Browse files
6543zeripath
andauthored
Enhance Ghost comment mitigation Settings (#14392)
* refactor models.DeleteComment and delete related reactions too * use deleteComment for UserDeleteWithCommentsMaxDays in DeleteUser * nits * Use time.Duration as other time settings have * docs * Resolve Fixme & fix potential deadlock * Disabled by Default * Update Config Value Description * switch args * Update models/issue_comment.go Co-authored-by: zeripath <[email protected]> Co-authored-by: zeripath <[email protected]>
1 parent 0e2e734 commit a0e424d

File tree

14 files changed

+72
-41
lines changed

14 files changed

+72
-41
lines changed

custom/conf/app.example.ini

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,8 @@ AUTO_WATCH_NEW_REPOS = true
688688
; Default value for AutoWatchOnChanges
689689
; Make the user watch a repository When they commit for the first time
690690
AUTO_WATCH_ON_CHANGES = false
691-
; Default value for the minimum age a user has to exist before deletion to keep issue comments.
692-
; If a user deletes his account before that amount of days, his comments will be deleted as well.
693-
USER_DELETE_WITH_COMMENTS_MAX_DAYS = 0
691+
; Minimum amount of time a user must exist before comments are kept when the user is deleted.
692+
USER_DELETE_WITH_COMMENTS_MAX_TIME = 0
694693

695694
[webhook]
696695
; Hook task queue length, increase if webhook shooting starts hanging

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ relation to port exhaustion.
474474
- `ALLOW_ONLY_EXTERNAL_REGISTRATION`: **false** Set to true to force registration only using third-party services.
475475
- `NO_REPLY_ADDRESS`: **DOMAIN** Default value for the domain part of the user's email address in the git log if he has set KeepEmailPrivate to true.
476476
The user's email will be replaced with a concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
477-
- `USER_DELETE_WITH_COMMENTS_MAX_DAYS`: **0** If a user deletes his account before that amount of days, his comments will be deleted as well.
477+
- `USER_DELETE_WITH_COMMENTS_MAX_TIME`: **0** Minimum amount of time a user must exist before comments are kept when the user is deleted.
478478

479479
## SSH Minimum Key Sizes (`ssh.minimum_key_sizes`)
480480

models/admin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func removeStorageWithNotice(e Engine, bucket storage.ObjectStorage, title, path
7575
if err := bucket.Delete(path); err != nil {
7676
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
7777
log.Warn(title+" [%s]: %v", path, err)
78-
if err = createNotice(x, NoticeRepository, desc); err != nil {
78+
if err = createNotice(e, NoticeRepository, desc); err != nil {
7979
log.Error("CreateRepositoryNotice: %v", err)
8080
}
8181
}

models/issue_assignees.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func isUserAssignedToIssue(e Engine, issue *Issue, user *User) (isAssigned bool,
8282
}
8383

8484
// ClearAssigneeByUserID deletes all assignments of an user
85-
func clearAssigneeByUserID(sess *xorm.Session, userID int64) (err error) {
85+
func clearAssigneeByUserID(sess Engine, userID int64) (err error) {
8686
_, err = sess.Delete(&IssueAssignees{AssigneeID: userID})
8787
return
8888
}

models/issue_comment.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,33 +1037,41 @@ func UpdateComment(c *Comment, doer *User) error {
10371037
}
10381038

10391039
// DeleteComment deletes the comment
1040-
func DeleteComment(comment *Comment, doer *User) error {
1040+
func DeleteComment(comment *Comment) error {
10411041
sess := x.NewSession()
10421042
defer sess.Close()
10431043
if err := sess.Begin(); err != nil {
10441044
return err
10451045
}
10461046

1047-
if _, err := sess.Delete(&Comment{
1047+
if err := deleteComment(sess, comment); err != nil {
1048+
return err
1049+
}
1050+
1051+
return sess.Commit()
1052+
}
1053+
1054+
func deleteComment(e Engine, comment *Comment) error {
1055+
if _, err := e.Delete(&Comment{
10481056
ID: comment.ID,
10491057
}); err != nil {
10501058
return err
10511059
}
10521060

10531061
if comment.Type == CommentTypeComment {
1054-
if _, err := sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
1062+
if _, err := e.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
10551063
return err
10561064
}
10571065
}
1058-
if _, err := sess.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}); err != nil {
1066+
if _, err := e.Where("comment_id = ?", comment.ID).Cols("is_deleted").Update(&Action{IsDeleted: true}); err != nil {
10591067
return err
10601068
}
10611069

1062-
if err := comment.neuterCrossReferences(sess); err != nil {
1070+
if err := comment.neuterCrossReferences(e); err != nil {
10631071
return err
10641072
}
10651073

1066-
return sess.Commit()
1074+
return deleteReaction(e, &ReactionOptions{Comment: comment})
10671075
}
10681076

10691077
// CodeComments represents comments on code by using this structure: FILENAME -> LINE (+ == proposed; - == previous) -> COMMENTS

models/issue_reaction.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,15 @@ func CreateCommentReaction(doer *User, issue *Issue, comment *Comment, content s
178178
})
179179
}
180180

181-
func deleteReaction(e *xorm.Session, opts *ReactionOptions) error {
181+
func deleteReaction(e Engine, opts *ReactionOptions) error {
182182
reaction := &Reaction{
183-
Type: opts.Type,
184-
UserID: opts.Doer.ID,
185-
IssueID: opts.Issue.ID,
183+
Type: opts.Type,
184+
}
185+
if opts.Doer != nil {
186+
reaction.UserID = opts.Doer.ID
187+
}
188+
if opts.Issue != nil {
189+
reaction.IssueID = opts.Issue.ID
186190
}
187191
if opts.Comment != nil {
188192
reaction.CommentID = opts.Comment.ID

models/user.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import (
3838
"golang.org/x/crypto/scrypt"
3939
"golang.org/x/crypto/ssh"
4040
"xorm.io/builder"
41-
"xorm.io/xorm"
4241
)
4342

4443
// UserType defines the user type
@@ -1071,8 +1070,7 @@ func deleteBeans(e Engine, beans ...interface{}) (err error) {
10711070
return nil
10721071
}
10731072

1074-
// FIXME: need some kind of mechanism to record failure. HINT: system notice
1075-
func deleteUser(e *xorm.Session, u *User) error {
1073+
func deleteUser(e Engine, u *User) error {
10761074
// Note: A user owns any repository or belongs to any organization
10771075
// cannot perform delete operation.
10781076

@@ -1151,12 +1149,30 @@ func deleteUser(e *xorm.Session, u *User) error {
11511149
return fmt.Errorf("deleteBeans: %v", err)
11521150
}
11531151

1154-
if setting.Service.UserDeleteWithCommentsMaxDays != 0 &&
1155-
u.CreatedUnix.AsTime().Add(time.Duration(setting.Service.UserDeleteWithCommentsMaxDays)*24*time.Hour).After(time.Now()) {
1156-
if err = deleteBeans(e,
1157-
&Comment{PosterID: u.ID},
1158-
); err != nil {
1159-
return fmt.Errorf("deleteBeans: %v", err)
1152+
if setting.Service.UserDeleteWithCommentsMaxTime != 0 &&
1153+
u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now()) {
1154+
1155+
// Delete Comments
1156+
const batchSize = 50
1157+
for start := 0; ; start += batchSize {
1158+
comments := make([]*Comment, 0, batchSize)
1159+
if err = e.Where("type=? AND poster_id=?", CommentTypeComment, u.ID).Limit(batchSize, start).Find(&comments); err != nil {
1160+
return err
1161+
}
1162+
if len(comments) == 0 {
1163+
break
1164+
}
1165+
1166+
for _, comment := range comments {
1167+
if err = deleteComment(e, comment); err != nil {
1168+
return err
1169+
}
1170+
}
1171+
}
1172+
1173+
// Delete Reactions
1174+
if err = deleteReaction(e, &ReactionOptions{Doer: u}); err != nil {
1175+
return err
11601176
}
11611177
}
11621178

@@ -1195,18 +1211,21 @@ func deleteUser(e *xorm.Session, u *User) error {
11951211
return fmt.Errorf("Delete: %v", err)
11961212
}
11971213

1198-
// FIXME: system notice
11991214
// Note: There are something just cannot be roll back,
12001215
// so just keep error logs of those operations.
12011216
path := UserPath(u.Name)
1202-
if err := util.RemoveAll(path); err != nil {
1203-
return fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
1217+
if err = util.RemoveAll(path); err != nil {
1218+
err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
1219+
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
1220+
return err
12041221
}
12051222

12061223
if len(u.Avatar) > 0 {
12071224
avatarPath := u.CustomAvatarRelativePath()
1208-
if err := storage.Avatars.Delete(avatarPath); err != nil {
1209-
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
1225+
if err = storage.Avatars.Delete(avatarPath); err != nil {
1226+
err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
1227+
_ = createNotice(e, NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
1228+
return err
12101229
}
12111230
}
12121231

modules/setting/service.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package setting
66

77
import (
88
"regexp"
9+
"time"
910

1011
"code.gitea.io/gitea/modules/structs"
1112
)
@@ -50,7 +51,7 @@ var Service struct {
5051
AutoWatchNewRepos bool
5152
AutoWatchOnChanges bool
5253
DefaultOrgMemberVisible bool
53-
UserDeleteWithCommentsMaxDays int
54+
UserDeleteWithCommentsMaxTime time.Duration
5455

5556
// OpenID settings
5657
EnableOpenIDSignIn bool
@@ -103,7 +104,7 @@ func newService() {
103104
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
104105
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
105106
Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()
106-
Service.UserDeleteWithCommentsMaxDays = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_DAYS").MustInt(0)
107+
Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0)
107108

108109
sec = Cfg.Section("openid")
109110
Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock)

options/locale/locale_en-US.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ repos_none = You do not own any repositories
648648

649649
delete_account = Delete Your Account
650650
delete_prompt = This operation will permanently delete your user account. It <strong>CAN NOT</strong> be undone.
651-
delete_with_all_comments = Your account is younger than %d days. To avoid ghost comments, all issue/PR comments will be deleted with it.
651+
delete_with_all_comments = Your account is younger than %s. To avoid ghost comments, all issue/PR comments will be deleted with it.
652652
confirm_delete_account = Confirm Deletion
653653
delete_account_title = Delete User Account
654654
delete_account_desc = Are you sure you want to permanently delete this user account?

routers/api/v1/repo/issue_comment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ func deleteIssueComment(ctx *context.APIContext) {
509509
return
510510
}
511511

512-
if err = comment_service.DeleteComment(comment, ctx.User); err != nil {
512+
if err = comment_service.DeleteComment(ctx.User, comment); err != nil {
513513
ctx.Error(http.StatusInternalServerError, "DeleteCommentByID", err)
514514
return
515515
}

routers/repo/issue.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2125,7 +2125,7 @@ func DeleteComment(ctx *context.Context) {
21252125
return
21262126
}
21272127

2128-
if err = comment_service.DeleteComment(comment, ctx.User); err != nil {
2128+
if err = comment_service.DeleteComment(ctx.User, comment); err != nil {
21292129
ctx.ServerError("DeleteCommentByID", err)
21302130
return
21312131
}

routers/user/setting/account.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ func loadAccountData(ctx *context.Context) {
302302
ctx.Data["ActivationsPending"] = pendingActivation
303303
ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm
304304

305-
if setting.Service.UserDeleteWithCommentsMaxDays != 0 {
306-
ctx.Data["UserDeleteWithCommentsMaxDays"] = setting.Service.UserDeleteWithCommentsMaxDays
307-
ctx.Data["UserDeleteWithComments"] = ctx.User.CreatedUnix.AsTime().Add(time.Duration(setting.Service.UserDeleteWithCommentsMaxDays) * 24 * time.Hour).After(time.Now())
305+
if setting.Service.UserDeleteWithCommentsMaxTime != 0 {
306+
ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String()
307+
ctx.Data["UserDeleteWithComments"] = ctx.User.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())
308308
}
309309
}

services/comments/comments.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func UpdateComment(c *models.Comment, doer *models.User, oldContent string) erro
4343
}
4444

4545
// DeleteComment deletes the comment
46-
func DeleteComment(comment *models.Comment, doer *models.User) error {
47-
if err := models.DeleteComment(comment, doer); err != nil {
46+
func DeleteComment(doer *models.User, comment *models.Comment) error {
47+
if err := models.DeleteComment(comment); err != nil {
4848
return err
4949
}
5050

templates/user/settings/account.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@
174174
<div class="ui red message">
175175
<p class="text left">{{svg "octicon-alert"}} {{.i18n.Tr "settings.delete_prompt" | Str2html}}</p>
176176
{{ if .UserDeleteWithComments }}
177-
<p class="text left" style="font-weight: bold;">{{.i18n.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxDays | Str2html}}</p>
177+
<p class="text left" style="font-weight: bold;">{{.i18n.Tr "settings.delete_with_all_comments" .UserDeleteWithCommentsMaxTime | Str2html}}</p>
178178
{{ end }}
179179
</div>
180180
<form class="ui form ignore-dirty" id="delete-form" action="{{AppSubUrl}}/user/settings/account/delete" method="post">

0 commit comments

Comments
 (0)