code.gitea.io/gitea@v1.22.3/services/user/user.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package user 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "strings" 11 "time" 12 13 "code.gitea.io/gitea/models" 14 "code.gitea.io/gitea/models/db" 15 "code.gitea.io/gitea/models/organization" 16 packages_model "code.gitea.io/gitea/models/packages" 17 repo_model "code.gitea.io/gitea/models/repo" 18 system_model "code.gitea.io/gitea/models/system" 19 user_model "code.gitea.io/gitea/models/user" 20 "code.gitea.io/gitea/modules/eventsource" 21 "code.gitea.io/gitea/modules/log" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/storage" 24 "code.gitea.io/gitea/modules/util" 25 "code.gitea.io/gitea/services/agit" 26 asymkey_service "code.gitea.io/gitea/services/asymkey" 27 org_service "code.gitea.io/gitea/services/org" 28 "code.gitea.io/gitea/services/packages" 29 container_service "code.gitea.io/gitea/services/packages/container" 30 repo_service "code.gitea.io/gitea/services/repository" 31 ) 32 33 // RenameUser renames a user 34 func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { 35 // Non-local users are not allowed to change their username. 36 if !u.IsOrganization() && !u.IsLocal() { 37 return user_model.ErrUserIsNotLocal{ 38 UID: u.ID, 39 Name: u.Name, 40 } 41 } 42 43 if newUserName == u.Name { 44 return nil 45 } 46 47 if err := user_model.IsUsableUsername(newUserName); err != nil { 48 return err 49 } 50 51 onlyCapitalization := strings.EqualFold(newUserName, u.Name) 52 oldUserName := u.Name 53 54 if onlyCapitalization { 55 u.Name = newUserName 56 if err := user_model.UpdateUserCols(ctx, u, "name"); err != nil { 57 u.Name = oldUserName 58 return err 59 } 60 return repo_model.UpdateRepositoryOwnerNames(ctx, u.ID, newUserName) 61 } 62 63 ctx, committer, err := db.TxContext(ctx) 64 if err != nil { 65 return err 66 } 67 defer committer.Close() 68 69 isExist, err := user_model.IsUserExist(ctx, u.ID, newUserName) 70 if err != nil { 71 return err 72 } 73 if isExist { 74 return user_model.ErrUserAlreadyExist{ 75 Name: newUserName, 76 } 77 } 78 79 if err = repo_model.UpdateRepositoryOwnerName(ctx, oldUserName, newUserName); err != nil { 80 return err 81 } 82 83 if err = user_model.NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil { 84 return err 85 } 86 87 if err := agit.UserNameChanged(ctx, u, newUserName); err != nil { 88 return err 89 } 90 if err := container_service.UpdateRepositoryNames(ctx, u, newUserName); err != nil { 91 return err 92 } 93 94 u.Name = newUserName 95 u.LowerName = strings.ToLower(newUserName) 96 if err := user_model.UpdateUserCols(ctx, u, "name", "lower_name"); err != nil { 97 u.Name = oldUserName 98 u.LowerName = strings.ToLower(oldUserName) 99 return err 100 } 101 102 // Do not fail if directory does not exist 103 if err = util.Rename(user_model.UserPath(oldUserName), user_model.UserPath(newUserName)); err != nil && !os.IsNotExist(err) { 104 u.Name = oldUserName 105 u.LowerName = strings.ToLower(oldUserName) 106 return fmt.Errorf("rename user directory: %w", err) 107 } 108 109 if err = committer.Commit(); err != nil { 110 u.Name = oldUserName 111 u.LowerName = strings.ToLower(oldUserName) 112 if err2 := util.Rename(user_model.UserPath(newUserName), user_model.UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { 113 log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2) 114 return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2) 115 } 116 return err 117 } 118 return nil 119 } 120 121 // DeleteUser completely and permanently deletes everything of a user, 122 // but issues/comments/pulls will be kept and shown as someone has been deleted, 123 // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. 124 func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { 125 if u.IsOrganization() { 126 return fmt.Errorf("%s is an organization not a user", u.Name) 127 } 128 129 if u.IsActive && user_model.IsLastAdminUser(ctx, u) { 130 return models.ErrDeleteLastAdminUser{UID: u.ID} 131 } 132 133 if purge { 134 // Disable the user first 135 // 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. 136 if err := user_model.UpdateUserCols(ctx, &user_model.User{ 137 ID: u.ID, 138 IsActive: false, 139 IsRestricted: true, 140 IsAdmin: false, 141 ProhibitLogin: true, 142 Passwd: "", 143 Salt: "", 144 PasswdHashAlgo: "", 145 MaxRepoCreation: 0, 146 }, "is_active", "is_restricted", "is_admin", "prohibit_login", "max_repo_creation", "passwd", "salt", "passwd_hash_algo"); err != nil { 147 return fmt.Errorf("unable to disable user: %s[%d] prior to purge. UpdateUserCols: %w", u.Name, u.ID, err) 148 } 149 150 // Force any logged in sessions to log out 151 // FIXME: We also need to tell the session manager to log them out too. 152 eventsource.GetManager().SendMessage(u.ID, &eventsource.Event{ 153 Name: "logout", 154 }) 155 156 // Delete all repos belonging to this user 157 // Now this is not within a transaction because there are internal transactions within the DeleteRepository 158 // BUT: the db will still be consistent even if a number of repos have already been deleted. 159 // And in fact we want to capture any repositories that are being created in other transactions in the meantime 160 // 161 // An alternative option here would be write a DeleteAllRepositoriesForUserID function which would delete all of the repos 162 // but such a function would likely get out of date 163 err := repo_service.DeleteOwnerRepositoriesDirectly(ctx, u) 164 if err != nil { 165 return err 166 } 167 168 // Remove from Organizations and delete last owner organizations 169 // Now this is not within a transaction because there are internal transactions within the DeleteOrganization 170 // BUT: the db will still be consistent even if a number of organizations memberships and organizations have already been deleted 171 // And in fact we want to capture any organization additions that are being created in other transactions in the meantime 172 // 173 // An alternative option here would be write a function which would delete all organizations but it seems 174 // but such a function would likely get out of date 175 for { 176 orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ 177 ListOptions: db.ListOptions{ 178 PageSize: repo_model.RepositoryListDefaultPageSize, 179 Page: 1, 180 }, 181 UserID: u.ID, 182 IncludePrivate: true, 183 }) 184 if err != nil { 185 return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err) 186 } 187 if len(orgs) == 0 { 188 break 189 } 190 for _, org := range orgs { 191 if err := models.RemoveOrgUser(ctx, org, u); err != nil { 192 if organization.IsErrLastOrgOwner(err) { 193 err = org_service.DeleteOrganization(ctx, org, true) 194 if err != nil { 195 return fmt.Errorf("unable to delete organization %d: %w", org.ID, err) 196 } 197 } 198 if err != nil { 199 return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %w", u.Name, u.ID, org.Name, org.ID, err) 200 } 201 } 202 } 203 } 204 205 // Delete Packages 206 if setting.Packages.Enabled { 207 if _, err := packages.RemoveAllPackages(ctx, u.ID); err != nil { 208 return err 209 } 210 } 211 } 212 213 ctx, committer, err := db.TxContext(ctx) 214 if err != nil { 215 return err 216 } 217 defer committer.Close() 218 219 // Note: A user owns any repository or belongs to any organization 220 // cannot perform delete operation. This causes a race with the purge above 221 // however consistency requires that we ensure that this is the case 222 223 // Check ownership of repository. 224 count, err := repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{OwnerID: u.ID}) 225 if err != nil { 226 return fmt.Errorf("GetRepositoryCount: %w", err) 227 } else if count > 0 { 228 return models.ErrUserOwnRepos{UID: u.ID} 229 } 230 231 // Check membership of organization. 232 count, err = organization.GetOrganizationCount(ctx, u) 233 if err != nil { 234 return fmt.Errorf("GetOrganizationCount: %w", err) 235 } else if count > 0 { 236 return models.ErrUserHasOrgs{UID: u.ID} 237 } 238 239 // Check ownership of packages. 240 if ownsPackages, err := packages_model.HasOwnerPackages(ctx, u.ID); err != nil { 241 return fmt.Errorf("HasOwnerPackages: %w", err) 242 } else if ownsPackages { 243 return models.ErrUserOwnPackages{UID: u.ID} 244 } 245 246 if err := deleteUser(ctx, u, purge); err != nil { 247 return fmt.Errorf("DeleteUser: %w", err) 248 } 249 250 if err := committer.Commit(); err != nil { 251 return err 252 } 253 _ = committer.Close() 254 255 if err = asymkey_service.RewriteAllPublicKeys(ctx); err != nil { 256 return err 257 } 258 if err = asymkey_service.RewriteAllPrincipalKeys(ctx); err != nil { 259 return err 260 } 261 262 // Note: There are something just cannot be roll back, so just keep error logs of those operations. 263 path := user_model.UserPath(u.Name) 264 if err = util.RemoveAll(path); err != nil { 265 err = fmt.Errorf("failed to RemoveAll %s: %w", path, err) 266 _ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 267 } 268 269 if u.Avatar != "" { 270 avatarPath := u.CustomAvatarRelativePath() 271 if err = storage.Avatars.Delete(avatarPath); err != nil { 272 err = fmt.Errorf("failed to remove %s: %w", avatarPath, err) 273 _ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 274 } 275 } 276 277 return nil 278 } 279 280 // DeleteInactiveUsers deletes all inactive users and their email addresses. 281 func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { 282 inactiveUsers, err := user_model.GetInactiveUsers(ctx, olderThan) 283 if err != nil { 284 return err 285 } 286 287 // FIXME: should only update authorized_keys file once after all deletions. 288 for _, u := range inactiveUsers { 289 if err = DeleteUser(ctx, u, false); err != nil { 290 // Ignore inactive users that were ever active but then were set inactive by admin 291 if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { 292 continue 293 } 294 select { 295 case <-ctx.Done(): 296 return db.ErrCancelledf("when deleting inactive user %q", u.Name) 297 default: 298 return err 299 } 300 } 301 } 302 return nil // TODO: there could be still inactive users left, and the number would increase gradually 303 }