Skip to content

Commit

Permalink
[SIEM] [Case] Case workflow api schema (#51535) (#54304)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephmilovic committed Jan 9, 2020
1 parent 0907022 commit 4fbf9c7
Show file tree
Hide file tree
Showing 38 changed files with 1,909 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-empty-interface */
/* eslint-disable @typescript-eslint/camelcase */
import {
NewCaseFormatted,
NewCommentFormatted,
} from '../../../../../../../x-pack/plugins/case/server';
import { ElasticsearchMappingOf } from '../../utils/typed_elasticsearch_mappings';

// Temporary file to write mappings for case
// while Saved Object Mappings API is programmed for the NP
// See: https://github.com/elastic/kibana/issues/50309

export const caseSavedObjectType = 'case-workflow';
export const caseCommentSavedObjectType = 'case-workflow-comment';

export const caseSavedObjectMappings: {
[caseSavedObjectType]: ElasticsearchMappingOf<NewCaseFormatted>;
} = {
[caseSavedObjectType]: {
properties: {
assignees: {
properties: {
username: {
type: 'keyword',
},
full_name: {
type: 'keyword',
},
},
},
created_at: {
type: 'date',
},
description: {
type: 'text',
},
title: {
type: 'keyword',
},
created_by: {
properties: {
username: {
type: 'keyword',
},
full_name: {
type: 'keyword',
},
},
},
state: {
type: 'keyword',
},
tags: {
type: 'keyword',
},
case_type: {
type: 'keyword',
},
},
},
};

export const caseCommentSavedObjectMappings: {
[caseCommentSavedObjectType]: ElasticsearchMappingOf<NewCommentFormatted>;
} = {
[caseCommentSavedObjectType]: {
properties: {
comment: {
type: 'text',
},
created_at: {
type: 'date',
},
created_by: {
properties: {
full_name: {
type: 'keyword',
},
username: {
type: 'keyword',
},
},
},
},
},
};
9 changes: 9 additions & 0 deletions x-pack/plugins/case/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Case Workflow

*Experimental Feature*

Elastic is developing a Case Management Workflow. Follow our progress:

- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest)
- [Github Meta](https://github.com/elastic/kibana/issues/50103)

9 changes: 9 additions & 0 deletions x-pack/plugins/case/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"configPath": ["xpack", "case"],
"id": "case",
"kibanaVersion": "kibana",
"requiredPlugins": ["security"],
"server": true,
"ui": false,
"version": "8.0.0"
}
15 changes: 15 additions & 0 deletions x-pack/plugins/case/server/config.ts
Original file line number Diff line number Diff line change
@@ -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 { schema, TypeOf } from '@kbn/config-schema';

export const ConfigSchema = schema.object({
enabled: schema.boolean({ defaultValue: false }),
indexPattern: schema.string({ defaultValue: '.case-test-2' }),
secret: schema.string({ defaultValue: 'Cool secret huh?' }),
});

export type ConfigType = TypeOf<typeof ConfigSchema>;
8 changes: 8 additions & 0 deletions x-pack/plugins/case/server/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 const CASE_SAVED_OBJECT = 'case-workflow';
export const CASE_COMMENT_SAVED_OBJECT = 'case-workflow-comment';
14 changes: 14 additions & 0 deletions x-pack/plugins/case/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { PluginInitializerContext } from '../../../../src/core/server';
import { ConfigSchema } from './config';
import { CasePlugin } from './plugin';
export { NewCaseFormatted, NewCommentFormatted } from './routes/api/types';

export const config = { schema: ConfigSchema };
export const plugin = (initializerContext: PluginInitializerContext) =>
new CasePlugin(initializerContext);
63 changes: 63 additions & 0 deletions x-pack/plugins/case/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { first, map } from 'rxjs/operators';
import { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server';
import { ConfigType } from './config';
import { initCaseApi } from './routes/api';
import { CaseService } from './services';
import { PluginSetupContract as SecurityPluginSetup } from '../../security/server';

function createConfig$(context: PluginInitializerContext) {
return context.config.create<ConfigType>().pipe(map(config => config));
}

export interface PluginsSetup {
security: SecurityPluginSetup;
}

export class CasePlugin {
private readonly log: Logger;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.log = this.initializerContext.logger.get();
}

public async setup(core: CoreSetup, plugins: PluginsSetup) {
const config = await createConfig$(this.initializerContext)
.pipe(first())
.toPromise();

if (!config.enabled) {
return;
}
const service = new CaseService(this.log);

this.log.debug(
`Setting up Case Workflow with core contract [${Object.keys(
core
)}] and plugins [${Object.keys(plugins)}]`
);

const caseService = await service.setup({
authentication: plugins.security.authc,
});

const router = core.http.createRouter();
initCaseApi({
caseService,
router,
});
}

public start() {
this.log.debug(`Starting Case Workflow`);
}

public stop() {
this.log.debug(`Stopping Case Workflow`);
}
}
35 changes: 35 additions & 0 deletions x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts
Original file line number Diff line number Diff line change
@@ -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 { Authentication } from '../../../../../security/server';

