From 886ab9098d4c8c48789d503553a6c96cfe315f0e Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Wed, 10 Jul 2024 10:12:05 -0300 Subject: [PATCH] Allow team_id=0 to signal "No Team" in os_versions (#20272) for #20150 # 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://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality --- changes/20150-no-team | 1 + .../services/entities/operating_systems.ts | 6 +----- server/datastore/mysql/hosts.go | 2 ++ server/service/hosts.go | 15 +++++++++------ server/service/integration_core_test.go | 9 +++++++++ server/service/integration_enterprise_test.go | 18 ++++++++++++++++++ 6 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 changes/20150-no-team diff --git a/changes/20150-no-team b/changes/20150-no-team new file mode 100644 index 0000000000..9a80caa2fc --- /dev/null +++ b/changes/20150-no-team @@ -0,0 +1 @@ +* Fixed a bug causing "No Team" os versions to display the wrong number diff --git a/frontend/services/entities/operating_systems.ts b/frontend/services/entities/operating_systems.ts index 1611243bfd..305c203e97 100644 --- a/frontend/services/entities/operating_systems.ts +++ b/frontend/services/entities/operating_systems.ts @@ -4,7 +4,6 @@ import endpoints from "utilities/endpoints"; import { IOperatingSystemVersion } from "interfaces/operating_system"; import { Platform } from "interfaces/platform"; import { buildQueryStringFromParams } from "utilities/url"; -import { API_NO_TEAM_ID } from "interfaces/team"; // TODO: add platforms to this constant as new ones are supported export const OS_VERSIONS_API_SUPPORTED_PLATFORMS = [ @@ -72,6 +71,7 @@ export const getOSVersions = ({ const params: IGetOSVersionsRequestQueryParams = { platform, + team_id: teamId, os_name, os_version, order_key, @@ -80,10 +80,6 @@ export const getOSVersions = ({ per_page, }; - if (teamId !== API_NO_TEAM_ID) { - params.team_id = teamId; - } - const queryString = buildQueryStringFromParams(params); if (queryString) path += `?${queryString}`; diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 8ff25625b8..8c671787a4 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -4665,6 +4665,8 @@ func (ds *Datastore) executeOSVersionQuery(ctx context.Context, teamFilter *flee args := []interface{}{aggregatedStatsTypeOSVersions} switch { case teamFilter != nil && teamFilter.TeamID != nil: + // Aggregated stats for os versions are stored by team id with 0 representing + // no team or the all teams if global_stats is true. query += " AND id = ? AND global_stats = ?" args = append(args, *teamFilter.TeamID, false) case teamFilter != nil: diff --git a/server/service/hosts.go b/server/service/hosts.go index f07d325727..eeb9a60357 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -1910,12 +1910,15 @@ func (svc *Service) OSVersions(ctx context.Context, teamID *uint, platform *stri if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil { return nil, count, nil, err } - exists, err := svc.ds.TeamExists(ctx, *teamID) - if err != nil { - return nil, count, nil, ctxerr.Wrap(ctx, err, "checking if team exists") - } else if !exists { - return nil, count, nil, fleet.NewInvalidArgumentError("team_id", fmt.Sprintf("team %d does not exist", *teamID)). - WithStatus(http.StatusNotFound) + + if *teamID != 0 { + exists, err := svc.ds.TeamExists(ctx, *teamID) + if err != nil { + return nil, count, nil, ctxerr.Wrap(ctx, err, "checking if team exists") + } else if !exists { + return nil, count, nil, fleet.NewInvalidArgumentError("team_id", fmt.Sprintf("team %d does not exist", *teamID)). + WithStatus(http.StatusNotFound) + } } } diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 3c3967d09b..2b24152bde 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -8991,6 +8991,15 @@ func (s *integrationTestSuite) TestOSVersions() { require.Equal(t, 4, osVersionsResp.Count) require.False(t, osVersionsResp.Meta.HasNextResults) require.True(t, osVersionsResp.Meta.HasPreviousResults) + + // same results with team_id=0 + s.DoJSON("GET", "/api/latest/fleet/os_versions", nil, http.StatusOK, &osVersionsResp, "page", "1", "per_page", "2", "team_id", "0") + require.Len(t, osVersionsResp.OSVersions, 2) + require.Equal(t, "macOS 13.2.1", osVersionsResp.OSVersions[0].Name) + require.Equal(t, "macOS 14.1.2", osVersionsResp.OSVersions[1].Name) + require.Equal(t, 4, osVersionsResp.Count) + require.False(t, osVersionsResp.Meta.HasNextResults) + require.True(t, osVersionsResp.Meta.HasPreviousResults) } func (s *integrationTestSuite) TestPingEndpoints() { diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 2ba8086263..c393bf45b1 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -3956,6 +3956,24 @@ func (s *integrationEnterpriseTestSuite) TestOSVersions() { "GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusForbidden, &osVersionResp, "team_id", "99999", ) + + // team user doesn't have acess to "no team" + osVersionsResp = osVersionsResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions"), nil, http.StatusForbidden, &osVersionsResp, "team_id", "0") + require.Len(t, osVersionsResp.OSVersions, 0) + + // team_id=0 is supported and returns results for hosts in "no team" + s.token = getTestAdminToken(t, s.server) + // no hosts, the results are empty + osVersionsResp = osVersionsResponse{} + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions"), nil, http.StatusOK, &osVersionsResp, "team_id", "0") + require.Len(t, osVersionsResp.OSVersions, 0) + osVersionsResp = osVersionsResponse{} + // move the host to "no team" and update the stats + require.NoError(t, s.ds.AddHostsToTeam(context.Background(), nil, []uint{hosts[0].ID})) + require.NoError(t, s.ds.UpdateOSVersions(context.Background())) + s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions"), nil, http.StatusOK, &osVersionsResp, "team_id", "0") + require.Len(t, osVersionsResp.OSVersions, 1) } func (s *integrationEnterpriseTestSuite) TestMDMNotConfiguredEndpoints() {