Skip to content

Commit 303e484

Browse files
authored
[SIEM] [Case] Case workflow api schema (#51535)
1 parent 26ce610 commit 303e484

38 files changed

+1909
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
/* eslint-disable @typescript-eslint/no-empty-interface */
7+
/* eslint-disable @typescript-eslint/camelcase */
8+
import {
9+
NewCaseFormatted,
10+
NewCommentFormatted,
11+
} from '../../../../../../../x-pack/plugins/case/server';
12+
import { ElasticsearchMappingOf } from '../../utils/typed_elasticsearch_mappings';
13+
14+
// Temporary file to write mappings for case
15+
// while Saved Object Mappings API is programmed for the NP
16+
// See: https://github.com/elastic/kibana/issues/50309
17+
18+
export const caseSavedObjectType = 'case-workflow';
19+
export const caseCommentSavedObjectType = 'case-workflow-comment';
20+
21+
export const caseSavedObjectMappings: {
22+
[caseSavedObjectType]: ElasticsearchMappingOf<NewCaseFormatted>;
23+
} = {
24+
[caseSavedObjectType]: {
25+
properties: {
26+
assignees: {
27+
properties: {
28+
username: {
29+
type: 'keyword',
30+
},
31+
full_name: {
32+
type: 'keyword',
33+
},
34+
},
35+
},
36+
created_at: {
37+
type: 'date',
38+
},
39+
description: {
40+
type: 'text',
41+
},
42+
title: {
43+
type: 'keyword',
44+
},
45+
created_by: {
46+
properties: {
47+
username: {
48+
type: 'keyword',
49+
},
50+
full_name: {
51+
type: 'keyword',
52+
},
53+
},
54+
},
55+
state: {
56+
type: 'keyword',
57+
},
58+
tags: {
59+
type: 'keyword',
60+
},
61+
case_type: {
62+
type: 'keyword',
63+
},
64+
},
65+
},
66+
};
67+
68+
export const caseCommentSavedObjectMappings: {
69+
[caseCommentSavedObjectType]: ElasticsearchMappingOf<NewCommentFormatted>;
70+
} = {
71+
[caseCommentSavedObjectType]: {
72+
properties: {
73+
comment: {
74+
type: 'text',
75+
},
76+
created_at: {
77+
type: 'date',
78+
},
79+
created_by: {
80+
properties: {
81+
full_name: {
82+
type: 'keyword',
83+
},
84+
username: {
85+
type: 'keyword',
86+
},
87+
},
88+
},
89+
},
90+
},
91+
};

x-pack/plugins/case/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Case Workflow
2+
3+
*Experimental Feature*
4+
5+
Elastic is developing a Case Management Workflow. Follow our progress:
6+
7+
- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest)
8+
- [Github Meta](https://github.com/elastic/kibana/issues/50103)
9+

x-pack/plugins/case/kibana.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"configPath": ["xpack", "case"],
3+
"id": "case",
4+
"kibanaVersion": "kibana",
5+
"requiredPlugins": ["security"],
6+
"server": true,
7+
"ui": false,
8+
"version": "8.0.0"
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { schema, TypeOf } from '@kbn/config-schema';
8+
9+
export const ConfigSchema = schema.object({
10+
enabled: schema.boolean({ defaultValue: false }),
11+
indexPattern: schema.string({ defaultValue: '.case-test-2' }),
12+
secret: schema.string({ defaultValue: 'Cool secret huh?' }),
13+
});
14+
15+
export type ConfigType = TypeOf<typeof ConfigSchema>;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export const CASE_SAVED_OBJECT = 'case-workflow';
8+
export const CASE_COMMENT_SAVED_OBJECT = 'case-workflow-comment';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { PluginInitializerContext } from '../../../../src/core/server';
8+
import { ConfigSchema } from './config';
9+
import { CasePlugin } from './plugin';
10+
export { NewCaseFormatted, NewCommentFormatted } from './routes/api/types';
11+
12+
export const config = { schema: ConfigSchema };
13+
export const plugin = (initializerContext: PluginInitializerContext) =>
14+
new CasePlugin(initializerContext);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { first, map } from 'rxjs/operators';
8+
import { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server';
9+
import { ConfigType } from './config';
10+
import { initCaseApi } from './routes/api';
11+
import { CaseService } from './services';
12+
import { PluginSetupContract as SecurityPluginSetup } from '../../security/server';
13+
14+
function createConfig$(context: PluginInitializerContext) {
15+
return context.config.create<ConfigType>().pipe(map(config => config));
16+
}
17+
18+
export interface PluginsSetup {
19+
security: SecurityPluginSetup;
20+
}
21+
22+
export class CasePlugin {
23+
private readonly log: Logger;
24+
25+
constructor(private readonly initializerContext: PluginInitializerContext) {
26+
this.log = this.initializerContext.logger.get();
27+
}
28+
29+
public async setup(core: CoreSetup, plugins: PluginsSetup) {
30+
const config = await createConfig$(this.initializerContext)
31+
.pipe(first())
32+
.toPromise();
33+
34+
if (!config.enabled) {
35+
return;
36+
}
37+
const service = new CaseService(this.log);
38+
39+
this.log.debug(
40+
`Setting up Case Workflow with core contract [${Object.keys(
41+
core
42+
)}] and plugins [${Object.keys(plugins)}]`
43+
);
44+
45+
const caseService = await service.setup({
46+
authentication: plugins.security.authc,
47+
});
48+
49+
const router = core.http.createRouter();
50+
initCaseApi({
51+
caseService,
52+
router,
53+
});
54+
}
55+
56+
public start() {
57+
this.log.debug(`Starting Case Workflow`);
58+
}
59+
60+
public stop() {
61+
this.log.debug(`Stopping Case Workflow`);
62+
}
63+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { Authentication } from '../../../../../security/server';
7+
8+
const getCurrentUser = jest.fn().mockReturnValue({
9+
username: 'awesome',
10+
full_name: 'Awesome D00d',
11+
});
12+
const getCurrentUserThrow = jest.fn().mockImplementation(() => {
13+
throw new Error('Bad User - the user is not authenticated');
14+
});
15+
16+
export const authenticationMock = {
17+
create: (): jest.Mocked<Authentication> => ({
18+
login: jest.fn(),
19+
createAPIKey: jest.fn(),
20+
getCurrentUser,
21+
invalidateAPIKey: jest.fn(),
22+
isAuthenticated: jest.fn(),
23+
logout: jest.fn(),
24+
getSessionInfo: jest.fn(),
25+
}),
26+
createInvalid: (): jest.Mocked<Authentication> => ({
27+
login: jest.fn(),
28+
createAPIKey: jest.fn(),
29+
getCurrentUser: getCurrentUserThrow,
30+
invalidateAPIKey: jest.fn(),
31+
isAuthenticated: jest.fn(),
32+
logout: jest.fn(),
33+
getSessionInfo: jest.fn(),
34+
}),
35+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from 'src/core/server';
8+
import { CASE_COMMENT_SAVED_OBJECT } from '../../../constants';
9+
10+
export const createMockSavedObjectsRepository = (savedObject: any[] = []) => {
11+
const mockSavedObjectsClientContract = ({
12+
get: jest.fn((type, id) => {
13+
const result = savedObject.filter(s => s.id === id);
14+
if (!result.length) {
15+
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
16+
}
17+
return result[0];
18+
}),
19+
find: jest.fn(findArgs => {
20+
if (findArgs.hasReference && findArgs.hasReference.id === 'bad-guy') {
21+
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
22+
}
23+
return {
24+
total: savedObject.length,
25+
saved_objects: savedObject,
26+
};
27+
}),
28+
create: jest.fn((type, attributes, references) => {
29+
if (attributes.description === 'Throw an error' || attributes.comment === 'Throw an error') {
30+
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
31+
}
32+
if (type === CASE_COMMENT_SAVED_OBJECT) {
33+
return {
34+
type,
35+
id: 'mock-comment',
36+
attributes,
37+
...references,
38+
updated_at: '2019-12-02T22:48:08.327Z',
39+
version: 'WzksMV0=',
40+
};
41+
}
42+
return {
43+
type,
44+
id: 'mock-it',
45+
attributes,
46+
references: [],
47+
updated_at: '2019-12-02T22:48:08.327Z',
48+
version: 'WzksMV0=',
49+
};
50+
}),
51+
update: jest.fn((type, id, attributes) => {
52+
if (!savedObject.find(s => s.id === id)) {
53+
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
54+
}
55+
return {
56+
id,
57+
type,
58+
updated_at: '2019-11-22T22:50:55.191Z',
59+
version: 'WzE3LDFd',
60+
attributes,
61+
};
62+
}),
63+
delete: jest.fn((type: string, id: string) => {
64+
const result = savedObject.filter(s => s.id === id);
65+
if (!result.length) {
66+
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
67+
}
68+
if (type === 'case-workflow-comment' && id === 'bad-guy') {
69+
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
70+
}
71+
return {};
72+
}),
73+
deleteByNamespace: jest.fn(),
74+
} as unknown) as jest.Mocked<SavedObjectsClientContract>;
75+
76+
return mockSavedObjectsClientContract;
77+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { mockCases, mockCasesErrorTriggerData, mockCaseComments } from './mock_saved_objects';
8+
export { createMockSavedObjectsRepository } from './create_mock_so_repository';
9+
export { createRouteContext } from './route_contexts';
10+
export { authenticationMock } from './authc_mock';
11+
export { createRoute } from './mock_router';

0 commit comments

Comments
 (0)