mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Filter query page API responses based on team membership (#850)
- Include only hosts that the user has access to in search targets API. - Add parameter to specify whether `observer` hosts should be included. - Generate counts based on which hosts user can access. - Update API doc.
This commit is contained in:
parent
e33391e8d3
commit
15b81824f5
@ -1361,11 +1361,11 @@ Returns a list of all enabled users
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| --------------------- | ------ | ---- | --------------------------------------------------------------- |
|
||||
| order_key | string | query | What to order results by. Can be any column in the users table. |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name` and `email`. |
|
||||
| Name | Type | In | Description |
|
||||
| --------------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| order_key | string | query | What to order results by. Can be any column in the users table. |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name` and `email`. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -1743,15 +1743,14 @@ Delete the specified user from Fleet.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ---------- | ------- | ---- | ------------------------------------------------ |
|
||||
| id | integer | path | **Required.** The user's id. |
|
||||
| Name | Type | In | Description |
|
||||
| ---- | ------- | ---- | ---------------------------- |
|
||||
| id | integer | path | **Required.** The user's id. |
|
||||
|
||||
#### Example
|
||||
|
||||
`DELETE /api/v1/fleet/users/3`
|
||||
|
||||
|
||||
##### Default response
|
||||
|
||||
`Status: 200`
|
||||
@ -3750,14 +3749,17 @@ In Fleet, targets are used to run queries against specific hosts or groups of ho
|
||||
|
||||
The search targets endpoint returns two lists. The first list includes the possible target hosts in Fleet given the search query provided and the hosts already selected as targets. The second list includes the possible target labels in Fleet given the search query provided and the labels already selected as targets.
|
||||
|
||||
The returned lists are filtered based on the hosts the requesting user has access to.
|
||||
|
||||
`POST /api/v1/fleet/targets`
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| -------- | ------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| query | string | body | The search query. Searchable items include a host's hostname or IPv4 address and labels. |
|
||||
| selected | object | body | The targets already selected. The object includes a `hosts` property which contains a list of host IDs and a `labels` property which contains a list of label IDs. |
|
||||
| Name | Type | In | Description |
|
||||
| ---------------- | ------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| query | string | body | The search query. Searchable items include a host's hostname or IPv4 address and labels. |
|
||||
| selected | object | body | The targets already selected. The object includes a `hosts` property which contains a list of host IDs and a `labels` property which contains a list of label IDs. |
|
||||
| include_observer | boolean | body | Whether to include hosts that the user only has `observer` permission on. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -3771,7 +3773,8 @@ The search targets endpoint returns two lists. The first list includes the possi
|
||||
"selected": {
|
||||
"hosts": [],
|
||||
"labels": [7]
|
||||
}
|
||||
},
|
||||
"include_observer": true
|
||||
}
|
||||
```
|
||||
|
||||
@ -4181,12 +4184,12 @@ Modifies and/or creates the specified enroll secret(s).
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ---------- | ------- | ---- | ------------------------------------------------ |
|
||||
| admin | boolean | body | **Required.** Whether or not the invited user will be granted admin privileges. |
|
||||
| email | string | body | **Required.** The email of the invited user. This email will receive the invitation link. |
|
||||
| name | string | body | **Required.** The name of the invited user. |
|
||||
| sso_enabled | boolean | body | **Required.** Whether or not SSO will be enabled for the invited user. |
|
||||
| Name | Type | In | Description |
|
||||
| ----------- | ------- | ---- | ----------------------------------------------------------------------------------------- |
|
||||
| admin | boolean | body | **Required.** Whether or not the invited user will be granted admin privileges. |
|
||||
| email | string | body | **Required.** The email of the invited user. This email will receive the invitation link. |
|
||||
| name | string | body | **Required.** The name of the invited user. |
|
||||
| sso_enabled | boolean | body | **Required.** Whether or not SSO will be enabled for the invited user. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -4230,11 +4233,11 @@ Returns a list of the active invitations in Fleet.
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| --------------------- | ------ | ---- | --------------------------------------------------------------- |
|
||||
| order_key | string | query | What to order results by. Can be any column in the invites table. |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name` and `email`. |
|
||||
| Name | Type | In | Description |
|
||||
| --------------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| order_key | string | query | What to order results by. Can be any column in the invites table. |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name` and `email`. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -495,31 +495,34 @@ func testSearchHosts(t *testing.T, ds kolide.Datastore) {
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
// We once threw errors when the search query was empty. Verify that we
|
||||
// don't error.
|
||||
_, err = ds.SearchHosts("")
|
||||
_, err = ds.SearchHosts(filter, "")
|
||||
require.Nil(t, err)
|
||||
|
||||
hosts, err := ds.SearchHosts("foo")
|
||||
hosts, err := ds.SearchHosts(filter, "foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, hosts, 2)
|
||||
|
||||
host, err := ds.SearchHosts("foo", h3.ID)
|
||||
host, err := ds.SearchHosts(filter, "foo", h3.ID)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, host, 1)
|
||||
assert.Equal(t, "foo.local", host[0].HostName)
|
||||
|
||||
host, err = ds.SearchHosts("foo", h3.ID, h2.ID)
|
||||
host, err = ds.SearchHosts(filter, "foo", h3.ID, h2.ID)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, host, 1)
|
||||
assert.Equal(t, "foo.local", host[0].HostName)
|
||||
|
||||
host, err = ds.SearchHosts("abc")
|
||||
host, err = ds.SearchHosts(filter, "abc")
|
||||
require.Nil(t, err)
|
||||
require.Len(t, host, 1)
|
||||
assert.Equal(t, "abc-def-ghi", host[0].UUID)
|
||||
|
||||
none, err := ds.SearchHosts("xxx")
|
||||
none, err := ds.SearchHosts(filter, "xxx")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, none, 0)
|
||||
|
||||
@ -528,26 +531,29 @@ func testSearchHosts(t *testing.T, ds kolide.Datastore) {
|
||||
err = ds.SaveHost(h2)
|
||||
require.Nil(t, err)
|
||||
|
||||
hits, err := ds.SearchHosts("99.100.101")
|
||||
hits, err := ds.SearchHosts(filter, "99.100.101")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 1, len(hits))
|
||||
|
||||
hits, err = ds.SearchHosts("99.100.111")
|
||||
hits, err = ds.SearchHosts(filter, "99.100.111")
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 0, len(hits))
|
||||
|
||||
h3.PrimaryIP = "99.100.101.104"
|
||||
err = ds.SaveHost(h3)
|
||||
require.Nil(t, err)
|
||||
hits, err = ds.SearchHosts("99.100.101")
|
||||
hits, err = ds.SearchHosts(filter, "99.100.101")
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 2, len(hits))
|
||||
hits, err = ds.SearchHosts("99.100.101", h3.ID)
|
||||
hits, err = ds.SearchHosts(filter, "99.100.101", h3.ID)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(hits))
|
||||
}
|
||||
|
||||
func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) {
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
_, err := ds.NewHost(&kolide.Host{
|
||||
DetailUpdateTime: time.Now(),
|
||||
@ -561,7 +567,7 @@ func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) {
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
hosts, err := ds.SearchHosts("foo")
|
||||
hosts, err := ds.SearchHosts(filter, "foo")
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, hosts, 10)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/fleetdm/fleet/server/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
func testLabels(t *testing.T, db kolide.Datastore) {
|
||||
@ -239,23 +240,26 @@ func testSearchLabels(t *testing.T, db kolide.Datastore) {
|
||||
l3, err := db.Label(specs[2].ID)
|
||||
require.Nil(t, err)
|
||||
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
// We once threw errors when the search query was empty. Verify that we
|
||||
// don't error.
|
||||
labels, err := db.SearchLabels("")
|
||||
labels, err := db.SearchLabels(filter, "")
|
||||
require.Nil(t, err)
|
||||
assert.Contains(t, labels, *all)
|
||||
|
||||
labels, err = db.SearchLabels("foo")
|
||||
labels, err = db.SearchLabels(filter, "foo")
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, labels, 3)
|
||||
assert.Contains(t, labels, *all)
|
||||
|
||||
labels, err = db.SearchLabels("foo", all.ID, l3.ID)
|
||||
labels, err = db.SearchLabels(filter, "foo", all.ID, l3.ID)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, labels, 1)
|
||||
assert.Equal(t, "foo", labels[0].Name)
|
||||
|
||||
labels, err = db.SearchLabels("xxx")
|
||||
labels, err = db.SearchLabels(filter, "xxx")
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, labels, 1)
|
||||
assert.Contains(t, labels, *all)
|
||||
@ -281,7 +285,10 @@ func testSearchLabelsLimit(t *testing.T, db kolide.Datastore) {
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
labels, err := db.SearchLabels("foo")
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
labels, err := db.SearchLabels(filter, "foo")
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, labels, 11)
|
||||
}
|
||||
@ -350,7 +357,10 @@ func testListHostsInLabel(t *testing.T, db kolide.Datastore) {
|
||||
func testBuiltInLabels(t *testing.T, db kolide.Datastore) {
|
||||
require.Nil(t, db.MigrateData())
|
||||
|
||||
hits, err := db.SearchLabels("macOS")
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
hits, err := db.SearchLabels(filter, "macOS")
|
||||
require.Nil(t, err)
|
||||
// Should get Mac OS X and All Hosts
|
||||
assert.Equal(t, 2, len(hits))
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/fleetdm/fleet/server/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
func testCountHostsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
@ -17,6 +18,9 @@ func testCountHostsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
t.Skip("inmem is being deprecated, test skipped")
|
||||
}
|
||||
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
mockClock := clock.NewMockClock()
|
||||
|
||||
hostCount := 0
|
||||
@ -67,42 +71,42 @@ func testCountHostsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
metrics, err := ds.CountHostsInTargets(nil, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
metrics, err := ds.CountHostsInTargets(filter, nil, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(2), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(3), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.MissingInActionHosts)
|
||||
|
||||
metrics, err = ds.CountHostsInTargets([]uint{h1.ID, h2.ID}, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, []uint{h1.ID, h2.ID}, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(2), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(3), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.MissingInActionHosts)
|
||||
|
||||
metrics, err = ds.CountHostsInTargets([]uint{h1.ID, h2.ID}, nil, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, []uint{h1.ID, h2.ID}, nil, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(2), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(1), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
metrics, err = ds.CountHostsInTargets([]uint{h1.ID}, []uint{l2.ID}, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, []uint{h1.ID}, []uint{l2.ID}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(4), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(3), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
metrics, err = ds.CountHostsInTargets(nil, nil, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, nil, nil, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(0), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(0), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(0), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
metrics, err = ds.CountHostsInTargets([]uint{}, []uint{}, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, []uint{}, []uint{}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(0), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(0), metrics.OnlineHosts)
|
||||
@ -111,7 +115,7 @@ func testCountHostsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
|
||||
// Advance clock so all hosts are offline
|
||||
mockClock.AddTime(2 * time.Minute)
|
||||
metrics, err = ds.CountHostsInTargets(nil, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
metrics, err = ds.CountHostsInTargets(filter, nil, []uint{l1.ID, l2.ID}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(0), metrics.OnlineHosts)
|
||||
@ -132,6 +136,9 @@ func testHostStatus(t *testing.T, ds kolide.Datastore) {
|
||||
h, err := ds.EnrollHost("1", "key1", "default", 0)
|
||||
require.Nil(t, err)
|
||||
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
// Make host no longer appear new
|
||||
mockClock.AddTime(36 * time.Hour)
|
||||
|
||||
@ -174,7 +181,7 @@ func testHostStatus(t *testing.T, ds kolide.Datastore) {
|
||||
require.Nil(t, ds.MarkHostSeen(h, tt.seenTime))
|
||||
|
||||
// Verify status
|
||||
metrics, err := ds.CountHostsInTargets([]uint{h.ID}, []uint{}, mockClock.Now())
|
||||
metrics, err := ds.CountHostsInTargets(filter, []uint{h.ID}, []uint{}, mockClock.Now())
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tt.metrics, metrics)
|
||||
})
|
||||
@ -186,6 +193,9 @@ func testHostIDsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
t.Skip("inmem is being deprecated, test skipped")
|
||||
}
|
||||
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
filter := kolide.TeamFilter{User: user}
|
||||
|
||||
hostCount := 0
|
||||
initHost := func() *kolide.Host {
|
||||
hostCount += 1
|
||||
@ -230,31 +240,31 @@ func testHostIDsInTargets(t *testing.T, ds kolide.Datastore) {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
ids, err := ds.HostIDsInTargets(nil, []uint{l1.ID, l2.ID})
|
||||
ids, err := ds.HostIDsInTargets(filter, nil, []uint{l1.ID, l2.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{1, 2, 3, 4, 5, 6}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{h1.ID}, nil)
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{h1.ID}, nil)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{1}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{h1.ID}, []uint{l1.ID})
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{h1.ID}, []uint{l1.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{1, 2, 3, 6}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{4}, []uint{l1.ID})
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{4}, []uint{l1.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{1, 2, 3, 4, 6}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{4}, []uint{l2.ID})
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{4}, []uint{l2.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{3, 4, 5}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{}, []uint{l2.ID})
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{}, []uint{l2.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{3, 4, 5}, ids)
|
||||
|
||||
ids, err = ds.HostIDsInTargets([]uint{1, 6}, []uint{l2.ID})
|
||||
ids, err = ds.HostIDsInTargets(filter, []uint{1, 6}, []uint{l2.ID})
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []uint{1, 3, 4, 5, 6}, ids)
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ func (d *Datastore) MarkHostSeen(host *kolide.Host, t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) SearchHosts(query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) SearchHosts(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
omitLookup := map[uint]bool{}
|
||||
for _, o := range omit {
|
||||
omitLookup[o] = true
|
||||
|
@ -151,7 +151,7 @@ func (d *Datastore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error)
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) SearchLabels(query string, omit ...uint) ([]kolide.Label, error) {
|
||||
func (d *Datastore) SearchLabels(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
omitLookup := map[uint]bool{}
|
||||
for _, o := range omit {
|
||||
omitLookup[o] = true
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
)
|
||||
|
||||
func (d *Datastore) CountHostsInTargets(hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
func (d *Datastore) CountHostsInTargets(filter kolide.TeamFilter, hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
// noop
|
||||
return kolide.TargetMetrics{}, nil
|
||||
}
|
||||
|
@ -602,24 +602,24 @@ func (d *Datastore) MarkHostsSeen(hostIDs []uint, t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) searchHostsWithOmits(query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) searchHostsWithOmits(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
hostQuery := transformQuery(query)
|
||||
ipQuery := `"` + query + `"`
|
||||
|
||||
sqlStatement :=
|
||||
`
|
||||
SELECT DISTINCT *
|
||||
FROM hosts
|
||||
WHERE
|
||||
(
|
||||
MATCH (host_name, uuid) AGAINST (? IN BOOLEAN MODE)
|
||||
OR MATCH (primary_ip, primary_mac) AGAINST (? IN BOOLEAN MODE)
|
||||
)
|
||||
AND id NOT IN (?)
|
||||
LIMIT 10
|
||||
`
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT DISTINCT *
|
||||
FROM hosts
|
||||
WHERE
|
||||
(
|
||||
MATCH (host_name, uuid) AGAINST (? IN BOOLEAN MODE)
|
||||
OR MATCH (primary_ip, primary_mac) AGAINST (? IN BOOLEAN MODE)
|
||||
)
|
||||
AND id NOT IN (?) AND %s
|
||||
LIMIT 10
|
||||
`, d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
sql, args, err := sqlx.In(sqlStatement, hostQuery, ipQuery, omit)
|
||||
sql, args, err := sqlx.In(sql, hostQuery, ipQuery, omit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "searching hosts")
|
||||
}
|
||||
@ -635,13 +635,14 @@ func (d *Datastore) searchHostsWithOmits(query string, omit ...uint) ([]*kolide.
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) searchHostsDefault(omit ...uint) ([]*kolide.Host, error) {
|
||||
sqlStatement := `
|
||||
SELECT * FROM hosts
|
||||
WHERE id NOT in (?)
|
||||
ORDER BY seen_time DESC
|
||||
LIMIT 5
|
||||
`
|
||||
func (d *Datastore) searchHostsDefault(filter kolide.TeamFilter, omit ...uint) ([]*kolide.Host, error) {
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT * FROM hosts
|
||||
WHERE id NOT in (?) AND %s
|
||||
ORDER BY seen_time DESC
|
||||
LIMIT 5
|
||||
`, d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
var in interface{}
|
||||
{
|
||||
@ -654,7 +655,7 @@ func (d *Datastore) searchHostsDefault(omit ...uint) ([]*kolide.Host, error) {
|
||||
}
|
||||
|
||||
var hosts []*kolide.Host
|
||||
sql, args, err := sqlx.In(sqlStatement, in)
|
||||
sql, args, err := sqlx.In(sql, in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "searching default hosts")
|
||||
}
|
||||
@ -668,32 +669,32 @@ func (d *Datastore) searchHostsDefault(omit ...uint) ([]*kolide.Host, error) {
|
||||
|
||||
// SearchHosts find hosts by query containing an IP address, a host name or UUID.
|
||||
// Optionally pass a list of IDs to omit from the search
|
||||
func (d *Datastore) SearchHosts(query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
func (d *Datastore) SearchHosts(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
hostQuery := transformQuery(query)
|
||||
if !queryMinLength(hostQuery) {
|
||||
return d.searchHostsDefault(omit...)
|
||||
return d.searchHostsDefault(filter, omit...)
|
||||
}
|
||||
if len(omit) > 0 {
|
||||
return d.searchHostsWithOmits(query, omit...)
|
||||
return d.searchHostsWithOmits(filter, query, omit...)
|
||||
}
|
||||
|
||||
// Needs quotes to avoid each . marking a word boundary
|
||||
ipQuery := `"` + query + `"`
|
||||
|
||||
sqlStatement :=
|
||||
`
|
||||
SELECT DISTINCT *
|
||||
FROM hosts
|
||||
WHERE
|
||||
(
|
||||
MATCH (host_name, uuid) AGAINST (? IN BOOLEAN MODE)
|
||||
OR MATCH (primary_ip, primary_mac) AGAINST (? IN BOOLEAN MODE)
|
||||
)
|
||||
LIMIT 10
|
||||
`
|
||||
hosts := []*kolide.Host{}
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT DISTINCT *
|
||||
FROM hosts
|
||||
WHERE
|
||||
(
|
||||
MATCH (host_name, uuid) AGAINST (? IN BOOLEAN MODE)
|
||||
OR MATCH (primary_ip, primary_mac) AGAINST (? IN BOOLEAN MODE)
|
||||
) AND %s
|
||||
LIMIT 10
|
||||
`, d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
if err := d.db.Select(&hosts, sqlStatement, hostQuery, ipQuery); err != nil {
|
||||
hosts := []*kolide.Host{}
|
||||
if err := d.db.Select(&hosts, sql, hostQuery, ipQuery); err != nil {
|
||||
return nil, errors.Wrap(err, "searching hosts")
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -437,19 +438,24 @@ func (d *Datastore) ListUniqueHostsInLabels(labels []uint) ([]kolide.Host, error
|
||||
|
||||
}
|
||||
|
||||
func (d *Datastore) searchLabelsWithOmits(query string, omit ...uint) ([]kolide.Label, error) {
|
||||
func (d *Datastore) searchLabelsWithOmits(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
transformedQuery := transformQuery(query)
|
||||
|
||||
sqlStatement := `
|
||||
SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count
|
||||
FROM labels
|
||||
WHERE (
|
||||
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
||||
)
|
||||
AND id NOT IN (?)
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 10
|
||||
`
|
||||
sqlStatement := fmt.Sprintf(`
|
||||
SELECT *,
|
||||
(SELECT COUNT(1)
|
||||
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
||||
WHERE label_id = l.id AND %s
|
||||
) AS host_count
|
||||
FROM labels l
|
||||
WHERE (
|
||||
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
||||
)
|
||||
AND id NOT IN (?)
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 10
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
sql, args, err := sqlx.In(sqlStatement, transformedQuery, omit)
|
||||
if err != nil {
|
||||
@ -464,7 +470,7 @@ func (d *Datastore) searchLabelsWithOmits(query string, omit ...uint) ([]kolide.
|
||||
return nil, errors.Wrap(err, "selecting labels with omits")
|
||||
}
|
||||
|
||||
matches, err = d.addAllHostsLabelToList(matches, omit...)
|
||||
matches, err = d.addAllHostsLabelToList(filter, matches, omit...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "adding all hosts label to matches")
|
||||
}
|
||||
@ -475,20 +481,24 @@ func (d *Datastore) searchLabelsWithOmits(query string, omit ...uint) ([]kolide.
|
||||
// When we search labels, we always want to make sure that the All Hosts label
|
||||
// is included in the results set. Sometimes it already is and we don't need to
|
||||
// add it, sometimes it's not so we explicitly add it.
|
||||
func (d *Datastore) addAllHostsLabelToList(labels []kolide.Label, omit ...uint) ([]kolide.Label, error) {
|
||||
sqlStatement := `
|
||||
SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count
|
||||
FROM labels
|
||||
WHERE
|
||||
label_type=?
|
||||
AND name = 'All Hosts'
|
||||
LIMIT 1
|
||||
`
|
||||
func (d *Datastore) addAllHostsLabelToList(filter kolide.TeamFilter, labels []kolide.Label, omit ...uint) ([]kolide.Label, error) {
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT *,
|
||||
(SELECT COUNT(1)
|
||||
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
||||
WHERE label_id = l.id AND %s
|
||||
) AS host_count
|
||||
FROM labels l
|
||||
WHERE
|
||||
label_type=?
|
||||
AND name = 'All Hosts'
|
||||
LIMIT 1
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
var allHosts kolide.Label
|
||||
err := d.db.Get(&allHosts, sqlStatement, kolide.LabelTypeBuiltIn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting all hosts label")
|
||||
if err := d.db.Get(&allHosts, sql, kolide.LabelTypeBuiltIn); err != nil {
|
||||
return nil, errors.Wrap(err, "get all hosts label")
|
||||
}
|
||||
|
||||
for _, omission := range omit {
|
||||
@ -506,15 +516,20 @@ func (d *Datastore) addAllHostsLabelToList(labels []kolide.Label, omit ...uint)
|
||||
return append(labels, allHosts), nil
|
||||
}
|
||||
|
||||
func (d *Datastore) searchLabelsDefault(omit ...uint) ([]kolide.Label, error) {
|
||||
sqlStatement := `
|
||||
SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count
|
||||
FROM labels
|
||||
WHERE id NOT IN (?)
|
||||
GROUP BY id
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 7
|
||||
`
|
||||
func (d *Datastore) searchLabelsDefault(filter kolide.TeamFilter, omit ...uint) ([]kolide.Label, error) {
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT *,
|
||||
(SELECT COUNT(1)
|
||||
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
||||
WHERE label_id = l.id AND %s
|
||||
) AS host_count
|
||||
FROM labels l
|
||||
WHERE id NOT IN (?)
|
||||
GROUP BY id
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 7
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
var in interface{}
|
||||
{
|
||||
@ -527,17 +542,16 @@ func (d *Datastore) searchLabelsDefault(omit ...uint) ([]kolide.Label, error) {
|
||||
}
|
||||
|
||||
var labels []kolide.Label
|
||||
sql, args, err := sqlx.In(sqlStatement, in)
|
||||
sql, args, err := sqlx.In(sql, in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "searching default labels")
|
||||
}
|
||||
sql = d.db.Rebind(sql)
|
||||
err = d.db.Select(&labels, sql, args...)
|
||||
if err != nil {
|
||||
if err := d.db.Select(&labels, sql, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "searching default labels rebound")
|
||||
}
|
||||
|
||||
labels, err = d.addAllHostsLabelToList(labels, omit...)
|
||||
labels, err = d.addAllHostsLabelToList(filter, labels, omit...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting all host label")
|
||||
}
|
||||
@ -546,35 +560,40 @@ func (d *Datastore) searchLabelsDefault(omit ...uint) ([]kolide.Label, error) {
|
||||
}
|
||||
|
||||
// SearchLabels performs wildcard searches on kolide.Label name
|
||||
func (d *Datastore) SearchLabels(query string, omit ...uint) ([]kolide.Label, error) {
|
||||
func (d *Datastore) SearchLabels(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
transformedQuery := transformQuery(query)
|
||||
if !queryMinLength(transformedQuery) {
|
||||
return d.searchLabelsDefault(omit...)
|
||||
return d.searchLabelsDefault(filter, omit...)
|
||||
}
|
||||
if len(omit) > 0 {
|
||||
return d.searchLabelsWithOmits(query, omit...)
|
||||
return d.searchLabelsWithOmits(filter, query, omit...)
|
||||
}
|
||||
|
||||
// Ordering first by label_type ensures that built-in labels come
|
||||
// first. We will probably need to make a custom ordering function here
|
||||
// if additional label types are added. Ordering next by ID ensures
|
||||
// that the order is always consistent.
|
||||
sqlStatement := `
|
||||
SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count
|
||||
FROM labels
|
||||
WHERE (
|
||||
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
||||
)
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 10
|
||||
`
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT *,
|
||||
(SELECT COUNT(1)
|
||||
FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id)
|
||||
WHERE label_id = l.id AND %s
|
||||
) AS host_count
|
||||
FROM labels l
|
||||
WHERE (
|
||||
MATCH(name) AGAINST(? IN BOOLEAN MODE)
|
||||
)
|
||||
ORDER BY label_type DESC, id ASC
|
||||
LIMIT 10
|
||||
`, d.whereFilterHostsByTeams(filter, "h"),
|
||||
)
|
||||
|
||||
matches := []kolide.Label{}
|
||||
err := d.db.Select(&matches, sqlStatement, transformedQuery)
|
||||
if err != nil {
|
||||
if err := d.db.Select(&matches, sql, transformedQuery); err != nil {
|
||||
return nil, errors.Wrap(err, "selecting labels for search")
|
||||
}
|
||||
|
||||
matches, err = d.addAllHostsLabelToList(matches, omit...)
|
||||
matches, err := d.addAllHostsLabelToList(filter, matches, omit...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "adding all hosts label to matches")
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -20,6 +21,7 @@ import (
|
||||
"github.com/fleetdm/fleet/server/datastore/mysql/migrations/tables"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
@ -337,6 +339,53 @@ func appendListOptionsToSQL(sql string, opts kolide.ListOptions) string {
|
||||
return sql
|
||||
}
|
||||
|
||||
// whereFilterHostsByTeams returns the appropriate condition to use in the WHERE
|
||||
// clause to render only the appropriate teams.
|
||||
//
|
||||
// filter provides the filtering parameters that should be used. hostKey is the
|
||||
// name/alias of the hosts table to use in generating the SQL.
|
||||
func (d *Datastore) whereFilterHostsByTeams(filter kolide.TeamFilter, hostKey string) string {
|
||||
if filter.User == nil {
|
||||
// This is likely unintentional, however we would like to return no
|
||||
// results rather than panicking or returning some other error. At least
|
||||
// log.
|
||||
level.Info(d.logger).Log("err", "team filter missing user")
|
||||
return "FALSE"
|
||||
}
|
||||
|
||||
switch filter.User.GlobalRole.String {
|
||||
|
||||
case kolide.RoleAdmin, kolide.RoleMaintainer:
|
||||
return "TRUE"
|
||||
|
||||
case kolide.RoleObserver:
|
||||
if filter.IncludeObserver {
|
||||
return "TRUE"
|
||||
} else {
|
||||
return "FALSE"
|
||||
}
|
||||
|
||||
default:
|
||||
// Fall through to specific teams
|
||||
}
|
||||
|
||||
// Collect matching teams
|
||||
var idStrs []string
|
||||
for _, team := range filter.User.Teams {
|
||||
if team.Role == kolide.RoleAdmin || team.Role == kolide.RoleMaintainer ||
|
||||
(team.Role == kolide.RoleObserver && filter.IncludeObserver) {
|
||||
idStrs = append(idStrs, strconv.Itoa(int(team.ID)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(idStrs) == 0 {
|
||||
// User has no global role and no teams allowed by includeObserver.
|
||||
return "FALSE"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s.team_id IN (%s)", hostKey, strings.Join(idStrs, ","))
|
||||
}
|
||||
|
||||
// registerTLS adds client certificate configuration to the mysql connection.
|
||||
func registerTLS(config config.MysqlConfig) error {
|
||||
rootCertPool := x509.NewCertPool()
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
func TestSanitizeColumn(t *testing.T) {
|
||||
@ -282,3 +283,146 @@ func TestAppendListOptionsToSQL(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWhereFilterHostsByTeams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
filter kolide.TeamFilter
|
||||
expected string
|
||||
}{
|
||||
// No teams or global role
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{},
|
||||
},
|
||||
expected: "FALSE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{Teams: []kolide.UserTeam{}},
|
||||
},
|
||||
expected: "FALSE",
|
||||
},
|
||||
|
||||
// Global role
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)},
|
||||
},
|
||||
expected: "TRUE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{GlobalRole: null.StringFrom(kolide.RoleMaintainer)},
|
||||
},
|
||||
expected: "TRUE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{GlobalRole: null.StringFrom(kolide.RoleObserver)},
|
||||
},
|
||||
expected: "FALSE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{GlobalRole: null.StringFrom(kolide.RoleObserver)},
|
||||
IncludeObserver: true,
|
||||
},
|
||||
expected: "TRUE",
|
||||
},
|
||||
|
||||
// Team roles
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "FALSE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
},
|
||||
},
|
||||
IncludeObserver: true,
|
||||
},
|
||||
expected: "hosts.team_id IN (1)",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "FALSE",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
{Role: kolide.RoleMaintainer, Team: kolide.Team{ID: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "hosts.team_id IN (2)",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
{Role: kolide.RoleMaintainer, Team: kolide.Team{ID: 2}},
|
||||
},
|
||||
},
|
||||
IncludeObserver: true,
|
||||
},
|
||||
expected: "hosts.team_id IN (1,2)",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
{Role: kolide.RoleMaintainer, Team: kolide.Team{ID: 2}},
|
||||
// Invalid role should be ignored
|
||||
{Role: "bad", Team: kolide.Team{ID: 37}},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "hosts.team_id IN (2)",
|
||||
},
|
||||
{
|
||||
filter: kolide.TeamFilter{
|
||||
User: &kolide.User{
|
||||
Teams: []kolide.UserTeam{
|
||||
{Role: kolide.RoleObserver, Team: kolide.Team{ID: 1}},
|
||||
{Role: kolide.RoleMaintainer, Team: kolide.Team{ID: 2}},
|
||||
{Role: kolide.RoleAdmin, Team: kolide.Team{ID: 3}},
|
||||
// Invalid role should be ignored
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "hosts.team_id IN (2,3)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ds := &Datastore{logger: log.NewNopLogger()}
|
||||
sql := ds.whereFilterHostsByTeams(tt.filter, "hosts")
|
||||
assert.Equal(t, tt.expected, sql)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (d *Datastore) CountHostsInTargets(hostIDs []uint, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
func (d *Datastore) CountHostsInTargets(filter kolide.TeamFilter, hostIDs []uint, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
// The logic in this function should remain synchronized with
|
||||
// host.Status and GenerateHostStatusStatistics
|
||||
|
||||
@ -26,8 +26,8 @@ func (d *Datastore) CountHostsInTargets(hostIDs []uint, labelIDs []uint, now tim
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) > ? THEN 1 ELSE 0 END), 0) online,
|
||||
COALESCE(SUM(CASE WHEN DATE_ADD(created_at, INTERVAL 1 DAY) >= ? THEN 1 ELSE 0 END), 0) new
|
||||
FROM hosts h
|
||||
WHERE (id IN (?) OR (id IN (SELECT DISTINCT host_id FROM label_membership WHERE label_id IN (?))))
|
||||
`, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer)
|
||||
WHERE (id IN (?) OR (id IN (SELECT DISTINCT host_id FROM label_membership WHERE label_id IN (?)))) AND %s
|
||||
`, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer, d.whereFilterHostsByTeams(filter, "h"))
|
||||
|
||||
// Using -1 in the ID slices for the IN clause allows us to include the
|
||||
// IN clause even if we have no IDs to use. -1 will not match the
|
||||
@ -56,18 +56,20 @@ func (d *Datastore) CountHostsInTargets(hostIDs []uint, labelIDs []uint, now tim
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) HostIDsInTargets(hostIDs []uint, labelIDs []uint) ([]uint, error) {
|
||||
func (d *Datastore) HostIDsInTargets(filter kolide.TeamFilter, hostIDs, labelIDs []uint) ([]uint, error) {
|
||||
if len(hostIDs) == 0 && len(labelIDs) == 0 {
|
||||
// No need to query if no targets selected
|
||||
return []uint{}, nil
|
||||
}
|
||||
|
||||
sql := `
|
||||
SELECT DISTINCT id
|
||||
FROM hosts
|
||||
WHERE (id IN (?) OR (id IN (SELECT host_id FROM label_membership WHERE label_id IN (?))))
|
||||
ORDER BY id ASC
|
||||
`
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT DISTINCT id
|
||||
FROM hosts
|
||||
WHERE (id IN (?) OR (id IN (SELECT host_id FROM label_membership WHERE label_id IN (?)))) AND %s
|
||||
ORDER BY id ASC
|
||||
`,
|
||||
d.whereFilterHostsByTeams(filter, "hosts"),
|
||||
)
|
||||
|
||||
// Using -1 in the ID slices for the IN clause allows us to include the
|
||||
// IN clause even if we have no IDs to use. -1 will not match the
|
||||
|
@ -57,7 +57,7 @@ type HostStore interface {
|
||||
AuthenticateHost(nodeKey string) (*Host, error)
|
||||
MarkHostSeen(host *Host, t time.Time) error
|
||||
MarkHostsSeen(hostIDs []uint, t time.Time) error
|
||||
SearchHosts(query string, omit ...uint) ([]*Host, error)
|
||||
SearchHosts(filter TeamFilter, query string, omit ...uint) ([]*Host, error)
|
||||
// CleanupIncomingHosts deletes hosts that have enrolled but never
|
||||
// updated their status details. This clears dead "incoming hosts" that
|
||||
// never complete their registration.
|
||||
|
@ -48,7 +48,7 @@ type LabelStore interface {
|
||||
// it is in multiple of the provided labels.
|
||||
ListUniqueHostsInLabels(labels []uint) ([]Host, error)
|
||||
|
||||
SearchLabels(query string, omit ...uint) ([]Label, error)
|
||||
SearchLabels(filter TeamFilter, query string, omit ...uint) ([]Label, error)
|
||||
|
||||
// LabelIDsByName Retrieve the IDs associated with the given labels
|
||||
LabelIDsByName(labels []string) ([]uint, error)
|
||||
|
@ -35,21 +35,21 @@ type TargetService interface {
|
||||
// SearchTargets will accept a search query, a slice of IDs of hosts to omit,
|
||||
// and a slice of IDs of labels to omit, and it will return a set of targets
|
||||
// (hosts and label) which match the supplied search query.
|
||||
SearchTargets(ctx context.Context, query string, selectedHostIDs []uint, selectedLabelIDs []uint) (*TargetSearchResults, error)
|
||||
SearchTargets(ctx context.Context, query string, selectedHostIDs []uint, selectedLabelIDs []uint, includeObserver bool) (*TargetSearchResults, error)
|
||||
|
||||
// CountHostsInTargets returns the metrics of the hosts in the provided
|
||||
// label and explicit host IDs.
|
||||
CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (*TargetMetrics, error)
|
||||
CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint, includeObserver bool) (*TargetMetrics, error)
|
||||
}
|
||||
|
||||
type TargetStore interface {
|
||||
// CountHostsInTargets returns the metrics of the hosts in the provided
|
||||
// label and explicit host IDs.
|
||||
CountHostsInTargets(hostIDs, labelIDs []uint, now time.Time) (TargetMetrics, error)
|
||||
CountHostsInTargets(filter TeamFilter, hostIDs, labelIDs []uint, now time.Time) (TargetMetrics, error)
|
||||
// HostIDsInTargets returns the host IDs of the hosts in the provided label
|
||||
// and explicit host IDs. The returned host IDs should be sorted in
|
||||
// ascending order.
|
||||
HostIDsInTargets(hostIDs, labelIDs []uint) ([]uint, error)
|
||||
HostIDsInTargets(filter TeamFilter, hostIDs, labelIDs []uint) ([]uint, error)
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
@ -6,6 +6,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RoleAdmin = "admin"
|
||||
RoleMaintainer = "maintainer"
|
||||
RoleObserver = "observer"
|
||||
)
|
||||
|
||||
type TeamStore interface {
|
||||
// NewTeam creates a new Team object in the store.
|
||||
NewTeam(team *Team) (*Team, error)
|
||||
@ -85,8 +91,8 @@ type TeamUser struct {
|
||||
}
|
||||
|
||||
var teamRoles = map[string]bool{
|
||||
"observer": true,
|
||||
"maintainer": true,
|
||||
RoleObserver: true,
|
||||
RoleMaintainer: true,
|
||||
}
|
||||
|
||||
// ValidTeamRole returns whether the role provided is valid for a team user.
|
||||
@ -97,16 +103,16 @@ func ValidTeamRole(role string) bool {
|
||||
// ValidTeamRoles returns the list of valid roles for a team user.
|
||||
func ValidTeamRoles() []string {
|
||||
var roles []string
|
||||
for role, _ := range teamRoles {
|
||||
for role := range teamRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
var globalRoles = map[string]bool{
|
||||
"observer": true,
|
||||
"maintainer": true,
|
||||
"admin": true,
|
||||
RoleObserver: true,
|
||||
RoleMaintainer: true,
|
||||
RoleAdmin: true,
|
||||
}
|
||||
|
||||
// ValidGlobalRole returns whether the role provided is valid for a global user.
|
||||
@ -117,8 +123,17 @@ func ValidGlobalRole(role string) bool {
|
||||
// ValidGlobalRoles returns the list of valid roles for a global user.
|
||||
func ValidGlobalRoles() []string {
|
||||
var roles []string
|
||||
for role, _ := range globalRoles {
|
||||
for role := range globalRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
// TeamFilter is the filtering information passed to the datastore for queries
|
||||
// that may be filtered by team.
|
||||
type TeamFilter struct {
|
||||
// User is the user to filter by.
|
||||
User *User
|
||||
// IncludeObserver determines whether to include teams the user is an observer on.
|
||||
IncludeObserver bool
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ type MarkHostsSeenFunc func(hostIDs []uint, t time.Time) error
|
||||
|
||||
type CleanupIncomingHostsFunc func(t time.Time) error
|
||||
|
||||
type SearchHostsFunc func(query string, omit ...uint) ([]*kolide.Host, error)
|
||||
type SearchHostsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error)
|
||||
|
||||
type GenerateHostStatusStatisticsFunc func(now time.Time) (online uint, offline uint, mia uint, new uint, err error)
|
||||
|
||||
@ -147,9 +147,9 @@ func (s *HostStore) CleanupIncomingHosts(t time.Time) error {
|
||||
return s.CleanupIncomingHostsFunc(t)
|
||||
}
|
||||
|
||||
func (s *HostStore) SearchHosts(query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
func (s *HostStore) SearchHosts(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
s.SearchHostsFuncInvoked = true
|
||||
return s.SearchHostsFunc(query, omit...)
|
||||
return s.SearchHostsFunc(filter, query, omit...)
|
||||
}
|
||||
|
||||
func (s *HostStore) GenerateHostStatusStatistics(now time.Time) (online uint, offline uint, mia uint, new uint, err error) {
|
||||
|
@ -36,7 +36,7 @@ type ListHostsInLabelFunc func(lid uint, opt kolide.HostListOptions) ([]kolide.H
|
||||
|
||||
type ListUniqueHostsInLabelsFunc func(labels []uint) ([]kolide.Host, error)
|
||||
|
||||
type SearchLabelsFunc func(query string, omit ...uint) ([]kolide.Label, error)
|
||||
type SearchLabelsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error)
|
||||
|
||||
type LabelIDsByNameFunc func(labels []string) ([]uint, error)
|
||||
|
||||
@ -152,9 +152,9 @@ func (s *LabelStore) ListUniqueHostsInLabels(labels []uint) ([]kolide.Host, erro
|
||||
return s.ListUniqueHostsInLabelsFunc(labels)
|
||||
}
|
||||
|
||||
func (s *LabelStore) SearchLabels(query string, omit ...uint) ([]kolide.Label, error) {
|
||||
func (s *LabelStore) SearchLabels(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
s.SearchLabelsFuncInvoked = true
|
||||
return s.SearchLabelsFunc(query, omit...)
|
||||
return s.SearchLabelsFunc(filter, query, omit...)
|
||||
}
|
||||
|
||||
func (s *LabelStore) LabelIDsByName(labels []string) ([]uint, error) {
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
|
||||
var _ kolide.TargetStore = (*TargetStore)(nil)
|
||||
|
||||
type CountHostsInTargetsFunc func(hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error)
|
||||
type HostIDsInTargetsFunc func(hostIDs, labelIDs []uint) ([]uint, error)
|
||||
type CountHostsInTargetsFunc func(filter kolide.TeamFilter, hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error)
|
||||
type HostIDsInTargetsFunc func(filter kolide.TeamFilter, hostIDs, labelIDs []uint) ([]uint, error)
|
||||
|
||||
type TargetStore struct {
|
||||
CountHostsInTargetsFunc CountHostsInTargetsFunc
|
||||
@ -20,12 +20,12 @@ type TargetStore struct {
|
||||
HostIDsInTargetsFuncInvoked bool
|
||||
}
|
||||
|
||||
func (s *TargetStore) CountHostsInTargets(hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
func (s *TargetStore) CountHostsInTargets(filter kolide.TeamFilter, hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
s.CountHostsInTargetsFuncInvoked = true
|
||||
return s.CountHostsInTargetsFunc(hostIDs, labelIDs, now)
|
||||
return s.CountHostsInTargetsFunc(filter, hostIDs, labelIDs, now)
|
||||
}
|
||||
|
||||
func (s *TargetStore) HostIDsInTargets(hostIDs, labelIDs []uint) ([]uint, error) {
|
||||
func (s *TargetStore) HostIDsInTargets(filter kolide.TeamFilter, hostIDs, labelIDs []uint) ([]uint, error) {
|
||||
s.HostIDsInTargetsFuncInvoked = true
|
||||
return s.HostIDsInTargetsFunc(hostIDs, labelIDs)
|
||||
return s.HostIDsInTargetsFunc(filter, hostIDs, labelIDs)
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func packResponseForPack(ctx context.Context, svc kolide.Service, pack kolide.Pa
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostMetrics, err := svc.CountHostsInTargets(ctx, hosts, labelIDs)
|
||||
hostMetrics, err := svc.CountHostsInTargets(ctx, hosts, labelIDs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ type searchTargetsRequest struct {
|
||||
Labels []uint `json:"labels"`
|
||||
Hosts []uint `json:"hosts"`
|
||||
} `json:"selected"`
|
||||
// IncludeObserver determines whether targets for which the user is only an
|
||||
// observer should be included.
|
||||
IncludeObserver bool `json:"include_observer"`
|
||||
}
|
||||
|
||||
type hostSearchResult struct {
|
||||
@ -51,7 +54,7 @@ func makeSearchTargetsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(searchTargetsRequest)
|
||||
|
||||
results, err := svc.SearchTargets(ctx, req.Query, req.Selected.Hosts, req.Selected.Labels)
|
||||
results, err := svc.SearchTargets(ctx, req.Query, req.Selected.Hosts, req.Selected.Labels, req.IncludeObserver)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
@ -83,7 +86,7 @@ func makeSearchTargetsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
)
|
||||
}
|
||||
|
||||
metrics, err := svc.CountHostsInTargets(ctx, req.Selected.Hosts, req.Selected.Labels)
|
||||
metrics, err := svc.CountHostsInTargets(ctx, req.Selected.Hosts, req.Selected.Labels, req.IncludeObserver)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
@ -88,7 +88,9 @@ func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
||||
}
|
||||
}
|
||||
|
||||
hostIDs, err := svc.ds.HostIDsInTargets(hosts, labels)
|
||||
filter := kolide.TeamFilter{User: vc.User}
|
||||
|
||||
hostIDs, err := svc.ds.HostIDsInTargets(filter, hosts, labels)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get target IDs")
|
||||
}
|
||||
@ -98,7 +100,7 @@ func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
||||
return nil, errors.Wrap(err, "run query")
|
||||
}
|
||||
|
||||
campaign.Metrics, err = svc.ds.CountHostsInTargets(hosts, labels, time.Now())
|
||||
campaign.Metrics, err = svc.ds.CountHostsInTargets(filter, hosts, labels, time.Now())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "counting hosts")
|
||||
}
|
||||
@ -184,7 +186,8 @@ func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Co
|
||||
}
|
||||
|
||||
updateStatus := func() error {
|
||||
metrics, err := svc.CountHostsInTargets(context.Background(), hostIDs, labelIDs)
|
||||
// TODO use appropriate includeObserver value
|
||||
metrics, err := svc.CountHostsInTargets(context.Background(), hostIDs, labelIDs, false)
|
||||
if err != nil {
|
||||
if err = conn.WriteJSONError("error retrieving target counts"); err != nil {
|
||||
return errors.New("retrieve target counts")
|
||||
|
@ -1158,10 +1158,10 @@ func TestNewDistributedQueryCampaign(t *testing.T) {
|
||||
return target, nil
|
||||
}
|
||||
|
||||
ds.CountHostsInTargetsFunc = func(hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
ds.CountHostsInTargetsFunc = func(filter kolide.TeamFilter, hostIDs, labelIDs []uint, now time.Time) (kolide.TargetMetrics, error) {
|
||||
return kolide.TargetMetrics{}, nil
|
||||
}
|
||||
ds.HostIDsInTargetsFunc = func(hostIDs, labelIDs []uint) ([]uint, error) {
|
||||
ds.HostIDsInTargetsFunc = func(filter kolide.TeamFilter, hostIDs, labelIDs []uint) ([]uint, error) {
|
||||
return []uint{1, 3, 5}, nil
|
||||
}
|
||||
lq.On("RunQuery", "21", "select year, month, day, hour, minutes, seconds from time", []uint{1, 3, 5}).Return(nil)
|
||||
|
@ -3,13 +3,21 @@ package service
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
)
|
||||
|
||||
func (svc service) SearchTargets(ctx context.Context, query string, selectedHostIDs []uint, selectedLabelIDs []uint) (*kolide.TargetSearchResults, error) {
|
||||
func (svc service) SearchTargets(ctx context.Context, query string, selectedHostIDs []uint, selectedLabelIDs []uint, includeObserver bool) (*kolide.TargetSearchResults, error) {
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errNoContext
|
||||
}
|
||||
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
results := &kolide.TargetSearchResults{}
|
||||
|
||||
hosts, err := svc.ds.SearchHosts(query, selectedHostIDs...)
|
||||
hosts, err := svc.ds.SearchHosts(filter, query, selectedHostIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -18,7 +26,7 @@ func (svc service) SearchTargets(ctx context.Context, query string, selectedHost
|
||||
results.Hosts = append(results.Hosts, *h)
|
||||
}
|
||||
|
||||
labels, err := svc.ds.SearchLabels(query, selectedLabelIDs...)
|
||||
labels, err := svc.ds.SearchLabels(filter, query, selectedLabelIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -27,8 +35,15 @@ func (svc service) SearchTargets(ctx context.Context, query string, selectedHost
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (svc service) CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (*kolide.TargetMetrics, error) {
|
||||
metrics, err := svc.ds.CountHostsInTargets(hostIDs, labelIDs, svc.clock.Now())
|
||||
func (svc service) CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint, includeObserver bool) (*kolide.TargetMetrics, error) {
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errNoContext
|
||||
}
|
||||
|
||||
filter := kolide.TeamFilter{User: vc.User, IncludeObserver: includeObserver}
|
||||
|
||||
metrics, err := svc.ds.CountHostsInTargets(filter, hostIDs, labelIDs, svc.clock.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,165 +2,68 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/server/config"
|
||||
"github.com/fleetdm/fleet/server/datastore/inmem"
|
||||
"github.com/fleetdm/fleet/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/fleetdm/fleet/server/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
func TestSearchTargets(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
require.Nil(t, err)
|
||||
|
||||
ds := new(mock.Store)
|
||||
svc, err := newTestService(ds, nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: user})
|
||||
|
||||
h1, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "foo.local",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
hosts := []*kolide.Host{
|
||||
{HostName: "foo.local"},
|
||||
}
|
||||
labels := []kolide.Label{
|
||||
{
|
||||
Name: "label foo",
|
||||
Query: "query foo",
|
||||
},
|
||||
}
|
||||
|
||||
l1, err := ds.NewLabel(&kolide.Label{
|
||||
Name: "label foo",
|
||||
Query: "query foo",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
ds.SearchHostsFunc = func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
assert.Equal(t, user, filter.User)
|
||||
return hosts, nil
|
||||
}
|
||||
ds.SearchLabelsFunc = func(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
assert.Equal(t, user, filter.User)
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
results, err := svc.SearchTargets(ctx, "foo", nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Len(t, results.Hosts, 1)
|
||||
assert.Equal(t, h1.HostName, results.Hosts[0].HostName)
|
||||
|
||||
require.Len(t, results.Labels, 1)
|
||||
assert.Equal(t, l1.Name, results.Labels[0].Name)
|
||||
results, err := svc.SearchTargets(ctx, "foo", nil, nil, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, *hosts[0], results.Hosts[0])
|
||||
assert.Equal(t, labels[0], results.Labels[0])
|
||||
}
|
||||
|
||||
func TestSearchWithOmit(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
require.Nil(t, err)
|
||||
|
||||
ds := new(mock.Store)
|
||||
svc, err := newTestService(ds, nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
user := &kolide.User{GlobalRole: null.StringFrom(kolide.RoleAdmin)}
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: user})
|
||||
|
||||
h1, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "foo.local",
|
||||
NodeKey: "1",
|
||||
UUID: "1",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
h2, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "foobar.local",
|
||||
NodeKey: "2",
|
||||
UUID: "2",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
l1, err := ds.NewLabel(&kolide.Label{
|
||||
Name: "label foo",
|
||||
Query: "query foo",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
{
|
||||
results, err := svc.SearchTargets(ctx, "foo", nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Len(t, results.Hosts, 2)
|
||||
|
||||
require.Len(t, results.Labels, 1)
|
||||
assert.Equal(t, l1.Name, results.Labels[0].Name)
|
||||
ds.SearchHostsFunc = func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) {
|
||||
assert.Equal(t, user, filter.User)
|
||||
assert.Equal(t, []uint{1, 2}, omit)
|
||||
return nil, nil
|
||||
}
|
||||
ds.SearchLabelsFunc = func(filter kolide.TeamFilter, query string, omit ...uint) ([]kolide.Label, error) {
|
||||
assert.Equal(t, user, filter.User)
|
||||
assert.Equal(t, []uint{3, 4}, omit)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
{
|
||||
results, err := svc.SearchTargets(ctx, "foo", []uint{h2.ID}, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Len(t, results.Hosts, 1)
|
||||
assert.Equal(t, h1.HostName, results.Hosts[0].HostName)
|
||||
|
||||
require.Len(t, results.Labels, 1)
|
||||
assert.Equal(t, l1.Name, results.Labels[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchHostsInLabels(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
require.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds, nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
h1, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "foo.local",
|
||||
NodeKey: "1",
|
||||
UUID: "1",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
h2, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "bar.local",
|
||||
NodeKey: "2",
|
||||
UUID: "2",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
h3, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "baz.local",
|
||||
NodeKey: "3",
|
||||
UUID: "3",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
l1, err := ds.NewLabel(&kolide.Label{
|
||||
Name: "label foo",
|
||||
Query: "query foo",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.NotZero(t, l1.ID)
|
||||
|
||||
for _, h := range []*kolide.Host{h1, h2, h3} {
|
||||
err = ds.RecordLabelQueryExecutions(h, map[uint]bool{l1.ID: true}, time.Now())
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
results, err := svc.SearchTargets(ctx, "baz", nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Len(t, results.Hosts, 1)
|
||||
assert.Equal(t, h3.HostName, results.Hosts[0].HostName)
|
||||
}
|
||||
|
||||
func TestSearchResultsLimit(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
require.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds, nil, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for i := 0; i < 15; i++ {
|
||||
_, err := ds.NewHost(&kolide.Host{
|
||||
HostName: fmt.Sprintf("foo.%d.local", i),
|
||||
NodeKey: fmt.Sprintf("%d", i+1),
|
||||
UUID: fmt.Sprintf("%d", i+1),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}
|
||||
targets, err := svc.SearchTargets(ctx, "foo", nil, nil)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, targets.Hosts, 10)
|
||||
_, err = svc.SearchTargets(ctx, "foo", []uint{1, 2}, []uint{3, 4}, false)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
@ -27,6 +27,24 @@ func ElementsMatchSkipID(t assert.TestingT, listA, listB interface{}, msgAndArgs
|
||||
return ElementsMatchWithOptions(t, listA, listB, []cmp.Option{opt}, msgAndArgs)
|
||||
}
|
||||
|
||||
// ElementsMatchSkipTimestampsID asserts that the elements match, skipping any field with
|
||||
// name "ID", "CreatedAt", and "UpdatedAt". This is useful for comparing after DB insertion.
|
||||
func ElementsMatchSkipTimestampsID(t assert.TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||
opt := cmp.FilterPath(func(p cmp.Path) bool {
|
||||
for _, ps := range p {
|
||||
switch ps := ps.(type) {
|
||||
case cmp.StructField:
|
||||
switch ps.Name() {
|
||||
case "ID", "UpdateCreateTimestamps", "CreateTimestamp", "UpdateTimestamp", "CreatedAt", "UpdatedAt":
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore())
|
||||
return ElementsMatchWithOptions(t, listA, listB, []cmp.Option{opt}, msgAndArgs)
|
||||
}
|
||||
|
||||
// The below functions adapted from
|
||||
// https://github.com/stretchr/testify/blob/v1.7.0/assert/assertions.go#L895 by
|
||||
// utilizing the options provided in github.com/google/go-cmp/cmp
|
||||
|
0
tools/api/fleet/teams/create
Normal file → Executable file
0
tools/api/fleet/teams/create
Normal file → Executable file
Loading…
Reference in New Issue
Block a user