Files
fleet/ee/server/service/mdm_external_test.go
Rajendra Kadam 25cfac309c Let an edit clear a declaration's activation (#50711)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #

Raised by the frontend while building the Edit modal: there was no way
to clear a declaration's custom activation. An absent `activation` field
meant "keep it" on a labels-only edit but "delete it" when the profile
contents were replaced, so clearing wasn't expressible and an ordinary
content edit silently dropped the activation.

The field is now three-state:

| Request | Result |
|---|---|
| no `activation` key | stored activation left alone |
| `activation` as an empty value | removed |
| `activation` as a file | replaced |

Multipart has no null, so an empty value stands in for one.

Note this changes one existing behaviour: replacing a profile's contents
without sending an activation used to delete it, and now preserves it.
Removal has to be explicit.

Anything ambiguous is rejected rather than guessed at, since every
ambiguous form would otherwise resolve to deleting the stored
activation:

| Request | Result |
|---|---|
| `activation` as a nonempty value | 422 — more likely a malformed
upload than a request to delete |
| `activation` as a zero-byte file | 422 — a failed upload shouldn't
delete anything |
| `activation` sent as both a file and a value | 422 — one says replace,
the other says remove |

The unsupported-profile check also keys on the field being present
rather than on it carrying content, so clearing an activation on a
Windows, Android or mobileconfig profile is rejected instead of quietly
succeeding.

On the datastore side, `SetOrUpdateMDMAppleDeclaration` now takes an
explicit action (`MDMAppleActivationKeep` / `MDMAppleActivationApply`)
instead of inferring intent from the struct. The write is a full
replace, so "keep" has to be stated — otherwise preserving the
activation would mean reading it back and handing it to the write, which
also risked dropping its Fleet variable associations. As a side effect
the OS updates cron no longer fires a DELETE for an activation it never
had.

# Checklist for submitter

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

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

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests

- [ ] QA'd all new/changed functionality manually

Integration test covers all three states end to end through the
multipart decoder, plus service-level tests for preserve and explicit
removal.


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

* **New Features**
* Apple MDM declaration updates now support preserving, replacing, or
explicitly removing activation settings.
* Omitted activation fields leave existing settings unchanged, while
empty fields remove them.
  * Apple OS update declarations retain activation settings by default.

* **Bug Fixes**
* Labels-only updates no longer unintentionally carry forward activation
data.
* Invalid, empty, or conflicting activation uploads now receive clear
validation errors.
  * Unsupported profile types now reject activation updates.

* **Tests**
* Added coverage for activation preservation, replacement, removal, and
integration scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-07 15:36:31 +05:30

649 lines
26 KiB
Go

package service_test
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"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/mysqltest"
"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"
"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 := slog.New(slog.DiscardHandler)
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,
nil,
nil,
nil,
)
if err != nil {
panic(err)
}
// Using a noop activity service since this test does not currently verify activity creation.
freeSvc.SetActivityService(&mock.MockActivityService{
NewActivityFunc: mock.NoopNewActivityFunc,
})
svc, err := eeservice.NewService(
freeSvc,
ds,
logger,
fleetConfig,
nil,
clock.C,
depStorage,
nil,
nil,
nil,
nil,
nil,
nil,
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.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.TeamConflictsWithNameFunc = func(ctx context.Context, name string, excludeID uint) (*fleet.Team, error) {
for _, team := range teamStore {
if team.ID == excludeID {
continue
}
if strings.EqualFold(team.Name, name) {
return team, nil
}
}
return nil, nil
}
ds.TeamWithExtrasFunc = 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, filter fleet.TeamFilter) (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, usesFleetVars []fleet.FleetVarName, activationAction fleet.MDMAppleActivationAction) (*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 := mysqltest.GenerateTestCertBytes(mdmtesting.NewTestMDMAppleCertTemplate())
require.NoError(t, err)
certPEM, keyPEM, tokenBytes, err := mysqltest.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.SetABMTokenInvalidForOrgNameFunc = func(ctx context.Context, orgName string, invalid bool) (bool, error) {
return false, nil
}
ds.IsABMTokenInvalidForOrgNameFunc = func(ctx context.Context, orgName string) (bool, error) {
return false, 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
}
ds.HasAppleUpdateConfigProfileConfiguredFunc = func(ctx context.Context, teamID uint) (bool, error) {
return false, 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()
})
}