Update macOS disk encryption banner copy for ADE-enrolled hosts

Relates #47832

Both the Host details and My device pages told the reader to log out or
restart when Fleet didn't have a Mac's FileVault key. That's wrong for
ADE-enrolled hosts: they escrow the key automatically, and the only
thing standing between the host and a cleared banner is the next vitals
refetch. It stays correct for manual enrollment, where Escrow Buddy only
generates a new key at next login.
This commit is contained in:
Juan Fernandez
2026-07-28 15:33:26 -04:00
committed by GitHub
parent 07d4423c84
commit 360e4b74ff
5 changed files with 163 additions and 7 deletions
@@ -0,0 +1 @@
* Updated the macOS disk encryption banner on the Host details and My device pages to tell IT admins and end users that ADE-enrolled hosts escrow their FileVault key automatically on the next refetch, instead of asking the end user to log out.
@@ -7,6 +7,7 @@ import DeviceUserBanners from "./DeviceUserBanners";
describe("Device User Banners", () => {
const turnOnMdmExpcetedText = /Mobile device management \(MDM\) is off\./;
const resetNonLinuxDiskEncryptKeyExpectedText = /Disk encryption: Log out of your device or restart it to safeguard your data in case your device is lost or stolen\./;
const adeDiskEncryptKeyExpectedText = /Disk encryption: Refetch to ensure data is safeguarded in case your device is lost or stolen\. If this banner persists, contact your IT admin\./;
const createNewLinuxDiskEncryptKeyExpectedText = /Disk encryption: Create a new disk encryption key\. This lets your organization help you unlock your device if you forget your passphrase\./;
const createPINExepectedText = /Disk encryption: Create a BitLocker PIN to safeguard your data/;
@@ -31,7 +32,7 @@ describe("Device User Banners", () => {
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (automatic)"
mdmEnrollmentStatus="On (manual)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
@@ -45,6 +46,45 @@ describe("Device User Banners", () => {
screen.getByText(resetNonLinuxDiskEncryptKeyExpectedText)
).toBeInTheDocument();
});
it("renders the refetch disk encryption banner for ADE-enrolled hosts", () => {
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (automatic)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
diskEncryptionActionRequired="rotate_key"
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
onClickTurnOnMdm={noop}
/>
);
expect(screen.getByText(adeDiskEncryptKeyExpectedText)).toBeInTheDocument();
expect(
screen.queryByText(resetNonLinuxDiskEncryptKeyExpectedText)
).not.toBeInTheDocument();
});
// "On (company-owned)" is the current name for automatic enrollment; "On (automatic)"
// is the legacy value the API still returns
it("renders the refetch disk encryption banner for company-owned hosts", () => {
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (company-owned)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
diskEncryptionActionRequired="rotate_key"
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
onClickTurnOnMdm={noop}
/>
);
expect(screen.getByText(adeDiskEncryptKeyExpectedText)).toBeInTheDocument();
});
it("renders the create new linux disk encryption key banner correctly for Ubuntu", () => {
render(
<DeviceUserBanners
@@ -7,6 +7,7 @@ import { MacDiskEncryptionActionRequired } from "interfaces/host";
import { IHostBannersBaseProps } from "pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners";
import CustomLink from "components/CustomLink";
import { isDiskEncryptionSupportedLinuxPlatform } from "interfaces/platform";
import { isAutomaticDeviceEnrollment } from "interfaces/mdm";
const baseClass = "device-user-banners";
@@ -58,6 +59,11 @@ const DeviceUserBanners = ({
diskEncryptionActionRequired === "rotate_key" &&
!isNewMdmEnrollment;
// ADE-enrolled hosts escrow their FileVault key automatically, so there's nothing
// for the end user to do but refetch. Manually-enrolled hosts only get a new key at
// next login, so they keep the log-out instruction.
const isAdeEnrolled = isAutomaticDeviceEnrollment(mdmEnrollmentStatus);
const turnOnMdmButton = mdmManualEnrolmentUrl ? (
<CustomLink
url={mdmManualEnrolmentUrl}
@@ -85,9 +91,19 @@ const DeviceUserBanners = ({
if (showMacDiskEncryptionKeyResetRequired) {
return (
<InfoBanner color="yellow">
Disk encryption: Log out of your device or restart it to safeguard
your data in case your device is lost or stolen. After, select{" "}
<strong>Refetch</strong> to clear this banner.
{isAdeEnrolled ? (
<>
Disk encryption: Refetch to ensure data is safeguarded in case
your device is lost or stolen. If this banner persists, contact
your IT admin.
</>
) : (
<>
Disk encryption: Log out of your device or restart it to safeguard
your data in case your device is lost or stolen. After, select{" "}
<strong>Refetch</strong> to clear this banner.
</>
)}
</InfoBanner>
);
}
@@ -0,0 +1,81 @@
import React from "react";
import { screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import createMockConfig from "__mocks__/configMock";
import HostDetailsBanners from "./HostDetailsBanners";
const render = createCustomRenderer({
context: { app: { config: createMockConfig() } },
});
describe("Host Details Banners", () => {
const logOutExpectedText = /Disk encryption: Requires action from the end user\. Ask the end user to log out of their device or restart it\./;
const escrowedAutomaticallyExpectedText = /Disk encryption: FileVault key will be escrowed automatically on this host's next refetch\./;
it("tells the admin the key is escrowed automatically for ADE-enrolled hosts", () => {
render(
<HostDetailsBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (automatic)"
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
/>
);
expect(
screen.getByText(escrowedAutomaticallyExpectedText)
).toBeInTheDocument();
expect(screen.queryByText(logOutExpectedText)).not.toBeInTheDocument();
});
// "On (company-owned)" is the current name for automatic enrollment; "On (automatic)"
// is the legacy value the API still returns
it("tells the admin the key is escrowed automatically for company-owned hosts", () => {
render(
<HostDetailsBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (company-owned)"
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
/>
);
expect(
screen.getByText(escrowedAutomaticallyExpectedText)
).toBeInTheDocument();
});
it("tells the admin to ask the end user to log out for manually-enrolled hosts", () => {
render(
<HostDetailsBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (manual)"
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
/>
);
expect(screen.getByText(logOutExpectedText)).toBeInTheDocument();
expect(
screen.queryByText(escrowedAutomaticallyExpectedText)
).not.toBeInTheDocument();
});
it("renders no disk encryption banner when the key is not in an action required state", () => {
render(
<HostDetailsBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (automatic)"
connectedToFleetMdm
macDiskEncryptionStatus="verifying"
/>
);
expect(
screen.queryByText(escrowedAutomaticallyExpectedText)
).not.toBeInTheDocument();
expect(screen.queryByText(logOutExpectedText)).not.toBeInTheDocument();
});
});
@@ -2,7 +2,11 @@ import React, { useContext } from "react";
import { AppContext } from "context/app";
import { addHours, isPast } from "date-fns";
import { DiskEncryptionStatus, MdmEnrollmentStatus } from "interfaces/mdm";
import {
DiskEncryptionStatus,
MdmEnrollmentStatus,
isAutomaticDeviceEnrollment,
} from "interfaces/mdm";
import { IOSSettings } from "interfaces/host";
import {
HostPlatform,
@@ -67,6 +71,11 @@ const HostDetailsBanners = ({
macDiskEncryptionStatus === "action_required" &&
!isNewMdmEnrollment;
// ADE-enrolled hosts escrow their FileVault key automatically, so the end user
// doesn't need to log out. Manually-enrolled hosts only get a new key at next
// login, so they keep the log-out instruction.
const isAdeEnrolled = isAutomaticDeviceEnrollment(mdmEnrollmentStatus);
const actionRequiredBanner = (
<div className={baseClass}>
<InfoBanner color="yellow">
@@ -92,8 +101,17 @@ const HostDetailsBanners = ({
return (
<div className={baseClass}>
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the end user
to log out of their device or restart it.
{isAdeEnrolled ? (
<>
Disk encryption: FileVault key will be escrowed automatically on
this host&apos;s next refetch.
</>
) : (
<>
Disk encryption: Requires action from the end user. Ask the end
user to log out of their device or restart it.
</>
)}
</InfoBanner>
</div>
);