Skip to content

Commit

Permalink
#232 Add the ability to merge alert with a case
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jun 12, 2017
1 parent 34c8f0e commit 7e72466
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 64 deletions.
18 changes: 13 additions & 5 deletions thehive-backend/app/controllers/AlertCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import play.api.Logger
import play.api.http.Status
import play.api.libs.json.{ JsArray, JsObject, Json }
import play.api.mvc.{ Action, AnyContent, Controller }
import services.AlertSrv
import services.{ AlertSrv, CaseSrv }
import services.JsonFormat.caseSimilarityWrites

import scala.concurrent.{ ExecutionContext, Future }
Expand All @@ -21,6 +21,7 @@ import scala.util.Try
@Singleton
class AlertCtrl @Inject() (
alertSrv: AlertSrv,
caseSrv: CaseSrv,
auxSrv: AuxSrv,
authenticated: Authenticated,
renderer: Renderer,
Expand All @@ -36,6 +37,15 @@ class AlertCtrl @Inject() (
.map(alert renderer.toOutput(CREATED, alert))
}

@Timed
def mergeWithCase(alertId: String, caseId: String): Action[Fields] = authenticated(Role.write).async(fieldsBodyParser) { implicit request
for {
alert alertSrv.get(alertId)
caze caseSrv.get(caseId)
_ alertSrv.mergeWithCase(alert, caze)
} yield renderer.toOutput(CREATED, caze)
}

@Timed
def get(id: String): Action[AnyContent] = authenticated(Role.read).async { implicit request
val withStats = request
Expand All @@ -49,7 +59,6 @@ class AlertCtrl @Inject() (
.get("similarity")
.flatMap(_.headOption)
.exists(v Try(v.toBoolean).getOrElse(v == "1"))
println(s"similarity=$withSimilarity")

for {
alert alertSrv.get(id)
Expand All @@ -59,7 +68,6 @@ class AlertCtrl @Inject() (
.map(sc Json.obj("similarCases" Json.toJson(sc)))
else Future.successful(JsObject(Nil))
} yield {
println(s"Similar cases = $similarCases")
renderer.toOutput(OK, alertsWithStats ++ similarCases)
}
}
Expand Down Expand Up @@ -124,8 +132,8 @@ class AlertCtrl @Inject() (
def createCase(id: String): Action[AnyContent] = authenticated(Role.write).async { implicit request
for {
alert alertSrv.get(id)
updatedAlert alertSrv.createCase(alert)
} yield renderer.toOutput(CREATED, updatedAlert)
caze alertSrv.createCase(alert)
} yield renderer.toOutput(CREATED, caze)
}

@Timed
Expand Down
2 changes: 0 additions & 2 deletions thehive-backend/app/models/Artifact.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ class ArtifactModel @Inject() (
// this method modify request in order to hash artifact and manager file upload
override def creationHook(parent: Option[BaseEntity], attrs: JsObject): Future[JsObject] = {
val keys = attrs.keys
println(s"keys=$keys")
println(s"attrs=$attrs")
if (!keys.contains("message") && (attrs \ "tags").asOpt[Seq[JsValue]].forall(_.isEmpty))
throw BadRequestError(s"Artifact must contain a message or on ore more tags")
if (keys.contains("data") == keys.contains("attachment"))
Expand Down
109 changes: 57 additions & 52 deletions thehive-backend/app/services/AlertSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import play.api.{ Configuration, Logger }

import scala.collection.immutable
import scala.concurrent.{ ExecutionContext, Future }
import scala.util.{ Failure, Try }
import scala.util.{ Failure, Success, Try }

trait AlertTransformer {
def createCase(alert: Alert)(implicit authContext: AuthContext): Future[Case]
def mergeWithCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Case]
}

case class CaseSimilarity(caze: Case, similarIOCCount: Int, iocCount: Int, similarArtifactCount: Int, artifactCount: Int)
Expand Down Expand Up @@ -136,9 +137,9 @@ class AlertSrv(
connectors.get(alert.tpe()) match {
case Some(connector: AlertTransformer) connector.createCase(alert)
case _
getCaseTemplate(alert).flatMap { caseTemplate
println(s"Create case using template $caseTemplate")
caseSrv.create(
for {
caseTemplate getCaseTemplate(alert)
caze caseSrv.create(
Fields.empty
.set("title", s"#${alert.sourceRef()} " + alert.title())
.set("description", alert.description())
Expand All @@ -147,57 +148,61 @@ class AlertSrv(
.set("tlp", JsNumber(alert.tlp()))
.set("status", CaseStatus.Open.toString),
caseTemplate)
.flatMap { caze setCase(alert, caze).map(_ caze) }
.flatMap { caze
val artifactsFields = alert.artifacts()
.map { artifact
val tags = (artifact \ "tags").asOpt[Seq[JsString]].getOrElse(Nil) :+ JsString("src:" + alert.tpe())
val message = (artifact \ "message").asOpt[JsString].getOrElse(JsString(""))
val artifactFields = Fields(artifact +
("tags" JsArray(tags)) +
("message" message))
if (artifactFields.getString("dataType").contains("file")) {
artifactFields.getString("data")
.map {
case dataExtractor(filename, contentType, data)
val f = Files.createTempFile("alert-", "-attachment")
Files.write(f, java.util.Base64.getDecoder.decode(data))
artifactFields
.set("attachment", FileInputValue(filename, f, contentType))
.unset("data")
case data
logger.warn(s"Invalid data format for file artifact: $data")
artifactFields
}
.getOrElse(artifactFields)
}
else {
artifactFields
}
}
_ mergeWithCase(alert, caze)
} yield caze
}
}
}

val createdCase = artifactSrv.create(caze, artifactsFields)
.map { r
r.foreach {
case Failure(e) logger.warn("Create artifact error", e)
case _
}
caze
}
createdCase.onComplete { _
// remove temporary files
artifactsFields
.flatMap(_.get("Attachment"))
.foreach {
case FileInputValue(_, file, _) Files.delete(file)
case _
}
}
createdCase
override def mergeWithCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Case] = {
setCase(alert, caze)
.map { _
val artifactsFields = alert.artifacts()
.map { artifact
val tags = (artifact \ "tags").asOpt[Seq[JsString]].getOrElse(Nil) :+ JsString("src:" + alert.tpe())
val message = (artifact \ "message").asOpt[JsString].getOrElse(JsString(""))
val artifactFields = Fields(artifact +
("tags" JsArray(tags)) +
("message" message))
if (artifactFields.getString("dataType").contains("file")) {
artifactFields.getString("data")
.map {
case dataExtractor(filename, contentType, data)
val f = Files.createTempFile("alert-", "-attachment")
Files.write(f, java.util.Base64.getDecoder.decode(data))
artifactFields
.set("attachment", FileInputValue(filename, f, contentType))
.unset("data")
case data
logger.warn(s"Invalid data format for file artifact: $data")
artifactFields
}
.getOrElse(artifactFields)
}
}
}
else {
artifactFields
}
}

artifactSrv.create(caze, artifactsFields)
.map {
_.foreach {
case Failure(e) logger.warn("Create artifact error", e)
case _
}
}
.onComplete { _
// remove temporary files
artifactsFields
.flatMap(_.get("Attachment"))
.foreach {
case FileInputValue(_, file, _) Files.delete(file)
case _
}
}
caze
}

}

def setCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Alert] = {
Expand Down
1 change: 1 addition & 0 deletions thehive-backend/conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ POST /api/alert/:alertId/markAsUnread controllers.AlertCtrl.markAsUn
POST /api/alert/:alertId/createCase controllers.AlertCtrl.createCase(alertId)
POST /api/alert/:alertId/follow controllers.AlertCtrl.followAlert(alertId)
POST /api/alert/:alertId/unfollow controllers.AlertCtrl.unfollowAlert(alertId)
POST /api/alert/:alertId/merge/:caseId controllers.AlertCtrl.mergeWithCase(alertId, caseId)

GET /api/flow controllers.FlowCtrl.flow(rootId: Option[String], count: Option[Int])

Expand Down
6 changes: 5 additions & 1 deletion thehive-misp/app/connectors/misp/MispCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ class MispCtrl @Inject() (
Ok("")
}

def createCase(alert: Alert)(implicit authContext: AuthContext): Future[Case] = {
override def createCase(alert: Alert)(implicit authContext: AuthContext): Future[Case] = {
mispSrv.createCase(alert)
}

override def mergeWithCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Case] = {
mispSrv.mergeWithCase(alert, caze)
}
}
14 changes: 10 additions & 4 deletions thehive-misp/app/connectors/misp/MispSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -348,16 +348,22 @@ class MispSrv @Inject() (
case Some(id) caseSrv.get(id)
case None
for {
instanceConfig getInstanceConfig(alert.source())
caseTemplate alertSrv.getCaseTemplate(alert)
caze caseSrv.create(Fields(alert.toCaseJson), caseTemplate)
_ alertSrv.setCase(alert, caze)
artifacts Future.sequence(alert.artifacts().flatMap(attributeToArtifact(instanceConfig, alert, _)))
_ artifactSrv.create(caze, artifacts)
_ mergeWithCase(alert, caze)
} yield caze
}
}

def mergeWithCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Case] = {
for {
instanceConfig getInstanceConfig(alert.source())
_ alertSrv.setCase(alert, caze)
artifacts Future.sequence(alert.artifacts().flatMap(attributeToArtifact(instanceConfig, alert, _)))
_ artifactSrv.create(caze, artifacts)
} yield caze
}

def updateMispAlertArtifact()(implicit authContext: AuthContext): Future[Unit] = {
import org.elastic4play.services.QueryDSL._
logger.info("Update MISP attributes in alerts")
Expand Down

0 comments on commit 7e72466

Please sign in to comment.