add UI for editing digicert CA (#27103)

This commit is contained in:
Gabriel Hernandez 2025-03-13 19:41:22 +00:00 committed by GitHub
parent a86253d2bf
commit e85b46e915
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 296 additions and 56 deletions

View file

@ -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 */

View file

@ -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 && <div>Modal showing</div>}
{showDeleteCertAuthoirtyModal &&
{showEditCertAuthorityModal && selectedCertAuthority && (
<EditCertAuthorityModal
certAuthority={selectedCertAuthority}
onExit={() => setShowEditCertAuthorityModal(false)}
/>
)}
{showDeleteCertAuthorityModal &&
selectedCertAuthority &&
selectedListItemId && (
<DeleteCertificateAuthorityModal

View file

@ -14,6 +14,10 @@ import DigicertForm from "../DigicertForm";
import { IDigicertFormData } from "../DigicertForm/DigicertForm";
import { useCertAuthorityDataGenerator } from "../DeleteCertificateAuthorityModal/helpers";
export type ICertFormData = IDigicertFormData;
// | IAnotherCertFormData
// | IYetAnotherCertFormData;
const baseClass = "add-cert-authority-modal";
interface IAddCertAuthorityModalProps {

View file

@ -17,7 +17,7 @@ interface IActionsProps {
const Actions = ({ onEdit, onDelete }: IActionsProps) => {
return (
<>
{/* <GitOpsModeTooltipWrapper
<GitOpsModeTooltipWrapper
position="left"
renderChildren={(disableChildren) => (
<Button
@ -29,7 +29,7 @@ const Actions = ({ onEdit, onDelete }: IActionsProps) => {
<Icon name="pencil" color="ui-fleet-black-75" />
</Button>
)}
/> */}
/>
<GitOpsModeTooltipWrapper
position="left"
renderChildren={(disableChildren) => (

View file

@ -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<IGlobalIntegrations> } = {
integrations: {},
};
const data: { integrations: Partial<IGlobalIntegrations> } = {
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<IGlobalIntegrations> } = {
@ -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<IGlobalIntegrations> } = {
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,

View file

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

View file

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

View file

@ -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<ICertFormData>(() =>
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 (
<FormComponent
formData={formData}
submitBtnText="Save"
isSubmitting={isUpdating}
onChange={onChangeForm}
onSubmit={onEditCertAuthority}
onCancel={onExit}
/>
);
};
return (
<Modal
className={baseClass}
title="Edit certificate authority (CA)"
width="large"
onExit={onExit}
>
{renderForm()}
</Modal>
);
};
export default EditCertAuthorityModal;

View file

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

View file

@ -0,0 +1 @@
export { default } from "./EditCertAuthorityModal";

View file

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

View file

@ -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<any> => {
return configAPI.update(updateData);
},
deleteCertificateAuthority: (updateData: any): Promise<any> => {
return configAPI.update(updateData);
},