diff --git a/ScalliGraph b/ScalliGraph index 7269094914..523852a3a5 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit 72690949146e846a51a405c9272699ec8a7eb86f +Subproject commit 523852a3a569d75f18c0205f7dab88e15fcac68c diff --git a/thehive/app/org/thp/thehive/controllers/v1/AdminCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/AdminCtrl.scala index 21637a1986..4766cf5103 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/AdminCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/AdminCtrl.scala @@ -6,7 +6,7 @@ import akka.util.Timeout import ch.qos.logback.classic.{Level, LoggerContext} import org.slf4j.LoggerFactory import org.thp.scalligraph.controllers.Entrypoint -import org.thp.scalligraph.models.Database +import org.thp.scalligraph.models._ import org.thp.scalligraph.services.GenIntegrityCheckOps import org.thp.thehive.models.Permissions import org.thp.thehive.services.{CheckState, CheckStats, GetCheckStats, GlobalCheckRequest} @@ -18,7 +18,7 @@ import javax.inject.{Inject, Named, Singleton} import scala.collection.immutable import scala.concurrent.duration.DurationInt import scala.concurrent.{ExecutionContext, Future} -import scala.util.Success +import scala.util.{Failure, Success} @Singleton class AdminCtrl @Inject() ( @@ -26,6 +26,7 @@ class AdminCtrl @Inject() ( @Named("integrity-check-actor") integrityCheckActor: ActorRef, integrityCheckOps: immutable.Set[GenIntegrityCheckOps], db: Database, + schemas: immutable.Set[UpdatableSchema], implicit val ec: ExecutionContext ) { @@ -113,6 +114,7 @@ class AdminCtrl @Inject() ( "Job", "Attachment" ) + def indexStatus: Action[AnyContent] = entrypoint("Get index status") .authPermittedRoTransaction(db, Permissions.managePlatform) { _ => graph => @@ -122,7 +124,7 @@ class AdminCtrl @Inject() ( catch { case error: Throwable => logger.error("Index fetch error", error) - 0 + 0L } Json.obj("name" -> label, "count" -> count) @@ -137,4 +139,86 @@ class AdminCtrl @Inject() ( Future(db.reindexData(label)) Success(Results.NoContent) } + + private val rangeRegex = "(\\d+)-(\\d+)".r + private def getOperations(schemaName: String, select: String, filter: String): Seq[(Operation, Int)] = { + val ranges = select match { + case "all" => Seq(0 until Int.MaxValue) + case other => + other.split(',').toSeq.map { + case rangeRegex(from, to) => from.toInt to to.toInt + case number => number.toInt to number.toInt + } + } + + val filters = filter.split(',') + + schemas + .filter(_.name == schemaName) + .toSeq + .flatMap { schema => + schema + .operations + .operations + .zipWithIndex + .filter { + case (_, i) => ranges.exists(_.contains(i + 1)) + } + .filter { + case (_: AddVertexModel, _) => + filters.contains("AddVertexModel") || filters + .contains("schema") || (filters.contains("all") && !filters.contains("!schema") && !filters.contains("!AddVertexModel")) + case (_: AddEdgeModel, _) => + filter.contains("AddEdgeModel") || filter + .contains("schema") || (filters.contains("all") && !filter.contains("!schema") && !filter.contains("!AddEdgeModel")) + case (_: AddProperty, _) => + filter.contains("AddProperty") || filter + .contains("schema") || (filters.contains("all") && !filter.contains("!schema") && !filter.contains("!AddProperty")) + case (_: RemoveProperty, _) => + filter.contains("RemoveProperty") || filter + .contains("schema") || (filters.contains("all") && !filter.contains("!schema") && !filter.contains("!RemoveProperty")) + case (_: UpdateGraph, _) => + filter.contains("UpdateGraph") || filter + .contains("data") || (filters.contains("all") && !filter.contains("!data") && !filter.contains("!UpdateGraph")) + case (_: AddIndex, _) => + filter.contains("AddIndex") || filter + .contains("index") || (filters.contains("all") && !filter.contains("!index") && !filter.contains("!AddIndex")) + case (RebuildIndexes, _) => + filter.contains("RebuildIndexes") || filter + .contains("index") || (filters.contains("all") && !filter.contains("!index") && !filter.contains("!RebuildIndexes")) + case (NoOperation, _) => false + case (_: RemoveIndex, _) => + filter.contains("RemoveIndex") || filter + .contains("index") || (filters.contains("all") && !filter.contains("!index") && !filter.contains("!RemoveIndex")) + case (_: DBOperation[_], _) => + filter.contains("DBOperation") || filter + .contains("data") || (filters.contains("all") && !filter.contains("!data") && !filter.contains("!DBOperation")) + } + } + } + + def schemaRepair(schemaName: String, select: String, filter: String): Action[AnyContent] = + entrypoint("Repair schema") + .authPermitted(Permissions.managePlatform) { _ => + val result = getOperations(schemaName, select, filter) + .map { + case (operation, index) => + logger.info(s"Repair schema: $index=${operation.info}") + operation.execute(db, logger.info(_)) match { + case _: Success[_] => s"${operation.info}: Success" + case Failure(error) => s"${operation.info}: Failure $error" + } + } + + Success(Results.Ok(Json.toJson(result))) + } + + def schemaInfo(schemaName: String, select: String, filter: String): Action[AnyContent] = + entrypoint("Schema info") + .authPermitted(Permissions.managePlatform) { _ => + val output = getOperations(schemaName, select, filter).map { + case (o, i) => s"$i=${o.info}" + } + Success(Results.Ok(Json.toJson(output))) + } } diff --git a/thehive/app/org/thp/thehive/controllers/v1/Router.scala b/thehive/app/org/thp/thehive/controllers/v1/Router.scala index e5b3ca9fa9..a868ec07b6 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Router.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Router.scala @@ -43,11 +43,13 @@ class Router @Inject() ( case GET(p"/status") => statusCtrl.get // GET /health controllers.StatusCtrl.health - case GET(p"/admin/check/stats") => adminCtrl.checkStats - case GET(p"/admin/check/$name/trigger") => adminCtrl.triggerCheck(name) - case GET(p"/admin/index/status") => adminCtrl.indexStatus - case GET(p"/admin/index/$name/reindex") => adminCtrl.reindex(name) - case GET(p"/admin/log/set/$packageName/$level") => adminCtrl.setLogLevel(packageName, level) + case GET(p"/admin/check/stats") => adminCtrl.checkStats + case GET(p"/admin/check/$name/trigger") => adminCtrl.triggerCheck(name) + case GET(p"/admin/index/status") => adminCtrl.indexStatus + case GET(p"/admin/index/$name/reindex") => adminCtrl.reindex(name) + case GET(p"/admin/log/set/$packageName/$level") => adminCtrl.setLogLevel(packageName, level) + case POST(p"/admin/schema/repair/$schemaName/$select/$filter") => adminCtrl.schemaRepair(schemaName, select, filter) + case POST(p"/admin/schema/info/$schemaName/$select/$filter") => adminCtrl.schemaInfo(schemaName, select, filter) // GET /logout controllers.AuthenticationCtrl.logout() case GET(p"/logout") => authenticationCtrl.logout diff --git a/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala b/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala index 32ae88ec67..3b12ab85b1 100644 --- a/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala +++ b/thehive/app/org/thp/thehive/models/TheHiveSchemaDefinition.scala @@ -173,7 +173,7 @@ class TheHiveSchemaDefinition @Inject() extends Schema with UpdatableSchema { traversal.removeProperty("colour").iterate() Success(()) } - .removeProperty("Tag", "colour", usedOnlyByThisModel = true) + .removeProperty[Int]("Tag", "colour", usedOnlyByThisModel = true) .addProperty[String]("Tag", "colour") .updateGraph("Add property colour for Tags ", "Tag") { traversal => traversal.raw.property("colour", "#000000").iterate() @@ -393,7 +393,7 @@ class TheHiveSchemaDefinition @Inject() extends Schema with UpdatableSchema { traversal.removeProperty("deleted") Success(()) } - .removeProperty(model = "Log", propertyName = "deleted", usedOnlyByThisModel = true) + .removeProperty[Boolean](model = "Log", propertyName = "deleted", usedOnlyByThisModel = true) .updateGraph("Make shared dashboard writable", "Dashboard") { traversal => traversal.outE("OrganisationDashboard").raw.property("writable", true).iterate() Success(())