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 >{" "}