[SIEM][Lists] Adds test mocks and README.md to the lists plugin

## Summary

* https://github.com/elastic/kibana/issues/67675
* Adds README.md to the lists plugin
* Adds the mocks to the server side of the lists plugin
* Changes out the SIEM code to use the mocks now that they are within the plugin

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [x] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials
- [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
This commit is contained in:
Frank Hassanabad 2020-05-30 18:52:01 -06:00 committed by GitHub
parent 6a8b07fe8e
commit 96e0e911ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 690 additions and 210 deletions

View file

@ -0,0 +1,254 @@
README.md for developers working on the backend lists on how to get started
using the CURL scripts in the scripts folder.
The scripts rely on CURL and jq:
- [CURL](https://curl.haxx.se)
- [jq](https://stedolan.github.io/jq/)
Install curl and jq (mac instructions)
```sh
brew update
brew install curl
brew install jq
```
Open `$HOME/.zshrc` or `${HOME}.bashrc` depending on your SHELL output from `echo $SHELL`
and add these environment variables:
```sh
export ELASTICSEARCH_USERNAME=${user}
export ELASTICSEARCH_PASSWORD=${password}
export ELASTICSEARCH_URL=https://${ip}:9200
export KIBANA_URL=http://localhost:5601
export TASK_MANAGER_INDEX=.kibana-task-manager-${your user id}
export KIBANA_INDEX=.kibana-${your user id}
```
source `$HOME/.zshrc` or `${HOME}.bashrc` to ensure variables are set:
```sh
source ~/.zshrc
```
Open your `kibana.dev.yml` file and add these lines:
```sh
# Enable lists feature
xpack.lists.enabled: true
xpack.lists.listIndex: '.lists-frank'
xpack.lists.listItemIndex: '.items-frank'
```
Restart Kibana and ensure that you are using `--no-base-path` as changing the base path is a feature but will
get in the way of the CURL scripts written as is.
Go to the scripts folder `cd kibana/x-pack/plugins/lists/server/scripts` and run:
```sh
./hard_reset.sh
./post_list.sh
```
which will:
- Delete any existing lists you have
- Delete any existing list items you have
- Delete any existing exception lists you have
- Delete any existing exception list items you have
- Delete any existing mapping, policies, and templates, you might have previously had.
- Add the latest list and list item index and its mappings using your settings from `kibana.dev.yml` environment variable of `xpack.lists.listIndex` and `xpack.lists.listItemIndex`.
- Posts the sample list from `./lists/new/list_ip.json`
Now you can run
```sh
./post_list.sh
```
You should see the new list created like so:
```sh
{
"id": "list-ip",
"created_at": "2020-05-28T19:15:22.344Z",
"created_by": "yo",
"description": "This list describes bad internet ip",
"name": "Simple list with an ip",
"tie_breaker_id": "c57efbc4-4977-4a32-995f-cfd296bed521",
"type": "ip",
"updated_at": "2020-05-28T19:15:22.344Z",
"updated_by": "yo"
}
```
You can add a list item like so:
```sh
./post_list_item.sh
```
You should see the new list item created and attached to the above list like so:
```sh
{
"id": "hand_inserted_item_id",
"type": "ip",
"value": "127.0.0.1",
"created_at": "2020-05-28T19:15:49.790Z",
"created_by": "yo",
"list_id": "list-ip",
"tie_breaker_id": "a881bf2e-1e17-4592-bba8-d567cb07d234",
"updated_at": "2020-05-28T19:15:49.790Z",
"updated_by": "yo"
}
```
If you want to post an exception list it would be like so:
```sh
./post_exception_list.sh
```
You should see the new exception list created like so:
```sh
{
"_tags": [
"endpoint",
"process",
"malware",
"os:linux"
],
"created_at": "2020-05-28T19:16:31.052Z",
"created_by": "yo",
"description": "This is a sample endpoint type exception",
"id": "bcb94680-a117-11ea-ad9d-c71f4820e65b",
"list_id": "endpoint_list",
"name": "Sample Endpoint Exception List",
"namespace_type": "single",
"tags": [
"user added string for a tag",
"malware"
],
"tie_breaker_id": "86e08c8c-c970-4b08-a6e2-cdba7bb4e023",
"type": "endpoint",
"updated_at": "2020-05-28T19:16:31.080Z",
"updated_by": "yo"
}
```
And you can attach exception list items like so:
```ts
{
"_tags": [
"endpoint",
"process",
"malware",
"os:linux"
],
"comment": [],
"created_at": "2020-05-28T19:17:21.099Z",
"created_by": "yo",
"description": "This is a sample endpoint type exception",
"entries": [
{
"field": "actingProcess.file.signer",
"operator": "included",
"match": "Elastic, N.V."
},
{
"field": "event.category",
"operator": "included",
"match_any": [
"process",
"malware"
]
}
],
"id": "da8d3b30-a117-11ea-ad9d-c71f4820e65b",
"item_id": "endpoint_list_item",
"list_id": "endpoint_list",
"name": "Sample Endpoint Exception List",
"namespace_type": "single",
"tags": [
"user added string for a tag",
"malware"
],
"tie_breaker_id": "21f84703-9476-4af8-a212-aad31e18dcb9",
"type": "simple",
"updated_at": "2020-05-28T19:17:21.123Z",
"updated_by": "yo"
}
```
You can then do find for each one like so:
```sh
./find_lists.sh
```
```sh
{
"cursor": "WzIwLFsiYzU3ZWZiYzQtNDk3Ny00YTMyLTk5NWYtY2ZkMjk2YmVkNTIxIl1d",
"data": [
{
"id": "list-ip",
"created_at": "2020-05-28T19:15:22.344Z",
"created_by": "yo",
"description": "This list describes bad internet ip",
"name": "Simple list with an ip",
"tie_breaker_id": "c57efbc4-4977-4a32-995f-cfd296bed521",
"type": "ip",
"updated_at": "2020-05-28T19:15:22.344Z",
"updated_by": "yo"
}
],
"page": 1,
"per_page": 20,
"total": 1
}
```
or for finding exception lists:
```sh
./find_exception_lists.sh
```
```sh
{
"data": [
{
"_tags": [
"endpoint",
"process",
"malware",
"os:linux"
],
"created_at": "2020-05-28T19:16:31.052Z",
"created_by": "yo",
"description": "This is a sample endpoint type exception",
"id": "bcb94680-a117-11ea-ad9d-c71f4820e65b",
"list_id": "endpoint_list",
"name": "Sample Endpoint Exception List",
"namespace_type": "single",
"tags": [
"user added string for a tag",
"malware"
],
"tie_breaker_id": "86e08c8c-c970-4b08-a6e2-cdba7bb4e023",
"type": "endpoint",
"updated_at": "2020-05-28T19:16:31.080Z",
"updated_by": "yo"
}
],
"page": 1,
"per_page": 20,
"total": 1
}
```
See the full scripts folder for all the capabilities.

View file

@ -0,0 +1,40 @@
/*
* 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.
*/
import { ExceptionListItemSchema } from './exception_list_item_schema';
export const getExceptionListItemSchemaMock = (): ExceptionListItemSchema => ({
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
comment: [],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',
entries: [
{
field: 'actingProcess.file.signer',
match: 'Elastic, N.V.',
match_any: undefined,
operator: 'included',
},
{
field: 'event.category',
match: undefined,
match_any: ['process', 'malware'],
operator: 'included',
},
],
id: '1',
item_id: 'endpoint_list_item',
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'simple',
updated_at: '2020-04-23T00:19:13.289Z',
updated_by: 'user_name',
});

View file

@ -0,0 +1,24 @@
/*
* 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.
*/
import { ExceptionListSchema } from './exception_list_schema';
export const getExceptionListSchemaMock = (): ExceptionListSchema => ({
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',
id: '1',
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'endpoint',
updated_at: '2020-04-23T00:19:13.289Z',
updated_by: 'user_name',
});

View file

@ -0,0 +1,15 @@
/*
* 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.
*/
import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock';
import { FoundExceptionListItemSchema } from './found_exception_list_item_schema';
export const getFoundExceptionListItemSchemaMock = (): FoundExceptionListItemSchema => ({
data: [getExceptionListItemSchemaMock()],
page: 1,
per_page: 1,
total: 1,
});

View file

@ -0,0 +1,15 @@
/*
* 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.
*/
import { getExceptionListSchemaMock } from './exception_list_schema.mock';
import { FoundExceptionListSchema } from './found_exception_list_schema';
export const getFoundExceptionListSchemaMock = (): FoundExceptionListSchema => ({
data: [getExceptionListSchemaMock()],
page: 1,
per_page: 1,
total: 1,
});

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
import { FoundListItemSchema } from './found_list_item_schema';
import { getListItemResponseMock } from './list_item_schema.mock';
export const getFoundListItemSchemaMock = (): FoundListItemSchema => ({
cursor: '123',
data: [getListItemResponseMock()],
page: 1,
per_page: 1,
total: 1,
});

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
import { FoundListSchema } from './found_list_schema';
import { getListResponseMock } from './list_schema.mock';
export const getFoundListSchemaMock = (): FoundListSchema => ({
cursor: '123',
data: [getListResponseMock()],
page: 1,
per_page: 1,
total: 1,
});

View file

@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
import {
ExceptionListItemSchema,
ExceptionListSchema,
@ -15,37 +17,38 @@ import {
ApiCallByIdProps,
ApiCallByListIdProps,
} from '../types';
import { mockExceptionItem, mockExceptionList } from '../mock';
/* eslint-disable @typescript-eslint/no-unused-vars */
export const addExceptionList = async ({
http,
list,
signal,
}: AddExceptionListProps): Promise<ExceptionListSchema> => Promise.resolve(mockExceptionList);
}: AddExceptionListProps): Promise<ExceptionListSchema> =>
Promise.resolve(getExceptionListSchemaMock());
export const addExceptionListItem = async ({
http,
listItem,
signal,
}: AddExceptionListItemProps): Promise<ExceptionListItemSchema> =>
Promise.resolve(mockExceptionItem);
Promise.resolve(getExceptionListItemSchemaMock());
export const fetchExceptionListById = async ({
http,
id,
signal,
}: ApiCallByIdProps): Promise<ExceptionListSchema> => Promise.resolve(mockExceptionList);
}: ApiCallByIdProps): Promise<ExceptionListSchema> => Promise.resolve(getExceptionListSchemaMock());
export const fetchExceptionListItemsByListId = async ({
http,
listId,
signal,
}: ApiCallByListIdProps): Promise<FoundExceptionListItemSchema> =>
Promise.resolve({ data: [mockExceptionItem], page: 1, per_page: 20, total: 1 });
Promise.resolve({ data: [getExceptionListItemSchemaMock()], page: 1, per_page: 20, total: 1 });
export const fetchExceptionListItemById = async ({
http,
id,
signal,
}: ApiCallByIdProps): Promise<ExceptionListItemSchema> => Promise.resolve(mockExceptionItem);
}: ApiCallByIdProps): Promise<ExceptionListItemSchema> =>
Promise.resolve(getExceptionListItemSchemaMock());

