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; };