"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.CaseCommentModel = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _boom = _interopRequireDefault(require("@hapi/boom")); var _domain = require("../../../common/types/domain"); var _constants = require("../../../common/constants"); var _error = require("../error"); var _limiter_checker = require("../limiter_checker"); var _utils = require("../utils"); var _runtime_types = require("../../../common/api/runtime_types"); /* * 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. */ /** * This class represents a case that can have a comment attached to it. */ class CaseCommentModel { constructor(caseInfo, params) { (0, _defineProperty2.default)(this, "params", void 0); (0, _defineProperty2.default)(this, "caseInfo", void 0); this.caseInfo = caseInfo; this.params = params; } static async create(id, options) { const savedObject = await options.services.caseService.getCase({ id }); return new CaseCommentModel(savedObject, options); } get savedObject() { return this.caseInfo; } /** * Update a comment and update the corresponding case's update_at and updated_by fields. */ async updateComment({ updateRequest, updatedAt, owner }) { try { const { id, version, ...queryRestAttributes } = updateRequest; const options = { version, /** * This is to handle a scenario where an update occurs for an attachment framework style comment. * The code that extracts the reference information from the attributes doesn't know about the reference to the case * and therefore will accidentally remove that reference and we'll lose the connection between the comment and the * case. */ references: [...this.buildRefsToCase()], refresh: false }; if (queryRestAttributes.type === _domain.AttachmentType.user && queryRestAttributes !== null && queryRestAttributes !== void 0 && queryRestAttributes.comment) { const currentComment = await this.params.services.attachmentService.getter.get({ attachmentId: id }); const updatedReferences = (0, _utils.getOrUpdateLensReferences)(this.params.lensEmbeddableFactory, queryRestAttributes.comment, currentComment); /** * The call to getOrUpdateLensReferences already handles retrieving the reference to the case and ensuring that is * also included here so it's ok to overwrite what was set before. */ options.references = updatedReferences; } const [comment, commentableCase] = await Promise.all([this.params.services.attachmentService.update({ attachmentId: id, updatedAttributes: { ...queryRestAttributes, updated_at: updatedAt, updated_by: this.params.user }, options }), this.updateCaseUserAndDateSkipRefresh(updatedAt)]); await commentableCase.createUpdateCommentUserAction(comment, updateRequest, owner); return commentableCase; } catch (error) { throw (0, _error.createCaseError)({ message: `Failed to update comment in commentable case, case id: ${this.caseInfo.id}: ${error}`, error, logger: this.params.logger }); } } async updateCaseUserAndDateSkipRefresh(date) { return this.updateCaseUserAndDate(date, false); } async updateCaseUserAndDate(date, refresh) { try { var _updatedCase$version; const updatedCase = await this.params.services.caseService.patchCase({ originalCase: this.caseInfo, caseId: this.caseInfo.id, updatedAttributes: { updated_at: date, updated_by: { ...this.params.user } }, version: this.caseInfo.version, refresh }); return this.newObjectWithInfo({ ...this.caseInfo, attributes: { ...this.caseInfo.attributes, ...updatedCase.attributes }, version: (_updatedCase$version = updatedCase.version) !== null && _updatedCase$version !== void 0 ? _updatedCase$version : this.caseInfo.version }); } catch (error) { throw (0, _error.createCaseError)({ message: `Failed to update commentable case, case id: ${this.caseInfo.id}: ${error}`, error, logger: this.params.logger }); } } newObjectWithInfo(caseInfo) { return new CaseCommentModel(caseInfo, this.params); } async createUpdateCommentUserAction(comment, updateRequest, owner) { const { id, version, ...queryRestAttributes } = updateRequest; await this.params.services.userActionService.creator.createUserAction({ type: _domain.UserActionTypes.comment, action: _domain.UserActionActions.update, caseId: this.caseInfo.id, attachmentId: comment.id, payload: { attachment: queryRestAttributes }, user: this.params.user, owner }); } /** * Create a new comment on the appropriate case. This updates the case's updated_at and updated_by fields. */ async createComment({ createdDate, commentReq, id }) { try { await this.validateCreateCommentRequest([commentReq]); const attachmentsWithoutDuplicateAlerts = await this.filterDuplicatedAlerts([{ ...commentReq, id }]); if (attachmentsWithoutDuplicateAlerts.length === 0) { return this; } const { id: commentId, ...attachment } = attachmentsWithoutDuplicateAlerts[0]; const references = [...this.buildRefsToCase(), ...this.getCommentReferences(attachment)]; const [comment, commentableCase] = await Promise.all([this.params.services.attachmentService.create({ attributes: (0, _utils.transformNewComment)({ createdDate, ...attachment, ...this.params.user }), references, id, refresh: false }), this.updateCaseUserAndDateSkipRefresh(createdDate)]); await Promise.all([commentableCase.handleAlertComments([attachment]), this.createCommentUserAction(comment, attachment)]); return commentableCase; } catch (error) { throw (0, _error.createCaseError)({ message: `Failed creating a comment on a commentable case, case id: ${this.caseInfo.id}: ${error}`, error, logger: this.params.logger }); } } async filterDuplicatedAlerts(attachments) { /** * This function removes the elements in items that exist at the passed in positions. */ const removeItemsByPosition = (items, positionsToRemove) => items.filter((_, itemIndex) => !positionsToRemove.some(position => position === itemIndex)); const dedupedAlertAttachments = []; const idsAlreadySeen = new Set(); const alertsAttachedToCase = await this.params.services.attachmentService.getter.getAllAlertIds({ caseId: this.caseInfo.id }); attachments.forEach(attachment => { if (!(0, _utils.isCommentRequestTypeAlert)(attachment)) { dedupedAlertAttachments.push(attachment); return; } const { ids, indices } = (0, _utils.getIDsAndIndicesAsArrays)(attachment); const idPositionsThatAlreadyExistInCase = []; ids.forEach((id, index) => { if (alertsAttachedToCase.has(id) || idsAlreadySeen.has(id)) { idPositionsThatAlreadyExistInCase.push(index); } idsAlreadySeen.add(id); }); const alertIdsNotAlreadyAttachedToCase = removeItemsByPosition(ids, idPositionsThatAlreadyExistInCase); const alertIndicesNotAlreadyAttachedToCase = removeItemsByPosition(indices, idPositionsThatAlreadyExistInCase); if (alertIdsNotAlreadyAttachedToCase.length > 0 && alertIdsNotAlreadyAttachedToCase.length === alertIndicesNotAlreadyAttachedToCase.length) { dedupedAlertAttachments.push({ ...attachment, alertId: alertIdsNotAlreadyAttachedToCase, index: alertIndicesNotAlreadyAttachedToCase }); } }); return dedupedAlertAttachments; } getAlertAttachments(attachments) { return attachments.filter(attachment => attachment.type === _domain.AttachmentType.alert); } async validateCreateCommentRequest(req) { const alertAttachments = this.getAlertAttachments(req); const hasAlertsInRequest = alertAttachments.length > 0; if (hasAlertsInRequest && this.caseInfo.attributes.status === _domain.CaseStatuses.closed) { throw _boom.default.badRequest('Alert cannot be attached to a closed case'); } if (req.some(attachment => attachment.owner !== this.caseInfo.attributes.owner)) { throw _boom.default.badRequest('The owner field of the comment must match the case'); } const limitChecker = new _limiter_checker.AttachmentLimitChecker(this.params.services.attachmentService, this.params.fileService, this.caseInfo.id); await limitChecker.validate(req); } buildRefsToCase() { return [{ type: _constants.CASE_SAVED_OBJECT, name: `associated-${_constants.CASE_SAVED_OBJECT}`, id: this.caseInfo.id }]; } getCommentReferences(commentReq) { let references = []; if (commentReq.type === _domain.AttachmentType.user && commentReq !== null && commentReq !== void 0 && commentReq.comment) { const commentStringReferences = (0, _utils.getOrUpdateLensReferences)(this.params.lensEmbeddableFactory, commentReq.comment); references = [...references, ...commentStringReferences]; } return references; } async handleAlertComments(attachments) { const alertAttachments = this.getAlertAttachments(attachments); const alerts = (0, _utils.getAlertInfoFromComments)(alertAttachments); if (alerts.length > 0) { await this.params.services.alertsService.ensureAlertsAuthorized({ alerts }); await this.updateAlertsSchemaWithCaseInfo(alerts); if (this.caseInfo.attributes.settings.syncAlerts) { await this.updateAlertsStatus(alerts); } } } async updateAlertsStatus(alerts) { const alertsToUpdate = alerts.map(alert => ({ ...alert, status: this.caseInfo.attributes.status })); await this.params.services.alertsService.updateAlertsStatus(alertsToUpdate); } async updateAlertsSchemaWithCaseInfo(alerts) { await this.params.services.alertsService.bulkUpdateCases({ alerts, caseIds: [this.caseInfo.id] }); } async createCommentUserAction(comment, req) { await this.params.services.userActionService.creator.createUserAction({ type: _domain.UserActionTypes.comment, action: _domain.UserActionActions.create, caseId: this.caseInfo.id, attachmentId: comment.id, payload: { attachment: req }, user: this.params.user, owner: comment.attributes.owner }); } async bulkCreateCommentUserAction(attachments) { await this.params.services.userActionService.creator.bulkCreateAttachmentCreation({ caseId: this.caseInfo.id, attachments: attachments.map(({ id, ...attachment }) => ({ id, owner: attachment.owner, attachment })), user: this.params.user }); } formatForEncoding(totalComment) { var _this$caseInfo$versio; return { id: this.caseInfo.id, version: (_this$caseInfo$versio = this.caseInfo.version) !== null && _this$caseInfo$versio !== void 0 ? _this$caseInfo$versio : '0', totalComment, ...this.caseInfo.attributes }; } async encodeWithComments() { try { var _countAlertsForID; const comments = await this.params.services.caseService.getAllCaseComments({ id: this.caseInfo.id, options: { fields: [], page: 1, perPage: _constants.MAX_DOCS_PER_PAGE } }); const totalAlerts = (_countAlertsForID = (0, _utils.countAlertsForID)({ comments, id: this.caseInfo.id })) !== null && _countAlertsForID !== void 0 ? _countAlertsForID : 0; const caseResponse = { comments: (0, _utils.flattenCommentSavedObjects)(comments.saved_objects), totalAlerts, ...this.formatForEncoding(comments.total) }; return (0, _runtime_types.decodeOrThrow)(_domain.CaseRt)(caseResponse); } catch (error) { throw (0, _error.createCaseError)({ message: `Failed encoding the commentable case, case id: ${this.caseInfo.id}: ${error}`, error, logger: this.params.logger }); } } async bulkCreate({ attachments }) { try { await this.validateCreateCommentRequest(attachments); const attachmentWithoutDuplicateAlerts = await this.filterDuplicatedAlerts(attachments); if (attachmentWithoutDuplicateAlerts.length === 0) { return this; } const caseReference = this.buildRefsToCase(); const [newlyCreatedAttachments, commentableCase] = await Promise.all([this.params.services.attachmentService.bulkCreate({ attachments: attachmentWithoutDuplicateAlerts.map(({ id, ...attachment }) => { return { attributes: (0, _utils.transformNewComment)({ createdDate: new Date().toISOString(), ...attachment, ...this.params.user }), references: [...caseReference, ...this.getCommentReferences(attachment)], id }; }), refresh: false }), this.updateCaseUserAndDateSkipRefresh(new Date().toISOString())]); const savedObjectsWithoutErrors = newlyCreatedAttachments.saved_objects.filter(attachment => attachment.error == null); const attachmentsWithoutErrors = attachments.filter(attachment => savedObjectsWithoutErrors.some(so => so.id === attachment.id)); await Promise.all([commentableCase.handleAlertComments(attachmentsWithoutErrors), this.bulkCreateCommentUserAction(attachmentsWithoutErrors)]); return commentableCase; } catch (error) { throw (0, _error.createCaseError)({ message: `Failed bulk creating attachments on a commentable case, case id: ${this.caseInfo.id}: ${error}`, error, logger: this.params.logger }); } } } exports.CaseCommentModel = CaseCommentModel;