fleet/ee/server/service/mdm_external_test.go
Victor Lyuboslavsky 9d24f20c98
Added support of $FLEET_VAR_HOST_UUID in Windows MDM configuration profiles (#31695)
Fixes #30879 

Demo video: https://www.youtube.com/watch?v=jVyh5x8EMnc

I added a `FleetVarName` type, which should improve
safety/maintainability, but that resulted in a lot of files touched.

I also added the following. However, these are not strictly needed for
this feature (only useful for debug right now). But we are following the
pattern created by MDM team.

  1. Add the migration to insert HOST_UUID into fleet_variables
2. Update the Windows profile save logic to populate
mdm_configuration_profile_variables


# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host isolation]
- [x] QA'd all new/changed functionality manually



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Added support for the `$FLEET_VAR_HOST_UUID` variable in Windows MDM
configuration profiles, enabling per-host customization during profile
deployment.
* Enhanced profile delivery by substituting Fleet variables with actual
host data in Windows profiles.
* Introduced a database migration to register the new Fleet variable for
host UUID.

* **Bug Fixes**
* Improved validation and error handling to reject unsupported Fleet
variables in Windows MDM profiles with detailed messages.
* Ensured robust handling of errors during profile command insertion
without aborting the entire reconciliation process.

* **Tests**
* Added extensive tests covering validation, substitution, error
handling, and reconciliation workflows for Windows MDM profiles using
Fleet variables.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-10 12:24:38 +02:00

619 lines
25 KiB
Go

package service_test
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/WatchBeam/clock"
eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
"github.com/fleetdm/fleet/v4/pkg/optjson"
"github.com/fleetdm/fleet/v4/server/config"
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
mdmtesting "github.com/fleetdm/fleet/v4/server/mdm/testing_utils"
"github.com/fleetdm/fleet/v4/server/mock"
nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/fleetdm/fleet/v4/server/worker"
kitlog "github.com/go-kit/log"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func setupMockDatastorePremiumService(t testing.TB) (*mock.Store, *eeservice.Service, context.Context) {
ds := new(mock.Store)
lic := &fleet.LicenseInfo{Tier: fleet.TierPremium}
ctx := license.NewContext(context.Background(), lic)
logger := kitlog.NewNopLogger()
fleetConfig := config.FleetConfig{
MDM: config.MDMConfig{
AppleSCEPCertBytes: eeservice.TestCert,
AppleSCEPKeyBytes: eeservice.TestKey,
},
Server: config.ServerConfig{
PrivateKey: "foo",
},
}
depStorage := &nanodep_mock.Storage{}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.Contains(r.URL.Path, "/server/devices"):
_, err := w.Write([]byte("{}"))
require.NoError(t, err)
case strings.Contains(r.URL.Path, "/session"):
_, err := w.Write([]byte(`{"auth_session_token": "yoo"}`))
require.NoError(t, err)
case strings.Contains(r.URL.Path, "/profile"):
_, err := w.Write([]byte(`{"profile_uuid": "profile123"}`))
require.NoError(t, err)
}
}))
depStorage.RetrieveConfigFunc = func(context.Context, string) (*nanodep_client.Config, error) {
return &nanodep_client.Config{
BaseURL: ts.URL,
}, nil
}
depStorage.RetrieveAuthTokensFunc = func(ctx context.Context, name string) (*nanodep_client.OAuth1Tokens, error) {
return &nanodep_client.OAuth1Tokens{}, nil
}
t.Cleanup(func() { ts.Close() })
freeSvc, err := service.NewService(
ctx,
ds,
nil,
nil,
logger,
nil,
fleetConfig,
nil,
clock.C,
nil,
nil,
ds,
nil,
&fleet.NoOpGeoIP{},
nil,
depStorage,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
)
if err != nil {
panic(err)
}
svc, err := eeservice.NewService(
freeSvc,
ds,
logger,
fleetConfig,
nil,
clock.C,
depStorage,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
)
if err != nil {
panic(err)
}
return ds, svc, ctx
}
func TestGetOrCreatePreassignTeam(t *testing.T) {
ds, svc, ctx := setupMockDatastorePremiumService(t)
ssoSettings := fleet.SSOProviderSettings{
EntityID: "foo",
MetadataURL: "https://example.com/metadata.xml",
}
appConfig := &fleet.AppConfig{MDM: fleet.MDM{
EnabledAndConfigured: true,
EndUserAuthentication: fleet.MDMEndUserAuthentication{SSOProviderSettings: ssoSettings},
MacOSSetup: fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("https://example.com/bootstrap.pkg"),
EnableEndUserAuthentication: true,
EnableReleaseDeviceManually: optjson.SetBool(true),
},
}}
preassignGroups := []string{"one", "three"}
// initialize team store with team one and two already created, one matches
// preassign group [0], two does not match any preassign group
team1 := &fleet.Team{
ID: 1,
Name: preassignGroups[0],
}
team2 := &fleet.Team{
ID: 2,
Name: "two",
Config: fleet.TeamConfig{
MDM: fleet.TeamMDM{
MacOSSetup: fleet.MacOSSetup{MacOSSetupAssistant: optjson.SetString("foo/bar")},
},
},
}
teamStore := map[uint]*fleet.Team{1: team1, 2: team2}
resetInvoked := func() {
ds.TeamByNameFuncInvoked = false
ds.NewTeamFuncInvoked = false
ds.SaveTeamFuncInvoked = false
ds.NewMDMAppleConfigProfileFuncInvoked = false
ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked = false
ds.AppConfigFuncInvoked = false
ds.NewJobFuncInvoked = false
ds.GetMDMAppleSetupAssistantFuncInvoked = false
ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false
ds.LabelIDsByNameFuncInvoked = false
ds.SetOrUpdateMDMAppleDeclarationFuncInvoked = false
ds.BulkSetPendingMDMHostProfilesFuncInvoked = false
ds.GetAllMDMConfigAssetsByNameFuncInvoked = false
}
setupDS := func(t *testing.T) {
resetInvoked()
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return appConfig, nil
}
ds.NewActivityFunc = func(ctx context.Context, u *fleet.User, a fleet.ActivityDetails, details []byte, createdAt time.Time) error {
return nil
}
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
for _, team := range teamStore {
if team.Name == name {
return team, nil
}
}
return nil, ctxerr.Wrap(ctx, &eeservice.NotFoundError{})
}
ds.TeamFunc = func(ctx context.Context, id uint) (*fleet.Team, error) {
tm, ok := teamStore[id]
if !ok {
return nil, errors.New("team id not found")
}
if id != tm.ID {
// sanity chec
return nil, errors.New("team id mismatch")
}
return tm, nil
}
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
return nil, errors.New("not implemented")
}
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
return nil, errors.New("not implemented")
}
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, profile fleet.MDMAppleConfigProfile, vars []fleet.FleetVarName) (*fleet.MDMAppleConfigProfile, error) {
return nil, errors.New("not implemented")
}
ds.DeleteMDMAppleConfigProfileByTeamAndIdentifierFunc = func(ctx context.Context, teamID *uint, profileIdentifier string) error {
return errors.New("not implemented")
}
ds.CopyDefaultMDMAppleBootstrapPackageFunc = func(ctx context.Context, ac *fleet.AppConfig, toTeamID uint) error {
return errors.New("not implemented")
}
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
return nil, errors.New("not implemented")
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, errors.New("not implemented")
}
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string) (map[string]uint, error) {
require.Len(t, names, 1)
require.ElementsMatch(t, names, []string{fleet.BuiltinLabelMacOS14Plus})
return map[string]uint{names[0]: 1}, nil
}
ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
declaration.DeclarationUUID = uuid.NewString()
return declaration, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
) (updates fleet.MDMProfilesUpdates, err error) {
return fleet.MDMProfilesUpdates{}, nil
}
apnsCert, apnsKey, err := mysql.GenerateTestCertBytes(mdmtesting.NewTestMDMAppleCertTemplate())
require.NoError(t, err)
certPEM, keyPEM, tokenBytes, err := mysql.GenerateTestABMAssets(t)
require.NoError(t, err)
ds.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName,
_ sqlx.QueryerContext,
) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
return map[fleet.MDMAssetName]fleet.MDMConfigAsset{
fleet.MDMAssetABMCert: {Name: fleet.MDMAssetABMCert, Value: certPEM},
fleet.MDMAssetABMKey: {Name: fleet.MDMAssetABMKey, Value: keyPEM},
fleet.MDMAssetABMTokenDeprecated: {Name: fleet.MDMAssetABMTokenDeprecated, Value: tokenBytes},
fleet.MDMAssetAPNSCert: {Name: fleet.MDMAssetAPNSCert, Value: apnsCert},
fleet.MDMAssetAPNSKey: {Name: fleet.MDMAssetAPNSKey, Value: apnsKey},
fleet.MDMAssetCACert: {Name: fleet.MDMAssetCACert, Value: certPEM},
fleet.MDMAssetCAKey: {Name: fleet.MDMAssetCAKey, Value: keyPEM},
}, nil
}
ds.GetMDMAppleEnrollmentProfileByTypeFunc = func(ctx context.Context, typ fleet.MDMAppleEnrollmentType) (*fleet.MDMAppleEnrollmentProfile, error) {
return &fleet.MDMAppleEnrollmentProfile{Token: "foobar"}, nil
}
ds.GetABMTokenOrgNamesAssociatedWithTeamFunc = func(ctx context.Context, teamID *uint) ([]string, error) {
return []string{"foobar"}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{{ID: 1}}, nil
}
ds.CountABMTokensWithTermsExpiredFunc = func(ctx context.Context) (int, error) {
return 0, nil
}
ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) {
return nil, &eeservice.NotFoundError{}
}
}
authzCtx := &authz_ctx.AuthorizationContext{}
ctx = authz_ctx.NewContext(ctx, authzCtx)
ctx = viewer.NewContext(ctx, viewer.Viewer{User: test.UserAdmin})
actx, _ := authz_ctx.FromContext(ctx)
actx.SetChecked()
t.Run("get preassign team", func(t *testing.T) {
setupDS(t)
// preasign group corresponds to existing team so simply get it
team, err := svc.GetOrCreatePreassignTeam(ctx, preassignGroups[0:1])
require.NoError(t, err)
require.Equal(t, uint(1), team.ID)
require.Equal(t, preassignGroups[0], team.Name)
require.True(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.NewTeamFuncInvoked)
require.False(t, ds.SaveTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.AppConfigFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
resetInvoked()
})
t.Run("create preassign team", func(t *testing.T) {
setupDS(t)
lastTeamID := uint(0)
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
for _, tm := range teamStore {
if tm.Name == team.Name {
return nil, errors.New("team name already exists")
}
}
id := uint(len(teamStore) + 1) //nolint:gosec // dismiss G115
_, ok := teamStore[id]
require.False(t, ok) // sanity check
team.ID = id
teamStore[id] = team
lastTeamID = id
return team, nil
}
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
tm, ok := teamStore[team.ID]
if !ok {
return nil, errors.New("invalid team id")
}
require.Equal(t, tm.ID, team.ID) // sanity check
require.Equal(t, tm.Name, team.Name) // sanity check
// NOTE: BootstrapPackage gets set by CopyDefaultMDMAppleBootstrapPackage
// require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // set to default
require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant, team.Config.MDM.MacOSSetup.MacOSSetupAssistant) // set to default
require.Equal(t, appConfig.MDM.MacOSSetup.EnableReleaseDeviceManually.Value, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value) // set to default
teamStore[tm.ID] = team
return team, nil
}
ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, profile fleet.MDMAppleConfigProfile, vars []fleet.FleetVarName) (*fleet.MDMAppleConfigProfile, error) {
require.Equal(t, lastTeamID, *profile.TeamID)
require.Equal(t, mobileconfig.FleetFileVaultPayloadIdentifier, profile.Identifier)
return &profile, nil
}
ds.DeleteMDMAppleConfigProfileByTeamAndIdentifierFunc = func(ctx context.Context, teamID *uint, profileIdentifier string) error {
require.Equal(t, lastTeamID, *teamID)
require.Equal(t, mobileconfig.FleetFileVaultPayloadIdentifier, profileIdentifier)
return nil
}
ds.CopyDefaultMDMAppleBootstrapPackageFunc = func(ctx context.Context, ac *fleet.AppConfig, toTeamID uint) error {
require.Equal(t, lastTeamID, toTeamID)
require.NotNil(t, ac)
require.Equal(t, "https://example.com/bootstrap.pkg", ac.MDM.MacOSSetup.BootstrapPackage.Value)
teamStore[toTeamID].Config.MDM.MacOSSetup.BootstrapPackage = optjson.SetString(ac.MDM.MacOSSetup.BootstrapPackage.Value)
return nil
}
var jobTask string
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
// first task is UpdateProfile, next is ProfileChanged (when setup assistant is set)
if jobTask == "" {
jobTask = string(worker.MacosSetupAssistantUpdateProfile)
} else {
jobTask = string(worker.MacosSetupAssistantProfileChanged)
}
wantArgs, err := json.Marshal(map[string]interface{}{
"task": jobTask,
"team_id": lastTeamID,
})
require.NoError(t, err)
wantJob := &fleet.Job{
Name: "macos_setup_assistant",
Args: (*json.RawMessage)(&wantArgs),
State: fleet.JobStateQueued,
}
require.Equal(t, wantJob.Name, job.Name)
require.Equal(t, string(*wantJob.Args), string(*job.Args))
require.Equal(t, wantJob.State, job.State)
return job, nil
}
setupAsstByTeam := make(map[uint]*fleet.MDMAppleSetupAssistant)
globalSetupAsst := &fleet.MDMAppleSetupAssistant{
ID: 15,
TeamID: nil,
Name: "test asst",
Profile: json.RawMessage(`{"foo": "bar"}`),
}
setupAsstByTeam[0] = globalSetupAsst
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
var tmID uint
if teamID != nil {
tmID = *teamID
}
asst := setupAsstByTeam[tmID]
if asst == nil {
return nil, eeservice.NotFoundError{}
}
return asst, nil
}
ds.SetOrUpdateMDMAppleSetupAssistantFunc = func(ctx context.Context, asst *fleet.MDMAppleSetupAssistant) (*fleet.MDMAppleSetupAssistant, error) {
require.Equal(t, globalSetupAsst.Name, asst.Name)
require.JSONEq(t, string(globalSetupAsst.Profile), string(asst.Profile))
require.NotNil(t, asst.TeamID)
require.EqualValues(t, lastTeamID, *asst.TeamID)
setupAsstByTeam[*asst.TeamID] = asst
return asst, nil
}
// new team ("one - three") is created with bootstrap package and end user auth based on app config
team, err := svc.GetOrCreatePreassignTeam(ctx, preassignGroups)
require.NoError(t, err)
require.Equal(t, uint(3), team.ID)
require.Equal(t, eeservice.TeamNameFromPreassignGroups(preassignGroups), team.Name)
require.True(t, ds.TeamByNameFuncInvoked)
require.True(t, ds.NewTeamFuncInvoked)
require.True(t, ds.SaveTeamFuncInvoked)
require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.True(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.True(t, ds.GetMDMAppleSetupAssistantFuncInvoked)
require.True(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked)
require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant.Value, team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value)
require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.True(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableReleaseDeviceManually.Value, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
require.True(t, ds.NewJobFuncInvoked)
resetInvoked()
jobTask = ""
// when called again, simply get the previously created team
team, err = svc.GetOrCreatePreassignTeam(ctx, preassignGroups)
require.NoError(t, err)
require.Equal(t, uint(3), team.ID)
require.Equal(t, eeservice.TeamNameFromPreassignGroups(preassignGroups), team.Name)
require.True(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.NewTeamFuncInvoked)
require.False(t, ds.SaveTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.AppConfigFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
require.False(t, ds.GetMDMAppleSetupAssistantFuncInvoked)
require.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked)
require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant.Value, team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value)
require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.True(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableReleaseDeviceManually.Value, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
resetInvoked()
jobTask = ""
// when a custom setup assistant is not set for "no team", we don't create
// a custom setup assistant
setupAsstByTeam[0] = nil
preassignGrousWithFoo := preassignGroups
preassignGrousWithFoo = append(preassignGrousWithFoo, "foo")
team, err = svc.GetOrCreatePreassignTeam(ctx, preassignGrousWithFoo)
require.NoError(t, err)
require.Equal(t, uint(4), team.ID)
require.Equal(t, eeservice.TeamNameFromPreassignGroups(preassignGrousWithFoo), team.Name)
require.True(t, ds.TeamByNameFuncInvoked)
require.True(t, ds.NewTeamFuncInvoked)
require.True(t, ds.SaveTeamFuncInvoked)
require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.True(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.True(t, ds.GetMDMAppleSetupAssistantFuncInvoked)
require.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked)
require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
require.True(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
require.Equal(t, appConfig.MDM.MacOSSetup.EnableReleaseDeviceManually.Value, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value)
resetInvoked()
})
t.Run("modify team via apply team spec", func(t *testing.T) {
setupDS(t)
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
tm, ok := teamStore[team.ID]
if !ok {
return nil, errors.New("invalid team id")
}
require.Equal(t, tm.ID, team.ID) // sanity check
require.Equal(t, tm.Name, team.Name) // sanity check
require.Empty(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // not modified
require.Empty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) // not modified
require.False(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value) // not modified
return teamStore[tm.ID], nil
}
// apply team spec does not apply defaults
spec := &fleet.TeamSpec{
Name: team2.Name,
}
_, err := svc.ApplyTeamSpecs(ctx, []*fleet.TeamSpec{spec}, fleet.ApplyTeamSpecOptions{})
require.NoError(t, err)
require.True(t, ds.SaveTeamFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.True(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.NewTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
resetInvoked()
})
t.Run("new team", func(t *testing.T) {
setupDS(t)
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
for _, tm := range teamStore {
if tm.Name == team.Name {
return nil, errors.New("team name already exists")
}
}
id := uint(len(teamStore) + 1) //nolint:gosec // dismiss G115
_, ok := teamStore[id]
require.False(t, ok) // sanity check
require.Equal(t, "new team", team.Name)
require.Equal(t, "new description", team.Description)
require.False(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // not set
require.Empty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) // not set
require.False(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value) // not set
team.ID = id
teamStore[id] = team
return team, nil
}
// new team does not apply defaults
_, err := svc.NewTeam(ctx, fleet.TeamPayload{
Name: ptr.String("new team"),
Description: ptr.String("new description"),
})
require.NoError(t, err)
require.True(t, ds.NewTeamFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.False(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.SaveTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
resetInvoked()
})
t.Run("apply team spec", func(t *testing.T) {
setupDS(t)
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
for _, tm := range teamStore {
if tm.Name == team.Name {
return nil, errors.New("team name already exists")
}
}
id := uint(len(teamStore) + 1) //nolint:gosec // dismiss G115
_, ok := teamStore[id]
require.False(t, ok) // sanity check
require.Equal(t, "new team spec", team.Name) // set
require.Equal(t, "12.0", team.Config.MDM.MacOSUpdates.MinimumVersion.Value) // set
require.Equal(t, "2024-01-01", team.Config.MDM.MacOSUpdates.Deadline.Value) // set
require.False(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // not set
require.Empty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) // not set
require.False(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value) // not set
team.ID = id
teamStore[id] = team
return team, nil
}
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
tm, ok := teamStore[team.ID]
if !ok {
return nil, errors.New("invalid team id")
}
require.Equal(t, tm.ID, team.ID) // sanity check
require.Equal(t, tm.Name, team.Name) // sanity check
require.Equal(t, "12.0", team.Config.MDM.MacOSUpdates.MinimumVersion.Value) // unchanged
require.Equal(t, "2025-01-01", team.Config.MDM.MacOSUpdates.Deadline.Value) // modified
require.Empty(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // not set
require.Empty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) // not set
require.False(t, team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Value) // not set
return teamStore[tm.ID], nil
}
spec := &fleet.TeamSpec{
Name: "new team spec",
MDM: fleet.TeamSpecMDM{
MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.0"),
Deadline: optjson.SetString("2024-01-01"),
},
},
}
// apply team spec creates new team without defaults
_, err := svc.ApplyTeamSpecs(ctx, []*fleet.TeamSpec{spec}, fleet.ApplyTeamSpecOptions{})
require.NoError(t, err)
require.True(t, ds.NewTeamFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.True(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.SaveTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
resetInvoked()
// apply team spec edits existing team without applying defaults
spec.MDM.MacOSUpdates.Deadline = optjson.SetString("2025-01-01")
_, err = svc.ApplyTeamSpecs(ctx, []*fleet.TeamSpec{spec}, fleet.ApplyTeamSpecOptions{})
require.NoError(t, err)
require.True(t, ds.SaveTeamFuncInvoked)
require.True(t, ds.AppConfigFuncInvoked)
require.True(t, ds.TeamByNameFuncInvoked)
require.False(t, ds.NewTeamFuncInvoked)
require.False(t, ds.NewMDMAppleConfigProfileFuncInvoked)
require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked)
require.False(t, ds.NewJobFuncInvoked)
resetInvoked()
})
}