allow turning off mdm for iphone and ipad hosts (#29087)

For [#23784](https://github.com/fleetdm/fleet/issues/23784)

This adds the "turn off mdm" option don't he host details page for
iPhone and iPad devices.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [ ] Added/updated automated tests
- [ ] Manual QA for all new/changed functionality
This commit is contained in:
Gabriel Hernandez 2025-05-15 12:38:07 +01:00 committed by GitHub
parent 5815dc4e54
commit 63b2e19630
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 53 additions and 37 deletions

View file

@ -0,0 +1 @@
- add ability to turn off mdm for iphone and ipad hosts on the hosts details page

View file

@ -1230,7 +1230,6 @@ describe("Host Actions Dropdown", () => {
expect(
screen.queryByText("Show disk encryption key")
).not.toBeInTheDocument();
expect(screen.queryByText("Turn off MDM")).not.toBeInTheDocument();
expect(screen.queryByText("Lock")).not.toBeInTheDocument();
});
@ -1270,7 +1269,6 @@ describe("Host Actions Dropdown", () => {
expect(
screen.queryByText("Show disk encryption key")
).not.toBeInTheDocument();
expect(screen.queryByText("Turn off MDM")).not.toBeInTheDocument();
expect(screen.queryByText("Lock")).not.toBeInTheDocument();
});
});

View file

