1// Copyright 2021 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package user 6 7import ( 8 "context" 9 "crypto/md5" 10 "fmt" 11 "image/png" 12 "io" 13 "time" 14 15 "code.gitea.io/gitea/models" 16 admin_model "code.gitea.io/gitea/models/admin" 17 asymkey_model "code.gitea.io/gitea/models/asymkey" 18 "code.gitea.io/gitea/models/db" 19 repo_model "code.gitea.io/gitea/models/repo" 20 user_model "code.gitea.io/gitea/models/user" 21 "code.gitea.io/gitea/modules/avatar" 22 "code.gitea.io/gitea/modules/log" 23 "code.gitea.io/gitea/modules/storage" 24 "code.gitea.io/gitea/modules/util" 25) 26 27// DeleteUser completely and permanently deletes everything of a user, 28// but issues/comments/pulls will be kept and shown as someone has been deleted, 29// unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. 30func DeleteUser(u *user_model.User) error { 31 if u.IsOrganization() { 32 return fmt.Errorf("%s is an organization not a user", u.Name) 33 } 34 35 ctx, committer, err := db.TxContext() 36 if err != nil { 37 return err 38 } 39 defer committer.Close() 40 41 // Note: A user owns any repository or belongs to any organization 42 // cannot perform delete operation. 43 44 // Check ownership of repository. 45 count, err := repo_model.GetRepositoryCount(ctx, u.ID) 46 if err != nil { 47 return fmt.Errorf("GetRepositoryCount: %v", err) 48 } else if count > 0 { 49 return models.ErrUserOwnRepos{UID: u.ID} 50 } 51 52 // Check membership of organization. 53 count, err = models.GetOrganizationCount(ctx, u) 54 if err != nil { 55 return fmt.Errorf("GetOrganizationCount: %v", err) 56 } else if count > 0 { 57 return models.ErrUserHasOrgs{UID: u.ID} 58 } 59 60 if err := models.DeleteUser(ctx, u); err != nil { 61 return fmt.Errorf("DeleteUser: %v", err) 62 } 63 64 if err := committer.Commit(); err != nil { 65 return err 66 } 67 committer.Close() 68 69 if err = asymkey_model.RewriteAllPublicKeys(); err != nil { 70 return err 71 } 72 if err = asymkey_model.RewriteAllPrincipalKeys(); err != nil { 73 return err 74 } 75 76 // Note: There are something just cannot be roll back, 77 // so just keep error logs of those operations. 78 path := user_model.UserPath(u.Name) 79 if err := util.RemoveAll(path); err != nil { 80 err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 81 _ = admin_model.CreateNotice(db.DefaultContext, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 82 return err 83 } 84 85 if u.Avatar != "" { 86 avatarPath := u.CustomAvatarRelativePath() 87 if err := storage.Avatars.Delete(avatarPath); err != nil { 88 err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 89 _ = admin_model.CreateNotice(db.DefaultContext, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 90 return err 91 } 92 } 93 94 return nil 95} 96 97// DeleteInactiveUsers deletes all inactive users and email addresses. 98func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { 99 users, err := user_model.GetInactiveUsers(ctx, olderThan) 100 if err != nil { 101 return err 102 } 103 104 // FIXME: should only update authorized_keys file once after all deletions. 105 for _, u := range users { 106 select { 107 case <-ctx.Done(): 108 return db.ErrCancelledf("Before delete inactive user %s", u.Name) 109 default: 110 } 111 if err := DeleteUser(u); err != nil { 112 // Ignore users that were set inactive by admin. 113 if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) { 114 continue 115 } 116 return err 117 } 118 } 119 120 return user_model.DeleteInactiveEmailAddresses(ctx) 121} 122 123// UploadAvatar saves custom avatar for user. 124func UploadAvatar(u *user_model.User, data []byte) error { 125 m, err := avatar.Prepare(data) 126 if err != nil { 127 return err 128 } 129 130 ctx, committer, err := db.TxContext() 131 if err != nil { 132 return err 133 } 134 defer committer.Close() 135 136 u.UseCustomAvatar = true 137 // Different users can upload same image as avatar 138 // If we prefix it with u.ID, it will be separated 139 // Otherwise, if any of the users delete his avatar 140 // Other users will lose their avatars too. 141 u.Avatar = fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) 142 if err = user_model.UpdateUserCols(ctx, u, "use_custom_avatar", "avatar"); err != nil { 143 return fmt.Errorf("updateUser: %v", err) 144 } 145 146 if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { 147 if err := png.Encode(w, *m); err != nil { 148 log.Error("Encode: %v", err) 149 } 150 return err 151 }); err != nil { 152 return fmt.Errorf("Failed to create dir %s: %v", u.CustomAvatarRelativePath(), err) 153 } 154 155 return committer.Commit() 156} 157 158// DeleteAvatar deletes the user's custom avatar. 159func DeleteAvatar(u *user_model.User) error { 160 aPath := u.CustomAvatarRelativePath() 161 log.Trace("DeleteAvatar[%d]: %s", u.ID, aPath) 162 if len(u.Avatar) > 0 { 163 if err := storage.Avatars.Delete(aPath); err != nil { 164 return fmt.Errorf("Failed to remove %s: %v", aPath, err) 165 } 166 } 167 168 u.UseCustomAvatar = false 169 u.Avatar = "" 170 if _, err := db.GetEngine(db.DefaultContext).ID(u.ID).Cols("avatar, use_custom_avatar").Update(u); err != nil { 171 return fmt.Errorf("UpdateUser: %v", err) 172 } 173 return nil 174} 175