import React, { useState, useEffect, useContext } from "react"; import { useQuery } from "react-query"; import { InjectedRouter } from "react-router"; import { isEmpty, omit } from "lodash"; import useDeepEffect from "hooks/useDeepEffect"; import useGitOpsMode from "hooks/useGitOpsMode"; import PATHS from "router/paths"; import { AppContext } from "context/app"; import configAPI from "services/entities/config"; import { SUPPORT_LINK } from "utilities/constants"; import { IJiraIntegration, IZendeskIntegration, IIntegration, IGlobalIntegrations, IIntegrationType, } from "interfaces/integration"; import { IConfig, CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS, } from "interfaces/config"; import { ITeamConfig } from "interfaces/team"; import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; import Slider from "components/forms/fields/Slider"; import Radio from "components/forms/fields/Radio"; import InputField from "components/forms/fields/InputField"; import CustomLink from "components/CustomLink"; import validUrl from "components/forms/validators/valid_url"; import TooltipWrapper from "components/TooltipWrapper"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; import PreviewPayloadModal from "../PreviewPayloadModal"; import PreviewTicketModal from "../PreviewTicketModal"; export const isGlobalSWConfig = ( config: IConfig | ITeamConfig ): config is IConfig => "vulnerabilities" in config; interface ISoftwareAutomations { webhook_settings: { vulnerabilities_webhook: IWebhookSoftwareVulnerabilities; }; integrations: { jira: IJiraIntegration[]; zendesk: IZendeskIntegration[]; }; } interface IManageSoftwareAutomationsModalProps { router: InjectedRouter; onCancel: () => void; onCreateWebhookSubmit: (formData: ISoftwareAutomations) => void; togglePreviewPayloadModal: () => void; togglePreviewTicketModal: () => void; showPreviewPayloadModal: boolean; showPreviewTicketModal: boolean; softwareConfig: IConfig | ITeamConfig; } const validateWebhookURL = (url: string) => { const errors: { [key: string]: string } = {}; if (!url) { errors.url = "Please add a destination URL"; } else if (!validUrl({ url })) { errors.url = "Destination URL is not a valid URL"; } else { delete errors.url; } return { valid: isEmpty(errors), errors }; }; const baseClass = "manage-software-automations-modal"; const ManageAutomationsModal = ({ router, onCancel: onReturnToApp, onCreateWebhookSubmit, togglePreviewPayloadModal, togglePreviewTicketModal, showPreviewPayloadModal, showPreviewTicketModal, softwareConfig, }: IManageSoftwareAutomationsModalProps): JSX.Element => { const vulnWebhookSettings = softwareConfig?.webhook_settings?.vulnerabilities_webhook; const softwareVulnerabilityWebhookEnabled = !!vulnWebhookSettings?.enable_vulnerabilities_webhook; const currentDestinationUrl = vulnWebhookSettings?.destination_url || ""; const isVulnIntegrationEnabled = !!softwareConfig?.integrations.jira?.some( (j) => j.enable_software_vulnerabilities ) || !!softwareConfig?.integrations.zendesk?.some( (z) => z.enable_software_vulnerabilities ); const softwareVulnerabilityAutomationEnabled = softwareVulnerabilityWebhookEnabled || isVulnIntegrationEnabled; const [destinationUrl, setDestinationUrl] = useState( currentDestinationUrl || "" ); const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [softwareAutomationsEnabled, setSoftwareAutomationsEnabled] = useState( softwareVulnerabilityAutomationEnabled || false ); const [integrationEnabled, setIntegrationEnabled] = useState( !softwareVulnerabilityWebhookEnabled ); const [jiraIntegrationsIndexed, setJiraIntegrationsIndexed] = useState< IIntegration[] >(); const [zendeskIntegrationsIndexed, setZendeskIntegrationsIndexed] = useState< IIntegration[] >(); const [allIntegrationsIndexed, setAllIntegrationsIndexed] = useState< IIntegration[] >(); const [ selectedIntegration, setSelectedIntegration, ] = useState(); const { config: globalConfigFromContext, isFreeTier } = useContext( AppContext ); const { gitOpsModeEnabled } = useGitOpsMode("software"); const maxAgeInNanoseconds = isGlobalSWConfig(softwareConfig) ? softwareConfig.vulnerabilities.recent_vulnerability_max_age : globalConfigFromContext?.vulnerabilities.recent_vulnerability_max_age; const recentVulnerabilityMaxAge = maxAgeInNanoseconds ? Math.round(maxAgeInNanoseconds / 86400000000000) // convert from nanoseconds to days : CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS; useDeepEffect(() => { setSoftwareAutomationsEnabled( softwareVulnerabilityAutomationEnabled || false ); }, [softwareVulnerabilityAutomationEnabled]); useDeepEffect(() => { if (destinationUrl) { setErrors({}); } }, [destinationUrl]); const { data: integrations } = useQuery( ["integrations"], () => configAPI.loadAll(), { select: (data: IConfig) => { return data.integrations; }, onSuccess: (data) => { // Set jira and zendesk integrations const addJiraIndexed = data.jira ? data.jira.map((integration, index) => { return { ...integration, originalIndex: index, type: "jira" as IIntegrationType, }; }) : []; setJiraIntegrationsIndexed(addJiraIndexed); const addZendeskIndexed = data.zendesk ? data.zendesk.map((integration, index) => { return { ...integration, originalIndex: index, type: "zendesk" as IIntegrationType, }; }) : []; setZendeskIntegrationsIndexed(addZendeskIndexed); }, } ); useEffect(() => { if (jiraIntegrationsIndexed && zendeskIntegrationsIndexed) { const combineDataSets = jiraIntegrationsIndexed.concat( zendeskIntegrationsIndexed ); setAllIntegrationsIndexed( combineDataSets?.map((integration, index) => { return { ...integration, dropdownIndex: index }; }) ); } }, [ jiraIntegrationsIndexed, zendeskIntegrationsIndexed, setAllIntegrationsIndexed, ]); useEffect(() => { if (allIntegrationsIndexed) { const currentSelectedIntegration = allIntegrationsIndexed.find( (integration) => { return integration.enable_software_vulnerabilities === true; } ); setSelectedIntegration(currentSelectedIntegration); } }, [allIntegrationsIndexed]); const onAddIntegration = () => { router.push(PATHS.ADMIN_INTEGRATIONS); }; const onURLChange = (value: string) => { setDestinationUrl(value); }; const handleSaveAutomation = (evt: React.MouseEvent) => { evt.preventDefault(); const { valid: validWebhookUrl, errors: errorsWebhookUrl, } = validateWebhookURL(destinationUrl); if (!validWebhookUrl) { setErrors((prevErrs) => ({ ...prevErrs, ...errorsWebhookUrl })); } else { setErrors((prevErrs) => omit(prevErrs, "url")); } // Original config keys for software automation (webhook_settings, integrations) const configSoftwareAutomations: ISoftwareAutomations = { webhook_settings: { vulnerabilities_webhook: { destination_url: validWebhookUrl ? destinationUrl : currentDestinationUrl, // if new destination url is not valid, revert to current destination url enable_vulnerabilities_webhook: softwareVulnerabilityWebhookEnabled, }, }, integrations: { jira: integrations?.jira || [], zendesk: integrations?.zendesk || [], }, }; const readyForSubmission = (): boolean => { if (!softwareAutomationsEnabled) { // set enable_vulnerabilities_webhook // jira.enable_software_vulnerabilities // and zendesk.enable_software_vulnerabilities to false configSoftwareAutomations.webhook_settings.vulnerabilities_webhook.enable_vulnerabilities_webhook = false; const disableAllJira = configSoftwareAutomations.integrations.jira.map( (integration) => { return { ...integration, enable_software_vulnerabilities: false }; } ); configSoftwareAutomations.integrations.jira = disableAllJira; const disableAllZendesk = configSoftwareAutomations.integrations.zendesk.map( (integration) => { return { ...integration, enable_software_vulnerabilities: false, }; } ); configSoftwareAutomations.integrations.zendesk = disableAllZendesk; return true; } if (!integrationEnabled) { if (!isEmpty(errorsWebhookUrl)) { return false; } // set enable_vulnerabilities_webhook to true // all jira.enable_software_vulnerabilities to false // all zendesk.enable_software_vulnerabilities to false configSoftwareAutomations.webhook_settings.vulnerabilities_webhook.enable_vulnerabilities_webhook = true; const disableAllJira = configSoftwareAutomations.integrations.jira.map( (integration) => { return { ...integration, enable_software_vulnerabilities: false, }; } ); configSoftwareAutomations.integrations.jira = disableAllJira; const disableAllZendesk = configSoftwareAutomations.integrations.zendesk.map( (integration) => { return { ...integration, enable_software_vulnerabilities: false, }; } ); configSoftwareAutomations.integrations.zendesk = disableAllZendesk; return true; } // set enable_vulnerabilities_webhook to false // all jira.enable_software_vulnerabilities to false // all zendesk.enable_software_vulnerabilities to false // except the one integration selected configSoftwareAutomations.webhook_settings.vulnerabilities_webhook.enable_vulnerabilities_webhook = false; const enableSelectedJiraIntegrationOnly = configSoftwareAutomations.integrations.jira.map( (integration, index) => { return { ...integration, enable_software_vulnerabilities: selectedIntegration?.type === "jira" ? index === selectedIntegration?.originalIndex : false, }; } ); configSoftwareAutomations.integrations.jira = enableSelectedJiraIntegrationOnly; const enableSelectedZendeskIntegrationOnly = configSoftwareAutomations.integrations.zendesk.map( (integration, index) => { return { ...integration, enable_software_vulnerabilities: selectedIntegration?.type === "zendesk" ? index === selectedIntegration?.originalIndex : false, }; } ); configSoftwareAutomations.integrations.zendesk = enableSelectedZendeskIntegrationOnly; return true; }; if (!readyForSubmission()) { return; } onCreateWebhookSubmit(configSoftwareAutomations); onReturnToApp(); }; const createIntegrationDropdownOptions = () => { const integrationOptions = allIntegrationsIndexed?.map((i) => { return { value: String(i.dropdownIndex), label: `${i.url} - ${i.project_key || i.group_id}`, }; }); return integrationOptions; }; const onChangeSelectIntegration = (selectIntegrationIndex: string) => { const integrationWithIndex: | IIntegration | undefined = allIntegrationsIndexed?.find( (integ: IIntegration) => integ.dropdownIndex === parseInt(selectIntegrationIndex, 10) ); setSelectedIntegration(integrationWithIndex); }; const onRadioChange = ( enableIntegration: boolean ): ((evt: string) => void) => { return () => { setIntegrationEnabled(enableIntegration); }; }; const renderTicket = () => { return ( <>
{isFreeTier ? ( <> A ticket will be created in your Integration for each detected vulnerability (CVE). ) : ( <> A ticket will be created in your Integration if a detected vulnerability (CVE) was published in the last{" "} {recentVulnerabilityMaxAge || CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS}{" "} days. )}
{(jiraIntegrationsIndexed && jiraIntegrationsIndexed.length > 0) || (zendeskIntegrationsIndexed && zendeskIntegrationsIndexed.length > 0) ? ( ) : (
You have no integrations.
)} {!!selectedIntegration && ( )} ); }; const renderWebhook = () => { return ( <>

{isFreeTier ? ( <> A request will be sent to your configured Destination URL{" "} for each detected vulnerability (CVE). ) : ( <> A request will be sent to your configured Destination URL{" "} if a detected vulnerability (CVE) was published in the last{" "} {recentVulnerabilityMaxAge || "30"} days. )}

); }; if (showPreviewTicketModal && selectedIntegration?.type) { return ( ); } if (showPreviewPayloadModal) { return ; } const renderSaveButton = () => { const hasIntegrations = !( ((jiraIntegrationsIndexed && jiraIntegrationsIndexed.length === 0) || (zendeskIntegrationsIndexed && zendeskIntegrationsIndexed.length === 0)) && integrationEnabled && softwareAutomationsEnabled ); const renderRawButton = (gomDisabled = false) => ( Add an integration to create
tickets for vulnerability automations. } disableTooltip={hasIntegrations || gomDisabled} position="bottom" underline={false} showArrow tipOffset={6} >
); return ( ); }; return (
setSoftwareAutomationsEnabled(!softwareAutomationsEnabled) } inactiveText="Vulnerability automations disabled" activeText="Vulnerability automations enabled" />
Workflow
{integrationEnabled ? renderTicket() : renderWebhook()}

Vulnerability automations currently run for software vulnerabilities. Interested in automations for OS vulnerabilities?{" "}

{renderSaveButton()}
); }; export default ManageAutomationsModal;