@ -7,6 +7,7 @@ import {
isAppleDevice,
isMobilePlatform,
isAndroid,
isIPadOrIPhone,
} from "interfaces/platform";
import { isScriptSupportedPlatform } from "interfaces/script";
@ -89,7 +90,7 @@ const canTransferTeam = (config: IHostActionConfigOptions) => {
return isPremiumTier && (isGlobalAdmin || isGlobalMaintainer);
};
const canEditMdm = (config: IHostActionConfigOptions) => {
const canTurnOffMdm = (config: IHostActionConfigOptions) => {
const {
hostPlatform,
isGlobalAdmin,
@ -102,7 +103,7 @@ const canEditMdm = (config: IHostActionConfigOptions) => {
} = config;
return (
!isAndroid(hostPlatform) && // TODO(android): confirm can't turn off MDM for windows, iOS, iPadOS?
hostPlatform === "darwin" &&
isAppleDevice(hostPlatform) &&
isMacMdmEnabledAndConfigured &&
isEnrolledInMdm &&
isConnectedToFleetMdm &&
@ -257,7 +258,7 @@ const removeUnavailableOptions = (
options = options.filter((option) => option.value !== "diskEncryption");
}
if (!canEditMdm(config)) {
if (!canTurnOffMdm(config)) {
options = options.filter((option) => option.value !== "mdmOff");
}
@ -339,7 +340,7 @@ const modifyOptions = (
let optionsToDisable: IDropdownOption[] = [];
if (
!isHostOnline ||
(!isIPadOrIPhone(hostPlatform) && !isHostOnline) ||
isDeviceStatusUpdating(hostMdmDeviceStatus) ||
hostMdmDeviceStatus === "locked" ||
hostMdmDeviceStatus === "wiped"
@ -386,7 +387,6 @@ const modifyOptions = (
* many variations of the options that are shown/not shown or disabled/enabled
* which are all controlled by the configurations options argument.
*/
// eslint-disable-next-line import/prefer-default-export
export const generateHostActionOptions = (config: IHostActionConfigOptions) => {
// deep clone to always start with a fresh copy of the default options.
let options: IDropdownOption[] = cloneDeep([...DEFAULT_OPTIONS]);

View file

@ -1160,7 +1160,12 @@ const HostDetailsPage = ({
/>
)}
{showUnenrollMdmModal && !!host && (
<UnenrollMdmModal hostId={host.id} onClose={toggleUnenrollMdmModal} />
<UnenrollMdmModal
hostId={host.id}
hostPlatform={host.platform}
hostName={host.display_name}
onClose={toggleUnenrollMdmModal}
/>
)}
{showDiskEncryptionModal && host && (
<DiskEncryptionKeyModal

View file

@ -6,15 +6,24 @@ import Modal from "components/Modal";
import { NotificationContext } from "context/notification";
import mdmAPI from "services/entities/mdm";
import { isIPadOrIPhone } from "interfaces/platform";
import CustomLink from "components/CustomLink";
interface IUnenrollMdmModalProps {
hostId: number;
hostPlatform: string;
hostName: string;
onClose: () => void;
}
const baseClass = "unenroll-mdm-modal";
const UnenrollMdmModal = ({ hostId, onClose }: IUnenrollMdmModalProps) => {
const UnenrollMdmModal = ({
hostId,
hostPlatform,
hostName,
onClose,
}: IUnenrollMdmModalProps) => {
const [requestState, setRequestState] = useState<
undefined | "unenrolling" | "error"
>(undefined);
@ -27,28 +36,50 @@ const UnenrollMdmModal = ({ hostId, onClose }: IUnenrollMdmModalProps) => {
await mdmAPI.unenrollHostFromMdm(hostId, 5000);
renderFlash(
"success",
"Turning off MDM or will turn off when the host comes online."
<>
MDM will be turned off for <b>{hostName}</b> next time this host
checks in.
</>
);
onClose();
} catch (unenrollMdmError: unknown) {
renderFlash("error", "Couldn't turn off MDM. Please try again.");
console.log(unenrollMdmError);
onClose();
renderFlash(
"error",
<>
Failed to turn off MDM for <b>{hostName}</b>.
</>
);
}
onClose();
};
const renderModalContent = () => {
if (requestState === "error") {
return <DataError />;
}
const turnOnMDMInstructions = isIPadOrIPhone(hostPlatform) ? (
<>
invite the end user to{" "}
<CustomLink
text="enroll a BYOD iPhone or iPad"
url="https://fleetdm.com/guides/enroll-byod-ios-ipados-hosts"
newTab
/>
</>
) : (
<>
ask the device user to follow the <b>Turn on MDM</b> instructions on
their <b>My device</b> page.
</>
);
return (
<>
<p className={`${baseClass}__description`}>
Settings configured by Fleet will be removed.
<br />
<br />
To turn on MDM again, ask the device user to follow the{" "}
<b>Turn on MDM</b> instructions on their <b>My device</b> page.
To turn on MDM again, {turnOnMDMInstructions}
</p>
<div className="modal-cta-wrap">
<Button
@ -72,7 +103,7 @@ const UnenrollMdmModal = ({ hostId, onClose }: IUnenrollMdmModalProps) => {
title="Turn off MDM"
onExit={onClose}
className={baseClass}
width="large"
width="medium"
>
{renderModalContent()}
</Modal>

View file

@ -27,7 +27,6 @@ var (
AppleMDMNotConfiguredMessage = "macOS MDM isn't turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM."
AppleABMDefaultTeamDeprecatedMessage = "mdm.apple_bm_default_team has been deprecated. Please use the new mdm.apple_business_manager key documented here: https://fleetdm.com/learn-more-about/apple-business-manager-gitops"
CantTurnOffMDMForWindowsHostsMessage = "Can't turn off MDM for Windows hosts."
CantTurnOffMDMForIOSOrIPadOSMessage = "Can't turn off MDM for iOS or iPadOS hosts. Use wipe instead."
)
// ErrWithStatusCode is an interface for errors that should set a specific HTTP

View file

@ -1995,12 +1995,6 @@ func (svc *Service) EnqueueMDMAppleCommandRemoveEnrollmentProfile(ctx context.Co
}
switch h.Platform {
case "ios":
fallthrough
case "ipados":
return &fleet.BadRequestError{
Message: fleet.CantTurnOffMDMForIOSOrIPadOSMessage,
}
case "windows":
return &fleet.BadRequestError{
Message: fleet.CantTurnOffMDMForWindowsHostsMessage,

View file

@ -14972,20 +14972,8 @@ func (s *integrationMDMTestSuite) TestWindowsMigrationEnabled() {
func (s *integrationMDMTestSuite) TestHostsCantTurnMDMOff() {
t := s.T()
iOSHost, _ := s.createAppleMobileHostThenEnrollMDM("ios")
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), iOSHost.ID, false, true, "https://foo.com", true, "", ""))
r := s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/hosts/%d/mdm", iOSHost.ID), nil, http.StatusBadRequest)
require.Contains(t, extractServerErrorText(r.Body), fleet.CantTurnOffMDMForIOSOrIPadOSMessage)
iPadOSHost, _ := s.createAppleMobileHostThenEnrollMDM("ipados")
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), iPadOSHost.ID, false, true, "https://foo.com", true, "", ""))
r = s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/hosts/%d/mdm", iPadOSHost.ID), nil, http.StatusBadRequest)
require.Contains(t, extractServerErrorText(r.Body), fleet.CantTurnOffMDMForIOSOrIPadOSMessage)
winHost, _ := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t)
r = s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/hosts/%d/mdm", winHost.ID), nil, http.StatusBadRequest)
r := s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/hosts/%d/mdm", winHost.ID), nil, http.StatusBadRequest)
require.Contains(t, extractServerErrorText(r.Body), fleet.CantTurnOffMDMForWindowsHostsMessage)
}