Skip to content

Commit

Permalink
#1499 Merge tags on existing observables when importing alert
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Nov 13, 2020
1 parent 8162550 commit 4954e2c
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 52 deletions.
19 changes: 10 additions & 9 deletions thehive/app/org/thp/thehive/models/Observable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
81 changes: 50 additions & 31 deletions thehive/app/org/thp/thehive/services/AlertSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -44,19 +44,20 @@ 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,
organisation: Organisation with Entity,
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))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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(_ => ())

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion thehive/app/org/thp/thehive/services/ObservableSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions thehive/test/org/thp/thehive/services/AlertSrvTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion thehive/test/resources/data/AlertObservable.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[
{"from": "alert1", "to": "alert-h.fr"},
{"from": "alert4", "to": "perdu.com"},
{"from": "alert5", "to": "c.fr"}
]
]
9 changes: 8 additions & 1 deletion thehive/test/resources/data/Observable.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,12 @@
"tlp": 1,
"ioc": false,
"sighted": true
},
{
"id": "alert-h.fr",
"message": "observable from alert",
"tlp": 1,
"ioc": true,
"sighted": true
}
]
]
5 changes: 3 additions & 2 deletions thehive/test/resources/data/ObservableData.json
Original file line number Diff line number Diff line change
@@ -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"}
]
{"from": "perdu.com", "to": "data-perdu.com"},
{"from": "alert-h.fr", "to": "data-h.fr"}
]
5 changes: 3 additions & 2 deletions thehive/test/resources/data/ObservableObservableType.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
{"from": "h.fr", "to": "domain"},
{"from": "c.fr", "to": "domain"},
{"from": "helloworld", "to": "file"},
{"from": "perdu.com", "to": "domain"}
]
{"from": "perdu.com", "to": "domain"},
{"from": "alert-h.fr", "to": "domain"}
]
5 changes: 3 additions & 2 deletions thehive/test/resources/data/ObservableTag.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
{"from": "h.fr", "to": "tagtestDomain"},
{"from": "c.fr", "to": "tagtestDomain"},
{"from": "helloworld", "to": "taghello"},
{"from": "helloworld", "to": "tagworld"}
]
{"from": "helloworld", "to": "tagworld"},
{"from": "alert-h.fr", "to": "taghello"}
]

0 comments on commit 4954e2c

Please sign in to comment.