Skip to content

Commit 1fd7038

Browse files
[Cases] Include rule registry client for updating alert statuses (#108588)
* Trying to get import to work * Plumbed alerts client through and logging errors * No longer need the ES cluster client * Fixing types * Fixing imports * Fixing integration tests and refactoring * Throwing an error when rule registry is disabled * Reworking alert update and get to catch errors * Adding tests and fixing errors
1 parent 7369bdf commit 1fd7038

File tree

28 files changed

+5794
-542
lines changed

28 files changed

+5794
-542
lines changed

x-pack/plugins/cases/kibana.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"id":"cases",
1111
"kibanaVersion":"kibana",
1212
"optionalPlugins":[
13+
"ruleRegistry",
1314
"security",
1415
"spaces"
1516
],

x-pack/plugins/cases/server/client/alerts/get.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,11 @@ export const get = async (
1212
{ alertsInfo }: AlertGet,
1313
clientArgs: CasesClientArgs
1414
): Promise<CasesClientGetAlertsResponse> => {
15-
const { alertsService, scopedClusterClient, logger } = clientArgs;
15+
const { alertsService, logger } = clientArgs;
1616
if (alertsInfo.length === 0) {
1717
return [];
1818
}
1919

20-
const alerts = await alertsService.getAlerts({ alertsInfo, scopedClusterClient, logger });
21-
if (!alerts) {
22-
return [];
23-
}
24-
25-
return alerts.docs.map((alert) => ({
26-
id: alert._id,
27-
index: alert._index,
28-
...alert._source,
29-
}));
20+
const alerts = await alertsService.getAlerts({ alertsInfo, logger });
21+
return alerts ?? [];
3022
};

x-pack/plugins/cases/server/client/alerts/types.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@
77

88
import { CaseStatuses } from '../../../common/api';
99
import { AlertInfo } from '../../common';
10-
11-
interface Alert {
12-
id: string;
13-
index: string;
14-
destination?: {
15-
ip: string;
16-
};
17-
source?: {
18-
ip: string;
19-
};
20-
}
10+
import { Alert } from '../../services/alerts/types';
2111

2212
export type CasesClientGetAlertsResponse = Alert[];
2313

x-pack/plugins/cases/server/client/alerts/update_status.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ export const updateStatus = async (
1616
{ alerts }: UpdateAlertsStatusArgs,
1717
clientArgs: CasesClientArgs
1818
): Promise<void> => {
19-
const { alertsService, scopedClusterClient, logger } = clientArgs;
20-
await alertsService.updateAlertsStatus({ alerts, scopedClusterClient, logger });
19+
const { alertsService, logger } = clientArgs;
20+
await alertsService.updateAlertsStatus({ alerts, logger });
2121
};

x-pack/plugins/cases/server/client/attachments/add.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,7 @@ import {
4040
} from '../../services/user_actions/helpers';
4141

4242
import { AttachmentService, CasesService, CaseUserActionService } from '../../services';
43-
import {
44-
createCaseError,
45-
CommentableCase,
46-
createAlertUpdateRequest,
47-
isCommentRequestTypeGenAlert,
48-
} from '../../common';
43+
import { createCaseError, CommentableCase, isCommentRequestTypeGenAlert } from '../../common';
4944
import { CasesClientArgs, CasesClientInternal } from '..';
5045

5146
import { decodeCommentRequest } from '../utils';
@@ -195,22 +190,9 @@ const addGeneratedAlerts = async (
195190
user: userDetails,
196191
commentReq: query,
197192
id: savedObjectID,
193+
casesClientInternal,
198194
});
199195

