From 425d16c245075a5eae07b0fdb1137896c8c8e007 Mon Sep 17 00:00:00 2001 From: To-om Date: Fri, 5 Mar 2021 14:03:21 +0100 Subject: [PATCH] #1817 Add dashboard controller for APIv1 --- .../thehive/controllers/v0/DescribeCtrl.scala | 2 + .../controllers/v1/DashboardCtrl.scala | 108 ++++++++++++++++++ .../thehive/controllers/v1/DescribeCtrl.scala | 40 +------ .../thehive/controllers/v1/Properties.scala | 37 +++++- .../controllers/v1/TheHiveQueryExecutor.scala | 4 +- 5 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 thehive/app/org/thp/thehive/controllers/v1/DashboardCtrl.scala diff --git a/thehive/app/org/thp/thehive/controllers/v0/DescribeCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/DescribeCtrl.scala index cd3d96db2b..d8b160040d 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/DescribeCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/DescribeCtrl.scala @@ -206,6 +206,8 @@ class DescribeCtrl @Inject() ( ) ) ) + case ("dashboard", "status") => + Some(Seq(PropertyDescription("status", "enumeration", Seq(JsString("Shared"), JsString("Private"), JsString("Deleted"))))) case _ => None } diff --git a/thehive/app/org/thp/thehive/controllers/v1/DashboardCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/DashboardCtrl.scala new file mode 100644 index 0000000000..36cad8ed50 --- /dev/null +++ b/thehive/app/org/thp/thehive/controllers/v1/DashboardCtrl.scala @@ -0,0 +1,108 @@ +package org.thp.thehive.controllers.v1 + +import org.thp.scalligraph.EntityIdOrName +import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} +import org.thp.scalligraph.models.Database +import org.thp.scalligraph.query._ +import org.thp.scalligraph.traversal.TraversalOps._ +import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} +import org.thp.thehive.controllers.v0.Conversion._ +import org.thp.thehive.dto.v0.InputDashboard +import org.thp.thehive.models.{Dashboard, RichDashboard} +import org.thp.thehive.services.DashboardOps._ +import org.thp.thehive.services.OrganisationOps._ +import org.thp.thehive.services.UserOps._ +import org.thp.thehive.services.{DashboardSrv, OrganisationSrv, UserSrv} +import play.api.mvc.{Action, AnyContent, Results} + +import javax.inject.{Inject, Singleton} +import scala.util.Success + +@Singleton +class DashboardCtrl @Inject() ( + entrypoint: Entrypoint, + properties: Properties, + db: Database, + dashboardSrv: DashboardSrv, + userSrv: UserSrv, + organisationSrv: OrganisationSrv +) extends QueryableCtrl { + + override val entityName: String = "dashboard" + override val publicProperties: PublicProperties = properties.dashboard + override val initialQuery: Query = + Query.init[Traversal.V[Dashboard]]( + "listDashboard", + (graph, authContext) => + graph + .union( + organisationSrv.filterTraversal(_).get(authContext.organisation).dashboards, + userSrv.filterTraversal(_).getByName(authContext.userId).dashboards + ) + .dedup + ) + + override val getQuery: ParamQuery[EntityIdOrName] = Query.initWithParam[EntityIdOrName, Traversal.V[Dashboard]]( + "getDashboard", + (idOrName, graph, authContext) => dashboardSrv.get(idOrName)(graph).visible(authContext) + ) + + override val pageQuery: ParamQuery[OutputParam] = Query.withParam[OutputParam, Traversal.V[Dashboard], IteratorOutput]( + "page", + (range, dashboardSteps, _) => dashboardSteps.richPage(range.from, range.to, withTotal = true)(_.richDashboard) + ) + override val outputQuery: Query = Query.output[RichDashboard, Traversal.V[Dashboard]](_.richDashboard) + + def create: Action[AnyContent] = + entrypoint("create dashboard") + .extract("dashboard", FieldsParser[InputDashboard]) + .authTransaction(db) { implicit request => implicit graph => + val dashboard: InputDashboard = request.body("dashboard") + dashboardSrv + .create(dashboard.toDashboard) + .flatMap { + case richDashboard if dashboard.status == "Shared" => + dashboardSrv + .share(richDashboard.dashboard, request.organisation, writable = false) + .flatMap(_ => dashboardSrv.get(richDashboard.dashboard).richDashboard.getOrFail("Dashboard")) + case richDashboard => Success(richDashboard) + } + .map(richDashboard => Results.Created(richDashboard.toJson)) + } + + def get(dashboardId: String): Action[AnyContent] = + entrypoint("get dashboard") + .authRoTransaction(db) { implicit request => implicit graph => + dashboardSrv + .get(EntityIdOrName(dashboardId)) + .visible + .richDashboard + .getOrFail("Dashboard") + .map(dashboard => Results.Ok(dashboard.toJson)) + } + + def update(dashboardId: String): Action[AnyContent] = + entrypoint("update dashboard") + .extract("dashboard", FieldsParser.update("dashboard", publicProperties)) + .authTransaction(db) { implicit request => implicit graph => + val propertyUpdaters: Seq[PropertyUpdater] = request.body("dashboard") + dashboardSrv + .update(_.get(EntityIdOrName(dashboardId)).canUpdate, propertyUpdaters) // TODO check permission + .flatMap { case (dashboardSteps, _) => dashboardSteps.richDashboard.getOrFail("Dashboard") } + .map(dashboard => Results.Ok(dashboard.toJson)) + } + + def delete(dashboardId: String): Action[AnyContent] = + entrypoint("delete dashboard") + .authTransaction(db) { implicit request => implicit graph => + userSrv + .current + .dashboards + .get(EntityIdOrName(dashboardId)) + .getOrFail("Dashboard") + .map { dashboard => + dashboardSrv.remove(dashboard) + Results.NoContent + } + } +} diff --git a/thehive/app/org/thp/thehive/controllers/v1/DescribeCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/DescribeCtrl.scala index bbbe63fba3..9b2b3df80f 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/DescribeCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/DescribeCtrl.scala @@ -31,7 +31,7 @@ class DescribeCtrl @Inject() ( caseCtrl: CaseCtrl, caseTemplateCtrl: CaseTemplateCtrl, customFieldCtrl: CustomFieldCtrl, -// dashboardCtrl: DashboardCtrl, + dashboardCtrl: DashboardCtrl, logCtrl: LogCtrl, observableCtrl: ObservableCtrl, observableTypeCtrl: ObservableTypeCtrl, @@ -102,7 +102,7 @@ class DescribeCtrl @Inject() ( EntityDescription("case", "listCase", caseCtrl.publicProperties.list.flatMap(propertyToJson("case", _))), EntityDescription("caseTemplate", "listCaseTemplate", caseTemplateCtrl.publicProperties.list.flatMap(propertyToJson("caseTemplate", _))), EntityDescription("customField", "listCustomField", customFieldCtrl.publicProperties.list.flatMap(propertyToJson("customField", _))), - // EntityDescription("dashboard", "listDashboard", dashboardCtrl.publicProperties.list.flatMap(propertyToJson("dashboard", _))), + EntityDescription("dashboard", "listDashboard", dashboardCtrl.publicProperties.list.flatMap(propertyToJson("dashboard", _))), EntityDescription("log", "listLog", logCtrl.publicProperties.list.flatMap(propertyToJson("case_task_log", _))), EntityDescription("observable", "listObservable", observableCtrl.publicProperties.list.flatMap(propertyToJson("observable", _))), EntityDescription( @@ -161,28 +161,8 @@ class DescribeCtrl @Inject() ( ) case ("case", "impactStatus") => Some(Seq(impactStatus)) case ("case", "resolutionStatus") => Some(Seq(resolutionStatus)) -// //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 ("dashboard", "status") => + Some(Seq(PropertyDescription("status", "enumeration", Seq(JsString("Shared"), JsString("Private"), JsString("Deleted"))))) case (_, "tlp") => Some( Seq(PropertyDescription("tlp", "number", Seq(JsNumber(0), JsNumber(1), JsNumber(2), JsNumber(3)), Seq("white", "green", "amber", "red"))) @@ -205,17 +185,7 @@ class DescribeCtrl @Inject() ( 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 + case _ => None } def propertyToJson(model: String, prop: PublicProperty): Seq[PropertyDescription] = diff --git a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala index 4afa5b0ffa..4429c00d0f 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala @@ -1,10 +1,10 @@ package org.thp.thehive.controllers.v1 -import org.thp.scalligraph.controllers.{FPathElem, FPathEmpty} +import org.thp.scalligraph.controllers.{FPathElem, FPathEmpty, FString} import org.thp.scalligraph.models.{Database, UMapping} import org.thp.scalligraph.query.{PublicProperties, PublicPropertyListBuilder} import org.thp.scalligraph.traversal.TraversalOps._ -import org.thp.scalligraph.{BadRequestError, EntityIdOrName, RichSeq} +import org.thp.scalligraph.{BadRequestError, EntityIdOrName, InvalidFormatAttributeError, RichSeq} import org.thp.thehive.dto.v1.InputCustomFieldValue import org.thp.thehive.models._ import org.thp.thehive.services.AlertOps._ @@ -12,6 +12,7 @@ import org.thp.thehive.services.AuditOps._ import org.thp.thehive.services.CaseOps._ import org.thp.thehive.services.CaseTemplateOps._ import org.thp.thehive.services.CustomFieldOps._ +import org.thp.thehive.services.DashboardOps._ import org.thp.thehive.services.LogOps._ import org.thp.thehive.services.ObservableOps._ import org.thp.thehive.services.OrganisationOps._ @@ -34,6 +35,7 @@ class Properties @Inject() ( caseSrv: CaseSrv, taskSrv: TaskSrv, userSrv: UserSrv, + dashboardSrv: DashboardSrv, caseTemplateSrv: CaseTemplateSrv, observableSrv: ObservableSrv, customFieldSrv: CustomFieldSrv, @@ -428,4 +430,35 @@ class Properties @Inject() ( ) .build + lazy val dashboard: PublicProperties = PublicPropertyListBuilder[Dashboard] + .property("title", UMapping.string)(_.field.updatable) + .property("description", UMapping.string)(_.field.updatable) + .property("definition", UMapping.string)(_.field.updatable) + .property("status", UMapping.string)( + _.select(_.choose(_.organisation, "Shared", "Private")) + .custom { + case (_, "Shared", vertex, graph, authContext) => + for { + dashboard <- dashboardSrv.get(vertex)(graph).filter(_.user.current(authContext)).getOrFail("Dashboard") + _ <- dashboardSrv.share(dashboard, authContext.organisation, writable = false)(graph, authContext) + } yield Json.obj("status" -> "Shared") + + case (_, "Private", vertex, graph, authContext) => + for { + d <- dashboardSrv.get(vertex)(graph).filter(_.user.current(authContext)).getOrFail("Dashboard") + _ <- dashboardSrv.unshare(d, authContext.organisation)(graph, authContext) + } yield Json.obj("status" -> "Private") + + case (_, "Deleted", vertex, graph, authContext) => + for { + d <- dashboardSrv.get(vertex)(graph).filter(_.user.current(authContext)).getOrFail("Dashboard") + _ <- dashboardSrv.remove(d)(graph, authContext) + } yield Json.obj("status" -> "Deleted") + + case (_, status, _, _, _) => + Failure(InvalidFormatAttributeError("status", "String", Set("Shared", "Private", "Deleted"), FString(status))) + } + ) + .build + } diff --git a/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala b/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala index 7e91472879..b11fcace4a 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala @@ -38,7 +38,7 @@ class TheHiveQueryExecutor @Inject() ( taskCtrl: TaskCtrl, userCtrl: UserCtrl, taxonomyCtrl: TaxonomyCtrl, - // dashboardCtrl: DashboardCtrl, + dashboardCtrl: DashboardCtrl, properties: Properties, implicit val db: Database ) extends QueryExecutor { @@ -50,7 +50,7 @@ class TheHiveQueryExecutor @Inject() ( caseCtrl, caseTemplateCtrl, customFieldCtrl, -// dashboardCtrl, + dashboardCtrl, logCtrl, observableCtrl, observableTypeCtrl,