From ae74ecf42c6179d4dd416717833c7e485252aa5b Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 14 Oct 2020 15:31:50 +0200 Subject: [PATCH] #1579 Add case filter and observable type count in alert similar case query --- ScalliGraph | 2 +- .../thehive/controllers/v0/AlertCtrl.scala | 2 +- .../thehive/controllers/v1/AlertCtrl.scala | 20 +++++++++++--- .../controllers/v1/AlertRenderer.scala | 5 ++-- thehive/app/org/thp/thehive/models/Case.scala | 2 +- .../org/thp/thehive/services/AlertSrv.scala | 26 ++++++++++++++----- .../thp/thehive/services/ObservableSrv.scala | 2 ++ 7 files changed, 44 insertions(+), 15 deletions(-) diff --git a/ScalliGraph b/ScalliGraph index b2a923dc4c..a6f82a382e 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit b2a923dc4c6d997ececf8d22c7b04abdbdeac40a +Subproject commit a6f82a382eb7191640a621591b20a072f96375c8 diff --git a/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala index 8677037f5a..85f54705d6 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala @@ -71,7 +71,7 @@ class AlertCtrl @Inject() ( def alertSimilarityRenderer(implicit authContext: AuthContext ): Traversal.V[Alert] => Traversal[JsArray, JList[JMap[String, Any]], Converter[JsArray, JList[JMap[String, Any]]]] = - _.similarCases + _.similarCases(None) .fold .domainMap { similarCases => JsArray { diff --git a/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala index 4ad779213c..333bf115d4 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala @@ -6,7 +6,7 @@ import javax.inject.{Inject, Named, Singleton} import org.thp.scalligraph.EntityIdOrName import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} import org.thp.scalligraph.models.Database -import org.thp.scalligraph.query.{ParamQuery, PropertyUpdater, PublicProperties, Query} +import org.thp.scalligraph.query._ import org.thp.scalligraph.traversal.TraversalOps._ import org.thp.scalligraph.traversal.{Converter, IteratorOutput, Traversal} import org.thp.thehive.controllers.v1.Conversion._ @@ -20,6 +20,9 @@ import org.thp.thehive.services._ import play.api.libs.json.{JsValue, Json} import play.api.mvc.{Action, AnyContent, Results} +import scala.reflect.runtime.{universe => ru} + +case class SimilarCaseFilter() @Singleton class AlertCtrl @Inject() ( entrypoint: Entrypoint, @@ -51,12 +54,23 @@ class AlertCtrl @Inject() ( ) ) override val outputQuery: Query = Query.output[RichAlert, Traversal.V[Alert]](_.richAlert) + val caseFilterParser: FieldsParser[Option[InputQuery[Traversal.Unk, Traversal.Unk]]] = + FilterQuery.default(db, properties.`case`).paramParser(ru.typeOf[Traversal.V[Case]]).optional.on("caseFilter") override val extraQueries: Seq[ParamQuery[_]] = Seq( Query[Traversal.V[Alert], Traversal.V[Observable]]("observables", (alertSteps, _) => alertSteps.observables), Query[Traversal.V[Alert], Traversal.V[Case]]("case", (alertSteps, _) => alertSteps.`case`), - Query[Traversal.V[Alert], Traversal[JsValue, JMap[String, Any], Converter[JsValue, JMap[String, Any]]]]( + Query.withParam[Option[InputQuery[Traversal.Unk, Traversal.Unk]], Traversal.V[Alert], Traversal[ + JsValue, + JMap[String, Any], + Converter[JsValue, JMap[String, Any]] + ]]( "similarCases", - (alertSteps, authContext) => alertSteps.similarCases(authContext).domainMap(Json.toJson(_)) + caseFilterParser, + { (maybeCaseFilterQuery, alertSteps, authContext) => + val maybeCaseFilter: Option[Traversal.V[Case] => Traversal.V[Case]] = + maybeCaseFilterQuery.map(f => cases => f(db, properties.`case`, ru.typeOf[Traversal.V[Case]], cases.cast, authContext).cast) + alertSteps.similarCases(maybeCaseFilter)(authContext).domainMap(Json.toJson(_)) + } ) ) diff --git a/thehive/app/org/thp/thehive/controllers/v1/AlertRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/AlertRenderer.scala index 83a3563834..23e0682d1b 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/AlertRenderer.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/AlertRenderer.scala @@ -18,7 +18,8 @@ trait AlertRenderer { "similarObservableCount" -> similarStats.observable._1, "observableCount" -> similarStats.observable._2, "similarIocCount" -> similarStats.ioc._1, - "iocCount" -> similarStats.ioc._2 + "iocCount" -> similarStats.ioc._2, + "observableTypes" -> similarStats.types ) } def similarCasesStats(implicit @@ -35,7 +36,7 @@ trait AlertRenderer { else if (x._2.ioc._2 > y._2.ioc._2) -1 else if (x._2.ioc._2 < y._2.ioc._2) 1 else 0 - _.similarCases.fold.domainMap(sc => JsArray(sc.sorted.map(Json.toJson(_)))) + _.similarCases(None).fold.domainMap(sc => JsArray(sc.sorted.map(Json.toJson(_)))) } def alertStatsRenderer[D, G, C <: Converter[D, G]](extraData: Set[String])(implicit diff --git a/thehive/app/org/thp/thehive/models/Case.scala b/thehive/app/org/thp/thehive/models/Case.scala index e15367fc82..fa18c888fe 100644 --- a/thehive/app/org/thp/thehive/models/Case.scala +++ b/thehive/app/org/thp/thehive/models/Case.scala @@ -162,4 +162,4 @@ object RichCase { } } -case class SimilarStats(observable: (Int, Int), ioc: (Int, Int)) +case class SimilarStats(observable: (Int, Int), ioc: (Int, Int), types: Map[String, Long]) diff --git a/thehive/app/org/thp/thehive/services/AlertSrv.scala b/thehive/app/org/thp/thehive/services/AlertSrv.scala index a91be8df3d..dc5edcdaa7 100644 --- a/thehive/app/org/thp/thehive/services/AlertSrv.scala +++ b/thehive/app/org/thp/thehive/services/AlertSrv.scala @@ -369,12 +369,14 @@ object AlertOps { .count .choose(_.is(P.gt(0)), onTrue = true, onFalse = false) - def similarCases(implicit + def similarCases(maybeCaseFilter: Option[Traversal.V[Case] => Traversal.V[Case]])(implicit authContext: AuthContext - ): Traversal[(RichCase, SimilarStats), JMap[String, Any], Converter[(RichCase, SimilarStats), JMap[String, Any]]] = - observables + ): Traversal[(RichCase, SimilarStats), JMap[String, Any], Converter[(RichCase, SimilarStats), JMap[String, Any]]] = { + val similarObservables = observables .similar .visible + maybeCaseFilter + .fold(similarObservables)(caseFilter => similarObservables.filter(o => caseFilter(o.`case`))) .group(_.by(_.`case`)) .unfold .project( @@ -384,17 +386,27 @@ object AlertOps { _.by(_.richCaseWithoutPerms) .by((_: Traversal.V[Case]).observables.groupCount(_.byValue(_.ioc))) ) - ).by(_.selectValues.unfold.groupCount(_.byValue(_.ioc))) + ) + .by( + _.selectValues + .unfold + .project( + _.by(_.groupCount(_.byValue(_.ioc))) + .by(_.groupCount(_.by(_.typeName))) + ) + ) ) .domainMap { - case ((richCase, obsStats), similarStats) => + case ((richCase, obsStats), (iocStats, observableTypeStats)) => val obsStatsMap = obsStats.mapValues(_.toInt) - val similarStatsMap = similarStats.mapValues(_.toInt) + val similarStatsMap = iocStats.mapValues(_.toInt) richCase -> SimilarStats( similarStatsMap.values.sum -> obsStatsMap.values.sum, - similarStatsMap.getOrElse(true, 0) -> obsStatsMap.getOrElse(true, 0) + similarStatsMap.getOrElse(true, 0) -> obsStatsMap.getOrElse(true, 0), + observableTypeStats ) } + } def alertUserOrganisation( permission: Permission diff --git a/thehive/app/org/thp/thehive/services/ObservableSrv.scala b/thehive/app/org/thp/thehive/services/ObservableSrv.scala index 6a9ca76fe3..fa61aa98e0 100644 --- a/thehive/app/org/thp/thehive/services/ObservableSrv.scala +++ b/thehive/app/org/thp/thehive/services/ObservableSrv.scala @@ -356,6 +356,8 @@ object ObservableOps { def observableType: Traversal.V[ObservableType] = traversal.out[ObservableObservableType].v[ObservableType] + def typeName: Traversal[String, String, Converter[String, String]] = observableType.value(_.name) + def shares: Traversal.V[Share] = traversal.in[ShareObservable].v[Share] def share(implicit authContext: AuthContext): Traversal.V[Share] = share(authContext.organisation)