mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 00:49:03 +00:00
add and delete digicert (#27083)
For #26606 This adds the ability to add and delete digicert integrations in the fleet UI. this includes: **The delete modal**  **the add digicert modal**  **integration with API to perform these actions** <!-- Note that API documentation changes are now addressed by the product design team. --> - [ ] Added/updated automated tests - [ ] Manual QA for all new/changed functionality
This commit is contained in:
parent
3195d0f974
commit
2f97fecf39
21 changed files with 765 additions and 76 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ const integrationSettingsNavItems: ISideNavItem<any>[] = [
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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<string | null>(
|
||||
null
|
||||
);
|
||||
const [
|
||||
selectedCertAuthority,
|
||||
setSelectedCertAuthority,
|
||||
] = useState<ICertificateIntegration | null>(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 = () => {
|
|||
/>
|
||||
</p>
|
||||
{renderContent()}
|
||||
{showAddCertAuthorityModal && <div>Modal showing</div>}
|
||||
{showAddCertAuthorityModal && (
|
||||
<AddCertAuthorityModal
|
||||
onExit={() => setShowAddCertAuthorityModal(false)}
|
||||
/>
|
||||
)}
|
||||
{showEditCertAuthorityModal && <div>Modal showing</div>}
|
||||
{showDeleteCertAuthoirtyModal && <div>Modal showing</div>}
|
||||
{showDeleteCertAuthoirtyModal &&
|
||||
selectedCertAuthority &&
|
||||
selectedListItemId && (
|
||||
<DeleteCertificateAuthorityModal
|
||||
listItemId={selectedListItemId}
|
||||
certAuthority={selectedCertAuthority}
|
||||
onExit={() => setShowDeleteCertAuthorityModal(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<ICertificateAuthorityType>("digicert");
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [formData, setFormData] = useState<IDigicertFormData>({
|
||||
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 (
|
||||
<Modal
|
||||
className={baseClass}
|
||||
title="Add certificate authority (CA)"
|
||||
width="large"
|
||||
onExit={onExit}
|
||||
>
|
||||
<>
|
||||
<Dropdown
|
||||
options={[{ label: "Digicert", value: "digicert" }]}
|
||||
value={certAuthorityType}
|
||||
className={`${baseClass}__cert-authority-dropdown`}
|
||||
onChange={onChangeDropdown}
|
||||
searchable={false}
|
||||
/>
|
||||
<DigicertForm
|
||||
formData={formData}
|
||||
submitBtnText="Add CA"
|
||||
isSubmitting={isAdding}
|
||||
onChange={onChangeForm}
|
||||
onSubmit={onAddCertAuthority}
|
||||
onCancel={onExit}
|
||||
/>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddCertAuthorityModal;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
.add-cert-authority-modal {
|
||||
|
||||
&__cert-authority-dropdown {
|
||||
margin-bottom: $pad-large;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./AddCertAuthorityModal";
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<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) => (
|
||||
|
|
@ -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 (
|
||||
<ListItem
|
||||
className={`${baseClass}__list-item`}
|
||||
className={baseClass}
|
||||
graphic="file-certificate"
|
||||
title={cert.name}
|
||||
details={cert.name}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ import UploadList from "pages/ManageControlsPage/components/UploadList";
|
|||
|
||||
import CertAuthorityListHeader from "../CertAuthorityListHeader";
|
||||
import CertAuthorityListItem from "../CertAuthorityListItem";
|
||||
import { ICertAuthority } from "../../helpers";
|
||||
import { ICertAuthorityListData } from "../../helpers";
|
||||
|
||||
const baseClass = "certificate-authority-list";
|
||||
|
||||
interface ICertificateAuthorityListProps {
|
||||
certAuthorities: ICertAuthority[];
|
||||
certAuthorities: ICertAuthorityListData[];
|
||||
onAddCertAuthority: () => 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 (
|
||||
<UploadList<ICertAuthority>
|
||||
<UploadList<ICertAuthorityListData>
|
||||
className={baseClass}
|
||||
keyAttribute="name"
|
||||
listItems={certAuthorities}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
.certificate-authority-list {
|
||||
.list-item__actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.list-item__actions {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Modal
|
||||
className={baseClass}
|
||||
title="Delete certificate authority (CA)"
|
||||
onExit={onExit}
|
||||
>
|
||||
<>
|
||||
<p>
|
||||
Fleet won't remove certificates from the certificate authority (
|
||||
<b>{certAuthorityName}</b>) on existing hosts.
|
||||
</p>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button
|
||||
variant="alert"
|
||||
onClick={onDeleteCertAuthority}
|
||||
isLoading={isUpdating}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="inverse-alert" onClick={onExit}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteCertificateAuthorityModal;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.delete-certificate-authority-modal {
|
||||
|
||||
}
|
||||
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
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<IGlobalIntegrations> } = {
|
||||
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 "";
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./DeleteCertificateAuthorityModal";
|
||||
|
|
@ -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<IDigicertFormValidation>(
|
||||
{
|
||||
isValid: false,
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
name,
|
||||
url,
|
||||
apiToken,
|
||||
profileId,
|
||||
commonName,
|
||||
userPrincipalName,
|
||||
certificateSeatId,
|
||||
} = formData;
|
||||
|
||||
const onSubmitForm = (evt: React.FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
const onInputChange = (update: { name: string; value: string }) => {
|
||||
setFormValidation(
|
||||
generateFormValidation({ ...formData, [update.name]: update.value })
|
||||
);
|
||||
onChange(update);
|
||||
};
|
||||
|
||||
return (
|
||||
<form className={baseClass} onSubmit={onSubmitForm}>
|
||||
<div className={`${baseClass}__fields`}>
|
||||
<InputField
|
||||
name="name"
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={onInputChange}
|
||||
error={formValidation.name?.message}
|
||||
helpText="Letters, numbers, and underscores only. Fleet will create configuration profile variables with the name as suffix (e.g. $FLEET_VAR_CERT_DATA_DIGICERT_WIFI)."
|
||||
parseTarget
|
||||
placeholder="DIGICERT_WIFI"
|
||||
/>
|
||||
<InputField
|
||||
name="url"
|
||||
label="URL"
|
||||
value={url}
|
||||
onChange={onInputChange}
|
||||
error={formValidation.url?.message}
|
||||
parseTarget
|
||||
helpText="DigiCert ONE instance URL."
|
||||
/>
|
||||
<InputField
|
||||
type="password"
|
||||
name="apiToken"
|
||||
label="API token"
|
||||
value={apiToken}
|
||||
onChange={onInputChange}
|
||||
parseTarget
|
||||
helpText="DigiCert One API token for service user."
|
||||
/>
|
||||
<InputField
|
||||
name="profileId"
|
||||
label="Profile GUID"
|
||||
value={profileId}
|
||||
onChange={onInputChange}
|
||||
parseTarget
|
||||
helpText={
|
||||
<>
|
||||
You can find the <b>Profile GUID</b> by opening one of the{" "}
|
||||
<CustomLink
|
||||
text="Digicert profiles"
|
||||
url="https://demo.one.digicert.com/mpki/policies/profiles"
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
name="commonName"
|
||||
label="Certificate common name (CN)"
|
||||
value={commonName}
|
||||
onChange={onInputChange}
|
||||
parseTarget
|
||||
helpText="Certificates delivered to your hosts will have this CN in the subject."
|
||||
placeholder="$FLEET_VAR_HOST_HARDWARE_SERIAL"
|
||||
/>
|
||||
<InputField
|
||||
name="userPrincipalName"
|
||||
label="User principal name (UPN)"
|
||||
value={userPrincipalName}
|
||||
onChange={onInputChange}
|
||||
parseTarget
|
||||
helpText="Certificates delivered to your hosts will have this UPN attribute in Subject Alternative Name (SAN). (optional)"
|
||||
placeholder="$FLEET_VAR_HOST_HARDWARE_SERIAL"
|
||||
/>
|
||||
<InputField
|
||||
name="certificateSeatId"
|
||||
label="Certificate seat ID"
|
||||
value={certificateSeatId}
|
||||
onChange={onInputChange}
|
||||
parseTarget
|
||||
helpText="Certificates delivered to your hosts will be assigned to this seat ID in DigiCert."
|
||||
placeholder="$FLEET_VAR_HOST_HARDWARE_SERIAL"
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__cta`}>
|
||||
<TooltipWrapper
|
||||
tipContent="Complete all required fields to save."
|
||||
underline={false}
|
||||
position="top"
|
||||
disableTooltip={formValidation.isValid}
|
||||
showArrow
|
||||
>
|
||||
<Button
|
||||
isLoading={isSubmitting}
|
||||
disabled={!formValidation.isValid || isSubmitting}
|
||||
type="submit"
|
||||
>
|
||||
{submitBtnText}
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<Button variant="inverse" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default DigicertForm;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IDigicertFormValidation, "isValid">;
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./DigicertForm";
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
11
frontend/services/entities/certificates.ts
Normal file
11
frontend/services/entities/certificates.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import configAPI from "./config";
|
||||
|
||||
export default {
|
||||
addCertificateAuthority: (updateData: any): Promise<any> => {
|
||||
return configAPI.update(updateData);
|
||||
},
|
||||
|
||||
deleteCertificateAuthority: (updateData: any): Promise<any> => {
|
||||
return configAPI.update(updateData);
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue