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