200-
if (
201-
(newComment.attributes.type === CommentType.alert ||
202-
newComment.attributes.type === CommentType.generatedAlert) &&
203-
caseInfo.attributes.settings.syncAlerts
204-
) {
205-
const alertsToUpdate = createAlertUpdateRequest({
206-
comment: query,
207-
status: subCase.attributes.status,
208-
});
209-
await casesClientInternal.alerts.updateStatus({
210-
alerts: alertsToUpdate,
211-
});
212-
}
213-
214196
await userActionService.bulkCreate({
215197
unsecuredSavedObjectsClient,
216198
actions: [
@@ -386,19 +368,9 @@ export const addComment = async (
386368
user: userInfo,
387369
commentReq: query,
388370
id: savedObjectID,
371+
casesClientInternal,
389372
});
390373

391-
if (newComment.attributes.type === CommentType.alert && updatedCase.settings.syncAlerts) {
392-
const alertsToUpdate = createAlertUpdateRequest({
393-
comment: query,
394-
status: updatedCase.status,
395-
});
396-
397-
await casesClientInternal.alerts.updateStatus({
398-
alerts: alertsToUpdate,
399-
});
400-
}
401-
402374
await userActionService.bulkCreate({
403375
unsecuredSavedObjectsClient,
404376
actions: [

x-pack/plugins/cases/server/client/cases/push.ts

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import Boom from '@hapi/boom';
9-
import { SavedObjectsFindResponse, SavedObject } from 'kibana/server';
9+
import { SavedObjectsFindResponse, SavedObject, Logger } from 'kibana/server';
1010

1111
import {
1212
ActionConnector,
@@ -22,26 +22,16 @@ import {
2222
import { buildCaseUserActionItem } from '../../services/user_actions/helpers';
2323

2424
import { createIncident, getCommentContextFromAttributes } from './utils';
25-
import { createCaseError, flattenCaseSavedObject, getAlertInfoFromComments } from '../../common';
25+
import {
26+
AlertInfo,
27+
createCaseError,
28+
flattenCaseSavedObject,
29+
getAlertInfoFromComments,
30+
} from '../../common';
2631
import { CasesClient, CasesClientArgs, CasesClientInternal } from '..';
2732
import { Operations } from '../../authorization';
2833
import { casesConnectors } from '../../connectors';
29-
30-
/**
31-
* Returns true if the case should be closed based on the configuration settings and whether the case
32-
* is a collection. Collections are not closable because we aren't allowing their status to be changed.
33-
* In the future we could allow push to close all the sub cases of a collection but that's not currently supported.
34-
*/
35-
function shouldCloseByPush(
36-
configureSettings: SavedObjectsFindResponse<CasesConfigureAttributes>,
37-
caseInfo: SavedObject<CaseAttributes>
38-
): boolean {
39-
return (
40-
configureSettings.total > 0 &&
41-
configureSettings.saved_objects[0].attributes.closure_type === 'close-by-pushing' &&
42-
caseInfo.attributes.type !== CaseType.collection
43-
);
44-
}
34+
import { CasesClientGetAlertsResponse } from '../alerts/types';
4535

4636
/**
4737
* Parameters for pushing a case to an external system
@@ -106,9 +96,7 @@ export const push = async (
10696

10797
const alertsInfo = getAlertInfoFromComments(theCase?.comments);
10898

109-
const alerts = await casesClientInternal.alerts.get({
110-
alertsInfo,
111-
});
99+
const alerts = await getAlertsCatchErrors({ casesClientInternal, alertsInfo, logger });
112100

113101
const getMappingsResponse = await casesClientInternal.configuration.getMappings({
114102
connector: theCase.connector,
@@ -278,3 +266,38 @@ export const push = async (
278266
throw createCaseError({ message: `Failed to push case: ${error}`, error, logger });
279267
}
280268
};
269+
270+
async function getAlertsCatchErrors({
271+
casesClientInternal,
272+
alertsInfo,
273+
logger,
274+
}: {
275+
casesClientInternal: CasesClientInternal;
276+
alertsInfo: AlertInfo[];
277+
logger: Logger;
278+
}): Promise<CasesClientGetAlertsResponse> {
279+
try {
280+
return await casesClientInternal.alerts.get({
281+
alertsInfo,
282+
});
283+
} catch (error) {
284+
logger.error(`Failed to retrieve alerts during push: ${error}`);
285+
return [];
286+
}
287+
}
288+
289+
/**
290+
* Returns true if the case should be closed based on the configuration settings and whether the case
291+
* is a collection. Collections are not closable because we aren't allowing their status to be changed.
292+
* In the future we could allow push to close all the sub cases of a collection but that's not currently supported.
293+
*/
294+
function shouldCloseByPush(
295+
configureSettings: SavedObjectsFindResponse<CasesConfigureAttributes>,
296+
caseInfo: SavedObject<CaseAttributes>
297+
): boolean {
298+
return (
299+
configureSettings.total > 0 &&
300+
configureSettings.saved_objects[0].attributes.closure_type === 'close-by-pushing' &&
301+
caseInfo.attributes.type !== CaseType.collection
302+
);
303+
}

x-pack/plugins/cases/server/client/cases/update.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { fold } from 'fp-ts/lib/Either';
1212
import { identity } from 'fp-ts/lib/function';
1313

1414
import {
15+
Logger,
1516
SavedObject,
1617
SavedObjectsClientContract,
1718
SavedObjectsFindResponse,
@@ -307,12 +308,14 @@ async function updateAlerts({
307308
caseService,
308309
unsecuredSavedObjectsClient,
309310
casesClientInternal,
311+
logger,
310312
}: {
311313
casesWithSyncSettingChangedToOn: UpdateRequestWithOriginalCase[];
312314
casesWithStatusChangedAndSynced: UpdateRequestWithOriginalCase[];
313315
caseService: CasesService;
314316
unsecuredSavedObjectsClient: SavedObjectsClientContract;
315317
casesClientInternal: CasesClientInternal;
318+
logger: Logger;
316319
}) {
317320
/**
318321
* It's possible that a case ID can appear multiple times in each array. I'm intentionally placing the status changes
@@ -361,7 +364,9 @@ async function updateAlerts({
361364
[]
362365
);
363366

364-
await casesClientInternal.alerts.updateStatus({ alerts: alertsToUpdate });
367+
await casesClientInternal.alerts.updateStatus({
368+
alerts: alertsToUpdate,
369+
});
365370
}
366371

367372
function partitionPatchRequest(
@@ -562,15 +567,6 @@ export const update = async (
562567
);
563568
});
564569

565-
// Update the alert's status to match any case status or sync settings changes
566-
await updateAlerts({
567-
casesWithStatusChangedAndSynced,
568-
casesWithSyncSettingChangedToOn,
569-
caseService,
570-
unsecuredSavedObjectsClient,
571-
casesClientInternal,
572-
});
573-
574570
const returnUpdatedCase = myCases.saved_objects
575571
.filter((myCase) =>
576572
updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id)
@@ -598,6 +594,17 @@ export const update = async (
598594
}),
599595
});
600596

597+
// Update the alert's status to match any case status or sync settings changes
598+
// Attempt to do this after creating/changing the other entities just in case it fails
599+
await updateAlerts({
600+
casesWithStatusChangedAndSynced,
601+
casesWithSyncSettingChangedToOn,
602+
caseService,
603+
unsecuredSavedObjectsClient,
604+
casesClientInternal,
605+
logger,
606+
});
607+
601608
return CasesResponseRt.encode(returnUpdatedCase);
602609
} catch (error) {
603610
const idVersions = cases.cases.map((caseInfo) => ({

x-pack/plugins/cases/server/client/factory.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@
55
* 2.0.
66
*/
77

8-
import {
9-
KibanaRequest,
10-
SavedObjectsServiceStart,
11-
Logger,
12-
ElasticsearchClient,
13-
} from 'kibana/server';
8+
import { KibanaRequest, SavedObjectsServiceStart, Logger } from 'kibana/server';
149
import { SecurityPluginSetup, SecurityPluginStart } from '../../../security/server';
1510
import { SAVED_OBJECT_TYPES } from '../../common';
1611
import { Authorization } from '../authorization/authorization';
@@ -25,8 +20,8 @@ import {
2520
} from '../services';
2621
import { PluginStartContract as FeaturesPluginStart } from '../../../features/server';
2722
import { PluginStartContract as ActionsPluginStart } from '../../../actions/server';
23+
import { RuleRegistryPluginStartContract } from '../../../rule_registry/server';
2824
import { LensServerPluginSetup } from '../../../lens/server';
29-
3025
import { AuthorizationAuditLogger } from '../authorization';
3126
import { CasesClient, createCasesClient } from '.';
3227

@@ -36,6 +31,7 @@ interface CasesClientFactoryArgs {
3631
getSpace: GetSpaceFn;
3732
featuresPluginStart: FeaturesPluginStart;
3833
actionsPluginStart: ActionsPluginStart;
34+
ruleRegistryPluginStart?: RuleRegistryPluginStartContract;
3935
lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory'];
4036
}
4137

@@ -69,12 +65,10 @@ export class CasesClientFactory {
6965
*/
7066
public async create({
7167
request,
72-
scopedClusterClient,
7368
savedObjectsService,
7469
}: {
7570
request: KibanaRequest;
7671
savedObjectsService: SavedObjectsServiceStart;
77-
scopedClusterClient: ElasticsearchClient;
7872
}): Promise<CasesClient> {
7973
if (!this.isInitialized || !this.options) {
8074
throw new Error('CasesClientFactory must be initialized before calling create');
@@ -94,9 +88,12 @@ export class CasesClientFactory {
9488
const caseService = new CasesService(this.logger, this.options?.securityPluginStart?.authc);
9589
const userInfo = caseService.getUser({ request });
9690

91+
const alertsClient = await this.options.ruleRegistryPluginStart?.getRacClientWithRequest(
92+
request
93+
);
94+
9795
return createCasesClient({
98-
alertsService: new AlertService(),
99-
scopedClusterClient,
96+
alertsService: new AlertService(alertsClient),
10097
unsecuredSavedObjectsClient: savedObjectsService.getScopedClient(request, {
10198
includedHiddenTypes: SAVED_OBJECT_TYPES,
10299
// this tells the security plugin to not perform SO authorization and audit logging since we are handling

x-pack/plugins/cases/server/client/sub_cases/update.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ async function updateAlerts({
246246
[]
247247
);
248248

249-
await casesClientInternal.alerts.updateStatus({ alerts: alertsToUpdate });
249+
await casesClientInternal.alerts.updateStatus({
250+
alerts: alertsToUpdate,
251+
});
250252
} catch (error) {
251253
throw createCaseError({
252254
message: `Failed to update alert status while updating sub cases: ${JSON.stringify(
@@ -355,14 +357,6 @@ export async function update({
355357
);
356358
});
357359

358-
await updateAlerts({
359-
caseService,
360-
unsecuredSavedObjectsClient,
361-
casesClientInternal,
362-
subCasesToSync: subCasesToSyncAlertsFor,
363-
logger: clientArgs.logger,
364-
});
365-
366360
const returnUpdatedSubCases = updatedCases.saved_objects.reduce<SubCaseResponse[]>(
367361
(acc, updatedSO) => {
368362
const originalSubCase = subCasesMap.get(updatedSO.id);
@@ -394,6 +388,15 @@ export async function update({
394388
}),
395389
});
396390

391+
// attempt to update the status of the alerts after creating all the user actions just in case it fails
392+
await updateAlerts({
393+
caseService,
394+
unsecuredSavedObjectsClient,
395+
casesClientInternal,
396+
subCasesToSync: subCasesToSyncAlertsFor,
397+
logger: clientArgs.logger,
398+
});
399+
397400
return SubCasesResponseRt.encode(returnUpdatedSubCases);
398401
} catch (error) {
399402
const idVersions = query.subCases.map((subCase) => ({

x-pack/plugins/cases/server/client/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
import type { PublicMethodsOf } from '@kbn/utility-types';
9-
import { ElasticsearchClient, SavedObjectsClientContract, Logger } from 'kibana/server';
9+
import { SavedObjectsClientContract, Logger } from 'kibana/server';
1010
import { User } from '../../common';
1111
import { Authorization } from '../authorization/authorization';
1212
import {
@@ -24,7 +24,6 @@ import { LensServerPluginSetup } from '../../../lens/server';
2424
* Parameters for initializing a cases client
2525
*/
2626
export interface CasesClientArgs {
27-
readonly scopedClusterClient: ElasticsearchClient;
2827
readonly caseConfigureService: CaseConfigureService;
2928
readonly caseService: CasesService;
3029
readonly connectorMappingsService: ConnectorMappingsService;

0 commit comments

Comments
 (0)