From 1d507aa76d17246af2a536b34670281459fbd020 Mon Sep 17 00:00:00 2001 From: To-om Date: Thu, 10 Dec 2020 16:48:55 +0100 Subject: [PATCH] #1454 Add APIs for task, observable and logs --- .../thp/thehive/controllers/v0/Router.scala | 44 ++++----- .../controllers/v1/ObservableCtrl.scala | 97 +++++++++++++------ .../thp/thehive/controllers/v1/Router.scala | 46 +++++++-- 3 files changed, 126 insertions(+), 61 deletions(-) diff --git a/thehive/app/org/thp/thehive/controllers/v0/Router.scala b/thehive/app/org/thp/thehive/controllers/v0/Router.scala index 050122e10d..353b7a55b8 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/Router.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/Router.scala @@ -7,31 +7,31 @@ import play.api.routing.sird._ @Singleton class Router @Inject() ( - statsCtrl: StatsCtrl, + authenticationCtrl: AuthenticationCtrl, + alertCtrl: AlertCtrl, + attachmentCtrl: AttachmentCtrl, + auditCtrl: AuditCtrl, caseCtrl: CaseCtrl, caseTemplateCtrl: CaseTemplateCtrl, - userCtrl: UserCtrl, - organisationCtrl: OrganisationCtrl, - taskCtrl: TaskCtrl, - logCtrl: LogCtrl, - observableCtrl: ObservableCtrl, + configCtrl: ConfigCtrl, customFieldCtrl: CustomFieldCtrl, - alertCtrl: AlertCtrl, - auditCtrl: AuditCtrl, - statusCtrl: StatusCtrl, dashboardCtrl: DashboardCtrl, - authenticationCtrl: AuthenticationCtrl, - listCtrl: ListCtrl, - streamCtrl: StreamCtrl, - attachmentCtrl: AttachmentCtrl, describeCtrl: DescribeCtrl, - configCtrl: ConfigCtrl, - profileCtrl: ProfileCtrl, - shareCtrl: ShareCtrl, - tagCtrl: TagCtrl, + listCtrl: ListCtrl, + logCtrl: LogCtrl, + observableCtrl: ObservableCtrl, + organisationCtrl: OrganisationCtrl, + observableTypeCtrl: ObservableTypeCtrl, pageCtrl: PageCtrl, permissionCtrl: PermissionCtrl, - observableTypeCtrl: ObservableTypeCtrl + profileCtrl: ProfileCtrl, + taskCtrl: TaskCtrl, + shareCtrl: ShareCtrl, + statsCtrl: StatsCtrl, + userCtrl: UserCtrl, + statusCtrl: StatusCtrl, + streamCtrl: StreamCtrl, + tagCtrl: TagCtrl ) extends SimpleRouter { override def routes: Routes = { @@ -86,11 +86,11 @@ class Router @Inject() ( case POST(p"/case/artifact/$observableId/shares") => shareCtrl.shareObservable(observableId) case GET(p"/case") => caseCtrl.search - case POST(p"/case") => caseCtrl.create // Audit ok + case POST(p"/case") => caseCtrl.create // Audit ok case GET(p"/case/$caseId") => caseCtrl.get(caseId) - case PATCH(p"/case/_bulk") => caseCtrl.bulkUpdate // Not used by the frontend - case PATCH(p"/case/$caseId") => caseCtrl.update(caseId) // Audit ok - case POST(p"/case/_merge/$caseIds") => caseCtrl.merge(caseIds) // Not implemented in backend and not used by frontend + case PATCH(p"/case/_bulk") => caseCtrl.bulkUpdate // Not used by the frontend + case PATCH(p"/case/$caseId") => caseCtrl.update(caseId) // Audit ok + case POST(p"/case/_merge/$caseIds") => caseCtrl.merge(caseIds) // Not implemented in backend and not used by frontend case POST(p"/case/_search") => caseCtrl.search case POST(p"/case/_stats") => caseCtrl.stats case DELETE(p"/case/$caseId") => caseCtrl.delete(caseId) // Not used by the frontend diff --git a/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala index f383a7a025..69f39d30fe 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala @@ -2,7 +2,6 @@ package org.thp.thehive.controllers.v1 import java.io.FilterInputStream import java.nio.file.Files - import javax.inject.{Inject, Named, Singleton} import net.lingala.zip4j.ZipFile import net.lingala.zip4j.model.FileHeader @@ -22,6 +21,7 @@ import org.thp.thehive.services.OrganisationOps._ import org.thp.thehive.services.ShareOps._ import org.thp.thehive.services._ import play.api.libs.Files.DefaultTemporaryFileCreator +import play.api.libs.json.{JsArray, JsValue, Json} import play.api.mvc.{Action, AnyContent, Results} import play.api.{Configuration, Logger} @@ -29,7 +29,7 @@ import scala.collection.JavaConverters._ @Singleton class ObservableCtrl @Inject() ( - entryPoint: Entrypoint, + entrypoint: Entrypoint, @Named("with-thehive-schema") db: Database, properties: Properties, observableSrv: ObservableSrv, @@ -37,7 +37,8 @@ class ObservableCtrl @Inject() ( caseSrv: CaseSrv, organisationSrv: OrganisationSrv, temporaryFileCreator: DefaultTemporaryFileCreator, - configuration: Configuration + configuration: Configuration, + errorHandler: ErrorHandler ) extends QueryableCtrl with ObservableRenderer { @@ -79,41 +80,73 @@ class ObservableCtrl @Inject() ( ) def create(caseId: String): Action[AnyContent] = - entryPoint("create artifact") - .extract("artifact", FieldsParser[InputObservable]) + entrypoint("create observable") + .extract("observable", FieldsParser[InputObservable]) .extract("isZip", FieldsParser.boolean.optional.on("isZip")) .extract("zipPassword", FieldsParser.string.optional.on("zipPassword")) - .authTransaction(db) { implicit request => implicit graph => + .auth { implicit request => + val inputObservable: InputObservable = request.body("observable") val isZip: Option[Boolean] = request.body("isZip") val zipPassword: Option[String] = request.body("zipPassword") - val inputObservable: InputObservable = request.body("artifact") val inputAttachObs = if (isZip.contains(true)) getZipFiles(inputObservable, zipPassword) else Seq(inputObservable) - for { - case0 <- - caseSrv - .get(EntityIdOrName(caseId)) - .can(Permissions.manageObservable) - .getOrFail("Case") - observableType <- observableTypeSrv.getOrFail(EntityName(inputObservable.dataType)) - observablesWithData <- - inputObservable - .data - .toTry(d => observableSrv.create(inputObservable.toObservable, observableType, d, inputObservable.tags, Nil)) - observableWithAttachment <- inputAttachObs.toTry( - _.attachment - .map(a => observableSrv.create(inputObservable.toObservable, observableType, a, inputObservable.tags, Nil)) - .flip - ) - createdObservables <- (observablesWithData ++ observableWithAttachment.flatten).toTry { richObservables => - caseSrv - .addObservable(case0, richObservables) - .map(_ => richObservables) + + db + .roTransaction { implicit graph => + for { + case0 <- + caseSrv + .get(EntityIdOrName(caseId)) + .can(Permissions.manageObservable) + .orFail(AuthorizationError("Operation not permitted")) + observableType <- observableTypeSrv.getOrFail(EntityName(inputObservable.dataType)) + } yield (case0, observableType) + } + .map { + case (case0, observableType) => + val initialSuccessesAndFailures: (Seq[JsValue], Seq[JsValue]) = + inputAttachObs.foldLeft[(Seq[JsValue], Seq[JsValue])](Nil -> Nil) { + case ((successes, failures), inputObservable) => + inputObservable.attachment.fold((successes, failures)) { attachment => + db + .tryTransaction { implicit graph => + observableSrv + .create(inputObservable.toObservable, observableType, attachment, inputObservable.tags, Nil) + .flatMap(o => caseSrv.addObservable(case0, o).map(_ => o.toJson)) + } + .fold( + e => + successes -> (failures :+ errorHandler.toErrorResult(e)._2 ++ Json + .obj( + "object" -> Json + .obj("data" -> s"file:${attachment.filename}", "attachment" -> Json.obj("name" -> attachment.filename)) + )), + s => (successes :+ s) -> failures + ) + } + } + + val (successes, failures) = inputObservable + .data + .foldLeft(initialSuccessesAndFailures) { + case ((successes, failures), data) => + db + .tryTransaction { implicit graph => + observableSrv + .create(inputObservable.toObservable, observableType, data, inputObservable.tags, Nil) + .flatMap(o => caseSrv.addObservable(case0, o).map(_ => o.toJson)) + } + .fold( + failure => (successes, failures :+ errorHandler.toErrorResult(failure)._2 ++ Json.obj("object" -> Json.obj("data" -> data))), + success => (successes :+ success, failures) + ) + } + if (failures.isEmpty) Results.Created(JsArray(successes)) + else Results.MultiStatus(Json.obj("success" -> successes, "failure" -> failures)) } - } yield Results.Created(createdObservables.toJson) } def get(observableId: String): Action[AnyContent] = - entryPoint("get observable") + entrypoint("get observable") .authRoTransaction(db) { _ => implicit graph => observableSrv .get(EntityIdOrName(observableId)) @@ -126,7 +159,7 @@ class ObservableCtrl @Inject() ( } def update(observableId: String): Action[AnyContent] = - entryPoint("update observable") + entrypoint("update observable") .extract("observable", FieldsParser.update("observable", publicProperties)) .authTransaction(db) { implicit request => implicit graph => val propertyUpdaters: Seq[PropertyUpdater] = request.body("observable") @@ -139,7 +172,7 @@ class ObservableCtrl @Inject() ( } def bulkUpdate: Action[AnyContent] = - entryPoint("bulk update") + entrypoint("bulk update") .extract("input", FieldsParser.update("observable", publicProperties)) .extract("ids", FieldsParser.seq[String].on("ids")) .authTransaction(db) { implicit request => implicit graph => @@ -154,7 +187,7 @@ class ObservableCtrl @Inject() ( } def delete(obsId: String): Action[AnyContent] = - entryPoint("delete") + entrypoint("delete") .authTransaction(db) { implicit request => implicit graph => for { observable <- diff --git a/thehive/app/org/thp/thehive/controllers/v1/Router.scala b/thehive/app/org/thp/thehive/controllers/v1/Router.scala index feffe865bb..d6f6e97945 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Router.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Router.scala @@ -7,17 +7,29 @@ import play.api.routing.sird._ @Singleton class Router @Inject() ( + authenticationCtrl: AuthenticationCtrl, + alertCtrl: AlertCtrl, + // attachmentCtrl: AttachmentCtrl, + auditCtrl: AuditCtrl, caseCtrl: CaseCtrl, caseTemplateCtrl: CaseTemplateCtrl, - userCtrl: UserCtrl, + // configCtrl: ConfigCtrl, + customFieldCtrl: CustomFieldCtrl, + // dashboardCtrl: DashboardCtrl, + describeCtrl: DescribeCtrl, + logCtrl: LogCtrl, + observableCtrl: ObservableCtrl, + observableTypeCtrl: ObservableTypeCtrl, organisationCtrl: OrganisationCtrl, + // pageCtrl: PageCtrl, + // permissionCtrl: PermissionCtrl, + profileCtrl: ProfileCtrl, taskCtrl: TaskCtrl, - customFieldCtrl: CustomFieldCtrl, - alertCtrl: AlertCtrl, - auditCtrl: AuditCtrl, - statusCtrl: StatusCtrl, - authenticationCtrl: AuthenticationCtrl, - describeCtrl: DescribeCtrl + // shareCtrl: ShareCtrl, + userCtrl: UserCtrl, + statusCtrl: StatusCtrl + // streamCtrl: StreamCtrl, + // tagCtrl: TagCtrl ) extends SimpleRouter { override def routes: Routes = { @@ -40,6 +52,14 @@ class Router @Inject() ( // case POST(p"/case/_stats") => caseCtrl.stats() // case GET(p"/case/$caseId/links") => caseCtrl.linkedCases(caseId) + case POST(p"/case/$caseId/observable") => observableCtrl.create(caseId) + case GET(p"/observable/$observableId") => observableCtrl.get(observableId) + case DELETE(p"/observable/$observableId") => observableCtrl.delete(observableId) + case PATCH(p"/observable/_bulk") => observableCtrl.bulkUpdate + case PATCH(p"/observable/$observableId") => observableCtrl.update(observableId) +// case GET(p"/observable/$observableId/similar") => observableCtrl.findSimilar(observableId) +// case POST(p"/observable/$observableId/shares") => shareCtrl.shareObservable(observableId) + case GET(p"/caseTemplate") => caseTemplateCtrl.list case POST(p"/caseTemplate") => caseTemplateCtrl.create case GET(p"/caseTemplate/$caseTemplateId") => caseTemplateCtrl.get(caseTemplateId) @@ -75,6 +95,10 @@ class Router @Inject() ( // POST /case/:caseId/task/_search controllers.TaskCtrl.findInCase(caseId) // POST /case/task/_stats controllers.TaskCtrl.stats() + case POST(p"/task/$taskId/log") => logCtrl.create(taskId) + case PATCH(p"/log/$logId") => logCtrl.update(logId) + case DELETE(p"/log/$logId") => logCtrl.delete(logId) + case GET(p"/customField") => customFieldCtrl.list case POST(p"/customField") => customFieldCtrl.create @@ -96,8 +120,16 @@ class Router @Inject() ( // POST /audit/_search controllers.AuditCtrl.find() // POST /audit/_stats controllers.AuditCtrl.stats() + case POST(p"/profile") => profileCtrl.create + case GET(p"/profile/$profileId") => profileCtrl.get(profileId) + case PATCH(p"/profile/$profileId") => profileCtrl.update(profileId) + case DELETE(p"/profile/$profileId") => profileCtrl.delete(profileId) + case GET(p"/describe/_all") => describeCtrl.describeAll case GET(p"/describe/$modelName") => describeCtrl.describe(modelName) + case GET(p"/observable/type/$idOrName") => observableTypeCtrl.get(idOrName) + case POST(p"/observable/type") => observableTypeCtrl.create + case DELETE(p"/observable/type/$idOrName") => observableTypeCtrl.delete(idOrName) } }