From b3e246f46f1527077f4695689fd6caf5df08a4c2 Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Fri, 19 Jul 2019 16:09:48 +0200 Subject: [PATCH 01/16] #1062 Display all similar observables in observable details page --- .../scripts/controllers/case/CaseObservablesItemCtrl.js | 4 +++- .../observables/details/artifact-details-information.html | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js b/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js index 12955fe013..996488c566 100644 --- a/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js +++ b/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js @@ -155,7 +155,9 @@ }; $scope.similarArtifacts = CaseArtifactSrv.api().similar({ - 'artifactId': observableId + artifactId: observableId, + range: 'all', + sort: ['-startDate'] }); diff --git a/ui/app/views/partials/observables/details/artifact-details-information.html b/ui/app/views/partials/observables/details/artifact-details-information.html index cfe09c2830..9384a04a41 100644 --- a/ui/app/views/partials/observables/details/artifact-details-information.html +++ b/ui/app/views/partials/observables/details/artifact-details-information.html @@ -88,7 +88,7 @@

Links

Observable seen in {{similarArtifacts.length}} other case(s) - +
@@ -108,9 +108,11 @@

Links

- - +
IOC [{{a.dataType}}]: {{a.data || a.attachment.name}}
#{{a.case.caseId}} - {{a.case.title}} +
+ + #{{a.case.caseId}} - {{a.case.title}} {{a.startDate | showDate}}{{a.startDate | shortDate}}
From accc9519fdbbfafde9e23872eb255f5b109b3c9e Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 24 Jul 2019 17:33:50 +0200 Subject: [PATCH 02/16] #1051 cache ES status to make /api/status more responsive --- thehive-backend/app/controllers/StatusCtrl.scala | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/thehive-backend/app/controllers/StatusCtrl.scala b/thehive-backend/app/controllers/StatusCtrl.scala index 2f9f698798..4466a847ec 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( From 009070654623b0f34715e1bbdb979830ba68931d Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Tue, 20 Aug 2019 16:38:16 +0200 Subject: [PATCH 03/16] #1067 Fix the error where migration page is replaced by login page --- project/Dependencies.scala | 2 +- .../app/controllers/StreamCtrl.scala | 7 +++++-- ui/app/scripts/app.js | 21 +++++++++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 59861dee38..c2ec9c232f 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -20,7 +20,7 @@ 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 elastic4play = "org.thehive-project" %% "elastic4play" % "1.11.5-SNAPSHOT" val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.19" val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.19" } diff --git a/thehive-backend/app/controllers/StreamCtrl.scala b/thehive-backend/app/controllers/StreamCtrl.scala index d37c55830d..ed4863b3cd 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,7 +99,7 @@ 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 ExpirationError if !migrationSrv.isMigrating ⇒ userSrv.getInitialUser(request).recoverWith { case _ => authenticated.getFromApiKey(request)}.map(_ ⇒ OK) case _: ExpirationWarning ⇒ Future.successful(220) case _ ⇒ Future.successful(OK) } 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; From d4f8fe4c4187415e1c9661dcadd23c566f55cfdf Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Tue, 20 Aug 2019 17:28:46 +0200 Subject: [PATCH 04/16] #1062 add pagination to observable similar items --- ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js | 5 +++++ .../observables/details/artifact-details-information.html | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js b/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js index 996488c566..26a0e3dc17 100644 --- a/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js +++ b/ui/app/scripts/controllers/case/CaseObservablesItemCtrl.js @@ -23,6 +23,7 @@ $scope.analysisEnabled = VersionSrv.hasCortex(); $scope.cortexServers = $scope.analysisEnabled && appConfig.connectors.cortex.servers; $scope.protectDownloadsWith = appConfig.config.protectDownloadsWith; + $scope.similarArtifactsLimit = 10; $scope.editorOptions = { lineNumbers: true, @@ -125,6 +126,10 @@ } }; + $scope.showMoreSimilar = function() { + $scope.similarArtifactsLimit = $scope.similarArtifactsLimit + 10; + }; + $scope.showReport = function (jobId) { $scope.report = {}; diff --git a/ui/app/views/partials/observables/details/artifact-details-information.html b/ui/app/views/partials/observables/details/artifact-details-information.html index 9384a04a41..8a8a43487d 100644 --- a/ui/app/views/partials/observables/details/artifact-details-information.html +++ b/ui/app/views/partials/observables/details/artifact-details-information.html @@ -98,7 +98,7 @@

Links

- + Links

