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

[Security Solution][Detections][Tech Debt] - Move to using common io-ts types #75009

Merged
merged 12 commits into from
Sep 4, 2020
2 changes: 2 additions & 0 deletions x-pack/plugins/lists/common/shared_exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ export {
} from './schemas';

export { ENDPOINT_LIST_ID } from './constants';

export { toPromise, toError } from './fp_utils';
2 changes: 1 addition & 1 deletion x-pack/plugins/lists/public/lists/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from '../../common/schemas';
import { LIST_INDEX, LIST_ITEM_URL, LIST_PRIVILEGES_URL, LIST_URL } from '../../common/constants';
import { validateEither } from '../../common/shared_imports';
import { toError, toPromise } from '../common/fp_utils';
import { toError, toPromise } from '../../common/fp_utils';

import {
ApiParams,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/lists/public/shared_exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export {
Pagination,
UseExceptionListSuccess,
} from './exceptions/types';
export { toPromise, toError } from '../common/fp_utils';
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export const sortFieldOrUndefined = t.union([sort_field, t.undefined]);
export type SortFieldOrUndefined = t.TypeOf<typeof sortFieldOrUndefined>;

export const sort_order = t.keyof({ asc: null, desc: null });
export type sortOrder = t.TypeOf<typeof sort_order>;
export type SortOrder = t.TypeOf<typeof sort_order>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was just a typo? Seems like all other typescript ones are uppercase.


export const sortOrderOrUndefined = t.union([sort_order, t.undefined]);
export type SortOrderOrUndefined = t.TypeOf<typeof sortOrderOrUndefined>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './add_prepackaged_rules_schema';
export * from './create_rules_bulk_schema';
export * from './create_rules_schema';
export * from './export_rules_schema';
export * from './find_rules_schema';
export * from './import_rules_schema';
export * from './patch_rules_bulk_schema';
export * from './patch_rules_schema';
export * from './query_rules_schema';
export * from './query_signals_index_schema';
export * from './set_signal_status_schema';
export * from './update_rules_bulk_schema';
export * from './update_rules_schema';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './error_schema';
export * from './find_rules_schema';
export * from './import_rules_schema';
export * from './prepackaged_rules_schema';
export * from './prepackaged_rules_status_schema';
export * from './rules_bulk_schema';
export * from './rules_schema';
export * from './type_timeline_only_schema';
2 changes: 2 additions & 0 deletions x-pack/plugins/security_solution/common/shared_imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ export {
ExceptionListType,
Type,
ENDPOINT_LIST_ID,
toPromise,
toError,
} from '../../lists/common';
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
*/

import {
AddRulesProps,
PatchRuleProps,
NewRule,
CreateRulesProps,
UpdateRulesProps,
PrePackagedRulesStatusResponse,
BasicFetchProps,
RuleStatusResponse,
Expand All @@ -16,13 +16,18 @@ import {
FetchRulesResponse,
FetchRulesProps,
} from '../types';
import { ruleMock, savedRuleMock, rulesMock } from '../mock';
import { savedRuleMock, rulesMock } from '../mock';
import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks';
import { RulesSchema } from '../../../../../../common/detection_engine/schemas/response';

export const addRule = async ({ rule, signal }: AddRulesProps): Promise<NewRule> =>
Promise.resolve(ruleMock);
export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<RulesSchema> =>
Promise.resolve(getRulesSchemaMock());

export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise<NewRule> =>
Promise.resolve(ruleMock);
export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<RulesSchema> =>
Promise.resolve(getRulesSchemaMock());

export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise<RulesSchema> =>
Promise.resolve(getRulesSchemaMock());

export const getPrePackagedRulesStatus = async ({
signal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import { KibanaServices } from '../../../../common/lib/kibana';
import {
addRule,
createRule,
updateRule,
patchRule,
fetchRules,
fetchRuleById,
enableRules,
Expand All @@ -19,9 +21,17 @@ import {
fetchTags,
getPrePackagedRulesStatus,
} from './api';
import { ruleMock, rulesMock } from './mock';
import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks';
import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/create_rules_schema.mock';
import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/update_rules_schema.mock';
import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock';
import { rulesMock } from './mock';
import { buildEsQuery } from 'src/plugins/data/common';

import {
UpdateRulesSchema,
CreateRulesSchema,
PatchRulesSchema,
} from '../../../../../common/detection_engine/schemas/request';
const abortCtrl = new AbortController();
const mockKibanaServices = KibanaServices.get as jest.Mock;
jest.mock('../../../../common/lib/kibana');
Expand All @@ -30,25 +40,145 @@ const fetchMock = jest.fn();
mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } });

describe('Detections Rules API', () => {
describe('addRule', () => {
describe('createRule', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(ruleMock);
fetchMock.mockResolvedValue(getRulesSchemaMock());
});

test('check parameter url, body', async () => {
await addRule({ rule: ruleMock, signal: abortCtrl.signal });
test('POSTs rule', async () => {
const payload = getCreateRulesSchemaMock();
await createRule({ rule: payload, signal: abortCtrl.signal });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', {
body:
'{"description":"some desc","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf","language":"kuery","risk_score":75,"name":"Test rule","query":"user.email: \'root@elastic.co\'","references":[],"severity":"high","tags":["APM"],"to":"now","type":"query","threat":[],"throttle":null}',
'{"description":"Detecting root and admin users","name":"Query with a rule id","severity":"high","type":"query","risk_score":55,"query":"user.name: root or user.name: admin","language":"kuery","rule_id":"rule-1","actions":[],"author":[],"enabled":true,"false_positives":[],"from":"now-6m","interval":"5m","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"tags":[],"to":"now","threat":[],"throttle":null,"references":[],"version":1,"exceptions_list":[]}',
method: 'POST',
signal: abortCtrl.signal,
});
});

test('happy path', async () => {
const ruleResp = await addRule({ rule: ruleMock, signal: abortCtrl.signal });
expect(ruleResp).toEqual(ruleMock);
it('rejects with an error if request payload is invalid (and does not make API call)', async () => {
const payload: Partial<CreateRulesSchema> = {
rule_id: 'rule-1',
description: 'some description',
from: 'now-5m',
to: 'now',
name: 'some-name',
severity: 'low',
type: 'query',
interval: '5m',
index: ['index-1'],
};

await expect(
createRule({
rule: (payload as unknown) as CreateRulesSchema,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "undefined" supplied to "risk_score"'));
expect(fetchMock).not.toHaveBeenCalled();
});

it('rejects with an error if response payload is invalid', async () => {
const payload: CreateRulesSchema = getCreateRulesSchemaMock();
const badResponse = { ...getRulesSchemaMock(), id: undefined };
fetchMock.mockResolvedValue(badResponse);

await expect(
createRule({
rule: payload,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"'));
});
});

describe('updateRule', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(getRulesSchemaMock());
});

test('POSTs rule', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test name here says POST, but the test is checking for PUT

const payload = getUpdateRulesSchemaMock();
await updateRule({ rule: payload, signal: abortCtrl.signal });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', {
body:
'{"description":"some description","name":"Query with a rule id","severity":"high","type":"query","risk_score":55,"query":"user.name: root or user.name: admin","language":"kuery","rule_id":"rule-1","actions":[],"author":[],"enabled":true,"false_positives":[],"from":"now-6m","interval":"5m","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"tags":[],"to":"now","threat":[],"throttle":null,"references":[],"exceptions_list":[]}',
method: 'PUT',
signal: abortCtrl.signal,
});
});

it('rejects with an error if request payload is invalid (and does not make API call)', async () => {
const payload: Omit<UpdateRulesSchema, 'note'> & {
note: number;
} = { ...getUpdateRulesSchemaMock(), note: 23 };

await expect(
updateRule({
rule: (payload as unknown) as UpdateRulesSchema,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "23" supplied to "note"'));
expect(fetchMock).not.toHaveBeenCalled();
});

it('rejects with an error if response payload is invalid', async () => {
const payload: UpdateRulesSchema = getUpdateRulesSchemaMock();
const badResponse = { ...getRulesSchemaMock(), id: undefined };
fetchMock.mockResolvedValue(badResponse);

await expect(
updateRule({
rule: payload,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"'));
});
});

describe('patchRule', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(getRulesSchemaMock());
});

test('PATCHs rule', async () => {
const payload = getPatchRulesSchemaMock();
await patchRule({ ruleProperties: payload, signal: abortCtrl.signal });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', {
body: JSON.stringify(payload),
method: 'PATCH',
signal: abortCtrl.signal,
});
});

it('rejects with an error if request payload is invalid (and does not make API call)', async () => {
const payload: Omit<PatchRulesSchema, 'note'> & {
note: number;
} = { ...getPatchRulesSchemaMock(), note: 3 };

await expect(
patchRule({
ruleProperties: (payload as unknown) as PatchRulesSchema,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "3" supplied to "note"'));
expect(fetchMock).not.toHaveBeenCalled();
});

it('rejects with an error if response payload is invalid', async () => {
const payload: PatchRulesSchema = getPatchRulesSchemaMock();
const badResponse = { ...getRulesSchemaMock(), id: undefined };
fetchMock.mockResolvedValue(badResponse);

await expect(
patchRule({
ruleProperties: payload,
signal: abortCtrl.signal,
})
).rejects.toEqual(new Error('Invalid value "undefined" supplied to "id"'));
});
});

Expand Down Expand Up @@ -280,7 +410,7 @@ describe('Detections Rules API', () => {
describe('fetchRuleById', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(ruleMock);
fetchMock.mockResolvedValue(getRulesSchemaMock());
});

test('check parameter url, query', async () => {
Expand All @@ -296,7 +426,7 @@ describe('Detections Rules API', () => {

test('happy path', async () => {
const ruleResp = await fetchRuleById({ id: 'mySuperRuleId', signal: abortCtrl.signal });
expect(ruleResp).toEqual(ruleMock);
expect(ruleResp).toEqual(getRulesSchemaMock());
});
});

Expand Down
Loading