From 4954e2c14d23e691548df98e2a8fab985a380fed Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 2 Sep 2020 11:55:28 +0200 Subject: [PATCH] #1499 Merge tags on existing observables when importing alert --- .../org/thp/thehive/models/Observable.scala | 19 ++--- .../org/thp/thehive/services/AlertSrv.scala | 81 ++++++++++++------- .../thp/thehive/services/ObservableSrv.scala | 2 +- .../thp/thehive/services/AlertSrvTest.scala | 10 ++- .../test/resources/data/AlertObservable.json | 3 +- thehive/test/resources/data/Observable.json | 9 ++- .../test/resources/data/ObservableData.json | 5 +- .../data/ObservableObservableType.json | 5 +- .../test/resources/data/ObservableTag.json | 5 +- 9 files changed, 87 insertions(+), 52 deletions(-) diff --git a/thehive/app/org/thp/thehive/models/Observable.scala b/thehive/app/org/thp/thehive/models/Observable.scala index d3f4df1ea6..5ad3524648 100644 --- a/thehive/app/org/thp/thehive/models/Observable.scala +++ b/thehive/app/org/thp/thehive/models/Observable.scala @@ -30,15 +30,16 @@ case class RichObservable( extensions: Seq[KeyValue with Entity], reportTags: Seq[ReportTag with Entity] ) { - def _id: String = observable._id - def _createdBy: String = observable._createdBy - def _updatedBy: Option[String] = observable._updatedBy - def _createdAt: Date = observable._createdAt - def _updatedAt: Option[Date] = observable._updatedAt - def message: Option[String] = observable.message - def tlp: Int = observable.tlp - def ioc: Boolean = observable.ioc - def sighted: Boolean = observable.sighted + def _id: String = observable._id + def _createdBy: String = observable._createdBy + def _updatedBy: Option[String] = observable._updatedBy + def _createdAt: Date = observable._createdAt + def _updatedAt: Option[Date] = observable._updatedAt + def message: Option[String] = observable.message + def tlp: Int = observable.tlp + def ioc: Boolean = observable.ioc + def sighted: Boolean = observable.sighted + def dataOrAttachment: Either[Data with Entity, Attachment with Entity] = data.toLeft(attachment.get) } @DefineIndex(IndexType.unique, "data") diff --git a/thehive/app/org/thp/thehive/services/AlertSrv.scala b/thehive/app/org/thp/thehive/services/AlertSrv.scala index 58eb721421..150b82c1c3 100644 --- a/thehive/app/org/thp/thehive/services/AlertSrv.scala +++ b/thehive/app/org/thp/thehive/services/AlertSrv.scala @@ -33,8 +33,8 @@ class AlertSrv @Inject() ( caseTemplateSrv: CaseTemplateSrv, observableSrv: ObservableSrv, auditSrv: AuditSrv -)( - implicit @Named("with-thehive-schema") db: Database +)(implicit + @Named("with-thehive-schema") db: Database ) extends VertexSrv[Alert] { val alertTagSrv = new EdgeSrv[AlertTag, Alert, Tag] @@ -44,10 +44,11 @@ class AlertSrv @Inject() ( val alertCaseTemplateSrv = new EdgeSrv[AlertCaseTemplate, Alert, CaseTemplate] val alertObservableSrv = new EdgeSrv[AlertObservable, Alert, Observable] - override def get(idOrSource: String)(implicit graph: Graph): Traversal.V[Alert] = idOrSource.split(';') match { - case Array(tpe, source, sourceRef) => startTraversal.getBySourceId(tpe, source, sourceRef) - case _ => super.getByIds(idOrSource) - } + override def get(idOrSource: String)(implicit graph: Graph): Traversal.V[Alert] = + idOrSource.split(';') match { + case Array(tpe, source, sourceRef) => startTraversal.getBySourceId(tpe, source, sourceRef) + case _ => super.getByIds(idOrSource) + } def create( alert: Alert, @@ -55,8 +56,8 @@ class AlertSrv @Inject() ( tagNames: Set[String], customFields: Map[String, Option[Any]], caseTemplate: Option[CaseTemplate with Entity] - )( - implicit graph: Graph, + )(implicit + graph: Graph, authContext: AuthContext ): Try[RichAlert] = tagNames.toTry(tagSrv.getOrCreate).flatMap(create(alert, organisation, _, customFields, caseTemplate)) @@ -67,8 +68,8 @@ class AlertSrv @Inject() ( tags: Seq[Tag with Entity], customFields: Map[String, Option[Any]], caseTemplate: Option[CaseTemplate with Entity] - )( - implicit graph: Graph, + )(implicit + graph: Graph, authContext: AuthContext ): Try[RichAlert] = { val alertAlreadyExist = organisationSrv.get(organisation).alerts.getBySourceId(alert.`type`, alert.source, alert.sourceRef).getCount @@ -142,8 +143,8 @@ class AlertSrv @Inject() ( auditSrv.observableInAlert.delete(observable, Some(alert)) } - def addObservable(alert: Alert with Entity, richObservable: RichObservable)( - implicit graph: Graph, + def addObservable(alert: Alert with Entity, richObservable: RichObservable)(implicit + graph: Graph, authContext: AuthContext ): Try[Unit] = { val alreadyExistInThatCase = observableSrv @@ -172,8 +173,8 @@ class AlertSrv @Inject() ( ccfe <- alertCustomFieldSrv.create(ccf, alert, cf) } yield RichCustomField(cf, ccfe) - def setOrCreateCustomField(alert: Alert with Entity, customFieldName: String, value: Option[Any])( - implicit graph: Graph, + def setOrCreateCustomField(alert: Alert with Entity, customFieldName: String, value: Option[Any])(implicit + graph: Graph, authContext: AuthContext ): Try[Unit] = { val cfv = get(alert).customFields(customFieldName) @@ -226,15 +227,16 @@ class AlertSrv @Inject() ( _ <- auditSrv.alert.update(alert, Json.obj("follow" -> false)) } yield () - def createCase(alert: RichAlert, user: Option[User with Entity], organisation: Organisation with Entity)( - implicit graph: Graph, + def createCase(alert: RichAlert, user: Option[User with Entity], organisation: Organisation with Entity)(implicit + graph: Graph, authContext: AuthContext ): Try[RichCase] = for { - caseTemplate <- alert - .caseTemplate - .map(caseTemplateSrv.get(_).richCaseTemplate.getOrFail("CaseTemplate")) - .flip + caseTemplate <- + alert + .caseTemplate + .map(caseTemplateSrv.get(_).richCaseTemplate.getOrFail("CaseTemplate")) + .flip customField = alert.customFields.map(f => (f.name, f.value, f.order)) case0 = Case( number = 0, @@ -274,8 +276,8 @@ class AlertSrv @Inject() ( _ <- auditSrv.alertToCase.merge(alert, c) } yield c - def importObservables(alert: Alert with Entity, `case`: Case with Entity)( - implicit graph: Graph, + def importObservables(alert: Alert with Entity, `case`: Case with Entity)(implicit + graph: Graph, authContext: AuthContext ): Try[Unit] = get(alert) @@ -286,7 +288,23 @@ class AlertSrv @Inject() ( observableSrv .duplicate(richObservable) .flatMap(duplicatedObservable => caseSrv.addObservable(`case`, duplicatedObservable)) - .recover { case _: CreateError => () } // ignore if case already contains observable + .recover { + case _: CreateError => // if case already contains observable, update tags + caseSrv + .get(`case`) + .observables + .filter { o => + richObservable.dataOrAttachment.fold(d => o.filterOnData(d.data), a => o.attachments.has("attachmentId", a.attachmentId)) + } + .headOption + .foreach { observable => + val newTags = observableSrv + .get(observable) + .tags + .toSet ++ richObservable.tags + observableSrv.updateTags(observable, newTags) + } + } } .map(_ => ()) @@ -302,10 +320,11 @@ class AlertSrv @Inject() ( object AlertOps { implicit class AlertOpsDefs(traversal: Traversal.V[Alert]) { - def get(idOrSource: String): Traversal.V[Alert] = idOrSource.split(';') match { - case Array(tpe, source, sourceRef) => getBySourceId(tpe, source, sourceRef) - case _ => traversal.getByIds(idOrSource) - } + def get(idOrSource: String): Traversal.V[Alert] = + idOrSource.split(';') match { + case Array(tpe, source, sourceRef) => getBySourceId(tpe, source, sourceRef) + case _ => traversal.getByIds(idOrSource) + } def getBySourceId(`type`: String, source: String, sourceRef: String): Traversal.V[Alert] = traversal @@ -339,8 +358,8 @@ object AlertOps { def imported: Traversal[Boolean, JLong, Converter[Boolean, JLong]] = traversal.outE[AlertCase].count.domainMap(_ > 0) - def similarCases( - implicit authContext: AuthContext + def similarCases(implicit + authContext: AuthContext ): Traversal[(RichCase, SimilarStats), JMap[String, Any], Converter[(RichCase, SimilarStats), JMap[String, Any]]] = observables .similar @@ -368,8 +387,8 @@ object AlertOps { def alertUserOrganisation( permission: Permission - )( - implicit authContext: AuthContext + )(implicit + authContext: AuthContext ): Traversal[(RichAlert, Organisation with Entity), JMap[String, Any], Converter[(RichAlert, Organisation with Entity), JMap[String, Any]]] = { val alertLabel = StepLabel.v[Alert] val organisationLabel = StepLabel.v[Organisation] diff --git a/thehive/app/org/thp/thehive/services/ObservableSrv.scala b/thehive/app/org/thp/thehive/services/ObservableSrv.scala index 2118689043..d1d88730c8 100644 --- a/thehive/app/org/thp/thehive/services/ObservableSrv.scala +++ b/thehive/app/org/thp/thehive/services/ObservableSrv.scala @@ -288,7 +288,7 @@ object ObservableOps { def richObservableWithCustomRenderer[D, G, C <: Converter[D, G]]( entityRenderer: Traversal.V[Observable] => Traversal[D, G, C] - )(implicit authContext: AuthContext) = + )(implicit authContext: AuthContext): Traversal[(RichObservable, D), JMap[String, Any], Converter[(RichObservable, D), JMap[String, Any]]] = traversal .project( _.by diff --git a/thehive/test/org/thp/thehive/services/AlertSrvTest.scala b/thehive/test/org/thp/thehive/services/AlertSrvTest.scala index d1703f8bc7..365c95dd27 100644 --- a/thehive/test/org/thp/thehive/services/AlertSrvTest.scala +++ b/thehive/test/org/thp/thehive/services/AlertSrvTest.scala @@ -193,15 +193,19 @@ class AlertSrvTest extends PlaySpecification with TestAppBuilder { } must beASuccessfulTry(()) } - "merge a case" in testApp { app => + "merge into an existing case" in testApp { app => app[Database] .tryTransaction { implicit graph => app[AlertSrv].mergeInCase("testType;testSource;ref1", "#1") } must beASuccessfulTry app[Database].roTransaction { implicit graph => - app[CaseSrv].get("#1").richCase.getOrFail("Case").get - pending("must check tags, description and observables") + val observables = app[CaseSrv].get("#1").observables.richObservable.toList + observables must have size 1 + observables must contain { (o: RichObservable) => + o.data must beSome.which((_: Data).data must beEqualTo("h.fr")) + o.tags.map(_.toString) must contain("testNamespace:testPredicate=\"testDomain\"", "testNamespace:testPredicate=\"hello\"").exactly + } } } diff --git a/thehive/test/resources/data/AlertObservable.json b/thehive/test/resources/data/AlertObservable.json index 1ef47c90ba..701d5c364c 100644 --- a/thehive/test/resources/data/AlertObservable.json +++ b/thehive/test/resources/data/AlertObservable.json @@ -1,4 +1,5 @@ [ + {"from": "alert1", "to": "alert-h.fr"}, {"from": "alert4", "to": "perdu.com"}, {"from": "alert5", "to": "c.fr"} -] \ No newline at end of file +] diff --git a/thehive/test/resources/data/Observable.json b/thehive/test/resources/data/Observable.json index 9490f52b28..88e79ee5cc 100644 --- a/thehive/test/resources/data/Observable.json +++ b/thehive/test/resources/data/Observable.json @@ -26,5 +26,12 @@ "tlp": 1, "ioc": false, "sighted": true + }, + { + "id": "alert-h.fr", + "message": "observable from alert", + "tlp": 1, + "ioc": true, + "sighted": true } -] \ No newline at end of file +] diff --git a/thehive/test/resources/data/ObservableData.json b/thehive/test/resources/data/ObservableData.json index f06ccbe345..344dc8f62b 100644 --- a/thehive/test/resources/data/ObservableData.json +++ b/thehive/test/resources/data/ObservableData.json @@ -1,5 +1,6 @@ [ {"from": "h.fr", "to": "data-h.fr"}, {"from": "c.fr", "to": "data-c.fr"}, - {"from": "perdu.com", "to": "data-perdu.com"} -] \ No newline at end of file + {"from": "perdu.com", "to": "data-perdu.com"}, + {"from": "alert-h.fr", "to": "data-h.fr"} +] diff --git a/thehive/test/resources/data/ObservableObservableType.json b/thehive/test/resources/data/ObservableObservableType.json index dec130bf6c..bf3337b719 100644 --- a/thehive/test/resources/data/ObservableObservableType.json +++ b/thehive/test/resources/data/ObservableObservableType.json @@ -2,5 +2,6 @@ {"from": "h.fr", "to": "domain"}, {"from": "c.fr", "to": "domain"}, {"from": "helloworld", "to": "file"}, - {"from": "perdu.com", "to": "domain"} -] \ No newline at end of file + {"from": "perdu.com", "to": "domain"}, + {"from": "alert-h.fr", "to": "domain"} +] diff --git a/thehive/test/resources/data/ObservableTag.json b/thehive/test/resources/data/ObservableTag.json index 42db528141..f5b3cb72a8 100644 --- a/thehive/test/resources/data/ObservableTag.json +++ b/thehive/test/resources/data/ObservableTag.json @@ -2,5 +2,6 @@ {"from": "h.fr", "to": "tagtestDomain"}, {"from": "c.fr", "to": "tagtestDomain"}, {"from": "helloworld", "to": "taghello"}, - {"from": "helloworld", "to": "tagworld"} -] \ No newline at end of file + {"from": "helloworld", "to": "tagworld"}, + {"from": "alert-h.fr", "to": "taghello"} +]