diff --git a/frontend/interfaces/integration.ts b/frontend/interfaces/integration.ts index 1dbd911c2a..f951838e92 100644 --- a/frontend/interfaces/integration.ts +++ b/frontend/interfaces/integration.ts @@ -35,7 +35,6 @@ export interface ICertificatesIntegrationDigicert { } export interface ICertificatesIntegrationCustomSCEP { - id: number; name: string; url: string; challenge: string; @@ -65,7 +64,9 @@ export const isDigicertCertIntegration = ( export const isCustomSCEPCertIntegration = ( integration: ICertificateIntegration ): integration is ICertificatesIntegrationCustomSCEP => { - return "id" in integration && "challenge" in integration; + return ( + "name" in integration && "url" in integration && "challenge" in integration + ); }; export type ICertificateAuthorityType = "ndes" | "digicert" | "custom"; diff --git a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx index 7b82f974ed..90eae34f82 100644 --- a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx +++ b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx @@ -36,7 +36,7 @@ const integrationSettingsNavItems: ISideNavItem[] = [ // TODO: digicert update: add this back when the feature is ready { title: "Certificates", - urlSection: "certificate-authorities", + urlSection: "certificates", path: PATHS.ADMIN_INTEGRATIONS_CERTIFICATE_AUTHORITIES, Card: CertificateAuthorities, }, diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx index b1dbf85149..98758e0519 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/AddCertAuthorityModal.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; import { NotificationContext } from "context/notification"; import certificatesAPI from "services/entities/certificates"; @@ -9,14 +9,20 @@ import { AppContext } from "context/app"; import Dropdown from "components/forms/fields/Dropdown"; import Modal from "components/Modal"; -import { generateErrorMessage } from "./helpers"; +import { generateDropdownOptions, getErrorMessage } from "./helpers"; + import DigicertForm from "../DigicertForm"; import { IDigicertFormData } from "../DigicertForm/DigicertForm"; import { useCertAuthorityDataGenerator } from "../DeleteCertificateAuthorityModal/helpers"; +import NDESForm from "../NDESForm"; +import { INDESFormData } from "../NDESForm/NDESForm"; +import CustomSCEPForm from "../CustomSCEPForm"; +import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm"; -export type ICertFormData = IDigicertFormData; -// | IAnotherCertFormData -// | IYetAnotherCertFormData; +export type ICertFormData = + | IDigicertFormData + | INDESFormData + | ICustomSCEPFormData; const baseClass = "add-cert-authority-modal"; @@ -25,14 +31,14 @@ interface IAddCertAuthorityModalProps { } const AddCertAuthorityModal = ({ onExit }: IAddCertAuthorityModalProps) => { - const { setConfig } = useContext(AppContext); + const { config, setConfig } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); const [ certAuthorityType, setCertAuthorityType, ] = useState("digicert"); const [isAdding, setIsAdding] = useState(false); - const [formData, setFormData] = useState({ + const [digicertFormData, setDigicertFormData] = useState({ name: "", url: "https://one.digicert.com", apiToken: "", @@ -41,6 +47,21 @@ const AddCertAuthorityModal = ({ onExit }: IAddCertAuthorityModalProps) => { userPrincipalName: "", certificateSeatId: "", }); + const [ndesFormData, setNDESFormData] = useState({ + scepURL: "", + adminURL: "", + username: "", + password: "", + }); + const [ + customSCEPFormData, + setCustomSCEPFormData, + ] = useState({ + name: "", + scepURL: "", + challenge: "", + }); + const { generateAddPatchData } = useCertAuthorityDataGenerator( certAuthorityType ); @@ -50,10 +71,47 @@ const AddCertAuthorityModal = ({ onExit }: IAddCertAuthorityModalProps) => { }; const onChangeForm = (update: { name: string; value: string }) => { - setFormData({ ...formData, [update.name]: update.value }); + let setFormData; + let formData: ICertFormData; + switch (certAuthorityType) { + case "digicert": + setFormData = setDigicertFormData; + formData = digicertFormData; + break; + case "ndes": + setFormData = setNDESFormData; + formData = ndesFormData; + break; + case "custom": + setFormData = setCustomSCEPFormData; + formData = customSCEPFormData; + break; + default: + return; + } + + (setFormData as React.Dispatch>)({ + ...formData, + [update.name]: update.value, + }); }; const onAddCertAuthority = async () => { + let formData: ICertFormData; + switch (certAuthorityType) { + case "digicert": + formData = digicertFormData; + break; + case "ndes": + formData = ndesFormData; + break; + case "custom": + formData = customSCEPFormData; + break; + default: + return; + } + const addPatchData = generateAddPatchData(formData); setIsAdding(true); try { @@ -64,34 +122,74 @@ const AddCertAuthorityModal = ({ onExit }: IAddCertAuthorityModalProps) => { onExit(); setConfig(newConfig); } catch (e) { - renderFlash("error", generateErrorMessage(e)); + renderFlash("error", getErrorMessage(e)); } setIsAdding(false); }; + const dropdownOptions = useMemo(() => { + return generateDropdownOptions(!!config?.integrations.ndes_scep_proxy); + }, [config?.integrations.ndes_scep_proxy]); + + const renderForm = () => { + const submitBtnText = "Add CA"; + + switch (certAuthorityType) { + case "digicert": + return ( + + ); + case "ndes": + return ( + + ); + case "custom": + return ( + + ); + default: + return null; + } + }; + return ( <> - + {renderForm()} ); diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx index d548cec89c..e241dee672 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx @@ -1,7 +1,33 @@ +import { IDropdownOption } from "interfaces/dropdownOption"; + +const DEFAULT_CERT_AUTHORITY_OPTIONS: IDropdownOption[] = [ + { label: "Digicert", value: "digicert" }, + { + label: "Microsoft NDES (Network Device Enrollment Service)", + value: "ndes", + }, + { + label: "Custom (SCEP: Simple Certificate Enrollment Protocol)", + value: "custom", + }, +]; + +export const generateDropdownOptions = (hasNDESCert: boolean) => { + if (!hasNDESCert) { + return DEFAULT_CERT_AUTHORITY_OPTIONS; + } + + const ndesOption = DEFAULT_CERT_AUTHORITY_OPTIONS[1]; + ndesOption.disabled = true; + ndesOption.tooltipContent = "Only one NDES can be added."; + + return DEFAULT_CERT_AUTHORITY_OPTIONS; +}; + 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) => { +export const getErrorMessage = (e: unknown) => { return DEFAULT_ERROR_MESSAGE; }; 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 8111dd701d..32977840c4 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertificateAuthorityList/_styles.scss @@ -1,11 +1,9 @@ .certificate-authority-list { - .list-item__actions { + li .list-item__actions { display: none; } - &:hover { - .list-item__actions { - display: flex; - } + li:hover .list-item__actions { + display: flex; } } diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx new file mode 100644 index 0000000000..9be5a4d90f --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; + +// @ts-ignore +import InputField from "components/forms/fields/InputField"; +import Button from "components/buttons/Button"; +import TooltipWrapper from "components/TooltipWrapper"; + +import { ICustomSCEPFormValidation, validateFormData } from "./helpers"; + +const baseClass = "ndes-form"; + +export interface ICustomSCEPFormData { + name: string; + scepURL: string; + challenge: string; +} + +interface ICustomSCEPFormProps { + formData: ICustomSCEPFormData; + submitBtnText: string; + isSubmitting: boolean; + onChange: (update: { name: string; value: string }) => void; + onSubmit: () => void; + onCancel: () => void; +} + +const CustomSCEPForm = ({ + formData, + submitBtnText, + isSubmitting, + onChange, + onSubmit, + onCancel, +}: ICustomSCEPFormProps) => { + const [ + formValidation, + setFormValidation, + ] = useState({ + isValid: false, + }); + + const { name, scepURL, challenge } = formData; + + const onSubmitForm = (evt: React.FormEvent) => { + evt.preventDefault(); + onSubmit(); + }; + + const onInputChange = (update: { name: string; value: string }) => { + setFormValidation( + validateFormData({ ...formData, [update.name]: update.value }) + ); + onChange(update); + }; + + return ( +
+
+ + + +
+
+ + + + +
+
+ ); +}; + +export default CustomSCEPForm; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/_styles.scss new file mode 100644 index 0000000000..f50f4b38f5 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/_styles.scss @@ -0,0 +1,13 @@ +.custom-scep-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/CustomSCEPForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts new file mode 100644 index 0000000000..705ee3a192 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts @@ -0,0 +1,148 @@ +import valid_url from "components/forms/validators/valid_url"; + +import { ICustomSCEPFormData } from "./CustomSCEPForm"; + +// TODO: create a validator abstraction for this and the other form validation files + +export interface ICustomSCEPFormValidation { + isValid: boolean; + name?: { isValid: boolean; message?: string }; + scepURL?: { isValid: boolean; message?: string }; + challenge?: { isValid: boolean }; +} + +type IMessageFunc = (formData: ICustomSCEPFormData) => string; +type IValidationMessage = string | IMessageFunc; +type IFormValidationKey = keyof Omit; + +interface IValidation { + name: string; + isValid: (formData: ICustomSCEPFormData) => boolean; + message?: IValidationMessage; +} + +const FORM_VALIDATIONS: Record< + IFormValidationKey, + { validations: IValidation[] } +> = { + name: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.name.length > 0; + }, + }, + { + name: "invalidCharacters", + isValid: (formData: ICustomSCEPFormData) => { + return /^[a-zA-Z0-9_]+$/.test(formData.name); + }, + message: + "Inalid characters. Only letters, numbers and underscores allowed.", + }, + ], + }, + scepURL: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.scepURL.length > 0; + }, + }, + { + name: "validUrl", + isValid: (formData: ICustomSCEPFormData) => { + return valid_url({ url: formData.scepURL }); + }, + message: (formData: ICustomSCEPFormData) => + `${formData.scepURL} is not a valid URL`, + }, + ], + }, + challenge: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.challenge.length > 0; + }, + }, + ], + }, +}; + +const getErrorMessage = ( + formData: ICustomSCEPFormData, + message?: IValidationMessage +) => { + if (message === undefined || typeof message === "string") { + return message; + } + return message(formData); +}; + +// eslint-disable-next-line import/prefer-default-export +export const validateFormData = (formData: ICustomSCEPFormData) => { + const formValidation: ICustomSCEPFormValidation = { + 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; +}; + +const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; +const BAD_CREDENTIALS_ERROR = + "Couldn't add. Admin URL or credentials are invalid."; +const CACHE_ERROR = + "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again. By default, NDES caches 5 passwords and they expire 60 minutes after they are created."; +const INSUFFICIENT_PERMISSIONS_ERROR = + "Couldn't add. This account doesn't have sufficient permissions. Please use the account with enroll permission."; +const SCEP_URL_TIMEOUT_ERROR = + "Couldn't add. Request to NDES (SCEP URL) timed out. Please try again."; +const DEFAULT_ERROR = + "Something went wrong updating your SCEP server. Please try again."; + +// export const getErrorMessage = ( +// err: unknown, +// formData: ICustomSCEPFormData +// ) => { +// const reason = getErrorReason(err); + +// if (reason.includes("invalid admin URL or credentials")) { +// return BAD_CREDENTIALS_ERROR; +// } else if (reason.includes("the password cache is full")) { +// return CACHE_ERROR; +// } else if (reason.includes("does not have sufficient permissions")) { +// INSUFFICIENT_PERMISSIONS_ERROR; +// } else if ( +// reason.includes(formData.scepURL) && +// reason.includes("context deadline exceeded") +// ) { +// return SCEP_URL_TIMEOUT_ERROR; +// } else if (reason.includes("invalid SCEP URL")) { +// return BAD_SCEP_URL_ERROR; +// } + +// return DEFAULT_ERROR; +// }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/index.ts new file mode 100644 index 0000000000..d803791051 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/index.ts @@ -0,0 +1 @@ +export { default } from "./CustomSCEPForm"; 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 ec100e8067..21e3093d37 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DeleteCertificateAuthorityModal/helpers.ts @@ -9,6 +9,9 @@ import { } from "interfaces/integration"; import { useCallback, useContext } from "react"; import { IDigicertFormData } from "../DigicertForm/DigicertForm"; +import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal"; +import { INDESFormData } from "../NDESForm/NDESForm"; +import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm"; export const useCertAuthorityDataGenerator = ( certAuthorityType: ICertificateAuthorityType, @@ -17,7 +20,7 @@ export const useCertAuthorityDataGenerator = ( const { config } = useContext(AppContext); const generateAddPatchData = useCallback( - (formData: IDigicertFormData) => { + (formData: ICertFormData) => { if (!config) return null; const data: { integrations: Partial } = { @@ -26,22 +29,59 @@ export const useCertAuthorityDataGenerator = ( switch (certAuthorityType) { case "ndes": + // eslint-disable-next-line no-case-declarations + const { + scepURL: ndesSCEPUrl, + adminURL, + username, + password, + } = formData as INDESFormData; + data.integrations.ndes_scep_proxy = { + url: ndesSCEPUrl, + admin_url: adminURL, + username, + password, + }; break; case "digicert": + // eslint-disable-next-line no-case-declarations + const { + name: digicertName, + url, + apiToken, + profileId, + commonName, + userPrincipalName, + certificateSeatId, + } = formData as IDigicertFormData; 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, + name: digicertName, + url, + api_token: apiToken, + profile_id: profileId, + certificate_common_name: commonName, + certificate_user_principal_names: [userPrincipalName], + certificate_seat_id: certificateSeatId, }, ]; break; case "custom": + // eslint-disable-next-line no-case-declarations + const { + name: customSCEPName, + scepURL: customSCEPUrl, + challenge, + } = formData as ICustomSCEPFormData; + data.integrations.custom_scep_proxy = [ + ...(config.integrations.custom_scep_proxy || []), + { + name: customSCEPName, + url: customSCEPUrl, + challenge, + }, + ]; break; default: break; @@ -82,8 +122,8 @@ export const useCertAuthorityDataGenerator = ( data.integrations.custom_scep_proxy = config.integrations.custom_scep_proxy?.filter( (cert) => { return ( - (certAuthority as ICertificatesIntegrationCustomSCEP).id === - cert.id + (certAuthority as ICertificatesIntegrationCustomSCEP).name !== + cert.name ); } ); @@ -101,7 +141,7 @@ export const useCertAuthorityDataGenerator = ( * have to generate the correct data for the PATCH request. */ const generateEditPatchData = useCallback( - (formData: IDigicertFormData) => { + (formData: ICertFormData) => { if (!config) return null; const data: { integrations: Partial } = { @@ -110,8 +150,31 @@ export const useCertAuthorityDataGenerator = ( switch (certAuthorityType) { case "ndes": + // eslint-disable-next-line no-case-declarations + const { + scepURL: ndesSCEPUrl, + adminURL, + username, + password, + } = formData as INDESFormData; + data.integrations.ndes_scep_proxy = { + url: ndesSCEPUrl, + admin_url: adminURL, + username, + password, + }; break; case "digicert": + // eslint-disable-next-line no-case-declarations + const { + name: digicertName, + url, + apiToken, + profileId, + commonName, + userPrincipalName, + certificateSeatId, + } = formData as IDigicertFormData; data.integrations.digicert = config.integrations.digicert?.map( (cert) => { // only update the certificate authority that we are editing @@ -120,15 +183,13 @@ export const useCertAuthorityDataGenerator = ( 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, + name: digicertName, + url, + api_token: apiToken, + profile_id: profileId, + certificate_common_name: commonName, + certificate_user_principal_names: [userPrincipalName], + certificate_seat_id: certificateSeatId, }; } return cert; @@ -136,6 +197,28 @@ export const useCertAuthorityDataGenerator = ( ); break; case "custom": + // eslint-disable-next-line no-case-declarations + const { + name: customSCEPName, + scepURL: customSCEPUrl, + challenge, + } = formData as ICustomSCEPFormData; + data.integrations.custom_scep_proxy = config.integrations.custom_scep_proxy?.map( + (cert) => { + // only update the certificate authority that we are editing + if ( + (certAuthority as ICertificatesIntegrationCustomSCEP).name === + cert.name + ) { + return { + name: customSCEPName, + url: customSCEPUrl, + challenge, + }; + } + return cert; + } + ); break; default: break; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx index ecfe86cd05..1ac6919d36 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx @@ -5,19 +5,23 @@ import { AppContext } from "context/app"; import { ICertificateIntegration, isDigicertCertIntegration, + isNDESCertIntegration, } 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 DigicertForm from "../DigicertForm"; import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal"; import { useCertAuthorityDataGenerator } from "../DeleteCertificateAuthorityModal/helpers"; +import NDESForm from "../NDESForm"; +import CustomSCEPForm from "../CustomSCEPForm"; const baseClass = "edit-cert-authority-modal"; @@ -70,10 +74,13 @@ const EditCertAuthorityModal = ({ }; const getFormComponent = () => { + if (isNDESCertIntegration(certAuthority)) { + return NDESForm; + } if (isDigicertCertIntegration(certAuthority)) { return DigicertForm; } - return null; + return CustomSCEPForm; }; const renderForm = () => { @@ -82,6 +89,7 @@ const EditCertAuthorityModal = ({ return ( {renderForm()} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx index d3f2e80f48..8d52de3276 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx @@ -1,8 +1,11 @@ import { ICertificateAuthorityType, ICertificateIntegration, + ICertificatesIntegrationCustomSCEP, ICertificatesIntegrationDigicert, + ICertificatesIntegrationNDES, isCustomSCEPCertIntegration, + isDigicertCertIntegration, isNDESCertIntegration, } from "interfaces/integration"; @@ -16,21 +19,6 @@ 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 => { @@ -38,3 +26,33 @@ export const getCertificateAuthorityType = ( if (isCustomSCEPCertIntegration(certAuthority)) return "custom"; return "digicert"; }; + +export const generateDefaultFormData = ( + certAuthority: ICertificateIntegration +): ICertFormData => { + if (isNDESCertIntegration(certAuthority)) { + return { + scepURL: certAuthority.url, + adminURL: certAuthority.admin_url, + username: certAuthority.username, + password: certAuthority.password, + }; + } else if (isDigicertCertIntegration(certAuthority)) { + return { + name: certAuthority.name, + url: certAuthority.url, + apiToken: certAuthority.api_token, + profileId: certAuthority.profile_id, + commonName: certAuthority.certificate_common_name, + userPrincipalName: certAuthority.certificate_user_principal_names[0], + certificateSeatId: certAuthority.certificate_seat_id, + }; + } + + const customSCEPcert = certAuthority as ICertificatesIntegrationCustomSCEP; + return { + name: customSCEPcert.name, + scepURL: customSCEPcert.url, + challenge: customSCEPcert.challenge, + }; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx new file mode 100644 index 0000000000..892dcf3b42 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx @@ -0,0 +1,120 @@ +import React, { useState } from "react"; + +// @ts-ignore +import InputField from "components/forms/fields/InputField"; +import Button from "components/buttons/Button"; +import TooltipWrapper from "components/TooltipWrapper"; + +import { INDESFormValidation, validateFormData } from "./helpers"; + +const baseClass = "ndes-form"; + +export interface INDESFormData { + scepURL: string; + adminURL: string; + username: string; + password: string; +} + +interface INDESFormProps { + formData: INDESFormData; + submitBtnText: string; + isSubmitting: boolean; + onChange: (update: { name: string; value: string }) => void; + onSubmit: () => void; + onCancel: () => void; +} + +const NDESForm = ({ + formData, + submitBtnText, + isSubmitting, + onChange, + onSubmit, + onCancel, +}: INDESFormProps) => { + const [formValidation, setFormValidation] = useState({ + isValid: false, + }); + + const { scepURL, adminURL, username, password } = formData; + + const onSubmitForm = (evt: React.FormEvent) => { + evt.preventDefault(); + onSubmit(); + }; + + const onInputChange = (update: { name: string; value: string }) => { + setFormValidation( + validateFormData({ ...formData, [update.name]: update.value }) + ); + onChange(update); + }; + + return ( +
+
+ + + + +
+
+ + + + +
+
+ ); +}; + +export default NDESForm; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/_styles.scss new file mode 100644 index 0000000000..9b35245f0f --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/_styles.scss @@ -0,0 +1,13 @@ +.ndes-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/NDESForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts new file mode 100644 index 0000000000..3af2e45906 --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts @@ -0,0 +1,135 @@ +import { getErrorReason } from "interfaces/errors"; +import { INDESFormData } from "./NDESForm"; + +// TODO: create a validator abstraction for this and the other form validation files + +export interface INDESFormValidation { + isValid: boolean; + scepURL?: { isValid: boolean }; + adminURL?: { isValid: boolean }; + username?: { isValid: boolean }; + password?: { isValid: boolean }; +} + +type IMessageFunc = (formData: INDESFormData) => string; +type IValidationMessage = string | IMessageFunc; +type IFormValidationKey = keyof Omit; + +interface IValidation { + name: string; + isValid: (formData: INDESFormData) => boolean; + message?: IValidationMessage; +} + +const FORM_VALIDATIONS: Record< + IFormValidationKey, + { validations: IValidation[] } +> = { + scepURL: { + validations: [ + { + name: "required", + isValid: (formData: INDESFormData) => { + return formData.scepURL.length > 0; + }, + }, + ], + }, + adminURL: { + validations: [ + { + name: "required", + isValid: (formData: INDESFormData) => { + return formData.adminURL.length > 0; + }, + }, + ], + }, + username: { + validations: [ + { + name: "required", + isValid: (formData: INDESFormData) => { + return formData.username.length > 0; + }, + }, + ], + }, + password: { + validations: [ + { + name: "required", + isValid: (formData: INDESFormData) => { + return formData.password.length > 0; + }, + }, + ], + }, +}; + +// eslint-disable-next-line import/prefer-default-export +export const validateFormData = (formData: INDESFormData) => { + const formValidation: INDESFormValidation = { + 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, + }; + } + }); + + return formValidation; +}; + +const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; +const BAD_CREDENTIALS_ERROR = + "Couldn't add. Admin URL or credentials are invalid."; +const CACHE_ERROR = + "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again. By default, NDES caches 5 passwords and they expire 60 minutes after they are created."; +const INSUFFICIENT_PERMISSIONS_ERROR = + "Couldn't add. This account doesn't have sufficient permissions. Please use the account with enroll permission."; +const SCEP_URL_TIMEOUT_ERROR = + "Couldn't add. Request to NDES (SCEP URL) timed out. Please try again."; +const ADMIN_URL_TIMEOUT_ERROR = + "Couldn't add. Request to NDES (admin URL) timed out. Please try again."; +const DEFAULT_ERROR = + "Something went wrong updating your SCEP server. Please try again."; + +export const getErrorMessage = (err: unknown, formData: INDESFormData) => { + const reason = getErrorReason(err); + + if (reason.includes("invalid admin URL or credentials")) { + return BAD_CREDENTIALS_ERROR; + } else if (reason.includes("the password cache is full")) { + return CACHE_ERROR; + } else if (reason.includes("does not have sufficient permissions")) { + INSUFFICIENT_PERMISSIONS_ERROR; + } else if ( + reason.includes(formData.scepURL) && + reason.includes("context deadline exceeded") + ) { + return SCEP_URL_TIMEOUT_ERROR; + } else if ( + reason.includes(formData.adminURL) && + reason.includes("context deadline exceeded") + ) { + return ADMIN_URL_TIMEOUT_ERROR; + } else if (reason.includes("invalid SCEP URL")) { + return BAD_SCEP_URL_ERROR; + } + + return DEFAULT_ERROR; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/index.ts new file mode 100644 index 0000000000..c1c8bfceba --- /dev/null +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/index.ts @@ -0,0 +1 @@ +export { default } from "./NDESForm"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx index 17ff4c7512..4a8ca1d838 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/helpers.tsx @@ -2,7 +2,6 @@ import { ICertificatesIntegrationCustomSCEP, ICertificatesIntegrationDigicert, ICertificatesIntegrationNDES, - isNDESCertIntegration, } from "interfaces/integration"; export interface ICertAuthorityListData { @@ -40,7 +39,7 @@ export const generateListData = ( if (customProxies?.length) { customProxies.forEach((cert) => { listData.push({ - id: `custom-scep-proxy-${cert.id}`, + id: `custom-scep-proxy-${cert.name}`, name: cert.name, description: "Custom Simple Certificate Enrollment Protocol (SCEP)", }); @@ -91,8 +90,7 @@ export const getCertificateAuthority = ( 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() + (cert) => id.split("custom-scep-proxy-")[1] === cert.name ) ?? null ); } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx index d663ff9545..9d2f9f8d1d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/MdmSettings.tsx @@ -133,12 +133,6 @@ const MdmSettings = ({ router }: IMdmSettingsProps) => { isVppOn={!noVppTokenUploaded} isPremiumTier={!!isPremiumTier} /> - {/* TODO: digicert update: remove scep section when digicert feature is ready */} - {isPremiumTier && !!config?.mdm.apple_bm_enabled_and_configured && ( <> diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx deleted file mode 100644 index 08de1d821f..0000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tests.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; - -import { createMockRouter } from "test/test-utils"; - -import createMockConfig, { createMockMdmConfig } from "__mocks__/configMock"; - -import { ScepCertificateContent } from "./ScepPage"; - -const FORM_DATA = { scepUrl: "", adminUrl: "", username: "", password: "" }; - -describe("Scep Page", () => { - it("renders PremiumFeatureMessage for non-premium tier", () => { - render( - - ); - expect( - screen.getByText("This feature is included in Fleet Premium.") - ).toBeInTheDocument(); - }); - it("renders TurnOnMdmMessage when MDM is not enabled", () => { - render( - - ); - expect(screen.getByText("Turn on Apple MDM")).toBeInTheDocument(); - }); - it("renders Spinner when loading", () => { - render( - - ); - expect(screen.getByTestId("spinner")).toBeInTheDocument(); - }); - it("renders DataError when showDataError is true", () => { - render( - - ); - expect(screen.getByText("Something's gone wrong.")).toBeInTheDocument(); - }); - it("renders form fields correctly", () => { - render( - - ); - expect(screen.getByLabelText("SCEP URL")).toBeInTheDocument(); - expect(screen.getByLabelText("Admin URL")).toBeInTheDocument(); - expect(screen.getByLabelText("Username")).toBeInTheDocument(); - expect(screen.getByLabelText("Password")).toBeInTheDocument(); - }); - it("displays error messages for invalid inputs", () => { - const FORM_ERRORS = { scepUrl: "Invalid URL", adminUrl: "Invalid URL" }; - const INVALID_FORM_DATA = { - scepUrl: "invalid", - adminUrl: "invalid", - username: "", - password: "", - }; - render( - - ); - expect(screen.getAllByLabelText("Invalid URL").length).toBe(2); - }); -}); diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx deleted file mode 100644 index 4bbe7a40e6..0000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/ScepPage.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import React, { useContext, useState } from "react"; -import { useQuery } from "react-query"; -import { InjectedRouter } from "react-router"; - -import PATHS from "router/paths"; -import configAPI from "services/entities/config"; -import { IConfig } from "interfaces/config"; -import { getErrorReason } from "interfaces/errors"; -import { NotificationContext } from "context/notification"; -import { AppContext } from "context/app"; - -import MainContent from "components/MainContent/MainContent"; -import Button from "components/buttons/Button"; -import BackLink from "components/BackLink/BackLink"; -import CustomLink from "components/CustomLink"; -import Card from "components/Card"; -import validateUrl from "components/forms/validators/valid_url"; -// @ts-ignore -import InputField from "components/forms/fields/InputField"; -import TooltipWrapper from "components/TooltipWrapper"; -import PremiumFeatureMessage from "components/PremiumFeatureMessage"; -import Spinner from "components/Spinner"; -import DataError from "components/DataError"; -import TurnOnMdmMessage from "components/TurnOnMdmMessage"; -import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; - -import { SCEP_SERVER_TIP_CONTENT } from "../components/ScepSection/ScepSection"; - -const baseClass = "scep-page"; - -const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; -const BAD_CREDENTIALS_ERROR = - "Couldn't add. Admin URL or credentials are invalid."; -const CACHE_ERROR = - "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again. By default, NDES caches 5 passwords and they expire 60 minutes after they are created."; -const INSUFFICIENT_PERMISSIONS_ERROR = - "Couldn't add. This account doesn't have sufficient permissions. Please use the account with enroll permission."; -const SCEP_URL_TIMEOUT_ERROR = - "Couldn't add. Request to NDES (SCEP URL) timed out. Please try again."; -const ADMIN_URL_TIMEOUT_ERROR = - "Couldn't add. Request to NDES (admin URL) timed out. Please try again."; -const DEFAULT_ERROR = - "Something went wrong updating your SCEP server. Please try again."; - -interface IScepCertificateContentProps { - router: InjectedRouter; - onFormSubmit: (evt: React.MouseEvent) => Promise; - formData: INdesFormData; - formErrors: INdesFormErrors; - onInputChange: ({ name, value }: IFormField) => void; - onBlur: (name: string, value: string) => void; - config: IConfig | null; - isPremiumTier: boolean; - isLoading: boolean; - isSaving: boolean; - showDataError: boolean; -} - -export const ScepCertificateContent = ({ - router, - onFormSubmit, - formData, - formErrors, - onInputChange, - onBlur, - config, - isPremiumTier, - isLoading, - isSaving, - showDataError, -}: IScepCertificateContentProps) => { - if (!isPremiumTier) { - return ; - } - - if (isLoading) { - return ; - } - - if (!config?.mdm.enabled_and_configured) { - return ( - - ); - } - - // TODO: error UI - if (showDataError) { - return ( -
- -
- ); - } - - const gitOpsModeEnabled = config?.gitops.gitops_mode_enabled; - - const disableSave = - // all fields aren't empty - !Object.values(formData).every((val) => val === "") && - // all fields aren't complete - !Object.values(formData).every((val) => val !== ""); - - return ( - <> -

- To help your end users connect to Wi-Fi you can add your{" "} - - SCEP server - - . -

-
-
    -
  1. -
    - Connect to your Network Device Enrollment Service ( - - ) admin account: -
    - -
    - - The URL used by client devices -
    to request and retrieve certificates. - - } - value={formData.scepUrl} - onChange={onInputChange} - parseTarget - error={formErrors.scepUrl} - placeholder="https://example.com/certsrv/mscep/mscep.dll" - disabled={gitOpsModeEnabled} - /> - - The admin interface for managing the SCEP -
    service and viewing configuration details. - - } - value={formData.adminUrl} - onChange={onInputChange} - onBlur={(e: any) => onBlur("adminUrl", e.target.value)} - parseTarget - error={formErrors.adminUrl} - placeholder="https://example.com/certsrv/mscep_admin/" - disabled={gitOpsModeEnabled} - /> - - The username in the down-level logon name format -
    - required to log in to the SCEP admin page. - - } - value={formData.username} - onChange={onInputChange} - onBlur={(e: any) => onBlur("username", e.target.value)} - parseTarget - placeholder="username@example.microsoft.com" - disabled={gitOpsModeEnabled} - /> - - The password to use to log in -
    - to the SCEP admin page. - - } - value={formData.password || ""} - type="password" - onChange={onInputChange} - parseTarget - placeholder="••••••••" - blockAutoComplete - error={formErrors.password} - disabled={gitOpsModeEnabled} - /> - ( - - )} - /> - -
    -
  2. -
  3. - - Now head over to{" "} - {" "} - to configure how SCEP certificates are delivered to your hosts.{" "} - - -
  4. -
