diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 273ea72a2ffe3..21fa780badd84 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -222,8 +222,9 @@ export const risk_score_mapping_value = t.string; export const risk_score_mapping_item = t.exact( t.type({ field: risk_score_mapping_field, - operator, value: risk_score_mapping_value, + operator, + risk_score: riskScoreOrUndefined, }) ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 2a6cd3fc5bb7a..0d98a0f2f26ff 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -331,6 +331,7 @@ describe('helpers', () => { const result: ListItems[] = buildSeverityDescription({ value: 'low', mapping: [{ field: 'host.name', operator: 'equals', value: 'hello', severity: 'high' }], + isMappingChecked: true, }); expect(result[0].title).toEqual('Severity'); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 1110c8c098988..600bc999849d1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -35,6 +35,7 @@ import { SeverityBadge } from '../severity_badge'; import ListTreeIcon from './assets/list_tree_icon.svg'; import { assertUnreachable } from '../../../../common/lib/helpers'; import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; +import { defaultToEmptyTag } from '../../../../common/components/empty_value'; const NoteDescriptionContainer = styled(EuiFlexItem)` height: 105px; @@ -236,35 +237,44 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems title: i18nSeverity.DEFAULT_SEVERITY, description: , }, - ...severity.mapping.map((severityItem, index) => { - return { - title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', - description: ( - - - - <>{severityItem.field} - - - - <>{severityItem.value} - - - - - - - - - ), - }; - }), + ...(severity.isMappingChecked + ? severity.mapping + .filter((severityItem) => severityItem.field !== '') + .map((severityItem, index) => { + return { + title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', + description: ( + + + + <>{`${severityItem.field}:`} + + + + + {defaultToEmptyTag(severityItem.value)} + + + + + + + + + + ), + }; + }) + : []), ]; export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListItems[] => [ @@ -272,27 +282,31 @@ export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListIt title: i18nRiskScore.RISK_SCORE, description: riskScore.value, }, - ...riskScore.mapping.map((riskScoreItem, index) => { - return { - title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', - description: ( - - - - <>{riskScoreItem.field} - - - - - - {'signal.rule.risk_score'} - - ), - }; - }), + ...(riskScore.isMappingChecked + ? riskScore.mapping + .filter((riskScoreItem) => riskScoreItem.field !== '') + .map((riskScoreItem, index) => { + return { + title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', + description: ( + + + + <>{riskScoreItem.field} + + + + + + {'signal.rule.risk_score'} + + ), + }; + }) + : []), ]; const MyRefUrlLink = styled(EuiLink)` diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index c9e2cb1a8ca24..35816e82540d1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -14,8 +14,9 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { noop } from 'lodash/fp'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { CommonUseField } from '../../../../cases/components/create'; @@ -24,6 +25,10 @@ import { FieldComponent } from '../../../../common/components/autocomplete/field import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +const RiskScoreMappingEuiFormRow = styled(EuiFormRow)` + width: 468px; +`; + const NestedContent = styled.div` margin-left: 24px; `; @@ -41,6 +46,7 @@ interface RiskScoreFieldProps { field: FieldHook; idAria: string; indices: IIndexPattern; + isDisabled: boolean; placeholder?: string; } @@ -49,40 +55,23 @@ export const RiskScoreField = ({ field, idAria, indices, + isDisabled, placeholder, }: RiskScoreFieldProps) => { - const [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked] = useState(false); - const [initialFieldCheck, setInitialFieldCheck] = useState(true); - const fieldTypeFilter = useMemo(() => ['number'], []); - useEffect(() => { - if ( - !isRiskScoreMappingChecked && - initialFieldCheck && - (field.value as AboutStepRiskScore).mapping?.length > 0 - ) { - setIsRiskScoreMappingChecked(true); - setInitialFieldCheck(false); - } - }, [ - field, - initialFieldCheck, - isRiskScoreMappingChecked, - setIsRiskScoreMappingChecked, - setInitialFieldCheck, - ]); - const handleFieldChange = useCallback( ([newField]: IFieldType[]): void => { const values = field.value as AboutStepRiskScore; field.setValue({ value: values.value, + isMappingChecked: values.isMappingChecked, mapping: [ { field: newField?.name ?? '', operator: 'equals', - value: '', + value: undefined, + riskScore: undefined, }, ], }); @@ -99,8 +88,13 @@ export const RiskScoreField = ({ }, [field.value, indices]); const handleRiskScoreMappingChecked = useCallback(() => { - setIsRiskScoreMappingChecked(!isRiskScoreMappingChecked); - }, [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked]); + const values = field.value as AboutStepRiskScore; + field.setValue({ + value: values.value, + mapping: [...values.mapping], + isMappingChecked: !values.isMappingChecked, + }); + }, [field]); const riskScoreLabel = useMemo(() => { return ( @@ -117,11 +111,16 @@ export const RiskScoreField = ({ const riskScoreMappingLabel = useMemo(() => { return (
- + @@ -133,7 +132,7 @@ export const RiskScoreField = ({
); - }, [handleRiskScoreMappingChecked, isRiskScoreMappingChecked]); + }, [field.value, handleRiskScoreMappingChecked, isDisabled]); return ( @@ -153,6 +152,7 @@ export const RiskScoreField = ({ componentProps={{ idAria: 'detectionEngineStepAboutRuleRiskScore', 'data-test-subj': 'detectionEngineStepAboutRuleRiskScore', + isDisabled, euiFieldProps: { max: 100, min: 0, @@ -166,11 +166,11 @@ export const RiskScoreField = ({ - {i18n.RISK_SCORE_MAPPING_DETAILS} ) : ( '' @@ -184,7 +184,7 @@ export const RiskScoreField = ({ > - {isRiskScoreMappingChecked && ( + {(field.value as AboutStepRiskScore).isMappingChecked && ( @@ -208,11 +208,11 @@ export const RiskScoreField = ({ fieldTypeFilter={fieldTypeFilter} isLoading={false} isClearable={false} - isDisabled={false} + isDisabled={isDisabled} onChange={handleFieldChange} data-test-subj={dataTestSubj} aria-label={idAria} - fieldInputWidth={230} + fieldInputWidth={270} /> @@ -226,7 +226,7 @@ export const RiskScoreField = ({ )} - + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index 579c60579b32e..54d505a4d867f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -14,7 +14,8 @@ import { EuiIcon, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { noop } from 'lodash/fp'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; @@ -27,10 +28,16 @@ import { } from '../../../../../../../../src/plugins/data/common/index_patterns'; import { FieldComponent } from '../../../../common/components/autocomplete/field'; import { AutocompleteFieldMatchComponent } from '../../../../common/components/autocomplete/field_value_match'; +import { + Severity, + SeverityMapping, + SeverityMappingItem, +} from '../../../../../common/detection_engine/schemas/common/schemas'; -const SeverityMappingParentContainer = styled(EuiFlexItem)` - max-width: 471px; +const SeverityMappingEuiFormRow = styled(EuiFormRow)` + width: 468px; `; + const NestedContent = styled.div` margin-left: 24px; `; @@ -48,6 +55,7 @@ interface SeverityFieldProps { field: FieldHook; idAria: string; indices: IIndexPattern; + isDisabled: boolean; options: SeverityOptionItem[]; } @@ -56,42 +64,20 @@ export const SeverityField = ({ field, idAria, indices, + isDisabled, options, }: SeverityFieldProps) => { - const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); - const [initialFieldCheck, setInitialFieldCheck] = useState(true); const fieldValueInputWidth = 160; - useEffect(() => { - if ( - !isSeverityMappingChecked && - initialFieldCheck && - (field.value as AboutStepSeverity).mapping?.length > 0 - ) { - setIsSeverityMappingChecked(true); - setInitialFieldCheck(false); - } - }, [ - field, - initialFieldCheck, - isSeverityMappingChecked, - setIsSeverityMappingChecked, - setInitialFieldCheck, - ]); - - const handleFieldChange = useCallback( - (index: number, severity: string, [newField]: IFieldType[]): void => { + const handleFieldValueChange = useCallback( + (newMappingItems: SeverityMapping, index: number): void => { const values = field.value as AboutStepSeverity; field.setValue({ value: values.value, + isMappingChecked: values.isMappingChecked, mapping: [ ...values.mapping.slice(0, index), - { - ...values.mapping[index], - field: newField?.name ?? '', - operator: 'equals', - severity, - }, + ...newMappingItems, ...values.mapping.slice(index + 1), ], }); @@ -99,40 +85,59 @@ export const SeverityField = ({ [field] ); + const handleFieldChange = useCallback( + (index: number, severity: Severity, [newField]: IFieldType[]): void => { + const values = field.value as AboutStepSeverity; + const newMappingItems: SeverityMapping = [ + { + ...values.mapping[index], + field: newField?.name ?? '', + value: newField != null ? values.mapping[index].value : '', + operator: 'equals', + severity, + }, + ]; + handleFieldValueChange(newMappingItems, index); + }, + [field, handleFieldValueChange] + ); + const handleFieldMatchValueChange = useCallback( - (index: number, severity: string, newMatchValue: string): void => { + (index: number, severity: Severity, newMatchValue: string): void => { const values = field.value as AboutStepSeverity; - field.setValue({ - value: values.value, - mapping: [ - ...values.mapping.slice(0, index), - { - ...values.mapping[index], - value: newMatchValue, - operator: 'equals', - severity, - }, - ...values.mapping.slice(index + 1), - ], - }); + const newMappingItems: SeverityMapping = [ + { + ...values.mapping[index], + field: values.mapping[index].field, + value: + values.mapping[index].field != null && values.mapping[index].field !== '' + ? newMatchValue + : '', + operator: 'equals', + severity, + }, + ]; + handleFieldValueChange(newMappingItems, index); }, - [field] + [field, handleFieldValueChange] ); - const selectedState = useMemo(() => { - return ( - (field.value as AboutStepSeverity).mapping?.map((mapping) => { - const [newSelectedField] = indices.fields.filter( - ({ name }) => mapping.field != null && mapping.field === name - ); - return { field: newSelectedField, value: mapping.value }; - }) ?? [] - ); - }, [field.value, indices]); + const getIFieldTypeFromFieldName = ( + fieldName: string | undefined, + iIndexPattern: IIndexPattern + ): IFieldType | undefined => { + const [iFieldType] = iIndexPattern.fields.filter(({ name }) => fieldName === name); + return iFieldType; + }; - const handleSeverityMappingSelected = useCallback(() => { - setIsSeverityMappingChecked(!isSeverityMappingChecked); - }, [isSeverityMappingChecked, setIsSeverityMappingChecked]); + const handleSeverityMappingChecked = useCallback(() => { + const values = field.value as AboutStepSeverity; + field.setValue({ + value: values.value, + mapping: [...values.mapping], + isMappingChecked: !values.isMappingChecked, + }); + }, [field]); const severityLabel = useMemo(() => { return ( @@ -149,12 +154,17 @@ export const SeverityField = ({ const severityMappingLabel = useMemo(() => { return (
- + {i18n.SEVERITY_MAPPING} @@ -165,7 +175,7 @@ export const SeverityField = ({
); - }, [handleSeverityMappingSelected, isSeverityMappingChecked]); + }, [field.value, handleSeverityMappingChecked, isDisabled]); return ( @@ -185,6 +195,7 @@ export const SeverityField = ({ componentProps={{ idAria: 'detectionEngineStepAboutRuleSeverity', 'data-test-subj': 'detectionEngineStepAboutRuleSeverity', + isDisabled, euiFieldProps: { fullWidth: false, disabled: false, @@ -195,12 +206,12 @@ export const SeverityField = ({ - - + {i18n.SEVERITY_MAPPING_DETAILS} ) : ( '' @@ -214,7 +225,7 @@ export const SeverityField = ({ > - {isSeverityMappingChecked && ( + {(field.value as AboutStepSeverity).isMappingChecked && ( @@ -231,53 +242,72 @@ export const SeverityField = ({ - {options.map((option, index) => ( - - - - - + {(field.value as AboutStepSeverity).mapping.map( + (severityMappingItem: SeverityMappingItem, index) => ( + + + + + - - - - - - - - {option.inputDisplay} - - - - ))} + + + + + + + + { + options.find((o) => o.value === severityMappingItem.severity) + ?.inputDisplay + } + + + + ) + )} )} - - + + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts index f5d61553b595b..b9c3e4f84c18e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts @@ -5,6 +5,7 @@ */ import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; +import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; export const threatDefault = [ { @@ -21,8 +22,8 @@ export const stepAboutDefaultValue: AboutStepRule = { isAssociatedToEndpointList: false, isBuildingBlock: false, isNew: true, - severity: { value: 'low', mapping: [] }, - riskScore: { value: 50, mapping: [] }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, + riskScore: { value: 50, mapping: [], isMappingChecked: false }, references: [''], falsePositives: [''], license: '', diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index a86c1b7ce1bea..cb3fd5e5bec32 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -16,6 +16,7 @@ import { stepAboutDefaultValue } from './default_value'; // we don't have the types for waitFor just yet, so using "as waitFor" until when we do import { wait as waitFor } from '@testing-library/react'; import { AboutStepRule } from '../../../pages/detection_engine/rules/types'; +import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers'; const theme = () => ({ eui: euiDarkVars, darkMode: true }); @@ -176,8 +177,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: { value: 50, mapping: [] }, - severity: { value: 'low', mapping: [] }, + riskScore: { value: 50, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: [], threat: [ { @@ -236,8 +237,8 @@ describe('StepAboutRuleComponent', () => { name: 'Test name text', note: '', references: [''], - riskScore: { value: 80, mapping: [] }, - severity: { value: 'low', mapping: [] }, + riskScore: { value: 80, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: [], threat: [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx index fbd03850eee75..20470d7bb924f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/schema.tsx @@ -117,18 +117,16 @@ export const schema: FormSchema = { }, ], }, - mapping: { - type: FIELD_TYPES.TEXT, - }, + mapping: {}, + isMappingChecked: {}, }, riskScore: { value: { type: FIELD_TYPES.RANGE, serializer: (input: string) => Number(input), }, - mapping: { - type: FIELD_TYPES.TEXT, - }, + mapping: {}, + isMappingChecked: {}, }, references: { label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts index 14cf476e66563..7a6b61f0dfd89 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -9,6 +9,7 @@ import { Rule, RuleError } from '../../../../../containers/detection_engine/rule import { List } from '../../../../../../../common/detection_engine/schemas/types'; import { AboutStepRule, ActionsStepRule, DefineStepRule, ScheduleStepRule } from '../../types'; import { FieldValueQueryBar } from '../../../../../components/rules/query_bar'; +import { fillEmptySeverityMappings } from '../../helpers'; export const mockQueryBar: FieldValueQueryBar = { query: { @@ -175,8 +176,8 @@ export const mockAboutStepRule = (isNew = false): AboutStepRule => ({ license: 'Elastic License', name: 'Query with rule-id', description: '24/7', - severity: { value: 'low', mapping: [] }, - riskScore: { value: 21, mapping: [] }, + riskScore: { value: 21, mapping: [], isMappingChecked: false }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, references: ['www.test.co'], falsePositives: ['test'], tags: ['tag1', 'tag2'], diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 705013beb750f..8b03f62fc82bd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -189,10 +189,14 @@ export const formatAboutStepData = ( false_positives: falsePositives.filter((item) => !isEmpty(item)), references: references.filter((item) => !isEmpty(item)), risk_score: riskScore.value, - risk_score_mapping: riskScore.mapping, + risk_score_mapping: riskScore.isMappingChecked + ? riskScore.mapping.filter((m) => m.field != null && m.field !== '') + : [], rule_name_override: ruleNameOverride !== '' ? ruleNameOverride : undefined, severity: severity.value, - severity_mapping: severity.mapping, + severity_mapping: severity.isMappingChecked + ? severity.mapping.filter((m) => m.field != null && m.field !== '' && m.value != null) + : [], threat: threat .filter((singleThreat) => singleThreat.tactic.name !== 'none') .map((singleThreat) => ({ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index b40243efcfb46..10a20807d6f87 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -17,6 +17,7 @@ import { getPrePackagedTimelineStatus, determineDetailsValue, userHasNoPermissions, + fillEmptySeverityMappings, } from './helpers'; import { mockRuleWithEverything, mockRule } from './all/__mocks__/mock'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; @@ -97,9 +98,9 @@ describe('rule helpers', () => { name: 'Query with rule-id', note: '# this is some markdown documentation', references: ['www.test.co'], - riskScore: { value: 21, mapping: [] }, + riskScore: { value: 21, mapping: [], isMappingChecked: false }, ruleNameOverride: 'message', - severity: { value: 'low', mapping: [] }, + severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, tags: ['tag1', 'tag2'], threat: [ { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 1f7b495bad920..1fcfcb4adb5bb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -24,6 +24,8 @@ import { ScheduleStepRule, ActionsStepRule, } from './types'; +import { SeverityMapping } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { severityOptions } from '../../../components/rules/step_about_rule/data'; export interface GetStepsData { aboutRuleData: AboutStepRule; @@ -150,18 +152,38 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu references, severity: { value: severity, - mapping: severityMapping, + mapping: fillEmptySeverityMappings(severityMapping), + isMappingChecked: severityMapping.length > 0, }, tags, riskScore: { value: riskScore, mapping: riskScoreMapping, + isMappingChecked: riskScoreMapping.length > 0, }, falsePositives, threat: threat as IMitreEnterpriseAttack[], }; }; +const severitySortMapping = { + low: 0, + medium: 1, + high: 2, + critical: 3, +}; + +export const fillEmptySeverityMappings = (mappings: SeverityMapping): SeverityMapping => { + const missingMappings: SeverityMapping = severityOptions.flatMap((so) => + mappings.find((mapping) => mapping.severity === so.value) == null + ? [{ field: '', value: '', operator: 'equals', severity: so.value }] + : [] + ); + return [...mappings, ...missingMappings].sort( + (a, b) => severitySortMapping[a.severity] - severitySortMapping[b.severity] + ); +}; + export const determineDetailsValue = ( rule: Rule, detailsView: boolean diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 23715a88efc7b..e36f08703dae5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -88,11 +88,13 @@ export interface AboutStepRuleDetails { export interface AboutStepSeverity { value: string; mapping: SeverityMapping; + isMappingChecked: boolean; } export interface AboutStepRiskScore { value: number; mapping: RiskScoreMapping; + isMappingChecked: boolean; } export interface DefineStepRule extends StepRuleData { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts index 356cf95fc0d24..888642f77af60 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts @@ -10,7 +10,6 @@ import { RiskScoreMappingOrUndefined, } from '../../../../../common/detection_engine/schemas/common/schemas'; import { SignalSourceHit } from '../types'; -import { RiskScore as RiskScoreIOTS } from '../../../../../common/detection_engine/schemas/types'; interface BuildRiskScoreFromMappingProps { doc: SignalSourceHit; @@ -33,8 +32,12 @@ export const buildRiskScoreFromMapping = ({ const mappedField = riskScoreMapping[0].field; // TODO: Expand by verifying fieldType from index via doc._index const mappedValue = get(mappedField, doc._source); - // TODO: This doesn't seem to validate...identified riskScore > 100 😬 - if (RiskScoreIOTS.is(mappedValue)) { + if ( + typeof mappedValue === 'number' && + Number.isSafeInteger(mappedValue) && + mappedValue >= 0 && + mappedValue <= 100 + ) { return { riskScore: mappedValue, riskScoreMeta: { riskScoreOverridden: true } }; } }