From 2e67ef61d4a5b76df753ff0806e23bdb7df384eb Mon Sep 17 00:00:00 2001 From: Tim Lee Date: Thu, 7 Mar 2024 12:57:27 -0700 Subject: [PATCH] Bugfix: Revert host filters fix (#17390) (#17464) This reverts commit 4b2ebdc8dcdde8476b4d19c417ffa79f949ddd47. --- server/fleet/app.go | 2 +- server/fleet/hosts.go | 57 ++---- server/fleet/hosts_test.go | 40 ----- server/fleet/service.go | 2 +- server/service/client_hosts.go | 15 +- server/service/hosts.go | 120 ++++--------- server/service/hosts_test.go | 227 +----------------------- server/service/integration_core_test.go | 10 +- 8 files changed, 70 insertions(+), 403 deletions(-) diff --git a/server/fleet/app.go b/server/fleet/app.go index 44bf79727b..778f6fe7eb 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -970,7 +970,7 @@ type ListOptions struct { // MatchQuery is the query string to match against columns of the entity // (varies depending on entity, eg. hostname, IP address for hosts). // Handling for this parameter must be implemented separately for each type. - MatchQuery string `query:"query,optional" json:"query,omitempty"` + MatchQuery string `query:"query,optional"` // After denotes the row to start from. This is meant to be used in conjunction with OrderKey // If OrderKey is "id", it'll assume After is a number and will try to convert it. After string `query:"after,optional"` diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go index 3c9f34e71a..f9815f7e4a 100644 --- a/server/fleet/hosts.go +++ b/server/fleet/hosts.go @@ -39,15 +39,6 @@ const ( OnlineIntervalBuffer = 60 ) -func (s HostStatus) IsValid() bool { - switch s { - case StatusOnline, StatusOffline, StatusNew, StatusMissing, StatusMIA: - return true - default: - return false - } -} - // MDMEnrollStatus defines the possible MDM enrollment statuses. type MDMEnrollStatus string @@ -59,15 +50,6 @@ const ( MDMEnrollStatusEnrolled = MDMEnrollStatus("enrolled") // combination of "manual" and "automatic" ) -func (s MDMEnrollStatus) IsValid() bool { - switch s { - case MDMEnrollStatusManual, MDMEnrollStatusAutomatic, MDMEnrollStatusPending, MDMEnrollStatusUnenrolled, MDMEnrollStatusEnrolled: - return true - default: - return false - } -} - // OSSettingsStatus defines the possible statuses of the host's OS settings, which is derived from the // status of MDM configuration profiles and non-profile settings applied the host. type OSSettingsStatus string @@ -137,15 +119,12 @@ type HostListOptions struct { // populated. AdditionalFilters []string // StatusFilter selects the online status of the hosts. - StatusFilter HostStatus `json:"status"` + StatusFilter HostStatus // TeamFilter selects the hosts for specified team - TeamFilter *uint `json:"team_id"` + TeamFilter *uint - PolicyIDFilter *uint `json:"policy_id"` - PolicyResponseFilterRequest *string `json:"policy_response"` - PolicyResponseFilter *bool - - LabelID *uint `json:"label_id"` + PolicyIDFilter *uint + PolicyResponseFilter *bool // Deprecated: SoftwareIDFilter is deprecated as of Fleet 4.42. It is // maintained for backwards compatibility. Use SoftwareVersionIDFilter @@ -153,16 +132,16 @@ type HostListOptions struct { SoftwareIDFilter *uint // SoftwareVersionIDFilter filters the hosts by the software version ID that // they use. This identifies a specific version of a "software title". - SoftwareVersionIDFilter *uint `json:"software_version_id"` + SoftwareVersionIDFilter *uint // SoftwareTitleIDFilter filers the hosts by the software title ID that they // use. This identifies a "software title" independent of the specific // version. - SoftwareTitleIDFilter *uint `json:"software_title_id"` + SoftwareTitleIDFilter *uint OSIDFilter *uint - OSNameFilter *string `json:"os_name"` - OSVersionFilter *string `json:"os_version"` - OSVersionIDFilter *uint `json:"os_version_id"` + OSNameFilter *string + OSVersionFilter *string + OSVersionIDFilter *uint DisableFailingPolicies bool @@ -176,29 +155,29 @@ type HostListOptions struct { // OSSettingsFilter filters the hosts by the status of MDM configuration profiles and // non-profile settings applied to the hosts. - OSSettingsFilter OSSettingsStatus `json:"os_settings"` + OSSettingsFilter OSSettingsStatus // OSSettingsDiskEncryptionFilter filters the hosts by the status of the disk encryption // OS setting. - OSSettingsDiskEncryptionFilter DiskEncryptionStatus `json:"os_settings_disk_encryption"` + OSSettingsDiskEncryptionFilter DiskEncryptionStatus // MDMBootstrapPackageFilter filters the hosts by the status of the MDM bootstrap package. - MDMBootstrapPackageFilter *MDMBootstrapPackageStatus `json:"bootstrap_package"` + MDMBootstrapPackageFilter *MDMBootstrapPackageStatus // MDMIDFilter filters the hosts by MDM ID. - MDMIDFilter *uint `json:"mdm_id"` + MDMIDFilter *uint // MDMNameFilter filters the hosts by MDM solution name (e.g. one of the // fleet.WellKnownMDM... constants). - MDMNameFilter *string `json:"mdm_name"` + MDMNameFilter *string // MDMEnrollmentStatusFilter filters the host by their MDM enrollment status. - MDMEnrollmentStatusFilter MDMEnrollStatus `json:"mdm_enrollment_status"` + MDMEnrollmentStatusFilter MDMEnrollStatus // MunkiIssueIDFilter filters the hosts by munki issue ID. - MunkiIssueIDFilter *uint `json:"munki_issue_id"` + MunkiIssueIDFilter *uint // LowDiskSpaceFilter filters the hosts by low disk space (defined as a host // with less than N gigs of disk space available). Note that this is a Fleet // Premium feature, Fleet Free ignores the setting (it forces it to nil to // disable it). - LowDiskSpaceFilter *int `json:"low_disk_space"` + LowDiskSpaceFilter *int // PopulateSoftware adds the `Software` field to all Hosts returned. PopulateSoftware bool @@ -207,7 +186,7 @@ type HostListOptions struct { PopulatePolicies bool // VulnerabilityFilter filters the hosts by the presence of a vulnerability (CVE) - VulnerabilityFilter *string `json:"vulnerability"` + VulnerabilityFilter *string } // TODO(Sarah): Are we missing any filters here? Should all MDM filters be included? diff --git a/server/fleet/hosts_test.go b/server/fleet/hosts_test.go index 2703309b27..2bba30a7d7 100644 --- a/server/fleet/hosts_test.go +++ b/server/fleet/hosts_test.go @@ -52,46 +52,6 @@ func TestHostStatus(t *testing.T) { } } -func TestHostStatusIsValid(t *testing.T) { - for _, tt := range []struct { - name string - status HostStatus - expected bool - }{ - {"online", StatusOnline, true}, - {"offline", StatusOffline, true}, - {"new", StatusNew, true}, - {"missing", StatusMissing, true}, - {"mia", StatusMIA, true}, // As of Fleet 4.15, StatusMIA is deprecated in favor of StatusOffline - {"empty", HostStatus(""), false}, - {"invalid", HostStatus("invalid"), false}, - } { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.status.IsValid()) - }) - } -} - -func TestMDMEnrollStatusIsValid(t *testing.T) { - for _, tt := range []struct { - name string - status MDMEnrollStatus - expected bool - }{ - {"manual", MDMEnrollStatusManual, true}, - {"automatic", MDMEnrollStatusAutomatic, true}, - {"pending", MDMEnrollStatusPending, true}, - {"unenrolled", MDMEnrollStatusUnenrolled, true}, - {"enrolled", MDMEnrollStatusEnrolled, true}, - {"empty", MDMEnrollStatus(""), false}, - {"invalid", MDMEnrollStatus("invalid"), false}, - } { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, tt.status.IsValid()) - }) - } -} - func TestHostIsNew(t *testing.T) { mockClock := clock.NewMockClock() diff --git a/server/fleet/service.go b/server/fleet/service.go index 576e1af118..0088594bb9 100644 --- a/server/fleet/service.go +++ b/server/fleet/service.go @@ -350,7 +350,7 @@ type Service interface { AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []uint, skipBulkPending bool) error // AddHostsToTeamByFilter adds hosts to an existing team, clearing their team settings if teamID is nil. Hosts are // selected by the label and HostListOptions provided. - AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt *HostListOptions, lid *uint) error + AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt HostListOptions, lid *uint) error DeleteHosts(ctx context.Context, ids []uint, opt *HostListOptions, lid *uint) error CountHosts(ctx context.Context, labelID *uint, opts HostListOptions) (int, error) // SearchHosts performs a search on the hosts table using the following criteria: diff --git a/server/service/client_hosts.go b/server/service/client_hosts.go index 9db19879f5..029916a3cb 100644 --- a/server/service/client_hosts.go +++ b/server/service/client_hosts.go @@ -122,16 +122,13 @@ func (c *Client) TransferHosts(hosts []string, label string, status, searchQuery verb, path := "POST", "/api/latest/fleet/hosts/transfer/filter" var responseBody addHostsToTeamByFilterResponse params := addHostsToTeamByFilterRequest{ - TeamID: teamIDPtr, - Filters: &fleet.HostListOptions{ - ListOptions: fleet.ListOptions{ - MatchQuery: searchQuery, - }, - LabelID: labelIDPtr, - StatusFilter: fleet.HostStatus(status), - }, + TeamID: teamIDPtr, Filters: struct { + MatchQuery string `json:"query"` + Status fleet.HostStatus `json:"status"` + LabelID *uint `json:"label_id"` + TeamID *uint `json:"team_id"` + }{MatchQuery: searchQuery, Status: fleet.HostStatus(status), LabelID: labelIDPtr}, } - return c.authenticatedRequest(params, verb, path, &responseBody) } diff --git a/server/service/hosts.go b/server/service/hosts.go index 10642fdfeb..7d030d0d00 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -217,10 +217,17 @@ var ( deleteHostsSkipAuthorization = false ) +type deleteHostsFilters struct { + MatchQuery string `json:"query"` + Status fleet.HostStatus `json:"status"` + LabelID *uint `json:"label_id"` + TeamID *uint `json:"team_id"` +} + type deleteHostsRequest struct { IDs []uint `json:"ids"` // Using a pointer to help determine whether an empty filter was passed, like: "filters":{} - Filters *fleet.HostListOptions `json:"filters"` + Filters *deleteHostsFilters `json:"filters"` } type deleteHostsResponse struct { @@ -235,9 +242,16 @@ func (r deleteHostsResponse) Status() int { return r.StatusCode } func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*deleteHostsRequest) - + var listOpts *fleet.HostListOptions var labelID *uint if req.Filters != nil { + listOpts = &fleet.HostListOptions{ + ListOptions: fleet.ListOptions{ + MatchQuery: req.Filters.MatchQuery, + }, + StatusFilter: req.Filters.Status, + TeamFilter: req.Filters.TeamID, + } labelID = req.Filters.LabelID } @@ -247,7 +261,7 @@ func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Ser deleteDone := make(chan bool, 1) ctx = context.WithoutCancel(ctx) // to make sure DB operations don't get killed after we return a 202 go func() { - err = svc.DeleteHosts(ctx, req.IDs, req.Filters, labelID) + err = svc.DeleteHosts(ctx, req.IDs, listOpts, labelID) if err != nil { // logging the error for future debug in case we already sent http.StatusAccepted logging.WithErr(ctx, err) @@ -270,13 +284,7 @@ func deleteHostsEndpoint(ctx context.Context, request interface{}, svc fleet.Ser } func (svc *Service) DeleteHosts(ctx context.Context, ids []uint, opts *fleet.HostListOptions, lid *uint) error { - var err error - if err = svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil { - return err - } - - opts, err = validateAndPopulateHostListOptionsFilters(ctx, opts) - if err != nil { + if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil { return err } @@ -854,8 +862,13 @@ func (svc *Service) createTransferredHostsActivity(ctx context.Context, teamID * //////////////////////////////////////////////////////////////////////////////// type addHostsToTeamByFilterRequest struct { - TeamID *uint `json:"team_id"` - Filters *fleet.HostListOptions `json:"filters"` + TeamID *uint `json:"team_id"` + Filters struct { + MatchQuery string `json:"query"` + Status fleet.HostStatus `json:"status"` + LabelID *uint `json:"label_id"` + TeamID *uint `json:"team_id"` + } `json:"filters"` } type addHostsToTeamByFilterResponse struct { @@ -866,8 +879,14 @@ func (r addHostsToTeamByFilterResponse) error() error { return r.Err } func addHostsToTeamByFilterEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) { req := request.(*addHostsToTeamByFilterRequest) - - err := svc.AddHostsToTeamByFilter(ctx, req.TeamID, req.Filters, req.Filters.LabelID) + listOpt := fleet.HostListOptions{ + ListOptions: fleet.ListOptions{ + MatchQuery: req.Filters.MatchQuery, + }, + StatusFilter: req.Filters.Status, + TeamFilter: req.Filters.TeamID, + } + err := svc.AddHostsToTeamByFilter(ctx, req.TeamID, listOpt, req.Filters.LabelID) if err != nil { return addHostsToTeamByFilterResponse{Err: err}, nil } @@ -875,7 +894,7 @@ func addHostsToTeamByFilterEndpoint(ctx context.Context, request interface{}, sv return addHostsToTeamByFilterResponse{}, err } -func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt *fleet.HostListOptions, lid *uint) error { +func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt fleet.HostListOptions, lid *uint) error { // This is currently treated as a "team write". If we ever give users // besides global admins permissions to modify team hosts, we will need to // check that the user has permissions for both the source and destination @@ -884,16 +903,7 @@ func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, op return err } - if opt == nil { - return &fleet.BadRequestError{Message: "filters must be specified"} - } - - opt, err := validateAndPopulateHostListOptionsFilters(ctx, opt) - if err != nil { - return err - } - - hostIDs, hostNames, err := svc.hostIDsAndNamesFromFilters(ctx, *opt, lid) + hostIDs, hostNames, err := svc.hostIDsAndNamesFromFilters(ctx, opt, lid) if err != nil { return err } @@ -2150,63 +2160,3 @@ func (svc *Service) HostLiteByID(ctx context.Context, id uint) (*fleet.HostLite, return host, nil } - -func validateAndPopulateHostListOptionsFilters(ctx context.Context, opt *fleet.HostListOptions) (*fleet.HostListOptions, error) { - if opt == nil { - return nil, nil - } - - if opt.StatusFilter != "" && !opt.StatusFilter.IsValid() { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid status %s", opt.StatusFilter))) - } - - if opt.PolicyResponseFilterRequest != nil && opt.PolicyIDFilter == nil { - return opt, ctxerr.Wrap(ctx, badRequest("Policy ID must be provided when filtering by policy response")) - } - - if opt.PolicyResponseFilterRequest != nil { - if *opt.PolicyResponseFilterRequest == "passing" { - opt.PolicyResponseFilter = ptr.Bool(true) - } else if *opt.PolicyResponseFilterRequest == "failing" { - opt.PolicyResponseFilter = ptr.Bool(false) - } else { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid policy response filter %s", *opt.PolicyResponseFilterRequest))) - } - } - - if opt.SoftwareTitleIDFilter != nil && opt.SoftwareVersionIDFilter != nil { - return opt, ctxerr.Wrap(ctx, badRequest("Software title ID and name cannot be used together")) - } - - if opt.OSNameFilter != nil && opt.OSVersionFilter == nil { - return opt, ctxerr.Wrap(ctx, badRequest("OS version must be provided when filtering by OS name")) - } - - if opt.OSNameFilter == nil && opt.OSVersionFilter != nil { - return opt, ctxerr.Wrap(ctx, badRequest("OS name must be provided when filtering by OS version")) - } - - if opt.MDMEnrollmentStatusFilter != "" && !opt.MDMEnrollmentStatusFilter.IsValid() { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid MDM enrollment status %s", opt.MDMEnrollmentStatusFilter))) - } - - if opt.OSSettingsFilter != "" && !opt.OSSettingsFilter.IsValid() { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid OS settings status %s", opt.OSSettingsFilter))) - } - - if opt.OSSettingsDiskEncryptionFilter != "" && !opt.OSSettingsDiskEncryptionFilter.IsValid() { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid disk encryption status %s", opt.OSSettingsDiskEncryptionFilter))) - } - - if opt.MDMBootstrapPackageFilter != nil && !opt.MDMBootstrapPackageFilter.IsValid() { - return opt, ctxerr.Wrap(ctx, badRequest(fmt.Sprintf("Invalid MDM bootstrap status %s", *opt.MDMBootstrapPackageFilter))) - } - - if opt.LowDiskSpaceFilter != nil { - if *opt.LowDiskSpaceFilter > 100 || *opt.LowDiskSpaceFilter < 1 { - return opt, ctxerr.Wrap(ctx, badRequest("Low disk space filter must be between 1 and 100")) - } - } - - return opt, nil -} diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 2b57634b7a..1d3a5ac4c5 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -4,11 +4,9 @@ import ( "context" "crypto/x509" "encoding/base64" - "encoding/json" "errors" "fmt" "strconv" - "strings" "testing" "time" @@ -734,7 +732,7 @@ func TestHostAuth(t *testing.T) { err = svc.AddHostsToTeam(ctx, ptr.Uint(1), []uint{1}, false) checkAuthErr(t, tt.shouldFailTeamWrite, err) - err = svc.AddHostsToTeamByFilter(ctx, ptr.Uint(1), &fleet.HostListOptions{}, nil) + err = svc.AddHostsToTeamByFilter(ctx, ptr.Uint(1), fleet.HostListOptions{}, nil) checkAuthErr(t, tt.shouldFailTeamWrite, err) err = svc.RefetchHost(ctx, 1) @@ -857,7 +855,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) { return nil } - require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, &fleet.HostListOptions{}, nil)) + require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, fleet.HostListOptions{}, nil)) assert.True(t, ds.ListHostsFuncInvoked) assert.True(t, ds.AddHostsToTeamFuncInvoked) } @@ -895,7 +893,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) { return nil } - require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, &fleet.HostListOptions{}, expectedLabel)) + require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), expectedTeam, fleet.HostListOptions{}, expectedLabel)) assert.True(t, ds.ListHostsInLabelFuncInvoked) assert.True(t, ds.AddHostsToTeamFuncInvoked) } @@ -914,7 +912,7 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) { return nil } - require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), nil, &fleet.HostListOptions{}, nil)) + require.NoError(t, svc.AddHostsToTeamByFilter(test.UserContext(ctx, test.UserAdmin), nil, fleet.HostListOptions{}, nil)) assert.True(t, ds.ListHostsFuncInvoked) assert.False(t, ds.AddHostsToTeamFuncInvoked) } @@ -1630,220 +1628,3 @@ func TestLockUnlockWipeHostAuth(t *testing.T) { }) } } - -func TestValidateAndPopulateHostListOptionsFilters(t *testing.T) { - cases := []struct { - name string - jsonBody string - expected *fleet.HostListOptions - has400Error bool - }{ - { - name: "no filter", - jsonBody: `{ - "somevalue": "somevalue" - }`, - expected: nil, - }, - { - name: "empty filter", - jsonBody: `{ - "somevalue": "somevalue", - "filter": {} - }`, - expected: &fleet.HostListOptions{}, - }, - { - name: "all valid filters", - jsonBody: `{ - "somevalue": "somevalue", - "filter": { - "query": "foo", - "status": "new", - "team_id": 1, - "policy_id": 2, - "policy_response": "passing", - "os_name": "macOS", - "os_version": "11.1", - "os_version_id": 3, - "os_settings": "pending", - "os_settings_disk_encryption": "failed", - "bootstrap_package": "installed", - "munki_issue_id": 4, - "vulnerability": "CVE-2021-1234", - "mdm_id": 4, - "mdm_name": "mdm_name", - "mdm_enrollment_status": "automatic", - "low_disk_space": 99 - } - }`, - expected: &fleet.HostListOptions{ - ListOptions: fleet.ListOptions{ - MatchQuery: "foo", - }, - StatusFilter: fleet.StatusNew, - TeamFilter: ptr.Uint(1), - PolicyIDFilter: ptr.Uint(2), - PolicyResponseFilter: ptr.Bool(true), - PolicyResponseFilterRequest: ptr.String("passing"), - OSNameFilter: ptr.String("macOS"), - OSVersionFilter: ptr.String("11.1"), - OSVersionIDFilter: ptr.Uint(3), - OSSettingsFilter: fleet.OSSettingsPending, - OSSettingsDiskEncryptionFilter: fleet.DiskEncryptionFailed, - MDMBootstrapPackageFilter: (*fleet.MDMBootstrapPackageStatus)(ptr.String(string(fleet.MDMBootstrapPackageInstalled))), - MunkiIssueIDFilter: ptr.Uint(4), - VulnerabilityFilter: ptr.String("CVE-2021-1234"), - MDMIDFilter: ptr.Uint(4), - MDMNameFilter: ptr.String("mdm_name"), - MDMEnrollmentStatusFilter: fleet.MDMEnrollStatusAutomatic, - LowDiskSpaceFilter: ptr.Int(99), - }, - }, - - { - name: "filter with invalid status", - jsonBody: `{ - "filter": { - "status": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "policy ID must be provided with policy response", - jsonBody: `{ - "filter": { - "policy_response": "passing" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "invalid policy response", - jsonBody: `{ - "filter": { - "policy_id": 1, - "policy_response": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "software title and versionID cannot be used together", - jsonBody: `{ - "filter": { - "software_title_id": 2, - "software_version_id": 1 - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "os version must be provided with os name", - jsonBody: `{ - "filter": { - "os_version": "11.1" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "os name must be provided with os version", - jsonBody: `{ - "filter": { - "os_name": "macOS" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "invalid mdm enrollment status", - jsonBody: `{ - "filter": { - "mdm_enrollment_status": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "invalid os settings", - jsonBody: `{ - "filter": { - "os_settings": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "invalid os settings disk encryption", - jsonBody: `{ - "filter": { - "os_settings_disk_encryption": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "invalid mdm bootstrap package", - jsonBody: `{ - "filter": { - "bootstrap_package": "invalid" - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "low disk space is too low", - jsonBody: `{ - "filter": { - "low_disk_space": 0 - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - { - name: "low disk space is too high", - jsonBody: `{ - "filter": { - "low_disk_space": 101 - } - }`, - expected: &fleet.HostListOptions{}, - has400Error: true, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - type request struct { - Somevalue string `json:"somevalue"` - Filter *fleet.HostListOptions `json:"filter"` - } - var in request - err := json.NewDecoder(strings.NewReader(tt.jsonBody)).Decode(&in) - require.NoError(t, err) - - opts, err := validateAndPopulateHostListOptionsFilters(context.Background(), in.Filter) - if tt.has400Error { - require.Error(t, err) - var be *fleet.BadRequestError - require.ErrorAs(t, err, &be) - } else { - require.NoError(t, err) - require.Equal(t, tt.expected, opts) - } - }) - } -} diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index f048b65782..c1e86e410f 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -1000,7 +1000,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsFromTeam() { require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{hosts[0].ID})) req := deleteHostsRequest{ - Filters: &fleet.HostListOptions{TeamFilter: ptr.Uint(team1.ID)}, + Filters: &deleteHostsFilters{TeamID: ptr.Uint(team1.ID)}, } resp := deleteHostsResponse{} s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp) @@ -1037,7 +1037,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsInLabel() { require.NoError(t, s.ds.RecordLabelQueryExecutions(context.Background(), hosts[2], map[uint]*bool{label.ID: ptr.Bool(true)}, time.Now(), false)) req := deleteHostsRequest{ - Filters: &fleet.HostListOptions{LabelID: ptr.Uint(label.ID)}, + Filters: &deleteHostsFilters{LabelID: ptr.Uint(label.ID)}, } resp := deleteHostsResponse{} s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp) @@ -1120,7 +1120,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsAll() { // All hosts should be deleted when an empty filter is specified req := deleteHostsRequest{ - Filters: &fleet.HostListOptions{}, + Filters: &deleteHostsFilters{}, } resp := deleteHostsResponse{} s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusOK, &resp) @@ -1163,7 +1163,7 @@ func (s *integrationTestSuite) TestBulkDeleteHostsErrors() { req := deleteHostsRequest{ IDs: []uint{hosts[0].ID, hosts[1].ID}, - Filters: &fleet.HostListOptions{LabelID: ptr.Uint(1)}, + Filters: &deleteHostsFilters{LabelID: ptr.Uint(1)}, } resp := deleteHostsResponse{} s.DoJSON("POST", "/api/latest/fleet/hosts/delete", req, http.StatusBadRequest, &resp) @@ -2814,7 +2814,7 @@ func (s *integrationTestSuite) TestHostsAddToTeam() { // assign host to team 2 with filter var addfResp addHostsToTeamByFilterResponse - req := addHostsToTeamByFilterRequest{TeamID: &tm2.ID, Filters: &fleet.HostListOptions{}} + req := addHostsToTeamByFilterRequest{TeamID: &tm2.ID} req.Filters.MatchQuery = hosts[2].Hostname s.DoJSON("POST", "/api/latest/fleet/hosts/transfer/filter", req, http.StatusOK, &addfResp) s.lastActivityOfTypeMatches(