Skip to content

Commit

Permalink
[Security Solution][Case] Fix subcases bugs on detections and case vi…
Browse files Browse the repository at this point in the history
…ew (#91836) (#92966)

Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>

Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>
  • Loading branch information
cnasikas and jonathan-buttner authored Feb 26, 2021
1 parent 12a223e commit 7baa691
Show file tree
Hide file tree
Showing 69 changed files with 683 additions and 366 deletions.
35 changes: 0 additions & 35 deletions x-pack/plugins/case/common/api/cases/commentable_case.ts

This file was deleted.

1 change: 0 additions & 1 deletion x-pack/plugins/case/common/api/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ export * from './comment';
export * from './status';
export * from './user_actions';
export * from './sub_case';
export * from './commentable_case';
8 changes: 4 additions & 4 deletions x-pack/plugins/case/common/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export const getSubCasesUrl = (caseID: string): string => {
return SUB_CASES_URL.replace('{case_id}', caseID);
};

export const getSubCaseDetailsUrl = (caseID: string, subCaseID: string): string => {
return SUB_CASE_DETAILS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseID);
export const getSubCaseDetailsUrl = (caseID: string, subCaseId: string): string => {
return SUB_CASE_DETAILS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseId);
};

export const getCaseCommentsUrl = (id: string): string => {
Expand All @@ -40,8 +40,8 @@ export const getCaseUserActionUrl = (id: string): string => {
return CASE_USER_ACTIONS_URL.replace('{case_id}', id);
};

export const getSubCaseUserActionUrl = (caseID: string, subCaseID: string): string => {
return SUB_CASE_USER_ACTIONS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseID);
export const getSubCaseUserActionUrl = (caseID: string, subCaseId: string): string => {
return SUB_CASE_USER_ACTIONS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseId);
};