View file

@ -4,13 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { createKibanaCoreStartMock } from '../common/mocks/kibana_core';
import { getExceptionListSchemaMock } from '../../common/schemas/response/exception_list_schema.mock';
import { getExceptionListItemSchemaMock } from '../../common/schemas/response/exception_list_item_schema.mock';
import {
mockExceptionItem,
mockExceptionList,
mockNewExceptionItem,
mockNewExceptionList,
} from './mock';
import { mockNewExceptionItem, mockNewExceptionList } from './mock';
import {
addExceptionList,
addExceptionListItem,
@ -43,7 +40,7 @@ describe('Exceptions Lists API', () => {
describe('addExceptionList', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(mockExceptionList);
fetchMock.mockResolvedValue(getExceptionListSchemaMock());
});
test('check parameter url, body', async () => {
@ -63,7 +60,7 @@ describe('Exceptions Lists API', () => {
test('check parameter url, body when "list.id" exists', async () => {
await addExceptionList({
http: mockKibanaHttpService(),
list: mockExceptionList,
list: getExceptionListSchemaMock(),
signal: abortCtrl.signal,
});
expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists', {
@ -80,14 +77,14 @@ describe('Exceptions Lists API', () => {
list: mockNewExceptionList,
signal: abortCtrl.signal,
});
expect(exceptionResponse).toEqual(mockExceptionList);
expect(exceptionResponse).toEqual(getExceptionListSchemaMock());
});
});
describe('addExceptionListItem', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(mockExceptionItem);
fetchMock.mockResolvedValue(getExceptionListItemSchemaMock());
});
test('check parameter url, body', async () => {
@ -107,7 +104,7 @@ describe('Exceptions Lists API', () => {
test('check parameter url, body when "listItem.id" exists', async () => {
await addExceptionListItem({
http: mockKibanaHttpService(),
listItem: mockExceptionItem,
listItem: getExceptionListItemSchemaMock(),
signal: abortCtrl.signal,
});
expect(fetchMock).toHaveBeenCalledWith('/api/exception_lists/items', {
@ -124,14 +121,14 @@ describe('Exceptions Lists API', () => {
listItem: mockNewExceptionItem,
signal: abortCtrl.signal,
});
expect(exceptionResponse).toEqual(mockExceptionItem);
expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock());
});
});
describe('fetchExceptionListById', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(mockExceptionList);
fetchMock.mockResolvedValue(getExceptionListSchemaMock());
});
test('check parameter url, body', async () => {
@ -155,7 +152,7 @@ describe('Exceptions Lists API', () => {
id: '1',
signal: abortCtrl.signal,
});
expect(exceptionResponse).toEqual(mockExceptionList);
expect(exceptionResponse).toEqual(getExceptionListSchemaMock());
});
});
@ -224,7 +221,7 @@ describe('Exceptions Lists API', () => {
describe('deleteExceptionListById', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(mockExceptionList);
fetchMock.mockResolvedValue(getExceptionListSchemaMock());
});
test('check parameter url, body when deleting exception item', async () => {
@ -248,14 +245,14 @@ describe('Exceptions Lists API', () => {
id: '1',
signal: abortCtrl.signal,
});
expect(exceptionResponse).toEqual(mockExceptionList);
expect(exceptionResponse).toEqual(getExceptionListSchemaMock());
});
});
describe('deleteExceptionListItemById', () => {
beforeEach(() => {
fetchMock.mockClear();
fetchMock.mockResolvedValue(mockExceptionItem);
fetchMock.mockResolvedValue(getExceptionListItemSchemaMock());
});
test('check parameter url, body when deleting exception item', async () => {
@ -279,7 +276,7 @@ describe('Exceptions Lists API', () => {
id: '1',
signal: abortCtrl.signal,
});
expect(exceptionResponse).toEqual(mockExceptionItem);
expect(exceptionResponse).toEqual(getExceptionListItemSchemaMock());
});
});
});

