fix: query host search by email (#15833)

> 📜 Related issue: https://github.com/fleetdm/fleet/issues/15522

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Jahziel Villasana-Espinoza 2024-01-02 09:25:09 -05:00 committed by GitHub
parent d6ee2ac121
commit 1b9b67aa9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 1 deletions

View File

@ -0,0 +1 @@
- Fixes bug in searching for hosts by email addresses.

View File

@ -2369,7 +2369,7 @@ func (ds *Datastore) SearchHosts(ctx context.Context, filter fleet.TeamFilter, m
if len(matchQuery) > 0 { if len(matchQuery) > 0 {
// first we'll find the hosts that match the search criteria, to keep thing simple, then we'll query again // first we'll find the hosts that match the search criteria, to keep thing simple, then we'll query again
// to get all the additional data for hosts that match the search criteria by host_id // to get all the additional data for hosts that match the search criteria by host_id
matchingHosts := "SELECT id FROM hosts WHERE TRUE" matchingHosts := "SELECT h.id FROM hosts h WHERE TRUE"
var args []interface{} var args []interface{}
// TODO: should search columns include display_name (requires join to host_display_names)? // TODO: should search columns include display_name (requires join to host_display_names)?
searchHostsQuery, args, matchesEmail := hostSearchLike(matchingHosts, args, matchQuery, hostSearchColumns...) searchHostsQuery, args, matchesEmail := hostSearchLike(matchingHosts, args, matchQuery, hostSearchColumns...)

View File

@ -1763,6 +1763,15 @@ func testHostsSearch(t *testing.T, ds *Datastore) {
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, hits, 3) assert.Len(t, hits, 3)
assert.Equal(t, []uint{h3.ID, h2.ID, h1.ID}, []uint{hits[0].ID, hits[1].ID, hits[2].ID}) assert.Equal(t, []uint{h3.ID, h2.ID, h1.ID}, []uint{hits[0].ID, hits[1].ID, hits[2].ID})
// Add email to mapping table
_, err = ds.writer(context.Background()).ExecContext(context.Background(), `INSERT INTO host_emails (host_id, email, source) VALUES (?, ?, ?)`,
hosts[0].ID, "a@b.c", "src1")
require.NoError(t, err)
// Verify search works
hits, err = ds.SearchHosts(context.Background(), filter, "a@b.c")
require.NoError(t, err)
assert.Len(t, hits, 1)
} }
func testSearchHostsWildCards(t *testing.T, ds *Datastore) { func testSearchHostsWildCards(t *testing.T, ds *Datastore) {

View File

@ -1118,6 +1118,8 @@ var (
nonacsiiReplace = regexp.MustCompile(`[^[:ascii:]]`) nonacsiiReplace = regexp.MustCompile(`[^[:ascii:]]`)
) )
// hostSearchLike searches hosts based on the given columns plus searching in hosts_emails. Note:
// the host from the `hosts` table must be aliased to `h` in `sql`.
func hostSearchLike(sql string, params []interface{}, match string, columns ...string) (string, []interface{}, bool) { func hostSearchLike(sql string, params []interface{}, match string, columns ...string) (string, []interface{}, bool) {
var matchesEmail bool var matchesEmail bool
base, args := searchLike(sql, params, match, columns...) base, args := searchLike(sql, params, match, columns...)

View File

@ -6232,6 +6232,22 @@ func (s *integrationTestSuite) TestSearchHosts() {
s.DoJSON("POST", "/api/latest/fleet/hosts/search", searchHostsRequest{MatchQuery: "foo.local0"}, http.StatusOK, &searchResp) s.DoJSON("POST", "/api/latest/fleet/hosts/search", searchHostsRequest{MatchQuery: "foo.local0"}, http.StatusOK, &searchResp)
require.Len(t, searchResp.Hosts, 1) require.Len(t, searchResp.Hosts, 1)
require.Greater(t, searchResp.Hosts[0].SoftwareUpdatedAt, searchResp.Hosts[0].CreatedAt) require.Greater(t, searchResp.Hosts[0].SoftwareUpdatedAt, searchResp.Hosts[0].CreatedAt)
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
_, err := db.ExecContext(
context.Background(),
`INSERT INTO host_emails (host_id, email, source) VALUES (?, ?, ?)`,
hosts[0].ID, "a@b.c", "src1")
return err
})
s.DoJSON("POST", "/api/latest/fleet/hosts/search", searchHostsRequest{MatchQuery: "a@b.c"}, http.StatusOK, &searchResp)
require.Len(t, searchResp.Hosts, 1)
// search for non-existent email, shouldn't get anything back
s.DoJSON("POST", "/api/latest/fleet/hosts/search", searchHostsRequest{MatchQuery: "not@found.com"}, http.StatusOK, &searchResp)
require.Len(t, searchResp.Hosts, 0)
} }
func (s *integrationTestSuite) TestCountTargets() { func (s *integrationTestSuite) TestCountTargets() {