diff --git a/models/user/search.go b/models/user/search.go index 45b051187ea0..382b6fac2b08 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -65,7 +65,19 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess builder.Like{"LOWER(full_name)", lowerKeyword}, ) if opts.SearchByEmail { - keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword}) + var emailCond builder.Cond + emailCond = builder.Like{"LOWER(email)", lowerKeyword} + if opts.Actor == nil { + emailCond = emailCond.And(builder.Eq{"keep_email_private": false}) + } else if !opts.Actor.IsAdmin { + emailCond = emailCond.And( + builder.Or( + builder.Eq{"keep_email_private": false}, + builder.Eq{"id": opts.Actor.ID}, + ), + ) + } + keywordCond = keywordCond.Or(emailCond) } cond = cond.And(keywordCond) diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index fedad87fc4fa..2c277a18c739 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -68,11 +68,12 @@ func Search(ctx *context.APIContext) { users = []*user_model.User{user_model.NewActionsUser()} default: users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ - Actor: ctx.Doer, - Keyword: ctx.FormTrim("q"), - UID: uid, - Type: user_model.UserTypeIndividual, - ListOptions: listOptions, + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + UID: uid, + Type: user_model.UserTypeIndividual, + SearchByEmail: true, + ListOptions: listOptions, }) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]any{ diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index f776b3532576..ff4671c54e94 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -109,3 +109,39 @@ func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { DecodeJSON(t, resp, &results) assert.Empty(t, results.Data) } + +func TestAPIUserSearchByEmail(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // admin can search user with private email + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + query := "user2@example.com" + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.Equal(t, 1, len(results.Data)) + assert.Equal(t, query, results.Data[0].Email) + + // no login user can not search user with private email + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) + resp = MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.Empty(t, results.Data) + + // user can search self with private email + user2 := "user2" + session = loginUser(t, user2) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &results) + assert.Equal(t, 1, len(results.Data)) + assert.Equal(t, query, results.Data[0].Email) +}