export const getCasePushUrl = (caseId: string, connectorId: string): string => {
Expand Down
27 changes: 25 additions & 2 deletions x-pack/plugins/case/common/api/runtime_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,37 @@ import { either, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import * as rt from 'io-ts';
import { failure } from 'io-ts/lib/PathReporter';
import { isObject } from 'lodash/fp';

type ErrorFactory = (message: string) => Error;

export const formatErrors = (errors: rt.Errors): string[] => {
const err = errors.map((error) => {
if (error.message != null) {
return error.message;
} else {
const keyContext = error.context
.filter(
(entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== ''
)
.map((entry) => entry.key)
.join(',');

const nameContext = error.context.find((entry) => entry.type?.name?.length > 0);
const suppliedValue =
keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : '';
const value = isObject(error.value) ? JSON.stringify(error.value) : error.value;
return `Invalid value "${value}" supplied to "${suppliedValue}"`;
}
});

return [...new Set(err)];
};

export const createPlainError = (message: string) => new Error(message);

export const throwErrors = (createError: ErrorFactory) => (errors: rt.Errors) => {
throw createError(failure(errors).join('\n'));
throw createError(formatErrors(errors).join());
};

export const decodeOrThrow = <A, O, I>(
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/case/server/client/cases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface TransformFieldsArgs<P, S> {

export interface ExternalServiceComment {
comment: string;
commentId?: string;
commentId: string;
}

export interface MapIncident {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/case/server/client/cases/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,7 @@ describe('utils', () => {
},
{
comment: 'Elastic Security Alerts attached to the case: 3',
commentId: 'mock-id-1-total-alerts',
},
]);
});
Expand Down Expand Up @@ -569,6 +570,7 @@ describe('utils', () => {
},
{
comment: 'Elastic Security Alerts attached to the case: 4',
commentId: 'mock-id-1-total-alerts',
},
]);
});
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/case/server/client/cases/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export const createIncident = async ({
if (totalAlerts > 0) {
comments.push({
comment: `Elastic Security Alerts attached to the case: ${totalAlerts}`,
commentId: `${theCase.id}-total-alerts`,
});
}

Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/case/server/client/comments/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
CaseType,
SubCaseAttributes,
CommentRequest,
CollectionWithSubCaseResponse,
CaseResponse,
User,
CommentRequestAlertType,
AlertCommentRequestRt,
Expand Down Expand Up @@ -113,7 +113,7 @@ const addGeneratedAlerts = async ({
caseClient,
caseId,
comment,
}: AddCommentFromRuleArgs): Promise<CollectionWithSubCaseResponse> => {
}: AddCommentFromRuleArgs): Promise<CaseResponse> => {
const query = pipe(
AlertCommentRequestRt.decode(comment),
fold(throwErrors(Boom.badRequest), identity)
Expand Down Expand Up @@ -260,7 +260,7 @@ export const addComment = async ({
caseId,
comment,
user,
}: AddCommentArgs): Promise<CollectionWithSubCaseResponse> => {
}: AddCommentArgs): Promise<CaseResponse> => {
const query = pipe(
CommentRequestRt.decode(comment),
fold(throwErrors(Boom.badRequest), identity)
Expand Down
3 changes: 1 addition & 2 deletions x-pack/plugins/case/server/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
CasesPatchRequest,
CasesResponse,
CaseStatuses,
CollectionWithSubCaseResponse,
CommentRequest,
ConnectorMappingsAttributes,
GetFieldsResponse,
Expand Down Expand Up @@ -89,7 +88,7 @@ export interface ConfigureFields {
* This represents the interface that other plugins can access.
*/
export interface CaseClient {
addComment(args: CaseClientAddComment): Promise<CollectionWithSubCaseResponse>;
addComment(args: CaseClientAddComment): Promise<CaseResponse>;
create(theCase: CasePostRequest): Promise<CaseResponse>;
get(args: CaseClientGet): Promise<CaseResponse>;
getAlerts(args: CaseClientGetAlerts): Promise<CaseClientGetAlertsResponse>;
Expand Down
63 changes: 41 additions & 22 deletions x-pack/plugins/case/server/common/models/commentable_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
CaseSettings,
CaseStatuses,
CaseType,
CollectionWithSubCaseResponse,
CollectWithSubCaseResponseRt,
CaseResponse,
CaseResponseRt,
CommentAttributes,
CommentPatchRequest,
CommentRequest,
Expand Down Expand Up @@ -254,7 +254,7 @@ export class CommentableCase {
};
}

public async encode(): Promise<CollectionWithSubCaseResponse> {
public async encode(): Promise<CaseResponse> {
const collectionCommentStats = await this.service.getAllCaseComments({
client: this.soClient,
id: this.collection.id,
Expand All @@ -265,22 +265,6 @@ export class CommentableCase {
},
});

if (this.subCase) {
const subCaseComments = await this.service.getAllSubCaseComments({
client: this.soClient,
id: this.subCase.id,
});

return CollectWithSubCaseResponseRt.encode({
subCase: flattenSubCaseSavedObject({
savedObject: this.subCase,
comments: subCaseComments.saved_objects,
totalAlerts: countAlertsForID({ comments: subCaseComments, id: this.subCase.id }),
}),
...this.formatCollectionForEncoding(collectionCommentStats.total),
});
}

const collectionComments = await this.service.getAllCaseComments({
client: this.soClient,
id: this.collection.id,
Expand All @@ -291,10 +275,45 @@ export class CommentableCase {
},
});

return CollectWithSubCaseResponseRt.encode({
const collectionTotalAlerts =
countAlertsForID({ comments: collectionComments, id: this.collection.id }) ?? 0;

const caseResponse = {
comments: flattenCommentSavedObjects(collectionComments.saved_objects),
totalAlerts: countAlertsForID({ comments: collectionComments, id: this.collection.id }),
totalAlerts: collectionTotalAlerts,
...this.formatCollectionForEncoding(collectionCommentStats.total),
});
};

if (this.subCase) {
const subCaseComments = await this.service.getAllSubCaseComments({
client: this.soClient,
id: this.subCase.id,
});
const totalAlerts = countAlertsForID({ comments: subCaseComments, id: this.subCase.id }) ?? 0;

return CaseResponseRt.encode({
...caseResponse,
/**
* For now we need the sub case comments and totals to be exposed on the top level of the response so that the UI
* functionality can stay the same. Ideally in the future we can refactor this so that the UI will look for the
* comments either in the top level for a case or a collection or in the subCases field if it is a sub case.
*
* If we ever need to return both the collection's comments and the sub case comments we'll need to refactor it then
* as well.
*/
comments: flattenCommentSavedObjects(subCaseComments.saved_objects),
totalComment: subCaseComments.saved_objects.length,
totalAlerts,
subCases: [
flattenSubCaseSavedObject({
savedObject: this.subCase,
totalComment: subCaseComments.saved_objects.length,
totalAlerts,
}),
],
});
}

return CaseResponseRt.encode(caseResponse);
}
}
4 changes: 2 additions & 2 deletions x-pack/plugins/case/server/connectors/case/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
AssociationType,
CaseResponse,
CasesResponse,
CollectionWithSubCaseResponse,
} from '../../../common/api';
import {
connectorMappingsServiceMock,
Expand Down Expand Up @@ -1018,9 +1017,10 @@ describe('case connector', () => {

describe('addComment', () => {
it('executes correctly', async () => {
const commentReturn: CollectionWithSubCaseResponse = {
const commentReturn: CaseResponse = {
id: 'mock-it',
totalComment: 0,
totalAlerts: 0,
version: 'WzksMV0=',

closed_at: null,
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/case/server/connectors/case/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ConnectorSchema,
CommentSchema,
} from './schema';
import { CaseResponse, CasesResponse, CollectionWithSubCaseResponse } from '../../../common/api';
import { CaseResponse, CasesResponse } from '../../../common/api';

export type CaseConfiguration = TypeOf<typeof CaseConfigurationSchema>;
export type Connector = TypeOf<typeof ConnectorSchema>;
Expand All @@ -29,7 +29,7 @@ export type ExecutorSubActionAddCommentParams = TypeOf<
>;

export type CaseExecutorParams = TypeOf<typeof CaseExecutorParamsSchema>;
export type CaseExecutorResponse = CaseResponse | CasesResponse | CollectionWithSubCaseResponse;
export type CaseExecutorResponse = CaseResponse | CasesResponse;

export type CaseActionType = ActionType<
CaseConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
}),
query: schema.maybe(
schema.object({
subCaseID: schema.maybe(schema.string()),
subCaseId: schema.maybe(schema.string()),
})
),
},
Expand All @@ -35,11 +35,11 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
const { username, full_name, email } = await caseService.getUser({ request });
const deleteDate = new Date().toISOString();

const id = request.query?.subCaseID ?? request.params.case_id;
const id = request.query?.subCaseId ?? request.params.case_id;
const comments = await caseService.getCommentsByAssociation({
client,
id,
associationType: request.query?.subCaseID
associationType: request.query?.subCaseId
? AssociationType.subCase
: AssociationType.case,
});
Expand All @@ -61,7 +61,7 @@ export function initDeleteAllCommentsApi({ caseService, router, userActionServic
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: request.params.case_id,
subCaseId: request.query?.subCaseID,
subCaseId: request.query?.subCaseId,
commentId: comment.id,
fields: ['comment'],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
}),
query: schema.maybe(
schema.object({
subCaseID: schema.maybe(schema.string()),
subCaseId: schema.maybe(schema.string()),
})
),
},
Expand All @@ -46,8 +46,8 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
throw Boom.notFound(`This comment ${request.params.comment_id} does not exist anymore.`);
}

const type = request.query?.subCaseID ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
const id = request.query?.subCaseID ?? request.params.case_id;
const type = request.query?.subCaseId ? SUB_CASE_SAVED_OBJECT : CASE_SAVED_OBJECT;
const id = request.query?.subCaseId ?? request.params.case_id;

const caseRef = myComment.references.find((c) => c.type === type);
if (caseRef == null || (caseRef != null && caseRef.id !== id)) {
Expand All @@ -69,7 +69,7 @@ export function initDeleteCommentApi({ caseService, router, userActionService }:
actionAt: deleteDate,
actionBy: { username, full_name, email },
caseId: id,
subCaseId: request.query?.subCaseID,
subCaseId: request.query?.subCaseId,
commentId: request.params.comment_id,
fields: ['comment'],
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { defaultPage, defaultPerPage } from '../..';

const FindQueryParamsRt = rt.partial({
...SavedObjectFindOptionsRt.props,
subCaseID: rt.string,
subCaseId: rt.string,
});

export function initFindCaseCommentsApi({ caseService, router }: RouteDeps) {
Expand All @@ -49,8 +49,8 @@ export function initFindCaseCommentsApi({ caseService, router }: RouteDeps) {
fold(throwErrors(Boom.badRequest), identity)
);

const id = query.subCaseID ?? request.params.case_id;
const associationType = query.subCaseID ? AssociationType.subCase : AssociationType.case;
const id = query.subCaseId ?? request.params.case_id;
const associationType = query.subCaseId ? AssociationType.subCase : AssociationType.case;
const args = query
? {
caseService,
Expand Down
Loading

0 comments on commit 7baa691

Please sign in to comment.