From 405ba258a84a567f230e78f957aeb7014faa2d4b Mon Sep 17 00:00:00 2001 From: soujasK <121026960+soujasK@users.noreply.github.com> Date: Sat, 31 Oct 2026 18:00:07 +0530 Subject: [PATCH] fix: list bot users in `gitea admin user list` and admin users API (#40060) Fixes https://github.com/go-gitea/gitea/issues/40056 Signed-off-by: Soujas K Co-authored-by: silverwind Co-authored-by: wxiaoguang --- cmd/admin_user_list.go | 10 +++++----- models/user/user.go | 15 +++++++++++---- routers/api/v1/admin/user.go | 2 +- routers/web/admin/users.go | 3 +-- tests/integration/api_admin_test.go | 17 ++++++++++------- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index 7f9ce57e24d..30d85551839 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -33,7 +33,7 @@ func runListUsers(ctx context.Context, c *cli.Command) error { return err } - users, err := user_model.GetAllUsers(ctx) + users, err := user_model.GetAllUsersForAdminListing(ctx) if err != nil { return err } @@ -41,17 +41,17 @@ func runListUsers(ctx context.Context, c *cli.Command) error { w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0) if c.IsSet("admin") { - fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n") + fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tType\n") for _, u := range users { if u.IsAdmin { - fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive) + fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%s\n", u.ID, u.Name, u.Email, u.IsActive, u.Type.DisplayName()) } } } else { twofa := user_model.UserList(users).GetTwoFaStatus(ctx) - fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n") + fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\tType\n") for _, u := range users { - fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID]) + fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\t%s\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID], u.Type.DisplayName()) } } diff --git a/models/user/user.go b/models/user/user.go index c4e366e667c..3bbf52b082c 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -68,13 +68,20 @@ const ( UserTypeRemoteUser // 5 ) +// AdminListedUserTypes HINT: USER-TYPE-GLOSSARY: administrators list every user type except organizations +var AdminListedUserTypes = []UserType{UserTypeIndividual, UserTypeUserReserved, UserTypeBot, UserTypeRemoteUser} + // DisplayName returns the English name of the user type for logs and the CLI, the UI translates "concept_user_*" instead func (t UserType) DisplayName() string { switch t { case UserTypeOrganization, UserTypeOrganizationReserved: return "Organization" + case UserTypeUserReserved: + return "Reserved" case UserTypeBot: return "Bot" + case UserTypeRemoteUser: + return "Remote" default: return "User" } @@ -251,10 +258,10 @@ func (u *User) GetEmail() string { return u.Email } -// GetAllUsers returns a slice of all individual users found in DB. -func GetAllUsers(ctx context.Context) ([]*User, error) { - users := make([]*User, 0) - return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) +// GetAllUsersForAdminListing returns a slice of all users of AdminListedUserTypes found in DB. +func GetAllUsersForAdminListing(ctx context.Context) (users []*User, err error) { + err = db.GetEngine(ctx).OrderBy("id").In("type", AdminListedUserTypes).Find(&users) + return users, err } // IsLocal returns true if user login type is LoginPlain. diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 8205b12f2e8..585a32c9dca 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -459,7 +459,7 @@ func SearchUsers(ctx *context.APIContext) { searchOpts := user_model.SearchUserOptions{ Actor: ctx.Doer, - Types: []user_model.UserType{user_model.UserTypeIndividual}, + Types: user_model.AdminListedUserTypes, LoginName: ctx.FormTrim("login_name"), SourceID: ctx.FormOptionalInt64("source_id"), Keyword: ctx.FormTrim("q"), diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index e41b4807e04..70ae4af1ebd 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -70,8 +70,7 @@ func Users(ctx *context.Context) { sortType := ctx.FormString("sort", UserSearchDefaultAdminSort) - // unfiltered, an administrator needs to list every account kind - types := []user_model.UserType{user_model.UserTypeIndividual, user_model.UserTypeUserReserved, user_model.UserTypeBot, user_model.UserTypeRemoteUser} + types := user_model.AdminListedUserTypes userTypeFilter := api.UserTypeString(ctx.FormString("user_type")) ctx.Data["UserTypeFilter"] = "" if t, err := convert.UserTypeFromString(userTypeFilter); err == nil { diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index dccf666e2e2..d4780f87987 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -22,6 +22,8 @@ import ( "gitea.dev/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "xorm.io/builder" ) func TestAPIAdminListPackages(t *testing.T) { @@ -55,20 +57,21 @@ func TestAPIAdminAccessAndSSHKeys(t *testing.T) { t.Run("ListUsers", func(t *testing.T) { adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) + bot := &user_model.User{Name: "list-bot", Email: "list-bot@example.com", Type: user_model.UserTypeBot} + require.NoError(t, user_model.CreateUser(t.Context(), bot, &user_model.Meta{})) - req := NewRequest(t, "GET", "/api/v1/admin/users"). + req := NewRequest(t, "GET", "/api/v1/admin/users?limit=50"). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) users := DecodeJSON(t, resp, []api.User{}) - found := false + userTypes := map[string]api.UserTypeString{} for _, user := range users { - if user.UserName == adminUsername { - found = true - } + userTypes[user.UserName] = user.Type } - assert.True(t, found) - numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0") + assert.Equal(t, api.UserTypeStringUser, userTypes[adminUsername]) + assert.Equal(t, api.UserTypeStringBot, userTypes[bot.Name]) + numberOfUsers := unittest.GetCount(t, &user_model.User{}, builder.In("type", user_model.AdminListedUserTypes)) assert.Len(t, users, numberOfUsers) })