Add team assignment checks to APIs that do label association (#37246)
Resolves #37104 ## 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) - [ ] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Label validation now enforces team-context constraints for policies, queries, and MDM profiles. * Global policies now verify label validity before creation. * **Bug Fixes** * Improved label association verification in team-specific configurations. * **Tests** * Added comprehensive test coverage for team label associations, including label scoping validation and team deletion scenarios. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Ian Littman <iansltx@gmail.com>
This commit is contained in:
co-authored by
Ian Littman
parent
a5b2e911d6
commit
554f268768
@@ -275,10 +275,13 @@ type Service interface {
|
||||
// ListLabelsForHost returns a slice of labels for a given host
|
||||
ListLabelsForHost(ctx context.Context, hostID uint) ([]*Label, error)
|
||||
|
||||
// BatchValidateLabels validates that each of the provided label names exists. The returned map
|
||||
// is keyed by label name. Caller must ensure that appropirate authorization checks are
|
||||
// performed prior to calling this method.
|
||||
BatchValidateLabels(ctx context.Context, labelNames []string) (map[string]LabelIdent, error)
|
||||
// BatchValidateLabels validates that each of the provided label names exists,
|
||||
// and verifies the provided label names belong to the given teamID.
|
||||
//
|
||||
// The returned map is keyed by label name.
|
||||
// Caller must ensure that appropriate authorization checks are performed prior
|
||||
// to calling this method.
|
||||
BatchValidateLabels(ctx context.Context, teamID *uint, labelNames []string) (map[string]LabelIdent, error)
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// QueryService
|
||||
|
||||
@@ -162,7 +162,7 @@ type ListHostsInLabelFunc func(ctx context.Context, lid uint, opt fleet.HostList
|
||||
|
||||
type ListLabelsForHostFunc func(ctx context.Context, hostID uint) ([]*fleet.Label, error)
|
||||
|
||||
type BatchValidateLabelsFunc func(ctx context.Context, labelNames []string) (map[string]fleet.LabelIdent, error)
|
||||
type BatchValidateLabelsFunc func(ctx context.Context, teamID *uint, labelNames []string) (map[string]fleet.LabelIdent, error)
|
||||
|
||||
type ApplyQuerySpecsFunc func(ctx context.Context, specs []*fleet.QuerySpec) error
|
||||
|
||||
@@ -2651,11 +2651,11 @@ func (s *Service) ListLabelsForHost(ctx context.Context, hostID uint) ([]*fleet.
|
||||
return s.ListLabelsForHostFunc(ctx, hostID)
|
||||
}
|
||||
|
||||
func (s *Service) BatchValidateLabels(ctx context.Context, labelNames []string) (map[string]fleet.LabelIdent, error) {
|
||||
func (s *Service) BatchValidateLabels(ctx context.Context, teamID *uint, labelNames []string) (map[string]fleet.LabelIdent, error) {
|
||||
s.mu.Lock()
|
||||
s.BatchValidateLabelsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.BatchValidateLabelsFunc(ctx, labelNames)
|
||||
return s.BatchValidateLabelsFunc(ctx, teamID, labelNames)
|
||||
}
|
||||
|
||||
func (s *Service) ApplyQuerySpecs(ctx context.Context, specs []*fleet.QuerySpec) error {
|
||||
|
||||
@@ -436,7 +436,7 @@ func (svc *Service) NewMDMAppleConfigProfile(ctx context.Context, teamID uint, d
|
||||
cp.Mobileconfig = data
|
||||
cp.SecretsUpdatedAt = secretsUpdatedAt
|
||||
|
||||
labelMap, err := svc.validateProfileLabels(ctx, labels)
|
||||
labelMap, err := svc.validateProfileLabels(ctx, &teamID, labels)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating labels")
|
||||
}
|
||||
|
||||
@@ -75,6 +75,11 @@ func (svc Service) NewGlobalPolicy(ctx context.Context, p fleet.PolicyPayload) (
|
||||
Message: fmt.Sprintf("policy payload verification: %s", err),
|
||||
})
|
||||
}
|
||||
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, nil, append(p.LabelsIncludeAny, p.LabelsExcludeAny...)); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
policy, err := svc.ds.NewGlobalPolicy(ctx, ptr.Uint(vc.UserID()), p)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "storing policy")
|
||||
|
||||
@@ -16917,7 +16917,6 @@ func (s *integrationMDMTestSuite) TestSetupExperience() {
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
}
|
||||
installerID1, titleID1, err := ds.MatchOrCreateSoftwareInstaller(ctx, &swInstallerPayload1)
|
||||
_ = installerID1
|
||||
require.NoError(t, err)
|
||||
|
||||
app1 := &fleet.VPPApp{Name: "vpp_app_1", VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "1", Platform: fleet.MacOSPlatform}}, BundleIdentifier: "b1"}
|
||||
@@ -19078,23 +19077,29 @@ func (s *integrationMDMTestSuite) TestTeamLabelsTeamDeletion() {
|
||||
|
||||
// Create label on team t1.
|
||||
l1t1, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "l1t1",
|
||||
Query: "SELECT t1;",
|
||||
TeamID: &t1.ID,
|
||||
Name: "l1t1",
|
||||
Query: "SELECT t1;",
|
||||
TeamID: &t1.ID,
|
||||
LabelType: fleet.LabelTypeRegular,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create two labels on team t2.
|
||||
// Create a label on team t2.
|
||||
l2t2, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "l2t2",
|
||||
Query: "SELECT t2;",
|
||||
TeamID: &t2.ID,
|
||||
Name: "l2t2",
|
||||
Query: "SELECT t2;",
|
||||
TeamID: &t2.ID,
|
||||
LabelType: fleet.LabelTypeRegular,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create global label.
|
||||
globalLabel, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "global",
|
||||
Query: "SELECT global;",
|
||||
TeamID: nil,
|
||||
Name: "global",
|
||||
Query: "SELECT global;",
|
||||
TeamID: nil,
|
||||
LabelType: fleet.LabelTypeRegular,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -19222,3 +19227,539 @@ func (s *integrationMDMTestSuite) TestTeamLabelsTeamDeletion() {
|
||||
require.Equal(t, l2t2.ID, hostLabels[0].ID)
|
||||
require.Equal(t, globalLabel.ID, hostLabels[1].ID)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestTeamLabelsAssociationsCheck() {
|
||||
t := s.T()
|
||||
|
||||
test.CreateInsertGlobalVPPToken(t, s.ds)
|
||||
|
||||
t1, err := s.ds.NewTeam(context.Background(), &fleet.Team{
|
||||
Name: "t1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t2, err := s.ds.NewTeam(context.Background(), &fleet.Team{
|
||||
Name: "t2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create label on team t1.
|
||||
l1t1, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "l1t1",
|
||||
Query: "SELECT t1;",
|
||||
TeamID: &t1.ID,
|
||||
LabelType: fleet.LabelTypeRegular,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create label on team t2.
|
||||
l2t2, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "l2t2",
|
||||
Query: "SELECT t2;",
|
||||
TeamID: &t2.ID,
|
||||
LabelType: fleet.LabelTypeRegular,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create global label.
|
||||
globalLabel, err := s.ds.NewLabel(t.Context(), &fleet.Label{
|
||||
Name: "global",
|
||||
Query: "SELECT global;",
|
||||
TeamID: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("1. policy labels assignment checks", func(t *testing.T) {
|
||||
// 1.A.1 Attempt to create global policy that references l1t1 (should fail).
|
||||
var gpResp globalPolicyResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", globalPolicyRequest{
|
||||
Name: "All teams policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{l1t1.Name, globalLabel.Name},
|
||||
}, http.StatusBadRequest, &gpResp)
|
||||
gpResp = globalPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", globalPolicyRequest{
|
||||
Name: "All teams policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name, l1t1.Name},
|
||||
}, http.StatusBadRequest, &gpResp)
|
||||
|
||||
// 1.A.2 Attempt to create a global policy with global labels (should succeed).
|
||||
gpResp = globalPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", globalPolicyRequest{
|
||||
Name: "All teams policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
}, http.StatusOK, &gpResp)
|
||||
globalPolicyID := gpResp.Policy.ID
|
||||
gpResp = globalPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", globalPolicyRequest{
|
||||
Name: "All teams policy 2",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name},
|
||||
}, http.StatusOK, &gpResp)
|
||||
|
||||
// 1.A.3 Attempt to modify a global policy with team labels (should fail).
|
||||
mgpr := &modifyGlobalPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Name: ptr.String("newName1"),
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
},
|
||||
}
|
||||
patchPol1 := &modifyGlobalPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", globalPolicyID), mgpr, http.StatusBadRequest, patchPol1)
|
||||
mgpr = &modifyGlobalPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Name: ptr.String("newName1"),
|
||||
LabelsExcludeAny: []string{l1t1.Name},
|
||||
},
|
||||
}
|
||||
patchPol1 = &modifyGlobalPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", globalPolicyID), mgpr, http.StatusBadRequest, patchPol1)
|
||||
|
||||
// 1.A.4 Attempt to modify a global policy with global labels (should succeed).
|
||||
mgpr = &modifyGlobalPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Name: ptr.String("newName1"),
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
},
|
||||
}
|
||||
patchPol1 = &modifyGlobalPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", globalPolicyID), mgpr, http.StatusOK, patchPol1)
|
||||
mgpr = &modifyGlobalPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Name: ptr.String("newName2"),
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsExcludeAny: []string{globalLabel.Name},
|
||||
},
|
||||
}
|
||||
patchPol1 = &modifyGlobalPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", globalPolicyID), mgpr, http.StatusOK, patchPol1)
|
||||
|
||||
// 1.B.1 Attempt to create a team policy that references l2t2 (should fail).
|
||||
tpResp := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "t1 policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{globalLabel.Name, l2t2.Name},
|
||||
}, http.StatusBadRequest, &tpResp)
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "t1 policy exclude",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name, l2t2.Name},
|
||||
}, http.StatusBadRequest, &tpResp)
|
||||
|
||||
// 1.B.2 Attempt to create a team policy with a global label and same team label (should succeed).
|
||||
tpResp = teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "t1 policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{globalLabel.Name, l1t1.Name},
|
||||
}, http.StatusOK, &tpResp)
|
||||
teamPolicyID := tpResp.Policy.ID
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "t1 policy 2",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name, l1t1.Name},
|
||||
}, http.StatusOK, &tpResp)
|
||||
|
||||
// 1.B.3 Attempt to edit a team policy to reference l2t2 (should fail; label is outside team).
|
||||
mtplr := modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, teamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &mtplr)
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, teamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsExcludeAny: []string{l2t2.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &mtplr)
|
||||
|
||||
// 1.B.3 Attempt to edit a team policy to reference a team label on the same team (should succeed).
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, teamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, teamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsExcludeAny: []string{l1t1.Name, globalLabel.Name},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
|
||||
// 1.C.1 Attempt to create a "No team" policy that references l1t1 (should fail).
|
||||
tpResp = teamPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams/0/policies", teamPolicyRequest{
|
||||
Name: "no team policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{globalLabel.Name, l2t2.Name},
|
||||
}, http.StatusBadRequest, &tpResp)
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams/0/policies", teamPolicyRequest{
|
||||
Name: "no team policy exclude",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name, l2t2.Name},
|
||||
}, http.StatusBadRequest, &tpResp)
|
||||
|
||||
// 1.B.2 Attempt to create a "No team" policy with a global label (should succeed).
|
||||
tpResp = teamPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams/0/policies", teamPolicyRequest{
|
||||
Name: "no team policy",
|
||||
Query: "SELECT 1;",
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
}, http.StatusOK, &tpResp)
|
||||
noTeamPolicyID := tpResp.Policy.ID
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams/0/policies", teamPolicyRequest{
|
||||
Name: "no team policy 2",
|
||||
Query: "SELECT 1;",
|
||||
LabelsExcludeAny: []string{globalLabel.Name},
|
||||
}, http.StatusOK, &tpResp)
|
||||
|
||||
// 1.B.3 Attempt to edit a "No team" policy with a team policy that references l2t2 (should fail).
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/0/policies/%d", noTeamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &mtplr)
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/0/policies/%d", noTeamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsExcludeAny: []string{l2t2.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &mtplr)
|
||||
|
||||
// 1.B.3 Attempt to edit a team policy to reference a global label (should succeed).
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/0/policies/%d", noTeamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/0/policies/%d", noTeamPolicyID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsExcludeAny: []string{globalLabel.Name},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
})
|
||||
|
||||
t.Run("2. query labels assignment checks", func(t *testing.T) {
|
||||
// 2.A.1 Attempt to create global query with team labels (should fail).
|
||||
var createQueryResp createQueryResponse
|
||||
reqQuery := &fleet.QueryPayload{
|
||||
Name: ptr.String("All teams query"),
|
||||
Query: ptr.String("SELECT 1;"),
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", reqQuery, http.StatusBadRequest, &createQueryResp)
|
||||
|
||||
// 2.A.2 Attempt to create global query with global label (should succeed).
|
||||
createQueryResp = createQueryResponse{}
|
||||
reqQuery = &fleet.QueryPayload{
|
||||
Name: ptr.String("All teams query"),
|
||||
Query: ptr.String("SELECT 1;"),
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", reqQuery, http.StatusOK, &createQueryResp)
|
||||
globalQueryID := createQueryResp.Query.ID
|
||||
|
||||
// 2.A.3 Attempt to edit global query with team label (should fail).
|
||||
modifyQueryResp := modifyQueryResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", globalQueryID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &modifyQueryResp)
|
||||
|
||||
// 2.A.4 Attempt to edit global query with global label (should succeed).
|
||||
modifyQueryResp = modifyQueryResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", globalQueryID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
},
|
||||
}, http.StatusOK, &modifyQueryResp)
|
||||
|
||||
// 2.B.1 Attempt to create a team query with a label of another team (should fail).
|
||||
createQueryResp = createQueryResponse{}
|
||||
reqQuery = &fleet.QueryPayload{
|
||||
Name: ptr.String("Team one query"),
|
||||
Query: ptr.String("SELECT 1;"),
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
|
||||
TeamID: &t1.ID,
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", reqQuery, http.StatusBadRequest, &createQueryResp)
|
||||
|
||||
// 2.B.2 Attempt to create a team query with a label of the same team (should succeed).
|
||||
createQueryResp = createQueryResponse{}
|
||||
reqQuery = &fleet.QueryPayload{
|
||||
Name: ptr.String("Team one query"),
|
||||
Query: ptr.String("SELECT 1;"),
|
||||
LabelsIncludeAny: []string{l1t1.Name, globalLabel.Name},
|
||||
|
||||
TeamID: &t1.ID,
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", reqQuery, http.StatusOK, &createQueryResp)
|
||||
team1LabelID := createQueryResp.Query.ID
|
||||
|
||||
// 2.A.3 Attempt to edit a team query with a label of another team (should fail).
|
||||
modifyQueryResp = modifyQueryResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", team1LabelID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
LabelsIncludeAny: []string{l2t2.Name, globalLabel.Name},
|
||||
},
|
||||
}, http.StatusBadRequest, &modifyQueryResp)
|
||||
|
||||
// 2.A.4 Attempt to edit team query with a label of the same team (should succeed).
|
||||
modifyQueryResp = modifyQueryResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", team1LabelID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
},
|
||||
}, http.StatusOK, &modifyQueryResp)
|
||||
})
|
||||
|
||||
t.Run("3. configuration profiles assignment check", func(t *testing.T) {
|
||||
// NOTE: Not testing the API endpoint POST /api/latest/fleet/mdm/profiles used by the UI
|
||||
// because of time constraints (we haven't yet implemented test utilities for multipart uploads).
|
||||
|
||||
// Attempt to create a team profile with labels from another team (should fail).
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N1", Profile: mobileconfigForTestWithContent("N1", "I1", "com.test.profile.content", "test inner type", "test inner name"),
|
||||
LabelsIncludeAll: []string{l2t2.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusBadRequest, "team_id", fmt.Sprint(t1.ID))
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N2", Profile: syncMLForTest("./Foo/Bar"),
|
||||
LabelsIncludeAll: []string{l2t2.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusBadRequest, "team_id", fmt.Sprint(t1.ID))
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N3", Profile: declarationForTest("D1"),
|
||||
LabelsIncludeAll: []string{l2t2.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusBadRequest, "team_id", fmt.Sprint(t1.ID))
|
||||
|
||||
// Attempt to create a profile with a label on the same team (should succeed).
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N3", Profile: declarationForTest("D1"),
|
||||
LabelsIncludeAll: []string{l1t1.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusNoContent, "team_id", fmt.Sprint(t1.ID))
|
||||
|
||||
// Attempt to create a profile in "No team" with a label that belongs to a team (should fail).
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N1", Profile: mobileconfigForTestWithContent("N1", "I1", "com.test.profile.content", "test inner type", "test inner name"),
|
||||
LabelsIncludeAll: []string{l1t1.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusBadRequest)
|
||||
|
||||
// Attempt to create a profile in "No team" with a global label (should succeed).
|
||||
s.Do("POST", "/api/latest/fleet/configuration_profiles/batch", batchModifyMDMConfigProfilesRequest{
|
||||
ConfigurationProfiles: []fleet.BatchModifyMDMConfigProfilePayload{
|
||||
{
|
||||
DisplayName: "N1", Profile: mobileconfigForTestWithContent("N1", "I1", "com.test.profile.content", "test inner type", "test inner name"),
|
||||
LabelsIncludeAll: []string{globalLabel.Name},
|
||||
},
|
||||
},
|
||||
}, http.StatusNoContent)
|
||||
})
|
||||
|
||||
t.Run("4. software installers assignment check", func(t *testing.T) {
|
||||
// Attempt to create a software installer with a label of another team (should fail).
|
||||
payloadRubyTm1 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "ruby.deb",
|
||||
SelfService: false,
|
||||
TeamID: &t1.ID,
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payloadRubyTm1, http.StatusBadRequest, "")
|
||||
|
||||
// Attempt to create a software installer with a label on the same team (should succeed).
|
||||
payloadRubyTm1 = &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "ruby.deb",
|
||||
SelfService: false,
|
||||
TeamID: &t1.ID,
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payloadRubyTm1, http.StatusOK, "")
|
||||
swTitleID := getSoftwareTitleID(t, s.ds, "ruby", "deb_packages")
|
||||
|
||||
// Attempt to edit a software installer with a label from another team (should fail).
|
||||
updatePayload := &fleet.UpdateSoftwareInstallerPayload{
|
||||
TitleID: swTitleID,
|
||||
TeamID: &t1.ID,
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
}
|
||||
s.updateSoftwareInstaller(t, updatePayload, http.StatusBadRequest, "")
|
||||
|
||||
// Attempt to edit a software installer with a label on the same team (should succeed).
|
||||
updatePayload = &fleet.UpdateSoftwareInstallerPayload{
|
||||
TitleID: swTitleID,
|
||||
TeamID: &t1.ID,
|
||||
LabelsIncludeAny: []string{l1t1.Name, globalLabel.Name},
|
||||
}
|
||||
s.updateSoftwareInstaller(t, updatePayload, http.StatusOK, "")
|
||||
|
||||
// Attempt to create a software installer in "No team" with a team label (should fail).
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "dummy_installer.pkg",
|
||||
Title: "DummyApp",
|
||||
TeamID: nil,
|
||||
LabelsExcludeAny: []string{l1t1.Name},
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusBadRequest, "")
|
||||
|
||||
// Attempt to create a software installer in "No team" with a global label (should succeed).
|
||||
payload = &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "dummy_installer.pkg",
|
||||
Title: "DummyApp",
|
||||
TeamID: nil,
|
||||
LabelsExcludeAny: []string{globalLabel.Name},
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
swTitleID = getSoftwareTitleID(t, s.ds, payload.Title, "apps")
|
||||
|
||||
// Attempt to edit a software installer in "No team" with a team label (should fail).
|
||||
updatePayload = &fleet.UpdateSoftwareInstallerPayload{
|
||||
TitleID: swTitleID,
|
||||
TeamID: nil,
|
||||
LabelsIncludeAny: []string{l1t1.Name, globalLabel.Name},
|
||||
}
|
||||
s.updateSoftwareInstaller(t, updatePayload, http.StatusBadRequest, "")
|
||||
|
||||
// Attempt to edit a software installer in "No team" with a global label (should succeed).
|
||||
updatePayload = &fleet.UpdateSoftwareInstallerPayload{
|
||||
TitleID: swTitleID,
|
||||
TeamID: nil,
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
}
|
||||
s.updateSoftwareInstaller(t, updatePayload, http.StatusOK, "")
|
||||
})
|
||||
|
||||
t.Run("5. vpp apps assignment checks", func(t *testing.T) {
|
||||
// Set up VPP token
|
||||
orgName := "Fleet Device Management Inc."
|
||||
token := "mycooltoken"
|
||||
expTime := time.Now().Add(200 * time.Hour).UTC().Round(time.Second)
|
||||
expDate := expTime.Format(fleet.VPPTimeFormat)
|
||||
tokenJSON := fmt.Sprintf(`{"expDate":"%s","token":"%s","orgName":"%s"}`, expDate, token, orgName)
|
||||
t.Setenv("FLEET_DEV_VPP_URL", s.appleVPPConfigSrv.URL)
|
||||
var validToken uploadVPPTokenResponse
|
||||
s.uploadDataViaForm("/api/latest/fleet/vpp_tokens", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "", &validToken)
|
||||
|
||||
// Get the token
|
||||
var resp getVPPTokensResponse
|
||||
s.DoJSON("GET", "/api/latest/fleet/vpp_tokens", &getVPPTokensRequest{}, http.StatusOK, &resp)
|
||||
require.NoError(t, resp.Err)
|
||||
|
||||
// Associate team to the VPP token.
|
||||
var resPatchVPP patchVPPTokensTeamsResponse
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{t1.ID}}, http.StatusOK, &resPatchVPP)
|
||||
|
||||
// Attempt to add an app store app on a team with a label from another team (should fail).
|
||||
var addAppResp addAppStoreAppResponse
|
||||
addAppReq := &addAppStoreAppRequest{
|
||||
TeamID: &t1.ID,
|
||||
AppStoreID: "1",
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", addAppReq, http.StatusBadRequest, &addAppResp)
|
||||
|
||||
// Attempt to add an app store app on a team with a label on the same team (should succeed).
|
||||
addAppReq = &addAppStoreAppRequest{
|
||||
TeamID: &t1.ID,
|
||||
AppStoreID: "1",
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
}
|
||||
addAppResp = addAppStoreAppResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", addAppReq, http.StatusOK, &addAppResp)
|
||||
|
||||
// Associate all teams token.
|
||||
resPatchVPP = patchVPPTokensTeamsResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP)
|
||||
|
||||
// Attempt to add an app store app to "No team" with a team label (should fail).
|
||||
addAppResp = addAppStoreAppResponse{}
|
||||
addAppReq = &addAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
AppStoreID: "1",
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", addAppReq, http.StatusBadRequest, &addAppResp)
|
||||
|
||||
// Attempt to add an app store app to "No team" with a global label (should succeed).
|
||||
addAppResp = addAppStoreAppResponse{}
|
||||
addAppReq = &addAppStoreAppRequest{
|
||||
TeamID: nil,
|
||||
AppStoreID: "1",
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{globalLabel.Name},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", addAppReq, http.StatusOK, &addAppResp)
|
||||
})
|
||||
|
||||
t.Run("6. in-house apps assignment checks", func(t *testing.T) {
|
||||
// Attempt to create in-house app in team t1 that references l2t2 (should fail).
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
TeamID: &t1.ID,
|
||||
Filename: "ipa_test2.ipa",
|
||||
Version: "1.0.0",
|
||||
StorageID: uuid.New().String(),
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{l2t2.Name},
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusBadRequest, "")
|
||||
|
||||
// Attempt to create in-house app in team t1 that references l1t1 (should succeed).
|
||||
payload = &fleet.UploadSoftwareInstallerPayload{
|
||||
TeamID: &t1.ID,
|
||||
Filename: "ipa_test2.ipa",
|
||||
Version: "1.0.0",
|
||||
StorageID: uuid.New().String(),
|
||||
SelfService: true,
|
||||
LabelsIncludeAny: []string{l1t1.Name},
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// Not testing edit as this was tested in (4) above.
|
||||
// This is used to populate the in house apps tables.
|
||||
})
|
||||
|
||||
// Make sure we can delete the teams given all the entities created.
|
||||
err = s.ds.DeleteTeam(t.Context(), t1.ID)
|
||||
require.NoError(t, err)
|
||||
err = s.ds.DeleteTeam(t.Context(), t2.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -680,7 +680,7 @@ func (svc *Service) GetLabelSpec(ctx context.Context, name string) (*fleet.Label
|
||||
return svc.ds.GetLabelSpec(ctx, name)
|
||||
}
|
||||
|
||||
func (svc *Service) BatchValidateLabels(ctx context.Context, labelNames []string) (map[string]fleet.LabelIdent, error) {
|
||||
func (svc *Service) BatchValidateLabels(ctx context.Context, teamID *uint, labelNames []string) (map[string]fleet.LabelIdent, error) {
|
||||
if authctx, ok := authz_ctx.FromContext(ctx); !ok {
|
||||
return nil, fleet.NewAuthRequiredError("batch validate labels: missing authorization context")
|
||||
} else if !authctx.Checked() {
|
||||
@@ -705,6 +705,10 @@ func (svc *Service) BatchValidateLabels(ctx context.Context, labelNames []string
|
||||
}
|
||||
}
|
||||
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, teamID, labelNames); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
byName := make(map[string]fleet.LabelIdent, len(labels))
|
||||
for labelName, labelID := range labels {
|
||||
byName[labelName] = fleet.LabelIdent{
|
||||
|
||||
@@ -375,7 +375,7 @@ func TestBatchValidateLabels(t *testing.T) {
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
|
||||
t.Run("no auth context", func(t *testing.T) {
|
||||
_, err := svc.BatchValidateLabels(context.Background(), nil)
|
||||
_, err := svc.BatchValidateLabels(context.Background(), nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
@@ -383,7 +383,7 @@ func TestBatchValidateLabels(t *testing.T) {
|
||||
ctx = authz_ctx.NewContext(ctx, &authCtx)
|
||||
|
||||
t.Run("no auth checked", func(t *testing.T) {
|
||||
_, err := svc.BatchValidateLabels(ctx, nil)
|
||||
_, err := svc.BatchValidateLabels(ctx, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
@@ -413,6 +413,21 @@ func TestBatchValidateLabels(t *testing.T) {
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
ds.LabelsByNameFunc = func(ctx context.Context, names []string) (map[string]*fleet.Label, error) {
|
||||
res := make(map[string]*fleet.Label)
|
||||
if names == nil {
|
||||
return res, nil
|
||||
}
|
||||
for _, name := range names {
|
||||
if id, ok := mockLabels[name]; ok {
|
||||
res[name] = &fleet.Label{
|
||||
ID: id,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -464,7 +479,7 @@ func TestBatchValidateLabels(t *testing.T) {
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := svc.BatchValidateLabels(ctx, tt.labelNames)
|
||||
got, err := svc.BatchValidateLabels(ctx, nil, tt.labelNames)
|
||||
if tt.expectError != "" {
|
||||
require.Contains(t, err.Error(), tt.expectError)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
func loadLabelsFromNames(ctx context.Context, ds fleet.Datastore, labelNames []string) (map[string]*fleet.Label, error) {
|
||||
labelsMap, err := ds.LabelsByName(ctx, labelNames)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get labels by name")
|
||||
}
|
||||
// Make sure that all labels were found
|
||||
for _, labelName := range labelNames {
|
||||
if _, ok := labelsMap[labelName]; !ok {
|
||||
return nil, ctxerr.Wrap(ctx, badRequestf("label %q not found", labelName))
|
||||
}
|
||||
}
|
||||
return labelsMap, nil
|
||||
}
|
||||
|
||||
func verifyLabelsToAssociate(ctx context.Context, ds fleet.Datastore, entityTeamID *uint, labelNames []string) error {
|
||||
if len(labelNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove duplicate names.
|
||||
seen := make(map[string]struct{})
|
||||
uniqueLabelNames := make([]string, 0, len(labelNames))
|
||||
for _, s := range labelNames {
|
||||
if _, ok := seen[s]; ok {
|
||||
continue
|
||||
}
|
||||
seen[s] = struct{}{}
|
||||
uniqueLabelNames = append(uniqueLabelNames, s)
|
||||
}
|
||||
|
||||
// Load data of all labels.
|
||||
labels, err := loadLabelsFromNames(ctx, ds, uniqueLabelNames)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "labels by name")
|
||||
}
|
||||
|
||||
// Perform team ID checks for "No team" or global entities.
|
||||
if entityTeamID == nil || *entityTeamID == 0 {
|
||||
// entityTeamID == nil: global entity (like "All teams" policies and "All team" queries)
|
||||
// entityTeamID == 0: "no team" entity.
|
||||
// For both cases, labels must be global because currently we don't support labels in "No team".
|
||||
for _, label := range labels {
|
||||
if label.TeamID != nil {
|
||||
return ctxerr.Wrap(ctx, badRequestf("label %q is a team label", label.Name))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Perform team ID checks for team entities.
|
||||
for _, label := range labels {
|
||||
// Team entities can reference global labels.
|
||||
if label.TeamID == nil {
|
||||
continue
|
||||
}
|
||||
// Team entities cannot reference labels that belong another team.
|
||||
if *label.TeamID != *entityTeamID {
|
||||
return ctxerr.Wrap(ctx, badRequestf("label %q belongs to a different team", label.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+20
-7
@@ -1769,7 +1769,7 @@ func (svc *Service) NewMDMAndroidConfigProfile(ctx context.Context, teamID uint,
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate profile")
|
||||
}
|
||||
|
||||
labelMap, err := svc.validateProfileLabels(ctx, labels)
|
||||
labelMap, err := svc.validateProfileLabels(ctx, &teamID, labels)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating labels")
|
||||
}
|
||||
@@ -1814,7 +1814,7 @@ func (svc *Service) NewMDMAndroidConfigProfile(ctx context.Context, teamID uint,
|
||||
return newCP, nil
|
||||
}
|
||||
|
||||
func (svc *Service) batchValidateProfileLabels(ctx context.Context, labelNames []string) (map[string]fleet.ConfigurationProfileLabel, error) {
|
||||
func (svc *Service) batchValidateProfileLabels(ctx context.Context, teamID *uint, labelNames []string) (map[string]fleet.ConfigurationProfileLabel, error) {
|
||||
if len(labelNames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1838,6 +1838,13 @@ func (svc *Service) batchValidateProfileLabels(ctx context.Context, labelNames [
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(lucas): To not break API error string returned above
|
||||
// AND for code reusability we are a-ok with loading labels again in verifyLabelsToAssociate.
|
||||
// This can definitely be optimized if need be.
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, teamID, labelNames); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
profLabels := make(map[string]fleet.ConfigurationProfileLabel)
|
||||
for labelName, labelID := range labels {
|
||||
profLabels[labelName] = fleet.ConfigurationProfileLabel{
|
||||
@@ -1848,8 +1855,8 @@ func (svc *Service) batchValidateProfileLabels(ctx context.Context, labelNames [
|
||||
return profLabels, nil
|
||||
}
|
||||
|
||||
func (svc *Service) validateProfileLabels(ctx context.Context, labelNames []string) ([]fleet.ConfigurationProfileLabel, error) {
|
||||
labelMap, err := svc.batchValidateProfileLabels(ctx, labelNames)
|
||||
func (svc *Service) validateProfileLabels(ctx context.Context, teamID *uint, labelNames []string) ([]fleet.ConfigurationProfileLabel, error) {
|
||||
labelMap, err := svc.batchValidateProfileLabels(ctx, teamID, labelNames)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating profile labels")
|
||||
}
|
||||
@@ -1959,8 +1966,14 @@ func batchSetMDMProfilesEndpoint(ctx context.Context, request interface{}, svc f
|
||||
}
|
||||
|
||||
func (svc *Service) BatchSetMDMProfiles(
|
||||
ctx context.Context, tmID *uint, tmName *string, profiles []fleet.MDMProfileBatchPayload, dryRun, skipBulkPending bool,
|
||||
assumeEnabled *bool, noCache bool,
|
||||
ctx context.Context,
|
||||
tmID *uint,
|
||||
tmName *string,
|
||||
profiles []fleet.MDMProfileBatchPayload,
|
||||
dryRun bool,
|
||||
skipBulkPending bool,
|
||||
assumeEnabled *bool,
|
||||
noCache bool,
|
||||
) error {
|
||||
var err error
|
||||
if tmID, tmName, err = svc.authorizeBatchProfiles(ctx, tmID, tmName); err != nil {
|
||||
@@ -2000,7 +2013,7 @@ func (svc *Service) BatchSetMDMProfiles(
|
||||
}
|
||||
var labelMap map[string]fleet.ConfigurationProfileLabel
|
||||
if !dryRun {
|
||||
labelMap, err = svc.batchValidateProfileLabels(ctx, labels)
|
||||
labelMap, err = svc.batchValidateProfileLabels(ctx, tmID, labels)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "validating labels")
|
||||
}
|
||||
|
||||
@@ -2360,6 +2360,19 @@ func TestBatchSetMDMProfilesLabels(t *testing.T) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
ds.LabelsByNameFunc = func(ctx context.Context, names []string) (map[string]*fleet.Label, error) {
|
||||
m := map[string]*fleet.Label{}
|
||||
for _, name := range names {
|
||||
if name != "baddy" {
|
||||
labelID++
|
||||
m[name] = &fleet.Label{
|
||||
ID: labelID,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
ds.ValidateEmbeddedSecretsFunc = func(ctx context.Context, documents []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -281,6 +281,10 @@ func (svc *Service) NewQuery(ctx context.Context, p fleet.QueryPayload) (*fleet.
|
||||
})
|
||||
}
|
||||
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, p.TeamID, p.LabelsIncludeAny); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
query := &fleet.Query{
|
||||
Saved: true,
|
||||
|
||||
@@ -417,6 +421,11 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo
|
||||
})
|
||||
}
|
||||
|
||||
// We use query.TeamID because we do not allow changing the team
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, query.TeamID, p.LabelsIncludeAny); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
shouldDiscardQueryResults, shouldDeleteStats := false, false
|
||||
|
||||
if p.Name != nil {
|
||||
|
||||
@@ -210,7 +210,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
||||
|
||||
t.Run("validate no update", func(t *testing.T) {
|
||||
t.Run("no auth context", func(t *testing.T) {
|
||||
_, err := eeservice.ValidateSoftwareLabels(context.Background(), svc, nil, nil)
|
||||
_, err := eeservice.ValidateSoftwareLabels(context.Background(), svc, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
@@ -218,7 +218,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
||||
ctx = authz_ctx.NewContext(ctx, &authCtx)
|
||||
|
||||
t.Run("no auth checked", func(t *testing.T) {
|
||||
_, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, nil)
|
||||
_, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
@@ -244,6 +244,21 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
ds.LabelsByNameFunc = func(ctx context.Context, names []string) (map[string]*fleet.Label, error) {
|
||||
res := make(map[string]*fleet.Label)
|
||||
if names == nil {
|
||||
return res, nil
|
||||
}
|
||||
for _, name := range names {
|
||||
if id, ok := mockLabels[name]; ok {
|
||||
res[name] = &fleet.Label{
|
||||
ID: id,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -328,7 +343,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := eeservice.ValidateSoftwareLabels(ctx, svc, tt.payloadIncludeAny, tt.payloadExcludeAny)
|
||||
got, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, tt.payloadIncludeAny, tt.payloadExcludeAny)
|
||||
if tt.expectError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectError)
|
||||
|
||||
@@ -90,6 +90,11 @@ func (svc Service) NewTeamPolicy(ctx context.Context, teamID uint, tp fleet.NewT
|
||||
Message: fmt.Sprintf("policy payload verification: %s", err),
|
||||
})
|
||||
}
|
||||
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, &teamID, append(tp.LabelsIncludeAny, tp.LabelsExcludeAny...)); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
policy, err := svc.ds.NewTeamPolicy(ctx, teamID, ptr.Uint(vc.UserID()), p)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "creating policy")
|
||||
@@ -565,6 +570,10 @@ func (svc *Service) modifyPolicy(ctx context.Context, teamID *uint, id uint, p f
|
||||
})
|
||||
}
|
||||
|
||||
if err := verifyLabelsToAssociate(ctx, svc.ds, teamID, append(p.LabelsIncludeAny, p.LabelsExcludeAny...)); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "verify labels to associate")
|
||||
}
|
||||
|
||||
var removeAllMemberships bool
|
||||
var removeStats bool
|
||||
if p.Name != nil {
|
||||
|
||||
@@ -148,7 +148,16 @@ func (ts *withServer) commonTearDownTest(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = q.ExecContext(ctx, "DELETE FROM in_house_apps;")
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.ExecContext(ctx, "DELETE FROM vpp_apps;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
lbls, err := ts.ds.ListLabels(ctx, fleet.TeamFilter{}, fleet.ListOptions{})
|
||||
|
||||
@@ -61,7 +61,7 @@ func (svc *Service) NewMDMWindowsConfigProfile(ctx context.Context, teamID uint,
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate profile")
|
||||
}
|
||||
|
||||
labelMap, err := svc.validateProfileLabels(ctx, labels)
|
||||
labelMap, err := svc.validateProfileLabels(ctx, &teamID, labels)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating labels")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user