"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.CasesService = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _esQuery = require("@kbn/es-query"); var _domain = require("../../../common/types/domain"); var _constants = require("../../../common/constants"); var _api = require("../../../common/api"); var _utils = require("../../common/utils"); var _api2 = require("../../routes/api"); var _utils2 = require("../../client/utils"); var _utils3 = require("../../authorization/utils"); var _transform = require("./transform"); var _error = require("../../common/error"); var _case = require("../../common/types/case"); var _utils4 = require("../utils"); /* * 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. */ const PartialCaseTransformedAttributesRt = (0, _case.getPartialCaseTransformedAttributesRt)(); class CasesService { constructor({ log, unsecuredSavedObjectsClient, attachmentService }) { (0, _defineProperty2.default)(this, "log", void 0); (0, _defineProperty2.default)(this, "unsecuredSavedObjectsClient", void 0); (0, _defineProperty2.default)(this, "attachmentService", void 0); (0, _defineProperty2.default)(this, "buildCaseIdsAggs", (size = 100) => ({ references: { nested: { path: `${_constants.CASE_COMMENT_SAVED_OBJECT}.references` }, aggregations: { caseIds: { terms: { field: `${_constants.CASE_COMMENT_SAVED_OBJECT}.references.id`, size } } } } })); this.log = log; this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; this.attachmentService = attachmentService; } async getCaseIdsByAlertId({ alertId, filter }) { try { this.log.debug(`Attempting to GET all cases for alert id ${alertId}`); const combinedFilter = (0, _utils2.combineFilters)([_esQuery.nodeBuilder.is(`${_constants.CASE_COMMENT_SAVED_OBJECT}.attributes.alertId`, alertId), filter]); const response = await this.unsecuredSavedObjectsClient.find({ type: _constants.CASE_COMMENT_SAVED_OBJECT, fields: (0, _utils3.includeFieldsRequiredForAuthentication)(), page: 1, perPage: 1, sortField: _utils.defaultSortField, aggs: this.buildCaseIdsAggs(_constants.MAX_DOCS_PER_PAGE), filter: combinedFilter }); const owners = []; for (const so of response.saved_objects) { const validatedAttributes = (0, _api.decodeOrThrow)(_case.OwnerRt)(so.attributes); owners.push(Object.assign(so, { attributes: validatedAttributes })); } return Object.assign(response, { saved_objects: owners }); } catch (error) { this.log.error(`Error on GET all cases for alert id ${alertId}: ${error}`); throw error; } } /** * Extracts the case IDs from the alert aggregation */ static getCaseIDsFromAlertAggs(result) { var _result$aggregations$, _result$aggregations; return (_result$aggregations$ = (_result$aggregations = result.aggregations) === null || _result$aggregations === void 0 ? void 0 : _result$aggregations.references.caseIds.buckets.map(b => b.key)) !== null && _result$aggregations$ !== void 0 ? _result$aggregations$ : []; } /** * Returns a map of all cases. */ async findCasesGroupedByID({ caseOptions }) { const cases = await this.findCases(caseOptions); const casesMap = cases.saved_objects.reduce((accMap, caseInfo) => { accMap.set(caseInfo.id, caseInfo); return accMap; }, new Map()); const commentTotals = await this.attachmentService.getter.getCaseCommentStats({ caseIds: Array.from(casesMap.keys()) }); const casesWithComments = new Map(); for (const [id, caseInfo] of casesMap.entries()) { var _commentTotals$get; const { alerts, userComments } = (_commentTotals$get = commentTotals.get(id)) !== null && _commentTotals$get !== void 0 ? _commentTotals$get : { alerts: 0, userComments: 0 }; casesWithComments.set(id, (0, _utils.flattenCaseSavedObject)({ savedObject: caseInfo, totalComment: userComments, totalAlerts: alerts })); } return { casesMap: casesWithComments, page: cases.page, perPage: cases.per_page, total: cases.total }; } async getCaseStatusStats({ searchOptions }) { var _cases$aggregations, _statusBuckets$get, _statusBuckets$get2, _statusBuckets$get3; const cases = await this.unsecuredSavedObjectsClient.find({ ...searchOptions, type: _constants.CASE_SAVED_OBJECT, perPage: 0, aggs: { statuses: { terms: { field: `${_constants.CASE_SAVED_OBJECT}.attributes.status`, size: _domain.caseStatuses.length, order: { _key: 'asc' } } } } }); const statusBuckets = CasesService.getStatusBuckets((_cases$aggregations = cases.aggregations) === null || _cases$aggregations === void 0 ? void 0 : _cases$aggregations.statuses.buckets); return { open: (_statusBuckets$get = statusBuckets === null || statusBuckets === void 0 ? void 0 : statusBuckets.get(_case.CasePersistedStatus.OPEN)) !== null && _statusBuckets$get !== void 0 ? _statusBuckets$get : 0, 'in-progress': (_statusBuckets$get2 = statusBuckets === null || statusBuckets === void 0 ? void 0 : statusBuckets.get(_case.CasePersistedStatus.IN_PROGRESS)) !== null && _statusBuckets$get2 !== void 0 ? _statusBuckets$get2 : 0, closed: (_statusBuckets$get3 = statusBuckets === null || statusBuckets === void 0 ? void 0 : statusBuckets.get(_case.CasePersistedStatus.CLOSED)) !== null && _statusBuckets$get3 !== void 0 ? _statusBuckets$get3 : 0 }; } static getStatusBuckets(buckets) { return buckets === null || buckets === void 0 ? void 0 : buckets.reduce((acc, bucket) => { acc.set(Number(bucket.key), bucket.doc_count); return acc; }, new Map()); } async deleteCase({ id: caseId, refresh }) { try { this.log.debug(`Attempting to DELETE case ${caseId}`); await this.unsecuredSavedObjectsClient.delete(_constants.CASE_SAVED_OBJECT, caseId, { refresh }); } catch (error) { this.log.error(`Error on DELETE case ${caseId}: ${error}`); throw error; } } async bulkDeleteCaseEntities({ entities, options }) { try { this.log.debug(`Attempting to bulk delete case entities ${JSON.stringify(entities)}`); await this.unsecuredSavedObjectsClient.bulkDelete(entities, options); } catch (error) { this.log.error(`Error bulk deleting case entities ${JSON.stringify(entities)}: ${error}`); } } async getCase({ id: caseId }) { try { this.log.debug(`Attempting to GET case ${caseId}`); const caseSavedObject = await this.unsecuredSavedObjectsClient.get(_constants.CASE_SAVED_OBJECT, caseId); const res = (0, _transform.transformSavedObjectToExternalModel)(caseSavedObject); const decodeRes = (0, _api.decodeOrThrow)(_case.CaseTransformedAttributesRt)(res.attributes); return { ...res, attributes: decodeRes }; } catch (error) { this.log.error(`Error on GET case ${caseId}: ${error}`); throw error; } } async getResolveCase({ id: caseId }) { try { this.log.debug(`Attempting to resolve case ${caseId}`); const resolveCaseResult = await this.unsecuredSavedObjectsClient.resolve(_constants.CASE_SAVED_OBJECT, caseId); const resolvedSO = (0, _transform.transformSavedObjectToExternalModel)(resolveCaseResult.saved_object); const decodeRes = (0, _api.decodeOrThrow)(_case.CaseTransformedAttributesRt)(resolvedSO.attributes); return { ...resolveCaseResult, saved_object: { ...resolvedSO, attributes: decodeRes } }; } catch (error) { this.log.error(`Error on resolve case ${caseId}: ${error}`); throw error; } } async getCases({ caseIds }) { try { this.log.debug(`Attempting to GET cases ${caseIds.join(', ')}`); const cases = await this.unsecuredSavedObjectsClient.bulkGet(caseIds.map(caseId => ({ type: _constants.CASE_SAVED_OBJECT, id: caseId }))); const res = cases.saved_objects.reduce((acc, theCase) => { if ((0, _utils.isSOError)(theCase)) { acc.push(theCase); return acc; } const so = Object.assign(theCase, (0, _transform.transformSavedObjectToExternalModel)(theCase)); const decodeRes = (0, _api.decodeOrThrow)(_case.CaseTransformedAttributesRt)(so.attributes); const soWithDecodedRes = Object.assign(so, { attributes: decodeRes }); acc.push(soWithDecodedRes); return acc; }, []); return Object.assign(cases, { saved_objects: res /** * The case is needed here because * the SavedObjectsBulkResponse is wrong. * It assumes that the attributes exist * on an error which is not true. */ }); } catch (error) { this.log.error(`Error on GET cases ${caseIds.join(', ')}: ${error}`); throw error; } } async findCases(options) { try { this.log.debug(`Attempting to find cases`); const cases = await this.unsecuredSavedObjectsClient.find({ sortField: _utils.defaultSortField, ...options, type: _constants.CASE_SAVED_OBJECT }); const res = (0, _transform.transformFindResponseToExternalModel)(cases); const decodeRes = (0, _utils4.bulkDecodeSOAttributes)(res.saved_objects, _case.CaseTransformedAttributesRt); return { ...res, saved_objects: res.saved_objects.map(so => ({ ...so, attributes: decodeRes.get(so.id) })) }; } catch (error) { this.log.error(`Error on find cases: ${error}`); throw error; } } asArray(id) { if (id === undefined) { return []; } else if (Array.isArray(id)) { return id; } else { return [id]; } } // TODO: This should probably be moved into the client since it is after the transform has // occurred within the attachment service async getAllComments({ id, options }) { try { this.log.debug(`Attempting to GET all comments internal for id ${JSON.stringify(id)}`); if ((options === null || options === void 0 ? void 0 : options.page) !== undefined || (options === null || options === void 0 ? void 0 : options.perPage) !== undefined) { return this.attachmentService.find({ options: { sortField: _utils.defaultSortField, ...options } }); } return this.attachmentService.find({ options: { page: 1, perPage: _constants.MAX_DOCS_PER_PAGE, sortField: _utils.defaultSortField, ...options } }); } catch (error) { this.log.error(`Error on GET all comments internal for ${JSON.stringify(id)}: ${error}`); throw error; } } // TODO: This should probably be moved into the client since it is after the transform has // occurred within the attachment service /** * Default behavior is to retrieve all comments that adhere to a given filter (if one is included). * to override this pass in the either the page or perPage options. */ async getAllCaseComments({ id, options }) { try { const refs = this.asArray(id).map(caseID => ({ type: _constants.CASE_SAVED_OBJECT, id: caseID })); if (refs.length <= 0) { var _options$perPage, _options$page; return { saved_objects: [], total: 0, per_page: (_options$perPage = options === null || options === void 0 ? void 0 : options.perPage) !== null && _options$perPage !== void 0 ? _options$perPage : _api2.DEFAULT_PER_PAGE, page: (_options$page = options === null || options === void 0 ? void 0 : options.page) !== null && _options$page !== void 0 ? _options$page : _api2.DEFAULT_PAGE }; } this.log.debug(`Attempting to GET all comments for case caseID ${JSON.stringify(id)}`); return await this.getAllComments({ id, options: { hasReferenceOperator: 'OR', hasReference: refs, filter: options === null || options === void 0 ? void 0 : options.filter, ...options } }); } catch (error) { this.log.error(`Error on GET all comments for case ${JSON.stringify(id)}: ${error}`); throw error; } } async getReporters({ filter }) { try { var _results$aggregations, _results$aggregations2, _results$aggregations3; this.log.debug(`Attempting to GET all reporters`); const results = await this.unsecuredSavedObjectsClient.find({ type: _constants.CASE_SAVED_OBJECT, page: 1, perPage: 1, filter, aggs: { reporters: { terms: { field: `${_constants.CASE_SAVED_OBJECT}.attributes.created_by.username`, size: _constants.MAX_DOCS_PER_PAGE, order: { _key: 'asc' } }, aggs: { top_docs: { top_hits: { sort: [{ [`${_constants.CASE_SAVED_OBJECT}.created_at`]: { order: 'desc' } }], size: 1, _source: [`${_constants.CASE_SAVED_OBJECT}.created_by`] } } } } } }); return (_results$aggregations = results === null || results === void 0 ? void 0 : (_results$aggregations2 = results.aggregations) === null || _results$aggregations2 === void 0 ? void 0 : (_results$aggregations3 = _results$aggregations2.reporters) === null || _results$aggregations3 === void 0 ? void 0 : _results$aggregations3.buckets.map(({ key: username, top_docs: topDocs }) => { var _topDocs$hits$hits$0$, _topDocs$hits, _topDocs$hits$hits, _topDocs$hits$hits$, _topDocs$hits$hits$$_, _topDocs$hits$hits$$_2, _user$full_name, _user$email; const user = (_topDocs$hits$hits$0$ = topDocs === null || topDocs === void 0 ? void 0 : (_topDocs$hits = topDocs.hits) === null || _topDocs$hits === void 0 ? void 0 : (_topDocs$hits$hits = _topDocs$hits.hits) === null || _topDocs$hits$hits === void 0 ? void 0 : (_topDocs$hits$hits$ = _topDocs$hits$hits[0]) === null || _topDocs$hits$hits$ === void 0 ? void 0 : (_topDocs$hits$hits$$_ = _topDocs$hits$hits$._source) === null || _topDocs$hits$hits$$_ === void 0 ? void 0 : (_topDocs$hits$hits$$_2 = _topDocs$hits$hits$$_.cases) === null || _topDocs$hits$hits$$_2 === void 0 ? void 0 : _topDocs$hits$hits$$_2.created_by) !== null && _topDocs$hits$hits$0$ !== void 0 ? _topDocs$hits$hits$0$ : {}; return { username, full_name: (_user$full_name = user.full_name) !== null && _user$full_name !== void 0 ? _user$full_name : null, email: (_user$email = user.email) !== null && _user$email !== void 0 ? _user$email : null, profile_uid: user.profile_uid }; })) !== null && _results$aggregations !== void 0 ? _results$aggregations : []; } catch (error) { this.log.error(`Error on GET all reporters: ${error}`); throw error; } } async getTags({ filter }) { try { var _results$aggregations4, _results$aggregations5, _results$aggregations6; this.log.debug(`Attempting to GET all cases`); const results = await this.unsecuredSavedObjectsClient.find({ type: _constants.CASE_SAVED_OBJECT, page: 1, perPage: 1, filter, aggs: { tags: { terms: { field: `${_constants.CASE_SAVED_OBJECT}.attributes.tags`, size: _constants.MAX_DOCS_PER_PAGE, order: { _key: 'asc' } } } } }); return (_results$aggregations4 = results === null || results === void 0 ? void 0 : (_results$aggregations5 = results.aggregations) === null || _results$aggregations5 === void 0 ? void 0 : (_results$aggregations6 = _results$aggregations5.tags) === null || _results$aggregations6 === void 0 ? void 0 : _results$aggregations6.buckets.map(({ key }) => key)) !== null && _results$aggregations4 !== void 0 ? _results$aggregations4 : []; } catch (error) { this.log.error(`Error on GET tags: ${error}`); throw error; } } async getCategories({ filter }) { try { var _results$aggregations7, _results$aggregations8, _results$aggregations9; this.log.debug(`Attempting to GET all categories`); const results = await this.unsecuredSavedObjectsClient.find({ type: _constants.CASE_SAVED_OBJECT, page: 1, perPage: 1, filter, aggs: { categories: { terms: { field: `${_constants.CASE_SAVED_OBJECT}.attributes.category`, size: _constants.MAX_DOCS_PER_PAGE, order: { _key: 'asc' } } } } }); return (_results$aggregations7 = results === null || results === void 0 ? void 0 : (_results$aggregations8 = results.aggregations) === null || _results$aggregations8 === void 0 ? void 0 : (_results$aggregations9 = _results$aggregations8.categories) === null || _results$aggregations9 === void 0 ? void 0 : _results$aggregations9.buckets.map(({ key }) => key)) !== null && _results$aggregations7 !== void 0 ? _results$aggregations7 : []; } catch (error) { this.log.error(`Error on GET categories: ${error}`); throw error; } } async postNewCase({ attributes, id, refresh }) { try { this.log.debug(`Attempting to POST a new case`); const decodedAttributes = (0, _api.decodeOrThrow)(_case.CaseTransformedAttributesRt)(attributes); const transformedAttributes = (0, _transform.transformAttributesToESModel)(decodedAttributes); transformedAttributes.attributes.total_alerts = -1; transformedAttributes.attributes.total_comments = -1; const createdCase = await this.unsecuredSavedObjectsClient.create(_constants.CASE_SAVED_OBJECT, transformedAttributes.attributes, { id, references: transformedAttributes.referenceHandler.build(), refresh }); const res = (0, _transform.transformSavedObjectToExternalModel)(createdCase); const decodedRes = (0, _api.decodeOrThrow)(_case.CaseTransformedAttributesRt)(res.attributes); return { ...res, attributes: decodedRes }; } catch (error) { this.log.error(`Error on POST a new case: ${error}`); throw error; } } async patchCase({ caseId, updatedAttributes, originalCase, version, refresh }) { try { this.log.debug(`Attempting to UPDATE case ${caseId}`); const decodedAttributes = (0, _api.decodeOrThrow)(PartialCaseTransformedAttributesRt)(updatedAttributes); const transformedAttributes = (0, _transform.transformAttributesToESModel)(decodedAttributes); const updatedCase = await this.unsecuredSavedObjectsClient.update(_constants.CASE_SAVED_OBJECT, caseId, transformedAttributes.attributes, { version, references: transformedAttributes.referenceHandler.build(originalCase.references), refresh }); const res = (0, _transform.transformUpdateResponseToExternalModel)(updatedCase); const decodeRes = (0, _api.decodeOrThrow)(PartialCaseTransformedAttributesRt)(res.attributes); return { ...res, attributes: decodeRes }; } catch (error) { this.log.error(`Error on UPDATE case ${caseId}: ${error}`); throw error; } } async patchCases({ cases, refresh }) { try { this.log.debug(`Attempting to UPDATE case ${cases.map(c => c.caseId).join(', ')}`); const bulkUpdate = cases.map(({ caseId, updatedAttributes, version, originalCase }) => { const decodedAttributes = (0, _api.decodeOrThrow)(PartialCaseTransformedAttributesRt)(updatedAttributes); const { attributes, referenceHandler } = (0, _transform.transformAttributesToESModel)(decodedAttributes); return { type: _constants.CASE_SAVED_OBJECT, id: caseId, attributes, references: referenceHandler.build(originalCase.references), version }; }); const updatedCases = await this.unsecuredSavedObjectsClient.bulkUpdate(bulkUpdate, { refresh }); const res = updatedCases.saved_objects.reduce((acc, theCase) => { if ((0, _utils.isSOError)(theCase)) { acc.push(theCase); return acc; } const so = Object.assign(theCase, (0, _transform.transformUpdateResponseToExternalModel)(theCase)); const decodeRes = (0, _api.decodeOrThrow)(PartialCaseTransformedAttributesRt)(so.attributes); const soWithDecodedRes = Object.assign(so, { attributes: decodeRes }); acc.push(soWithDecodedRes); return acc; }, []); return Object.assign(updatedCases, { saved_objects: res /** * The case is needed here because * the SavedObjectsBulkUpdateResponse is wrong. * It assumes that the attributes exist * on an error which is not true. */ }); } catch (error) { this.log.error(`Error on UPDATE case ${cases.map(c => c.caseId).join(', ')}: ${error}`); throw error; } } async executeAggregations({ aggregationBuilders, options }) { try { const builtAggs = aggregationBuilders.reduce((acc, agg) => { return Object.assign(acc, agg.build()); }, {}); const res = await this.unsecuredSavedObjectsClient.find({ sortField: _utils.defaultSortField, ...options, aggs: builtAggs, type: _constants.CASE_SAVED_OBJECT }); return res.aggregations; } catch (error) { const aggregationNames = aggregationBuilders.map(agg => agg.getName()); throw (0, _error.createCaseError)({ message: `Failed to execute aggregations [${aggregationNames.join(',')}]: ${error}`, error, logger: this.log }); } } } exports.CasesService = CasesService;