diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index ab5270e749..ff142318de 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -169,10 +169,6 @@ export interface IConfig { disable_data_sync: boolean; recent_vulnerability_max_age: number; }; - // Note: `vulnerability_settings` is deprecated and should not be used - // vulnerability_settings: { - // databases_path: string; - // }; webhook_settings: IWebhookSettings; integrations: IGlobalIntegrations; logging: { diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 26e7f79eda..891aba0a54 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -25,8 +25,8 @@ export interface ICertificatesIntegrationNDES { } export interface ICertificatesIntegrationDigicert { - id: number; name: string; + url: string; api_token: string; profile_id: string; certificate_common_name: string; @@ -37,10 +37,18 @@ export interface ICertificatesIntegrationDigicert { export interface ICertificatesIntegrationCustomSCEP { id: number; name: string; - server_url: string; + url: string; challenge: string; } +export type ICertificateAuthorityType = "ndes" | "digicert" | "custom"; + +/** all the types of certificate integrations */ +export type ICertificateIntegration = + | ICertificatesIntegrationNDES + | ICertificatesIntegrationDigicert + | ICertificatesIntegrationCustomSCEP; + export interface IIntegration { url: string; username?: string; diff --git a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx index 40d175edff..7b82f974ed 100644 --- a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx +++ b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx @@ -34,12 +34,12 @@ const integrationSettingsNavItems: ISideNavItem[] = [ Card: ChangeManagement, }, // TODO: digicert update: add this back when the feature is ready - // { - // title: "Certificates", - // urlSection: "certificate-authorities", - // path: PATHS.ADMIN_INTEGRATIONS_CERTIFICATE_AUTHORITIES, - // Card: CertificateAuthorities, - // }, + { + title: "Certificates", + urlSection: "certificate-authorities", + path: PATHS.ADMIN_INTEGRATIONS_CERTIFICATE_AUTHORITIES, + Card: CertificateAuthorities, + }, ]; export default integrationSettingsNavItems; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx index a639c6cc5d..4145346f9f 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx @@ -3,7 +3,7 @@ import { Link } from "react-router"; import paths from "router/paths"; import { AppContext } from "context/app"; -import createMockConfig from "__mocks__/configMock"; +import { ICertificateIntegration } from "interfaces/integration"; import SectionHeader from "components/SectionHeader"; import CustomLink from "components/CustomLink"; @@ -12,52 +12,16 @@ import CertificateAuthorityList from "./components/CertificateAuthorityList"; import { generateListData, getCertificateAuthority, - ICertAuthority, + ICertAuthorityListData, } from "./helpers"; import AddCertAuthorityCard from "./components/AddCertAuthorityCard"; +import DeleteCertificateAuthorityModal from "./components/DeleteCertificateAuthorityModal"; +import AddCertAuthorityModal from "./components/AddCertAuthorityModal"; const baseClass = "certificate-authorities"; const CertificateAuthorities = () => { - let { config } = useContext(AppContext); - config = createMockConfig({ - integrations: { - zendesk: [], - jira: [], - digicert: [ - { - name: "DigiCert CA", - id: 1, - api_token: "123456", - profile_id: "7ed77396-9186-4bfa-9fa7-63dddc46b8a3", - certificate_common_name: - "$FLEET_VAR_HOST_HARDWARE_SERIAL@example.com", - certificate_user_principal_names: ["$FLEET_VAR_HOST_HARDWARE_SERIAL"], - certificate_seat_id: "$FLEET_VAR_HOST_HARDWARE_SERIAL@example.com", - }, - ], - ndes_scep_proxy: { - url: "https://ndes.scep.com", - admin_url: "https://ndes.scep.com/admin", - username: "ndes", - password: "password", - }, - custom_scep_proxy: [ - { - id: 1, - name: "Custom SCEP Proxy", - server_url: "https://custom.scep.com", - challenge: "challenge", - }, - { - id: 2, - name: "Custom SCEP Proxy 2", - server_url: "https://custom.scep2.com", - challenge: "challenge-2", - }, - ], - }, - }); + const { config } = useContext(AppContext); const [showAddCertAuthorityModal, setShowAddCertAuthorityModal] = useState( false @@ -70,6 +34,14 @@ const CertificateAuthorities = () => { setShowDeleteCertAuthorityModal, ] = useState(false); + const [selectedListItemId, setSelectedListItemId] = useState( + null + ); + const [ + selectedCertAuthority, + setSelectedCertAuthority, + ] = useState(null); + const certificateAuthorities = useMemo(() => { if (!config) return []; return generateListData( @@ -83,26 +55,29 @@ const CertificateAuthorities = () => { setShowAddCertAuthorityModal(true); }; - const onEditCertAuthority = (cert: ICertAuthority) => { + const onEditCertAuthority = (cert: ICertAuthorityListData) => { // TODO: use useCallback - const ca = getCertificateAuthority( + const certAuthority = getCertificateAuthority( cert.id, config?.integrations.ndes_scep_proxy, config?.integrations.digicert, config?.integrations.custom_scep_proxy ); - console.log(ca); + setSelectedListItemId(cert.id); + setSelectedCertAuthority(certAuthority); setShowEditCertAuthorityModal(true); }; - const onDeleteCertAuthority = (cert: ICertAuthority) => { + const onDeleteCertAuthority = (cert: ICertAuthorityListData) => { // TODO: use useCallback - getCertificateAuthority( + const certAuthority = getCertificateAuthority( cert.id, config?.integrations.ndes_scep_proxy, config?.integrations.digicert, config?.integrations.custom_scep_proxy ); + setSelectedListItemId(cert.id); + setSelectedCertAuthority(certAuthority); setShowDeleteCertAuthorityModal(true); }; @@ -138,9 +113,21 @@ const CertificateAuthorities = () => { />

