From e9bebfdf9642389e674569b80f012f3a09178a3a Mon Sep 17 00:00:00 2001
From: jacobshandling <61553566+jacobshandling@users.noreply.github.com>
Date: Wed, 4 Mar 2026 15:16:13 -0800
Subject: [PATCH] UI: Remove experimental `conditional_access_bypass_enabled`
policy setting in favor of `critical` (#40924)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Related issue:** Resolves #40417
- [x] Changes file added for user-visible changes in `changes/`
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added visual badge to identify critical policies.
* **Improvements**
* Critical policies can no longer be bypassed when Okta conditional
access is configured.
* Simplified conditional access policy management UI by removing
per-policy bypass toggles.
* Updated tooltips to clarify bypass restrictions for critical policies.
---
frontend/__mocks__/policyMock.ts | 4 -
.../CriticalPolicyBadge.tsx | 30 +++++++
.../components/CriticalPolicyBadge/index.ts | 1 +
frontend/interfaces/policy.ts | 4 +-
.../ConditionalAccess/ConditionalAccess.tsx | 6 +-
.../DeviceUserPage/DeviceUserPage.tests.tsx | 1 -
.../details/DeviceUserPage/DeviceUserPage.tsx | 10 +--
.../ManagePoliciesPage/ManagePoliciesPage.tsx | 2 -
.../ConditionalAccessModal.tsx | 79 +++----------------
.../ConditionalAccessModal/_styles.scss | 3 +
.../PoliciesPaginatedList.tsx | 3 +
.../PoliciesTable/PoliciesTableConfig.tsx | 29 +------
.../ManagePoliciesPage/helpers.tests.tsx | 4 -
.../components/PolicyForm/PolicyForm.tsx | 5 +-
.../SaveNewPolicyModal/SaveNewPolicyModal.tsx | 5 +-
frontend/services/entities/team_policies.ts | 2 -
16 files changed, 64 insertions(+), 124 deletions(-)
create mode 100644 frontend/components/CriticalPolicyBadge/CriticalPolicyBadge.tsx
create mode 100644 frontend/components/CriticalPolicyBadge/index.ts
diff --git a/frontend/__mocks__/policyMock.ts b/frontend/__mocks__/policyMock.ts
index 09b5995d49..ad569e31d9 100644
--- a/frontend/__mocks__/policyMock.ts
+++ b/frontend/__mocks__/policyMock.ts
@@ -24,7 +24,6 @@ const DEFAULT_POLICY_MOCK: IPolicyStats = {
next_update_ms: 3600000,
calendar_events_enabled: true,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
install_software: {
name: "testSw0",
software_title_id: 1,
@@ -62,7 +61,6 @@ export const createMockPoliciesResponse = (
next_update_ms: 3600000,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
{
id: 29090,
@@ -86,7 +84,6 @@ export const createMockPoliciesResponse = (
next_update_ms: 3600000,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
{
id: 136,
@@ -112,7 +109,6 @@ export const createMockPoliciesResponse = (
next_update_ms: 3600000,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
],
};
diff --git a/frontend/components/CriticalPolicyBadge/CriticalPolicyBadge.tsx b/frontend/components/CriticalPolicyBadge/CriticalPolicyBadge.tsx
new file mode 100644
index 0000000000..6650957fff
--- /dev/null
+++ b/frontend/components/CriticalPolicyBadge/CriticalPolicyBadge.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+
+import Icon from "components/Icon";
+import TooltipWrapper from "components/TooltipWrapper";
+
+const baseClass = "critical-badge";
+
+const CriticalPolicyBadge = () => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default CriticalPolicyBadge;
diff --git a/frontend/components/CriticalPolicyBadge/index.ts b/frontend/components/CriticalPolicyBadge/index.ts
new file mode 100644
index 0000000000..f074959f48
--- /dev/null
+++ b/frontend/components/CriticalPolicyBadge/index.ts
@@ -0,0 +1 @@
+export { default } from "./CriticalPolicyBadge";
diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts
index 454b4c2681..fc657d5d5b 100644
--- a/frontend/interfaces/policy.ts
+++ b/frontend/interfaces/policy.ts
@@ -42,10 +42,10 @@ export interface IPolicy {
team_id: number | null;
created_at: string;
updated_at: string;
+ // A critical policy cannot be "resolved later" if Okta conditional access is enabled for it
critical: boolean;
calendar_events_enabled: boolean;
conditional_access_enabled: boolean;
- conditional_access_bypass_enabled: boolean;
install_software?: IPolicySoftwareToInstall;
run_script?: Pick;
labels_include_any?: ILabelPolicy[];
@@ -105,6 +105,7 @@ export interface ILoadTeamPolicyResponse {
export interface IPolicyFormData {
description?: string | number | boolean | undefined;
resolution?: string | number | boolean | undefined;
+ // A critical policy cannot be "resolved later" if Okta conditional access is enabled for it
critical?: boolean;
platform?: CommaSeparatedPlatformString;
name?: string | number | boolean | undefined;
@@ -113,7 +114,6 @@ export interface IPolicyFormData {
id?: number;
calendar_events_enabled?: boolean;
conditional_access_enabled?: boolean;
- conditional_access_bypass_enabled?: boolean;
software_title_id?: number | null;
// null for PATCH to unset - note asymmetry with GET/LIST - see IPolicy.run_script
script_id?: number | null;
diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
index 3afa98bfa2..6f8bafc8f7 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
@@ -551,15 +551,15 @@ const ConditionalAccess = () => {
- When enabled, disables the per-policy setting to allow
- bypassing Okta conditional access.{" "}
+ Disables bypassing Okta conditional access for non-critical
+ policies.{" "}
(Default: Off)
Bypassing is valid for a single login attempt and is tracked
- in audit logs.
+ in audit logs. Critical policies can never be bypassed.
>
}
showArrow={false}
diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx
index 4c1f5b60be..dc6926fa53 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tests.tsx
@@ -556,7 +556,6 @@ describe("Device User Page", () => {
critical: false,
calendar_events_enabled: false,
conditional_access_enabled: true,
- conditional_access_bypass_enabled: true,
response: "fail",
});
diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
index e496eb6601..31ae20fc62 100644
--- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
+++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx
@@ -673,11 +673,8 @@ const DeviceUserPage = ({
);
}
- const hasAnyBypassDisabledFailingCAPolicy = host?.policies.some(
- (p) =>
- p.response === "fail" &&
- p.conditional_access_enabled &&
- !p.conditional_access_bypass_enabled
+ const hasAnyCriticalFailingCAPolicy = host?.policies.some(
+ (p) => p.response === "fail" && p.conditional_access_enabled && p.critical
);
return (
@@ -847,8 +844,7 @@ const DeviceUserPage = ({
onResolveLater={
globalConfig?.features?.enable_conditional_access &&
globalConfig.features?.enable_conditional_access_bypass &&
- !hasAnyBypassDisabledFailingCAPolicy &&
- selectedPolicy?.conditional_access_bypass_enabled
+ !hasAnyCriticalFailingCAPolicy
? () => {
onCancelPolicyDetailsModal();
setShowBypassModal(true);
diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
index 2dc553f227..e01f726aec 100644
--- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
@@ -789,8 +789,6 @@ const ManagePolicyPage = ({
const policyPromises = changedPolicies.map((changedPolicy) => {
return teamPoliciesAPI.update(changedPolicy.id, {
conditional_access_enabled: changedPolicy.conditional_access_enabled,
- conditional_access_bypass_enabled:
- changedPolicy.conditional_access_bypass_enabled,
team_id: teamIdForApi,
});
});
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
index 0d1cf38bc3..f73aa037b0 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
@@ -1,18 +1,13 @@
import React, { useContext, useRef, useState } from "react";
-import {
- FLEET_WEBSITE_URL,
- LEARN_MORE_ABOUT_BASE_LINK,
-} from "utilities/constants";
-
-import { isOktaConditionalAccessConfigured } from "interfaces/config";
+import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
+import TooltipTruncatedText from "components/TooltipTruncatedText";
+import CriticalPolicyBadge from "components/CriticalPolicyBadge";
import CustomLink from "components/CustomLink";
import Modal from "components/Modal";
import Button from "components/buttons/Button";
import Slider from "components/forms/fields/Slider";
-import Checkbox from "components/forms/fields/Checkbox";
-import TooltipWrapper from "components/TooltipWrapper";
import { AppContext } from "context/app";
import { IPaginatedListHandle } from "components/PaginatedList";
import PoliciesPaginatedList, {
@@ -53,7 +48,7 @@ const ConditionalAccessModal = ({
});
const paginatedListRef = useRef>(null);
- const { isGlobalAdmin, isTeamAdmin, config } = useContext(AppContext);
+ const { isGlobalAdmin, isTeamAdmin } = useContext(AppContext);
const isAdmin = isGlobalAdmin || isTeamAdmin;
const onChangeEnabled = () => {
@@ -82,58 +77,12 @@ const ConditionalAccessModal = ({
/>
);
- const renderItemRow = (
- item: IFormPolicy,
- onChange: (item: IFormPolicy) => void
- ) => {
- const shouldShowCheckbox =
- item.conditional_access_enabled &&
- // currently redundant as only darwin-targeting policies are enabled in this list
- item.platform.includes("darwin") &&
- isOktaConditionalAccessConfigured(config) &&
- !config?.conditional_access?.bypass_disabled;
-
- if (!shouldShowCheckbox) {
- return null;
- }
-
- return (
- {
- e.stopPropagation();
- }}
- >
- {
- onChange({
- ...item,
- conditional_access_bypass_enabled: !item.conditional_access_bypass_enabled,
- });
- }}
- >
-
- Allows end users to bypass conditional access for a single login
- if they are unable to resolve the failing policy.
-
-
-
- This experimental setting will be removed in Fleet 4.83, and
- only non-critical policies will allow bypass. For a seamless
- upgrade, please avoid enabling bypass for policies marked
- critical.
-
- >
- }
- >
- End users can bypass
-
-
-
- );
- };
+ const renderItemLabel = (policy: IFormPolicy) => (
+ <>
+
+ {policy.critical && }
+ >
+ );
const renderConfigured = () => {
return (
@@ -147,19 +96,13 @@ const ConditionalAccessModal = ({
activeText="Enabled"
disabled={gitOpsModeEnabled || !isAdmin}
/>
-
{
item.conditional_access_enabled = !item.conditional_access_enabled;
return item;
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/_styles.scss
index e2b7fedaad..37d9700cc4 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/_styles.scss
+++ b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/_styles.scss
@@ -6,4 +6,7 @@
white-space: nowrap;
}
}
+ .paginated-list .fleet-checkbox__label {
+ gap: 0.5rem;
+ }
}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesPaginatedList/PoliciesPaginatedList.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesPaginatedList/PoliciesPaginatedList.tsx
index 6c14451b08..c4847b926b 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesPaginatedList/PoliciesPaginatedList.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesPaginatedList/PoliciesPaginatedList.tsx
@@ -45,6 +45,7 @@ interface IPoliciesPaginatedListProps {
item: IFormPolicy,
onChange: (item: IFormPolicy) => void
) => ReactElement | false | null | undefined;
+ renderItemLabel?: (item: IFormPolicy) => ReactElement | null;
onToggleItem: (item: IFormPolicy) => IFormPolicy;
/** A function defining the conditions under which to disable a policy. */
getPolicyDisabled?: (policy: IFormPolicy) => boolean;
@@ -64,6 +65,7 @@ function PoliciesPaginatedList(
{
isSelected,
renderItemRow,
+ renderItemLabel,
onToggleItem,
getPolicyDisabled,
getPolicyTooltipContent,
@@ -238,6 +240,7 @@ function PoliciesPaginatedList(
isItemDisabled={getPolicyDisabled}
getItemTooltipContent={getPolicyTooltipContent}
onClickRow={onToggleItem}
+ renderItemLabel={renderItemLabel}
renderItemRow={renderItemRow}
pageSize={DEFAULT_PAGE_SIZE}
onUpdate={onUpdate}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
index e143983571..f478d91d0e 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
@@ -8,7 +8,6 @@ import { Tooltip as ReactTooltip5 } from "react-tooltip-5";
import Checkbox from "components/forms/fields/Checkbox";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell";
-import Icon from "components/Icon";
import { IPolicyStats } from "interfaces/policy";
import PATHS from "router/paths";
@@ -16,6 +15,7 @@ import { getPathWithQueryParams } from "utilities/url";
import sortUtils from "utilities/sort";
import { PolicyResponse } from "utilities/constants";
+import CriticalPolicyBadge from "components/CriticalPolicyBadge";
import InheritedBadge from "components/InheritedBadge";
import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
@@ -116,32 +116,7 @@ const generateTableHeaders = (
value={cellProps.cell.value}
suffix={
<>
- {isPremiumTier && critical && (
-
-
-
-
-
- This policy has been marked as critical.
-
-
- )}
+ {isPremiumTier && critical && }
{viewingTeamPolicies && team_id === null && (
)}
diff --git a/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx
index 485e9a2369..949311c966 100644
--- a/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/helpers.tests.tsx
@@ -28,7 +28,6 @@ describe("getInstallSoftwareErrorMessage", () => {
critical: false,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
{
swIdToInstall: 456,
@@ -50,7 +49,6 @@ describe("getInstallSoftwareErrorMessage", () => {
critical: false,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
];
@@ -162,7 +160,6 @@ describe("getRunScriptErrorMessage", () => {
critical: false,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
{
scriptIdToRun: 456,
@@ -184,7 +181,6 @@ describe("getRunScriptErrorMessage", () => {
critical: false,
calendar_events_enabled: false,
conditional_access_enabled: false,
- conditional_access_bypass_enabled: true,
},
];
diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx
index 3577aaa289..a65324a8f7 100644
--- a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx
+++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx
@@ -641,8 +641,9 @@ const PolicyForm = ({
- If automations are turned on, this
-
information is included.
+ If automations are turned on, this information is included. If
+ Okta conditional access is configured, end users can never
+ bypass critical policies.
}
>
diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx
index 1c515be7d3..97fd55f9c2 100644
--- a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx
+++ b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx
@@ -313,8 +313,9 @@ const SaveNewPolicyModal = ({
- If automations are turned on, this
-
information is included.
+ If automations are turned on, this information is
+ included. If Okta conditional access is configured, end
+ users can never bypass critical policies.
}
>
diff --git a/frontend/services/entities/team_policies.ts b/frontend/services/entities/team_policies.ts
index 691e82071d..a528183282 100644
--- a/frontend/services/entities/team_policies.ts
+++ b/frontend/services/entities/team_policies.ts
@@ -97,7 +97,6 @@ export default {
// automations-related fields
calendar_events_enabled,
conditional_access_enabled,
- conditional_access_bypass_enabled,
software_title_id,
script_id,
labels_include_any,
@@ -115,7 +114,6 @@ export default {
critical,
calendar_events_enabled,
conditional_access_enabled,
- conditional_access_bypass_enabled,
software_title_id,
script_id,
labels_include_any,