diff --git a/thehive-backend/app/controllers/CaseReportingTemplateCtrl.scala b/thehive-backend/app/controllers/CaseReportingTemplateCtrl.scala new file mode 100644 index 0000000000..d6767138fa --- /dev/null +++ b/thehive-backend/app/controllers/CaseReportingTemplateCtrl.scala @@ -0,0 +1,106 @@ +package controllers + +import javax.inject.{ Inject, Singleton } + +import scala.concurrent.{ ExecutionContext, Future } +import scala.util.control.NonFatal +import scala.io.Source + +import play.api.Logger +import play.api.http.Status +import play.api.mvc._ +import play.api.libs.json.{ JsTrue, JsFalse } + +import models.Roles +import services.CaseReportingTemplateSrv + +import org.elastic4play.{ BadRequestError, Timed } +import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer, FileInputValue } +import org.elastic4play.models.JsonFormat.baseModelEntityWrites +import org.elastic4play.services.JsonFormat.queryReads +import org.elastic4play.services.{ AuxSrv, QueryDSL, QueryDef } + +@Singleton +class CaseReportingTemplateCtrl @Inject() ( + caseReportingTemplateSrv: CaseReportingTemplateSrv, + auxSrv: AuxSrv, + authenticated: Authenticated, + renderer: Renderer, + components: ControllerComponents, + fieldsBodyParser: FieldsBodyParser, + implicit val ec: ExecutionContext) extends AbstractController(components) with Status { + + private[CaseReportingTemplateCtrl] lazy val logger = Logger(getClass) + + @Timed + def create: Action[Fields] = authenticated(Roles.admin).async(fieldsBodyParser) { implicit request ⇒ + caseReportingTemplateSrv.create(request.body) + .map(caseReporting ⇒ renderer.toOutput(CREATED, caseReporting)) + } + + @Timed + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + caseReportingTemplateSrv.get(id) + .map(caseReporting ⇒ renderer.toOutput(OK, caseReporting)) + } + + @Timed + def update(id: String): Action[Fields] = authenticated(Roles.admin).async(fieldsBodyParser) { implicit request ⇒ + val updates = Fields.empty + .set("title", request.body.getString("title").getOrElse("")) + .set("content", request.body.getString("content").getOrElse("")) + .set("isDefault", request.body.getValue("isDefault").getOrElse(JsFalse)) + + caseReportingTemplateSrv.update(id, updates) + .map(caseReporting ⇒ renderer.toOutput(OK, caseReporting)) + } + + @Timed + def delete(id: String): Action[AnyContent] = authenticated(Roles.admin).async { implicit request ⇒ + caseReportingTemplateSrv.delete(id) + .map(_ ⇒ NoContent) + } + + @Timed + def find: Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { implicit request ⇒ + val query = request.body.getValue("query").fold[QueryDef](QueryDSL.any)(_.as[QueryDef]) + val range = request.body.getString("range") + val sort = request.body.getStrings("sort").getOrElse(Nil) + val nparent = request.body.getLong("nparent").getOrElse(0L).toInt + val withStats = request.body.getBoolean("nstats").getOrElse(false) + + val (caseReportingTemplates, total) = caseReportingTemplateSrv.find(query, range, sort) + val caseReportingTemplatesWithStats = auxSrv(caseReportingTemplates, nparent, withStats, removeUnaudited = false) + renderer.toOutput(OK, caseReportingTemplatesWithStats, total) + } + + @Timed + def importTemplate: Action[Fields] = authenticated(Roles.write).async(fieldsBodyParser) { implicit request ⇒ + val file = request.body.get("template") match { + case Some(FileInputValue(_, filepath, _)) ⇒ Source.fromFile(filepath.toFile) + case _ ⇒ throw BadRequestError("") + } + val title = request.body.get("template") + + val templateTitle = title.toString().split('(')(2).split(',')(0) + val templateContent = file.mkString + + val caseReportingTemplateFields = Fields.empty + .set("title", templateTitle) + .set("content", templateContent) + .set("isDefault", JsFalse) + caseReportingTemplateSrv.create(caseReportingTemplateFields) + .recoverWith { + case NonFatal(_) ⇒ + caseReportingTemplateSrv.update(templateTitle, Fields.empty.set("content", templateContent)) + } + .map(_.id → JsTrue) + .recoverWith { + case NonFatal(e) ⇒ + logger.error(s"The import of the case reporting template $templateTitle has failed", e) + Future.successful(templateTitle → JsFalse) + } + Future { renderer.toOutput(OK, templateTitle) } + } +} + diff --git a/thehive-backend/app/models/CaseReportingTemplate.scala b/thehive-backend/app/models/CaseReportingTemplate.scala new file mode 100644 index 0000000000..f500e4adaa --- /dev/null +++ b/thehive-backend/app/models/CaseReportingTemplate.scala @@ -0,0 +1,19 @@ +package models + +import javax.inject.{ Inject, Singleton } + +import play.api.libs.json.JsObject + +import org.elastic4play.models.{ AttributeDef, EntityDef, ModelDef, AttributeFormat ⇒ F } +trait CaseReportingTemplateAttributes { _: AttributeDef ⇒ + + val title: A[String] = attribute("title", F.stringFmt, "Title of the template") + val content = attribute("content", F.textFmt, "Content of the template") + val isDefault = attribute("isDefault", F.booleanFmt, "Is the template set as default") +} + +@Singleton +class CaseReportingTemplateModel @Inject() extends ModelDef[CaseReportingTemplateModel, CaseReportingTemplate]("caseReportingTemplate", "Case reporting template", "/case/reporting") with CaseReportingTemplateAttributes { + +} +class CaseReportingTemplate(model: CaseReportingTemplateModel, attributes: JsObject) extends EntityDef[CaseReportingTemplateModel, CaseReportingTemplate](model, attributes) with CaseReportingTemplateAttributes diff --git a/thehive-backend/app/services/CaseReportingTemplateSrv.scala b/thehive-backend/app/services/CaseReportingTemplateSrv.scala new file mode 100644 index 0000000000..10e30d8606 --- /dev/null +++ b/thehive-backend/app/services/CaseReportingTemplateSrv.scala @@ -0,0 +1,54 @@ +package services + +import javax.inject.{ Inject, Singleton } + +import scala.concurrent.{ ExecutionContext, Future } + +import akka.NotUsed +import akka.stream.Materializer +import akka.stream.scaladsl.{ Sink, Source } +import models.{ CaseReportingTemplate, CaseReportingTemplateModel } + +import org.elastic4play.NotFoundError +import org.elastic4play.controllers.Fields +import org.elastic4play.database.ModifyConfig +import org.elastic4play.services._ + +@Singleton +class CaseReportingTemplateSrv @Inject() ( + caseReportingTemplateModel: CaseReportingTemplateModel, + createSrv: CreateSrv, + getSrv: GetSrv, + updateSrv: UpdateSrv, + deleteSrv: DeleteSrv, + findSrv: FindSrv, + implicit val ec: ExecutionContext, + implicit val mat: Materializer) { + + def create(fields: Fields)(implicit authContext: AuthContext): Future[CaseReportingTemplate] = + createSrv[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, fields) + + def get(id: String): Future[CaseReportingTemplate] = + getSrv[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, id) + + def getByName(name: String): Future[CaseReportingTemplate] = { + import org.elastic4play.services.QueryDSL._ + findSrv[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, "name" ~= name, Some("0-1"), Nil) + ._1 + .runWith(Sink.headOption) + .map(_.getOrElse(throw NotFoundError(s"Case reporting template $name not found"))) + } + + def update(id: String, fields: Fields)(implicit authContext: AuthContext): Future[CaseReportingTemplate] = + update(id, fields, ModifyConfig.default) + + def update(id: String, fields: Fields, modifyConfig: ModifyConfig)(implicit authContext: AuthContext): Future[CaseReportingTemplate] = + updateSrv[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, id, fields, modifyConfig) + + def delete(id: String)(implicit authContext: AuthContext): Future[Unit] = + deleteSrv.realDelete[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, id) + + def find(queryDef: QueryDef, range: Option[String], sortBy: Seq[String]): (Source[CaseReportingTemplate, NotUsed], Future[Long]) = { + findSrv[CaseReportingTemplateModel, CaseReportingTemplate](caseReportingTemplateModel, queryDef, range, sortBy) + } +} diff --git a/thehive-backend/conf/routes b/thehive-backend/conf/routes index d76d2cfd7d..eb2f960b11 100644 --- a/thehive-backend/conf/routes +++ b/thehive-backend/conf/routes @@ -1,123 +1,132 @@ -# Routes -# This file defines all application routes (Higher priority routes first) -# ~~~~ - -GET / controllers.Default.redirect(to = "/index.html") -GET /api/status controllers.StatusCtrl.get -GET /api/health controllers.StatusCtrl.health -GET /api/logout controllers.AuthenticationCtrl.logout() -POST /api/login controllers.AuthenticationCtrl.login() -POST /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() - -POST /api/_search controllers.SearchCtrl.find() -POST /api/_stats controllers.SearchCtrl.stats() - -GET /api/case controllers.CaseCtrl.find() -POST /api/case/_search controllers.CaseCtrl.find() -PATCH /api/case/_bulk controllers.CaseCtrl.bulkUpdate() -POST /api/case/_stats controllers.CaseCtrl.stats() -POST /api/case controllers.CaseCtrl.create() -GET /api/case/:caseId controllers.CaseCtrl.get(caseId) -PATCH /api/case/:caseId controllers.CaseCtrl.update(caseId) -DELETE /api/case/:caseId controllers.CaseCtrl.delete(caseId) -DELETE /api/case/:caseId/force controllers.CaseCtrl.realDelete(caseId) -GET /api/case/:caseId/links controllers.CaseCtrl.linkedCases(caseId) -POST /api/case/:caseId1/_merge/:caseId2 controllers.CaseCtrl.merge(caseId1, caseId2) - -POST /api/case/template/_search controllers.CaseTemplateCtrl.find() -POST /api/case/template controllers.CaseTemplateCtrl.create() -GET /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.get(caseTemplateId) -PATCH /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.update(caseTemplateId) -DELETE /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.delete(caseTemplateId) - -POST /api/case/artifact/_search controllers.ArtifactCtrl.find() -POST /api/case/:caseId/artifact/_search controllers.ArtifactCtrl.findInCase(caseId) -POST /api/case/artifact/_stats controllers.ArtifactCtrl.stats() -POST /api/case/:caseId/artifact controllers.ArtifactCtrl.create(caseId) -GET /api/case/artifact/:artifactId controllers.ArtifactCtrl.get(artifactId) -DELETE /api/case/artifact/:artifactId controllers.ArtifactCtrl.delete(artifactId) -PATCH /api/case/artifact/_bulk controllers.ArtifactCtrl.bulkUpdate() -PATCH /api/case/artifact/:artifactId controllers.ArtifactCtrl.update(artifactId) -GET /api/case/artifact/:artifactId/similar controllers.ArtifactCtrl.findSimilar(artifactId) - -POST /api/case/:caseId/task/_search controllers.TaskCtrl.findInCase(caseId) -POST /api/case/task/_search controllers.TaskCtrl.find() -POST /api/case/task/_stats controllers.TaskCtrl.stats() -GET /api/case/task/:taskId controllers.TaskCtrl.get(taskId) -PATCH /api/case/task/:taskId controllers.TaskCtrl.update(taskId) -POST /api/case/:caseId/task controllers.TaskCtrl.create(caseId) - -GET /api/case/task/:taskId/log controllers.LogCtrl.findInTask(taskId) -POST /api/case/task/:taskId/log/_search controllers.LogCtrl.findInTask(taskId) -POST /api/case/task/log/_search controllers.LogCtrl.find() -POST /api/case/task/:taskId/log controllers.LogCtrl.create(taskId) -PATCH /api/case/task/log/:logId controllers.LogCtrl.update(logId) -DELETE /api/case/task/log/:logId controllers.LogCtrl.delete(logId) -GET /api/case/task/log/:logId controllers.LogCtrl.get(logId) - -GET /api/alert controllers.AlertCtrl.find() -POST /api/alert/_search controllers.AlertCtrl.find() -PATCH /api/alert/_bulk controllers.AlertCtrl.bulkUpdate() -POST /api/alert/_stats controllers.AlertCtrl.stats() -GET /api/alert/_fixStatus controllers.AlertCtrl.fixStatus() -POST /api/alert controllers.AlertCtrl.create() -GET /api/alert/:alertId controllers.AlertCtrl.get(alertId) -PATCH /api/alert/:alertId controllers.AlertCtrl.update(alertId) -DELETE /api/alert/:alertId controllers.AlertCtrl.delete(alertId) -POST /api/alert/:alertId/markAsRead controllers.AlertCtrl.markAsRead(alertId) -POST /api/alert/:alertId/markAsUnread controllers.AlertCtrl.markAsUnread(alertId) -POST /api/alert/:alertId/createCase controllers.AlertCtrl.createCase(alertId) -POST /api/alert/:alertId/follow controllers.AlertCtrl.followAlert(alertId) -POST /api/alert/:alertId/unfollow controllers.AlertCtrl.unfollowAlert(alertId) -POST /api/alert/:alertId/merge/:caseId controllers.AlertCtrl.mergeWithCase(alertId, caseId) - -GET /api/flow controllers.AuditCtrl.flow(rootId: Option[String], count: Option[Int]) -GET /api/audit controllers.AuditCtrl.find() -POST /api/audit/_search controllers.AuditCtrl.find() -POST /api/audit/_stats controllers.AuditCtrl.stats() - -GET /api/datastore/:hash controllers.AttachmentCtrl.download(hash, name: Option[String]) -GET /api/datastorezip/:hash controllers.AttachmentCtrl.downloadZip(hash, name: Option[String]) - -POST /api/maintenance/migrate org.elastic4play.controllers.MigrationCtrl.migrate -#POST /api/maintenance/rehash controllers.MaintenanceCtrl.reHash - -GET /api/list org.elastic4play.controllers.DBListCtrl.list() -DELETE /api/list/:itemId org.elastic4play.controllers.DBListCtrl.deleteItem(itemId) -PATCH /api/list/:itemId org.elastic4play.controllers.DBListCtrl.updateItem(itemId) -POST /api/list/:listName org.elastic4play.controllers.DBListCtrl.addItem(listName) -GET /api/list/:listName org.elastic4play.controllers.DBListCtrl.listItems(listName) -POST /api/list/:listName/_exists org.elastic4play.controllers.DBListCtrl.itemExists(listName) - - -GET /api/user/current controllers.UserCtrl.currentUser() -POST /api/user/_search controllers.UserCtrl.find() -POST /api/user controllers.UserCtrl.create() -GET /api/user/:userId controllers.UserCtrl.get(userId) -DELETE /api/user/:userId controllers.UserCtrl.delete(userId) -PATCH /api/user/:userId controllers.UserCtrl.update(userId) -POST /api/user/:userId/password/set controllers.UserCtrl.setPassword(userId) -POST /api/user/:userId/password/change controllers.UserCtrl.changePassword(userId) -GET /api/user/:userId/key controllers.UserCtrl.getKey(userId) -DELETE /api/user/:userId/key controllers.UserCtrl.removeKey(userId) -POST /api/user/:userId/key/renew controllers.UserCtrl.renewKey(userId) - - -POST /api/stream controllers.StreamCtrl.create() -GET /api/stream/status controllers.StreamCtrl.status -GET /api/stream/:streamId controllers.StreamCtrl.get(streamId) - -GET /api/describe/_all controllers.DescribeCtrl.describeAll -GET /api/describe/:modelName controllers.DescribeCtrl.describe(modelName) - -GET /api/dashboard controllers.DashboardCtrl.find() -POST /api/dashboard/_search controllers.DashboardCtrl.find() -POST /api/dashboard/_stats controllers.DashboardCtrl.stats() -POST /api/dashboard controllers.DashboardCtrl.create() -GET /api/dashboard/:dashboardId controllers.DashboardCtrl.get(dashboardId) -PATCH /api/dashboard/:dashboardId controllers.DashboardCtrl.update(dashboardId) -DELETE /api/dashboard/:dashboardId controllers.DashboardCtrl.delete(dashboardId) - --> /api/connector connectors.ConnectorRouter - -GET /*file controllers.AssetCtrl.get(file) +# Routes +# This file defines all application routes (Higher priority routes first) +# ~~~~ + +GET / controllers.Default.redirect(to = "/index.html") +GET /api/status controllers.StatusCtrl.get +GET /api/health controllers.StatusCtrl.health +GET /api/logout controllers.AuthenticationCtrl.logout() +POST /api/login controllers.AuthenticationCtrl.login() +POST /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() + +POST /api/_search controllers.SearchCtrl.find() +POST /api/_stats controllers.SearchCtrl.stats() + +GET /api/case controllers.CaseCtrl.find() +POST /api/case/_search controllers.CaseCtrl.find() +PATCH /api/case/_bulk controllers.CaseCtrl.bulkUpdate() +POST /api/case/_stats controllers.CaseCtrl.stats() +POST /api/case controllers.CaseCtrl.create() +GET /api/case/:caseId controllers.CaseCtrl.get(caseId) +PATCH /api/case/:caseId controllers.CaseCtrl.update(caseId) +DELETE /api/case/:caseId controllers.CaseCtrl.delete(caseId) +DELETE /api/case/:caseId/force controllers.CaseCtrl.realDelete(caseId) +GET /api/case/:caseId/links controllers.CaseCtrl.linkedCases(caseId) +POST /api/case/:caseId1/_merge/:caseId2 controllers.CaseCtrl.merge(caseId1, caseId2) + +POST /api/case/template/_search controllers.CaseTemplateCtrl.find() +POST /api/case/template controllers.CaseTemplateCtrl.create() +GET /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.get(caseTemplateId) +PATCH /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.update(caseTemplateId) +DELETE /api/case/template/:caseTemplateId controllers.CaseTemplateCtrl.delete(caseTemplateId) + +GET /api/case/reporting/template/_search controllers.CaseReportingTemplateCtrl.find() +POST /api/case/reporting/template/_search controllers.CaseReportingTemplateCtrl.find() +POST /api/case/reporting/template controllers.CaseReportingTemplateCtrl.create() +GET /api/case/reporting/template/:templateId controllers.CaseReportingTemplateCtrl.get(templateId) +PATCH /api/case/reporting/template/:templateId controllers.CaseReportingTemplateCtrl.update(templateId) +DELETE /api/case/reporting/template/:templateId controllers.CaseReportingTemplateCtrl.delete(templateId) +POST /api/case/reporting/template/_import controllers.CaseReportingTemplateCtrl.importTemplate() + +GET /api/case/artifact/_search controllers.ArtifactCtrl.find() +POST /api/case/artifact/_search controllers.ArtifactCtrl.find() +POST /api/case/:caseId/artifact/_search controllers.ArtifactCtrl.findInCase(caseId) +POST /api/case/artifact/_stats controllers.ArtifactCtrl.stats() +POST /api/case/:caseId/artifact controllers.ArtifactCtrl.create(caseId) +GET /api/case/artifact/:artifactId controllers.ArtifactCtrl.get(artifactId) +DELETE /api/case/artifact/:artifactId controllers.ArtifactCtrl.delete(artifactId) +PATCH /api/case/artifact/_bulk controllers.ArtifactCtrl.bulkUpdate() +PATCH /api/case/artifact/:artifactId controllers.ArtifactCtrl.update(artifactId) +GET /api/case/artifact/:artifactId/similar controllers.ArtifactCtrl.findSimilar(artifactId) + +POST /api/case/:caseId/task/_search controllers.TaskCtrl.findInCase(caseId) +POST /api/case/task/_search controllers.TaskCtrl.find() +POST /api/case/task/_stats controllers.TaskCtrl.stats() +GET /api/case/task/:taskId controllers.TaskCtrl.get(taskId) +PATCH /api/case/task/:taskId controllers.TaskCtrl.update(taskId) +POST /api/case/:caseId/task controllers.TaskCtrl.create(caseId) + +GET /api/case/task/:taskId/log controllers.LogCtrl.findInTask(taskId) +POST /api/case/task/:taskId/log/_search controllers.LogCtrl.findInTask(taskId) +POST /api/case/task/log/_search controllers.LogCtrl.find() +POST /api/case/task/:taskId/log controllers.LogCtrl.create(taskId) +PATCH /api/case/task/log/:logId controllers.LogCtrl.update(logId) +DELETE /api/case/task/log/:logId controllers.LogCtrl.delete(logId) +GET /api/case/task/log/:logId controllers.LogCtrl.get(logId) + +GET /api/alert controllers.AlertCtrl.find() +POST /api/alert/_search controllers.AlertCtrl.find() +PATCH /api/alert/_bulk controllers.AlertCtrl.bulkUpdate() +POST /api/alert/_stats controllers.AlertCtrl.stats() +GET /api/alert/_fixStatus controllers.AlertCtrl.fixStatus() +POST /api/alert controllers.AlertCtrl.create() +GET /api/alert/:alertId controllers.AlertCtrl.get(alertId) +PATCH /api/alert/:alertId controllers.AlertCtrl.update(alertId) +DELETE /api/alert/:alertId controllers.AlertCtrl.delete(alertId) +POST /api/alert/:alertId/markAsRead controllers.AlertCtrl.markAsRead(alertId) +POST /api/alert/:alertId/markAsUnread controllers.AlertCtrl.markAsUnread(alertId) +POST /api/alert/:alertId/createCase controllers.AlertCtrl.createCase(alertId) +POST /api/alert/:alertId/follow controllers.AlertCtrl.followAlert(alertId) +POST /api/alert/:alertId/unfollow controllers.AlertCtrl.unfollowAlert(alertId) +POST /api/alert/:alertId/merge/:caseId controllers.AlertCtrl.mergeWithCase(alertId, caseId) + +GET /api/flow controllers.AuditCtrl.flow(rootId: Option[String], count: Option[Int]) +GET /api/audit controllers.AuditCtrl.find() +POST /api/audit/_search controllers.AuditCtrl.find() +POST /api/audit/_stats controllers.AuditCtrl.stats() + +GET /api/datastore/:hash controllers.AttachmentCtrl.download(hash, name: Option[String]) +GET /api/datastorezip/:hash controllers.AttachmentCtrl.downloadZip(hash, name: Option[String]) + +POST /api/maintenance/migrate org.elastic4play.controllers.MigrationCtrl.migrate +#POST /api/maintenance/rehash controllers.MaintenanceCtrl.reHash + +GET /api/list org.elastic4play.controllers.DBListCtrl.list() +DELETE /api/list/:itemId org.elastic4play.controllers.DBListCtrl.deleteItem(itemId) +PATCH /api/list/:itemId org.elastic4play.controllers.DBListCtrl.updateItem(itemId) +POST /api/list/:listName org.elastic4play.controllers.DBListCtrl.addItem(listName) +GET /api/list/:listName org.elastic4play.controllers.DBListCtrl.listItems(listName) +POST /api/list/:listName/_exists org.elastic4play.controllers.DBListCtrl.itemExists(listName) + + +GET /api/user/current controllers.UserCtrl.currentUser() +POST /api/user/_search controllers.UserCtrl.find() +POST /api/user controllers.UserCtrl.create() +GET /api/user/:userId controllers.UserCtrl.get(userId) +DELETE /api/user/:userId controllers.UserCtrl.delete(userId) +PATCH /api/user/:userId controllers.UserCtrl.update(userId) +POST /api/user/:userId/password/set controllers.UserCtrl.setPassword(userId) +POST /api/user/:userId/password/change controllers.UserCtrl.changePassword(userId) +GET /api/user/:userId/key controllers.UserCtrl.getKey(userId) +DELETE /api/user/:userId/key controllers.UserCtrl.removeKey(userId) +POST /api/user/:userId/key/renew controllers.UserCtrl.renewKey(userId) + + +POST /api/stream controllers.StreamCtrl.create() +GET /api/stream/status controllers.StreamCtrl.status +GET /api/stream/:streamId controllers.StreamCtrl.get(streamId) + +GET /api/describe/_all controllers.DescribeCtrl.describeAll +GET /api/describe/:modelName controllers.DescribeCtrl.describe(modelName) + +GET /api/dashboard controllers.DashboardCtrl.find() +POST /api/dashboard/_search controllers.DashboardCtrl.find() +POST /api/dashboard/_stats controllers.DashboardCtrl.stats() +POST /api/dashboard controllers.DashboardCtrl.create() +GET /api/dashboard/:dashboardId controllers.DashboardCtrl.get(dashboardId) +PATCH /api/dashboard/:dashboardId controllers.DashboardCtrl.update(dashboardId) +DELETE /api/dashboard/:dashboardId controllers.DashboardCtrl.delete(dashboardId) + +-> /api/connector connectors.ConnectorRouter + +GET /*file controllers.AssetCtrl.get(file) diff --git a/ui/app/index.html b/ui/app/index.html index b50b9cafe9..87149403db 100644 --- a/ui/app/index.html +++ b/ui/app/index.html @@ -113,7 +113,6 @@ - @@ -140,6 +139,7 @@ + @@ -164,6 +164,7 @@ + @@ -176,6 +177,7 @@ + @@ -238,6 +240,7 @@ + diff --git a/ui/app/scripts/app.js b/ui/app/scripts/app.js index 4c3a6d7108..d94f5a4d13 100644 --- a/ui/app/scripts/app.js +++ b/ui/app/scripts/app.js @@ -192,6 +192,13 @@ angular.module('thehive', ['ngAnimate', 'ngMessages', 'ngSanitize', 'ui.bootstra } } }) + .state('app.administration.case-reporting-templates', { + url: '/case-reporting-templates', + templateUrl: 'views/partials/admin/case-reporting-templates.html', + controller: 'AdminCaseReportingTemplatesCtrl', + controllerAs: 'vm', + title: 'Case Reporting Templates administration' + }) .state('app.administration.report-templates', { url: '/report-templates', templateUrl: 'views/partials/admin/report-templates.html', diff --git a/ui/app/scripts/controllers/admin/AdminCaseReportingTemplatesCtrl.js b/ui/app/scripts/controllers/admin/AdminCaseReportingTemplatesCtrl.js new file mode 100644 index 0000000000..625057f8a1 --- /dev/null +++ b/ui/app/scripts/controllers/admin/AdminCaseReportingTemplatesCtrl.js @@ -0,0 +1,195 @@ +(function () { + 'use strict'; + + angular.module('theHiveControllers') + .controller('AdminCaseReportingTemplatesCtrl', AdminCaseReportingTemplatesCtrl) + .controller('AdminCaseReportingTemplateDialogCtrl', AdminCaseReportingTemplateDialogCtrl) + .controller('AdminCaseReportingTemplateImportCtrl', AdminCaseReportingTemplateImportCtrl) + .controller('AdminCaseReportingTemplateMakeDefaultCtrl', AdminCaseReportingTemplateMakeDefaultCtrl) + .controller('AdminCaseReportingTemplateDeleteCtrl', AdminCaseReportingTemplateDeleteCtrl); + + function AdminCaseReportingTemplatesCtrl($q, $uibModal, CaseReportingTemplateSrv, NotificationSrv){ + var self = this; + + this.templates = []; + this.templateTitles = []; + this.templateCount = 0; + + this.load = function(){ + $q.all([ + CaseReportingTemplateSrv.list() + ]).then(function (response) { + self.templates = response[0]; + + self.templateTitles = []; + for (var ii = 0; ii < self.templates.length; ++ii) { + self.templateTitles.push(self.templates[ii].title); + } + self.templateCount = self.templateTitles.length; + return $q.resolve(self.templates); + }, function(rejection){ + NotificationSrv.error('AdminCaseReportingTemplatesCtrl', rejection.data, rejection.status); + }) + }; + + this.showTemplate = function (template) { + var modalInstance = $uibModal.open({ + templateUrl: 'views/partials/admin/case-reporting-template-dialog.html', + controller: 'AdminCaseReportingTemplateDialogCtrl', + controllerAs: 'vm', + size: 'max', + resolve: { + template: function () { + return template; + } + } + }); + + modalInstance.result.then(function() { + self.load(); + }); + }; + + this.deleteTemplate = function(template) { + var modalInstance = $uibModal.open({ + templateUrl: 'views/partials/admin/case-reporting-template-delete.html', + controller: 'AdminCaseReportingTemplateDeleteCtrl', + controllerAs: 'vm', + size: '', + resolve: { + template: function() { + return template; + } + } + }); + + modalInstance.result.then(function() { + self.load(); + }); + }; + + this.makeDefaultTemplate = function(template, templates) { + var modalInstance = $uibModal.open({ + templateUrl: 'views/partials/admin/case-reporting-template-make-default.html', + controller: 'AdminCaseReportingTemplateMakeDefaultCtrl', + controllerAs: 'vm', + size: '', + resolve: { + template: function() { + return template; + }, + templates: function() { + return templates; + } + } + }); + + modalInstance.result.then(function() { + self.load(); + }); + }; + + this.import = function () { + var modalInstance = $uibModal.open({ + animation: true, + templateUrl: 'views/partials/admin/case-reporting-template-import.html', + controller: 'AdminCaseReportingTemplateImportCtrl', + controllerAs: 'vm', + size: 'lg' + }); + + modalInstance.result.then(function() { + self.load(); + }); + }; + + this.load(); + } + + + function AdminCaseReportingTemplateDialogCtrl($uibModalInstance, CaseReportingTemplateSrv, NotificationSrv, template) { + this.template = template; + this.editorOptions = { + useWrapMode: true, + showGutter: true + }; + + this.formData = _.pick(template, 'id', 'title', 'content'); + + this.cancel = function () { + $uibModalInstance.dismiss(); + }; + + this.ok = function() { + this.template.content = this.formData.content; + CaseReportingTemplateSrv.update(this.template.id, this.template) + .then(function() { + $uibModalInstance.close(); + }, function(response) { + NotificationSrv.error('AdminCaseReportingTemplateDialogCtrl', response.data, response.status); + }); + }; + } + + function AdminCaseReportingTemplateDeleteCtrl($uibModalInstance, CaseReportingTemplateSrv, NotificationSrv, template) { + this.template = template; + + this.ok = function () { + CaseReportingTemplateSrv.delete(template.id) + .then(function() { + $uibModalInstance.close(); + }, function(response) { + NotificationSrv.error('AdminCaseReportingTemplateDeleteCtrl', response.data, response.status); + }); + }; + this.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } + + function AdminCaseReportingTemplateMakeDefaultCtrl($uibModalInstance, CaseReportingTemplateSrv, NotificationSrv, template, templates) { + this.template = template; + this.templates = templates; + + this.ok = function () { + for (var ii = 0; ii < this.templates.length; ++ii) { + this.templates[ii].isDefault = false; + CaseReportingTemplateSrv.update(this.templates[ii].id, this.templates[ii]) + .then(function() { + + }, + function(response) { + NotificationSrv.error('AdminCaseReportingTemplateMakeDefaultCtrl', response.data, response.status); + }); + } + + template.isDefault = true; + CaseReportingTemplateSrv.update(template.id, template) + .then(function() { + $uibModalInstance.close(); + }, function(response) { + NotificationSrv.error('AdminCaseReportingTemplateMakeDefaultCtrl', response.data, response.status); + }); + }; + this.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } + + function AdminCaseReportingTemplateImportCtrl($uibModalInstance, CaseReportingTemplateSrv, NotificationSrv) { + this.formData = {}; + + this.ok = function () { + CaseReportingTemplateSrv.import(this.formData) + .then(function() { + $uibModalInstance.close(); + }, function(response) { + NotificationSrv.error('AdminCaseReportingTemplateImportCtrl', response.data, response.status); + }); + }; + + this.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + } +})(); diff --git a/ui/app/scripts/controllers/case/CaseMainCtrl.js b/ui/app/scripts/controllers/case/CaseMainCtrl.js index 1ca48847b1..af8f6cb976 100644 --- a/ui/app/scripts/controllers/case/CaseMainCtrl.js +++ b/ui/app/scripts/controllers/case/CaseMainCtrl.js @@ -26,7 +26,7 @@ $scope.caze = caze; $rootScope.title = 'Case #' + caze.caseId + ': ' + caze.title; - + $scope.initExports = function() { $scope.existingExports = _.filter($scope.caze.stats.alerts || [], function(item) { return item.type === 'misp'; @@ -231,6 +231,20 @@ }); }; + $scope.reportCase = function() { + $uibModal.open({ + templateUrl: 'views/partials/case/case.report.html', + controller: 'CaseReportModalCtrl', + controllerAs: 'dialog', + size: 'lg', + resolve: { + caze: function() { + return $scope.caze; + } + } + }); + }; + $scope.shareCase = function() { if($scope.appConfig.connectors.misp && $scope.appConfig.connectors.misp.servers.length === 0) { NotificationSrv.log('There are no MISP servers defined', 'error'); @@ -333,6 +347,7 @@ return tags; }; + } ); })(); diff --git a/ui/app/scripts/controllers/case/CaseReportModalCtrl.js b/ui/app/scripts/controllers/case/CaseReportModalCtrl.js new file mode 100644 index 0000000000..609b4d6d8c --- /dev/null +++ b/ui/app/scripts/controllers/case/CaseReportModalCtrl.js @@ -0,0 +1,101 @@ +(function () { + 'use strict'; + + angular.module('theHiveControllers') + .controller('CaseReportModalCtrl', CaseReportModalCtrl); + + function CaseReportModalCtrl($scope, $state, $uibModalInstance, $q, SearchSrv, CaseSrv, UserInfoSrv, NotificationSrv, CaseReportingTemplateSrv, PSearchSrv, caze, $http) { + var self = this; + + this.caze = caze; + this.templates = []; + this.templatesLoaded = false; + this.artifacts = []; + this.artifactsLoaded = false; + this.tasks = []; + this.tasksLoaded = false; + this.abstract = ''; + + $scope.caseId = this.caze.id; + + $scope.artifactsList = PSearchSrv($scope.caseId, 'case_artifact', { + scope: $scope, + baseFilter: { + '_and': [{ + '_parent': { + "_type": "case", + "_query": { + "_id": $scope.caseId + } + } + },   { + 'status': 'Ok' + }] + }, + filter : { '_any': '*' }, + skipStream : true, + loadAll: true, + sort: '-startDate', + pageSize: 10, + onUpdate: function () { + self.artifacts = $scope.artifactsList.allValues; + self.artifactsLoaded = true; + }, + nstats: true + }); + + $scope.taskList = PSearchSrv($scope.caseId, 'case_task', { + scope: $scope, + loadAll: true, + baseFilter: { + _and: [{ + _parent: { + _type: 'case', + _query: { + '_id': $scope.caseId + } + } + }, { + _not: { + 'status': 'Cancel' + } + }] + }, + sort: ['-flag', '+order', '+startDate', '+title'], + onUpdate: function() { + self.tasks = $scope.taskList.allValues; + self.tasksLoaded = true; + } + }); + + + this.load = function(){ + $q.all([ + CaseReportingTemplateSrv.list() + ]).then(function (response) { + self.templates = response[0]; + self.templatesLoaded = true; + return $q.resolve(self.templates); + }, function(rejection){ + NotificationSrv.error('CaseReportModalCtrl', rejection.data, rejection.status); + }) + }; + + this.cancel = function () { + $uibModalInstance.dismiss(); + }; + + this.report = function(event) { + var report = angular.element(document.querySelector('#casereport')); + + var printWindow = window.open('', '', 'height=400,width=800'); + printWindow.document.write(report.html()); + printWindow.document.close(); + printWindow.print(); + + $uibModalInstance.close(); + }; + + this.load(); + } +})(); diff --git a/ui/app/scripts/directives/case-report.js b/ui/app/scripts/directives/case-report.js new file mode 100644 index 0000000000..349821a4e5 --- /dev/null +++ b/ui/app/scripts/directives/case-report.js @@ -0,0 +1,31 @@ +(function () { + 'use strict'; + angular.module('theHiveDirectives') + .directive('caseReport', function ($compile) { + return { + restrict: 'AEC', + replace: false, + scope: { + abstract: '=', + caze: '=', + templates: '=', + artifacts: '=', + tasks: '=' + }, + transclude : true, + link: function (scope, element, attributes) { + var defaultTemplate = null; + for (var ii = 0; ii < scope.templates.length; ++ii) { + if (scope.templates[ii].isDefault === true) { + defaultTemplate = scope.templates[ii]; + } + } + + element.html(defaultTemplate.content).show(); + var e = $compile(element.contents())(scope); + element.replaceWith(e); + scope.$root.apply(); + } + }; + }); +})(); diff --git a/ui/app/scripts/services/CaseReportingTemplateSrv.js b/ui/app/scripts/services/CaseReportingTemplateSrv.js new file mode 100644 index 0000000000..e4c8fe0898 --- /dev/null +++ b/ui/app/scripts/services/CaseReportingTemplateSrv.js @@ -0,0 +1,71 @@ +(function() { + 'use strict'; + angular.module('theHiveServices').service('CaseReportingTemplateSrv', function($q, $http) { + this.baseUrl = './api/case/reporting/template' + + this.list = function() { + var defer = $q.defer(); + $http.post(this.baseUrl + '/_search', {}, { + params: { + range: 'all' + } + }).then(function(response) { + defer.resolve(response.data); + }, function(err) { + defer.reject(err); + }); + return defer.promise; + }; + + this.get = function(id) { + var defer = $q.defer(); + $http.get(this.baseUrl + '/' + id).then(function(response) { + defer.resolve(response.data); + }, function(err) { + defer.reject(err); + }); + return defer.promise; + }; + + this.delete = function(id) { + return $http.delete(this.baseUrl + '/' + id); + }; + + this.create = function(template) { + return $http.post(this.baseUrl, template); + }; + + this.update = function(id, template) { + return $http.patch(this.baseUrl + '/' + id, template); + }; + + this.import = function(post) { + var postData = { + template: post.attachment + }; + + return $http({ + method: 'POST', + url: this.baseUrl + '/_import', + headers: { + 'Content-Type': undefined + }, + transformRequest: function (data) { + var formData = new FormData(), + copy = angular.copy(data, {}), + _json = {}; + + angular.forEach(data, function (value, key) { + if (Object.getPrototypeOf(value) instanceof Blob || Object.getPrototypeOf(value) instanceof File) { + formData.append(key, value); + delete copy[key]; + } + }); + + return formData; + }, + data: postData + }); + }; + }); +})(); diff --git a/ui/app/views/components/header.component.html b/ui/app/views/components/header.component.html index e82f7a38b4..627db1b97a 100644 --- a/ui/app/views/components/header.component.html +++ b/ui/app/views/components/header.component.html @@ -119,6 +119,12 @@ Case templates +
  • + + + Case reporting templates + +
  • diff --git a/ui/app/views/partials/admin/case-reporting-template-delete.html b/ui/app/views/partials/admin/case-reporting-template-delete.html new file mode 100644 index 0000000000..bb9870179e --- /dev/null +++ b/ui/app/views/partials/admin/case-reporting-template-delete.html @@ -0,0 +1,15 @@ + + + diff --git a/ui/app/views/partials/admin/case-reporting-template-dialog.html b/ui/app/views/partials/admin/case-reporting-template-dialog.html new file mode 100644 index 0000000000..73e782c3cd --- /dev/null +++ b/ui/app/views/partials/admin/case-reporting-template-dialog.html @@ -0,0 +1,22 @@ +
    + + + +
    diff --git a/ui/app/views/partials/admin/case-reporting-template-import.html b/ui/app/views/partials/admin/case-reporting-template-import.html new file mode 100644 index 0000000000..36ba78d9ab --- /dev/null +++ b/ui/app/views/partials/admin/case-reporting-template-import.html @@ -0,0 +1,18 @@ +
    + + + +
    diff --git a/ui/app/views/partials/admin/case-reporting-template-make-default.html b/ui/app/views/partials/admin/case-reporting-template-make-default.html new file mode 100644 index 0000000000..fa03c85625 --- /dev/null +++ b/ui/app/views/partials/admin/case-reporting-template-make-default.html @@ -0,0 +1,12 @@ + + + diff --git a/ui/app/views/partials/admin/case-reporting-templates.html b/ui/app/views/partials/admin/case-reporting-templates.html new file mode 100644 index 0000000000..47e12286c1 --- /dev/null +++ b/ui/app/views/partials/admin/case-reporting-templates.html @@ -0,0 +1,51 @@ +
    +
    +

    Case reporting template management

    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    No case report templates found.
    +
    +
    +
    +
    + + + + + + + + + + + + + +
    NameTemplate
    + {{::template.title}}
    +
    +
    + + + +
    +
    +
    +
    + +
    +
    diff --git a/ui/app/views/partials/case/case.panelinfo.html b/ui/app/views/partials/case/case.panelinfo.html index 0ec5ed4cb0..1349656501 100644 --- a/ui/app/views/partials/case/case.panelinfo.html +++ b/ui/app/views/partials/case/case.panelinfo.html @@ -37,6 +37,13 @@

    + + + + + Report + + | diff --git a/ui/app/views/partials/case/case.report.html b/ui/app/views/partials/case/case.report.html new file mode 100644 index 0000000000..817327a6df --- /dev/null +++ b/ui/app/views/partials/case/case.report.html @@ -0,0 +1,16 @@ + + + + + diff --git a/ui/test/karma.conf.js b/ui/test/karma.conf.js index e94fdac0b1..55a5bd5a63 100644 --- a/ui/test/karma.conf.js +++ b/ui/test/karma.conf.js @@ -72,7 +72,6 @@ module.exports = function(config) { 'bower_components/underscore.string/dist/underscore.string.js', 'bower_components/angular-drag-and-drop-lists/angular-drag-and-drop-lists.js', 'bower_components/angular-bootstrap-colorpicker/js/bootstrap-colorpicker-module.js', - 'bower_components/file-saver/FileSaver.js', 'bower_components/js-url/url.js', 'bower_components/angular-mocks/angular-mocks.js', // endbower