diff options
author | zeripath <art27@cantab.net> | 2022-07-14 08:22:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-14 08:22:09 +0100 |
commit | bffa30302070b594a1c40cdc56264b9731036fb3 (patch) | |
tree | 92104ff6b8a51f5d1506742427dd1399fa42428c /services/user/user.go | |
parent | 175705356cac06c22d13d86b31605a6ad6dd9642 (diff) | |
download | gitea-bffa30302070b594a1c40cdc56264b9731036fb3.tar.gz gitea-bffa30302070b594a1c40cdc56264b9731036fb3.zip |
Add option to purge users (#18064)
Add the ability to purge users when deleting them.
Close #15588
Signed-off-by: Andrew Thornton <art27@cantab.net>
Diffstat (limited to 'services/user/user.go')
-rw-r--r-- | services/user/user.go | 106 |
1 files changed, 102 insertions, 4 deletions
diff --git a/services/user/user.go b/services/user/user.go index 4db4d7ca17..448b7c2daf 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -21,19 +21,116 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/avatar" + "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/packages" ) // DeleteUser completely and permanently deletes everything of a user, // but issues/comments/pulls will be kept and shown as someone has been deleted, // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. -func DeleteUser(u *user_model.User) error { +func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { if u.IsOrganization() { return fmt.Errorf("%s is an organization not a user", u.Name) } + if purge { + // Disable the user first + // NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged. + if err := user_model.UpdateUserCols(ctx, &user_model.User{ + ID: u.ID, + IsActive: false, + IsRestricted: true, + IsAdmin: false, + ProhibitLogin: true, + Passwd: "", + Salt: "", + PasswdHashAlgo: "", + MaxRepoCreation: 0, + }, "is_active", "is_restricted", "is_admin", "prohibit_login", "max_repo_creation", "passwd", "salt", "passwd_hash_algo"); err != nil { + return fmt.Errorf("unable to disable user: %s[%d] prior to purge. UpdateUserCols: %w", u.Name, u.ID, err) + } + + // Force any logged in sessions to log out + // FIXME: We also need to tell the session manager to log them out too. + eventsource.GetManager().SendMessage(u.ID, &eventsource.Event{ + Name: "logout", + }) + + // Delete all repos belonging to this user + // Now this is not within a transaction because there are internal transactions within the DeleteRepository + // BUT: the db will still be consistent even if a number of repos have already been deleted. + // And in fact we want to capture any repositories that are being created in other transactions in the meantime + // + // An alternative option here would be write a DeleteAllRepositoriesForUserID function which would delete all of the repos + // but such a function would likely get out of date + for { + repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ + ListOptions: db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: 1, + }, + Private: true, + OwnerID: u.ID, + }) + if err != nil { + return fmt.Errorf("SearchRepositoryByName: %v", err) + } + if len(repos) == 0 { + break + } + for _, repo := range repos { + if err := models.DeleteRepository(u, u.ID, repo.ID); err != nil { + return fmt.Errorf("unable to delete repository %s for %s[%d]. Error: %v", repo.Name, u.Name, u.ID, err) + } + } + } + + // Remove from Organizations and delete last owner organizations + // Now this is not within a transaction because there are internal transactions within the DeleteOrganization + // BUT: the db will still be consistent even if a number of organizations memberships and organizations have already been deleted + // And in fact we want to capture any organization additions that are being created in other transactions in the meantime + // + // An alternative option here would be write a function which would delete all organizations but it seems + // but such a function would likely get out of date + for { + orgs, err := organization.FindOrgs(organization.FindOrgOptions{ + ListOptions: db.ListOptions{ + PageSize: repo_model.RepositoryListDefaultPageSize, + Page: 1, + }, + UserID: u.ID, + IncludePrivate: true, + }) + if err != nil { + return fmt.Errorf("unable to find org list for %s[%d]. Error: %v", u.Name, u.ID, err) + } + if len(orgs) == 0 { + break + } + for _, org := range orgs { + if err := models.RemoveOrgUser(org.ID, u.ID); err != nil { + if organization.IsErrLastOrgOwner(err) { + err = organization.DeleteOrganization(ctx, org) + } + if err != nil { + return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %v", u.Name, u.ID, org.Name, org.ID, err) + } + } + } + } + + // Delete Packages + if setting.Packages.Enabled { + if _, err := packages.RemoveAllPackages(ctx, u.ID); err != nil { + return err + } + } + } + ctx, committer, err := db.TxContext() if err != nil { return err @@ -41,7 +138,8 @@ func DeleteUser(u *user_model.User) error { defer committer.Close() // Note: A user owns any repository or belongs to any organization - // cannot perform delete operation. + // cannot perform delete operation. This causes a race with the purge above + // however consistency requires that we ensure that this is the case // Check ownership of repository. count, err := repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{OwnerID: u.ID}) @@ -66,7 +164,7 @@ func DeleteUser(u *user_model.User) error { return models.ErrUserOwnPackages{UID: u.ID} } - if err := models.DeleteUser(ctx, u); err != nil { + if err := models.DeleteUser(ctx, u, purge); err != nil { return fmt.Errorf("DeleteUser: %v", err) } @@ -117,7 +215,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { return db.ErrCancelledf("Before delete inactive user %s", u.Name) default: } - if err := DeleteUser(u); err != nil { + if err := DeleteUser(ctx, u, false); err != nil { // Ignore users that were set inactive by admin. if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { continue |