mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution] Rule is created when the conditional logic "If alert matches a query" is left blank (#159690)
## Summary
Original ticket: https://github.com/elastic/kibana/issues/156706
These changes prevent user from creating/updating the rule when alert
filter is selected and query left blank on the rule's action page. We
gonna show an error saying "A custom query is required." in this case.
<img width="1739" alt="Screenshot 2023-06-14 at 14 36 35"
src="0456f211
-603c-44d9-9271-9cfdf59f12b6">
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
8f01d85966
commit
9b6ad7280d
6 changed files with 60 additions and 16 deletions
|
@ -119,7 +119,7 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
}) => {
|
||||
const [fieldErrors, setFieldErrors] = useState<string | null>(null);
|
||||
const form = useFormContext();
|
||||
const { isSubmitted, isSubmitting, isValid } = form;
|
||||
const { isValid } = form;
|
||||
const {
|
||||
triggersActionsUi: { getActionForm },
|
||||
} = useKibana().services;
|
||||
|
@ -231,6 +231,7 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
[field]
|
||||
);
|
||||
|
||||
const isFormValidated = isValid !== undefined;
|
||||
const actionForm = useMemo(
|
||||
() =>
|
||||
getActionForm({
|
||||
|
@ -251,6 +252,7 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
hasSummary: true,
|
||||
notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS,
|
||||
defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY,
|
||||
disableErrorMessages: !isFormValidated,
|
||||
}),
|
||||
[
|
||||
actions,
|
||||
|
@ -262,18 +264,17 @@ export const RuleActionsField: React.FC<Props> = ({
|
|||
setActionParamsProperty,
|
||||
setAlertActionsProperty,
|
||||
setActionAlertsFilterProperty,
|
||||
isFormValidated,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitting || !field.errors.length) {
|
||||
return setFieldErrors(null);
|
||||
}
|
||||
if (isSubmitted && !isSubmitting && isValid === false && field.errors.length) {
|
||||
if (isValid === false) {
|
||||
const errorsString = field.errors.map(({ message }) => message).join('\n');
|
||||
return setFieldErrors(errorsString);
|
||||
}
|
||||
}, [isSubmitted, isSubmitting, field.isChangingValue, isValid, field.errors, setFieldErrors]);
|
||||
return setFieldErrors(null);
|
||||
}, [field.errors, isValid]);
|
||||
|
||||
return (
|
||||
<ContainerActions $caseIndexes={caseActionIndexes}>
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
RuleAction,
|
||||
ActionTypeRegistryContract,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { validateActionFilterQuery } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { RuleActionsFormData } from '../../../../../detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form';
|
||||
import type { ActionsStepRule } from '../../../../pages/detection_engine/rules/types';
|
||||
import type { ValidationFunc, ERROR_CODE } from '../../../../../shared_imports';
|
||||
|
@ -29,8 +30,9 @@ export const validateSingleAction = async (
|
|||
): Promise<string[]> => {
|
||||
const actionParamsErrors = await validateActionParams(actionItem, actionTypeRegistry);
|
||||
const mustacheErrors = validateMustache(actionItem.params);
|
||||
const queryErrors = validateActionFilterQuery(actionItem);
|
||||
|
||||
return [...actionParamsErrors, ...mustacheErrors];
|
||||
return [...actionParamsErrors, ...mustacheErrors, ...(queryErrors ? [queryErrors] : [])];
|
||||
};
|
||||
|
||||
export const validateRuleActionsField =
|
||||
|
|
|
@ -7,7 +7,23 @@
|
|||
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
import { constant, get } from 'lodash';
|
||||
import { UserConfiguredActionConnector, IErrorObject, Rule } from '../../types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { UserConfiguredActionConnector, IErrorObject, Rule, RuleAction } from '../../types';
|
||||
|
||||
const filterQueryRequiredError = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.actionTypeForm.error.requiredFilterQuery',
|
||||
{
|
||||
defaultMessage: 'A custom query is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const validateActionFilterQuery = (actionItem: RuleAction): string | null => {
|
||||
const query = actionItem.alertsFilter?.query;
|
||||
if (query && !query.kql) {
|
||||
return filterQueryRequiredError;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function throwIfAbsent<T>(message: string) {
|
||||
return (value: T | undefined): T => {
|
||||
|
|
|
@ -86,6 +86,7 @@ export interface ActionAccordionFormProps {
|
|||
defaultRuleFrequency?: RuleActionFrequency;
|
||||
ruleTypeId?: string;
|
||||
hasFieldsForAAD?: boolean;
|
||||
disableErrorMessages?: boolean;
|
||||
}
|
||||
|
||||
interface ActiveActionConnectorState {
|
||||
|
@ -122,6 +123,7 @@ export const ActionForm = ({
|
|||
ruleTypeId,
|
||||
producerId,
|
||||
hasFieldsForAAD,
|
||||
disableErrorMessages,
|
||||
}: ActionAccordionFormProps) => {
|
||||
const {
|
||||
http,
|
||||
|
@ -499,6 +501,7 @@ export const ActionForm = ({
|
|||
producerId={producerId}
|
||||
ruleTypeId={ruleTypeId}
|
||||
hasFieldsForAAD={hasFieldsForAAD}
|
||||
disableErrorMessages={disableErrorMessages}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -63,6 +63,7 @@ import { ActionNotifyWhen } from './action_notify_when';
|
|||
import { validateParamsForWarnings } from '../../lib/validate_params_for_warnings';
|
||||
import { ActionAlertsFilterTimeframe } from './action_alerts_filter_timeframe';
|
||||
import { ActionAlertsFilterQuery } from './action_alerts_filter_query';
|
||||
import { validateActionFilterQuery } from '../../lib/value_validators';
|
||||
|
||||
export type ActionTypeFormProps = {
|
||||
actionItem: RuleAction;
|
||||
|
@ -92,6 +93,7 @@ export type ActionTypeFormProps = {
|
|||
producerId: string;
|
||||
ruleTypeId?: string;
|
||||
hasFieldsForAAD?: boolean;
|
||||
disableErrorMessages?: boolean;
|
||||
} & Pick<
|
||||
ActionAccordionFormProps,
|
||||
| 'defaultActionGroupId'
|
||||
|
@ -142,6 +144,7 @@ export const ActionTypeForm = ({
|
|||
featureId,
|
||||
ruleTypeId,
|
||||
hasFieldsForAAD,
|
||||
disableErrorMessages,
|
||||
}: ActionTypeFormProps) => {
|
||||
const {
|
||||
application: { capabilities },
|
||||
|
@ -247,13 +250,28 @@ export const ActionTypeForm = ({
|
|||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (disableErrorMessages) {
|
||||
setActionParamsErrors({ errors: {} });
|
||||
return;
|
||||
}
|
||||
const res: { errors: IErrorObject } = await actionTypeRegistry
|
||||
.get(actionItem.actionTypeId)
|
||||
?.validateParams(actionItem.params);
|
||||
setActionParamsErrors(res);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actionItem]);
|
||||
}, [actionItem, disableErrorMessages]);
|
||||
|
||||
const [queryError, setQueryError] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (disableErrorMessages) {
|
||||
setQueryError(null);
|
||||
return;
|
||||
}
|
||||
setQueryError(validateActionFilterQuery(actionItem));
|
||||
})();
|
||||
}, [actionItem, disableErrorMessages]);
|
||||
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
|
@ -424,13 +442,15 @@ export const ActionTypeForm = ({
|
|||
{showActionAlertsFilter && (
|
||||
<>
|
||||
{!hideNotifyWhen && <EuiSpacer size="xl" />}
|
||||
<ActionAlertsFilterQuery
|
||||
state={actionItem.alertsFilter?.query}
|
||||
onChange={(query) => setActionAlertsFilterProperty('query', query, index)}
|
||||
featureIds={[producerId as ValidFeatureId]}
|
||||
appName={featureId!}
|
||||
ruleTypeId={ruleTypeId}
|
||||
/>
|
||||
<EuiFormRow error={queryError} isInvalid={!!queryError} fullWidth>
|
||||
<ActionAlertsFilterQuery
|
||||
state={actionItem.alertsFilter?.query}
|
||||
onChange={(query) => setActionAlertsFilterProperty('query', query, index)}
|
||||
featureIds={[producerId as ValidFeatureId]}
|
||||
appName={featureId!}
|
||||
ruleTypeId={ruleTypeId}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="s" />
|
||||
<ActionAlertsFilterTimeframe
|
||||
state={actionItem.alertsFilter?.timeframe}
|
||||
|
|
|
@ -147,3 +147,5 @@ export const getNotifyWhenOptions = async () => {
|
|||
};
|
||||
|
||||
export { transformRule } from './application/lib/rule_api/common_transformations';
|
||||
|
||||
export { validateActionFilterQuery } from './application/lib/value_validators';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue