check for DEP capable migration before showing automatic preview (#41274)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #39252

I couldn't find any other code path that would resolve to automatic
other than the device not being MDM enrolled, and we naively assumed 3
lines for manual migration, so I added the new IsDEPCapable method which
checks if the first line returned by profiles status is No or Yes, to
check if the device was enrolled via ABM/DEP, if not and not MDM
enrolled then show the manual.

# Checklist for submitter

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

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

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## fleetd/orbit/Fleet Desktop

- [x] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [x] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [x] Verified that fleetd runs on macOS, Linux and Windows
- [x] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))
This commit is contained in:
Magnus Jensen 2026-03-10 21:51:29 +02:00 committed by GitHub
parent bae3f022e2
commit 4149c22ae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 13 deletions

View file

@ -0,0 +1 @@
* Fixed an issue where the automatic migration preview would show when the device was not MDM-enrolled and not enrolled via DEP.

View file

@ -127,7 +127,10 @@ func IsEnrolledInMDM() (bool, string, error) {
return true, enrollmentURL, nil
}
func IsManuallyEnrolledInMDM() (bool, error) {
// ParseMDMEnrollmentStatus runs the `profiles` command to get the current MDM
// enrollment information and reports if the host is enrolled via DEP or not.
// Which is used to check for manual enrollment or not.
func ParseMDMEnrollmentStatus() (enrolledViaDEP bool, err error) {
out, err := getMDMInfoFromProfilesCmd()
if err != nil {
return false, fmt.Errorf("calling /usr/bin/profiles: %w", err)
@ -141,21 +144,21 @@ func IsManuallyEnrolledInMDM() (bool, error) {
// MDM server: https://test.example.com/mdm/apple/mdm
// ```
//
// If the host is not enrolled into an MDM, the last line is ommitted,
// If the host is not enrolled into an MDM, the last line is omitted,
// so we need to check that:
//
// 1. We've got three rows
// 2. Whether the first line contains "Yes" or "No"
// 1. We've got two rows
// 2. The first row contains "Yes" or "No"
lines := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
if len(lines) < 3 {
return false, nil
if len(lines) < 2 {
return false, fmt.Errorf("Got %d lines of output, when expected at least 2 or more", len(lines))
}
if strings.Contains(string(lines[0]), "Yes") {
return false, nil
return true, nil
}
return true, nil
return false, nil
}
// getMDMInfoFromProfilesCmd is declared as a variable so it can be overwritten by tests.

View file

@ -327,6 +327,45 @@ MDM server: https://valid.com/mdm/apple/mdm
}
}
func TestParseMDMEnrollmentStatus(t *testing.T) {
cases := []struct {
cmdOut *string
cmdErr error
want bool
wantErr bool
}{
{nil, errors.New("test error"), false, true},
{ptr.String(""), nil, false, true},
{ptr.String("Enrolled via DEP: No\nMDM Enrollment: No"), nil, false, false},
{ptr.String("Enrolled via DEP: Yes\nMDM Enrollment: No"), nil, true, false},
{ptr.String("Enrolled via DEP: No\nMDM Enrollment: Yes (User Approved)"), nil, false, false},
{ptr.String("Enrolled via DEP: No\nMDM Enrollment: Yes (User Approved)\nMDM Server: https://mdm.example.com"), nil, false, false},
{ptr.String("Enrolled via DEP: Yes\nMDM Enrollment: Yes\nMDM Server: https://mdm.example.com"), nil, true, false},
}
origCmd := getMDMInfoFromProfilesCmd
t.Cleanup(func() { getMDMInfoFromProfilesCmd = origCmd })
for _, c := range cases {
getMDMInfoFromProfilesCmd = func() ([]byte, error) {
if c.cmdOut == nil {
return nil, c.cmdErr
}
var buf bytes.Buffer
buf.WriteString(*c.cmdOut)
return buf.Bytes(), nil
}
got, err := ParseMDMEnrollmentStatus()
if c.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, c.want, got)
}
}
func TestCheckAssignedEnrollmentProfile(t *testing.T) {
fleetURL := "https://valid.com"
cases := []struct {

View file

@ -406,7 +406,7 @@ func (m *swiftDialogMDMMigrator) waitForUnenrollment(isADEMigration bool) error
func (m *swiftDialogMDMMigrator) renderMigration() error {
log.Debug().Msg("checking current enrollment status")
isCurrentlyManuallyEnrolled, err := profiles.IsManuallyEnrolledInMDM()
enrolledViaDEP, err := profiles.ParseMDMEnrollmentStatus()
if err != nil {
return err
}
@ -418,10 +418,10 @@ func (m *swiftDialogMDMMigrator) renderMigration() error {
return fmt.Errorf("getting migration type: %w", err)
}
isManualMigration := isCurrentlyManuallyEnrolled || previousMigrationType == constant.MDMMigrationTypeManual
isManualMigration := !enrolledViaDEP || previousMigrationType == constant.MDMMigrationTypeManual
isADEMigration := previousMigrationType == constant.MDMMigrationTypeADE
log.Debug().Bool("isManualMigration", isManualMigration).Bool("isADEMigration", isADEMigration).Bool("isCurrentlyManuallyEnrolled", isCurrentlyManuallyEnrolled).Str("previousMigrationType", previousMigrationType).Msg("props after assigning")
log.Debug().Bool("isManualMigration", isManualMigration).Bool("isADEMigration", isADEMigration).Bool("enrolledViaDEP", enrolledViaDEP).Str("previousMigrationType", previousMigrationType).Msg("props after assigning")
vers, err := m.getMacOSMajorVersion()
if err != nil {
@ -465,7 +465,7 @@ func (m *swiftDialogMDMMigrator) renderMigration() error {
if !m.props.IsUnmanaged {
// show the loading spinner
m.renderLoadingSpinner(isPreSonoma, isCurrentlyManuallyEnrolled)
m.renderLoadingSpinner(isPreSonoma, isManualMigration)
// send the API call
if notifyErr := m.handler.NotifyRemote(); notifyErr != nil {
@ -498,7 +498,7 @@ func (m *swiftDialogMDMMigrator) renderMigration() error {
switch {
case isPreSonoma:
if err := m.mrw.SetMigrationFile(constant.MDMMigrationTypePreSonoma); err != nil {
log.Error().Str("migration_type", constant.MDMMigrationTypeADE).Err(err).Msg("set migration file")
log.Error().Str("migration_type", constant.MDMMigrationTypePreSonoma).Err(err).Msg("set migration file")
}
log.Info().Msg("showing instructions after pre-sonoma unenrollment")