diff --git a/CHANGELOG.md b/CHANGELOG.md
index d10cd01815..b504a828a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,25 @@
# Change Log
-## [3.4.0-4C1](https://github.com/TheHive-Project/TheHive/tree/3.4.0-RC1) (2019-07-09)
+## [3.4.0](https://github.com/TheHive-Project/TheHive/tree/HEAD) (2019-09-05)
+[Full Changelog](https://github.com/TheHive-Project/TheHive/compare/3.4.0-RC2...3.4.0)
+
+**Implemented enhancements:**
+
+- Removing custom fields [\#954](https://github.com/TheHive-Project/TheHive/issues/954)
+
+**Fixed bugs:**
+
+- Cosmetic Bug: wrong number of exported observables displayed [\#1071](https://github.com/TheHive-Project/TheHive/issues/1071)
+- Update Database button does not appear in training appliance [\#1067](https://github.com/TheHive-Project/TheHive/issues/1067)
+- bulk merge alerts into case lose description's alert [\#1065](https://github.com/TheHive-Project/TheHive/issues/1065)
+- Incorrect number of related observables returned [\#1062](https://github.com/TheHive-Project/TheHive/issues/1062)
+- Incorrect tag filter results when observables with tags are added then deleted [\#1061](https://github.com/TheHive-Project/TheHive/issues/1061)
+- Cannot setup TheHive 3.4.0-RC2 using Docker [\#1051](https://github.com/TheHive-Project/TheHive/issues/1051)
+- Case statistics dashboard loads with an error message and the case over time panel fails to display any data [\#1050](https://github.com/TheHive-Project/TheHive/issues/1050)
+- Can't secure ElasticSearch connection [\#1046](https://github.com/TheHive-Project/TheHive/issues/1046)
+
+## [3.4.0-RC2](https://github.com/TheHive-Project/TheHive/tree/3.4.0-RC2) (2019-07-10)
[Full Changelog](https://github.com/TheHive-Project/TheHive/compare/3.4.0-RC1...3.4.0-RC2)
**Implemented enhancements:**
@@ -25,7 +43,7 @@
- Cannot add custom fields to case template [\#1042](https://github.com/TheHive-Project/TheHive/issues/1042)
- sample hive does not connect to cortex and prints no helpful error message [\#1028](https://github.com/TheHive-Project/TheHive/issues/1028)
-## [3.4.0-4C1](https://github.com/TheHive-Project/TheHive/tree/HEAD) (2019-06-05)
+## [3.4.0-RC1](https://github.com/TheHive-Project/TheHive/tree/HEAD) (2019-06-05)
[Full Changelog](https://github.com/TheHive-Project/TheHive/compare/3.3.1...3.4.0-4C1)
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 59861dee38..7be462cf0c 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -20,8 +20,8 @@ object Dependencies {
val reflections = "org.reflections" % "reflections" % "0.9.11"
val zip4j = "net.lingala.zip4j" % "zip4j" % "1.3.2"
- val elastic4play = "org.thehive-project" %% "elastic4play" % "1.11.4"
- val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.19"
- val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.19"
+ val elastic4play = "org.thehive-project" %% "elastic4play" % "1.11.5"
+ val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.21"
+ val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.21"
}
}
diff --git a/project/plugins.sbt b/project/plugins.sbt
index c037cfab7e..c8ec87a869 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,6 +1,6 @@
// Comment to get more information during initialization
logLevel := Level.Info
-addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.22")
+addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.23")
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0")
diff --git a/thehive-backend/app/controllers/AlertCtrl.scala b/thehive-backend/app/controllers/AlertCtrl.scala
index 38a1ee4af6..9e87c3fb0e 100644
--- a/thehive-backend/app/controllers/AlertCtrl.scala
+++ b/thehive-backend/app/controllers/AlertCtrl.scala
@@ -128,8 +128,9 @@ class AlertCtrl @Inject()(
@Timed
def bulkDelete(): Action[Fields] = authenticated(Roles.admin).async(fieldsBodyParser) { implicit request ⇒
request.body.getStrings("ids").fold(Future.successful(NoContent)) { ids ⇒
- Future.traverse(ids)(alertSrv.delete(_, request.body.getBoolean("force").getOrElse(false)))
- .map(_ => NoContent)
+ Future
+ .traverse(ids)(alertSrv.delete(_, request.body.getBoolean("force").getOrElse(false)))
+ .map(_ ⇒ NoContent)
}
}
@@ -180,7 +181,9 @@ class AlertCtrl @Inject()(
def createCase(id: String): Action[Fields] = authenticated(Roles.write).async(fieldsBodyParser) { implicit request ⇒
for {
alert ← alertSrv.get(id)
- customCaseTemplate = request.body.getString("caseTemplate")
+ customCaseTemplate = request
+ .body
+ .getString("caseTemplate")
.orElse(alert.caseTemplate())
caze ← alertSrv.createCase(alert, customCaseTemplate)
} yield renderer.toOutput(CREATED, caze)
diff --git a/thehive-backend/app/controllers/CustomFieldsCtrl.scala b/thehive-backend/app/controllers/CustomFieldsCtrl.scala
new file mode 100644
index 0000000000..a5a7b38b35
--- /dev/null
+++ b/thehive-backend/app/controllers/CustomFieldsCtrl.scala
@@ -0,0 +1,56 @@
+package controllers
+
+import scala.concurrent.{ExecutionContext, Future}
+
+import play.api.http.Status
+import play.api.libs.json.{JsNumber, JsObject, Json}
+import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
+
+import akka.stream.Materializer
+import akka.stream.scaladsl.Sink
+import com.sksamuel.elastic4s.http.ElasticDsl.{search, termsAggregation}
+import javax.inject.{Inject, Singleton}
+import models.Roles
+
+import org.elastic4play.NotFoundError
+import org.elastic4play.controllers.Authenticated
+import org.elastic4play.database.DBFind
+import org.elastic4play.services.DBLists
+import org.elastic4play.services.QueryDSL._
+
+@Singleton
+class CustomFieldsCtrl @Inject()(
+ authenticated: Authenticated,
+ dbfind: DBFind,
+ dblists: DBLists,
+ components: ControllerComponents,
+ implicit val ec: ExecutionContext,
+ implicit val mat: Materializer
+) extends AbstractController(components)
+ with Status {
+
+ def useCount(customField: String): Action[AnyContent] =
+ authenticated(Roles.read)
+ .async {
+ dblists("custom_fields")
+ .getItems[JsObject]
+ ._1
+ .collect {
+ case (_, value) if (value \ "reference").asOpt[String].contains(customField) ⇒ (value \ "type").as[String]
+ }
+ .runWith(Sink.head)
+ .recoverWith { case _ ⇒ Future.failed(NotFoundError(s"CustomField $customField not found")) }
+ .flatMap { customFieldType ⇒
+ val filter = and("relations" in ("case", "alert", "caseTemplate"), contains(s"customFields.$customField.$customFieldType"))
+ dbfind(
+ indexName ⇒ search(indexName).query(filter.query).aggregations(termsAggregation("t").field("relations"))
+ ).map { searchResponse ⇒
+ val buckets = searchResponse.aggregations.terms("t").buckets
+ val total = buckets.map(_.docCount).sum
+ val result = buckets.map(b ⇒ b.key → JsNumber(b.docCount)) :+ ("total" → JsNumber(total))
+ Ok(JsObject(result))
+ }
+ .recover { case _ ⇒ Ok(Json.obj("total" → 0)) }
+ }
+ }
+}
diff --git a/thehive-backend/app/controllers/DBListCtrl.scala b/thehive-backend/app/controllers/DBListCtrl.scala
index f1b1dbbc03..d4c6a68df3 100644
--- a/thehive-backend/app/controllers/DBListCtrl.scala
+++ b/thehive-backend/app/controllers/DBListCtrl.scala
@@ -1,14 +1,14 @@
-package org.elastic4play.controllers
-
-import javax.inject.{Inject, Singleton}
+package controllers
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.json.{JsValue, Json}
import play.api.mvc._
+import javax.inject.{Inject, Singleton}
import models.Roles
+import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser, Renderer}
import org.elastic4play.services.DBLists
import org.elastic4play.{MissingAttributeError, Timed}
diff --git a/thehive-backend/app/controllers/StatusCtrl.scala b/thehive-backend/app/controllers/StatusCtrl.scala
index 2f9f698798..08ee2f7145 100644
--- a/thehive-backend/app/controllers/StatusCtrl.scala
+++ b/thehive-backend/app/controllers/StatusCtrl.scala
@@ -1,40 +1,48 @@
package controllers
+import akka.actor.ActorSystem
+
import scala.collection.immutable
import scala.concurrent.ExecutionContext
import scala.util.Try
-
import play.api.Configuration
import play.api.libs.json.Json.toJsFieldJsValueWrapper
import play.api.libs.json.{JsBoolean, JsObject, JsString, Json}
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
-
import com.sksamuel.elastic4s.http.ElasticDsl
import connectors.Connector
import javax.inject.{Inject, Singleton}
import models.HealthStatus
import org.elasticsearch.client.Node
-
import org.elastic4play.Timed
import org.elastic4play.database.DBIndex
import org.elastic4play.services.AuthSrv
import org.elastic4play.services.auth.MultiAuthSrv
+import scala.concurrent.duration.{DurationInt, FiniteDuration}
+
@Singleton
class StatusCtrl @Inject()(
connectors: immutable.Set[Connector],
configuration: Configuration,
dbIndex: DBIndex,
authSrv: AuthSrv,
+ system: ActorSystem,
components: ControllerComponents,
implicit val ec: ExecutionContext
) extends AbstractController(components) {
private[controllers] def getVersion(c: Class[_]) = Option(c.getPackage.getImplementationVersion).getOrElse("SNAPSHOT")
+ private var clusterStatusName: String = "Init"
+ val checkStatusInterval: FiniteDuration = configuration.getOptional[FiniteDuration]("statusCheckInterval").getOrElse(1.minute)
+ private def updateStatus(): Unit = {
+ clusterStatusName = Try(dbIndex.clusterStatusName).getOrElse("ERROR")
+ system.scheduler.scheduleOnce(checkStatusInterval)(updateStatus())
+ }
+ updateStatus()
@Timed("controllers.StatusCtrl.get")
def get: Action[AnyContent] = Action {
- val clusterStatusName = Try(dbIndex.clusterStatusName).getOrElse("ERROR")
Ok(
Json.obj(
"versions" → Json.obj(
diff --git a/thehive-backend/app/controllers/StreamCtrl.scala b/thehive-backend/app/controllers/StreamCtrl.scala
index d37c55830d..7f3ca03a27 100644
--- a/thehive-backend/app/controllers/StreamCtrl.scala
+++ b/thehive-backend/app/controllers/StreamCtrl.scala
@@ -23,7 +23,7 @@ import services.StreamActor
import services.StreamActor.StreamMessages
import org.elastic4play.controllers._
-import org.elastic4play.services.{AuxSrv, EventSrv, MigrationSrv}
+import org.elastic4play.services.{AuxSrv, EventSrv, MigrationSrv, UserSrv}
import org.elastic4play.Timed
@Singleton
@@ -33,6 +33,7 @@ class StreamCtrl(
authenticated: Authenticated,
renderer: Renderer,
eventSrv: EventSrv,
+ userSrv: UserSrv,
auxSrv: AuxSrv,
migrationSrv: MigrationSrv,
components: ControllerComponents,
@@ -46,6 +47,7 @@ class StreamCtrl(
authenticated: Authenticated,
renderer: Renderer,
eventSrv: EventSrv,
+ userSrv: UserSrv,
auxSrv: AuxSrv,
migrationSrv: MigrationSrv,
components: ControllerComponents,
@@ -58,6 +60,7 @@ class StreamCtrl(
authenticated,
renderer,
eventSrv,
+ userSrv,
auxSrv,
migrationSrv,
components,
@@ -96,9 +99,10 @@ class StreamCtrl(
Future.successful(BadRequest("Invalid stream id"))
} else {
val futureStatus = authenticated.expirationStatus(request) match {
- case ExpirationError if !migrationSrv.isMigrating ⇒ authenticated.getFromApiKey(request).map(_ ⇒ OK)
- case _: ExpirationWarning ⇒ Future.successful(220)
- case _ ⇒ Future.successful(OK)
+ case ExpirationError if !migrationSrv.isMigrating ⇒
+ userSrv.getInitialUser(request).recoverWith { case _ ⇒ authenticated.getFromApiKey(request) }.map(_ ⇒ OK)
+ case _: ExpirationWarning ⇒ Future.successful(220)
+ case _ ⇒ Future.successful(OK)
}
// Check if stream actor exists
diff --git a/thehive-backend/app/services/AlertSrv.scala b/thehive-backend/app/services/AlertSrv.scala
index 6a6ea09102..4b367c3ca0 100644
--- a/thehive-backend/app/services/AlertSrv.scala
+++ b/thehive-backend/app/services/AlertSrv.scala
@@ -3,27 +3,27 @@ package services
import java.nio.file.Files
import scala.collection.immutable
-import scala.concurrent.{ ExecutionContext, Future }
+import scala.concurrent.{ExecutionContext, Future}
import scala.util.matching.Regex
-import scala.util.{ Failure, Success, Try }
+import scala.util.{Failure, Success, Try}
import play.api.libs.json._
-import play.api.{ Configuration, Logger }
+import play.api.{Configuration, Logger}
import akka.NotUsed
import akka.stream.Materializer
-import akka.stream.scaladsl.{ Sink, Source }
+import akka.stream.scaladsl.{Sink, Source}
import connectors.ConnectorRouter
-import javax.inject.{ Inject, Singleton }
+import javax.inject.{Inject, Singleton}
import models._
-import org.elastic4play.controllers.{ Fields, FileInputValue }
+import org.elastic4play.controllers.{Fields, FileInputValue}
import org.elastic4play.database.ModifyConfig
import org.elastic4play.services.JsonFormat.attachmentFormat
-import org.elastic4play.services.QueryDSL.{ groupByField, parent, selectCount, withId }
+import org.elastic4play.services.QueryDSL.{groupByField, parent, selectCount, withId}
import org.elastic4play.services._
import org.elastic4play.utils.Collection
-import org.elastic4play.{ ConflictError, InternalError }
+import org.elastic4play.{ConflictError, InternalError}
trait AlertTransformer {
def createCase(alert: Alert, customCaseTemplate: Option[String])(implicit authContext: AuthContext): Future[Case]
@@ -312,7 +312,7 @@ class AlertSrv(
.create(caze, artifactsFields)
.flatMap { artifacts ⇒
Future.traverse(artifacts) {
- case Success(_) => Future.successful(())
+ case Success(_) ⇒ Future.successful(())
case Failure(ConflictError(_, attributes)) ⇒ // if it already exists, add tags from alert
import org.elastic4play.services.QueryDSL._
(for {
@@ -340,7 +340,7 @@ class AlertSrv(
Future.successful(())
}
}
- .map(_ => caze)
+ .map(_ ⇒ caze)
updatedCase.onComplete { _ ⇒
// remove temporary files
artifactsFields
diff --git a/thehive-backend/conf/routes b/thehive-backend/conf/routes
index 6d7bae50d6..f0acbc4687 100644
--- a/thehive-backend/conf/routes
+++ b/thehive-backend/conf/routes
@@ -1,126 +1,126 @@
-# Routes
-# This file defines all application routes (Higher priority routes first)
-# ~~~~
-
-GET / controllers.Home.redirect
-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/log/_stats controllers.LogCtrl.stats()
-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, force: Option[Boolean])
-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)
-POST /api/alert/merge/_bulk controllers.AlertCtrl.bulkMergeWithCase()
-POST /api/alert/delete/_bulk controllers.AlertCtrl.bulkDelete()
-
-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.Home.redirect
+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/log/_stats controllers.LogCtrl.stats()
+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, force: Option[Boolean])
+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)
+POST /api/alert/merge/_bulk controllers.AlertCtrl.bulkMergeWithCase()
+POST /api/alert/delete/_bulk controllers.AlertCtrl.bulkDelete()
+
+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 controllers.DBListCtrl.list()
+DELETE /api/list/:itemId controllers.DBListCtrl.deleteItem(itemId)
+PATCH /api/list/:itemId controllers.DBListCtrl.updateItem(itemId)
+POST /api/list/:listName controllers.DBListCtrl.addItem(listName)
+GET /api/list/:listName controllers.DBListCtrl.listItems(listName)
+POST /api/list/:listName/_exists controllers.DBListCtrl.itemExists(listName)
+GET /api/customFields/:name controllers.CustomFieldsCtrl.useCount(name)
+
+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/thehive-misp/app/connectors/misp/MispExport.scala b/thehive-misp/app/connectors/misp/MispExport.scala
index 4e0fe2d44c..3104c06f76 100644
--- a/thehive-misp/app/connectors/misp/MispExport.scala
+++ b/thehive-misp/app/connectors/misp/MispExport.scala
@@ -51,17 +51,16 @@ class MispExport @Inject()(
}
def removeDuplicateAttributes(attributes: Seq[ExportedMispAttribute]): Seq[ExportedMispAttribute] = {
- val attrIndex = attributes.zipWithIndex
-
- attrIndex
- .filter {
- case (ExportedMispAttribute(_, category, tpe, _, value, _), index) ⇒
- attrIndex.exists {
- case (ExportedMispAttribute(_, `category`, `tpe`, _, `value`, _), otherIndex) ⇒ otherIndex >= index
- case _ ⇒ true
- }
+ var attrSet = Set.empty[(String, String, String)]
+ val builder = Seq.newBuilder[ExportedMispAttribute]
+ attributes.foreach { attr ⇒
+ val tuple = (attr.category, attr.tpe, attr.value.fold(identity, _.name))
+ if (!attrSet.contains(tuple)) {
+ builder += attr
+ attrSet += tuple
}
- .map(_._1)
+ }
+ builder.result()
}
def createEvent(
diff --git a/ui/app/index.html b/ui/app/index.html
index f4a81a5a5b..276ad53014 100644
--- a/ui/app/index.html
+++ b/ui/app/index.html
@@ -246,6 +246,7 @@
+
diff --git a/ui/app/scripts/app.js b/ui/app/scripts/app.js
index e2873c001d..5628903a9c 100644
--- a/ui/app/scripts/app.js
+++ b/ui/app/scripts/app.js
@@ -40,7 +40,7 @@ angular.module('thehive', [
})
.config(function($compileProvider) {
'use strict';
- $compileProvider.debugInfoEnabled(false);
+ $compileProvider.debugInfoEnabled(false);
})
.config(function($stateProvider, $urlRouterProvider) {
'use strict';
@@ -80,7 +80,7 @@ angular.module('thehive', [
templateUrl: 'views/app.html',
controller: 'RootCtrl',
resolve: {
- currentUser: function($q, $state, AuthenticationSrv) {
+ currentUser: function($q, $state, AuthenticationSrv, NotificationSrv) {
var deferred = $q.defer();
AuthenticationSrv.current()
@@ -88,7 +88,9 @@ angular.module('thehive', [
return deferred.resolve(userData);
})
.catch( function(err) {
- return deferred.resolve(err.status === 520 ? err.status : null);
+ NotificationSrv.error('App', err.data, err.status);
+ return deferred.reject(err);
+ //return deferred.resolve(err.status === 520 ? err.status : null);
});
return deferred.promise;
@@ -148,19 +150,20 @@ angular.module('thehive', [
controller: 'SettingsCtrl',
title: 'Personal settings',
resolve: {
- currentUser: function($q, $state, $timeout, AuthenticationSrv) {
+ currentUser: function($q, $state, $timeout, AuthenticationSrv, NotificationSrv) {
var deferred = $q.defer();
AuthenticationSrv.current()
.then(function(userData) {
return deferred.resolve(userData);
})
- .catch( function(/*err*/) {
- $timeout(function() {
- $state.go('login');
- });
+ .catch( function(err) {
+ NotificationSrv.error('SettingsCtrl', err.data, err.status);
+ // $timeout(function() {
+ // $state.go('login');
+ // });
- return deferred.reject();
+ return deferred.reject(err);
});
return deferred.promise;
diff --git a/ui/app/scripts/controllers/SearchCtrl.js b/ui/app/scripts/controllers/SearchCtrl.js
index 4daf9305e7..27f760936b 100644
--- a/ui/app/scripts/controllers/SearchCtrl.js
+++ b/ui/app/scripts/controllers/SearchCtrl.js
@@ -51,7 +51,9 @@
event: event,
templates: function() {
return CaseTemplateSrv.list();
- }
+ },
+ readonly: true,
+ isAdmin: false
}
}).result.then(function(/*response*/) {
$scope.searchResults.update();
diff --git a/ui/app/scripts/controllers/admin/AdminCustomFieldsCtrl.js b/ui/app/scripts/controllers/admin/AdminCustomFieldsCtrl.js
index 9b4a631bd3..04b56cac48 100644
--- a/ui/app/scripts/controllers/admin/AdminCustomFieldsCtrl.js
+++ b/ui/app/scripts/controllers/admin/AdminCustomFieldsCtrl.js
@@ -2,7 +2,7 @@
'use strict';
angular.module('theHiveControllers').controller('AdminCustomFieldsCtrl',
- function($scope, $uibModal, ListSrv, CustomFieldsCacheSrv, NotificationSrv) {
+ function($scope, $uibModal, ListSrv, CustomFieldsCacheSrv, NotificationSrv, ModalUtilsSrv, CustomFieldsSrv) {
var self = this;
self.reference = {
@@ -66,6 +66,66 @@
});
};
+ self.deleteField = function(customField) {
+ CustomFieldsSrv.usage(customField)
+ .then(function(response) {
+ var usage = response.data,
+ message,
+ isHtml = false;
+
+
+ if (usage.total === 0) {
+ message = 'Are you sure you want to delete this custom field?';
+ } else {
+ var segs = [
+ 'Are you sure you want to delete this custom field?',
+ '
',
+ '
',
+ 'This custom field is used by:',
+ '