+
+ Show more +
From ec885d9e4278485cb094ad832456bc8e1aa272d6 Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Fri, 30 Aug 2019 15:04:57 +0200 Subject: [PATCH 05/16] #1071 Fix the notification message when exporting cases to MISP --- ui/app/scripts/controllers/case/CaseExportDialogCtrl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/app/scripts/controllers/case/CaseExportDialogCtrl.js b/ui/app/scripts/controllers/case/CaseExportDialogCtrl.js index b86458f7f9..e6de0c147d 100644 --- a/ui/app/scripts/controllers/case/CaseExportDialogCtrl.js +++ b/ui/app/scripts/controllers/case/CaseExportDialogCtrl.js @@ -66,7 +66,7 @@ NotificationSrv.log('The case has been successfully exported, but '+ failure +' observable(s) failed', 'warning'); } else { - success = angular.isObject(response.data) ? 1 : response.data.length; + success = angular.isArray(response.data) ? response.data.length : 1 ; NotificationSrv.log('The case has been successfully exported with ' + success+ ' observable(s)', 'success'); $uibModalInstance.close(); } @@ -85,6 +85,6 @@ } self.loading = false; }); - } + }; }); })(); From 7ab234e90b2f020ed8981ab988e4f5c39944c040 Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Fri, 30 Aug 2019 17:02:26 +0200 Subject: [PATCH 06/16] #1065 Add a Preview button to the case alerts list --- ui/app/scripts/controllers/SearchCtrl.js | 4 +++- .../controllers/alert/AlertEventCtrl.js | 3 ++- .../scripts/controllers/alert/AlertListCtrl.js | 3 ++- .../scripts/controllers/case/CaseAlertsCtrl.js | 18 ++++++++++++++++++ ui/app/views/partials/alert/event.dialog.html | 12 ++++++------ ui/app/views/partials/case/case.alerts.html | 6 ++++++ 6 files changed, 37 insertions(+), 9 deletions(-) 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/alert/AlertEventCtrl.js b/ui/app/scripts/controllers/alert/AlertEventCtrl.js index cde6a84015..265440d64b 100644 --- a/ui/app/scripts/controllers/alert/AlertEventCtrl.js +++ b/ui/app/scripts/controllers/alert/AlertEventCtrl.js @@ -1,10 +1,11 @@ (function() { 'use strict'; angular.module('theHiveControllers') - .controller('AlertEventCtrl', function($scope, $rootScope, $state, $uibModal, $uibModalInstance, ModalUtilsSrv, CustomFieldsCacheSrv, CaseResolutionStatus, AlertingSrv, NotificationSrv, UiSettingsSrv, clipboard, event, templates, isAdmin) { + .controller('AlertEventCtrl', function($scope, $rootScope, $state, $uibModal, $uibModalInstance, ModalUtilsSrv, CustomFieldsCacheSrv, CaseResolutionStatus, AlertingSrv, NotificationSrv, UiSettingsSrv, clipboard, event, templates, isAdmin, readonly) { var self = this; var eventId = event.id; + self.readonly = readonly; self.isAdmin = isAdmin; self.templates = _.pluck(templates, 'name'); self.CaseResolutionStatus = CaseResolutionStatus; diff --git a/ui/app/scripts/controllers/alert/AlertListCtrl.js b/ui/app/scripts/controllers/alert/AlertListCtrl.js index 9d2e4836c3..83740c9e86 100755 --- a/ui/app/scripts/controllers/alert/AlertListCtrl.js +++ b/ui/app/scripts/controllers/alert/AlertListCtrl.js @@ -225,7 +225,8 @@ templates: function() { return CaseTemplateSrv.list(); }, - isAdmin: self.isAdmin + isAdmin: self.isAdmin, + readonly: false } }); }; diff --git a/ui/app/scripts/controllers/case/CaseAlertsCtrl.js b/ui/app/scripts/controllers/case/CaseAlertsCtrl.js index c379fbfc43..12aa928dfe 100644 --- a/ui/app/scripts/controllers/case/CaseAlertsCtrl.js +++ b/ui/app/scripts/controllers/case/CaseAlertsCtrl.js @@ -73,6 +73,24 @@ } }; + $scope.previewEvent = function(event) { + $uibModal.open({ + templateUrl: 'views/partials/alert/event.dialog.html', + controller: 'AlertEventCtrl', + controllerAs: 'dialog', + size: 'max', + resolve: { + event: event, + templates: function() { + //return CaseTemplateSrv.list(); + return []; + }, + isAdmin: false, + readonly: true + } + }); + }; + $scope.alertStats = $scope.initStats($scope.alerts); } ); diff --git a/ui/app/views/partials/alert/event.dialog.html b/ui/app/views/partials/alert/event.dialog.html index 92dd5e8b3a..60425d7aa5 100644 --- a/ui/app/views/partials/alert/event.dialog.html +++ b/ui/app/views/partials/alert/event.dialog.html @@ -143,31 +143,31 @@

- - - -
+
diff --git a/ui/app/views/partials/case/case.alerts.html b/ui/app/views/partials/case/case.alerts.html index 0c4c3b39d3..6ad492473f 100644 --- a/ui/app/views/partials/case/case.alerts.html +++ b/ui/app/views/partials/case/case.alerts.html @@ -91,6 +91,7 @@ + @@ -129,6 +130,11 @@ {{::event.artifacts.length || 0}} {{event.date | showDate}} + + + + + From 6366042e54722b7d5b00240f5d334b7c985d09df Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Mon, 2 Sep 2019 16:50:55 +0200 Subject: [PATCH 07/16] #1061 Escape quotes from case list filters --- ui/app/scripts/controllers/case/CaseListCtrl.js | 4 ++-- ui/app/scripts/services/CasesUISrv.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui/app/scripts/controllers/case/CaseListCtrl.js b/ui/app/scripts/controllers/case/CaseListCtrl.js index 0997c18634..933bb964f0 100644 --- a/ui/app/scripts/controllers/case/CaseListCtrl.js +++ b/ui/app/scripts/controllers/case/CaseListCtrl.js @@ -246,8 +246,8 @@ self.caseResponders = responders; }) .catch(function(err) { - NotificationSrv.error('CaseList', response.data, response.status); - }) + NotificationSrv.error('CaseList', err.data, err.status); + }); }; this.runResponder = function(responderId, responderName, caze) { diff --git a/ui/app/scripts/services/CasesUISrv.js b/ui/app/scripts/services/CasesUISrv.js index 20080a716f..cae968fdfc 100644 --- a/ui/app/scripts/services/CasesUISrv.js +++ b/ui/app/scripts/services/CasesUISrv.js @@ -165,10 +165,10 @@ // Prepare the filter value if (field === 'keyword') { - query = value; + query = value.replace(/"/gi, '\\"'); } else if (angular.isArray(value) && value.length > 0) { query = _.map(value, function(val) { - return field + ':"' + convertFn(val.text) + '"'; + return field + ':"' + convertFn(val.text.replace(/"/gi, '\\"')) + '"'; }).join(' OR '); query = '(' + query + ')'; } else if (filterDef.type === 'date') { @@ -178,7 +178,7 @@ query = field + ':[ ' + fromDate + ' TO ' + toDate + ' ]'; } else { - query = field + ':' + convertFn(value); + query = field + ':' + convertFn(value.replace(/"/gi, '\\"')); } factory.filters[field] = { From 38f1a7673c9ab138af56061706782aa45bbb5966 Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 3 Sep 2019 11:35:10 +0200 Subject: [PATCH 08/16] #954 Add API to check if a custom field is used --- .../app/controllers/CustomFieldsCtrl.scala | 56 ++++ .../app/controllers/DBListCtrl.scala | 6 +- thehive-backend/conf/routes | 252 +++++++++--------- 3 files changed, 185 insertions(+), 129 deletions(-) create mode 100644 thehive-backend/app/controllers/CustomFieldsCtrl.scala diff --git a/thehive-backend/app/controllers/CustomFieldsCtrl.scala b/thehive-backend/app/controllers/CustomFieldsCtrl.scala new file mode 100644 index 0000000000..abb51ad995 --- /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 } +import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents } + +import akka.stream.Materializer +import akka.stream.scaladsl.Sink +import com.sksamuel.elastic4s.http.ElasticDsl.search +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).size(0) + ).map { searchResponse ⇒ + Ok(JsNumber(searchResponse.totalHits)) + } + } + } + + /* +{"query":{"_and":[{"_not":{"_field":"customFields.cf1.string","_value":"ss"}},{"_not":{"status":"Deleted"}}]}} + */ +} 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/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) From e5d3803f8f17e294c33906d13fade24adfcedc94 Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 3 Sep 2019 11:35:44 +0200 Subject: [PATCH 09/16] Update Play --- project/Dependencies.scala | 4 ++-- project/plugins.sbt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c2ec9c232f..b8ccb66f23 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -21,7 +21,7 @@ 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.5-SNAPSHOT" - val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.19" - val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.19" + 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") From 64d69a81bb252b9c1a17f5bc0dee9ef0bc8e6f63 Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 3 Sep 2019 11:36:31 +0200 Subject: [PATCH 10/16] Fix MISP attribute deduplication when exporting a case --- .../app/connectors/misp/MispExport.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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( From ac3afcc27e3eb6d06deb0d9206ccae90d72c0b8f Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 3 Sep 2019 14:58:20 +0200 Subject: [PATCH 11/16] #954 Add more details on custom fields use --- .../app/controllers/CustomFieldsCtrl.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/thehive-backend/app/controllers/CustomFieldsCtrl.scala b/thehive-backend/app/controllers/CustomFieldsCtrl.scala index abb51ad995..097b92800b 100644 --- a/thehive-backend/app/controllers/CustomFieldsCtrl.scala +++ b/thehive-backend/app/controllers/CustomFieldsCtrl.scala @@ -8,7 +8,7 @@ import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponen import akka.stream.Materializer import akka.stream.scaladsl.Sink -import com.sksamuel.elastic4s.http.ElasticDsl.search +import com.sksamuel.elastic4s.http.ElasticDsl.{ search, termsAggregation } import javax.inject.{ Inject, Singleton } import models.Roles @@ -43,14 +43,14 @@ class CustomFieldsCtrl @Inject()( .flatMap { customFieldType ⇒ val filter = and("relations" in ("case", "alert", "caseTemplate"), contains(s"customFields.$customField.$customFieldType")) dbfind( - indexName ⇒ search(indexName).query(filter.query).size(0) + indexName ⇒ search(indexName).query(filter.query).aggregations(termsAggregation("t").field("relations")) ).map { searchResponse ⇒ - Ok(JsNumber(searchResponse.totalHits)) - } + val result = JsObject(searchResponse.aggregations.terms("t").buckets.map { b ⇒ + b.key → JsNumber(b.docCount) + }) + Ok(result) + } + .recover { case _ ⇒ Ok(JsObject.empty) } } } - - /* -{"query":{"_and":[{"_not":{"_field":"customFields.cf1.string","_value":"ss"}},{"_not":{"status":"Deleted"}}]}} - */ } From e00f4c57ff77d53424a4bbeba3462515073529d1 Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 4 Sep 2019 08:58:45 +0200 Subject: [PATCH 12/16] #954 Add total in custom field use count --- .../app/controllers/AlertCtrl.scala | 9 ++++++--- .../app/controllers/CustomFieldsCtrl.scala | 20 +++++++++---------- .../app/controllers/StatusCtrl.scala | 4 ++-- .../app/controllers/StreamCtrl.scala | 9 +++++---- thehive-backend/app/services/AlertSrv.scala | 20 +++++++++---------- 5 files changed, 33 insertions(+), 29 deletions(-) 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 index 097b92800b..a5a7b38b35 100644 --- a/thehive-backend/app/controllers/CustomFieldsCtrl.scala +++ b/thehive-backend/app/controllers/CustomFieldsCtrl.scala @@ -1,15 +1,15 @@ package controllers -import scala.concurrent.{ ExecutionContext, Future } +import scala.concurrent.{ExecutionContext, Future} import play.api.http.Status -import play.api.libs.json.{ JsNumber, JsObject } -import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents } +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 com.sksamuel.elastic4s.http.ElasticDsl.{search, termsAggregation} +import javax.inject.{Inject, Singleton} import models.Roles import org.elastic4play.NotFoundError @@ -45,12 +45,12 @@ class CustomFieldsCtrl @Inject()( dbfind( indexName ⇒ search(indexName).query(filter.query).aggregations(termsAggregation("t").field("relations")) ).map { searchResponse ⇒ - val result = JsObject(searchResponse.aggregations.terms("t").buckets.map { b ⇒ - b.key → JsNumber(b.docCount) - }) - Ok(result) + 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(JsObject.empty) } + .recover { case _ ⇒ Ok(Json.obj("total" → 0)) } } } } diff --git a/thehive-backend/app/controllers/StatusCtrl.scala b/thehive-backend/app/controllers/StatusCtrl.scala index 4466a847ec..08ee2f7145 100644 --- a/thehive-backend/app/controllers/StatusCtrl.scala +++ b/thehive-backend/app/controllers/StatusCtrl.scala @@ -33,8 +33,8 @@ class StatusCtrl @Inject()( ) 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 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()) diff --git a/thehive-backend/app/controllers/StreamCtrl.scala b/thehive-backend/app/controllers/StreamCtrl.scala index ed4863b3cd..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, UserSrv } +import org.elastic4play.services.{AuxSrv, EventSrv, MigrationSrv, UserSrv} import org.elastic4play.Timed @Singleton @@ -99,9 +99,10 @@ class StreamCtrl( Future.successful(BadRequest("Invalid stream id")) } else { val futureStatus = authenticated.expirationStatus(request) match { - case ExpirationError if !migrationSrv.isMigrating ⇒ userSrv.getInitialUser(request).recoverWith { case _ => 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 From d219cb0abdc02102526acbb428c5ba9554a348c8 Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Wed, 4 Sep 2019 15:46:39 +0200 Subject: [PATCH 13/16] #954 Add the possibility to delete a custom field from the admin section --- ui/app/index.html | 1 + .../admin/AdminCustomFieldsCtrl.js | 55 ++++++++++++++++++- .../controllers/case/CaseCloseModalCtrl.js | 9 ++- ui/app/scripts/services/CustomFieldsSrv.js | 17 ++++++ .../admin/case-template/custom-fields.html | 2 +- .../views/partials/admin/custom-fields.html | 3 +- .../views/partials/utils/confirm.modal.html | 3 +- 7 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 ui/app/scripts/services/CustomFieldsSrv.js 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/controllers/admin/AdminCustomFieldsCtrl.js b/ui/app/scripts/controllers/admin/AdminCustomFieldsCtrl.js index 9b4a631bd3..021dedf731 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,59 @@ }); }; + 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:', + '
    ' + ]; + + if(usage.case) { + segs.push('
  • ' + usage.case + ' cases
  • '); + } + + if(usage.alert) { + segs.push('
  • ' + usage.alert + ' alerts
  • '); + } + + if(usage.caseTemplate) { + segs.push('
  • ' + usage.caseTemplate + ' case templates
  • '); + } + + segs.push('
'); + + message = segs.join(''); + isHtml = true; + } + + return ModalUtilsSrv.confirm('Remove custom field', message, { + okText: 'Yes, remove it', + flavor: 'danger', + isHtml: isHtml + }); + }) + .then(function(response) { + return CustomFieldsSrv.removeField(customField); + }) + .then(function() { + self.initCustomfields(); + CustomFieldsCacheSrv.clearCache(); + $scope.$emit('custom-fields:refresh'); + }); + }; + self.initCustomfields(); }); })(); diff --git a/ui/app/scripts/controllers/case/CaseCloseModalCtrl.js b/ui/app/scripts/controllers/case/CaseCloseModalCtrl.js index d52daf3d79..9d481674a7 100644 --- a/ui/app/scripts/controllers/case/CaseCloseModalCtrl.js +++ b/ui/app/scripts/controllers/case/CaseCloseModalCtrl.js @@ -43,15 +43,20 @@ }), 'name'); return result; - } + }; $scope.initialize = function() { CustomFieldsCacheSrv.all().then(function(fields) { $scope.orderedFields = getTemplateCustomFields($scope.caze.customFields); - $scope.allCustomFields = fields; + $scope.allCustomFields = fields; $scope.mandatoryFields = _.without(_.map($scope.orderedFields, function(cf) { var fieldDef = fields[cf]; + + if(!fieldDef) { + return; + } + var fieldValue = $scope.caze.customFields[cf][cf.type]; if((fieldValue === undefined || fieldValue === null) && fieldDef.mandatory === true) { diff --git a/ui/app/scripts/services/CustomFieldsSrv.js b/ui/app/scripts/services/CustomFieldsSrv.js new file mode 100644 index 0000000000..e6ac051a2b --- /dev/null +++ b/ui/app/scripts/services/CustomFieldsSrv.js @@ -0,0 +1,17 @@ +(function () { + 'use strict'; + angular.module('theHiveServices') + .factory('CustomFieldsSrv', function ($http) { + + var factory = { + removeField: function (field) { + return $http.delete('./api/list/' + field.id); + }, + usage: function(field) { + return $http.get('./api/customFields/' + field.reference); + } + }; + + return factory; + }); +})(); diff --git a/ui/app/views/partials/admin/case-template/custom-fields.html b/ui/app/views/partials/admin/case-template/custom-fields.html index b1a0ef0531..d85d6440b0 100644 --- a/ui/app/views/partials/admin/case-template/custom-fields.html +++ b/ui/app/views/partials/admin/case-template/custom-fields.html @@ -20,7 +20,7 @@

Delete

- +