-
- - ); -}; - -interface IScepPageProps { - router: InjectedRouter; -} - -interface INdesFormData { - scepUrl: string; - adminUrl: string; - username: string; - password: string; -} - -interface INdesFormErrors { - scepUrl?: string | null; - adminUrl?: string | null; - password?: string | null; -} - -export interface IFormField { - name: string; - value: string; -} - -const ScepPage = ({ router }: IScepPageProps) => { - const { isPremiumTier, config, setConfig } = useContext(AppContext); - - const { renderFlash } = useContext(NotificationContext); - - const [formData, setFormData] = useState({ - scepUrl: config?.integrations.ndes_scep_proxy?.url || "", - adminUrl: config?.integrations.ndes_scep_proxy?.admin_url || "", - username: config?.integrations.ndes_scep_proxy?.username || "", - password: config?.integrations.ndes_scep_proxy?.password || "", - }); - - const [formErrors, setFormErrors] = useState({}); - const [isUpdatingNdesScepProxy, setIsUpdatingNdesScepProxy] = useState(false); - - const { - data: appConfig, - isLoading: isLoadingAppConfig, - refetch: refetchConfig, - isError: isErrorAppConfig, - } = useQuery(["config"], () => configAPI.loadAll(), { - select: (data: IConfig) => data, - onSuccess: (data) => { - setConfig(data); - }, - }); - - const onInputChange = ({ name, value }: IFormField) => { - setFormErrors((prev) => ({ ...prev, [name]: null })); - setFormData((prev) => ({ ...prev, [name]: value })); - }; - - const handleBlur = (name: string, value: string) => { - // If the value of admin url or username has changed and - // it was not originally empty, prompt user to re-enter password - if ( - (name === "adminUrl" && - value !== config?.integrations.ndes_scep_proxy?.admin_url && - config?.integrations.ndes_scep_proxy?.admin_url !== "") || - (name === "username" && - value !== config?.integrations.ndes_scep_proxy?.username && - config?.integrations.ndes_scep_proxy?.username !== "") - ) { - setFormErrors((prev: INdesFormErrors) => ({ - ...prev, - password: - "Please re-enter your password due to changes in admin URL or username", - })); - setFormData((prev: INdesFormData) => ({ ...prev, password: "" })); - } - }; - - const onFormSubmit = async (evt: React.MouseEvent) => { - evt.preventDefault(); - - const scepUrlValid = validateUrl({ url: formData.scepUrl }); - const adminUrlValid = validateUrl({ url: formData.adminUrl }); - const newFormErrors = { - scepUrl: - scepUrlValid || formData.scepUrl === "" - ? undefined - : "Must be a valid URL.", - adminUrl: - adminUrlValid || formData.adminUrl === "" - ? undefined - : "Must be a valid URL.", - }; - - setFormErrors(newFormErrors); - - // Remove when all fields set to empty - const isRemovingNdesScepProxy = Object.values(formData).every( - (val) => val === "" - ); - - if (!isRemovingNdesScepProxy && (!scepUrlValid || !adminUrlValid)) { - return; - } - - setIsUpdatingNdesScepProxy(true); - - // Format for API - const formDataToSubmit = isRemovingNdesScepProxy - ? null - : { - url: formData.scepUrl, - admin_url: formData.adminUrl, - username: formData.username, - password: formData.password, - }; - // Update integrations.ndes_scep_proxy only - const destination = { - ndes_scep_proxy: formDataToSubmit, - }; - - try { - await configAPI.update({ integrations: destination }); - renderFlash( - "success", - `Successfully ${ - isRemovingNdesScepProxy ? "removed" : "added" - } your SCEP server.` - ); - refetchConfig(); - } catch (error) { - console.error(error); - const reason = getErrorReason(error); - if (reason.includes("invalid admin URL or credentials")) { - renderFlash("error", BAD_CREDENTIALS_ERROR); - } else if (reason.includes("the password cache is full")) { - renderFlash("error", CACHE_ERROR); - } else if (reason.includes("does not have sufficient permissions")) { - renderFlash("error", INSUFFICIENT_PERMISSIONS_ERROR); - } else if ( - reason.includes(formData.scepUrl) && - reason.includes("context deadline exceeded") - ) { - renderFlash("error", SCEP_URL_TIMEOUT_ERROR); - } else if ( - reason.includes(formData.adminUrl) && - reason.includes("context deadline exceeded") - ) { - renderFlash("error", ADMIN_URL_TIMEOUT_ERROR); - } else if (reason.includes("invalid SCEP URL")) { - renderFlash("error", BAD_SCEP_URL_ERROR); - } else renderFlash("error", DEFAULT_ERROR); - } finally { - setIsUpdatingNdesScepProxy(false); - } - }; - - return ( - - <> - -
-
-

Simple Certificate Enrollment Protocol (SCEP)

-
- - -
- -
- ); -}; - -export default ScepPage; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss deleted file mode 100644 index c109c7d675..0000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/__styles.scss +++ /dev/null @@ -1,68 +0,0 @@ -.scep-page { - &__back-to-mdm { - margin-bottom: $pad-xlarge; - } - - h1 { - margin-bottom: $pad-xxlarge; - font-size: $x-large; - } - - p { - font-size: $x-small; - margin: 0 0 $pad-large; - } - - &__steps { - font-size: $x-small; - padding: 0; - margin: 0; - max-width: 660px; - counter-reset: step-counter; - list-style-type: none; - display: flex; - flex-direction: column; - gap: $pad-large; - - form { - gap: $pad-small; - - button { - align-self: flex-end; - } - } - - li { - display: flex; - flex-direction: column; - flex-wrap: wrap; - gap: $pad-small; - position: relative; - padding-left: $pad-medium; - - &::before { - content: counter(step-counter) "."; - counter-increment: step-counter; - position: absolute; - left: 0; - top: 0; - display: flex; - align-items: center; - justify-content: center; - } - - > span { - flex: 1 0 100%; - } - - p { - margin: 0; - } - - .url-inputs-wrapper { - flex: 1 0 100%; - gap: $pad-small; - } - } - } -} diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts deleted file mode 100644 index 362df28ec8..0000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./ScepPage"; diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 4886b582a9..a138a188b5 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -65,8 +65,6 @@ import SetupExperience from "pages/ManageControlsPage/SetupExperience/SetupExper import WindowsMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/WindowsMdmPage"; import AppleMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/AppleMdmPage"; import AndroidMdmPage from "pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage"; -import ScepPage from "pages/admin/IntegrationsPage/cards/MdmSettings/ScepPage"; -import CertificatesPage from "pages/admin/IntegrationsPage/cards/CertificateAuthorities"; import Scripts from "pages/ManageControlsPage/Scripts/Scripts"; import WindowsAutomaticEnrollmentPage from "pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage"; import AppleBusinessManagerPage from "pages/admin/IntegrationsPage/cards/MdmSettings/AppleBusinessManagerPage"; @@ -201,8 +199,6 @@ const routes = ( - {/* TODO: digicert update: remove scep route when digicert feature is ready */} - {/* This redirect is used to handle old apple automatic enrollments page */}