{renderContent()} - {showAddCertAuthorityModal &&
Modal showing
} + {showAddCertAuthorityModal && ( + setShowAddCertAuthorityModal(false)} + /> + )} {showEditCertAuthorityModal &&
Modal showing
} - {showDeleteCertAuthoirtyModal &&
Modal showing
} + {showDeleteCertAuthoirtyModal && + selectedCertAuthority && + selectedListItemId && ( + setShowDeleteCertAuthorityModal(false)} + /> + )} ); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx new file mode 100644 index 0000000000..38018dc204 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx @@ -0,0 +1,96 @@ +import React, { useContext, useState } from "react"; + +import { NotificationContext } from "context/notification"; +import certificatesAPI from "services/entities/certificates"; +import { ICertificateAuthorityType } from "interfaces/integration"; +import { AppContext } from "context/app"; + +// @ts-ignore +import Dropdown from "components/forms/fields/Dropdown"; +import Modal from "components/Modal"; + +import { generateErrorMessage } from "./helpers"; +import DigicertForm from "../DigicertForm"; +import { IDigicertFormData } from "../DigicertForm/DigicertForm"; +import { useCertAuthorityDataGenerator } from "../DeleteCertificateAuthorityModal/helpers"; + +const baseClass = "add-cert-authority-modal"; + +interface IAddCertAuthorityModalProps { + onExit: () => void; +} + +const AddCertAuthorityModal = ({ onExit }: IAddCertAuthorityModalProps) => { + const { setConfig } = useContext(AppContext); + const { renderFlash } = useContext(NotificationContext); + const [ + certAuthorityType, + setCertAuthorityType, + ] = useState("digicert"); + const [isAdding, setIsAdding] = useState(false); + const [formData, setFormData] = useState({ + name: "", + url: "https://one.digicert.com", + apiToken: "", + profileId: "", + commonName: "", + userPrincipalName: "", + certificateSeatId: "", + }); + const { generateAddPatchData } = useCertAuthorityDataGenerator( + certAuthorityType + ); + + const onChangeDropdown = (value: ICertificateAuthorityType) => { + setCertAuthorityType(value); + }; + + const onChangeForm = (update: { name: string; value: string }) => { + setFormData({ ...formData, [update.name]: update.value }); + }; + + const onAddCertAuthority = async () => { + const addPatchData = generateAddPatchData(formData); + setIsAdding(true); + try { + const newConfig = await certificatesAPI.addCertificateAuthority( + addPatchData + ); + renderFlash("success", "Successfully added your certificate authority."); + onExit(); + setConfig(newConfig); + } catch (e) { + renderFlash("error", generateErrorMessage(e)); + } + setIsAdding(false); + }; + + return ( + + <> + + + + + ); +}; + +export default AddCertAuthorityModal; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss new file mode 100644 index 0000000000..8457d0880e --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss @@ -0,0 +1,6 @@ +.add-cert-authority-modal { + + &__cert-authority-dropdown { + margin-bottom: $pad-large; + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx new file mode 100644 index 0000000000..d548cec89c --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx @@ -0,0 +1,7 @@ +const DEFAULT_ERROR_MESSAGE = + "Couldn't add certificate authority. Please try again."; + +// eslint-disable-next-line import/prefer-default-export +export const generateErrorMessage = (e: unknown) => { + return DEFAULT_ERROR_MESSAGE; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/index.ts new file mode 100644 index 0000000000..9965f127fa --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/index.ts @@ -0,0 +1 @@ +export { default } from "./AddCertAuthorityModal"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListItem/CertAuthorityListItem.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListItem/CertAuthorityListItem.tsx index 3c70797613..f9ba0adbbc 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListItem/CertAuthorityListItem.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListItem/CertAuthorityListItem.tsx @@ -5,7 +5,7 @@ import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; -import { ICertAuthority } from "../../helpers"; +import { ICertAuthorityListData } from "../../helpers"; const baseClass = "cert-authority-list-item"; @@ -17,7 +17,7 @@ interface IActionsProps { const Actions = ({ onEdit, onDelete }: IActionsProps) => { return ( <> - ( )} - /> + /> */} ( @@ -48,7 +48,7 @@ const Actions = ({ onEdit, onDelete }: IActionsProps) => { }; interface ICertAuthorityListItemProps { - cert: ICertAuthority; + cert: ICertAuthorityListData; onClickEdit: () => void; onClickDelete: () => void; } @@ -60,7 +60,7 @@ const CertAuthorityListItem = ({ }: ICertAuthorityListItemProps) => { return ( void; - onClickEdit: (cert: ICertAuthority) => void; - onClickDelete: (cert: ICertAuthority) => void; + onClickEdit: (cert: ICertAuthorityListData) => void; + onClickDelete: (cert: ICertAuthorityListData) => void; } const CertificateAuthorityList = ({ @@ -22,7 +22,7 @@ const CertificateAuthorityList = ({ onClickDelete, }: ICertificateAuthorityListProps) => { return ( - + className={baseClass} keyAttribute="name" listItems={certAuthorities} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss index 71bc9ca3e7..8111dd701d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss @@ -1,3 +1,11 @@ .certificate-authority-list { + .list-item__actions { + display: none; + } + &:hover { + .list-item__actions { + display: flex; + } + } } diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx new file mode 100644 index 0000000000..e286a9e76e --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/DeleteCertificateAuthorityModal.tsx @@ -0,0 +1,99 @@ +import React, { useContext, useState } from "react"; + +import { + ICertificateAuthorityType, + ICertificateIntegration, +} from "interfaces/integration"; +import certificatesAPI from "services/entities/certificates"; +import { NotificationContext } from "context/notification"; +import { AppContext } from "context/app"; + +import Button from "components/buttons/Button"; +import Modal from "components/Modal"; + +import { + generateCertAuthorityDisplayName, + useCertAuthorityDataGenerator, +} from "./helpers"; + +const baseClass = "delete-certificate-authority-modal"; + +interface IDeleteCertificateAuthorityModalProps { + listItemId: string; + certAuthority: ICertificateIntegration; + onExit: () => void; +} + +const DeleteCertificateAuthorityModal = ({ + listItemId, + certAuthority, + onExit, +}: IDeleteCertificateAuthorityModalProps) => { + const certAuthorityType = listItemId.split( + "-" + )[0] as ICertificateAuthorityType; + + const { generateDeletePatchData } = useCertAuthorityDataGenerator( + certAuthorityType, + certAuthority + ); + const { setConfig } = useContext(AppContext); + const { renderFlash } = useContext(NotificationContext); + const [isUpdating, setIsUpdating] = useState(false); + + const onDeleteCertAuthority = async () => { + setIsUpdating(true); + try { + const newConfig = await certificatesAPI.deleteCertificateAuthority( + generateDeletePatchData() + ); + renderFlash( + "success", + "Successfully deleted your certificate authority." + ); + setConfig(newConfig); + } catch (e) { + renderFlash( + "error", + "Couldn't delete certificate authority. Please try again." + ); + } + setIsUpdating(false); + onExit(); + }; + + const certAuthorityName = generateCertAuthorityDisplayName( + certAuthorityType, + certAuthority + ); + + return ( + + <> +

+ Fleet won't remove certificates from the certificate authority ( + {certAuthorityName}) on existing hosts. +

+
+ + +
+ +
+ ); +}; + +export default DeleteCertificateAuthorityModal; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/_styles.scss new file mode 100644 index 0000000000..dbe4b65e62 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/_styles.scss @@ -0,0 +1,3 @@ +.delete-certificate-authority-modal { + +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts new file mode 100644 index 0000000000..563e4d49aa --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts @@ -0,0 +1,125 @@ +import { AppContext } from "context/app"; + +import { + ICertificateAuthorityType, + ICertificateIntegration, + ICertificatesIntegrationCustomSCEP, + ICertificatesIntegrationDigicert, + IGlobalIntegrations, +} from "interfaces/integration"; +import { useContext } from "react"; +import { IDigicertFormData } from "../DigicertForm/DigicertForm"; + +export const useCertAuthorityDataGenerator = ( + certAuthorityType: ICertificateAuthorityType, + certAuthority?: ICertificateIntegration +) => { + const { config } = useContext(AppContext); + + const generateAddPatchData = (formData: IDigicertFormData) => { + if (!config) return null; + + 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; + } + + return data; + }; + + /** + * 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 = () => { + if (!config) return null; + + const data: { integrations: Partial } = { + integrations: {}, + }; + + switch (certAuthorityType) { + case "ndes": + data.integrations.ndes_scep_proxy = null; + break; + case "digicert": + data.integrations.digicert = config.integrations.digicert?.filter( + (cert) => { + return ( + (certAuthority as ICertificatesIntegrationDigicert).name !== + cert.name + ); + } + ); + break; + case "custom": + data.integrations.custom_scep_proxy = config.integrations.custom_scep_proxy?.filter( + (cert) => { + return ( + (certAuthority as ICertificatesIntegrationCustomSCEP).id === + cert.id + ); + } + ); + break; + default: + break; + } + + return data; + }; + + /** + * 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; + }; + + return { + generateAddPatchData, + generateDeletePatchData, + generateEditPatchData, + }; +}; + +export const generateCertAuthorityDisplayName = ( + certAuthorityType: ICertificateAuthorityType, + certAuthority: ICertificateIntegration +) => { + switch (certAuthorityType) { + case "ndes": + return "NDES"; + case "digicert": + return (certAuthority as ICertificatesIntegrationDigicert).name; + case "custom": + return (certAuthority as ICertificatesIntegrationCustomSCEP).name; + default: + return ""; + } +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/index.ts new file mode 100644 index 0000000000..d942a39da8 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/index.ts @@ -0,0 +1 @@ +export { default } from "./DeleteCertificateAuthorityModal"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx new file mode 100644 index 0000000000..53237eec62 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx @@ -0,0 +1,167 @@ +import React, { useState } from "react"; + +// @ts-ignore +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"; + +const baseClass = "digicert-form"; + +export interface IDigicertFormData { + name: string; + url: string; + apiToken: string; + profileId: string; + commonName: string; + userPrincipalName: string; + certificateSeatId: string; +} + +interface IDigicertFormProps { + formData: IDigicertFormData; + submitBtnText: string; + isSubmitting: boolean; + onChange: (update: { name: string; value: string }) => void; + onSubmit: () => void; + onCancel: () => void; +} + +const DigicertForm = ({ + formData, + submitBtnText, + isSubmitting, + onChange, + onSubmit, + onCancel, +}: IDigicertFormProps) => { + const [formValidation, setFormValidation] = useState( + { + isValid: false, + } + ); + + const { + name, + url, + apiToken, + profileId, + commonName, + userPrincipalName, + certificateSeatId, + } = formData; + + const onSubmitForm = (evt: React.FormEvent) => { + evt.preventDefault(); + onSubmit(); + }; + + const onInputChange = (update: { name: string; value: string }) => { + setFormValidation( + generateFormValidation({ ...formData, [update.name]: update.value }) + ); + onChange(update); + }; + + return ( +
+
+ + + + + You can find the Profile GUID by opening one of the{" "} + + + } + /> + + + +
+
+ + + + +
+
+ ); +}; + +export default DigicertForm; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/_styles.scss new file mode 100644 index 0000000000..2c5187f9aa --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/_styles.scss @@ -0,0 +1,13 @@ +.digicert-form { + &__fields { + display: flex; + flex-direction: column; + gap: $pad-large; + } + + &__cta { + display: flex; + flex-direction: row-reverse; + gap: $pad-medium; + } +} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts new file mode 100644 index 0000000000..2d89ca38ba --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts @@ -0,0 +1,144 @@ +import valid_url from "components/forms/validators/valid_url"; +import { IDigicertFormData } from "./DigicertForm"; + +// TODO: create a validator abstraction for this and the other form validation files + +export interface IDigicertFormValidation { + isValid: boolean; + name?: { isValid: boolean; message?: string }; + url?: { isValid: boolean; message?: string }; + apiToken?: { isValid: boolean }; + profileId?: { isValid: boolean }; + commonName?: { isValid: boolean }; + certificateSeatId?: { isValid: boolean }; +} + +type IMessageFunc = (formData: IDigicertFormData) => string; +type IValidationMessage = string | IMessageFunc; +type IFormValidationKey = keyof Omit; + +interface IValidation { + name: string; + isValid: (formData: IDigicertFormData) => boolean; + message?: IValidationMessage; +} + +const FORM_VALIDATIONS: Record< + IFormValidationKey, + { validations: IValidation[] } +> = { + name: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.name.length > 0; + }, + }, + { + name: "invalidCharacters", + isValid: (formData: IDigicertFormData) => { + return /^[a-zA-Z0-9_]+$/.test(formData.name); + }, + message: + "Inalid characters. Only letters, numbers and underscores allowed.", + }, + ], + }, + url: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.url.length > 0; + }, + }, + { + name: "validUrl", + isValid: (formData: IDigicertFormData) => { + return valid_url({ url: formData.url }); + }, + message: (formData: IDigicertFormData) => + `${formData.url} is not a valid URL`, + }, + ], + }, + apiToken: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.apiToken.length > 0; + }, + }, + ], + }, + profileId: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.profileId.length > 0; + }, + }, + ], + }, + commonName: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.commonName.length > 0; + }, + }, + ], + }, + certificateSeatId: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.certificateSeatId.length > 0; + }, + }, + ], + }, +}; + +const getErrorMessage = ( + formData: IDigicertFormData, + message?: IValidationMessage +) => { + if (message === undefined || typeof message === "string") { + return message; + } + return message(formData); +}; + +// eslint-disable-next-line import/prefer-default-export +export const generateFormValidation = (formData: IDigicertFormData) => { + const formValidation: IDigicertFormValidation = { + isValid: true, + }; + + Object.keys(FORM_VALIDATIONS).forEach((key) => { + const objKey = key as keyof typeof FORM_VALIDATIONS; + const failedValidation = FORM_VALIDATIONS[objKey].validations.find( + (validation) => !validation.isValid(formData) + ); + + if (!failedValidation) { + formValidation[objKey] = { + isValid: true, + }; + } else { + formValidation.isValid = false; + formValidation[objKey] = { + isValid: false, + message: getErrorMessage(formData, failedValidation.message), + }; + } + }); + + return formValidation; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/index.ts new file mode 100644 index 0000000000..5371d1b145 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/index.ts @@ -0,0 +1 @@ +export { default } from "./DigicertForm"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx index a69feb63f4..65228043a1 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx @@ -4,7 +4,7 @@ import { ICertificatesIntegrationNDES, } from "interfaces/integration"; -export interface ICertAuthority { +export interface ICertAuthorityListData { id: string; name: string; description: string; @@ -15,7 +15,7 @@ export const generateListData = ( digicertCerts?: ICertificatesIntegrationDigicert[], customProxies?: ICertificatesIntegrationCustomSCEP[] ) => { - const listData: ICertAuthority[] = []; + const listData: ICertAuthorityListData[] = []; // these values for the certificateAuthority is meant to be a hard coded . if (ndesProxy) { @@ -29,7 +29,7 @@ export const generateListData = ( if (digicertCerts?.length) { digicertCerts.forEach((cert) => { listData.push({ - id: `digicert-${cert.id}`, + id: `digicert-${cert.name}`, name: cert.name, description: "DigiCert", }); @@ -51,6 +51,21 @@ export const generateListData = ( ); }; +export interface ICertIntegrationNDESWithListId + extends ICertificatesIntegrationNDES { + listId: string; +} + +export interface ICertIntegrationDigicertWithListId + extends ICertificatesIntegrationDigicert { + listId: string; +} + +export interface ICertIntegrationCustomSCEPWithListId + extends ICertificatesIntegrationCustomSCEP { + listId: string; +} + /** * Gets the original certificate aithority integration object from the id * of the data representing a certificate authority list item. @@ -67,14 +82,15 @@ export const getCertificateAuthority = ( if (id.includes("digicert")) { return digicertCerts?.find( - (cert) => id.split("digicert-")[1] === cert.id.toString() - ); + (cert) => id.split("digicert-")[1] === cert.name + ) as ICertificatesIntegrationDigicert; } 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; } return null; diff --git a/frontend/services/entities/certificates.ts b/frontend/services/entities/certificates.ts new file mode 100644 index 0000000000..265376e3f9 --- /dev/null +++ b/frontend/services/entities/certificates.ts @@ -0,0 +1,11 @@ +import configAPI from "./config"; + +export default { + addCertificateAuthority: (updateData: any): Promise => { + return configAPI.update(updateData); + }, + + deleteCertificateAuthority: (updateData: any): Promise => { + return configAPI.update(updateData); + }, +};