From c78c63be704e7b3ccafef9de4675dd03c7da9cf1 Mon Sep 17 00:00:00 2001
From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com>
Date: Thu, 18 Dec 2025 10:37:47 -0600
Subject: [PATCH] Conditional access dropdown and copy (#37338)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Related issue:** Resolves #37305
Resolves issue that conditional access modal was not visible for
self-hosted and contained wrong copy.
## Testing
- [x] QA'd all new/changed functionality manually
For unreleased bug fixes in a release candidate, one of:
- [x] Confirmed that the fix is not expected to adversely impact load
test results
## Summary by CodeRabbit
* **Improvements**
* "Conditional access" automation option is now available to all users
without license restrictions.
* Enhanced conditional access detection for both Okta and Microsoft
Entra identity providers.
* Improved conditional access setup process with clearer provider
information display based on your configuration.
✏️ Tip: You can customize this high-level summary in your review
settings.
---
frontend/interfaces/config.ts | 32 +++++++++++++++++++
.../ConditionalAccess/ConditionalAccess.tsx | 14 ++------
.../OktaConditionalAccessModal.tsx | 8 ++---
.../ManagePoliciesPage/ManagePoliciesPage.tsx | 27 +++++++++-------
.../ConditionalAccessModal.tsx | 4 ++-
5 files changed, 56 insertions(+), 29 deletions(-)
diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts
index acd4599477..337d4d481b 100644
--- a/frontend/interfaces/config.ts
+++ b/frontend/interfaces/config.ts
@@ -272,3 +272,35 @@ export interface IGitOpsModeConfig {
gitops_mode_enabled: boolean;
repository_url: string;
}
+
+/** Check if Okta conditional access is configured (all 4 fields must be present) */
+export const isOktaConditionalAccessConfigured = (
+ config: IConfig | null | undefined
+): boolean => {
+ const ca = config?.conditional_access;
+ return !!(
+ ca?.okta_idp_id &&
+ ca?.okta_assertion_consumer_service_url &&
+ ca?.okta_audience_uri &&
+ ca?.okta_certificate
+ );
+};
+
+/** Check if Microsoft Entra conditional access is configured */
+export const isEntraConditionalAccessConfigured = (
+ config: IConfig | null | undefined
+): boolean => {
+ return (
+ config?.conditional_access?.microsoft_entra_connection_configured ?? false
+ );
+};
+
+/** Check if any conditional access provider is configured (Okta or Entra) */
+export const isConditionalAccessConfigured = (
+ config: IConfig | null | undefined
+): boolean => {
+ return (
+ isOktaConditionalAccessConfigured(config) ||
+ isEntraConditionalAccessConfigured(config)
+ );
+};
diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
index 45ae2126a4..b14ba3431d 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
@@ -25,7 +25,7 @@ import PremiumFeatureMessage from "components/PremiumFeatureMessage";
import { useQuery } from "react-query";
import DataError from "components/DataError";
import Modal from "components/Modal";
-import { IConfig } from "interfaces/config";
+import { IConfig, isOktaConditionalAccessConfigured } from "interfaces/config";
import SectionCard from "../MdmSettings/components/SectionCard";
import EntraConditionalAccessModal from "./components/EntraConditionalAccessModal";
@@ -245,19 +245,9 @@ const ConditionalAccess = () => {
const {
microsoft_entra_tenant_id: entraTenantId,
microsoft_entra_connection_configured: entraConfigured,
- okta_idp_id: oktaIdpId,
- okta_assertion_consumer_service_url: oktaAcsUrl,
- okta_audience_uri: oktaAudienceUri,
- okta_certificate: oktaCertificate,
} = config?.conditional_access || {};
- // Determine if Okta is configured (all 4 fields must be present)
- const oktaConfigured = !!(
- oktaIdpId &&
- oktaAcsUrl &&
- oktaAudienceUri &&
- oktaCertificate
- );
+ const oktaConfigured = isOktaConditionalAccessConfigured(config);
// Check if this is a managed cloud deployment (Microsoft Entra requires proxy infrastructure)
const isManagedCloud = config?.license?.managed_cloud || false;
diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx
index 0e74f62536..529852b2d2 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx
@@ -59,18 +59,18 @@ const validate = (formData: IFormData) => {
errs[OKTA_IDP_ID] = `IdP ID must be ${maxURLLength} characters or less`;
}
- // Assertion Consumer Service URL validation
+ // Assertion consumer service URL validation
if (!formData[OKTA_ACS_URL] || !formData[OKTA_ACS_URL].trim()) {
- errs[OKTA_ACS_URL] = "Assertion Consumer Service URL must be present";
+ errs[OKTA_ACS_URL] = "Assertion consumer service URL must be present";
} else if (formData[OKTA_ACS_URL].length > maxURLLength) {
errs[
OKTA_ACS_URL
- ] = `Assertion Consumer Service URL must be ${maxURLLength} characters or less`;
+ ] = `Assertion consumer service URL must be ${maxURLLength} characters or less`;
} else if (
!valid_url({ url: formData[OKTA_ACS_URL], protocols: ["http", "https"] })
) {
errs[OKTA_ACS_URL] =
- "Assertion Consumer Service URL must be a valid URL with http or https scheme and a host";
+ "Assertion consumer service URL must be a valid URL with http or https scheme and a host";
}
// Audience URI validation
diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
index 9951730ae7..941a291a8c 100644
--- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
@@ -12,7 +12,11 @@ import { PolicyContext } from "context/policy";
import { TableContext } from "context/table";
import { NotificationContext } from "context/notification";
import useTeamIdParam from "hooks/useTeamIdParam";
-import { IConfig, IWebhookSettings } from "interfaces/config";
+import {
+ IConfig,
+ IWebhookSettings,
+ isConditionalAccessConfigured,
+} from "interfaces/config";
import { IZendeskJiraIntegrations } from "interfaces/integration";
import { INotification } from "interfaces/notification";
import {
@@ -121,6 +125,11 @@ const ManagePolicyPage = ({
} = useContext(AppContext);
const isPrimoMode =
globalConfigFromContext?.partnerships?.enable_primo || false;
+ const isManagedCloud =
+ globalConfigFromContext?.license?.managed_cloud || false;
+ const conditionalAccessProviderText = isManagedCloud
+ ? "Okta or Microsoft Entra"
+ : "Okta";
const { renderFlash, renderMultiFlash } = useContext(NotificationContext);
const { setResetSelectedRows } = useContext(TableContext);
@@ -1115,10 +1124,6 @@ const ManagePolicyPage = ({
const isCalEventsEnabled =
teamConfig?.integrations.google_calendar?.enable_calendar_events ?? false;
- const isConditionalAccessConfigured =
- globalConfig?.conditional_access?.microsoft_entra_connection_configured ??
- false;
-
const isConditionalAccessEnabled =
(teamIdForApi === API_NO_TEAM_ID
? globalConfig?.integrations.conditional_access_enabled
@@ -1198,17 +1203,14 @@ const ManagePolicyPage = ({
helpText: "Run script to resolve failing policies.",
tooltipContent: disabledRunScriptTooltipContent,
},
- ];
-
- if (globalConfigFromContext?.license.managed_cloud) {
- options.push({
+ {
label: "Conditional access",
value: "conditional_access",
isDisabled: !!disabledConditionalAccessTooltipContent,
helpText: "Block single sign-on for hosts failing policies.",
tooltipContent: disabledConditionalAccessTooltipContent,
- });
- }
+ },
+ ];
// Maintainers do not have access to other workflows
if (!isGlobalMaintainer && !isTeamMaintainer) {
@@ -1391,11 +1393,12 @@ const ManagePolicyPage = ({
)}
>
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
index 950069b2c3..92db7ab33e 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/ConditionalAccessModal/ConditionalAccessModal.tsx
@@ -30,6 +30,7 @@ interface IConditionalAccessModal {
isUpdating: boolean;
gitOpsModeEnabled?: boolean;
teamId: number;
+ providerText: string;
}
const ConditionalAccessModal = ({
@@ -40,6 +41,7 @@ const ConditionalAccessModal = ({
isUpdating,
gitOpsModeEnabled = false,
teamId,
+ providerText,
}: IConditionalAccessModal) => {
const [formData, setFormData] = useState({
enabled,
@@ -130,7 +132,7 @@ const ConditionalAccessModal = ({
const renderNotConfigured = () => (
<>
To block single sign-on from hosts failing policies, you must first
- connect Fleet to Microsoft Entra.
+ connect Fleet to {providerText}.
This can be configured in Settings > Integrations >{" "}