From d2f515336c982dafdea47bd8e6ac5a1b9a80ce89 Mon Sep 17 00:00:00 2001 From: To-om Date: Fri, 28 May 2021 15:44:23 +0200 Subject: [PATCH] #2010 Add query to list linked cases --- .../thp/thehive/controllers/v0/CaseCtrl.scala | 47 ++++++++++++------- .../thp/thehive/controllers/v1/CaseCtrl.scala | 18 ++++++- .../org/thp/thehive/services/CaseSrv.scala | 5 +- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala index 705cb35e95..ed3cd16585 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala @@ -4,10 +4,10 @@ import org.apache.tinkerpop.gremlin.process.traversal.P import org.thp.scalligraph._ import org.thp.scalligraph.controllers.{Entrypoint, FPathElem, FPathEmpty, FieldsParser} import org.thp.scalligraph.models.{Database, UMapping} -import org.thp.scalligraph.query._ import org.thp.scalligraph.query.PredicateOps._ +import org.thp.scalligraph.query._ import org.thp.scalligraph.traversal.TraversalOps._ -import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} +import org.thp.scalligraph.traversal.{Converter, IteratorOutput, Traversal} import org.thp.thehive.controllers.v0.Conversion._ import org.thp.thehive.dto.v0.{InputCase, InputTask} import org.thp.thehive.dto.v1.InputCustomFieldValue @@ -24,6 +24,7 @@ import org.thp.thehive.services._ import play.api.libs.json._ import play.api.mvc.{Action, AnyContent, Results} +import java.util.{Map => JMap} import javax.inject.{Inject, Named, Singleton} import scala.util.{Failure, Success} @@ -158,19 +159,21 @@ class CaseCtrl @Inject() ( def linkedCases(caseIdOrNumber: String): Action[AnyContent] = entrypoint("case link") - .authRoTransaction(db) { implicit request => implicit graph => - val relatedCases = caseSrv - .get(EntityIdOrName(caseIdOrNumber)) - .visible(organisationSrv) - .linkedCases - .map { - case (c, o) => - c.toJson.as[JsObject] + - ("linkedWith" -> o.toJson) + - ("linksCount" -> JsNumber(o.size)) - } - - Success(Results.Ok(JsArray(relatedCases))) + .auth { implicit request => + val src = db.source { implicit graph => + caseSrv + .get(EntityIdOrName(caseIdOrNumber)) + .visible(organisationSrv) + .linkedCases + .domainMap { + case (c, o) => + c.toJson.as[JsObject] + + ("linkedWith" -> o.toJson) + + ("linksCount" -> JsNumber(o.size)) + } + .toIterator + } + Success(Results.Ok.chunked(src.map(_.toString).intersperse("[", ",", "]"), Some("application/json"))) } } @@ -221,7 +224,19 @@ class PublicCase @Inject() ( ), Query[Traversal.V[Case], Traversal.V[User]]("assignableUsers", (caseSteps, authContext) => caseSteps.assignableUsers(authContext)), Query[Traversal.V[Case], Traversal.V[Organisation]]("organisations", (caseSteps, authContext) => caseSteps.organisations.visible(authContext)), - Query[Traversal.V[Case], Traversal.V[Alert]]("alerts", (caseSteps, authContext) => caseSteps.alert.visible(organisationSrv)(authContext)) + Query[Traversal.V[Case], Traversal.V[Alert]]("alerts", (caseSteps, authContext) => caseSteps.alert.visible(organisationSrv)(authContext)), + Query[Traversal.V[Case], Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]]]( + "linkedCases", + (caseSteps, authContext) => + caseSteps + .linkedCases(authContext) + .domainMap { + case (c, o) => + c.toJson.as[JsObject] + + ("linkedWith" -> o.toJson) + + ("linksCount" -> JsNumber(o.size)) + } + ) ) override val publicProperties: PublicProperties = PublicPropertyListBuilder[Case] diff --git a/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala index 04064ecfaf..a1c4a292b4 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala @@ -6,7 +6,7 @@ import org.thp.scalligraph.models.Database import org.thp.scalligraph.query.{ParamQuery, PropertyUpdater, PublicProperties, Query} import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem} import org.thp.scalligraph.traversal.TraversalOps._ -import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} +import org.thp.scalligraph.traversal.{Converter, IteratorOutput, Traversal} import org.thp.scalligraph.{EntityIdOrName, RichOptionTry, RichSeq} import org.thp.thehive.controllers.v1.Conversion._ import org.thp.thehive.dto.v1.{InputCase, InputTask} @@ -20,8 +20,10 @@ import org.thp.thehive.services.ShareOps._ import org.thp.thehive.services.TaskOps._ import org.thp.thehive.services.UserOps._ import org.thp.thehive.services._ +import play.api.libs.json.{JsNumber, JsObject} import play.api.mvc.{Action, AnyContent, Results} +import java.util.{Map => JMap} import javax.inject.{Inject, Singleton} @Singleton @@ -88,7 +90,19 @@ class CaseCtrl @Inject() ( alertSrv.startTraversal(caseSteps.graph).has(_.caseId, P.within(caseSteps._id.toSeq: _*)).visible(organisationSrv)(authContext) ), Query[Traversal.V[Case], Traversal.V[Share]]("shares", (caseSteps, authContext) => caseSteps.shares.visible(authContext)), - Query[Traversal.V[Case], Traversal.V[Procedure]]("procedures", (caseSteps, _) => caseSteps.procedure) + Query[Traversal.V[Case], Traversal.V[Procedure]]("procedures", (caseSteps, _) => caseSteps.procedure), + Query[Traversal.V[Case], Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]]]( + "linkedCases", + (caseSteps, authContext) => + caseSteps + .linkedCases(authContext) + .domainMap { + case (c, o) => + c.toJson.as[JsObject] + + ("linkedWith" -> o.toJson) + + ("linksCount" -> JsNumber(o.size)) + } + ) ) def create: Action[AnyContent] = diff --git a/thehive/app/org/thp/thehive/services/CaseSrv.scala b/thehive/app/org/thp/thehive/services/CaseSrv.scala index 5c2e9e1179..17ecd57d73 100644 --- a/thehive/app/org/thp/thehive/services/CaseSrv.scala +++ b/thehive/app/org/thp/thehive/services/CaseSrv.scala @@ -590,7 +590,9 @@ object CaseOps { // .in[AuditContext] // .v[Audit] - def linkedCases(implicit authContext: AuthContext): Seq[(RichCase, Seq[RichObservable])] = { + def linkedCases(implicit + authContext: AuthContext + ): Traversal[(RichCase, Seq[RichObservable]), JMap[String, Any], Converter[(RichCase, Seq[RichObservable]), JMap[String, Any]]] = { val originCaseLabel = StepLabel.v[Case] val observableLabel = StepLabel.v[Observable] // TODO add similarity on attachment traversal @@ -608,7 +610,6 @@ object CaseOps { .group(_.by, _.by(_.select(observableLabel).richObservable.fold)) .unfold .project(_.by(_.selectKeys.richCase).by(_.selectValues)) - .toSeq } def isShared: Traversal[Boolean, Boolean, Identity[Boolean]] =