const getCurrentUser = jest.fn().mockReturnValue({
username: 'awesome',
full_name: 'Awesome D00d',
});
const getCurrentUserThrow = jest.fn().mockImplementation(() => {
throw new Error('Bad User - the user is not authenticated');
});

export const authenticationMock = {
create: (): jest.Mocked<Authentication> => ({
login: jest.fn(),
createAPIKey: jest.fn(),
getCurrentUser,
invalidateAPIKey: jest.fn(),
isAuthenticated: jest.fn(),
logout: jest.fn(),
getSessionInfo: jest.fn(),
}),
createInvalid: (): jest.Mocked<Authentication> => ({
login: jest.fn(),
createAPIKey: jest.fn(),
getCurrentUser: getCurrentUserThrow,
invalidateAPIKey: jest.fn(),
isAuthenticated: jest.fn(),
logout: jest.fn(),
getSessionInfo: jest.fn(),
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 { SavedObjectsClientContract, SavedObjectsErrorHelpers } from 'src/core/server';
import { CASE_COMMENT_SAVED_OBJECT } from '../../../constants';

export const createMockSavedObjectsRepository = (savedObject: any[] = []) => {
const mockSavedObjectsClientContract = ({
get: jest.fn((type, id) => {
const result = savedObject.filter(s => s.id === id);
if (!result.length) {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
return result[0];
}),
find: jest.fn(findArgs => {
if (findArgs.hasReference && findArgs.hasReference.id === 'bad-guy') {
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
}
return {
total: savedObject.length,
saved_objects: savedObject,
};
}),
create: jest.fn((type, attributes, references) => {
if (attributes.description === 'Throw an error' || attributes.comment === 'Throw an error') {
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
}
if (type === CASE_COMMENT_SAVED_OBJECT) {
return {
type,
id: 'mock-comment',
attributes,
...references,
updated_at: '2019-12-02T22:48:08.327Z',
version: 'WzksMV0=',
};
}
return {
type,
id: 'mock-it',
attributes,
references: [],
updated_at: '2019-12-02T22:48:08.327Z',
version: 'WzksMV0=',
};
}),
update: jest.fn((type, id, attributes) => {
if (!savedObject.find(s => s.id === id)) {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
return {
id,
type,
updated_at: '2019-11-22T22:50:55.191Z',
version: 'WzE3LDFd',
attributes,
};
}),
delete: jest.fn((type: string, id: string) => {
const result = savedObject.filter(s => s.id === id);
if (!result.length) {
throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id);
}
if (type === 'case-workflow-comment' && id === 'bad-guy') {
throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing');
}
return {};
}),
deleteByNamespace: jest.fn(),
} as unknown) as jest.Mocked<SavedObjectsClientContract>;

return mockSavedObjectsClientContract;
};
11 changes: 11 additions & 0 deletions x-pack/plugins/case/server/routes/api/__fixtures__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { mockCases, mockCasesErrorTriggerData, mockCaseComments } from './mock_saved_objects';
export { createMockSavedObjectsRepository } from './create_mock_so_repository';
export { createRouteContext } from './route_contexts';
export { authenticationMock } from './authc_mock';
export { createRoute } from './mock_router';
34 changes: 34 additions & 0 deletions x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts
Original file line number Diff line number Diff line change
@@ -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 { IRouter } from 'kibana/server';
import { loggingServiceMock, httpServiceMock } from '../../../../../../../src/core/server/mocks';
import { CaseService } from '../../../services';
import { authenticationMock } from '../__fixtures__';
import { RouteDeps } from '../index';

export const createRoute = async (
api: (deps: RouteDeps) => void,
method: 'get' | 'post' | 'delete',
badAuth = false
) => {
const httpService = httpServiceMock.createSetupContract();
const router = httpService.createRouter('') as jest.Mocked<IRouter>;

const log = loggingServiceMock.create().get('case');

const service = new CaseService(log);
const caseService = await service.setup({
authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(),
});

api({
router,
caseService,
});

return router[method].mock.calls[0][1];
};
Loading

0 comments on commit 4fbf9c7

Please sign in to comment.