View file

@ -6,7 +6,7 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { mockExceptionItem } from '../mock';
import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core';
import { ReturnPersistExceptionItem, usePersistExceptionItem } from './persist_exception_item';
@ -32,7 +32,7 @@ describe('usePersistExceptionItem', () => {
() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);
await waitForNextUpdate();
result.current[1](mockExceptionItem);
result.current[1](getExceptionListItemSchemaMock());
rerender();
expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]);
});
@ -45,7 +45,7 @@ describe('usePersistExceptionItem', () => {
usePersistExceptionItem({ http: mockKibanaHttpService, onError })
);
await waitForNextUpdate();
result.current[1](mockExceptionItem);
result.current[1](getExceptionListItemSchemaMock());
await waitForNextUpdate();
expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]);
});

View file

@ -6,7 +6,7 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { mockExceptionList } from '../mock';
import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
import { createKibanaCoreStartMock } from '../../common/mocks/kibana_core';
import { ReturnPersistExceptionList, usePersistExceptionList } from './persist_exception_list';
@ -32,7 +32,7 @@ describe('usePersistExceptionList', () => {
() => usePersistExceptionList({ http: mockKibanaHttpService, onError })
);
await waitForNextUpdate();
result.current[1](mockExceptionList);
result.current[1](getExceptionListSchemaMock());
rerender();
expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]);
});
@ -45,7 +45,7 @@ describe('usePersistExceptionList', () => {
usePersistExceptionList({ http: mockKibanaHttpService, onError })
);
await waitForNextUpdate();
result.current[1](mockExceptionList);
result.current[1](getExceptionListSchemaMock());
await waitForNextUpdate();
expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]);
});

