Skip to content

Commit

Permalink
#1410 Add describe API
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jun 26, 2020
1 parent 8859768 commit 6770187
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 1 deletion.
177 changes: 177 additions & 0 deletions thehive/app/org/thp/thehive/controllers/v1/DescribeCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package org.thp.thehive.controllers.v1

import java.lang.{Boolean => JBoolean}
import java.util.Date

import javax.inject.{Inject, Named, Singleton}
import org.thp.scalligraph.NotFoundError
import org.thp.scalligraph.controllers.Entrypoint
import org.thp.scalligraph.models.Database
import org.thp.scalligraph.query.PublicProperty
import org.thp.scalligraph.services.config.ApplicationConfig.durationFormat
import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem}
import org.thp.scalligraph.steps.StepsOps._
import org.thp.scalligraph.utils.Hash
import org.thp.thehive.services.CustomFieldSrv
import play.api.Logger
import play.api.cache.SyncCacheApi
import play.api.inject.Injector
import play.api.libs.json._
import play.api.mvc.{Action, AnyContent, Results}

import scala.concurrent.duration.Duration
import scala.util.{Failure, Success, Try}

@Singleton
class DescribeCtrl @Inject() (
cacheApi: SyncCacheApi,
entrypoint: Entrypoint,
caseCtrl: CaseCtrl,
taskCtrl: TaskCtrl,
alertCtrl: AlertCtrl,
observableCtrl: ObservableCtrl,
userCtrl: UserCtrl,
// logCtrl: LogCtrl,
auditCtrl: AuditCtrl,
customFieldSrv: CustomFieldSrv,
injector: Injector,
@Named("with-thehive-schema") db: Database,
applicationConfig: ApplicationConfig
) {

case class PropertyDescription(name: String, `type`: String, values: Seq[JsValue] = Nil, labels: Seq[String] = Nil)
case class EntityDescription(label: String, attributes: Seq[PropertyDescription]) {
def toJson: JsObject =
Json.obj(
"label" -> label,
"attributes" -> attributes
)
}

lazy val logger: Logger = Logger(getClass)

val cacheExpireConfig: ConfigItem[Duration, Duration] =
applicationConfig.item[Duration]("describe.cache.expire", "Custom fields refresh in describe")
def cacheExpire: Duration = cacheExpireConfig.get

def describeCortexEntity(
name: String,
className: String,
packageName: String = "org.thp.thehive.connector.cortex.controllers.v1"
): Option[EntityDescription] =
Try(
EntityDescription(
name,
injector
.instanceOf(getClass.getClassLoader.loadClass(s"$packageName.$className"))
.asInstanceOf[QueryableCtrl]
.publicProperties
.flatMap(propertyToJson(name, _))
)
).toOption

val entityDescriptions: Seq[EntityDescription] = Seq(
EntityDescription("case", caseCtrl.publicProperties.flatMap(propertyToJson("case", _))),
EntityDescription("case_task", taskCtrl.publicProperties.flatMap(propertyToJson("case_task", _))),
EntityDescription("alert", alertCtrl.publicProperties.flatMap(propertyToJson("alert", _))),
EntityDescription("case_artifact", observableCtrl.publicProperties.flatMap(propertyToJson("case_artifact", _))),
EntityDescription("user", userCtrl.publicProperties.flatMap(propertyToJson("user", _))),
// EntityDescription("case_task_log", logCtrl.publicProperties.flatMap(propertyToJson("case_task_log", _))),
EntityDescription("audit", auditCtrl.publicProperties.flatMap(propertyToJson("audit", _)))
) ++ describeCortexEntity("case_artifact_job", "/connector/cortex/job", "JobCtrl") ++
describeCortexEntity("action", "/connector/cortex/action", "ActionCtrl")

implicit val propertyDescriptionWrites: Writes[PropertyDescription] =
Json.writes[PropertyDescription].transform((_: JsObject) + ("description" -> JsString("")))

def customFields: List[PropertyDescription] = db.roTransaction { implicit graph =>
customFieldSrv.initSteps.toList.map(cf => PropertyDescription(s"customFields.${cf.name}", cf.`type`.toString))
}

def customDescription(model: String, propertyName: String): Option[Seq[PropertyDescription]] = (model, propertyName) match {
// case (_, "owner") => Some(Seq(PropertyDescription("owner", "user")))
// case ("case", "status") =>
// Some(
// Seq(PropertyDescription("status", "enumeration", Seq(JsString("Open"), JsString("Resolved"), JsString("Deleted"), JsString("Duplicated"))))
// )
// //case ("observable", "status") =>
// // Some(PropertyDescription("status", "enumeration", Seq(JsString("Ok"))))
// //case ("observable", "dataType") =>
// // Some(PropertyDescription("status", "enumeration", Seq(JsString("sometesttype", "fqdn", "url", "regexp", "mail", "hash", "registry", "custom-type", "uri_path", "ip", "user-agent", "autonomous-system", "file", "mail_subject", "filename", "other", "domain"))))
// case ("alert", "status") =>
// Some(Seq(PropertyDescription("status", "enumeration", Seq(JsString("New"), JsString("Updated"), JsString("Ignored"), JsString("Imported")))))
// case ("case_task", "status") =>
// Some(
// Seq(PropertyDescription("status", "enumeration", Seq(JsString("Waiting"), JsString("InProgress"), JsString("Completed"), JsString("Cancel"))))
// )
// case ("case", "impactStatus") =>
// Some(Seq(PropertyDescription("impactStatus", "enumeration", Seq(JsString("NoImpact"), JsString("WithImpact"), JsString("NotApplicable")))))
// case ("case", "resolutionStatus") =>
// Some(
// Seq(
// PropertyDescription(
// "resolutionStatus",
// "enumeration",
// Seq(JsString("FalsePositive"), JsString("Duplicated"), JsString("Indeterminate"), JsString("TruePositive"), JsString("Other"))
// )
// )
// )
// case (_, "tlp") =>
// Some(Seq(PropertyDescription("tlp", "number", Seq(JsNumber(0), JsNumber(1), JsNumber(2), JsNumber(3)), Seq("white", "green", "amber", "red"))))
// case (_, "pap") =>
// Some(Seq(PropertyDescription("pap", "number", Seq(JsNumber(0), JsNumber(1), JsNumber(2), JsNumber(3)), Seq("white", "green", "amber", "red"))))
// case (_, "severity") =>
// Some(
// Seq(
// PropertyDescription("severity", "number", Seq(JsNumber(1), JsNumber(2), JsNumber(3), JsNumber(4)), Seq("low", "medium", "high", "critical"))
// )
// )
// case (_, "createdBy") => Some(Seq(PropertyDescription("createdBy", "user")))
// case (_, "updatedBy") => Some(Seq(PropertyDescription("updatedBy", "user")))
// case (_, "customFields") => Some(customFields)
// case ("case_artifact_job" | "action", "status") =>
// Some(
// Seq(
// PropertyDescription(
// "status",
// "enumeration",
// Seq(JsString("InProgress"), JsString("Success"), JsString("Failure"), JsString("Waiting"), JsString("Deleted"))
// )
// )
// )
case _ => None
}

def propertyToJson(model: String, prop: PublicProperty[_, _]): Seq[PropertyDescription] =
customDescription(model, prop.propertyName).getOrElse {
prop.mapping.domainTypeClass match {
case c if c == classOf[Boolean] || c == classOf[JBoolean] => Seq(PropertyDescription(prop.propertyName, "boolean"))
case c if c == classOf[Date] => Seq(PropertyDescription(prop.propertyName, "date"))
case c if c == classOf[Hash] => Seq(PropertyDescription(prop.propertyName, "hash"))
case c if classOf[Number].isAssignableFrom(c) => Seq(PropertyDescription(prop.propertyName, "number"))
case c if c == classOf[String] => Seq(PropertyDescription(prop.propertyName, "string"))
case _ =>
logger.warn(s"Unrecognized property $prop. Add a custom description")
Seq(PropertyDescription(prop.propertyName, "unknown"))
}
}

def describe(modelName: String): Action[AnyContent] =
entrypoint("describe model")
.auth { _ =>
entityDescriptions
.collectFirst {
case desc if desc.label == modelName => Success(Results.Ok(cacheApi.getOrElseUpdate(s"describe.$modelName", cacheExpire)(desc.toJson)))
}
.getOrElse(Failure(NotFoundError(s"Model $modelName not found")))
}

def describeAll: Action[AnyContent] =
entrypoint("describe all models")
.auth { _ =>
val descriptors = entityDescriptions.map { desc =>
desc.label -> cacheApi.getOrElseUpdate(s"describe.${desc.label}", cacheExpire)(desc.toJson)
}
Success(Results.Ok(JsObject(descriptors)))
}
}
6 changes: 5 additions & 1 deletion thehive/app/org/thp/thehive/controllers/v1/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class Router @Inject() (
alertCtrl: AlertCtrl,
auditCtrl: AuditCtrl,
statusCtrl: StatusCtrl,
authenticationCtrl: AuthenticationCtrl
authenticationCtrl: AuthenticationCtrl,
describeCtrl: DescribeCtrl
) extends SimpleRouter {

override def routes: Routes = {
Expand Down Expand Up @@ -94,5 +95,8 @@ class Router @Inject() (
// POST /audit/_search controllers.AuditCtrl.find()
// POST /audit/_stats controllers.AuditCtrl.stats()

case GET(p"/describe/_all") => describeCtrl.describeAll
case GET(p"/describe/$modelName") => describeCtrl.describe(modelName)

}
}

0 comments on commit 6770187

Please sign in to comment.