Skip to content

Commit

Permalink
Create route
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Jun 10, 2021
1 parent 7917e3c commit 8ce2fc4
Show file tree
Hide file tree
Showing 15 changed files with 211 additions and 31 deletions.
17 changes: 17 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,17 @@
/*
* 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({
alertId: rt.array(rt.string),
index: rt.array(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
52 changes: 51 additions & 1 deletion x-pack/plugins/cases/server/client/cases/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,65 @@ import {
AllReportersFindRequest,
CasesByAlertIDRequest,
CasesByAlertIDRequestRt,
AlertResponse,
} from '../../../common/api';
import { countAlertsForID, flattenCaseSavedObject } 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';

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 alerts.map((alert) => ({
alertId: Array.isArray(alert.attributes.alertId)
? alert.attributes.alertId
: [alert.attributes.alertId],
index: Array.isArray(alert.attributes.index)
? alert.attributes.index
: [alert.attributes.index],
}));
};

/**
* 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 {}
2 changes: 1 addition & 1 deletion 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
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);
}
51 changes: 49 additions & 2 deletions x-pack/plugins/cases/server/services/cases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
CaseStatuses,
OWNER_FIELD,
GetCaseIdsByAlertIdAggs,
AttributesTypeAlerts,
} from '../../../common/api';
import {
defaultSortField,
Expand All @@ -55,10 +56,18 @@ import {
SUB_CASE_SAVED_OBJECT,
} from '../../../common/constants';
import { ClientArgs } from '..';
import { combineFilters } from '../../client/utils';
import { includeFieldsRequiredForAuthentication } from '../../authorization/utils';
import { buildFilter, combineFilters } from '../../client/utils';
import {
combineFilterWithAuthorizationFilter,
includeFieldsRequiredForAuthentication,
} from '../../authorization/utils';
import { EnsureSOAuthCallback } from '../../authorization';

interface GetAllAlertsAttachToCaseArgs extends ClientArgs {
caseId: string;
filter?: KueryNode;
}

interface GetCaseIdsByAlertIdArgs extends ClientArgs {
alertId: string;
filter?: KueryNode;
Expand Down Expand Up @@ -240,6 +249,44 @@ export class CasesService {
},
});

public async getAllAlertsAttachToCase({
unsecuredSavedObjectsClient,
caseId,
filter,
}: GetAllAlertsAttachToCaseArgs): Promise<Array<SavedObject<AttributesTypeAlerts>>> {
try {
this.log.debug(`Attempting to GET all alerts for alert id ${caseId}`);
const alertsFilter = buildFilter({
filters: [CommentType.alert, CommentType.generatedAlert],
field: 'type',
operator: 'or',
type: CASE_COMMENT_SAVED_OBJECT,
});

const combinedFilter = combineFilters([alertsFilter, filter]);

const finder = unsecuredSavedObjectsClient.createPointInTimeFinder<AttributesTypeAlerts>({
type: CASE_COMMENT_SAVED_OBJECT,
hasReference: { type: CASE_SAVED_OBJECT, id: caseId },
sortField: 'created_at',
sortOrder: 'asc',
filter: combinedFilter,
fields: includeFieldsRequiredForAuthentication(),
perPage: MAX_DOCS_PER_PAGE,
});

let result: Array<SavedObject<AttributesTypeAlerts>> = [];
for await (const userActionSavedObject of finder.find()) {
result = result.concat(userActionSavedObject.saved_objects);
}

return result;
} catch (error) {
this.log.error(`Error on GET all cases for alert id ${caseId}: ${error}`);
throw error;
}
}

public async getCaseIdsByAlertId({
unsecuredSavedObjectsClient,
alertId,
Expand Down

0 comments on commit 8ce2fc4

Please sign in to comment.