Skip to content

Commit

Permalink
[Security Solution][Case] Fix comment content when pushing alerts to …
Browse files Browse the repository at this point in the history
…external services (#86812)

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
cnasikas and kibanamachine authored Jan 6, 2021
1 parent e63e3d8 commit 34a3982
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,24 @@ interface Signal {
rule: {
id: string;
name: string;
to: string;
from: string;
};
}

interface SignalHit {
_id: string;
_index: string;
_source: {
'@timestamp': string;
signal: Signal;
};
}

export type Alert = {
_id: string;
_index: string;
'@timestamp': string;
} & Signal;

export const CaseComponent = React.memo<CaseProps>(
Expand Down Expand Up @@ -153,6 +157,7 @@ export const CaseComponent = React.memo<CaseProps>(
[_id]: {
_id,
_index,
'@timestamp': _source['@timestamp'],
..._source.signal,
},
}),
Expand Down Expand Up @@ -291,6 +296,7 @@ export const CaseComponent = React.memo<CaseProps>(
updateCase: handleUpdateCase,
userCanCrud,
isValidConnector,
alerts,
});

const onSubmitConnector = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('usePushToService', () => {
isLoading: false,
postPushToService,
};

const mockConnector = connectorsMock[0];
const actionLicense = actionLicenses[0];
const caseServices = {
Expand All @@ -53,6 +54,7 @@ describe('usePushToService', () => {
hasDataToPush: true,
},
};

const defaultArgs = {
connector: {
id: mockConnector.id,
Expand All @@ -67,6 +69,19 @@ describe('usePushToService', () => {
updateCase,
userCanCrud: true,
isValidConnector: true,
alerts: {
'alert-id-1': {
_id: 'alert-id-1',
_index: 'alert-index-1',
'@timestamp': '2020-11-20T15:35:28.373Z',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
from: 'now-360s',
to: 'now',
},
},
},
};

beforeEach(() => {
Expand Down Expand Up @@ -98,6 +113,19 @@ describe('usePushToService', () => {
type: ConnectorTypes.servicenow,
},
updateCase,
alerts: {
'alert-id-1': {
_id: 'alert-id-1',
_index: 'alert-index-1',
'@timestamp': '2020-11-20T15:35:28.373Z',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
from: 'now-360s',
to: 'now',
},
},
},
});
expect(result.current.pushCallouts).toBeNull();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { CaseServices } from '../../containers/use_get_case_user_actions';
import { LinkAnchor } from '../../../common/components/links';
import { SecurityPageName } from '../../../app/types';
import { ErrorMessage } from '../callout/types';
import { Alert } from '../case_view';

export interface UsePushToService {
caseId: string;
Expand All @@ -31,6 +32,7 @@ export interface UsePushToService {
updateCase: (newCase: Case) => void;
userCanCrud: boolean;
isValidConnector: boolean;
alerts: Record<string, Alert>;
}

export interface ReturnUsePushToService {
Expand All @@ -47,6 +49,7 @@ export const usePushToService = ({
updateCase,
userCanCrud,
isValidConnector,
alerts,
}: UsePushToService): ReturnUsePushToService => {
const history = useHistory();
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
Expand All @@ -61,9 +64,10 @@ export const usePushToService = ({
caseServices,
connector,
updateCase,
alerts,
});
}
}, [caseId, caseServices, connector, postPushToService, updateCase]);
}, [alerts, caseId, caseServices, connector, postPushToService, updateCase]);

const goToConfigureCases = useCallback(
(ev) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import { i18n } from '@kbn/i18n';

export * from '../translations';

export const ERROR_TITLE = i18n.translate('xpack.securitySolution.containers.case.errorTitle', {
defaultMessage: 'Error fetching data',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@ import { CaseServices } from './use_get_case_user_actions';
import { CaseConnector, ConnectorTypes, CommentType } from '../../../../case/common/api';

jest.mock('./api');
jest.mock('../../common/components/link_to', () => {
const originalModule = jest.requireActual('../../common/components/link_to');
return {
...originalModule,
getTimelineTabsUrl: jest.fn(),
useFormatUrl: jest.fn().mockReturnValue({ formatUrl: jest.fn(), search: 'urlSearch' }),
};
});

describe('usePostPushToService', () => {
const abortCtrl = new AbortController();
const updateCase = jest.fn();
const formatUrl = jest.fn();

const samplePush = {
caseId: pushedCase.id,
caseServices: {
Expand All @@ -45,7 +55,21 @@ describe('usePostPushToService', () => {
fields: { issueType: 'Task', priority: 'Low', parent: null },
} as CaseConnector,
updateCase,
alerts: {
'alert-id-1': {
_id: 'alert-id-1',
_index: 'alert-index-1',
'@timestamp': '2020-11-20T15:35:28.373Z',
rule: {
id: 'rule-id-1',
name: 'Awesome rule',
from: 'now-360s',
to: 'now',
},
},
},
};

const sampleServiceRequestData = {
savedObjectId: pushedCase.id,
createdAt: pushedCase.createdAt,
Expand Down Expand Up @@ -142,11 +166,13 @@ describe('usePostPushToService', () => {
expect(spyOnPushToService).toBeCalledWith(
samplePush.connector.id,
samplePush.connector.type,
formatServiceRequestData(
basicCase,
samplePush.connector,
sampleCaseServices as CaseServices
),
formatServiceRequestData({
myCase: basicCase,
connector: samplePush.connector,
caseServices: sampleCaseServices as CaseServices,
alerts: samplePush.alerts,
formatUrl,
}),
abortCtrl.signal
);
});
Expand All @@ -162,6 +188,7 @@ describe('usePostPushToService', () => {
type: ConnectorTypes.none,
fields: null,
},
alerts: samplePush.alerts,
updateCase,
};
const spyOnPushToService = jest.spyOn(api, 'pushToService');
Expand All @@ -176,7 +203,13 @@ describe('usePostPushToService', () => {
expect(spyOnPushToService).toBeCalledWith(
samplePush2.connector.id,
samplePush2.connector.type,
formatServiceRequestData(basicCase, samplePush2.connector, {}),
formatServiceRequestData({
myCase: basicCase,
connector: samplePush2.connector,
caseServices: {},
alerts: samplePush.alerts,
formatUrl,
}),
abortCtrl.signal
);
});
Expand Down Expand Up @@ -213,7 +246,13 @@ describe('usePostPushToService', () => {

it('formatServiceRequestData - current connector', () => {
const caseServices = sampleCaseServices;
const result = formatServiceRequestData(pushedCase, samplePush.connector, caseServices);
const result = formatServiceRequestData({
myCase: pushedCase,
connector: samplePush.connector,
caseServices,
alerts: samplePush.alerts,
formatUrl,
});
expect(result).toEqual(sampleServiceRequestData);
});

Expand All @@ -225,7 +264,13 @@ describe('usePostPushToService', () => {
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: 'RJ-01' },
};
const result = formatServiceRequestData(pushedCase, connector as CaseConnector, caseServices);
const result = formatServiceRequestData({
myCase: pushedCase,
connector: connector as CaseConnector,
caseServices,
alerts: samplePush.alerts,
formatUrl,
});
expect(result).toEqual({
...sampleServiceRequestData,
...connector.fields,
Expand All @@ -237,20 +282,55 @@ describe('usePostPushToService', () => {
const caseServices = {
'123': sampleCaseServices['123'],
};

const connector = {
id: '456',
name: 'connector 2',
type: ConnectorTypes.jira,
fields: { issueType: 'Task', priority: 'High', parent: null },
};
const result = formatServiceRequestData(pushedCase, connector as CaseConnector, caseServices);

const result = formatServiceRequestData({
myCase: pushedCase,
connector: connector as CaseConnector,
caseServices,
alerts: samplePush.alerts,
formatUrl,
});

expect(result).toEqual({
...sampleServiceRequestData,
...connector.fields,
externalId: null,
});
});

it('formatServiceRequestData - Alert comment content', () => {
formatUrl.mockReturnValue('https://app.com/detections');
const caseServices = sampleCaseServices;
const result = formatServiceRequestData({
myCase: {
...pushedCase,
comments: [
{
...pushedCase.comments[0],
type: CommentType.alert,
alertId: 'alert-id-1',
index: 'alert-index-1',
},
],
},
connector: samplePush.connector,
caseServices,
alerts: samplePush.alerts,
formatUrl,
});

expect(result.comments![0].comment).toEqual(
'[Alert](https://app.com/detections?filters=!((%27$state%27:(store:appState),meta:(alias:!n,disabled:!f,key:_id,negate:!f,params:(query:alert-id-1),type:phrase),query:(match:(_id:(query:alert-id-1,type:phrase)))))&sourcerer=(default:!())&timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-11-20T15:29:28.373Z%27,kind:absolute,to:%272020-11-20T15:35:28.373Z%27)),timeline:(linkTo:!(global),timerange:(from:%272020-11-20T15:29:28.373Z%27,kind:absolute,to:%272020-11-20T15:35:28.373Z%27)))) added to case.'
);
});

it('unhappy path', async () => {
const spyOnPushToService = jest.spyOn(api, 'pushToService');
spyOnPushToService.mockImplementation(() => {
Expand Down
Loading

0 comments on commit 34a3982

Please sign in to comment.