Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for editing correlation rule #643

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions public/pages/Correlations/containers/CorrelationRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ export const CorrelationRules: React.FC<RouteComponentProps> = (props: RouteComp

const onRuleNameClick = useCallback((rule: CorrelationRule) => {
props.history.push({
pathname: ROUTES.CORRELATION_RULE_CREATE,
state: { rule, isReadOnly: true },
pathname: `${ROUTES.CORRELATION_RULE_EDIT}/${rule.id}`,
state: { rule, isReadOnly: false },
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
});
}, []);

Expand Down
156 changes: 73 additions & 83 deletions public/pages/Correlations/containers/CreateCorrelationRule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import {
CorrelationRuleModel,
CorrelationRuleQuery,
} from '../../../../types';
import { BREADCRUMBS, ROUTES, isDarkMode } from '../../../utils/constants';
import { BREADCRUMBS, ROUTES } from '../../../utils/constants';
import { CoreServicesContext } from '../../../components/core_services';
import { RouteComponentProps } from 'react-router-dom';
import { RouteComponentProps, useParams } from 'react-router-dom';
import { CorrelationsExperimentalBanner } from '../components/ExperimentalBanner';
import { validateName } from '../../../utils/validation';
import { FieldMappingService, IndexService } from '../../../services';
Expand Down Expand Up @@ -99,41 +99,46 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (

return undefined;
}, []);
const params = useParams<{ ruleId: string }>();
const [initialValues, setInitialValues] = useState({
...correlationRuleStateDefaultValue,
});
const [action, setAction] = useState<CorrelationRuleAction>('Create');

useEffect(() => {
if (props.history.location.state?.rule) {
setAction('Edit');
setInitialValues(props.history.location.state?.rule);
} else if (params.ruleId) {
const setInitialRuleValues = async () => {
const ruleRes = await correlationStore.getCorrelationRule(params.ruleId);
if (ruleRes) {
setInitialValues(ruleRes);
}
};

setAction('Edit');
setInitialRuleValues();
}
}, []);

const submit = async (values: any) => {
let error;
if ((error = validateCorrelationRule(values))) {
errorNotificationToast(props.notifications, 'Create', 'rule', error);
errorNotificationToast(props.notifications, action, 'rule', error);
return;
}

await correlationStore.createCorrelationRule(values);
if (action === 'Edit') {
await correlationStore.updateCorrelationRule(values);
} else {
await correlationStore.createCorrelationRule(values);
}

props.history.push(ROUTES.CORRELATION_RULES);
};

const context = useContext(CoreServicesContext);
let action: CorrelationRuleAction = 'Create';
let initialValues = {
...correlationRuleStateDefaultValue,
};

if (props.history.location.state?.rule) {
action = 'Edit';
initialValues = props.history.location.state?.rule;

if (props.history.location.state.isReadOnly) {
action = 'Readonly';
}
}

const disableForm = action === 'Readonly';
const textClassName = disableForm
? isDarkMode
? 'readonly-text-color-dark-mode'
: 'readonly-text-color-light-mode'
: undefined;

const parseOptions = (indices: string[]) => {
return indices.map(
(index: string): CorrelationOptions => ({
Expand Down Expand Up @@ -206,9 +211,17 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
</EuiTitle>
}
extraAction={
queryIdx > 1 ? (
correlationQueries.length > 2 ? (
<EuiToolTip title={'Delete query'}>
<EuiButtonIcon iconType={'trash'} color="danger" />
<EuiButtonIcon
iconType={'trash'}
color="danger"
onClick={() => {
const newQueries = [...correlationQueries];
newQueries.splice(queryIdx, 1);
props.setFieldValue('queries', newQueries);
}}
/>
</EuiToolTip>
) : null
}
Expand Down Expand Up @@ -247,8 +260,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
query.index ? [{ value: query.index, label: query.index }] : []
}
isClearable={true}
isDisabled={disableForm}
className={textClassName}
/>
</EuiFormRow>
<EuiSpacer size="m" />
Expand Down Expand Up @@ -279,8 +290,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
onCreateOption={(e) => {
props.handleChange(`queries[${queryIdx}].logType`)(e);
}}
isDisabled={disableForm}
className={textClassName}
/>
</EuiFormRow>
<EuiSpacer size="xl" />
Expand Down Expand Up @@ -313,8 +322,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
)(e);
}}
isClearable={true}
isDisabled={disableForm}
className={textClassName}
/>
);

