fix usage of query params in host software endpoint (#42302)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #41631 

# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually
This commit is contained in:
Jahziel Villasana-Espinoza 2026-03-24 17:53:19 -04:00 committed by GitHub
parent 8b3674bc55
commit 2e6ffa747d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 84 additions and 52 deletions

View file

@ -0,0 +1,2 @@
- Fixed issue where the `include_available_for_install` query param wasn't being applied correctly
to the `GET /api/latest/fleet/hosts/{id}/software` endpoint.

View file

@ -3735,19 +3735,15 @@ func getHostSoftwareEndpoint(ctx context.Context, request interface{}, svc fleet
}
func (svc *Service) ListHostSoftware(ctx context.Context, hostID uint, opts fleet.HostSoftwareTitleListOptions) ([]*fleet.HostSoftwareWithInstaller, *fleet.PaginationMetadata, error) {
// When accessed via "My device", we default to only showing inventory (excluding software available for install
// but not in inventory), unless we're asked to filter to self-service software only.
//
// Otherwise (e.g. host software UI within Fleet's admin interface), the default is to show both installed and
// available-for-install software, to maintain existing API behavior. This behavior can be explicitly overridden
// if needed (see opts.IncludeAvailableForInstallExplicitlySet).
// Default to only showing inventory (excluding software available for install but not in
// inventory). Callers can explicitly set include_available_for_install=true to also see
// library items. See #41631.
var includeAvailableForInstall bool
var host *fleet.Host
if !svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnDeviceToken) &&
!svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnDeviceCertificate) &&
!svc.authz.IsAuthenticatedWith(ctx, authzctx.AuthnDeviceURL) {
includeAvailableForInstall = true
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return nil, nil, err

View file

@ -11989,9 +11989,17 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftware() {
return err
})
// available installer is returned by user-authenticated endpoint
// default (no include_available_for_install param) should only return installed software, not library items (#41631)
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 2) // foo and bar only — ruby is not installed, just available
require.Equal(t, getHostSw.Software[0].Name, "bar")
require.Equal(t, getHostSw.Software[1].Name, "foo")
require.Len(t, getHostSw.Software[1].InstalledVersions, 2)
// user authenticated endpoint, explicitly request to include available for install software
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 3) // foo, bar and ruby.deb
require.Equal(t, getHostSw.Software[0].Name, "bar")
require.Equal(t, getHostSw.Software[1].Name, "foo")
@ -12005,7 +12013,7 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftware() {
require.True(t, *getHostSw.Software[2].SoftwarePackage.SelfService)
require.Nil(t, getHostSw.Software[2].Status)
// user authenticated endpoint, but explicitly request to not include available for install software
// user authenticated endpoint, explicitly request to not include available for install software
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=false", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 2) // foo and bar
@ -12110,9 +12118,16 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftware() {
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install",
host.ID, titleID), nil, http.StatusAccepted, &installResp)
// still returned by user-authenticated endpoint, now pending
// default (no param) should still only return installed software, even after install is requested (#41631)
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 2) // foo and bar — ruby has a pending install but is not yet installed
require.Equal(t, getHostSw.Software[0].Name, "bar")
require.Equal(t, getHostSw.Software[1].Name, "foo")
// with include_available_for_install=true, the pending install is visible
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 3) // foo, bar and ruby.deb
require.Equal(t, getHostSw.Software[0].Name, "bar")
require.Equal(t, getHostSw.Software[1].Name, "foo")
@ -14173,7 +14188,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
// Get the install response, should be pending
getHostSoftwareResp := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Equal(t, fleet.SoftwareInstallPending, *getHostSoftwareResp.Software[0].Status)
// Switch self-service flag
@ -14214,12 +14229,12 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
// install should no longer be pending
afterPreinstallHostResp := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &afterPreinstallHostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &afterPreinstallHostResp, "include_available_for_install", "true")
require.Nil(t, afterPreinstallHostResp.Software[0].Status)
// install software fully
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h.ID, titlesResp.SoftwareTitles[0].ID), nil, http.StatusAccepted, &installResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
installUUID := getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(fmt.Sprintf(`{
"orbit_node_key": %q,
@ -14237,7 +14252,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
// install should show as complete
hostResp := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp, "include_available_for_install", "true")
require.Equal(t, fleet.SoftwareInstalled, *hostResp.Software[0].Status)
// update install script
@ -14261,7 +14276,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
// install should still show as complete
hostResp = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp, "include_available_for_install", "true")
require.Equal(t, fleet.SoftwareInstalled, *hostResp.Software[0].Status)
trailer = " " // add a character to the response for the installer HTTP call to ensure the file hashes differently
@ -14283,7 +14298,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
// install should be nulled out
hostResp = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &hostResp, "include_available_for_install", "true")
require.Nil(t, hostResp.Software[0].Status)
// install details record should still show as installed
@ -14296,7 +14311,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h.ID, titlesResp.SoftwareTitles[0].ID), nil, http.StatusAccepted, &pendingResp)
// install should show as pending
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &afterPreinstallHostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &afterPreinstallHostResp, "include_available_for_install", "true")
require.Equal(t, fleet.SoftwareInstallPending, *afterPreinstallHostResp.Software[0].Status)
installUUID = afterPreinstallHostResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
@ -14305,7 +14320,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallersSideEffec
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/uninstall", h2.ID, titlesResp.SoftwareTitles[0].ID), nil, http.StatusAccepted, &uninstallResp)
// uninstall should show as pending
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &afterPreinstallHostResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &afterPreinstallHostResp, "include_available_for_install", "true")
require.Equal(t, fleet.SoftwareUninstallPending, *afterPreinstallHostResp.Software[0].Status)
// delete all installers
@ -14689,7 +14704,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
// Get the results, should be pending
getHostSoftwareResp := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
@ -14724,7 +14739,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
require.NoError(t, s.ds.UpdateHostRefetchRequested(context.Background(), h4.ID, false))
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h2.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
installUUID2 := getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(fmt.Sprintf(`{
@ -14735,13 +14750,24 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
"install_script_output": "ok"
}`, *h2.OrbitNodeKey, installUUID2)), http.StatusNoContent)
software := []fleet.Software{
{Name: payload.Title, Version: payload.Version, Source: "deb_packages"},
}
_, err = s.ds.UpdateHostSoftware(context.Background(), h2.ID, software)
require.NoError(t, err)
// Note: no need to use the "include_available_for_install" query param
// since we simulated ingesting the software above
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp)
require.Len(t, getHostSoftwareResp.Software, 1)
// Verify refetch requested is set after successful install
var hostResp getHostResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", h2.ID), nil, http.StatusOK, &hostResp)
require.True(t, hostResp.Host.RefetchRequested, "RefetchRequested should be true after successful software install")
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h3.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h3.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h3.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
installUUID3 := getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(fmt.Sprintf(`{
@ -14761,7 +14787,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
// Server-side retries queue up to MaxSoftwareInstallAttempts attempts.
for attempt := 2; attempt <= fleet.MaxSoftwareInstallAttempts; attempt++ {
getHostSoftwareResp = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h3.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h3.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
@ -14778,7 +14804,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h4.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
installUUID4a := getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
s.Do("POST", "/api/fleet/orbit/software_install/result", json.RawMessage(fmt.Sprintf(`{
@ -14795,7 +14821,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
// Exhaust automatic retries for h4 so it reaches terminal "failed" state.
for attempt := 2; attempt <= fleet.MaxSoftwareInstallAttempts; attempt++ {
getHostSoftwareResp = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
@ -14810,7 +14836,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", h4.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h4.ID), nil, http.StatusOK, &getHostSoftwareResp, "include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
installUUID4b := getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall.InstallUUID
_ = installUUID4b
@ -14941,7 +14967,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
}
distributedResp := submitDistributedQueryResultsResponse{}
s.DoJSON("POST", "/api/osquery/distributed/write", distributedReq, http.StatusOK, &distributedResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
assert.NotNil(t, getHostSoftwareResp.Software[0].Status)
assert.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
@ -14962,7 +14989,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
distributedResp = submitDistributedQueryResultsResponse{}
s.DoJSON("POST", "/api/osquery/distributed/write", distributedReq, http.StatusOK, &distributedResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h2.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
assert.Nil(t, getHostSoftwareResp.Software[0].Status)
assert.Nil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
@ -15000,7 +15028,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
// Do uninstall on h
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/uninstall", h.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
assert.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
assert.Equal(t, fleet.SoftwareUninstallPending, *getHostSoftwareResp.Software[0].Status)
@ -15073,7 +15102,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
s.DoRawNoAuth("GET", fmt.Sprintf("/api/v1/fleet/device/%s/software/uninstall/%s/results", token, uninstallExecutionID), nil, http.StatusOK)
// Software should be available for install again
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
assert.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
require.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastUninstall)
@ -15087,7 +15117,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
// Since host_script_results does not use fine-grained timestamps yet, we adjust
beforeUninstall = beforeUninstall.Add(-time.Second)
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/uninstall", h.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", h.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 1)
assert.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
assert.Equal(t, fleet.SoftwareUninstallPending, *getHostSoftwareResp.Software[0].Status)
@ -15261,7 +15292,8 @@ func (s *integrationEnterpriseTestSuite) TestSelfServiceSoftwareInstallUninstall
// Do uninstall on host
s.DoRawNoAuth("POST", fmt.Sprintf("/api/v1/fleet/device/%s/software/uninstall/%d", token, titleIDSS), nil, http.StatusAccepted)
var getHostSoftwareResp getHostSoftwareResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host1.ID), nil, http.StatusOK, &getHostSoftwareResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host1.ID), nil, http.StatusOK, &getHostSoftwareResp,
"include_available_for_install", "true")
require.Len(t, getHostSoftwareResp.Software, 2)
assert.NotNil(t, getHostSoftwareResp.Software[0].SoftwarePackage.LastInstall)
assert.Equal(t, fleet.SoftwareUninstallPending, *getHostSoftwareResp.Software[0].Status)
@ -17169,9 +17201,9 @@ func (s *integrationEnterpriseTestSuite) TestVPPAppsWithoutMDM() {
}
s.uploadSoftwareInstaller(t, pkgPayload, http.StatusOK, "")
// We don't see VPP, but we do still see the installers
// We don't see VPP, but we do still see the installers (need include_available_for_install=true since it's not in osquery inventory)
resp := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", orbitHost.ID), getHostSoftwareRequest{}, http.StatusOK, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", orbitHost.ID), getHostSoftwareRequest{}, http.StatusOK, &resp)
assert.Len(t, resp.Software, 1)
assert.NotNil(t, resp.Software[0].SoftwarePackage)
assert.Nil(t, resp.Software[0].AppStoreApp)
@ -20724,8 +20756,9 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftwareWithLabelScoping()
http.StatusNoContent)
// Software is now installed on the host. We should see it in the host software list
// (need include_available_for_install=true since the install was tracked via an installer, not osquery inventory)
getHostSw := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 1)
require.Equal(t, getHostSw.Software[0].Name, "ruby")
@ -20770,7 +20803,7 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftwareWithLabelScoping()
updateInstallerLabel(installerID, lbl2.ID, true)
// We should still see the software at this point, because we haven't uninstalled it yet
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 1)
// installer should be out of scope since the label is "exclude any"
@ -20785,7 +20818,7 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftwareWithLabelScoping()
// uninstall the software
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/uninstall", host.ID, titleID), nil, http.StatusAccepted, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software?include_available_for_install=true", host.ID), nil, http.StatusOK, &getHostSw)
require.Len(t, getHostSw.Software, 1)
// TODO this is a corner case where we have visibility on a descoped uninstall, but this will disappear
// once we complete the uninstall because at that point we'll be relying solely on inventory to determine software

View file

@ -4082,9 +4082,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceAndroid() {
require.Equal(t, app2.AdamID, getHostSw.Software[1].AppStoreApp.AppStoreID)
require.Nil(t, getHostSw.Software[1].Status)
// the software now shows up in the host inventory
// the software now shows up when including available-for-install (tracked via installer, not osquery inventory)
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw, "order_key", "name")
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw, "order_key", "name",
"include_available_for_install", "true")
require.Len(t, getHostSw.Software, 2)
require.NotNil(t, getHostSw.Software[0].AppStoreApp)
require.Equal(t, app1.AdamID, getHostSw.Software[0].AppStoreApp.AppStoreID)

View file

@ -14249,7 +14249,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Check list host software
getHostSw := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW := getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1, got2 := gotSW[0], gotSW[1]
@ -14276,7 +14276,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Check with a query
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "query", "App 1")
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "query", "App 1", "include_available_for_install", "true")
require.Len(t, getHostSw.Software, 1) // App 1 only
got1 = getHostSw.Software[0]
require.Equal(t, got1.Name, "App 1")
@ -14332,7 +14332,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Check list host software
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1 = gotSW[0]
@ -14507,7 +14507,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Check list host software
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", installHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", installHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
require.Len(t, getHostSw.Software, install.hostCount+install.extraAvailable)
var foundInstalledApp bool
for index := range getHostSw.Software {
@ -17956,7 +17956,7 @@ func (s *integrationMDMTestSuite) TestVPPAppsMDMFiltering() {
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", orbitHost.ID), getHostSoftwareRequest{}, http.StatusOK, &resp)
assert.Len(t, resp.Software, 0)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), getHostSoftwareRequest{}, http.StatusOK, &resp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), getHostSoftwareRequest{}, http.StatusOK, &resp, "include_available_for_install", "true")
assert.Len(t, resp.Software, 1)
}
@ -19759,7 +19759,7 @@ func (s *integrationMDMTestSuite) TestSoftwareCategories() {
host.ID, titleID), nil, http.StatusAccepted, &installResp)
getHostSw := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
require.Len(t, getHostSw.Software, 1)
require.Equal(t, getHostSw.Software[0].Name, "ruby")
require.NotNil(t, getHostSw.Software[0].SoftwarePackage)
@ -19789,7 +19789,7 @@ func (s *integrationMDMTestSuite) TestSoftwareCategories() {
require.Equal(t, cat3.Name, getDeviceSw.Software[0].SoftwarePackage.Categories[0])
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", host.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
require.Len(t, getHostSw.Software, 1)
require.Equal(t, getHostSw.Software[0].Name, "ruby")
require.NotNil(t, getHostSw.Software[0].SoftwarePackage)

View file

@ -505,7 +505,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
// Check list host software
getHostSw := getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW := getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1, got2 := gotSW[0], gotSW[1]
@ -543,7 +543,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
require.Equal(t, 0, countResp.Count)
// We should instead have 1 pending
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
checkVPPApp(gotSW[0], addedApp, installCmdUUID, fleet.SoftwareInstallPending)
@ -570,7 +570,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
require.Equal(t, 0, countResp.Count)
// We should instead have 1 pending
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
checkVPPApp(gotSW[0], addedApp, installCmdUUID, fleet.SoftwareInstallPending)
@ -589,7 +589,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
checkCommandsInFlight(0)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
checkVPPApp(gotSW[0], addedApp, installCmdUUID, fleet.SoftwareInstalled)
@ -665,7 +665,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
// Check list host software
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1, got2 = gotSW[0], gotSW[1]
@ -757,7 +757,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/hosts/%d/activities/upcoming/%s", selfServiceHost.ID, listUpcomingAct.Activities[0].UUID), nil, http.StatusNoContent)
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1, got2 = gotSW[0], gotSW[1]
@ -814,7 +814,7 @@ func (s *integrationMDMTestSuite) TestVPPAppInstallVerification() {
})
getHostSw = getHostSoftwareResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/software", mdmHost.ID), nil, http.StatusOK, &getHostSw, "include_available_for_install", "true")
gotSW = getHostSw.Software
require.Len(t, gotSW, 2) // App 1 and App 2
got1, got2 = gotSW[0], gotSW[1]