mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
17744 policies count backend (#18567)
#17744 This adds the `merged_inherited` query param to `/teams/:id/policies/count` related to https://github.com/fleetdm/fleet/pull/18564 - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/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 --------- Co-authored-by: RachelElysia <rachel@fleetdm.com>
This commit is contained in:
parent
37ac878ce7
commit
511f9bdbdb
7 changed files with 69 additions and 5 deletions
|
|
@ -451,6 +451,25 @@ func (ds *Datastore) CountPolicies(ctx context.Context, teamID *uint, matchQuery
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ds *Datastore) CountMergedTeamPolicies(ctx context.Context, teamID uint, matchQuery string) (int, error) {
|
||||||
|
var args []interface{}
|
||||||
|
|
||||||
|
query := `SELECT count(*) FROM policies p WHERE p.team_id = ? OR p.team_id IS NULL`
|
||||||
|
args = append(args, teamID)
|
||||||
|
|
||||||
|
// We must normalize the name for full Unicode support (Unicode equivalence).
|
||||||
|
match := norm.NFC.String(matchQuery)
|
||||||
|
query, args = searchLike(query, args, match, policySearchColumns...)
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err := sqlx.GetContext(ctx, ds.reader(ctx), &count, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, ctxerr.Wrap(ctx, err, "counting merged team policies")
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ds *Datastore) PoliciesByID(ctx context.Context, ids []uint) (map[uint]*fleet.Policy, error) {
|
func (ds *Datastore) PoliciesByID(ctx context.Context, ids []uint) (map[uint]*fleet.Policy, error) {
|
||||||
sql := `SELECT ` + policyCols + `,
|
sql := `SELECT ` + policyCols + `,
|
||||||
COALESCE(u.name, '<deleted>') AS author_name,
|
COALESCE(u.name, '<deleted>') AS author_name,
|
||||||
|
|
|
||||||
|
|
@ -2954,6 +2954,10 @@ func testCountPolicies(t *testing.T, ds *Datastore) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 0, teamCount)
|
assert.Equal(t, 0, teamCount)
|
||||||
|
|
||||||
|
mergedCount, err := ds.CountMergedTeamPolicies(ctx, tm.ID, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, mergedCount)
|
||||||
|
|
||||||
// 10 global policies
|
// 10 global policies
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
_, err := ds.NewGlobalPolicy(ctx, nil, fleet.PolicyPayload{Name: fmt.Sprintf("global policy %d", i)})
|
_, err := ds.NewGlobalPolicy(ctx, nil, fleet.PolicyPayload{Name: fmt.Sprintf("global policy %d", i)})
|
||||||
|
|
@ -2968,6 +2972,10 @@ func testCountPolicies(t *testing.T, ds *Datastore) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 0, teamCount)
|
assert.Equal(t, 0, teamCount)
|
||||||
|
|
||||||
|
mergedCount, err = ds.CountMergedTeamPolicies(ctx, tm.ID, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 10, mergedCount)
|
||||||
|
|
||||||
// add 5 team policies
|
// add 5 team policies
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
_, err := ds.NewTeamPolicy(ctx, tm.ID, nil, fleet.PolicyPayload{Name: fmt.Sprintf("team policy %d", i)})
|
_, err := ds.NewTeamPolicy(ctx, tm.ID, nil, fleet.PolicyPayload{Name: fmt.Sprintf("team policy %d", i)})
|
||||||
|
|
@ -2981,6 +2989,10 @@ func testCountPolicies(t *testing.T, ds *Datastore) {
|
||||||
globalCount, err = ds.CountPolicies(ctx, nil, "")
|
globalCount, err = ds.CountPolicies(ctx, nil, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 10, globalCount)
|
assert.Equal(t, 10, globalCount)
|
||||||
|
|
||||||
|
mergedCount, err = ds.CountMergedTeamPolicies(ctx, tm.ID, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 15, mergedCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testUpdatePolicyHostCounts(t *testing.T, ds *Datastore) {
|
func testUpdatePolicyHostCounts(t *testing.T, ds *Datastore) {
|
||||||
|
|
|
||||||
|
|
@ -612,6 +612,7 @@ type Datastore interface {
|
||||||
PoliciesByID(ctx context.Context, ids []uint) (map[uint]*Policy, error)
|
PoliciesByID(ctx context.Context, ids []uint) (map[uint]*Policy, error)
|
||||||
DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error)
|
DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error)
|
||||||
CountPolicies(ctx context.Context, teamID *uint, matchQuery string) (int, error)
|
CountPolicies(ctx context.Context, teamID *uint, matchQuery string) (int, error)
|
||||||
|
CountMergedTeamPolicies(ctx context.Context, teamID uint, matchQuery string) (int, error)
|
||||||
UpdateHostPolicyCounts(ctx context.Context) error
|
UpdateHostPolicyCounts(ctx context.Context) error
|
||||||
|
|
||||||
PolicyQueriesForHost(ctx context.Context, host *Host) (map[string]string, error)
|
PolicyQueriesForHost(ctx context.Context, host *Host) (map[string]string, error)
|
||||||
|
|
|
||||||
|
|
@ -646,7 +646,7 @@ type Service interface {
|
||||||
DeleteTeamPolicies(ctx context.Context, teamID uint, ids []uint) ([]uint, error)
|
DeleteTeamPolicies(ctx context.Context, teamID uint, ids []uint) ([]uint, error)
|
||||||
ModifyTeamPolicy(ctx context.Context, teamID uint, id uint, p ModifyPolicyPayload) (*Policy, error)
|
ModifyTeamPolicy(ctx context.Context, teamID uint, id uint, p ModifyPolicyPayload) (*Policy, error)
|
||||||
GetTeamPolicyByIDQueries(ctx context.Context, teamID uint, policyID uint) (*Policy, error)
|
GetTeamPolicyByIDQueries(ctx context.Context, teamID uint, policyID uint) (*Policy, error)
|
||||||
CountTeamPolicies(ctx context.Context, teamID uint, matchQuery string) (int, error)
|
CountTeamPolicies(ctx context.Context, teamID uint, matchQuery string, mergeInherited bool) (int, error)
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////////
|
||||||
// Geolocation
|
// Geolocation
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,8 @@ type DeleteGlobalPoliciesFunc func(ctx context.Context, ids []uint) ([]uint, err
|
||||||
|
|
||||||
type CountPoliciesFunc func(ctx context.Context, teamID *uint, matchQuery string) (int, error)
|
type CountPoliciesFunc func(ctx context.Context, teamID *uint, matchQuery string) (int, error)
|
||||||
|
|
||||||
|
type CountMergedTeamPoliciesFunc func(ctx context.Context, teamID uint, matchQuery string) (int, error)
|
||||||
|
|
||||||
type UpdateHostPolicyCountsFunc func(ctx context.Context) error
|
type UpdateHostPolicyCountsFunc func(ctx context.Context) error
|
||||||
|
|
||||||
type PolicyQueriesForHostFunc func(ctx context.Context, host *fleet.Host) (map[string]string, error)
|
type PolicyQueriesForHostFunc func(ctx context.Context, host *fleet.Host) (map[string]string, error)
|
||||||
|
|
@ -1566,6 +1568,9 @@ type DataStore struct {
|
||||||
CountPoliciesFunc CountPoliciesFunc
|
CountPoliciesFunc CountPoliciesFunc
|
||||||
CountPoliciesFuncInvoked bool
|
CountPoliciesFuncInvoked bool
|
||||||
|
|
||||||
|
CountMergedTeamPoliciesFunc CountMergedTeamPoliciesFunc
|
||||||
|
CountMergedTeamPoliciesFuncInvoked bool
|
||||||
|
|
||||||
UpdateHostPolicyCountsFunc UpdateHostPolicyCountsFunc
|
UpdateHostPolicyCountsFunc UpdateHostPolicyCountsFunc
|
||||||
UpdateHostPolicyCountsFuncInvoked bool
|
UpdateHostPolicyCountsFuncInvoked bool
|
||||||
|
|
||||||
|
|
@ -3781,6 +3786,13 @@ func (s *DataStore) CountPolicies(ctx context.Context, teamID *uint, matchQuery
|
||||||
return s.CountPoliciesFunc(ctx, teamID, matchQuery)
|
return s.CountPoliciesFunc(ctx, teamID, matchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DataStore) CountMergedTeamPolicies(ctx context.Context, teamID uint, matchQuery string) (int, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.CountMergedTeamPoliciesFuncInvoked = true
|
||||||
|
s.mu.Unlock()
|
||||||
|
return s.CountMergedTeamPoliciesFunc(ctx, teamID, matchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DataStore) UpdateHostPolicyCounts(ctx context.Context) error {
|
func (s *DataStore) UpdateHostPolicyCounts(ctx context.Context) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.UpdateHostPolicyCountsFuncInvoked = true
|
s.UpdateHostPolicyCountsFuncInvoked = true
|
||||||
|
|
|
||||||
|
|
@ -844,6 +844,16 @@ func (s *integrationEnterpriseTestSuite) TestTeamPolicies() {
|
||||||
assert.Equal(t, gpol.Name, ts.InheritedPolicies[0].Name)
|
assert.Equal(t, gpol.Name, ts.InheritedPolicies[0].Name)
|
||||||
assert.Equal(t, gpol.ID, ts.InheritedPolicies[0].ID)
|
assert.Equal(t, gpol.ID, ts.InheritedPolicies[0].ID)
|
||||||
|
|
||||||
|
tc := countTeamPoliciesResponse{}
|
||||||
|
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/count", team1.ID), nil, http.StatusOK, &tc)
|
||||||
|
require.Nil(t, tc.Err)
|
||||||
|
require.Equal(t, 1, tc.Count)
|
||||||
|
|
||||||
|
gc := countGlobalPoliciesResponse{}
|
||||||
|
s.DoJSON("GET", "/api/latest/fleet/policies/count", nil, http.StatusOK, &gc)
|
||||||
|
require.Nil(t, gc.Err)
|
||||||
|
require.Equal(t, 1, gc.Count)
|
||||||
|
|
||||||
// Test merge inherited
|
// Test merge inherited
|
||||||
ts = listTeamPoliciesResponse{}
|
ts = listTeamPoliciesResponse{}
|
||||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), nil, http.StatusOK, &ts, "merge_inherited", "true", "order_key", "team_id", "order_direction", "desc")
|
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), nil, http.StatusOK, &ts, "merge_inherited", "true", "order_key", "team_id", "order_direction", "desc")
|
||||||
|
|
@ -857,6 +867,11 @@ func (s *integrationEnterpriseTestSuite) TestTeamPolicies() {
|
||||||
assert.Equal(t, gpol.Name, ts.Policies[1].Name)
|
assert.Equal(t, gpol.Name, ts.Policies[1].Name)
|
||||||
assert.Equal(t, gpol.ID, ts.Policies[1].ID)
|
assert.Equal(t, gpol.ID, ts.Policies[1].ID)
|
||||||
|
|
||||||
|
countResp := countTeamPoliciesResponse{}
|
||||||
|
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/count", team1.ID), nil, http.StatusOK, &countResp, "merge_inherited", "true")
|
||||||
|
require.Nil(t, countResp.Err)
|
||||||
|
require.Equal(t, 2, countResp.Count)
|
||||||
|
|
||||||
// Test delete
|
// Test delete
|
||||||
deletePolicyParams := deleteTeamPoliciesRequest{IDs: []uint{ts.Policies[0].ID}}
|
deletePolicyParams := deleteTeamPoliciesRequest{IDs: []uint{ts.Policies[0].ID}}
|
||||||
deletePolicyResp := deleteTeamPoliciesResponse{}
|
deletePolicyResp := deleteTeamPoliciesResponse{}
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,9 @@ func (svc *Service) ListTeamPolicies(ctx context.Context, teamID uint, opts flee
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
type countTeamPoliciesRequest struct {
|
type countTeamPoliciesRequest struct {
|
||||||
ListOptions fleet.ListOptions `url:"list_options"`
|
ListOptions fleet.ListOptions `url:"list_options"`
|
||||||
TeamID uint `url:"team_id"`
|
TeamID uint `url:"team_id"`
|
||||||
|
MergeInherited bool `query:"merge_inherited,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type countTeamPoliciesResponse struct {
|
type countTeamPoliciesResponse struct {
|
||||||
|
|
@ -173,14 +174,14 @@ func (r countTeamPoliciesResponse) error() error { return r.Err }
|
||||||
|
|
||||||
func countTeamPoliciesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
func countTeamPoliciesEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||||
req := request.(*countTeamPoliciesRequest)
|
req := request.(*countTeamPoliciesRequest)
|
||||||
resp, err := svc.CountTeamPolicies(ctx, req.TeamID, req.ListOptions.MatchQuery)
|
resp, err := svc.CountTeamPolicies(ctx, req.TeamID, req.ListOptions.MatchQuery, req.MergeInherited)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return countTeamPoliciesResponse{Err: err}, nil
|
return countTeamPoliciesResponse{Err: err}, nil
|
||||||
}
|
}
|
||||||
return countTeamPoliciesResponse{Count: resp}, nil
|
return countTeamPoliciesResponse{Count: resp}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) CountTeamPolicies(ctx context.Context, teamID uint, matchQuery string) (int, error) {
|
func (svc *Service) CountTeamPolicies(ctx context.Context, teamID uint, matchQuery string, mergeInherited bool) (int, error) {
|
||||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{
|
if err := svc.authz.Authorize(ctx, &fleet.Policy{
|
||||||
PolicyData: fleet.PolicyData{
|
PolicyData: fleet.PolicyData{
|
||||||
TeamID: ptr.Uint(teamID),
|
TeamID: ptr.Uint(teamID),
|
||||||
|
|
@ -193,6 +194,10 @@ func (svc *Service) CountTeamPolicies(ctx context.Context, teamID uint, matchQue
|
||||||
return 0, ctxerr.Wrapf(ctx, err, "loading team %d", teamID)
|
return 0, ctxerr.Wrapf(ctx, err, "loading team %d", teamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mergeInherited {
|
||||||
|
return svc.ds.CountMergedTeamPolicies(ctx, teamID, matchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
return svc.ds.CountPolicies(ctx, &teamID, matchQuery)
|
return svc.ds.CountPolicies(ctx, &teamID, matchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue