diff --git a/ScalliGraph b/ScalliGraph index dda73f4dca..c4ed5935b6 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit dda73f4dca0a93f4be7b3a98c614ef59a0dcec88 +Subproject commit c4ed5935b62b6df3c2ae0a0058e232df72f32697 diff --git a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/models/CortexSchemaDefinition.scala b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/models/CortexSchemaDefinition.scala index ef457c42e8..1727dc9526 100644 --- a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/models/CortexSchemaDefinition.scala +++ b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/models/CortexSchemaDefinition.scala @@ -14,8 +14,7 @@ import scala.reflect.runtime.{universe => ru} class CortexSchemaDefinition @Inject() () extends Schema with UpdatableSchema { lazy val logger: Logger = Logger(getClass) - val name: String = "thehive-cortex" - val operations: Operations = Operations(name) + val operations: Operations = Operations("thehive-cortex") lazy val reflectionClasses = new Reflections( new ConfigurationBuilder() diff --git a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/services/Connector.scala b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/services/Connector.scala index 99435c545d..5bdb6d70e1 100644 --- a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/services/Connector.scala +++ b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/services/Connector.scala @@ -2,10 +2,13 @@ package org.thp.thehive.connector.cortex.services import akka.actor.ActorSystem import akka.stream.Materializer + import javax.inject.{Inject, Singleton} import org.thp.cortex.client.{CortexClient, CortexClientConfig} +import org.thp.scalligraph.models.SchemaStatus import org.thp.scalligraph.services.config.ApplicationConfig.finiteDurationFormat import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem} +import org.thp.thehive.connector.cortex.models.CortexSchemaDefinition import org.thp.thehive.models.HealthStatus import org.thp.thehive.services.{Connector => TheHiveConnector} import play.api.libs.json.{JsObject, Json} @@ -17,6 +20,7 @@ import scala.util.{Failure, Success} @Singleton class Connector @Inject() ( appConfig: ApplicationConfig, + schemaDefinition: CortexSchemaDefinition, mat: Materializer, implicit val system: ActorSystem, implicit val ec: ExecutionContext @@ -44,10 +48,11 @@ class Connector @Inject() ( .traverse(clients)(_.getHealth) .foreach { healthStatus => val distinctStatus = healthStatus.toSet.map(HealthStatus.withName) - cachedHealth = if (distinctStatus.contains(HealthStatus.Ok)) { - if (distinctStatus.size > 1) HealthStatus.Warning else HealthStatus.Ok - } else if (distinctStatus.contains(HealthStatus.Error)) HealthStatus.Error - else HealthStatus.Warning + cachedHealth = + if (distinctStatus.contains(HealthStatus.Ok)) + if (distinctStatus.size > 1) HealthStatus.Warning else HealthStatus.Ok + else if (distinctStatus.contains(HealthStatus.Error)) HealthStatus.Error + else HealthStatus.Warning system.scheduler.scheduleOnce(statusCheckInterval)(updateHealth()) } @@ -67,9 +72,10 @@ class Connector @Inject() ( } .foreach { statusDetails => val distinctStatus = statusDetails.map(_._3).toSet - val healthStatus = if (distinctStatus.contains("OK")) { - if (distinctStatus.size > 1) "WARNING" else "OK" - } else "ERROR" + val healthStatus = + if (distinctStatus.contains("OK")) + if (distinctStatus.size > 1) "WARNING" else "OK" + else "ERROR" cachedStatus = Json.obj( "enabled" -> true, @@ -83,4 +89,5 @@ class Connector @Inject() ( } updateStatus() + override def schemaStatus: Option[SchemaStatus] = schemaDefinition.schemaStatus } diff --git a/cortex/connector/src/test/scala/org/thp/thehive/connector/cortex/services/TestConnector.scala b/cortex/connector/src/test/scala/org/thp/thehive/connector/cortex/services/TestConnector.scala index 3cda6c562a..b6dc5cd410 100644 --- a/cortex/connector/src/test/scala/org/thp/thehive/connector/cortex/services/TestConnector.scala +++ b/cortex/connector/src/test/scala/org/thp/thehive/connector/cortex/services/TestConnector.scala @@ -2,15 +2,23 @@ package org.thp.thehive.connector.cortex.services import akka.actor.ActorSystem import akka.stream.Materializer + import javax.inject.{Inject, Singleton} import org.thp.cortex.client.CortexClient import org.thp.scalligraph.services.config.ApplicationConfig +import org.thp.thehive.connector.cortex.models.CortexSchemaDefinition import scala.concurrent.ExecutionContext @Singleton -class TestConnector @Inject() (client: CortexClient, appConfig: ApplicationConfig, mat: Materializer, system: ActorSystem, ec: ExecutionContext) - extends Connector(appConfig, mat, system, ec) { +class TestConnector @Inject() ( + client: CortexClient, + appConfig: ApplicationConfig, + schemaDefinition: CortexSchemaDefinition, + mat: Materializer, + system: ActorSystem, + ec: ExecutionContext +) extends Connector(appConfig, schemaDefinition, mat, system, ec) { override def clients: Seq[CortexClient] = Seq(client) override protected def updateHealth(): Unit = () diff --git a/thehive/app/org/thp/thehive/controllers/v0/StatusCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/StatusCtrl.scala index 9e03134ae1..61404c7d1f 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/StatusCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/StatusCtrl.scala @@ -7,7 +7,7 @@ import org.thp.scalligraph.services.config.ApplicationConfig.finiteDurationForma import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem} import org.thp.scalligraph.{EntityName, ScalligraphApplicationLoader} import org.thp.thehive.TheHiveModule -import org.thp.thehive.models.{HealthStatus, User} +import org.thp.thehive.models.{HealthStatus, TheHiveSchemaDefinition, User} import org.thp.thehive.services.{Connector, UserSrv} import play.api.libs.json.{JsObject, JsString, Json} import play.api.mvc.{AbstractController, Action, AnyContent, Results} @@ -24,6 +24,7 @@ class StatusCtrl @Inject() ( authSrv: AuthSrv, userSrv: UserSrv, connectors: immutable.Set[Connector], + theHiveSchemaDefinition: TheHiveSchemaDefinition, @Named("with-thehive-schema") db: Database ) { @@ -55,7 +56,15 @@ class StatusCtrl @Inject() ( "capabilities" -> authSrv.capabilities.map(c => JsString(c.toString)), "ssoAutoLogin" -> authSrv.capabilities.contains(AuthCapability.sso), "pollingDuration" -> streamPollingDuration.toMillis - ) + ), + "schemaStatus" -> (connectors.flatMap(_.schemaStatus) ++ theHiveSchemaDefinition.schemaStatus).map { schemaStatus => + Json.obj( + "name" -> schemaStatus.name, + "currentVersion" -> schemaStatus.currentVersion, + "expectedVersion" -> schemaStatus.expectedVersion, + "error" -> schemaStatus.error.map(_.getMessage) + ) + } ) ) ) diff --git a/thehive/app/org/thp/thehive/controllers/v1/StatusCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/StatusCtrl.scala index 1289dc0e89..99574a0bb7 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/StatusCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/StatusCtrl.scala @@ -9,15 +9,25 @@ import org.thp.scalligraph.controllers.Entrypoint import org.thp.scalligraph.services.config.ApplicationConfig.finiteDurationFormat import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem} import org.thp.thehive.TheHiveModule +import org.thp.thehive.models.TheHiveSchemaDefinition +import org.thp.thehive.services.Connector import play.api.libs.json.{JsObject, JsString, Json, Writes} import play.api.mvc.{AbstractController, Action, AnyContent, Results} import javax.inject.{Inject, Singleton} +import scala.collection.immutable import scala.concurrent.duration.FiniteDuration import scala.util.Success @Singleton -class StatusCtrl @Inject() (entrypoint: Entrypoint, appConfig: ApplicationConfig, authSrv: AuthSrv, system: ActorSystem) { +class StatusCtrl @Inject() ( + entrypoint: Entrypoint, + appConfig: ApplicationConfig, + authSrv: AuthSrv, + connectors: immutable.Set[Connector], + theHiveSchemaDefinition: TheHiveSchemaDefinition, + system: ActorSystem +) { private def getVersion(c: Class[_]): String = Option(c.getPackage.getImplementationVersion).getOrElse("SNAPSHOT") @@ -67,10 +77,17 @@ class StatusCtrl @Inject() (entrypoint: Entrypoint, appConfig: ApplicationConfig "ssoAutoLogin" -> authSrv.capabilities.contains(AuthCapability.sso), "pollingDuration" -> streamPollingDuration.toMillis ), - "cluster" -> cluster.state + "cluster" -> cluster.state, + "schemaStatus" -> (connectors.flatMap(_.schemaStatus) ++ theHiveSchemaDefinition.schemaStatus).map { schemaStatus => + Json.obj( + "name" -> schemaStatus.name, + "currentVersion" -> schemaStatus.currentVersion, + "expectedVersion" -> schemaStatus.expectedVersion, + "error" -> schemaStatus.error.map(_.getMessage) + ) + } ) ) ) } - } diff --git a/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala b/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala index da0fe21586..5f797b4f89 100644 --- a/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala +++ b/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala @@ -27,8 +27,7 @@ class TheHiveSchemaDefinition @Inject() extends Schema with UpdatableSchema { // Make sure TypeDefinitionCategory has been initialised before ModifierType to prevent ExceptionInInitializerError TypeDefinitionCategory.BACKING_INDEX lazy val logger: Logger = Logger(getClass) - val name: String = "thehive" - val operations: Operations = Operations(name) + val operations: Operations = Operations("thehive") .addProperty[Option[Boolean]]("Observable", "seen") .updateGraph("Add manageConfig permission to org-admin profile", "Profile") { traversal => traversal.unsafeHas("name", "org-admin").raw.property("permissions", "manageConfig").iterate() diff --git a/thehive/app/org/thp/thehive/services/Connector.scala b/thehive/app/org/thp/thehive/services/Connector.scala index fec3027ce2..e53de30a9d 100644 --- a/thehive/app/org/thp/thehive/services/Connector.scala +++ b/thehive/app/org/thp/thehive/services/Connector.scala @@ -1,10 +1,12 @@ package org.thp.thehive.services +import org.thp.scalligraph.models.SchemaStatus import org.thp.thehive.models.HealthStatus import play.api.libs.json.{JsObject, Json} trait Connector { val name: String - def status: JsObject = Json.obj("enabled" -> true) - def health: HealthStatus.Value = HealthStatus.Ok + def status: JsObject = Json.obj("enabled" -> true) + def health: HealthStatus.Value = HealthStatus.Ok + def schemaStatus: Option[SchemaStatus] = None } diff --git a/thehive/test/org/thp/thehive/controllers/v0/StatusCtrlTest.scala b/thehive/test/org/thp/thehive/controllers/v0/StatusCtrlTest.scala index eb1921fae6..e3516edecb 100644 --- a/thehive/test/org/thp/thehive/controllers/v0/StatusCtrlTest.scala +++ b/thehive/test/org/thp/thehive/controllers/v0/StatusCtrlTest.scala @@ -1,5 +1,6 @@ package org.thp.thehive.controllers.v0 +import org.thp.scalligraph.models.SchemaStatus import org.thp.scalligraph.{AppBuilder, ScalligraphApplicationLoader} import org.thp.thehive.models.HealthStatus import org.thp.thehive.services.Connector @@ -28,6 +29,8 @@ class StatusCtrlTest extends PlaySpecification with TestAppBuilder { ) override def health: HealthStatus.Value = HealthStatus.Warning + + override def schemaStatus: Option[SchemaStatus] = None } override def appConfigure: AppBuilder = super.appConfigure.multiBindInstance[Connector](fakeCortexConnector)