diff --git a/thehive/app/org/thp/thehive/services/AuditSrv.scala b/thehive/app/org/thp/thehive/services/AuditSrv.scala index 259c751da9..806e20ca47 100644 --- a/thehive/app/org/thp/thehive/services/AuditSrv.scala +++ b/thehive/app/org/thp/thehive/services/AuditSrv.scala @@ -176,11 +176,6 @@ class AuditSrv @Inject() ( def delete(entity: E with Entity, context: C with Entity)(implicit graph: Graph, authContext: AuthContext): Try[Unit] = auditSrv.create(Audit(Audit.delete, entity, None), context, None) - def merge(entity: E with Entity, destination: C with Entity, details: Option[JsObject] = None)(implicit - graph: Graph, - authContext: AuthContext - ): Try[Unit] = - auditSrv.create(Audit(Audit.merge, destination, details.map(_.toString())), destination, Some(destination)) } class SelfContextObjectAudit[E <: Product] { @@ -197,6 +192,12 @@ class AuditSrv @Inject() ( authContext: AuthContext ): Try[Unit] = auditSrv.create(Audit(Audit.delete, entity, details.map(_.toString())), context, None) + + def merge(entity: E with Entity, details: Option[JsObject] = None)(implicit + graph: Graph, + authContext: AuthContext + ): Try[Unit] = + auditSrv.create(Audit(Audit.merge, entity, details.map(_.toString())), entity, Some(entity)) } class UserAudit extends SelfContextObjectAudit[User] { diff --git a/thehive/app/org/thp/thehive/services/CaseSrv.scala b/thehive/app/org/thp/thehive/services/CaseSrv.scala index 582f75a26c..27304f1520 100644 --- a/thehive/app/org/thp/thehive/services/CaseSrv.scala +++ b/thehive/app/org/thp/thehive/services/CaseSrv.scala @@ -352,61 +352,67 @@ class CaseSrv @Inject() ( } def merge(cases: Seq[Case with Entity])(implicit graph: Graph, authContext: AuthContext): Try[RichCase] = - if (cases.size > 1 && canMerge(cases)) { - val mergedCase = Case( - cases.map(_.title).mkString(" / "), - cases.map(_.description).mkString("\n\n"), - cases.map(_.severity).max, - cases.map(_.startDate).min, - None, - cases.exists(_.flag), - cases.map(_.tlp).max, - cases.map(_.pap).max, - CaseStatus.Open, - cases.map(_.summary).fold(None)((s1, s2) => (s1 ++ s2).reduceOption(_ + "\n\n" + _)), - cases.flatMap(_.tags).distinct - ) + if (cases.size > 1 && canMerge(cases)) + auditSrv.mergeAudits { + val mergedCase = Case( + cases.map(_.title).mkString(" / "), + cases.map(_.description).mkString("\n\n"), + cases.map(_.severity).max, + cases.map(_.startDate).min, + None, + cases.exists(_.flag), + cases.map(_.tlp).max, + cases.map(_.pap).max, + CaseStatus.Open, + cases.map(_.summary).fold(None)((s1, s2) => (s1 ++ s2).reduceOption(_ + "\n\n" + _)), + cases.flatMap(_.tags).distinct + ) - val allProfilesOrgas: Seq[(Profile with Entity, Organisation with Entity)] = get(cases.head) - .shares - .project(_.by(_.profile).by(_.organisation)) - .toSeq - - for { - user <- userSrv.current.getOrFail("User") - currentOrga <- organisationSrv.current.getOrFail("Organisation") - richCase <- create(mergedCase, Some(user), currentOrga, Seq(), None, Seq()) - // Share case with all organisations except the one who created the merged case - _ <- - allProfilesOrgas - .filterNot(_._2._id == currentOrga._id) - .toTry(profileOrg => shareSrv.shareCase(owner = false, richCase.`case`, profileOrg._2, profileOrg._1)) - _ <- cases.toTry { c => - for { - - _ <- shareMergedCaseTasks(allProfilesOrgas.map(_._2), c, richCase.`case`) - _ <- shareMergedCaseObservables(allProfilesOrgas.map(_._2), c, richCase.`case`) - _ <- - get(c) - .alert - .update(_.caseId, richCase._id) - .toSeq - .toTry(alertSrv.alertCaseSrv.create(AlertCase(), _, richCase.`case`)) - _ <- - get(c) - .procedure - .toSeq - .toTry(caseProcedureSrv.create(CaseProcedure(), richCase.`case`, _)) - _ <- - get(c) - .richCustomFields - .toSeq - .toTry(c => createCustomField(richCase.`case`, EntityIdOrName(c.customField.name), c.value, c.order)) - } yield Success(()) - } - _ <- cases.toTry(super.delete(_)) - } yield richCase - } else + val allProfilesOrgas: Seq[(Profile with Entity, Organisation with Entity)] = get(cases.head) + .shares + .project(_.by(_.profile).by(_.organisation)) + .toSeq + + for { + user <- userSrv.current.getOrFail("User") + currentOrga <- organisationSrv.current.getOrFail("Organisation") + richCase <- create(mergedCase, Some(user), currentOrga, Seq(), None, Seq()) + // Share case with all organisations except the one who created the merged case + _ <- + allProfilesOrgas + .filterNot(_._2._id == currentOrga._id) + .toTry(profileOrg => shareSrv.shareCase(owner = false, richCase.`case`, profileOrg._2, profileOrg._1)) + _ <- cases.toTry { c => + for { + + _ <- shareMergedCaseTasks(allProfilesOrgas.map(_._2), c, richCase.`case`) + _ <- shareMergedCaseObservables(allProfilesOrgas.map(_._2), c, richCase.`case`) + _ <- + get(c) + .alert + .update(_.caseId, richCase._id) + .toSeq + .toTry(alertSrv.alertCaseSrv.create(AlertCase(), _, richCase.`case`)) + _ <- + get(c) + .procedure + .toSeq + .toTry(caseProcedureSrv.create(CaseProcedure(), richCase.`case`, _)) + _ <- + get(c) + .richCustomFields + .toSeq + .toTry(c => createCustomField(richCase.`case`, EntityIdOrName(c.customField.name), c.value, c.order)) + } yield Success(()) + } + _ <- cases.toTry(super.delete(_)) + } yield richCase + }(mergedCase => + auditSrv + .`case` + .merge(mergedCase.`case`, Some(Json.obj("cases" -> cases.map(c => Json.obj("_id" -> c._id, "number" -> c.number, "title" -> c.title))))) + ) + else Failure(BadRequestError("To be able to merge, cases must have same organisation / profile pair and user must be org-admin")) private def canMerge(cases: Seq[Case with Entity])(implicit graph: Graph, authContext: AuthContext): Boolean = { diff --git a/thehive/app/org/thp/thehive/services/notification/notifiers/Webhook.scala b/thehive/app/org/thp/thehive/services/notification/notifiers/Webhook.scala index 718d33a36d..c4b99340a3 100644 --- a/thehive/app/org/thp/thehive/services/notification/notifiers/Webhook.scala +++ b/thehive/app/org/thp/thehive/services/notification/notifiers/Webhook.scala @@ -86,7 +86,7 @@ class Webhook( object v0 extends AuditRenderer object v1 { - import org.thp.thehive.controllers.v0.Conversion._ + import org.thp.thehive.controllers.v1.Conversion._ def caseToJson: Traversal.V[Case] => Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]] = _.richCaseWithoutPerms.domainMap[JsObject](_.toJson.as[JsObject]) @@ -226,7 +226,7 @@ class Webhook( Json.obj( "operation" -> audit.action, "details" -> audit.details.fold[JsValue](JsObject.empty)(fixCustomFieldDetails(objectType, _)), - "objectType" -> fromObjectType(objectType), + "objectType" -> objectType, "objectId" -> audit.objectId, "base" -> audit.mainAction, "startDate" -> audit._createdAt,