Expand All @@ -332,8 +339,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
`queries[${queryIdx}].conditions[${conditionIdx}].value`
)}
value={condition.value}
disabled={disableForm}
className={textClassName}
/>
);

Expand All @@ -352,7 +357,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
)(e);
}}
className={'correlation_rule_field_condition'}
isDisabled={disableForm}
/>
);

Expand Down Expand Up @@ -387,7 +391,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
initialIsOpen={true}
buttonContent={`Field ${conditionIdx + 1}`}
extraAction={
query.conditions.length > 1 && !disableForm ? (
query.conditions.length > 1 ? (
<EuiToolTip title={'Delete field'}>
<EuiButtonIcon
iconType={'trash'}
Expand All @@ -400,7 +404,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
newCases
);
}}
disabled={disableForm}
/>
</EuiToolTip>
) : null
Expand All @@ -415,43 +418,37 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
</>
);
})}
{disableForm ? null : (
<EuiButton
style={{ width: 125 }}
onClick={() => {
props.setFieldValue(`queries[${queryIdx}].conditions`, [
...query.conditions,
...correlationRuleStateDefaultValue.queries[0].conditions,
]);
}}
iconType={'plusInCircle'}
disabled={disableForm}
>
Add field
</EuiButton>
)}
<EuiButton
style={{ width: 125 }}
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => {
props.setFieldValue(`queries[${queryIdx}].conditions`, [
...query.conditions,
...correlationRuleStateDefaultValue.queries[0].conditions,
]);
}}
iconType={'plusInCircle'}
>
Add field
</EuiButton>
</EuiAccordion>
</EuiPanel>
<EuiSpacer />
</>
);
})}
<EuiSpacer />
{disableForm ? null : (
<EuiButton
onClick={() => {
props.setFieldValue('queries', [
...correlationQueries,
{ ...correlationRuleStateDefaultValue.queries[0] },
]);
}}
iconType={'plusInCircle'}
fullWidth={true}
disabled={disableForm}
>
Add query
</EuiButton>
)}
<EuiButton
onClick={() => {
props.setFieldValue('queries', [
...correlationQueries,
{ ...correlationRuleStateDefaultValue.queries[0] },
]);
}}
iconType={'plusInCircle'}
fullWidth={true}
>
Add query
</EuiButton>
</>
);
};
Expand All @@ -469,14 +466,12 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
<>
<CorrelationsExperimentalBanner />
<EuiTitle>
<h1>{action === 'Readonly' ? 'C' : `${action} c`}orrelation rule</h1>
<h1>{`${action} correlation rule`}</h1>
</EuiTitle>
{action === 'Readonly' ? null : (
<EuiText size="s" color="subdued">
{action === 'Create' ? 'Create a' : 'Edit'} correlation rule to define threat scenarios of
interest between different log sources.
</EuiText>
)}
<EuiText size="s" color="subdued">
{action === 'Create' ? 'Create a' : 'Edit'} correlation rule to define threat scenarios of
interest between different log sources.
</EuiText>
<EuiSpacer size="l" />
<Formik
initialValues={initialValues}
Expand All @@ -497,6 +492,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
setSubmitting(false);
submit(values);
}}
enableReinitialize={true}
>
{({ values: { name, queries }, touched, errors, ...props }) => {
return (
Expand All @@ -514,9 +510,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
isInvalid={touched.name && !!errors?.name}
error={errors.name}
helpText={
disableForm
? undefined
: 'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores.'
'Rule name must contain 5-50 characters. Valid characters are a-z, A-Z, 0-9, hyphens, spaces, and underscores.'
}
>
<EuiFieldText
Expand All @@ -528,8 +522,6 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
}}
onBlur={props.handleBlur('name')}
value={name}
className={textClassName}
disabled={disableForm}
/>
</EuiFormRow>
<EuiSpacer />
Expand All @@ -538,9 +530,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (
<ContentPanel
title="Correlation queries"
subTitleText={
disableForm
? 'Conditions used to match correlated findings.'
: 'Configure two or more queries to set the conditions for correlating findings.'
'Configure two or more queries to set the conditions for correlating findings.'
}
panelStyles={{ paddingLeft: 10, paddingRight: 10 }}
>
Expand Down
11 changes: 11 additions & 0 deletions public/pages/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,17 @@ export default class Main extends Component<MainProps, MainState> {
/>
)}
/>
<Route
path={`${ROUTES.CORRELATION_RULE_EDIT}/:ruleId`}
render={(props: RouteComponentProps<any, any, any>) => (
<CreateCorrelationRule
{...props}
indexService={services?.indexService}
fieldMappingService={services?.fieldMappingService}
notifications={core?.notifications}
/>
)}
/>
<Route
path={`${ROUTES.CORRELATIONS}`}
render={(props: RouteComponentProps<any, any, any>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
EuiButtonIcon,
EuiExpression,
} from '@elastic/eui';
import * as _ from 'lodash';
import _ from 'lodash';
import { Selection } from '../DetectionVisualEditor';

export interface SelectionExpFieldProps {
Expand All @@ -24,20 +24,46 @@ interface UsedSelection {
description: string;
}

const operationOptionsFirstExpression = [
{ value: '', text: '' },
{ value: 'not', text: 'NOT' },
];

const operatorOptions = [
{ value: '', text: '' },
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
{ value: 'and not', text: 'AND NOT' },
{ value: 'or not', text: 'OR NOT' },
];

export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
selections,
dataTestSubj,
onChange,
value,
}) => {
const DEFAULT_DESCRIPTION = 'Select';
const OPERATORS = ['and', 'or', 'not'];
const OPERATORS = ['and', 'or', 'and not', 'or not', 'not'];
const [usedExpressions, setUsedExpressions] = useState<UsedSelection[]>([]);

useEffect(() => {
let expressions: UsedSelection[] = [];
if (value?.length) {
let values = value.split(' ');
const temp = value.split('and not');
let values = temp
.map((_) => {
return _.trim()
.split('or not')
.map((leaf) => leaf.split(' '))
.reduce((prev, curr) => {
return [...prev, 'or not', ...curr];
});
})
.reduce((prev, curr) => {
return [...prev, 'and not', ...curr];
});

if (OPERATORS.indexOf(values[0]) === -1) values = ['', ...values];

let counter = 0;
Expand Down Expand Up @@ -110,12 +136,7 @@ export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
compressed
value={exp.description}
onChange={(e) => changeExtDescription(e, exp, idx)}
options={[
{ value: '', text: '' },
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
{ value: 'not', text: 'NOT' },
]}
options={idx === 0 ? operationOptionsFirstExpression : operatorOptions}
/>
</EuiFlexItem>
{selections.length > usedExpressions.length && (
Expand Down Expand Up @@ -212,7 +233,7 @@ export const SelectionExpField: React.FC<SelectionExpFieldProps> = ({
description={exp.description}
value={exp.name}
isActive={exp.isOpen}
onClick={(e) => onSelectionPopup(e, idx)}
onClick={(e: any) => onSelectionPopup(e, idx)}
/>
}
isOpen={exp.isOpen}
Expand Down
Loading