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