mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Team user should not access OS version on another team. (#17347)
#17117 For `fleet/os_versions` and `/fleet/os_versions/[id]`, team users can no longer access os versions on hosts from other teams. ### Team admin /os_versions - only returns os versions for the user's team(s) GET https://localhost:8080/api/v1/fleet/os_versions ### Team admin /os_versions/:id on 'No Team' - 403 GET https://localhost:8080/api/v1/fleet/os_versions/5 ### Global admin /os_versions/:id?team_id does not exist anywhere - 404 GET https://localhost:8080/api/v1/fleet/os_versions/999999?team_id=1 # Checklist for submitter <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Changes file added for user-visible changes in `changes/` or `orbit/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
This commit is contained in:
parent
8d8181eb0d
commit
ad5c0a90be
1
changes/17347-team-user-os-version-restrict
Normal file
1
changes/17347-team-user-os-version-restrict
Normal file
@ -0,0 +1 @@
|
||||
For GET fleet/os_versions and GET fleet/os_versions/[id], team users no longer have access to os versions on hosts from other teams.
|
@ -304,7 +304,9 @@ func TestCronVulnerabilitiesCreatesDatabasesPath(t *testing.T) {
|
||||
// we should not get this far before we see the directory being created
|
||||
return nil, errors.New("shouldn't happen")
|
||||
}
|
||||
ds.OSVersionsFunc = func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
ds.OSVersionsFunc = func(
|
||||
ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string,
|
||||
) (*fleet.OSVersions, error) {
|
||||
return &fleet.OSVersions{}, nil
|
||||
}
|
||||
ds.SyncHostsSoftwareFunc = func(ctx context.Context, updatedAt time.Time) error {
|
||||
@ -452,7 +454,9 @@ func TestScanVulnerabilities(t *testing.T) {
|
||||
ds.DeleteOutOfDateVulnerabilitiesFunc = func(ctx context.Context, source fleet.VulnerabilitySource, duration time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
ds.OSVersionsFunc = func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
ds.OSVersionsFunc = func(
|
||||
ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string,
|
||||
) (*fleet.OSVersions, error) {
|
||||
return &fleet.OSVersions{
|
||||
CountsUpdatedAt: time.Now(),
|
||||
OSVersions: []fleet.OSVersion{
|
||||
|
@ -4464,8 +4464,10 @@ func (ds *Datastore) UpdateHost(ctx context.Context, host *fleet.Host) error {
|
||||
)
|
||||
}
|
||||
|
||||
func (ds *Datastore) OSVersion(ctx context.Context, osVersionID uint, teamID *uint) (*fleet.OSVersion, *time.Time, error) {
|
||||
jsonValue, updatedAt, err := ds.executeOSVersionQuery(ctx, teamID)
|
||||
func (ds *Datastore) OSVersion(ctx context.Context, osVersionID uint, teamFilter *fleet.TeamFilter) (
|
||||
*fleet.OSVersion, *time.Time, error,
|
||||
) {
|
||||
jsonValue, updatedAt, err := ds.executeOSVersionQuery(ctx, teamFilter)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil, notFound("OSVersion")
|
||||
@ -4510,7 +4512,9 @@ func (ds *Datastore) OSVersion(ctx context.Context, osVersionID uint, teamID *ui
|
||||
// counts for the same macOS version on x86_64 and arm64 architectures are counted together.
|
||||
// Results can be filtered using the following optional criteria: team id, platform, or name and
|
||||
// version. Name cannot be used without version, and conversely, version cannot be used without name.
|
||||
func (ds *Datastore) OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
func (ds *Datastore) OSVersions(
|
||||
ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string,
|
||||
) (*fleet.OSVersions, error) {
|
||||
if name != nil && version == nil {
|
||||
return nil, errors.New("invalid usage: cannot filter by name without version")
|
||||
}
|
||||
@ -4518,7 +4522,7 @@ func (ds *Datastore) OSVersions(ctx context.Context, teamID *uint, platform *str
|
||||
return nil, errors.New("invalid usage: cannot filter by version without name")
|
||||
}
|
||||
|
||||
jsonValue, updatedAt, err := ds.executeOSVersionQuery(ctx, teamID)
|
||||
jsonValue, updatedAt, err := ds.executeOSVersionQuery(ctx, teamFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -4568,30 +4572,34 @@ func (ds *Datastore) OSVersions(ctx context.Context, teamID *uint, platform *str
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) executeOSVersionQuery(ctx context.Context, teamID *uint) (*json.RawMessage, time.Time, error) {
|
||||
func (ds *Datastore) executeOSVersionQuery(ctx context.Context, teamFilter *fleet.TeamFilter) (
|
||||
*json.RawMessage, time.Time, error,
|
||||
) {
|
||||
query := `
|
||||
SELECT
|
||||
json_value,
|
||||
updated_at
|
||||
FROM aggregated_stats
|
||||
WHERE
|
||||
id = ? AND
|
||||
global_stats = ? AND
|
||||
type = ?
|
||||
WHERE type = ?
|
||||
`
|
||||
args := []interface{}{aggregatedStatsTypeOSVersions}
|
||||
switch {
|
||||
case teamFilter != nil && teamFilter.TeamID != nil:
|
||||
query += " AND id = ? AND global_stats = ?"
|
||||
args = append(args, *teamFilter.TeamID, false)
|
||||
case teamFilter != nil:
|
||||
query += " AND " + ds.whereFilterGlobalOrTeamIDByTeamsWithSqlFilter(
|
||||
*teamFilter, "global_stats = 1 AND id = 0", "global_stats = 0 AND id",
|
||||
)
|
||||
default:
|
||||
query += " AND id = ? AND global_stats = ?"
|
||||
args = append(args, 0, true)
|
||||
}
|
||||
var row struct {
|
||||
JSONValue *json.RawMessage `db:"json_value"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
id := uint(0)
|
||||
globalStats := true
|
||||
if teamID != nil {
|
||||
id = *teamID
|
||||
globalStats = false
|
||||
}
|
||||
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &row, query, id, globalStats, aggregatedStatsTypeOSVersions)
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &row, query, args...)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, time.Time{}, ctxerr.Wrap(ctx, notFound("OSVersion"))
|
||||
|
@ -6275,7 +6275,8 @@ func testOSVersions(t *testing.T, ds *Datastore) {
|
||||
require.Equal(t, &expected[0], osVersion)
|
||||
|
||||
// team 1
|
||||
osVersions, err = ds.OSVersions(ctx, &team1.ID, nil, nil, nil)
|
||||
userAdmin := &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}
|
||||
osVersions, err = ds.OSVersions(ctx, &fleet.TeamFilter{TeamID: &team1.ID, User: userAdmin}, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected = []fleet.OSVersion{
|
||||
@ -6284,16 +6285,25 @@ func testOSVersions(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
require.Equal(t, expected, osVersions.OSVersions)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 5, &team1.ID)
|
||||
osVersion, _, err = ds.OSVersion(ctx, 5, &fleet.TeamFilter{TeamID: &team1.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected[0], osVersion)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &team1.ID)
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &fleet.TeamFilter{TeamID: &team1.ID, User: userAdmin})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected[1], osVersion)
|
||||
|
||||
userTeam1 := &fleet.User{Teams: []fleet.UserTeam{{Team: *team1, Role: fleet.RoleAdmin}}}
|
||||
osVersions, err = ds.OSVersions(ctx, &fleet.TeamFilter{User: userTeam1}, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, osVersions.OSVersions)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &fleet.TeamFilter{User: userTeam1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected[1], osVersion)
|
||||
|
||||
// team 2
|
||||
osVersions, err = ds.OSVersions(ctx, &team2.ID, nil, nil, nil)
|
||||
osVersions, err = ds.OSVersions(ctx, &fleet.TeamFilter{TeamID: &team2.ID}, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected = []fleet.OSVersion{
|
||||
@ -6302,26 +6312,30 @@ func testOSVersions(t *testing.T, ds *Datastore) {
|
||||
}
|
||||
require.Equal(t, expected, osVersions.OSVersions)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &team2.ID)
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &fleet.TeamFilter{TeamID: &team2.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected[0], osVersion)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 3, &team2.ID)
|
||||
osVersion, _, err = ds.OSVersion(ctx, 3, &fleet.TeamFilter{TeamID: &team2.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected[1], osVersion)
|
||||
|
||||
// Wrong team
|
||||
_, _, err = ds.OSVersion(ctx, 3, &fleet.TeamFilter{User: userTeam1})
|
||||
require.True(t, fleet.IsNotFound(err))
|
||||
|
||||
// team 3 (no hosts assigned to team)
|
||||
osVersions, err = ds.OSVersions(ctx, &team3.ID, nil, nil, nil)
|
||||
osVersions, err = ds.OSVersions(ctx, &fleet.TeamFilter{TeamID: &team3.ID}, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
expected = []fleet.OSVersion{}
|
||||
require.Equal(t, expected, osVersions.OSVersions)
|
||||
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &team3.ID)
|
||||
osVersion, _, err = ds.OSVersion(ctx, 2, &fleet.TeamFilter{TeamID: &team3.ID})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, osVersion)
|
||||
|
||||
// non-existent team
|
||||
_, err = ds.OSVersions(ctx, ptr.Uint(404), nil, nil, nil)
|
||||
_, err = ds.OSVersions(ctx, &fleet.TeamFilter{TeamID: ptr.Uint(404)}, nil, nil, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// new host with arm64
|
||||
|
@ -889,6 +889,14 @@ func (ds *Datastore) whereFilterHostsByTeams(filter fleet.TeamFilter, hostKey st
|
||||
// filterTableAlias is the name/alias of the table to use in generating the
|
||||
// SQL.
|
||||
func (ds *Datastore) whereFilterGlobalOrTeamIDByTeams(filter fleet.TeamFilter, filterTableAlias string) string {
|
||||
globalFilter := fmt.Sprintf("%s.team_id = 0", filterTableAlias)
|
||||
teamIDFilter := fmt.Sprintf("%s.team_id", filterTableAlias)
|
||||
return ds.whereFilterGlobalOrTeamIDByTeamsWithSqlFilter(filter, globalFilter, teamIDFilter)
|
||||
}
|
||||
|
||||
func (ds *Datastore) whereFilterGlobalOrTeamIDByTeamsWithSqlFilter(
|
||||
filter fleet.TeamFilter, globalSqlFilter string, teamIDSqlFilter 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
|
||||
@ -897,9 +905,9 @@ func (ds *Datastore) whereFilterGlobalOrTeamIDByTeams(filter fleet.TeamFilter, f
|
||||
return "FALSE"
|
||||
}
|
||||
|
||||
defaultAllowClause := fmt.Sprintf("%s.team_id = 0", filterTableAlias)
|
||||
defaultAllowClause := globalSqlFilter
|
||||
if filter.TeamID != nil {
|
||||
defaultAllowClause = fmt.Sprintf("%s.team_id = %d", filterTableAlias, *filter.TeamID)
|
||||
defaultAllowClause = fmt.Sprintf("%s = %d", teamIDSqlFilter, *filter.TeamID)
|
||||
}
|
||||
|
||||
if filter.User.GlobalRole != nil {
|
||||
@ -944,7 +952,7 @@ func (ds *Datastore) whereFilterGlobalOrTeamIDByTeams(filter fleet.TeamFilter, f
|
||||
return "FALSE"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s.team_id IN (%s)", filterTableAlias, strings.Join(idStrs, ","))
|
||||
return fmt.Sprintf("%s IN (%s)", teamIDSqlFilter, strings.Join(idStrs, ","))
|
||||
}
|
||||
|
||||
// whereFilterTeams returns the appropriate condition to use in the WHERE
|
||||
|
@ -108,7 +108,11 @@ func (ds *Datastore) Vulnerability(ctx context.Context, cve string, teamID *uint
|
||||
}
|
||||
|
||||
func (ds *Datastore) OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) (vos []*fleet.VulnerableOS, updatedAt time.Time, err error) {
|
||||
osvs, err := ds.OSVersions(ctx, teamID, nil, nil, nil)
|
||||
var teamFilter *fleet.TeamFilter
|
||||
if teamID != nil {
|
||||
teamFilter = &fleet.TeamFilter{TeamID: teamID}
|
||||
}
|
||||
osvs, err := ds.OSVersions(ctx, teamFilter, nil, nil, nil)
|
||||
if err != nil && !fleet.IsNotFound(err) {
|
||||
return nil, updatedAt, ctxerr.Wrap(ctx, err, "fetching team OS versions")
|
||||
}
|
||||
|
@ -315,10 +315,12 @@ type Datastore interface {
|
||||
GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*MunkiIssue, error)
|
||||
GetMDMSolution(ctx context.Context, mdmID uint) (*MDMSolution, error)
|
||||
|
||||
OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*OSVersions, error)
|
||||
OSVersions(ctx context.Context, teamFilter *TeamFilter, platform *string, name *string, version *string) (*OSVersions, error)
|
||||
OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) ([]*VulnerableOS, time.Time, error)
|
||||
SoftwareByCVE(ctx context.Context, cve string, teamID *uint) ([]*VulnerableSoftware, time.Time, error)
|
||||
OSVersion(ctx context.Context, osVersionID uint, teamID *uint) (*OSVersion, *time.Time, error)
|
||||
// OSVersion returns the OSVersion with the provided ID. If teamFilter is not nil, then the OSVersion is filtered.
|
||||
// The returned OSVersion is accompanied by the time it was last updated.
|
||||
OSVersion(ctx context.Context, osVersionID uint, teamFilter *TeamFilter) (*OSVersion, *time.Time, error)
|
||||
UpdateOSVersions(ctx context.Context) error
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -244,13 +244,13 @@ type GetMunkiIssueFunc func(ctx context.Context, munkiIssueID uint) (*fleet.Munk
|
||||
|
||||
type GetMDMSolutionFunc func(ctx context.Context, mdmID uint) (*fleet.MDMSolution, error)
|
||||
|
||||
type OSVersionsFunc func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error)
|
||||
type OSVersionsFunc func(ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string) (*fleet.OSVersions, error)
|
||||
|
||||
type OSVersionsByCVEFunc func(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableOS, time.Time, error)
|
||||
|
||||
type SoftwareByCVEFunc func(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableSoftware, time.Time, error)
|
||||
|
||||
type OSVersionFunc func(ctx context.Context, osVersionID uint, teamID *uint) (*fleet.OSVersion, *time.Time, error)
|
||||
type OSVersionFunc func(ctx context.Context, osVersionID uint, teamFilter *fleet.TeamFilter) (*fleet.OSVersion, *time.Time, error)
|
||||
|
||||
type UpdateOSVersionsFunc func(ctx context.Context) error
|
||||
|
||||
@ -2908,11 +2908,11 @@ func (s *DataStore) GetMDMSolution(ctx context.Context, mdmID uint) (*fleet.MDMS
|
||||
return s.GetMDMSolutionFunc(ctx, mdmID)
|
||||
}
|
||||
|
||||
func (s *DataStore) OSVersions(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
func (s *DataStore) OSVersions(ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
s.mu.Lock()
|
||||
s.OSVersionsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.OSVersionsFunc(ctx, teamID, platform, name, version)
|
||||
return s.OSVersionsFunc(ctx, teamFilter, platform, name, version)
|
||||
}
|
||||
|
||||
func (s *DataStore) OSVersionsByCVE(ctx context.Context, cve string, teamID *uint) ([]*fleet.VulnerableOS, time.Time, error) {
|
||||
@ -2929,11 +2929,11 @@ func (s *DataStore) SoftwareByCVE(ctx context.Context, cve string, teamID *uint)
|
||||
return s.SoftwareByCVEFunc(ctx, cve, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) OSVersion(ctx context.Context, osVersionID uint, teamID *uint) (*fleet.OSVersion, *time.Time, error) {
|
||||
func (s *DataStore) OSVersion(ctx context.Context, osVersionID uint, teamFilter *fleet.TeamFilter) (*fleet.OSVersion, *time.Time, error) {
|
||||
s.mu.Lock()
|
||||
s.OSVersionFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.OSVersionFunc(ctx, osVersionID, teamID)
|
||||
return s.OSVersionFunc(ctx, osVersionID, teamFilter)
|
||||
}
|
||||
|
||||
func (s *DataStore) UpdateOSVersions(ctx context.Context) error {
|
||||
|
@ -1816,17 +1816,33 @@ func (svc *Service) OSVersions(ctx context.Context, teamID *uint, platform *stri
|
||||
return nil, count, nil, &fleet.BadRequestError{Message: "Invalid order key"}
|
||||
}
|
||||
|
||||
osVersions, err := svc.ds.OSVersions(ctx, teamID, platform, name, version)
|
||||
if err != nil && fleet.IsNotFound(err) {
|
||||
// differentiate case where team was added after UpdateOSVersions last ran
|
||||
if teamID != nil && *teamID > 0 {
|
||||
// most of the time, team should exist so checking here saves unnecessary db calls
|
||||
_, err := svc.ds.Team(ctx, *teamID)
|
||||
if err != nil {
|
||||
return nil, count, nil, err
|
||||
}
|
||||
if teamID != nil {
|
||||
// This auth check ensures we return 403 if the user doesn't have access to the team
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, count, nil, err
|
||||
}
|
||||
// if team exists but stats have not yet been gathered, return empty JSON array
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, count, nil, fleet.ErrNoContext
|
||||
}
|
||||
osVersions, err := svc.ds.OSVersions(
|
||||
ctx, &fleet.TeamFilter{
|
||||
User: vc.User,
|
||||
IncludeObserver: true,
|
||||
TeamID: teamID,
|
||||
}, platform, name, version,
|
||||
)
|
||||
if err != nil && fleet.IsNotFound(err) {
|
||||
// It is possible that os exists, but aggregation job has not run yet.
|
||||
osVersions = &fleet.OSVersions{}
|
||||
} else if err != nil {
|
||||
return nil, count, nil, err
|
||||
@ -1913,15 +1929,36 @@ func (svc *Service) OSVersion(ctx context.Context, osID uint, teamID *uint, incl
|
||||
}
|
||||
|
||||
if teamID != nil {
|
||||
// This auth check ensures we return 403 if the user doesn't have access to the team
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AuthzSoftwareInventory{TeamID: teamID}, fleet.ActionRead); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
exists, err := svc.ds.TeamExists(ctx, *teamID)
|
||||
if err != nil {
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "checking if team exists")
|
||||
} else if !exists {
|
||||
return nil, nil, authz.ForbiddenWithInternal("team does not exist", nil, nil, nil)
|
||||
return nil, nil, fleet.NewInvalidArgumentError("team_id", fmt.Sprintf("team %d does not exist", *teamID)).
|
||||
WithStatus(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
osVersion, updateTime, err := svc.ds.OSVersion(ctx, osID, teamID)
|
||||
|
||||
vc, ok := viewer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, nil, fleet.ErrNoContext
|
||||
}
|
||||
osVersion, updateTime, err := svc.ds.OSVersion(
|
||||
ctx, osID, &fleet.TeamFilter{
|
||||
User: vc.User,
|
||||
IncludeObserver: true,
|
||||
TeamID: teamID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
if fleet.IsNotFound(err) {
|
||||
// We return an empty result here to be consistent with the fleet/os_versions behavior.
|
||||
// It is possible the os version exists, but the aggregation job has not run yet.
|
||||
return nil, nil, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
|
@ -994,26 +994,19 @@ func TestEmptyTeamOSVersions(t *testing.T) {
|
||||
|
||||
testVersions := []fleet.OSVersion{{HostsCount: 1, Name: "macOS 12.1", Platform: "darwin"}}
|
||||
|
||||
ds.TeamFunc = func(ctx context.Context, teamID uint) (*fleet.Team, error) {
|
||||
if teamID == 1 {
|
||||
return &fleet.Team{
|
||||
Name: "team1",
|
||||
}, nil
|
||||
ds.TeamExistsFunc = func(ctx context.Context, teamID uint) (bool, error) {
|
||||
if teamID == 3 {
|
||||
return false, nil
|
||||
}
|
||||
if teamID == 2 {
|
||||
return &fleet.Team{
|
||||
Name: "team2",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, newNotFoundError()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ds.OSVersionsFunc = func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
if *teamID == 1 {
|
||||
ds.OSVersionsFunc = func(
|
||||
ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string,
|
||||
) (*fleet.OSVersions, error) {
|
||||
if *teamFilter.TeamID == 1 {
|
||||
return &fleet.OSVersions{CountsUpdatedAt: time.Now(), OSVersions: testVersions}, nil
|
||||
}
|
||||
if *teamID == 4 {
|
||||
if *teamFilter.TeamID == 4 {
|
||||
return nil, errors.New("some unknown error")
|
||||
}
|
||||
|
||||
@ -1037,7 +1030,7 @@ func TestEmptyTeamOSVersions(t *testing.T) {
|
||||
// team does not exist
|
||||
_, _, _, err = svc.OSVersions(test.UserContext(ctx, test.UserAdmin), ptr.Uint(3), ptr.String("darwin"), nil, nil, fleet.ListOptions{}, false)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, "not found", fmt.Sprint(err))
|
||||
require.Contains(t, fmt.Sprint(err), "does not exist")
|
||||
|
||||
// some unknown error
|
||||
_, _, _, err = svc.OSVersions(test.UserContext(ctx, test.UserAdmin), ptr.Uint(4), ptr.String("darwin"), nil, nil, fleet.ListOptions{}, false)
|
||||
@ -1058,7 +1051,9 @@ func TestOSVersionsListOptions(t *testing.T) {
|
||||
{HostsCount: 6, NameOnly: "Ubuntu 21.04", Platform: "ubuntu"},
|
||||
}
|
||||
|
||||
ds.OSVersionsFunc = func(ctx context.Context, teamID *uint, platform *string, name *string, version *string) (*fleet.OSVersions, error) {
|
||||
ds.OSVersionsFunc = func(
|
||||
ctx context.Context, teamFilter *fleet.TeamFilter, platform *string, name *string, version *string,
|
||||
) (*fleet.OSVersions, error) {
|
||||
return &fleet.OSVersions{CountsUpdatedAt: time.Now(), OSVersions: testVersions}, nil
|
||||
}
|
||||
|
||||
|
@ -7903,7 +7903,8 @@ func (s *integrationTestSuite) TestOSVersions() {
|
||||
require.Equal(t, &expectedVersion, osVersionResp.OSVersion)
|
||||
|
||||
// invalid id
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions/999", nil, http.StatusNotFound, &osVersionResp)
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions/999", nil, http.StatusOK, &osVersionResp)
|
||||
assert.Zero(t, osVersionResp.OSVersion.HostsCount)
|
||||
|
||||
// name and version filters
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions", nil, http.StatusOK, &osVersionsResp, "os_name", "Windows 11 Pro 21H2", "os_version", "10.0.22000.2")
|
||||
|
@ -3406,14 +3406,15 @@ func (s *integrationEnterpriseTestSuite) TestOSVersions() {
|
||||
require.Equal(t, *vulnMeta[0].CISAKnownExploit, **osVersionsResp.OSVersions[0].Vulnerabilities[0].CISAKnownExploit)
|
||||
require.Equal(t, *vulnMeta[0].Published, **osVersionsResp.OSVersions[0].Vulnerabilities[0].CVEPublished)
|
||||
require.Equal(t, vulnMeta[0].Description, **osVersionsResp.OSVersions[0].Vulnerabilities[0].Description)
|
||||
expectedOSVersion := osVersionsResp.OSVersions[0]
|
||||
|
||||
var osVersionResp getOSVersionResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusOK, &osVersionResp)
|
||||
require.Equal(t, &osVersionsResp.OSVersions[0], osVersionResp.OSVersion)
|
||||
require.Equal(t, &expectedOSVersion, osVersionResp.OSVersion)
|
||||
|
||||
// OS versions with invalid team
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusForbidden, &osVersionResp, "team_id",
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusNotFound, &osVersionResp, "team_id",
|
||||
"99999",
|
||||
)
|
||||
|
||||
@ -3428,19 +3429,73 @@ func (s *integrationEnterpriseTestSuite) TestOSVersions() {
|
||||
)
|
||||
osVersionResp = getOSVersionResponse{}
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusNotFound, &osVersionResp, "team_id",
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusOK, &osVersionResp, "team_id",
|
||||
fmt.Sprintf("%d", tr.Team.ID),
|
||||
)
|
||||
assert.Zero(t, osVersionResp.OSVersion.HostsCount)
|
||||
|
||||
// return empty json if UpdateOSVersions cron hasn't run yet for new team
|
||||
team, err := s.ds.NewTeam(context.Background(), &fleet.Team{Name: "new team"})
|
||||
team0, err := s.ds.NewTeam(context.Background(), &fleet.Team{Name: "new team"})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team.ID, []uint{hosts[0].ID}))
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions", nil, http.StatusOK, &osVersionsResp, "team_id", fmt.Sprintf("%d", team.ID))
|
||||
require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team0.ID, []uint{hosts[0].ID}))
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions", nil, http.StatusOK, &osVersionsResp, "team_id", fmt.Sprintf("%d", team0.ID))
|
||||
require.Len(t, osVersionsResp.OSVersions, 0)
|
||||
|
||||
// return err if team_id is invalid
|
||||
s.DoJSON("GET", "/api/latest/fleet/os_versions", nil, http.StatusBadRequest, &osVersionsResp, "team_id", "invalid")
|
||||
|
||||
// Create another team and a team user
|
||||
team1, err := s.ds.NewTeam(
|
||||
context.Background(), &fleet.Team{
|
||||
ID: 42,
|
||||
Name: "team1-os_version",
|
||||
Description: "desc team1",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
// Create a new admin for team1.
|
||||
password := test.GoodPassword
|
||||
email := "admin-team1-os_version@example.com"
|
||||
u := &fleet.User{
|
||||
Name: "admin team1",
|
||||
Email: email,
|
||||
GlobalRole: nil,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: *team1,
|
||||
Role: fleet.RoleAdmin,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, u.SetPassword(password, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.setTokenForTest(t, email, test.GoodPassword)
|
||||
|
||||
// generate aggregated stats
|
||||
require.NoError(t, s.ds.UpdateOSVersions(context.Background()))
|
||||
// team1 user does not have access to team0 host
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions"), nil, http.StatusOK, &osVersionsResp)
|
||||
assert.Empty(t, osVersionsResp.OSVersions)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusOK, &osVersionResp)
|
||||
assert.Zero(t, osVersionResp.OSVersion.HostsCount)
|
||||
|
||||
// Move host from team0 to team1
|
||||
require.NoError(t, s.ds.AddHostsToTeam(context.Background(), &team1.ID, []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)
|
||||
require.Len(t, osVersionsResp.OSVersions, 1)
|
||||
assert.Equal(t, expectedOSVersion, osVersionsResp.OSVersions[0])
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusOK, &osVersionResp)
|
||||
require.Equal(t, &expectedOSVersion, osVersionResp.OSVersion)
|
||||
|
||||
// Team user is forbidden to access invalid team
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusForbidden, &osVersionResp, "team_id",
|
||||
"99999",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestMDMNotConfiguredEndpoints() {
|
||||
|
Loading…
Reference in New Issue
Block a user