From bf77501eea975410674d0936ed766c2dd4f243ab Mon Sep 17 00:00:00 2001 From: Michal Gold Date: Wed, 13 Aug 2025 12:43:19 +0300 Subject: [PATCH] Wizard: Display customized policy rules in review summary Previously, when a user selected a compliance policy with tailored rules, the review page always showed the default profile customizations instead of the policy-specific customizations. Root cause: OscapProfileInformation component was only using the profile endpoint (/oscap/{distribution}/{profile}/customizations) which returns base profile rules, not the policy endpoint (/oscap/{policy}/{distribution}/policy_customizations) which returns customized rules. Changes: - Add useGetOscapCustomizationsForPolicyQuery export to backendApi - Implement dual data fetching in OscapProfileInformation: * Profile endpoint: for description and reference ID * Policy endpoint: for customized packages, services, kernel args Fixes the compliance policy customization display bug where edited policy rules were not reflected in the image build summary. Add unit tests for compliance policy customizations Fix profile description title --- .../components/OscapProfileInformation.tsx | 64 +++++++++++++++---- .../steps/Oscap/components/PolicySelector.tsx | 12 ++-- src/store/backendApi.ts | 6 ++ .../steps/Oscap/Oscap.test.tsx | 26 ++++++++ src/test/fixtures/compliance.ts | 14 +++- src/test/fixtures/oscap.ts | 16 +++-- 6 files changed, 115 insertions(+), 23 deletions(-) diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx index d8aa0102..a2d8a404 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/OscapProfileInformation.tsx @@ -8,7 +8,10 @@ import { Spinner, } from '@patternfly/react-core'; -import { useGetOscapCustomizationsQuery } from '../../../../../store/backendApi'; +import { + useGetComplianceCustomizationsQuery, + useGetOscapCustomizationsQuery, +} from '../../../../../store/backendApi'; import { PolicyRead, usePolicyQuery } from '../../../../../store/complianceApi'; import { useAppDispatch, useAppSelector } from '../../../../../store/hooks'; import { OpenScapProfile } from '../../../../../store/imageBuilderApi'; @@ -16,6 +19,7 @@ import { changeCompliance, selectCompliancePolicyID, selectComplianceProfileID, + selectComplianceType, selectDistribution, selectFips, } from '../../../../../store/wizardSlice'; @@ -31,12 +35,29 @@ export const OscapProfileInformation = ({ const release = useAppSelector(selectDistribution); const compliancePolicyID = useAppSelector(selectCompliancePolicyID); const complianceProfileID = useAppSelector(selectComplianceProfileID); + const complianceType = useAppSelector(selectComplianceType); const fips = useAppSelector(selectFips); + const { + data: oscapPolicyInfo, + isFetching: isFetchingOscapPolicyInfo, + isSuccess: isSuccessOscapPolicyInfo, + error: policyError, + } = useGetComplianceCustomizationsQuery( + { + distribution: release, + policy: compliancePolicyID!, + }, + { + skip: !compliancePolicyID || !!process.env.IS_ON_PREMISE, + }, + ); + const { data: oscapProfileInfo, isFetching: isFetchingOscapProfileInfo, isSuccess: isSuccessOscapProfileInfo, + error: profileError, } = useGetOscapCustomizationsQuery( { distribution: release, @@ -48,6 +69,20 @@ export const OscapProfileInformation = ({ }, ); + const customizationData = + compliancePolicyID && oscapPolicyInfo ? oscapPolicyInfo : oscapProfileInfo; + const profileMetadata = oscapProfileInfo; + const isPolicyDataLoading = compliancePolicyID + ? isFetchingOscapPolicyInfo + : false; + const isFetchingOscapData = isPolicyDataLoading || isFetchingOscapProfileInfo; + const isPolicyDataSuccess = compliancePolicyID + ? isSuccessOscapPolicyInfo + : true; + const isSuccessOscapData = isPolicyDataSuccess && isSuccessOscapProfileInfo; + const hasCriticalError = profileError || (compliancePolicyID && policyError); + const shouldShowData = isSuccessOscapData && !hasCriticalError; + const { data: policyInfo, isFetching: isFetchingPolicyInfo, @@ -74,23 +109,28 @@ export const OscapProfileInformation = ({ policyTitle: pol.title, }), ); - }, [isSuccessPolicyInfo]); + }, [isSuccessPolicyInfo, dispatch, policyInfo]); - const oscapProfile = oscapProfileInfo?.openscap as OpenScapProfile; + const oscapProfile = profileMetadata?.openscap as OpenScapProfile | undefined; return ( <> - {(isFetchingOscapProfileInfo || isFetchingPolicyInfo) && ( - + {(isFetchingOscapData || isFetchingPolicyInfo) && } + {hasCriticalError && ( + + Unable to load compliance information. Please try again. + )} - {isSuccessOscapProfileInfo && ( + {shouldShowData && ( <> - Profile description + {complianceType === 'compliance' + ? 'Policy description' + : 'Profile description'} {oscapProfile?.profile_description} @@ -116,7 +156,7 @@ export const OscapProfileInformation = ({ - {(oscapProfileInfo?.packages ?? []).join(', ')} + {(customizationData?.packages ?? []).join(', ')} @@ -129,7 +169,7 @@ export const OscapProfileInformation = ({ - {oscapProfileInfo?.kernel?.append} + {customizationData?.kernel?.append} @@ -142,7 +182,7 @@ export const OscapProfileInformation = ({ - {(oscapProfileInfo?.services?.enabled ?? []).join(' ')} + {(customizationData?.services?.enabled ?? []).join(' ')} @@ -155,8 +195,8 @@ export const OscapProfileInformation = ({ - {(oscapProfileInfo?.services?.disabled ?? []) - .concat(oscapProfileInfo?.services?.masked ?? []) + {(customizationData?.services?.disabled ?? []) + .concat(customizationData?.services?.masked ?? []) .join(' ')} diff --git a/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx b/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx index 5e2cf9c8..6f407aec 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx @@ -10,15 +10,15 @@ import { import { useSelectorHandlers } from './useSelectorHandlers'; +import { + useGetComplianceCustomizationsQuery, + useLazyGetComplianceCustomizationsQuery, +} from '../../../../../store/backendApi'; import { PolicyRead, usePoliciesQuery, } from '../../../../../store/complianceApi'; import { useAppDispatch, useAppSelector } from '../../../../../store/hooks'; -import { - useGetOscapCustomizationsForPolicyQuery, - useLazyGetOscapCustomizationsForPolicyQuery, -} from '../../../../../store/imageBuilderApi'; import { changeCompliance, changeFileSystemConfigurationType, @@ -97,7 +97,7 @@ const PolicySelector = () => { filter: `os_major_version=${majorVersion}`, }); - const { data: currentProfileData } = useGetOscapCustomizationsForPolicyQuery( + const { data: currentProfileData } = useGetComplianceCustomizationsQuery( { distribution: release, policy: policyID!, @@ -105,7 +105,7 @@ const PolicySelector = () => { { skip: !policyID }, ); - const [trigger] = useLazyGetOscapCustomizationsForPolicyQuery(); + const [trigger] = useLazyGetComplianceCustomizationsQuery(); useEffect(() => { if (!policies || policies.data === undefined) { diff --git a/src/store/backendApi.ts b/src/store/backendApi.ts index 55e94173..6fa85557 100644 --- a/src/store/backendApi.ts +++ b/src/store/backendApi.ts @@ -43,6 +43,12 @@ export const useLazyGetOscapCustomizationsQuery = process.env.IS_ON_PREMISE ? cockpitQueries.useLazyGetOscapCustomizationsQuery : serviceQueries.useLazyGetOscapCustomizationsQuery; +export const useGetComplianceCustomizationsQuery = + serviceQueries.useGetOscapCustomizationsForPolicyQuery; + +export const useLazyGetComplianceCustomizationsQuery = + serviceQueries.useLazyGetOscapCustomizationsForPolicyQuery; + export const useComposeBlueprintMutation = process.env.IS_ON_PREMISE ? cockpitQueries.useComposeBlueprintMutation : serviceQueries.useComposeBlueprintMutation; diff --git a/src/test/Components/CreateImageWizard/steps/Oscap/Oscap.test.tsx b/src/test/Components/CreateImageWizard/steps/Oscap/Oscap.test.tsx index 6feb8098..90a3b183 100644 --- a/src/test/Components/CreateImageWizard/steps/Oscap/Oscap.test.tsx +++ b/src/test/Components/CreateImageWizard/steps/Oscap/Oscap.test.tsx @@ -332,4 +332,30 @@ describe('OpenSCAP edit mode', () => { user.click(selectedBtn); await screen.findByText('neovim'); }); + + test('customized policy shows only non-removed rules', async () => { + const { oscapCustomizations, oscapCustomizationsPolicy } = await import( + '../../../../fixtures/oscap' + ); + + const profileId = 'xccdf_org.ssgproject.content_profile_cis_workstation_l1'; + const normalProfile = oscapCustomizations(profileId); + expect(normalProfile.packages).toEqual(['aide', 'neovim']); + const customPolicy = oscapCustomizationsPolicy('custom-policy-123'); + expect(customPolicy.packages).toEqual(['neovim']); + await renderCreateMode(); + await selectRhel9(); + await selectGuestImageTarget(); + await goToOscapStep(); + await selectProfile(); + + await waitFor(() => { + expect(screen.getByText(/aide, neovim/i)).toBeInTheDocument(); + }); + + expect(customPolicy.packages).not.toContain('aide'); + expect(customPolicy.packages).toContain('neovim'); + expect(normalProfile.packages).toContain('aide'); + expect(normalProfile.packages).toContain('neovim'); + }); }); diff --git a/src/test/fixtures/compliance.ts b/src/test/fixtures/compliance.ts index 5556352f..83afabd7 100644 --- a/src/test/fixtures/compliance.ts +++ b/src/test/fixtures/compliance.ts @@ -36,8 +36,20 @@ export const mockPolicies = { profile_title: 'DISA STIG with GUI for Red Hat Enterprise Linux 8', ref_id: 'xccdf_org.ssgproject.content_profile_stig_gui', }, + { + id: 'custom-policy-123', + title: 'Custom CIS Policy (Partial Rules)', + description: 'A customized policy where user removed some rules', + compliance_threshold: 100, + total_system_count: 5, + type: 'policy', + os_major_version: 8, + profile_title: + 'Custom CIS Red Hat Enterprise Linux 8 Benchmark for Level 1 - Workstation', + ref_id: 'xccdf_org.ssgproject.content_profile_cis_workstation_l1', + }, ], meta: { - total: 3, + total: 4, }, }; diff --git a/src/test/fixtures/oscap.ts b/src/test/fixtures/oscap.ts index 6f807460..de50ce9b 100644 --- a/src/test/fixtures/oscap.ts +++ b/src/test/fixtures/oscap.ts @@ -124,9 +124,17 @@ export const oscapCustomizationsPolicy = ( ): GetOscapCustomizationsApiResponse => { const policyData = mockPolicies.data.find((p) => p.id === policy); const customizations = oscapCustomizations(policyData!.ref_id); - // filter out a single package to simulate the customizations being tailored - customizations.packages = customizations.packages!.filter( - (p) => p !== 'aide', - ); + + // Simulate different levels of customization based on policy + if (policy === 'custom-policy-123') { + // This policy has user-customized rules - only neovim remains + customizations.packages = ['neovim']; // User removed aide package + } else { + // Other policies: filter out a single package to simulate basic customizations + customizations.packages = customizations.packages!.filter( + (p) => p !== 'aide', + ); + } + return customizations; };