From e803f088715fbee1ce6e256d3a2f1b44d685ce8c Mon Sep 17 00:00:00 2001 From: Steve Garon Date: Mon, 30 Aug 2021 19:09:45 +0000 Subject: [PATCH] Added support for safelisted heuristics signatures --- .../routes/manage/safelist_detail.tsx | 23 +++++- src/components/visual/FileDetail/tags.tsx | 3 +- src/components/visual/Heuristic.tsx | 74 ++++++++++++++++++- .../visual/ResultCard/result_section.tsx | 2 + .../visual/SearchResult/safelist.tsx | 9 ++- src/locales/en/manage/safelist_detail.json | 3 + src/locales/fr/manage/safelist_detail.json | 3 + 7 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/components/routes/manage/safelist_detail.tsx b/src/components/routes/manage/safelist_detail.tsx index e4e7d50e7..79c0c5d8d 100644 --- a/src/components/routes/manage/safelist_detail.tsx +++ b/src/components/routes/manage/safelist_detail.tsx @@ -38,6 +38,9 @@ export type Safelist = { reason: string[]; type: string; }[]; + signature: { + name: string; + }; tag: { type: string; value: string; @@ -90,6 +93,8 @@ const SafelistDetail = ({ safelist_id, close }: SafelistDetailProps) => { query: safelist.type === 'file' ? `result.sections.heuristic.signature.name:"SAFELIST_${safelist_id || id}"` + : safelist.type === 'signature' + ? `result.sections.heuristic.signature.name:"${safelist.signature.name}"` : `result.sections.safelisted_tags.${safelist.tag.type}:"${safelist.tag.value}"`, mincount: 0, start: 'now-30d/d', @@ -214,6 +219,8 @@ const SafelistDetail = ({ safelist_id, close }: SafelistDetailProps) => { ? `/search/result/?query=result.sections.heuristic.signature.name:"SAFELIST_${ safelist_id || id }"` + : safelist.type === 'signature' + ? `/search/result/?query=result.sections.heuristic.signature.name:"${safelist.signature.name}"` : `/search/result/?query=result.sections.safelisted_tags.${safelist.tag.type}:"${safelist.tag.value}"` } > @@ -266,7 +273,7 @@ const SafelistDetail = ({ safelist_id, close }: SafelistDetailProps) => { - + {t('hashes')} @@ -354,6 +361,20 @@ const SafelistDetail = ({ safelist_id, close }: SafelistDetailProps) => { )} + {safelist && safelist.signature && ( + + {t('signature.title')} + + + + {t('signature.name')} + + + {safelist.signature.name} + + + + )} {safelist && safelist.tag && ( {t('tag.title')} diff --git a/src/components/visual/FileDetail/tags.tsx b/src/components/visual/FileDetail/tags.tsx index 4929781fa..691135f46 100644 --- a/src/components/visual/FileDetail/tags.tsx +++ b/src/components/visual/FileDetail/tags.tsx @@ -55,13 +55,14 @@ const WrappedTagSection: React.FC = ({ signatures, tags }) => { heuristic.signature - {signatures.map(([value, lvl], idx) => ( + {signatures.map(([value, lvl, safe], idx) => ( ))} diff --git a/src/components/visual/Heuristic.tsx b/src/components/visual/Heuristic.tsx index 1ae1e2331..0f91f55b8 100644 --- a/src/components/visual/Heuristic.tsx +++ b/src/components/visual/Heuristic.tsx @@ -1,17 +1,24 @@ import { Menu, MenuItem } from '@material-ui/core'; import AssignmentOutlinedIcon from '@material-ui/icons/AssignmentOutlined'; +import PlaylistAddCheckOutlinedIcon from '@material-ui/icons/PlaylistAddCheckOutlined'; import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined'; import SelectAllOutlinedIcon from '@material-ui/icons/SelectAllOutlined'; import useClipboard from 'commons/components/hooks/useClipboard'; +import useALContext from 'components/hooks/useALContext'; import useHighlighter from 'components/hooks/useHighlighter'; +import useMyAPI from 'components/hooks/useMyAPI'; +import useMySnackbar from 'components/hooks/useMySnackbar'; import CustomChip, { PossibleColors } from 'components/visual/CustomChip'; import { scoreToVerdict } from 'helpers/utils'; import React, { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; +import InputDialog from './InputDialog'; const STYLE = { height: 'auto', minHeight: '20px' }; const SEARCH_ICON = ; const CLIPBOARD_ICON = ; +const SAFELIST_ICON = ; const HIGHLIGHT_ICON = ; const initialMenuState = { mouseX: null, @@ -26,6 +33,7 @@ type HeuristicProps = { show_type?: boolean; highlight_key?: string; fullWidth?: boolean; + safe?: boolean; }; const Heuristic: React.FC = ({ @@ -35,12 +43,19 @@ const Heuristic: React.FC = ({ signature = false, show_type = false, highlight_key = null, - fullWidth = false + fullWidth = false, + safe = false }) => { + const { t } = useTranslation(); const [state, setState] = React.useState(initialMenuState); + const [safelistDialog, setSafelistDialog] = React.useState(false); + const [safelistReason, setSafelistReason] = React.useState(null); const history = useHistory(); + const apiCall = useMyAPI(); + const { showSuccessMessage } = useMySnackbar(); const { isHighlighted, triggerHighlight } = useHighlighter(); const { copy } = useClipboard(); + const { user: currentUser } = useALContext(); const handleClick = useCallback(() => triggerHighlight(highlight_key), [triggerHighlight, highlight_key]); @@ -77,8 +92,42 @@ const Heuristic: React.FC = ({ handleClose(); }, [handleClick, handleClose]); + const handleMenuSafelist = useCallback(() => { + setSafelistDialog(true); + handleClose(); + }, [setSafelistDialog, handleClose]); + + const addToSafelist = useCallback(() => { + const data = { + signature: { + name: text + }, + sources: [ + { + name: currentUser.username, + reason: [safelistReason], + type: 'user' + } + ], + type: 'signature' + }; + + apiCall({ + url: `/api/v4/safelist/`, + method: 'PUT', + body: data, + onSuccess: _ => { + setSafelistDialog(false); + showSuccessMessage(t('safelist.success')); + } + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [safelistReason, t, text]); + let color: PossibleColors = 'default' as 'default'; - if (lvl) { + if (safe) { + color = 'success' as 'success'; + } else if (lvl) { color = { info: 'default' as 'default', safe: 'success' as 'success', @@ -97,6 +146,20 @@ const Heuristic: React.FC = ({ return ( <> + {signature && ( + setSafelistDialog(false)} + handleAccept={addToSafelist} + handleInputChange={event => setSafelistReason(event.target.value)} + inputValue={safelistReason} + title={t('safelist.title')} + cancelText={t('safelist.cancelText')} + acceptText={t('safelist.acceptText')} + inputLabel={t('safelist.input')} + text={t('safelist.text')} + /> + )} = ({ {HIGHLIGHT_ICON}Toggle Highlight + {signature && ( + + {SAFELIST_ICON} + {t('safelist')} + + )} = ({ signature show_type highlight_key={getKey('heuristic.signature', signature.name)} + safe={signature.safe} /> ))} {Array.isArray(section.tags) && diff --git a/src/components/visual/SearchResult/safelist.tsx b/src/components/visual/SearchResult/safelist.tsx index 4111a31ea..1210e4934 100644 --- a/src/components/visual/SearchResult/safelist.tsx +++ b/src/components/visual/SearchResult/safelist.tsx @@ -38,6 +38,9 @@ export type Safelist = { sources: { name: string[]; }; + signature: { + name: string; + }; tag: { type: string; value: string; @@ -114,7 +117,11 @@ const WrappedSafelistTable: React.FC = ({ {sl_item.type} - {sl_item.type === 'file' ? sl_item.id : `${sl_item.tag.type} - ${maxLenStr(sl_item.tag.value, 100)}`} + {sl_item.type === 'file' + ? sl_item.id + : sl_item.type === 'signature' + ? maxLenStr(sl_item.signature.name, 100) + : `${sl_item.tag.type} - ${maxLenStr(sl_item.tag.value, 100)}`} {sl_item.sources.name.join(' | ')} {c12nDef.enforce && ( diff --git a/src/locales/en/manage/safelist_detail.json b/src/locales/en/manage/safelist_detail.json index 361ad4522..9c87982e6 100644 --- a/src/locales/en/manage/safelist_detail.json +++ b/src/locales/en/manage/safelist_detail.json @@ -26,6 +26,8 @@ "remove": "Remove this hash from the safelist", "size": "Size", "sources": "Sources", + "signature.title": "Signature information", + "signature.name": "Name", "tag.title": "Tag information", "tag.type": "Type", "tag.value": "Value", @@ -34,6 +36,7 @@ "timing.updated": "Last updated", "title": "Safelisted hash details", "title.file": "Safelisted file details", + "title.signature": "Safelisted signature details", "title.tag": "Safelisted tag details", "unknown": "Unavailable", "usage": "Show hash usage", diff --git a/src/locales/fr/manage/safelist_detail.json b/src/locales/fr/manage/safelist_detail.json index c10cc43ed..8b7566e60 100644 --- a/src/locales/fr/manage/safelist_detail.json +++ b/src/locales/fr/manage/safelist_detail.json @@ -24,6 +24,8 @@ "file.type": "Type", "hashes": "Hachages associés", "remove": "Supprimer ce hachage de la liste sûre", + "signature.title": "Informations sur la signature", + "signature.name": "Nom", "size": "Taille", "sources": "Sources", "tag.title": "Informations sur les balises", @@ -34,6 +36,7 @@ "timing.updated": "Dernière mise à jour", "title": "Détails de hachage en liste sûre", "title.file": "Détails de fichier en liste sûre", + "title.signature": "Détails de signature en liste sûre", "title.tag": "Détails de balises en liste sûre", "unknown": "Indisponible", "usage": "Afficher l'utilisation du hachage",