Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cases] Route: Get all alerts attach to a case #101878

Merged
merged 12 commits into from
Jun 18, 2021
18 changes: 18 additions & 0 deletions x-pack/plugins/cases/common/api/cases/alerts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as rt from 'io-ts';

const AlertRt = rt.type({
id: rt.string,
index: rt.string,
attached_at: rt.string,
});

export const AlertResponseRt = rt.array(AlertRt);

export type AlertResponse = rt.TypeOf<typeof AlertResponseRt>;
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/api/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './status';
export * from './user_actions';
export * from './sub_case';
export * from './constants';
export * from './alerts';
1 change: 1 addition & 0 deletions x-pack/plugins/cases/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const CASE_TAGS_URL = `${CASES_URL}/tags`;
export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`;

export const CASE_ALERTS_URL = `${CASES_URL}/alerts/{alert_id}`;
export const CASE_DETAILS_ALERTS_URL = `${CASE_DETAILS_URL}/alerts`;

/**
* Action routes
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/cases/server/authorization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ export const Operations: Record<ReadOperations | WriteOperations, OperationDetai
docType: 'case',
savedObjectType: CASE_SAVED_OBJECT,
},
[ReadOperations.GetAlertsAttachedToCase]: {
ecsType: EVENT_TYPES.access,
name: ACCESS_CASE_OPERATION,
action: 'case_alerts_attach_to_case',
verbs: accessVerbs,
docType: 'cases',
savedObjectType: CASE_COMMENT_SAVED_OBJECT,
},
// comments operations
[WriteOperations.CreateComment]: {
ecsType: EVENT_TYPES.creation,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/server/authorization/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export enum ReadOperations {
GetReporters = 'getReporters',
FindConfigurations = 'findConfigurations',
GetUserActions = 'getUserActions',
GetAlertsAttachedToCase = 'getAlertsAttachedToCase',
}

/**
Expand Down
21 changes: 1 addition & 20 deletions x-pack/plugins/cases/server/client/alerts/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,11 @@
* 2.0.
*/

import { CaseStatuses } from '../../../common/api';
import { AlertInfo } from '../../common';
import { CasesClientGetAlertsResponse } from './types';
import { AlertGet, AlertUpdateStatus, CasesClientGetAlertsResponse } from './types';
import { get } from './get';
import { updateStatus } from './update_status';
import { CasesClientArgs } from '../types';

/**
* Defines the fields necessary to update an alert's status.
*/
export interface UpdateAlertRequest {
id: string;
index: string;
status: CaseStatuses;
}

export interface AlertUpdateStatus {
alerts: UpdateAlertRequest[];
}

export interface AlertGet {
alertsInfo: AlertInfo[];
}

export interface AlertSubClient {
get(args: AlertGet): Promise<CasesClientGetAlertsResponse>;
updateStatus(args: AlertUpdateStatus): Promise<void>;
Expand Down
9 changes: 2 additions & 7 deletions x-pack/plugins/cases/server/client/alerts/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@
* 2.0.
*/

import { AlertInfo } from '../../common';
import { CasesClientGetAlertsResponse } from './types';
import { CasesClientGetAlertsResponse, AlertGet } from './types';
import { CasesClientArgs } from '..';

interface GetParams {
alertsInfo: AlertInfo[];
}

export const get = async (
{ alertsInfo }: GetParams,
{ alertsInfo }: AlertGet,
clientArgs: CasesClientArgs
): Promise<CasesClientGetAlertsResponse> => {
const { alertsService, scopedClusterClient, logger } = clientArgs;
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/cases/server/client/alerts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* 2.0.
*/

import { CaseStatuses } from '../../../common/api';
import { AlertInfo } from '../../common';

interface Alert {
id: string;
index: string;
Expand All @@ -17,3 +20,20 @@ interface Alert {
}

export type CasesClientGetAlertsResponse = Alert[];

/**
* Defines the fields necessary to update an alert's status.
*/
export interface UpdateAlertRequest {
id: string;
index: string;
status: CaseStatuses;
}

export interface AlertUpdateStatus {
alerts: UpdateAlertRequest[];
}

export interface AlertGet {
alertsInfo: AlertInfo[];
}
9 changes: 9 additions & 0 deletions x-pack/plugins/cases/server/client/cases/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { CasesClient } from '../client';
import { CasesClientInternal } from '../client_internal';
import {
IAlertResponse,
ICasePostRequest,
ICaseResponse,
ICasesFindRequest,
Expand All @@ -30,6 +31,8 @@ import { find } from './find';
import {
CaseIDsByAlertIDParams,
get,
getAllAlertsAttachToCase,
GetAllAlertsAttachToCase,
getCaseIDsByAlertID,
GetParams,
getReporters,
Expand Down Expand Up @@ -82,6 +85,10 @@ export interface CasesSubClient {
* Retrieves the case IDs given a single alert ID
*/
getCaseIDsByAlertID(params: CaseIDsByAlertIDParams): Promise<string[]>;
/**
* Retrieves all alerts attach to a case given a single case ID
*/
getAllAlertsAttachToCase(params: GetAllAlertsAttachToCase): Promise<IAlertResponse>;
}

/**
Expand All @@ -105,6 +112,8 @@ export const createCasesSubClient = (
getReporters: (params: AllReportersFindRequest) => getReporters(params, clientArgs),
getCaseIDsByAlertID: (params: CaseIDsByAlertIDParams) =>
getCaseIDsByAlertID(params, clientArgs),
getAllAlertsAttachToCase: (params: GetAllAlertsAttachToCase) =>
getAllAlertsAttachToCase(params, clientArgs, casesClient),
};

return Object.freeze(casesSubClient);
Expand Down
66 changes: 64 additions & 2 deletions x-pack/plugins/cases/server/client/cases/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,77 @@ import {
AllReportersFindRequest,
CasesByAlertIDRequest,
CasesByAlertIDRequestRt,
AlertResponse,
AttributesTypeAlerts,
} from '../../../common/api';
import { countAlertsForID, flattenCaseSavedObject } from '../../common';
import { countAlertsForID, flattenCaseSavedObject, getIDsAndIndicesAsArrays } from '../../common';
import { createCaseError } from '../../common/error';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import { CasesClientArgs } from '..';
import { CasesClient, CasesClientArgs } from '..';
import { Operations } from '../../authorization';
import { combineAuthorizedAndOwnerFilter } from '../utils';
import { CasesService } from '../../services';

const normalizeAlertResponse = (alerts: Array<SavedObject<AttributesTypeAlerts>>): AlertResponse =>
alerts.reduce((acc: AlertResponse, alert) => {
const { ids, indices } = getIDsAndIndicesAsArrays(alert.attributes);

if (ids.length !== indices.length) {
return acc;
}

return [
...acc,
...ids.map((id, index) => ({
id,
index: indices[index],
attached_at: alert.attributes.created_at,
})),
];
}, []);

export interface GetAllAlertsAttachToCase {
caseId: string;
}

export const getAllAlertsAttachToCase = async (
{ caseId }: GetAllAlertsAttachToCase,
clientArgs: CasesClientArgs,
casesClient: CasesClient
): Promise<AlertResponse> => {
const { unsecuredSavedObjectsClient, authorization, caseService } = clientArgs;
const theCase = await casesClient.cases.get({
id: caseId,
includeComments: false,
includeSubCaseComments: false,
});

await authorization.ensureAuthorized({
entities: [{ owner: theCase.owner, id: caseId }],
operation: Operations.getAlertsAttachedToCase,
});

const {
filter: authorizationFilter,
ensureSavedObjectsAreAuthorized,
} = await authorization.getAuthorizationFilter(Operations.getAlertsAttachedToCase);

const alerts = await caseService.getAllAlertsAttachToCase({
unsecuredSavedObjectsClient,
caseId: theCase.id,
filter: authorizationFilter,
});

ensureSavedObjectsAreAuthorized(
alerts.map((alert) => ({
owner: alert.attributes.owner,
id: alert.id,
}))
);

return normalizeAlertResponse(alerts);
};

/**
* Parameters for finding cases IDs using an alert ID
*/
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/cases/server/client/typedoc_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SubCaseResponse,
SubCasesFindResponse,
SubCasesResponse,
AlertResponse,
} from '../../common';

/**
Expand All @@ -55,3 +56,5 @@ export interface ISubCaseResponse extends SubCaseResponse {}
export interface ISubCasesResponse extends SubCasesResponse {}

export interface ICaseUserActionsResponse extends CaseUserActionsResponse {}

export interface IAlertResponse extends AlertResponse {}
11 changes: 3 additions & 8 deletions x-pack/plugins/cases/server/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
User,
} from '../../common/api';
import { ENABLE_CASE_CONNECTOR } from '../../common/constants';
import { UpdateAlertRequest } from '../client/alerts/client';
import { UpdateAlertRequest } from '../client/alerts/types';

/**
* Default sort field for querying saved objects.
Expand Down Expand Up @@ -271,17 +271,12 @@ const getAndValidateAlertInfoFromComment = (comment: CommentRequest): AlertInfo[
/**
* Builds an AlertInfo object accumulating the alert IDs and indices for the passed in alerts.
*/
export const getAlertInfoFromComments = (comments: CommentRequest[] | undefined): AlertInfo[] => {
if (comments === undefined) {
return [];
}

return comments.reduce((acc: AlertInfo[], comment) => {
export const getAlertInfoFromComments = (comments: CommentRequest[] = []): AlertInfo[] =>
comments.reduce((acc: AlertInfo[], comment) => {
const alertInfo = getAndValidateAlertInfoFromComment(comment);
acc.push(...alertInfo);
return acc;
}, []);
};

type NewCommentArgs = CommentRequest & {
associationType: AssociationType;
Expand Down
45 changes: 45 additions & 0 deletions x-pack/plugins/cases/server/routes/api/cases/alerts/get_alerts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { isEmpty } from 'lodash';
import { schema } from '@kbn/config-schema';
import Boom from '@hapi/boom';

import { RouteDeps } from '../../types';
import { wrapError } from '../../utils';
import { CASE_DETAILS_ALERTS_URL } from '../../../../../common/constants';

export function initGetAllAlertsAttachToCaseApi({ router, logger }: RouteDeps) {
router.get(
{
path: CASE_DETAILS_ALERTS_URL,
validate: {
params: schema.object({
case_id: schema.string(),
}),
},
},
async (context, request, response) => {
try {
const caseId = request.params.case_id;
if (isEmpty(caseId)) {
throw Boom.badRequest('The `caseId` is not valid');
}
const casesClient = await context.cases.getCasesClient();

return response.ok({
body: await casesClient.cases.getAllAlertsAttachToCase({ caseId }),
});
} catch (error) {
logger.error(
`Failed to retrieve case ids for this alert id: ${request.params.case_id}: ${error}`
);
return response.customError(wrapError(error));
}
}
);
}
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/server/routes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { initFindSubCasesApi } from './sub_case/find_sub_cases';
import { initDeleteSubCasesApi } from './sub_case/delete_sub_cases';
import { ENABLE_CASE_CONNECTOR } from '../../../common/constants';
import { initGetCaseIdsByAlertIdApi } from './cases/alerts/get_cases';
import { initGetAllAlertsAttachToCaseApi } from './cases/alerts/get_alerts';

/**
* Default page number when interacting with the saved objects API.
Expand Down Expand Up @@ -89,4 +90,5 @@ export function initCaseApi(deps: RouteDeps) {
initGetTagsApi(deps);
// Alerts
initGetCaseIdsByAlertIdApi(deps);
initGetAllAlertsAttachToCaseApi(deps);
}
Loading