View file

@ -6,27 +6,8 @@
import {
CreateExceptionListItemSchemaPartial,
CreateExceptionListSchemaPartial,
ExceptionListItemSchema,
ExceptionListSchema,
} from '../../common/schemas';
export const mockExceptionList: ExceptionListSchema = {
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',
id: '1',
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'endpoint',
updated_at: '2020-04-23T00:19:13.289Z',
updated_by: 'user_name',
};
export const mockNewExceptionList: CreateExceptionListSchemaPartial = {
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
description: 'This is a sample endpoint type exception',
@ -59,36 +40,3 @@ export const mockNewExceptionItem: CreateExceptionListItemSchemaPartial = {
tags: ['user added string for a tag', 'malware'],
type: 'simple',
};
export const mockExceptionItem: ExceptionListItemSchema = {
_tags: ['endpoint', 'process', 'malware', 'os:linux'],
comment: [],
created_at: '2020-04-23T00:19:13.289Z',
created_by: 'user_name',
description: 'This is a sample endpoint type exception',
entries: [
{
field: 'actingProcess.file.signer',
match: 'Elastic, N.V.',
match_any: undefined,
operator: 'included',
},
{
field: 'event.category',
match: undefined,
match_any: ['process', 'malware'],
operator: 'included',
},
],
id: '1',
item_id: 'endpoint_list_item',
list_id: 'endpoint_list',
meta: {},
name: 'Sample Endpoint Exception List',
namespace_type: 'single',
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: '77fd1909-6786-428a-a671-30229a719c1f',
type: 'simple',
updated_at: '2020-04-23T00:19:13.289Z',
updated_by: 'user_name',
};

View file

@ -7,9 +7,4 @@
export { usePersistExceptionItem } from './exceptions/hooks/persist_exception_item';
export { usePersistExceptionList } from './exceptions/hooks/persist_exception_list';
export { useExceptionList } from './exceptions/hooks/use_exception_list';
export {
mockExceptionItem,
mockExceptionList,
mockNewExceptionItem,
mockNewExceptionList,
} from './exceptions/mock';
export { mockNewExceptionItem, mockNewExceptionList } from './exceptions/mock';

View file

@ -8,7 +8,6 @@ import { httpServerMock } from 'src/core/server/mocks';
import { KibanaRequest } from 'src/core/server';
import { securityMock } from '../../security/server/mocks';
import { SecurityPluginSetup } from '../../security/server';
import { getUser } from './get_user';
@ -24,42 +23,42 @@ describe('get_user', () => {
});
test('it returns "bob" as the user given a security request with "bob"', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'bob' });
const user = getUser({ request, security });
expect(user).toEqual('bob');
});
test('it returns "alice" as the user given a security request with "alice"', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue({ username: 'alice' });
const user = getUser({ request, security });
expect(user).toEqual('alice');
});
test('it returns "elastic" as the user given null as the current user', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(null);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the current user', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given undefined as the plugin', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: undefined });
expect(user).toEqual('elastic');
});
test('it returns "elastic" as the user given null as the plugin', () => {
const security: SecurityPluginSetup = securityMock.createSetup();
const security = securityMock.createSetup();
security.authc.getCurrentUser = jest.fn().mockReturnValue(undefined);
const user = getUser({ request, security: null });
expect(user).toEqual('elastic');

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
import { ListPluginSetup } from './types';
import { getListClientMock } from './services/lists/list_client.mock';
import { getExceptionListClientMock } from './services/exception_lists/exception_list_client.mock';
const createSetupMock = (): jest.Mocked<ListPluginSetup> => {
const mock: jest.Mocked<ListPluginSetup> = {
getExceptionListClient: jest.fn().mockReturnValue(getExceptionListClientMock()),
getListClient: jest.fn().mockReturnValue(getListClientMock()),
};
return mock;
};
export const listMock = {
createSetup: createSetupMock,
getExceptionList: getExceptionListClientMock,
getListClient: getListClientMock,
};

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { getFoundExceptionListSchemaMock } from '../../../common/schemas/response/found_exception_list_schema.mock';
import { getFoundExceptionListItemSchemaMock } from '../../../common/schemas/response/found_exception_list_item_schema.mock';
import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
import { ExceptionListClient } from './exception_list_client';
export class ExceptionListClientMock extends ExceptionListClient {
public getExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
public getExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
public createExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
public updateExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
public deleteExceptionList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
public createExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
public updateExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
public deleteExceptionListItem = jest.fn().mockResolvedValue(getExceptionListItemSchemaMock());
public findExceptionListItem = jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock());
public findExceptionList = jest.fn().mockResolvedValue(getFoundExceptionListSchemaMock());
}
export const getExceptionListClientMock = (): ExceptionListClient => {
const mock = new ExceptionListClientMock({
savedObjectsClient: savedObjectsClientMock.create(),
user: 'elastic',
});
return mock;
};

View file

@ -0,0 +1,34 @@
/*
* 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.
*/
import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock';
import { getExceptionListSchemaMock } from '../../../common/schemas/response/exception_list_schema.mock';
import { getExceptionListClientMock } from './exception_list_client.mock';
describe('exception_list_client', () => {
describe('Mock client sanity checks', () => {
test('it returns the exception list as expected', async () => {
const mock = getExceptionListClientMock();
const list = await mock.getExceptionList({
id: '123',
listId: '123',
namespaceType: 'single',
});
expect(list).toEqual(getExceptionListSchemaMock());
});
test('it returns the the exception list item as expected', async () => {
const mock = getExceptionListClientMock();
const listItem = await mock.getExceptionListItem({
id: '123',
itemId: '123',
namespaceType: 'single',
});
expect(listItem).toEqual(getExceptionListItemSchemaMock());
});
});
});

View file

@ -0,0 +1,69 @@
/*
* 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.
*/
import { getFoundListItemSchemaMock } from '../../../common/schemas/response/found_list_item_schema.mock';
import { getFoundListSchemaMock } from '../../../common/schemas/response/found_list_schema.mock';
import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock';
import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock';
import { getCallClusterMock } from '../../../common/get_call_cluster.mock';
import { LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock';
import { ListClient } from './list_client';
export class ListClientMock extends ListClient {
public getListIndex = jest.fn().mockReturnValue(LIST_INDEX);
public getListItemIndex = jest.fn().mockReturnValue(LIST_ITEM_INDEX);
public getList = jest.fn().mockResolvedValue(getListResponseMock());
public createList = jest.fn().mockResolvedValue(getListResponseMock());
public createListIfItDoesNotExist = jest.fn().mockResolvedValue(getListResponseMock());
public getListIndexExists = jest.fn().mockResolvedValue(true);
public getListItemIndexExists = jest.fn().mockResolvedValue(true);
public createListBootStrapIndex = jest.fn().mockResolvedValue({});
public createListItemBootStrapIndex = jest.fn().mockResolvedValue({});
public getListPolicyExists = jest.fn().mockResolvedValue(true);
public getListItemPolicyExists = jest.fn().mockResolvedValue(true);
public getListTemplateExists = jest.fn().mockResolvedValue(true);
public getListItemTemplateExists = jest.fn().mockResolvedValue(true);
public getListTemplate = jest.fn().mockResolvedValue({});
public getListItemTemplate = jest.fn().mockResolvedValue({});
public setListTemplate = jest.fn().mockResolvedValue({});
public setListItemTemplate = jest.fn().mockResolvedValue({});
public setListPolicy = jest.fn().mockResolvedValue({});
public setListItemPolicy = jest.fn().mockResolvedValue({});
public deleteListIndex = jest.fn().mockResolvedValue(true);
public deleteListItemIndex = jest.fn().mockResolvedValue(true);
public deleteListPolicy = jest.fn().mockResolvedValue({});
public deleteListItemPolicy = jest.fn().mockResolvedValue({});
public deleteListTemplate = jest.fn().mockResolvedValue({});
public deleteListItemTemplate = jest.fn().mockResolvedValue({});
public deleteListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
public deleteListItemByValue = jest.fn().mockResolvedValue(getListItemResponseMock());
public deleteList = jest.fn().mockResolvedValue(getListResponseMock());
public exportListItemsToStream = jest.fn().mockResolvedValue(undefined);
public importListItemsToStream = jest.fn().mockResolvedValue(undefined);
public getListItemByValue = jest.fn().mockResolvedValue([getListItemResponseMock()]);
public createListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
public updateListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
public updateList = jest.fn().mockResolvedValue(getListResponseMock());
public getListItem = jest.fn().mockResolvedValue(getListItemResponseMock());
public getListItemByValues = jest.fn().mockResolvedValue([getListItemResponseMock()]);
public findList = jest.fn().mockResolvedValue(getFoundListSchemaMock());
public findListItem = jest.fn().mockResolvedValue(getFoundListItemSchemaMock());
}
export const getListClientMock = (): ListClient => {
const mock = new ListClientMock({
callCluster: getCallClusterMock(),
config: {
enabled: true,
listIndex: LIST_INDEX,
listItemIndex: LIST_ITEM_INDEX,
},
spaceId: 'default',
user: 'elastic',
});
return mock;
};

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock';
import { LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock';
import { getListClientMock } from './list_client.mock';
describe('list_client', () => {
describe('Mock client sanity checks', () => {
test('it returns the get list index as expected', () => {
const mock = getListClientMock();
expect(mock.getListIndex()).toEqual(LIST_INDEX);
});
test('it returns the get list item index as expected', () => {
const mock = getListClientMock();
expect(mock.getListItemIndex()).toEqual(LIST_ITEM_INDEX);
});
test('it returns a mock list item', async () => {
const mock = getListClientMock();
const listItem = await mock.getListItem({ id: '123' });
expect(listItem).toEqual(getListItemResponseMock());
});
});
});

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
interface Scroll {
export interface Scroll {
searchAfter: string[] | undefined;
validSearchAfterFound: boolean;
}

View file

@ -7,6 +7,7 @@
import { APICaller } from 'kibana/server';
import { Filter, SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas';
import { Scroll } from '../lists/types';
import { getQueryFilter } from './get_query_filter';
import { getSortWithTieBreaker } from './get_sort_with_tie_breaker';

View file

@ -7,6 +7,7 @@
import { APICaller } from 'kibana/server';
import { Filter, SortFieldOrUndefined, SortOrderOrUndefined } from '../../../common/schemas';
import { Scroll } from '../lists/types';
import { calculateScrollMath } from './calculate_scroll_math';
import { getSearchAfterScroll } from './get_search_after_scroll';

View file

@ -8,8 +8,6 @@ export {
useExceptionList,
usePersistExceptionItem,
usePersistExceptionList,
mockExceptionItem,
mockExceptionList,
mockNewExceptionItem,
mockNewExceptionList,
} from '../../lists/public';

View file

@ -8,17 +8,23 @@ import uuid from 'uuid';
import { filterEventsAgainstList } from './filter_events_with_list';
import { mockLogger, repeatedSearchResultsWithSortId } from './__mocks__/es_results';
import { ListClient } from '../../../../../lists/server';
import { getListItemResponseMock } from '../../../../../lists/common/schemas/response/list_item_schema.mock';
import { listMock } from '../../../../../lists/server/mocks';
const someGuids = Array.from({ length: 13 }).map((x) => uuid.v4());
describe('filterEventsAgainstList', () => {
let listClient = listMock.getListClient();
beforeEach(() => {
jest.clearAllMocks();
listClient = listMock.getListClient();
listClient.getListItemByValues = jest.fn().mockResolvedValue([]);
});
it('should respond with eventSearchResult if exceptionList is empty', async () => {
const res = await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: undefined,
eventSearchResult: repeatedSearchResultsWithSortId(4, 4, someGuids.slice(0, 3), [
'1.1.1.1',
@ -35,9 +41,7 @@ describe('filterEventsAgainstList', () => {
try {
await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -66,9 +70,7 @@ describe('filterEventsAgainstList', () => {
try {
await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -101,9 +103,7 @@ describe('filterEventsAgainstList', () => {
it('should respond with same list if no items match value list', async () => {
const res = await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -122,27 +122,17 @@ describe('filterEventsAgainstList', () => {
expect(res.hits.hits.length).toEqual(4);
});
it('should respond with less items in the list if some values match', async () => {
let outerType = '';
let outerListId = '';
listClient.getListItemByValues = jest.fn(({ value }) =>
Promise.resolve(
value.slice(0, 2).map((item) => ({
...getListItemResponseMock(),
value: item,
}))
)
);
const res = await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async ({
value,
type,
listId,
}: {
type: string;
listId: string;
value: string[];
}) => {
outerType = type;
outerListId = listId;
return value.slice(0, 2).map((item) => ({
value: item,
}));
},
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -163,8 +153,10 @@ describe('filterEventsAgainstList', () => {
'7.7.7.7',
]),
});
expect(outerType).toEqual('ip');
expect(outerListId).toEqual('ci-badguys.txt');
expect((listClient.getListItemByValues as jest.Mock).mock.calls[0][0].type).toEqual('ip');
expect((listClient.getListItemByValues as jest.Mock).mock.calls[0][0].listId).toEqual(
'ci-badguys.txt'
);
expect(res.hits.hits.length).toEqual(2);
});
});
@ -172,9 +164,7 @@ describe('filterEventsAgainstList', () => {
it('should respond with empty list if no items match value list', async () => {
const res = await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -193,27 +183,17 @@ describe('filterEventsAgainstList', () => {
expect(res.hits.hits.length).toEqual(0);
});
it('should respond with less items in the list if some values match', async () => {
let outerType = '';
let outerListId = '';
listClient.getListItemByValues = jest.fn(({ value }) =>
Promise.resolve(
value.slice(0, 2).map((item) => ({
...getListItemResponseMock(),
value: item,
}))
)
);
const res = await filterEventsAgainstList({
logger: mockLogger,
listClient: ({
getListItemByValues: async ({
value,
type,
listId,
}: {
type: string;
listId: string;
value: string[];
}) => {
outerType = type;
outerListId = listId;
return value.slice(0, 2).map((item) => ({
value: item,
}));
},
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -234,8 +214,10 @@ describe('filterEventsAgainstList', () => {
'7.7.7.7',
]),
});
expect(outerType).toEqual('ip');
expect(outerListId).toEqual('ci-badguys.txt');
expect((listClient.getListItemByValues as jest.Mock).mock.calls[0][0].type).toEqual('ip');
expect((listClient.getListItemByValues as jest.Mock).mock.calls[0][0].listId).toEqual(
'ci-badguys.txt'
);
expect(res.hits.hits.length).toEqual(2);
});
});

View file

@ -15,15 +15,18 @@ import { searchAfterAndBulkCreate } from './search_after_bulk_create';
import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants';
import { alertsMock, AlertServicesMock } from '../../../../../alerting/server/mocks';
import uuid from 'uuid';
import { ListClient } from '../../../../../lists/server';
import { ListItemArraySchema } from '../../../../../lists/common/schemas';
import { getListItemResponseMock } from '../../../../../lists/common/schemas/response/list_item_schema.mock';
import { listMock } from '../../../../../lists/server/mocks';
describe('searchAfterAndBulkCreate', () => {
let mockService: AlertServicesMock;
let inputIndexPattern: string[] = [];
let listClient = listMock.getListClient();
const someGuids = Array.from({ length: 13 }).map(() => uuid.v4());
beforeEach(() => {
jest.clearAllMocks();
listClient = listMock.getListClient();
listClient.getListItemByValues = jest.fn().mockResolvedValue([]);
inputIndexPattern = ['auditbeat-*'];
mockService = alertsMock.createAlertServices();
});
@ -93,9 +96,7 @@ describe('searchAfterAndBulkCreate', () => {
});
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleParams: sampleParams,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -169,9 +170,7 @@ describe('searchAfterAndBulkCreate', () => {
});
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleParams: sampleParams,
listClient: ({
getListItemByValues: async () => [],
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -243,19 +242,18 @@ describe('searchAfterAndBulkCreate', () => {
},
],
});
listClient.getListItemByValues = jest.fn(({ value }) =>
Promise.resolve(
value.slice(0, 2).map((item) => ({
...getListItemResponseMock(),
value: item,
}))
)
);
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
ruleParams: sampleParams,
listClient: ({
getListItemByValues: async ({
value,
}: {
type: string;
listId: string;
value: string[];
}) => {
return value.map((item) => ({ value: item }));
},
} as unknown) as ListClient,
listClient,
exceptionsList: undefined,
services: mockService,
logger: mockLogger,
@ -288,11 +286,7 @@ describe('searchAfterAndBulkCreate', () => {
.mockResolvedValueOnce(repeatedSearchResultsWithSortId(4, 1, someGuids.slice(0, 3)))
.mockRejectedValue(new Error('bulk failed')); // Added this recently
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient: ({
getListItemByValues: async () => {
return ([] as unknown) as ListItemArraySchema;
},
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -335,18 +329,16 @@ describe('searchAfterAndBulkCreate', () => {
test('should return success with 0 total hits', async () => {
const sampleParams = sampleRuleAlertParams();
mockService.callCluster.mockResolvedValueOnce(sampleEmptyDocSearchResults());
listClient.getListItemByValues = jest.fn(({ value }) =>
Promise.resolve(
value.slice(0, 2).map((item) => ({
...getListItemResponseMock(),
value: item,
}))
)
);
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient: ({
getListItemByValues: async ({
value,
}: {
type: string;
listId: string;
value: string[];
}) => {
return value.map((item) => ({ value: item }));
},
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',
@ -405,18 +397,16 @@ describe('searchAfterAndBulkCreate', () => {
.mockImplementation(() => {
throw Error('Fake Error');
});
listClient.getListItemByValues = jest.fn(({ value }) =>
Promise.resolve(
value.slice(0, 2).map((item) => ({
...getListItemResponseMock(),
value: item,
}))
)
);
const { success, createdSignalsCount, lastLookBackDate } = await searchAfterAndBulkCreate({
listClient: ({
getListItemByValues: async ({
value,
}: {
type: string;
listId: string;
value: string[];
}) => {
return value.map((item) => ({ value: item }));
},
} as unknown) as ListClient,
listClient,
exceptionsList: [
{
field: 'source.ip',

View file

@ -81,7 +81,7 @@ export const searchAfterAndBulkCreate = async ({
let sortId; // tells us where to start our next search_after query
let searchResultSize = 0;
/*
/*
The purpose of `maxResults` is to ensure we do not perform
extra search_after's. This will be reset on each
iteration, although it really only matters for the first

View file

@ -17,7 +17,7 @@ import { scheduleNotificationActions } from '../notifications/schedule_notificat
import { RuleAlertType } from '../rules/types';
import { findMlSignals } from './find_ml_signals';
import { bulkCreateMlSignals } from './bulk_create_ml_signals';
import { ListPluginSetup } from '../../../../../lists/server/types';
import { listMock } from '../../../../../lists/server/mocks';
jest.mock('./rule_status_saved_objects_client');
jest.mock('./rule_status_service');
@ -69,11 +69,6 @@ describe('rules_notification_alert_type', () => {
modulesProvider: jest.fn(),
resultsServiceProvider: jest.fn(),
};
const listMock = {
getListClient: () => ({
getListItemByValues: () => [],
}),
};
let payload: jest.Mocked<RuleExecutorOptions>;
let alert: ReturnType<typeof signalRulesAlertType>;
let logger: ReturnType<typeof loggingServiceMock.createLogger>;
@ -116,7 +111,7 @@ describe('rules_notification_alert_type', () => {
logger,
version,
ml: mlMock,
lists: (listMock as unknown) as ListPluginSetup,
lists: listMock.createSetup(),
});
});