"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.AlertsClient = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _boom = _interopRequireDefault(require("@hapi/boom")); var _esQuery = require("@kbn/es-query"); var _securitysolutionEsUtils = require("@kbn/securitysolution-es-utils"); var _ruleDataUtils = require("@kbn/rule-data-utils"); var _server = require("@kbn/alerting-plugin/server"); var _server2 = require("@kbn/data-plugin/server"); var _lodash = require("lodash"); var _audit_events = require("./audit_events"); var _technical_rule_data_field_names = require("../../common/technical_rule_data_field_names"); var _rule_data_plugin_service = require("../rule_data_plugin_service"); var _lib = require("../lib"); var _browser_fields = require("./browser_fields"); /* * 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 isValidAlert = source => { var _source$_source, _source$_source2, _source$_source3, _source$fields, _source$fields2, _source$fields3; return (source === null || source === void 0 ? void 0 : (_source$_source = source._source) === null || _source$_source === void 0 ? void 0 : _source$_source[_technical_rule_data_field_names.ALERT_RULE_TYPE_ID]) != null && (source === null || source === void 0 ? void 0 : (_source$_source2 = source._source) === null || _source$_source2 === void 0 ? void 0 : _source$_source2[_technical_rule_data_field_names.ALERT_RULE_CONSUMER]) != null && (source === null || source === void 0 ? void 0 : (_source$_source3 = source._source) === null || _source$_source3 === void 0 ? void 0 : _source$_source3[_technical_rule_data_field_names.SPACE_IDS]) != null || (source === null || source === void 0 ? void 0 : (_source$fields = source.fields) === null || _source$fields === void 0 ? void 0 : _source$fields[_technical_rule_data_field_names.ALERT_RULE_TYPE_ID][0]) != null && (source === null || source === void 0 ? void 0 : (_source$fields2 = source.fields) === null || _source$fields2 === void 0 ? void 0 : _source$fields2[_technical_rule_data_field_names.ALERT_RULE_CONSUMER][0]) != null && (source === null || source === void 0 ? void 0 : (_source$fields3 = source.fields) === null || _source$fields3 === void 0 ? void 0 : _source$fields3[_technical_rule_data_field_names.SPACE_IDS][0]) != null; }; /** * Provides apis to interact with alerts as data * ensures the request is authorized to perform read / write actions * on alerts as data. */ class AlertsClient { constructor(options) { (0, _defineProperty2.default)(this, "logger", void 0); (0, _defineProperty2.default)(this, "auditLogger", void 0); (0, _defineProperty2.default)(this, "authorization", void 0); (0, _defineProperty2.default)(this, "esClient", void 0); (0, _defineProperty2.default)(this, "spaceId", void 0); (0, _defineProperty2.default)(this, "ruleDataService", void 0); (0, _defineProperty2.default)(this, "getRuleType", void 0); this.logger = options.logger; this.authorization = options.authorization; this.esClient = options.esClient; this.auditLogger = options.auditLogger; // If spaceId is undefined, it means that spaces is disabled // Otherwise, if space is enabled and not specified, it is "default" this.spaceId = this.authorization.getSpaceId(); this.ruleDataService = options.ruleDataService; this.getRuleType = options.getRuleType; } getOutcome(operation) { return { outcome: operation === _server.WriteOperations.Update ? 'unknown' : 'success' }; } getAlertStatusFieldUpdate(source, status) { return (source === null || source === void 0 ? void 0 : source[_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS]) == null ? { signal: { status } } : { [_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS]: status }; } getAlertCaseIdsFieldUpdate(source, caseIds) { var _source$ALERT_CASE_ID; const uniqueCaseIds = new Set([...((_source$ALERT_CASE_ID = source === null || source === void 0 ? void 0 : source[_ruleDataUtils.ALERT_CASE_IDS]) !== null && _source$ALERT_CASE_ID !== void 0 ? _source$ALERT_CASE_ID : []), ...caseIds]); return { [_ruleDataUtils.ALERT_CASE_IDS]: Array.from(uniqueCaseIds.values()) }; } validateTotalCasesPerAlert(source, caseIds) { var _source$ALERT_CASE_ID2; const currentCaseIds = (_source$ALERT_CASE_ID2 = source === null || source === void 0 ? void 0 : source[_ruleDataUtils.ALERT_CASE_IDS]) !== null && _source$ALERT_CASE_ID2 !== void 0 ? _source$ALERT_CASE_ID2 : []; if (currentCaseIds.length + caseIds.length > _ruleDataUtils.MAX_CASES_PER_ALERT) { throw _boom.default.badRequest(`You cannot attach more than ${_ruleDataUtils.MAX_CASES_PER_ALERT} cases to an alert`); } } /** * Accepts an array of ES documents and executes ensureAuthorized for the given operation * @param items * @param operation * @returns */ async ensureAllAuthorized(items, operation) { const { hitIds, ownersAndRuleTypeIds } = items.reduce((acc, hit) => { var _hit$_source, _hit$_source2; return { hitIds: [hit._id, ...acc.hitIds], ownersAndRuleTypeIds: [{ [_technical_rule_data_field_names.ALERT_RULE_TYPE_ID]: hit === null || hit === void 0 ? void 0 : (_hit$_source = hit._source) === null || _hit$_source === void 0 ? void 0 : _hit$_source[_technical_rule_data_field_names.ALERT_RULE_TYPE_ID], [_technical_rule_data_field_names.ALERT_RULE_CONSUMER]: hit === null || hit === void 0 ? void 0 : (_hit$_source2 = hit._source) === null || _hit$_source2 === void 0 ? void 0 : _hit$_source2[_technical_rule_data_field_names.ALERT_RULE_CONSUMER] }] }; }, { hitIds: [], ownersAndRuleTypeIds: [] }); const assertString = hit => hit !== null && hit !== undefined; return Promise.all(ownersAndRuleTypeIds.map(hit => { const alertOwner = hit === null || hit === void 0 ? void 0 : hit[_technical_rule_data_field_names.ALERT_RULE_CONSUMER]; const ruleId = hit === null || hit === void 0 ? void 0 : hit[_technical_rule_data_field_names.ALERT_RULE_TYPE_ID]; if (hit != null && assertString(alertOwner) && assertString(ruleId)) { return this.authorization.ensureAuthorized({ ruleTypeId: ruleId, consumer: alertOwner, operation, entity: _server.AlertingAuthorizationEntity.Alert }); } })).catch(error => { for (const hitId of hitIds) { var _this$auditLogger; (_this$auditLogger = this.auditLogger) === null || _this$auditLogger === void 0 ? void 0 : _this$auditLogger.log((0, _audit_events.alertAuditEvent)({ action: _audit_events.operationAlertAuditActionMap[operation], id: hitId, error })); } throw error; }); } /** * This will be used as a part of the "find" api * In the future we will add an "aggs" param * @param param0 * @returns */ async singleSearchAfterAndAudit({ id, query, aggs, _source, track_total_hits: trackTotalHits, size, index, operation, sort, lastSortIds = [] }) { try { var _result$hits; const alertSpaceId = this.spaceId; if (alertSpaceId == null) { const errorMessage = 'Failed to acquire spaceId from authorization client'; this.logger.error(`fetchAlertAndAudit threw an error: ${errorMessage}`); throw _boom.default.failedDependency(`fetchAlertAndAudit threw an error: ${errorMessage}`); } const config = (0, _ruleDataUtils.getEsQueryConfig)(); let queryBody = { fields: [_technical_rule_data_field_names.ALERT_RULE_TYPE_ID, _technical_rule_data_field_names.ALERT_RULE_CONSUMER, _technical_rule_data_field_names.ALERT_WORKFLOW_STATUS, _technical_rule_data_field_names.SPACE_IDS], query: await this.buildEsQueryWithAuthz(query, id, alertSpaceId, operation, config), aggs, _source, track_total_hits: trackTotalHits, size, sort: sort || [{ '@timestamp': { order: 'asc', unmapped_type: 'date' } }] }; if (lastSortIds.length > 0) { queryBody = { ...queryBody, search_after: lastSortIds }; } const result = await this.esClient.search({ index: index !== null && index !== void 0 ? index : '.alerts-*', ignore_unavailable: true, body: queryBody, seq_no_primary_term: true }); if (!(result !== null && result !== void 0 && result.hits.hits.every(hit => isValidAlert(hit)))) { const errorMessage = `Invalid alert found with id of "${id}" or with query "${query}" and operation ${operation}`; this.logger.error(errorMessage); throw _boom.default.badData(errorMessage); } if ((result === null || result === void 0 ? void 0 : (_result$hits = result.hits) === null || _result$hits === void 0 ? void 0 : _result$hits.hits) != null && (result === null || result === void 0 ? void 0 : result.hits.hits.length) > 0) { await this.ensureAllAuthorized(result.hits.hits, operation); result === null || result === void 0 ? void 0 : result.hits.hits.map(item => { var _this$auditLogger2; return (_this$auditLogger2 = this.auditLogger) === null || _this$auditLogger2 === void 0 ? void 0 : _this$auditLogger2.log((0, _audit_events.alertAuditEvent)({ action: _audit_events.operationAlertAuditActionMap[operation], id: item._id, ...this.getOutcome(operation) })); }); } return result; } catch (error) { const errorMessage = `Unable to retrieve alert details for alert with id of "${id}" or with query "${query}" and operation ${operation} \nError: ${error}`; this.logger.error(errorMessage); throw _boom.default.notFound(errorMessage); } } /** * When an update by ids is requested, do a multi-get, ensure authz and audit alerts, then execute bulk update * @param param0 * @returns */ async mgetAlertsAuditOperate({ alerts, operation, fieldToUpdate, validate }) { try { const mgetRes = await this.ensureAllAlertsAuthorized({ alerts, operation }); const updateRequests = []; for (const item of mgetRes.docs) { if (validate) { // @ts-expect-error doesn't handle error branch in MGetResponse validate(item === null || item === void 0 ? void 0 : item._source); } updateRequests.push([{ update: { _index: item._index, _id: item._id } }, { doc: { // @ts-expect-error doesn't handle error branch in MGetResponse ...fieldToUpdate(item === null || item === void 0 ? void 0 : item._source) } }]); } const bulkUpdateRequest = updateRequests.flat(); const bulkUpdateResponse = await this.esClient.bulk({ refresh: 'wait_for', body: bulkUpdateRequest }); return bulkUpdateResponse; } catch (exc) { this.logger.error(`error in mgetAlertsAuditOperate ${exc}`); throw exc; } } /** * When an update by ids is requested, do a multi-get, ensure authz and audit alerts, then execute bulk update * @param param0 * @returns */ async mgetAlertsAuditOperateStatus({ alerts, status, operation }) { return this.mgetAlertsAuditOperate({ alerts, operation, fieldToUpdate: source => this.getAlertStatusFieldUpdate(source, status) }); } async buildEsQueryWithAuthz(query, id, alertSpaceId, operation, config) { try { const authzFilter = await (0, _lib.getAuthzFilter)(this.authorization, operation); const spacesFilter = (0, _lib.getSpacesFilter)(alertSpaceId); let esQuery; if (id != null) { esQuery = { query: `_id:${id}`, language: 'kuery' }; } else if (typeof query === 'string') { esQuery = { query, language: 'kuery' }; } else if (query != null && typeof query === 'object') { esQuery = []; } const builtQuery = (0, _esQuery.buildEsQuery)(undefined, esQuery == null ? { query: ``, language: 'kuery' } : esQuery, [authzFilter, spacesFilter], config); if (query != null && typeof query === 'object') { return { ...builtQuery, bool: { ...builtQuery.bool, must: [...builtQuery.bool.must, query] } }; } return builtQuery; } catch (exc) { this.logger.error(exc); throw _boom.default.expectationFailed(`buildEsQueryWithAuthz threw an error: unable to get authorization filter \n ${exc}`); } } /** * executes a search after to find alerts with query (+ authz filter) * @param param0 * @returns */ async queryAndAuditAllAlerts({ index, query, operation }) { let lastSortIds; let hasSortIds = true; const alertSpaceId = this.spaceId; if (alertSpaceId == null) { this.logger.error('Failed to acquire spaceId from authorization client'); return; } const config = (0, _ruleDataUtils.getEsQueryConfig)(); const authorizedQuery = await this.buildEsQueryWithAuthz(query, null, alertSpaceId, operation, config); while (hasSortIds) { try { var _result$hits$hits; const result = await this.singleSearchAfterAndAudit({ id: null, query, index, operation, lastSortIds }); if (lastSortIds != null && (result === null || result === void 0 ? void 0 : result.hits.hits.length) === 0) { return { auditedAlerts: true, authorizedQuery }; } if (result == null) { this.logger.error('RESULT WAS EMPTY'); return { auditedAlerts: false, authorizedQuery }; } if (result.hits.hits.length === 0) { this.logger.error('Search resulted in no hits'); return { auditedAlerts: true, authorizedQuery }; } lastSortIds = (0, _ruleDataUtils.getSafeSortIds)((_result$hits$hits = result.hits.hits[result.hits.hits.length - 1]) === null || _result$hits$hits === void 0 ? void 0 : _result$hits$hits.sort); if (lastSortIds != null && lastSortIds.length !== 0) { hasSortIds = true; } else { hasSortIds = false; return { auditedAlerts: true, authorizedQuery }; } } catch (error) { const errorMessage = `queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query "${query}" and operation ${operation} \n ${error}`; this.logger.error(errorMessage); throw _boom.default.notFound(errorMessage); } } } /** * Ensures that the user has access to the alerts * for a given operation */ async ensureAllAlertsAuthorized({ alerts, operation }) { try { const mgetRes = await this.esClient.mget({ docs: alerts.map(({ id, index }) => ({ _id: id, _index: index })) }); await this.ensureAllAuthorized(mgetRes.docs, operation); const ids = mgetRes.docs.map(({ _id }) => _id); for (const id of ids) { var _this$auditLogger3; (_this$auditLogger3 = this.auditLogger) === null || _this$auditLogger3 === void 0 ? void 0 : _this$auditLogger3.log((0, _audit_events.alertAuditEvent)({ action: _audit_events.operationAlertAuditActionMap[operation], id, ...this.getOutcome(operation) })); } return mgetRes; } catch (exc) { this.logger.error(`error in ensureAllAlertsAuthorized ${exc}`); throw exc; } } async ensureAllAlertsAuthorizedRead({ alerts }) { try { await this.ensureAllAlertsAuthorized({ alerts, operation: _server.ReadOperations.Get }); } catch (error) { this.logger.error(`error authenticating alerts for read access: ${error}`); throw error; } } async get({ id, index }) { try { // first search for the alert by id, then use the alert info to check if user has access to it const alert = await this.singleSearchAfterAndAudit({ id, index, operation: _server.ReadOperations.Get }); if (alert == null || alert.hits.hits.length === 0) { const errorMessage = `Unable to retrieve alert details for alert with id of "${id}" and operation ${_server.ReadOperations.Get}`; this.logger.error(errorMessage); throw _boom.default.notFound(errorMessage); } // move away from pulling data from _source in the future return alert.hits.hits[0]._source; } catch (error) { this.logger.error(`get threw an error: ${error}`); throw error; } } async getAlertSummary({ gte, lte, featureIds, filter, fixedInterval = '1m' }) { try { var _ref, _responseAlertSum$agg, _responseAlertSum$agg2, _buckets, _responseAlertSum$agg3, _responseAlertSum$agg4, _buckets2, _responseAlertSum$agg5, _responseAlertSum$agg6, _responseAlertSum$agg7; const indexToUse = await this.getAuthorizedAlertsIndices(featureIds); if ((0, _lodash.isEmpty)(indexToUse)) { throw _boom.default.badRequest('no featureIds were provided for getting alert summary'); } // first search for the alert by id, then use the alert info to check if user has access to it const responseAlertSum = await this.singleSearchAfterAndAudit({ index: (indexToUse !== null && indexToUse !== void 0 ? indexToUse : []).join(), operation: _server.ReadOperations.Get, aggs: { active_alerts_bucket: { date_histogram: { field: _ruleDataUtils.ALERT_TIME_RANGE, fixed_interval: fixedInterval, hard_bounds: { min: gte, max: lte }, extended_bounds: { min: gte, max: lte }, min_doc_count: 0 } }, recovered_alerts: { filter: { term: { [_ruleDataUtils.ALERT_STATUS]: _ruleDataUtils.ALERT_STATUS_RECOVERED } }, aggs: { container: { date_histogram: { field: _ruleDataUtils.ALERT_END, fixed_interval: fixedInterval, extended_bounds: { min: gte, max: lte }, min_doc_count: 0 } } } }, count: { terms: { field: _ruleDataUtils.ALERT_STATUS } } }, query: { bool: { filter: [{ range: { [_ruleDataUtils.ALERT_TIME_RANGE]: { gt: gte, lt: lte } } }, ...(filter ? filter : [])] } }, size: 0 }); let activeAlertCount = 0; let recoveredAlertCount = 0; ((_ref = (_responseAlertSum$agg = responseAlertSum.aggregations) === null || _responseAlertSum$agg === void 0 ? void 0 : (_responseAlertSum$agg2 = _responseAlertSum$agg.count) === null || _responseAlertSum$agg2 === void 0 ? void 0 : _responseAlertSum$agg2.buckets) !== null && _ref !== void 0 ? _ref : []).forEach(b => { if (b.key === _ruleDataUtils.ALERT_STATUS_ACTIVE) { activeAlertCount = b.doc_count; } else if (b.key === _ruleDataUtils.ALERT_STATUS_RECOVERED) { recoveredAlertCount = b.doc_count; } }); return { activeAlertCount, recoveredAlertCount, activeAlerts: (_buckets = (_responseAlertSum$agg3 = responseAlertSum.aggregations) === null || _responseAlertSum$agg3 === void 0 ? void 0 : (_responseAlertSum$agg4 = _responseAlertSum$agg3.active_alerts_bucket) === null || _responseAlertSum$agg4 === void 0 ? void 0 : _responseAlertSum$agg4.buckets) !== null && _buckets !== void 0 ? _buckets : [], recoveredAlerts: (_buckets2 = (_responseAlertSum$agg5 = responseAlertSum.aggregations) === null || _responseAlertSum$agg5 === void 0 ? void 0 : (_responseAlertSum$agg6 = _responseAlertSum$agg5.recovered_alerts) === null || _responseAlertSum$agg6 === void 0 ? void 0 : (_responseAlertSum$agg7 = _responseAlertSum$agg6.container) === null || _responseAlertSum$agg7 === void 0 ? void 0 : _responseAlertSum$agg7.buckets) !== null && _buckets2 !== void 0 ? _buckets2 : [] }; } catch (error) { this.logger.error(`getAlertSummary threw an error: ${error}`); throw error; } } async update({ id, status, _version, index }) { try { const alert = await this.singleSearchAfterAndAudit({ id, index, operation: _server.WriteOperations.Update }); if (alert == null || alert.hits.hits.length === 0) { const errorMessage = `Unable to retrieve alert details for alert with id of "${id}" and operation ${_server.ReadOperations.Get}`; this.logger.error(errorMessage); throw _boom.default.notFound(errorMessage); } const fieldToUpdate = this.getAlertStatusFieldUpdate(alert === null || alert === void 0 ? void 0 : alert.hits.hits[0]._source, status); const response = await this.esClient.update({ ...(0, _securitysolutionEsUtils.decodeVersion)(_version), id, index, body: { doc: { ...fieldToUpdate } }, refresh: 'wait_for' }); return { ...response, _version: (0, _securitysolutionEsUtils.encodeHitVersion)(response) }; } catch (error) { this.logger.error(`update threw an error: ${error}`); throw error; } } async bulkUpdate({ ids, query, index, status }) { // rejects at the route level if more than 1000 id's are passed in if (ids != null) { const alerts = ids.map(id => ({ id, index })); return this.mgetAlertsAuditOperateStatus({ alerts, status, operation: _server.WriteOperations.Update }); } else if (query != null) { try { // execute search after with query + authorization filter // audit results of that query const fetchAndAuditResponse = await this.queryAndAuditAllAlerts({ query, index, operation: _server.WriteOperations.Update }); if (!(fetchAndAuditResponse !== null && fetchAndAuditResponse !== void 0 && fetchAndAuditResponse.auditedAlerts)) { throw _boom.default.forbidden('Failed to audit alerts'); } // executes updateByQuery with query + authorization filter // used in the queryAndAuditAllAlerts function const result = await this.esClient.updateByQuery({ index, conflicts: 'proceed', body: { script: { source: `if (ctx._source['${_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS}'] != null) { ctx._source['${_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS}'] = '${status}' } if (ctx._source.signal != null && ctx._source.signal.status != null) { ctx._source.signal.status = '${status}' }`, lang: 'painless' }, query: fetchAndAuditResponse.authorizedQuery }, ignore_unavailable: true }); return result; } catch (err) { this.logger.error(`bulkUpdate threw an error: ${err}`); throw err; } } else { throw _boom.default.badRequest('no ids or query were provided for updating'); } } /** * This function updates the case ids of multiple alerts per index. * It is supposed to be used only by Cases. * Cases implements its own RBAC. By using this function directly * Cases RBAC is bypassed. * Plugins that want to attach alerts to a case should use the * cases client that does all the necessary cases RBAC checks * before updating the alert with the case ids. */ async bulkUpdateCases({ alerts, caseIds }) { if (alerts.length === 0) { throw _boom.default.badRequest('You need to define at least one alert to update case ids'); } /** * We do this check to avoid any mget calls or authorization checks. * The check below does not ensure that an alert may exceed the limit. * We need to also throw in case alert.caseIds + caseIds > MAX_CASES_PER_ALERT. * The validateTotalCasesPerAlert function ensures that. */ if (caseIds.length > _ruleDataUtils.MAX_CASES_PER_ALERT) { throw _boom.default.badRequest(`You cannot attach more than ${_ruleDataUtils.MAX_CASES_PER_ALERT} cases to an alert`); } return this.mgetAlertsAuditOperate({ alerts, /** * A user with read access to an alert and write access to a case should be able to link * the case to the alert (update the alert's data to include the case ids). * For that reason, the operation is a read operation. */ operation: _server.ReadOperations.Get, fieldToUpdate: source => this.getAlertCaseIdsFieldUpdate(source, caseIds), validate: source => this.validateTotalCasesPerAlert(source, caseIds) }); } async removeCaseIdFromAlerts({ caseId, alerts }) { /** * We intentionally do not perform any authorization * on the alerts. Users should be able to remove * cases from alerts when deleting a case or an * attachment */ try { if (alerts.length === 0) { return; } const painlessScript = `if (ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'] != null) { if (ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'].contains('${caseId}')) { int index = ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'].indexOf('${caseId}'); ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'].remove(index); } }`; const bulkUpdateRequest = []; for (const alert of alerts) { bulkUpdateRequest.push({ update: { _index: alert.index, _id: alert.id } }, { script: { source: painlessScript, lang: 'painless' } }); } await this.esClient.bulk({ refresh: 'wait_for', body: bulkUpdateRequest }); } catch (error) { this.logger.error(`Error removing case ${caseId} from alerts: ${error}`); throw error; } } async removeCaseIdsFromAllAlerts({ caseIds }) { /** * We intentionally do not perform any authorization * on the alerts. Users should be able to remove * cases from alerts when deleting a case or an * attachment */ try { if (caseIds.length === 0) { return; } const index = `${this.ruleDataService.getResourcePrefix()}-*`; const query = `${_ruleDataUtils.ALERT_CASE_IDS}: (${caseIds.join(' or ')})`; const esQuery = (0, _esQuery.buildEsQuery)(undefined, { query, language: 'kuery' }, []); const SCRIPT_PARAMS_ID = 'caseIds'; const painlessScript = `if (ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'] != null && ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}'].length > 0 && params['${SCRIPT_PARAMS_ID}'] != null && params['${SCRIPT_PARAMS_ID}'].length > 0) { List storedCaseIds = ctx._source['${_ruleDataUtils.ALERT_CASE_IDS}']; List caseIdsToRemove = params['${SCRIPT_PARAMS_ID}']; for (int i=0; i < caseIdsToRemove.length; i++) { if (storedCaseIds.contains(caseIdsToRemove[i])) { int index = storedCaseIds.indexOf(caseIdsToRemove[i]); storedCaseIds.remove(index); } } }`; await this.esClient.updateByQuery({ index, conflicts: 'proceed', body: { script: { source: painlessScript, lang: 'painless', params: { caseIds } }, query: esQuery }, ignore_unavailable: true }); } catch (err) { this.logger.error(`Failed removing ${caseIds} from all alerts: ${err}`); throw err; } } async find({ aggs, featureIds, index, query, search_after: searchAfter, size, sort, track_total_hits: trackTotalHits, _source }) { try { let indexToUse = index; if (featureIds && !(0, _lodash.isEmpty)(featureIds)) { const tempIndexToUse = await this.getAuthorizedAlertsIndices(featureIds); if (!(0, _lodash.isEmpty)(tempIndexToUse)) { indexToUse = (tempIndexToUse !== null && tempIndexToUse !== void 0 ? tempIndexToUse : []).join(); } } // first search for the alert by id, then use the alert info to check if user has access to it const alertsSearchResponse = await this.singleSearchAfterAndAudit({ query, aggs, _source, track_total_hits: trackTotalHits, size, index: indexToUse, operation: _server.ReadOperations.Find, sort, lastSortIds: searchAfter }); if (alertsSearchResponse == null) { const errorMessage = `Unable to retrieve alert details for alert with query and operation ${_server.ReadOperations.Find}`; this.logger.error(errorMessage); throw _boom.default.notFound(errorMessage); } return alertsSearchResponse; } catch (error) { this.logger.error(`find threw an error: ${error}`); throw error; } } async getAuthorizedAlertsIndices(featureIds) { try { // ATTENTION FUTURE DEVELOPER when you are a super user the augmentedRuleTypes.authorizedRuleTypes will // return all of the features that you can access and does not care about your featureIds const augmentedRuleTypes = await this.authorization.getAugmentedRuleTypesWithAuthorization(featureIds, [_server.ReadOperations.Find, _server.ReadOperations.Get, _server.WriteOperations.Update], _server.AlertingAuthorizationEntity.Alert); // As long as the user can read a minimum of one type of rule type produced by the provided feature, // the user should be provided that features' alerts index. // Limiting which alerts that user can read on that index will be done via the findAuthorizationFilter const authorizedFeatures = new Set(); for (const ruleType of augmentedRuleTypes.authorizedRuleTypes) { authorizedFeatures.add(ruleType.producer); } const validAuthorizedFeatures = Array.from(authorizedFeatures).filter(feature => featureIds.includes(feature) && (0, _ruleDataUtils.isValidFeatureId)(feature)); const toReturn = validAuthorizedFeatures.map(feature => { var _index$getPrimaryAlia, _this$spaceId; const index = this.ruleDataService.findIndexByFeature(feature, _rule_data_plugin_service.Dataset.alerts); if (index == null) { throw new Error(`This feature id ${feature} should be associated to an alert index`); } return (_index$getPrimaryAlia = index === null || index === void 0 ? void 0 : index.getPrimaryAlias(feature === _ruleDataUtils.AlertConsumers.SIEM ? (_this$spaceId = this.spaceId) !== null && _this$spaceId !== void 0 ? _this$spaceId : '*' : '*')) !== null && _index$getPrimaryAlia !== void 0 ? _index$getPrimaryAlia : ''; }); return toReturn; } catch (exc) { const errMessage = `getAuthorizedAlertsIndices failed to get authorized rule types: ${exc}`; this.logger.error(errMessage); throw _boom.default.failedDependency(errMessage); } } async getFeatureIdsByRegistrationContexts(RegistrationContexts) { try { const featureIds = this.ruleDataService.findFeatureIdsByRegistrationContexts(RegistrationContexts); if (featureIds.length > 0) { // ATTENTION FUTURE DEVELOPER when you are a super user the augmentedRuleTypes.authorizedRuleTypes will // return all of the features that you can access and does not care about your featureIds const augmentedRuleTypes = await this.authorization.getAugmentedRuleTypesWithAuthorization(featureIds, [_server.ReadOperations.Find, _server.ReadOperations.Get, _server.WriteOperations.Update], _server.AlertingAuthorizationEntity.Alert); // As long as the user can read a minimum of one type of rule type produced by the provided feature, // the user should be provided that features' alerts index. // Limiting which alerts that user can read on that index will be done via the findAuthorizationFilter const authorizedFeatures = new Set(); for (const ruleType of augmentedRuleTypes.authorizedRuleTypes) { authorizedFeatures.add(ruleType.producer); } const validAuthorizedFeatures = Array.from(authorizedFeatures).filter(feature => featureIds.includes(feature) && (0, _ruleDataUtils.isValidFeatureId)(feature)); return validAuthorizedFeatures; } return featureIds; } catch (exc) { const errMessage = `getFeatureIdsByRegistrationContexts failed to get feature ids: ${exc}`; this.logger.error(errMessage); throw _boom.default.failedDependency(errMessage); } } async getBrowserFields({ indices, metaFields, allowNoIndex }) { const indexPatternsFetcherAsInternalUser = new _server2.IndexPatternsFetcher(this.esClient); const { fields } = await indexPatternsFetcherAsInternalUser.getFieldsForWildcard({ pattern: indices, metaFields, fieldCapsOptions: { allow_no_indices: allowNoIndex } }); return { browserFields: (0, _browser_fields.fieldDescriptorToBrowserFieldMapper)(fields), fields }; } async getAADFields({ ruleTypeId }) { var _indices$filter; const { producer, fieldsForAAD = [] } = this.getRuleType(ruleTypeId); const indices = await this.getAuthorizedAlertsIndices([producer]); const o11yIndices = (_indices$filter = indices === null || indices === void 0 ? void 0 : indices.filter(index => index.startsWith('.alerts-observability'))) !== null && _indices$filter !== void 0 ? _indices$filter : []; const indexPatternsFetcherAsInternalUser = new _server2.IndexPatternsFetcher(this.esClient); const { fields } = await indexPatternsFetcherAsInternalUser.getFieldsForWildcard({ pattern: o11yIndices, metaFields: ['_id', '_index'], fieldCapsOptions: { allow_no_indices: true }, fields: [...fieldsForAAD, 'kibana.*'] }); return fields; } } exports.AlertsClient = AlertsClient;