From e85b46e915f28da1158ddc9ba341ffb36b853cdd Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 13 Mar 2025 19:41:22 +0000 Subject: [PATCH] add UI for editing digicert CA (#27103) --- frontend/interfaces/integration.ts | 27 ++++ .../CertificateAuthorities.tsx | 14 ++- .../AddCertAuthorityModal.tsx | 4 + .../CertAuthorityListItem.tsx | 4 +- .../helpers.ts | 115 ++++++++++++------ .../components/DigicertForm/DigicertForm.tsx | 4 +- .../components/DigicertForm/helpers.ts | 2 +- .../EditCertAuthorityModal.tsx | 107 ++++++++++++++++ .../EditCertAuthorityModal/_styles.scss | 3 + .../EditCertAuthorityModal/helpers.tsx | 40 ++++++ .../EditCertAuthorityModal/index.ts | 1 + .../cards/CertificateAuthorities/helpers.tsx | 26 ++-- frontend/services/entities/certificates.ts | 5 + 13 files changed, 296 insertions(+), 56 deletions(-) create mode 100644 frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss create mode 100644 frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx create mode 100644 frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/index.ts diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 891aba0a54..1dbd911c2a 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -41,6 +41,33 @@ export interface ICertificatesIntegrationCustomSCEP { challenge: string; } +export const isNDESCertIntegration = ( + integration: ICertificateIntegration +): integration is ICertificatesIntegrationNDES => { + return ( + "admin_url" in integration && + "username" in integration && + "password" in integration + ); +}; + +export const isDigicertCertIntegration = ( + integration: ICertificateIntegration +): integration is ICertificatesIntegrationDigicert => { + return ( + "profile_id" in integration && + "certificate_common_name" in integration && + "certificate_user_principal_names" in integration && + "certificate_seat_id" in integration + ); +}; + +export const isCustomSCEPCertIntegration = ( + integration: ICertificateIntegration +): integration is ICertificatesIntegrationCustomSCEP => { + return "id" in integration && "challenge" in integration; +}; + export type ICertificateAuthorityType = "ndes" | "digicert" | "custom"; /** all the types of certificate integrations */ diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx index 4145346f9f..d74a2cd4fd 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx @@ -17,6 +17,7 @@ import { import AddCertAuthorityCard from "./components/AddCertAuthorityCard"; import DeleteCertificateAuthorityModal from "./components/DeleteCertificateAuthorityModal"; import AddCertAuthorityModal from "./components/AddCertAuthorityModal"; +import EditCertAuthorityModal from "./components/EditCertAuthorityModal"; const baseClass = "certificate-authorities"; @@ -30,7 +31,7 @@ const CertificateAuthorities = () => { false ); const [ - showDeleteCertAuthoirtyModal, + showDeleteCertAuthorityModal, setShowDeleteCertAuthorityModal, ] = useState(false); @@ -56,7 +57,6 @@ const CertificateAuthorities = () => { }; const onEditCertAuthority = (cert: ICertAuthorityListData) => { - // TODO: use useCallback const certAuthority = getCertificateAuthority( cert.id, config?.integrations.ndes_scep_proxy, @@ -69,7 +69,6 @@ const CertificateAuthorities = () => { }; const onDeleteCertAuthority = (cert: ICertAuthorityListData) => { - // TODO: use useCallback const certAuthority = getCertificateAuthority( cert.id, config?.integrations.ndes_scep_proxy, @@ -118,8 +117,13 @@ const CertificateAuthorities = () => { onExit={() => setShowAddCertAuthorityModal(false)} /> )} - {showEditCertAuthorityModal &&
Modal showing
} - {showDeleteCertAuthoirtyModal && + {showEditCertAuthorityModal && selectedCertAuthority && ( + setShowEditCertAuthorityModal(false)} + /> + )} + {showDeleteCertAuthorityModal && selectedCertAuthority && selectedListItemId && ( { return ( <> - {/* ( )} - /> */} + /> ( diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts index 563e4d49aa..ec100e8067 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts @@ -7,7 +7,7 @@ import { ICertificatesIntegrationDigicert, IGlobalIntegrations, } from "interfaces/integration"; -import { useContext } from "react"; +import { useCallback, useContext } from "react"; import { IDigicertFormData } from "../DigicertForm/DigicertForm"; export const useCertAuthorityDataGenerator = ( @@ -16,45 +16,48 @@ export const useCertAuthorityDataGenerator = ( ) => { const { config } = useContext(AppContext); - const generateAddPatchData = (formData: IDigicertFormData) => { - if (!config) return null; + const generateAddPatchData = useCallback( + (formData: IDigicertFormData) => { + if (!config) return null; - const data: { integrations: Partial } = { - integrations: {}, - }; + const data: { integrations: Partial } = { + integrations: {}, + }; - switch (certAuthorityType) { - case "ndes": - break; - case "digicert": - data.integrations.digicert = [ - ...(config.integrations.digicert || []), - { - name: formData.name, - url: formData.url, - api_token: formData.apiToken, - profile_id: formData.profileId, - certificate_common_name: formData.commonName, - certificate_user_principal_names: [formData.userPrincipalName], - certificate_seat_id: formData.certificateSeatId, - }, - ]; - break; - case "custom": - break; - default: - break; - } + switch (certAuthorityType) { + case "ndes": + break; + case "digicert": + data.integrations.digicert = [ + ...(config.integrations.digicert || []), + { + name: formData.name, + url: formData.url, + api_token: formData.apiToken, + profile_id: formData.profileId, + certificate_common_name: formData.commonName, + certificate_user_principal_names: [formData.userPrincipalName], + certificate_seat_id: formData.certificateSeatId, + }, + ]; + break; + case "custom": + break; + default: + break; + } - return data; - }; + return data; + }, + [certAuthorityType, config] + ); /** * generates the data to be sent to the API to delete the certificate authority. * under the hood we are updating the app config object with the new data and * have to generate the correct data for the PATCH request. */ - const generateDeletePatchData = () => { + const generateDeletePatchData = useCallback(() => { if (!config) return null; const data: { integrations: Partial } = { @@ -90,16 +93,58 @@ export const useCertAuthorityDataGenerator = ( } return data; - }; + }, [certAuthority, certAuthorityType, config]); /** * generates the data to be sent to the API to edit the certificate authority. * under the hood we are updating the app config object with the new data and * have to generate the correct data for the PATCH request. */ - const generateEditPatchData = (formData: IDigicertFormData) => { - if (!config) return null; - }; + const generateEditPatchData = useCallback( + (formData: IDigicertFormData) => { + if (!config) return null; + + const data: { integrations: Partial } = { + integrations: {}, + }; + + switch (certAuthorityType) { + case "ndes": + break; + case "digicert": + data.integrations.digicert = config.integrations.digicert?.map( + (cert) => { + // only update the certificate authority that we are editing + if ( + (certAuthority as ICertificatesIntegrationDigicert).name === + cert.name + ) { + return { + name: formData.name, + url: formData.url, + api_token: formData.apiToken, + profile_id: formData.profileId, + certificate_common_name: formData.commonName, + certificate_user_principal_names: [ + formData.userPrincipalName, + ], + certificate_seat_id: formData.certificateSeatId, + }; + } + return cert; + } + ); + break; + case "custom": + break; + default: + break; + } + + return data; + }, + [certAuthority, certAuthorityType, config] + ); return { generateAddPatchData, diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx index 53237eec62..9f30595060 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx @@ -5,7 +5,7 @@ import InputField from "components/forms/fields/InputField"; import Button from "components/buttons/Button"; import CustomLink from "components/CustomLink"; import TooltipWrapper from "components/TooltipWrapper"; -import { generateFormValidation, IDigicertFormValidation } from "./helpers"; +import { validateFormData, IDigicertFormValidation } from "./helpers"; const baseClass = "digicert-form"; @@ -59,7 +59,7 @@ const DigicertForm = ({ const onInputChange = (update: { name: string; value: string }) => { setFormValidation( - generateFormValidation({ ...formData, [update.name]: update.value }) + validateFormData({ ...formData, [update.name]: update.value }) ); onChange(update); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts index 2d89ca38ba..ddc946f18b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts @@ -116,7 +116,7 @@ const getErrorMessage = ( }; // eslint-disable-next-line import/prefer-default-export -export const generateFormValidation = (formData: IDigicertFormData) => { +export const validateFormData = (formData: IDigicertFormData) => { const formValidation: IDigicertFormValidation = { isValid: true, }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx new file mode 100644 index 0000000000..ecfe86cd05 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx @@ -0,0 +1,107 @@ +import React, { useContext, useRef, useState } from "react"; + +import { NotificationContext } from "context/notification"; +import { AppContext } from "context/app"; +import { + ICertificateIntegration, + isDigicertCertIntegration, +} from "interfaces/integration"; +import certificatesAPI from "services/entities/certificates"; + +import Modal from "components/Modal"; + +import DigicertForm from "../DigicertForm"; +import { + generateDefaultFormData, + generateErrorMessage, + getCertificateAuthorityType, +} from "./helpers"; +import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal"; +import { useCertAuthorityDataGenerator } from "../DeleteCertificateAuthorityModal/helpers"; + +const baseClass = "edit-cert-authority-modal"; + +interface IEditCertAuthorityModalProps { + certAuthority: ICertificateIntegration; + onExit: () => void; +} + +const EditCertAuthorityModal = ({ + certAuthority, + onExit, +}: IEditCertAuthorityModalProps) => { + const certType = useRef(getCertificateAuthorityType(certAuthority)); + const { setConfig } = useContext(AppContext); + const { renderFlash } = useContext(NotificationContext); + const [isUpdating, setIsUpdating] = useState(false); + const [formData, setFormData] = useState(() => + generateDefaultFormData(certAuthority) + ); + const { generateEditPatchData } = useCertAuthorityDataGenerator( + certType.current, + certAuthority + ); + + const onChangeForm = (update: { name: string; value: string }) => { + setFormData((prevFormData) => { + if (!prevFormData) return prevFormData; + + return { + ...prevFormData, + [update.name]: update.value, + }; + }); + }; + + const onEditCertAuthority = async () => { + const editPatchData = generateEditPatchData(formData); + setIsUpdating(true); + try { + const newConfig = await certificatesAPI.editCertAuthorityModal( + editPatchData + ); + renderFlash("success", "Successfully edited your certificate authority."); + onExit(); + setConfig(newConfig); + } catch (e) { + renderFlash("error", generateErrorMessage(e)); + } + setIsUpdating(false); + }; + + const getFormComponent = () => { + if (isDigicertCertIntegration(certAuthority)) { + return DigicertForm; + } + return null; + }; + + const renderForm = () => { + const FormComponent = getFormComponent(); + if (!FormComponent || !formData) return <>; + + return ( + + ); + }; + + return ( + + {renderForm()} + + ); +}; + +export default EditCertAuthorityModal; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss new file mode 100644 index 0000000000..ff608def43 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss @@ -0,0 +1,3 @@ +.edit-cert-authority-modal { + +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx new file mode 100644 index 0000000000..d3f2e80f48 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx @@ -0,0 +1,40 @@ +import { + ICertificateAuthorityType, + ICertificateIntegration, + ICertificatesIntegrationDigicert, + isCustomSCEPCertIntegration, + isNDESCertIntegration, +} from "interfaces/integration"; + +import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal"; + +const DEFAULT_ERROR_MESSAGE = + "Couldn't edit certificate authority. Please try again."; + +// eslint-disable-next-line import/prefer-default-export +export const generateErrorMessage = (e: unknown) => { + return DEFAULT_ERROR_MESSAGE; +}; + +export const generateDefaultFormData = ( + certAuthority: ICertificateIntegration +): ICertFormData => { + const cert = certAuthority as ICertificatesIntegrationDigicert; + return { + name: cert.name, + url: cert.url, + apiToken: cert.api_token, + profileId: cert.profile_id, + commonName: cert.certificate_common_name, + userPrincipalName: cert.certificate_user_principal_names[0], + certificateSeatId: cert.certificate_seat_id, + }; +}; + +export const getCertificateAuthorityType = ( + certAuthority: ICertificateIntegration +): ICertificateAuthorityType => { + if (isNDESCertIntegration(certAuthority)) return "ndes"; + if (isCustomSCEPCertIntegration(certAuthority)) return "custom"; + return "digicert"; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/index.ts new file mode 100644 index 0000000000..ecfc0036c3 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/index.ts @@ -0,0 +1 @@ +export { default } from "./EditCertAuthorityModal"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx index 65228043a1..17ff4c7512 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx @@ -2,6 +2,7 @@ import { ICertificatesIntegrationCustomSCEP, ICertificatesIntegrationDigicert, ICertificatesIntegrationNDES, + isNDESCertIntegration, } from "interfaces/integration"; export interface ICertAuthorityListData { @@ -76,21 +77,24 @@ export const getCertificateAuthority = ( digicertCerts?: ICertificatesIntegrationDigicert[], customProxies?: ICertificatesIntegrationCustomSCEP[] ) => { - if (id === "ndes") { - return ncspProxy as ICertificatesIntegrationNDES; + if (id === "ndes" && ncspProxy) { + return ncspProxy; } - if (id.includes("digicert")) { - return digicertCerts?.find( - (cert) => id.split("digicert-")[1] === cert.name - ) as ICertificatesIntegrationDigicert; + if (id.includes("digicert") && digicertCerts) { + return ( + digicertCerts.find((cert) => id.split("digicert-")[1] === cert.name) ?? + null + ); } - if (id.includes("custom-scep-proxy")) { - return customProxies?.find( - // TODO: remove custom scep id - (cert) => id.split("custom-scep-proxy-")[1] === cert.id.toString() - ) as ICertificatesIntegrationCustomSCEP; + if (id.includes("custom-scep-proxy") && customProxies) { + return ( + customProxies?.find( + // TODO: remove custom scep id + (cert) => id.split("custom-scep-proxy-")[1] === cert.id.toString() + ) ?? null + ); } return null; diff --git a/frontend/services/entities/certificates.ts b/frontend/services/entities/certificates.ts index 265376e3f9..928a0477a1 100644 --- a/frontend/services/entities/certificates.ts +++ b/frontend/services/entities/certificates.ts @@ -1,3 +1,4 @@ +import EditCertAuthorityModal from "pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal"; import configAPI from "./config"; export default { @@ -5,6 +6,10 @@ export default { return configAPI.update(updateData); }, + editCertAuthorityModal: (updateData: any): Promise => { + return configAPI.update(updateData); + }, + deleteCertificateAuthority: (updateData: any): Promise => { return configAPI.update(updateData); },