mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Detection Engine] fixes empty EQL query validation (#212117)
## Summary - addresses https://github.com/elastic/kibana/issues/201778
This commit is contained in:
parent
0eb08ccc05
commit
fd1a0a9b95
4 changed files with 302 additions and 32 deletions
|
@ -269,6 +269,78 @@ describe('query_preview/helpers', () => {
|
|||
expect(isDisabled).toEqual(false);
|
||||
});
|
||||
|
||||
test('disabled when eql rule with empty query and non-empty filters', () => {
|
||||
const isDisabled = getIsRulePreviewDisabled({
|
||||
ruleType: 'eql',
|
||||
isQueryBarValid: true,
|
||||
isThreatQueryBarValid: false,
|
||||
index: ['test-*'],
|
||||
dataViewId: undefined,
|
||||
dataSourceType: DataSourceType.IndexPatterns,
|
||||
threatIndex: [],
|
||||
threatMapping: [],
|
||||
machineLearningJobId: [],
|
||||
queryBar: {
|
||||
filters: [
|
||||
{
|
||||
meta: {},
|
||||
query: {
|
||||
exists: {
|
||||
field: '_index',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
query: { query: '', language: 'eql' },
|
||||
saved_id: null,
|
||||
},
|
||||
newTermsFields: [],
|
||||
});
|
||||
expect(isDisabled).toEqual(true);
|
||||
});
|
||||
|
||||
test('disabled when eql rule with empty query and empty filters', () => {
|
||||
const isDisabled = getIsRulePreviewDisabled({
|
||||
ruleType: 'eql',
|
||||
isQueryBarValid: true,
|
||||
isThreatQueryBarValid: false,
|
||||
index: ['test-*'],
|
||||
dataViewId: undefined,
|
||||
dataSourceType: DataSourceType.IndexPatterns,
|
||||
threatIndex: [],
|
||||
threatMapping: [],
|
||||
machineLearningJobId: [],
|
||||
queryBar: {
|
||||
filters: [],
|
||||
query: { query: '', language: 'eql' },
|
||||
saved_id: null,
|
||||
},
|
||||
newTermsFields: [],
|
||||
});
|
||||
expect(isDisabled).toEqual(true);
|
||||
});
|
||||
|
||||
test('enabled when eql rule with non empty query', () => {
|
||||
const isDisabled = getIsRulePreviewDisabled({
|
||||
ruleType: 'eql',
|
||||
isQueryBarValid: true,
|
||||
isThreatQueryBarValid: false,
|
||||
index: ['test-*'],
|
||||
dataViewId: undefined,
|
||||
dataSourceType: DataSourceType.IndexPatterns,
|
||||
threatIndex: [],
|
||||
threatMapping: [],
|
||||
machineLearningJobId: [],
|
||||
queryBar: {
|
||||
filters: [],
|
||||
query: { query: 'any where true', language: 'eql' },
|
||||
saved_id: null,
|
||||
},
|
||||
newTermsFields: [],
|
||||
});
|
||||
expect(isDisabled).toEqual(false);
|
||||
});
|
||||
|
||||
// ML rule does not have index or data view id properties, so preview should not depend on these fields
|
||||
test('enabled for ML rule when index patterns and data view id are empty', () => {
|
||||
const isDisabled = getIsRulePreviewDisabled({
|
||||
|
|
|
@ -141,7 +141,10 @@ export const getIsRulePreviewDisabled = ({
|
|||
isThreatQueryBarValid,
|
||||
});
|
||||
}
|
||||
if (ruleType === 'eql' || ruleType === 'query' || ruleType === 'threshold') {
|
||||
if (ruleType === 'eql') {
|
||||
return isEmpty(queryBar.query.query);
|
||||
}
|
||||
if (ruleType === 'query' || ruleType === 'threshold') {
|
||||
return isEmpty(queryBar.query.query) && isEmpty(queryBar.filters);
|
||||
}
|
||||
if (ruleType === 'new_terms') {
|
||||
|
|
|
@ -794,6 +794,186 @@ describe('StepDefineRule', () => {
|
|||
expect(screen.queryByTestId('ai-assistant')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('query validation', () => {
|
||||
describe('Query rule', () => {
|
||||
it('shows query is required when filters and query empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: '', language: 'kuery' },
|
||||
filters: [],
|
||||
saved_id: null,
|
||||
},
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('detectionEngineStepDefineRuleQueryBar')).toHaveTextContent(
|
||||
'A custom query is required'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show query is required when filters not empty and query empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: '', language: 'kuery' },
|
||||
filters: [
|
||||
{
|
||||
meta: {},
|
||||
query: {
|
||||
exists: {
|
||||
field: '_index',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
saved_id: null,
|
||||
},
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await expect(
|
||||
waitFor(() => {
|
||||
expect(screen.getByTestId('detectionEngineStepDefineRuleQueryBar')).toHaveTextContent(
|
||||
'A custom query is required'
|
||||
);
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ES|QL rule', () => {
|
||||
it('shows ES|QL query is required when it is empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: '', language: 'esql' },
|
||||
filters: [],
|
||||
saved_id: null,
|
||||
},
|
||||
ruleType: 'esql' as const,
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('ruleEsqlQueryBar')).toHaveTextContent(
|
||||
'ES|QL query is required'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show ES|QL query is required when it is not empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: 'from my_index metadata _id', language: 'esql' },
|
||||
filters: [],
|
||||
saved_id: null,
|
||||
},
|
||||
ruleType: 'esql' as const,
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await expect(
|
||||
waitFor(() => {
|
||||
expect(screen.getByTestId('ruleEsqlQueryBar')).toHaveTextContent(
|
||||
'ES|QL query is required'
|
||||
);
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('EQL rule', () => {
|
||||
it('shows EQL query is required when it is empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: '', language: 'eql' },
|
||||
filters: [],
|
||||
saved_id: null,
|
||||
},
|
||||
ruleType: 'eql' as const,
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('ruleEqlQueryBar')).toHaveTextContent('EQL query is required');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows EQL query is required when query empty, but filters non-empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: '', language: 'eql' },
|
||||
filters: [
|
||||
{
|
||||
meta: {},
|
||||
query: {
|
||||
exists: {
|
||||
field: '_index',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
saved_id: null,
|
||||
},
|
||||
ruleType: 'eql' as const,
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('ruleEqlQueryBar')).toHaveTextContent('EQL query is required');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show EQL query is required when it is not empty', async () => {
|
||||
const initialState = {
|
||||
queryBar: {
|
||||
query: { query: 'any where true', language: 'eql' },
|
||||
filters: [],
|
||||
saved_id: null,
|
||||
},
|
||||
ruleType: 'eql' as const,
|
||||
};
|
||||
render(<TestForm formProps={{ isQueryBarValid: false }} initialState={initialState} />, {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
await submitForm();
|
||||
|
||||
await expect(
|
||||
waitFor(() => {
|
||||
expect(screen.getByTestId('detectionEngineStepDefineRuleQueryBar')).toHaveTextContent(
|
||||
'EQL query is required'
|
||||
);
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
interface TestFormProps {
|
||||
|
|
|
@ -12,45 +12,60 @@ import type { FormData, ValidationFunc } from '../../../shared_imports';
|
|||
import { isEqlRule, isEsqlRule } from '../../../../common/detection_engine/utils';
|
||||
import type { FieldValueQueryBar } from '../components/query_bar_field';
|
||||
|
||||
const EQL_REQUIRED = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'An EQL query is required.',
|
||||
}
|
||||
);
|
||||
|
||||
const ESQL_REQUIRED = i18n.translate(
|
||||
'xpack.securitySolution.ruleManagement.ruleCreation.validation.query.esqlQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'An ES|QL query is required.',
|
||||
}
|
||||
);
|
||||
|
||||
const CUSTOM_QUERY_REQUIRED = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'A custom query is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export function queryRequiredValidatorFactory(
|
||||
ruleType: RuleType
|
||||
): ValidationFunc<FormData, string, FieldValueQueryBar> {
|
||||
return (...args) => {
|
||||
const [{ path, value }] = args;
|
||||
const validationError = {
|
||||
code: 'ERR_FIELD_MISSING',
|
||||
path,
|
||||
};
|
||||
|
||||
if (isEmpty(value.query.query as string) && isEmpty(value.filters)) {
|
||||
if (!isEmpty(value.query.query as string)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEqlRule(ruleType)) {
|
||||
return {
|
||||
code: 'ERR_FIELD_MISSING',
|
||||
path,
|
||||
message: getErrorMessage(ruleType),
|
||||
...validationError,
|
||||
message: EQL_REQUIRED,
|
||||
};
|
||||
}
|
||||
|
||||
if (isEsqlRule(ruleType)) {
|
||||
return {
|
||||
...validationError,
|
||||
message: ESQL_REQUIRED,
|
||||
};
|
||||
}
|
||||
|
||||
if (isEmpty(value.filters)) {
|
||||
return {
|
||||
...validationError,
|
||||
message: CUSTOM_QUERY_REQUIRED,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getErrorMessage(ruleType: RuleType): string {
|
||||
if (isEsqlRule(ruleType)) {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.ruleManagement.ruleCreation.validation.query.esqlQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'An ES|QL query is required.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (isEqlRule(ruleType)) {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.eqlQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'An EQL query is required.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.customQueryFieldRequiredError',
|
||||
{
|
||||
defaultMessage: 'A custom query is required.',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue