From d52840ef4ecd451a65038fa4e91f4055344a7e90 Mon Sep 17 00:00:00 2001 From: Nabil Adouani Date: Fri, 24 Apr 2020 17:48:20 +0200 Subject: [PATCH 01/14] #1285 Update the observable import from analyzer reports and take into account the tags and message of the observable --- .../scripts/directives/report-observables.js | 55 ++++++++++++++----- .../views/directives/report-observables.html | 4 +- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/ui/app/scripts/directives/report-observables.js b/ui/app/scripts/directives/report-observables.js index 499b5bcc5d..c10f5ff534 100644 --- a/ui/app/scripts/directives/report-observables.js +++ b/ui/app/scripts/directives/report-observables.js @@ -67,22 +67,49 @@ return item.selected === true; }), 'dataType'); - var message = [ - '### Discovered from:', - '- Observable: **['+ $scope.origin.dataType + '] - ' + $filter('fang')($scope.origin.data) + '**', - '- Analyzer: **'+ $scope.analyzer + '**' - ].join('\n'); + _.each(toImport, function(list, key) { - var params = { - dataType: key, - single: list.length === 1, - ioc: false, - sighted: false, - tlp: 2, - message: message, - tags: [{text: 'src:' + $scope.analyzer}] - }; + var message = [ + '### Discovered from:', + '- Observable: **['+ $scope.origin.dataType + '] - ' + $filter('fang')($scope.origin.data) + '**', + '- Analyzer: **'+ $scope.analyzer + '**' + ]; + + var params; + + if(list.length === 1) { + var obs = list[0]; + + if(obs.message) { + message.push('- Message: ' + obs.message); + } + + params = { + dataType: key, + single: true, + ioc: false, + sighted: false, + tlp: obs.tlp || 2, + message: message.join('\n'), + tags: [{text: 'src:' + $scope.analyzer}].concat(_.map(_.uniq(obs.tags), function(i) { + return {text: i}; + })) + }; + } else { + params = { + dataType: key, + single: list.length === 1, + ioc: false, + sighted: false, + tlp: 2, + message: message.join('\n'), + tags: [{text: 'src:' + $scope.analyzer}] + }; + } + + + if(key === 'file') { params.attachment = _.pluck(list, 'attachment'); diff --git a/ui/app/views/directives/report-observables.html b/ui/app/views/directives/report-observables.html index 8f167f57a6..0f37686f21 100644 --- a/ui/app/views/directives/report-observables.html +++ b/ui/app/views/directives/report-observables.html @@ -26,7 +26,8 @@ - + + @@ -38,6 +39,7 @@ offset: (pagination.currentPage-1)*pagination.pageSize | limitTo: pagination.pageSize "> + From caf82a786fd56912ffe3292d2b8a9df4289e8d6d Mon Sep 17 00:00:00 2001 From: steoleary <16339695+steoleary@users.noreply.github.com> Date: Wed, 29 Apr 2020 09:21:49 +0100 Subject: [PATCH 02/14] Add link to add a task to a case template (#1049) Added an "add a task" link to make the tasks section more consistent with the custom fields and metrics sections which all have links to add items when they are empty --- ui/app/views/partials/admin/case-template/tasks.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/views/partials/admin/case-template/tasks.html b/ui/app/views/partials/admin/case-template/tasks.html index a09eae453c..e60f16559b 100644 --- a/ui/app/views/partials/admin/case-template/tasks.html +++ b/ui/app/views/partials/admin/case-template/tasks.html @@ -7,7 +7,7 @@

- No tasks have been specified. + No tasks have been specified. Add a task
From c24a3972c38811a6514b46d1b0fa31c8bcc3c494 Mon Sep 17 00:00:00 2001 From: To-om Date: Sat, 25 Apr 2020 09:11:58 +0200 Subject: [PATCH 03/14] Prepare release --- CHANGELOG.md | 11 +++++++++++ project/plugins.sbt | 2 +- ui/bower.json | 2 +- ui/package.json | 2 +- version.sbt | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0951807e57..31f7833080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## [3.4.2](https://github.com/TheHive-Project/TheHive/milestone/57) (2020-04-25) + +**Implemented enhancements:** + +- [Feature Request] Providing output details for Responders [\#962](https://github.com/TheHive-Project/TheHive/issues/962) + +**Fixed bugs:** + +- Analyzer's artifacts tags and message are not kept when importing observables [\#1285](https://github.com/TheHive-Project/TheHive/issues/1285) +- [Bug] File observables in alert are not created in case [\#1292](https://github.com/TheHive-Project/TheHive/issues/1292) + ## [3.4.1](https://github.com/TheHive-Project/TheHive/milestone/53) (2020-04-17) **Implemented enhancements:** diff --git a/project/plugins.sbt b/project/plugins.sbt index 5af970f6ad..8069c5c167 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,4 +4,4 @@ logLevel := Level.Info 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") -addSbtPlugin("org.thehive-project" % "sbt-github-changelog" % "0.2.0") +addSbtPlugin("org.thehive-project" % "sbt-github-changelog" % "0.3.0") diff --git a/ui/bower.json b/ui/bower.json index 7338a0bee8..baf64fcae5 100644 --- a/ui/bower.json +++ b/ui/bower.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.0-RC2", + "version": "3.4.2", "license": "AGPL-3.0", "dependencies": { "jquery": "^3.4.1", diff --git a/ui/package.json b/ui/package.json index cbe2c7d71e..4203f746c1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.0-RC2", + "version": "3.4.2", "license": "AGPL-3.0", "repository": { "type": "git", diff --git a/version.sbt b/version.sbt index cd1276f56c..06daa17521 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.4.1-1" +version in ThisBuild := "3.4.2-1" From 2b91a5a6496de8626d1b9a0508352ecd45b1abd1 Mon Sep 17 00:00:00 2001 From: To-om Date: Thu, 28 May 2020 16:40:07 +0200 Subject: [PATCH 04/14] #1377 Add support of ES7 --- build.sbt | 1 + project/Dependencies.scala | 28 +++++++------- project/plugins.sbt | 4 +- .../app/connectors/Connectors.scala | 5 +-- .../app/controllers/ArtifactCtrl.scala | 23 ++++++------ .../app/controllers/AttachmentCtrl.scala | 37 +++++++++---------- .../app/controllers/AuditCtrl.scala | 2 +- .../app/controllers/AuthenticationCtrl.scala | 6 +-- .../app/controllers/CaseCtrl.scala | 2 +- .../app/controllers/CaseTemplateCtrl.scala | 2 +- .../app/controllers/CustomFieldsCtrl.scala | 10 ++--- .../app/controllers/DBListCtrl.scala | 4 +- .../app/controllers/DashboardCtrl.scala | 3 +- .../app/controllers/DescribeCtrl.scala | 4 +- thehive-backend/app/controllers/LogCtrl.scala | 2 +- .../app/controllers/StatusCtrl.scala | 19 +++++----- .../app/controllers/StreamCtrl.scala | 37 +++++++------------ .../app/controllers/TaskCtrl.scala | 2 +- .../app/controllers/UserCtrl.scala | 2 +- thehive-backend/app/models/Alert.scala | 34 +++++------------ thehive-backend/app/models/Artifact.scala | 3 +- thehive-backend/app/models/Audit.scala | 4 +- thehive-backend/app/models/CaseTemplate.scala | 2 +- thehive-backend/app/models/Log.scala | 4 +- thehive-backend/app/models/Migration.scala | 31 +++++++--------- thehive-backend/app/models/Roles.scala | 12 +++--- thehive-backend/app/models/User.scala | 11 ++---- thehive-backend/app/models/package.scala | 2 +- thehive-backend/app/services/AlertSrv.scala | 8 +--- thehive-backend/app/services/AuditSrv.scala | 4 +- .../app/services/CustomWSAPI.scala | 2 +- .../app/services/StreamMessage.scala | 2 +- thehive-backend/app/services/StreamSrv.scala | 2 +- .../app/services/TheHiveAuthSrv.scala | 1 - .../services/mappers/GroupUserMapper.scala | 25 ++++++------- .../connectors/cortex/CortexConnector.scala | 7 ++-- .../cortex/controllers/CortexCtrl.scala | 19 ++++------ .../controllers/ReportTemplateCtrl.scala | 35 ++++++++---------- .../connectors/cortex/models/Artifact.scala | 6 +-- .../app/connectors/cortex/models/Job.scala | 2 +- .../connectors/cortex/models/JsonFormat.scala | 15 ++++---- .../cortex/models/ReportTemplate.scala | 2 +- .../cortex/services/ActionOperation.scala | 24 ++++++------ .../cortex/services/CortexAnalyzerSrv.scala | 1 - .../cortex/services/ReportTemplateSrv.scala | 22 ++++------- .../app/connectors/misp/MispConnector.scala | 8 ++-- .../app/connectors/misp/MispCtrl.scala | 7 +--- .../app/connectors/misp/MispExport.scala | 28 ++++++-------- .../app/connectors/misp/MispSrv.scala | 36 ++++++++---------- .../app/connectors/misp/MispSynchro.scala | 2 +- 50 files changed, 238 insertions(+), 316 deletions(-) diff --git a/build.sbt b/build.sbt index 82efa88501..c97fc2ebac 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ lazy val thehiveBackend = (project in file("thehive-backend")) Library.zip4j, Library.reflections, Library.akkaCluster, + Library.akkaClusterTyped, Library.akkaClusterTools ), play.sbt.routes.RoutesKeys.routesImport -= "controllers.Assets.Asset" diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 7be462cf0c..5c3a9d1e06 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,27 +1,27 @@ import sbt._ object Dependencies { - val scalaVersion = "2.12.6" + val scalaVersion = "2.12.11" object Library { object Play { - val version = play.core.PlayVersion.current - val ws = "com.typesafe.play" %% "play-ws" % version - val ahc = "com.typesafe.play" %% "play-ahc-ws" % version - val cache = "com.typesafe.play" %% "play-ehcache" % version - val test = "com.typesafe.play" %% "play-test" % version - val specs2 = "com.typesafe.play" %% "play-specs2" % version - val filters = "com.typesafe.play" %% "filters-helpers" % version - val guice = "com.typesafe.play" %% "play-guice" % version + val ws = "com.typesafe.play" %% "play-ws" % play.core.PlayVersion.current + val ahc = "com.typesafe.play" %% "play-ahc-ws" % play.core.PlayVersion.current + val cache = "com.typesafe.play" %% "play-ehcache" % play.core.PlayVersion.current + val test = "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current + val specs2 = "com.typesafe.play" %% "play-specs2" % play.core.PlayVersion.current + val filters = "com.typesafe.play" %% "filters-helpers" % play.core.PlayVersion.current + val guice = "com.typesafe.play" %% "play-guice" % play.core.PlayVersion.current } - val scalaGuice = "net.codingwell" %% "scala-guice" % "4.2.3" + val scalaGuice = "net.codingwell" %% "scala-guice" % "4.2.6" 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" - val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % "2.5.21" - val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % "2.5.21" + val zip4j = "net.lingala.zip4j" % "zip4j" % "2.6.0" + val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.0-SNAPSHOT" + val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % play.core.PlayVersion.akkaVersion + val akkaClusterTyped = "com.typesafe.akka" %% "akka-cluster-typed" % play.core.PlayVersion.akkaVersion + val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % play.core.PlayVersion.akkaVersion } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 8069c5c167..efb8586d60 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ // Comment to get more information during initialization logLevel := Level.Info -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.23") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.2") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.0") addSbtPlugin("org.thehive-project" % "sbt-github-changelog" % "0.3.0") diff --git a/thehive-backend/app/connectors/Connectors.scala b/thehive-backend/app/connectors/Connectors.scala index fe3d0982d6..3668976de1 100644 --- a/thehive-backend/app/connectors/Connectors.scala +++ b/thehive-backend/app/connectors/Connectors.scala @@ -1,13 +1,12 @@ package connectors import scala.collection.immutable - import play.api.libs.json.{JsObject, Json} import play.api.mvc._ import play.api.routing.sird.UrlContext import play.api.routing.{Router, SimpleRouter} - import com.google.inject.AbstractModule +import scala.reflect.runtime.{universe => ru} import javax.inject.{Inject, Singleton} import models.HealthStatus import net.codingwell.scalaguice.{ScalaModule, ScalaMultibinder} @@ -35,7 +34,7 @@ class ConnectorRouter @Inject()(connectors: immutable.Set[Connector], actionBuil abstract class ConnectorModule extends AbstractModule with ScalaModule { - def registerController[C <: Connector](implicit evidence: Manifest[C]): Unit = { + def registerController[C <: Connector: ru.TypeTag]: Unit = { val connectorBindings = ScalaMultibinder.newSetBinder[Connector](binder) connectorBindings.addBinding.to[C] () diff --git a/thehive-backend/app/controllers/ArtifactCtrl.scala b/thehive-backend/app/controllers/ArtifactCtrl.scala index 3bde2e0543..646c49c1e0 100644 --- a/thehive-backend/app/controllers/ArtifactCtrl.scala +++ b/thehive-backend/app/controllers/ArtifactCtrl.scala @@ -3,25 +3,24 @@ package controllers import java.io.FilterInputStream import java.nio.file.{Files, Path} -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} - +import javax.inject.{Inject, Singleton} +import models.Roles +import net.lingala.zip4j.ZipFile import play.api.http.Status import play.api.libs.json.{JsArray, JsValue} import play.api.mvc._ import play.api.{Configuration, Logger} -import javax.inject.{Inject, Singleton} -import models.Roles -import net.lingala.zip4j.core.ZipFile +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +//import net.lingala.zip4j.core.ZipFile import net.lingala.zip4j.model.FileHeader -import services.ArtifactSrv - import org.elastic4play.controllers._ import org.elastic4play.models.JsonFormat.baseModelEntityWrites import org.elastic4play.services.JsonFormat.{aggReads, queryReads} import org.elastic4play.services._ import org.elastic4play.{BadRequestError, InternalError, InvalidFormatAttributeError, Timed} +import services.ArtifactSrv @Singleton class ArtifactCtrl @Inject()( @@ -70,7 +69,7 @@ class ArtifactCtrl @Inject()( } } - private def getFieldsFromZipFile(caseId: String, fields: Fields, filepath: Path)(implicit authContext: AuthContext): Seq[Fields] = { + private def getFieldsFromZipFile(fields: Fields, filepath: Path)(implicit authContext: AuthContext): Seq[Fields] = { val zipFile = new ZipFile(filepath.toFile) val files: Seq[FileHeader] = zipFile.getFileHeaders.asScala.asInstanceOf[Seq[FileHeader]] @@ -79,7 +78,7 @@ class ArtifactCtrl @Inject()( .getString("zipPassword") .filterNot(_.isEmpty) .getOrElse(configuration.get[String]("datastore.attachment.password")) - zipFile.setPassword(pw) + zipFile.setPassword(pw.toCharArray) } /*val multiFields = */ @@ -125,7 +124,7 @@ class ArtifactCtrl @Inject()( .get("attachment") .map { case FileInputValue(_, filepath, _) if fields.getBoolean("isZip").getOrElse(false) ⇒ - Future.successful(getFieldsFromZipFile(caseId, fields, filepath)) + Future.successful(getFieldsFromZipFile(fields, filepath)) case _: FileInputValue ⇒ Future.successful(Seq(fields)) case JsonInputValue(JsArray(attachments)) ⇒ Future.traverse(attachments)(attachment ⇒ getFieldsFromAttachment(fields, attachment)).map(_.flatten) @@ -155,7 +154,7 @@ class ArtifactCtrl @Inject()( } @Timed - def get(id: String): Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { implicit request ⇒ + def get(id: String): Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { _ ⇒ artifactSrv .get(id) .map(artifact ⇒ renderer.toOutput(OK, artifact)) diff --git a/thehive-backend/app/controllers/AttachmentCtrl.scala b/thehive-backend/app/controllers/AttachmentCtrl.scala index 46dce09a56..b66aebe746 100644 --- a/thehive-backend/app/controllers/AttachmentCtrl.scala +++ b/thehive-backend/app/controllers/AttachmentCtrl.scala @@ -1,23 +1,21 @@ package controllers import java.nio.file.Files -import javax.inject.{Inject, Singleton} - -import play.api.http.HttpEntity -import play.api.libs.Files.DefaultTemporaryFileCreator -import play.api.mvc._ -import play.api.{mvc, Configuration} import akka.stream.scaladsl.FileIO -import net.lingala.zip4j.core.ZipFile -import net.lingala.zip4j.model.ZipParameters -import net.lingala.zip4j.util.Zip4jConstants +import javax.inject.{Inject, Singleton} import models.Roles - +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.ZipParameters +import net.lingala.zip4j.model.enums.{CompressionLevel, EncryptionMethod} import org.elastic4play.Timed -import org.elastic4play.controllers.{Authenticated, Renderer} +import org.elastic4play.controllers.Authenticated import org.elastic4play.models.AttachmentAttributeFormat import org.elastic4play.services.AttachmentSrv +import play.api.http.HttpEntity +import play.api.libs.Files.DefaultTemporaryFileCreator +import play.api.mvc._ +import play.api.{Configuration, mvc} /** * Controller used to access stored attachments (plain or zipped) @@ -29,7 +27,6 @@ class AttachmentCtrl( attachmentSrv: AttachmentSrv, authenticated: Authenticated, components: ControllerComponents, - renderer: Renderer ) extends AbstractController(components) { @Inject() def this( @@ -38,9 +35,8 @@ class AttachmentCtrl( attachmentSrv: AttachmentSrv, authenticated: Authenticated, components: ControllerComponents, - renderer: Renderer ) = - this(configuration.get[String]("datastore.attachment.password"), tempFileCreator, attachmentSrv, authenticated, components, renderer) + this(configuration.get[String]("datastore.attachment.password"), tempFileCreator, attachmentSrv, authenticated, components) /** * Download an attachment, identified by its hash, in plain format @@ -48,7 +44,7 @@ class AttachmentCtrl( * open the document directly. It must be used only for safe file */ @Timed("controllers.AttachmentCtrl.download") - def download(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def download(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ if (hash.startsWith("{{")) // angularjs hack NoContent else if (!name.getOrElse("").intersect(AttachmentAttributeFormat.forbiddenChar).isEmpty) @@ -69,20 +65,21 @@ class AttachmentCtrl( * File name can be specified (zip extension is append) */ @Timed("controllers.AttachmentCtrl.downloadZip") - def downloadZip(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def downloadZip(hash: String, name: Option[String]): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ if (!name.getOrElse("").intersect(AttachmentAttributeFormat.forbiddenChar).isEmpty) BadRequest("File name is invalid") else { val f = tempFileCreator.create("zip", hash).path Files.delete(f) val zipFile = new ZipFile(f.toFile) + zipFile.setPassword(password.toCharArray) val zipParams = new ZipParameters - zipParams.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_FASTEST) + zipParams.setCompressionLevel(CompressionLevel.FASTEST) zipParams.setEncryptFiles(true) - zipParams.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD) - zipParams.setPassword(password) + zipParams.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD) +// zipParams.setsetPassword(password.toCharArray) zipParams.setFileNameInZip(name.getOrElse(hash)) - zipParams.setSourceExternalStream(true) +// zipParams.setSourceExternalStream(true) zipFile.addStream(attachmentSrv.stream(hash), zipParams) Result( diff --git a/thehive-backend/app/controllers/AuditCtrl.scala b/thehive-backend/app/controllers/AuditCtrl.scala index 128a96e277..49002b4803 100644 --- a/thehive-backend/app/controllers/AuditCtrl.scala +++ b/thehive-backend/app/controllers/AuditCtrl.scala @@ -30,7 +30,7 @@ class AuditCtrl @Inject()( * Return audit logs. For each item, include ancestor entities */ @Timed - def flow(rootId: Option[String], count: Option[Int]): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def flow(rootId: Option[String], count: Option[Int]): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ val (audits, total) = auditSrv(rootId.filterNot(_ == "any"), count.getOrElse(10)) renderer.toOutput(OK, audits, total) } diff --git a/thehive-backend/app/controllers/AuthenticationCtrl.scala b/thehive-backend/app/controllers/AuthenticationCtrl.scala index cae6774be5..e9579cee5c 100644 --- a/thehive-backend/app/controllers/AuthenticationCtrl.scala +++ b/thehive-backend/app/controllers/AuthenticationCtrl.scala @@ -1,9 +1,8 @@ package controllers import javax.inject.{Inject, Singleton} - import models.UserStatus -import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser, Renderer} +import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser} import org.elastic4play.database.DBIndex import org.elastic4play.services.AuthSrv import org.elastic4play.{AuthorizationError, OAuth2Redirect, Timed} @@ -18,7 +17,6 @@ class AuthenticationCtrl @Inject()( userSrv: UserSrv, authenticated: Authenticated, dbIndex: DBIndex, - renderer: Renderer, components: ControllerComponents, fieldsBodyParser: FieldsBodyParser, implicit val ec: ExecutionContext @@ -63,7 +61,7 @@ class AuthenticationCtrl @Inject()( } @Timed - def logout = Action { + def logout: Action[AnyContent] = Action { Ok.withNewSession } } diff --git a/thehive-backend/app/controllers/CaseCtrl.scala b/thehive-backend/app/controllers/CaseCtrl.scala index a3654677a0..1160f20686 100644 --- a/thehive-backend/app/controllers/CaseCtrl.scala +++ b/thehive-backend/app/controllers/CaseCtrl.scala @@ -126,7 +126,7 @@ class CaseCtrl @Inject()( } @Timed - def linkedCases(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def linkedCases(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ caseSrv .linkedCases(id) .runWith(Sink.seq) diff --git a/thehive-backend/app/controllers/CaseTemplateCtrl.scala b/thehive-backend/app/controllers/CaseTemplateCtrl.scala index 66292283c3..091b4b45b3 100644 --- a/thehive-backend/app/controllers/CaseTemplateCtrl.scala +++ b/thehive-backend/app/controllers/CaseTemplateCtrl.scala @@ -36,7 +36,7 @@ class CaseTemplateCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ caseTemplateSrv .get(id) .map(caze ⇒ renderer.toOutput(OK, caze)) diff --git a/thehive-backend/app/controllers/CustomFieldsCtrl.scala b/thehive-backend/app/controllers/CustomFieldsCtrl.scala index a5a7b38b35..4975e243a8 100644 --- a/thehive-backend/app/controllers/CustomFieldsCtrl.scala +++ b/thehive-backend/app/controllers/CustomFieldsCtrl.scala @@ -1,17 +1,15 @@ 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 com.sksamuel.elastic4s.ElasticDsl.{search, termsAgg} +import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.Terms import javax.inject.{Inject, Singleton} import models.Roles - import org.elastic4play.NotFoundError import org.elastic4play.controllers.Authenticated import org.elastic4play.database.DBFind @@ -43,9 +41,9 @@ 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).aggregations(termsAggregation("t").field("relations")) + indexName ⇒ search(indexName).query(filter.query).aggregations(termsAgg("t","relations")) ).map { searchResponse ⇒ - val buckets = searchResponse.aggregations.terms("t").buckets + val buckets = searchResponse.aggregations.result[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)) diff --git a/thehive-backend/app/controllers/DBListCtrl.scala b/thehive-backend/app/controllers/DBListCtrl.scala index d4c6a68df3..74496673d6 100644 --- a/thehive-backend/app/controllers/DBListCtrl.scala +++ b/thehive-backend/app/controllers/DBListCtrl.scala @@ -23,14 +23,14 @@ class DBListCtrl @Inject()( ) extends AbstractController(components) { @Timed("controllers.DBListCtrl.list") - def list: Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def list: Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ dblists.listAll.map { listNames ⇒ renderer.toOutput(OK, listNames) } } @Timed("controllers.DBListCtrl.listItems") - def listItems(listName: String): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def listItems(listName: String): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ val (src, _) = dblists(listName).getItems[JsValue] val items = src .map { case (id, value) ⇒ s""""$id":$value""" } diff --git a/thehive-backend/app/controllers/DashboardCtrl.scala b/thehive-backend/app/controllers/DashboardCtrl.scala index 901a0b7dc5..b4311da67a 100644 --- a/thehive-backend/app/controllers/DashboardCtrl.scala +++ b/thehive-backend/app/controllers/DashboardCtrl.scala @@ -21,7 +21,6 @@ import org.elastic4play.{AuthorizationError, BadRequestError, Timed} @Singleton class DashboardCtrl @Inject()( dashboardSrv: DashboardSrv, - auxSrv: AuxSrv, authenticated: Authenticated, renderer: Renderer, components: ControllerComponents, @@ -41,7 +40,7 @@ class DashboardCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ dashboardSrv.get(id).map { dashboard ⇒ renderer.toOutput(OK, dashboard) } diff --git a/thehive-backend/app/controllers/DescribeCtrl.scala b/thehive-backend/app/controllers/DescribeCtrl.scala index 24829e7b57..5e00dbc370 100644 --- a/thehive-backend/app/controllers/DescribeCtrl.scala +++ b/thehive-backend/app/controllers/DescribeCtrl.scala @@ -33,7 +33,7 @@ class DescribeCtrl @Inject()( Json.obj("label" → model.label, "path" → model.path, "attributes" → attributeDefinitions) } - def describe(modelName: String): Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def describe(modelName: String): Action[AnyContent] = authenticated(Roles.read) { _ ⇒ modelSrv(modelName) .map { model ⇒ renderer.toOutput(OK, modelToJson(model)) @@ -43,7 +43,7 @@ class DescribeCtrl @Inject()( private val allModels: Seq[String] = Seq("case", "case_artifact", "case_task", "case_task_log", "alert", "case_artifact_job", "audit", "action") - def describeAll: Action[AnyContent] = authenticated(Roles.read) { implicit request ⇒ + def describeAll: Action[AnyContent] = authenticated(Roles.read) { _ ⇒ val entityDefinitions = modelSrv .list .collect { diff --git a/thehive-backend/app/controllers/LogCtrl.scala b/thehive-backend/app/controllers/LogCtrl.scala index 5bb5398e67..ff4879dfd4 100644 --- a/thehive-backend/app/controllers/LogCtrl.scala +++ b/thehive-backend/app/controllers/LogCtrl.scala @@ -34,7 +34,7 @@ class LogCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ logSrv .get(id) .map(log ⇒ renderer.toOutput(OK, log)) diff --git a/thehive-backend/app/controllers/StatusCtrl.scala b/thehive-backend/app/controllers/StatusCtrl.scala index 936a029744..63b24b1281 100644 --- a/thehive-backend/app/controllers/StatusCtrl.scala +++ b/thehive-backend/app/controllers/StatusCtrl.scala @@ -1,25 +1,24 @@ 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 com.sksamuel.elastic4s.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 org.elasticsearch.client.Node +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 scala.collection.immutable +import scala.concurrent.ExecutionContext import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.util.Try @Singleton class StatusCtrl @Inject()( diff --git a/thehive-backend/app/controllers/StreamCtrl.scala b/thehive-backend/app/controllers/StreamCtrl.scala index 7f3ca03a27..756236b735 100644 --- a/thehive-backend/app/controllers/StreamCtrl.scala +++ b/thehive-backend/app/controllers/StreamCtrl.scala @@ -1,30 +1,27 @@ package controllers +import akka.actor.{ActorIdentity, ActorSystem, Identify, Props} +import akka.cluster.pubsub.DistributedPubSub +import akka.cluster.pubsub.DistributedPubSubMediator.{Put, Send} +import akka.pattern.{AskTimeoutException, ask} +import akka.util.Timeout import javax.inject.{Inject, Singleton} - -import scala.collection.immutable -import scala.concurrent.{ExecutionContext, Future} -import scala.concurrent.duration.{DurationLong, FiniteDuration} -import scala.util.Random - +import models.Roles +import org.elastic4play.Timed +import org.elastic4play.controllers._ +import org.elastic4play.services.{MigrationSrv, UserSrv} import play.api.http.Status import play.api.libs.json.Json import play.api.libs.json.Json.toJsFieldJsValueWrapper import play.api.mvc._ import play.api.{Configuration, Logger} - -import akka.actor.{ActorIdentity, ActorSystem, Identify, Props} -import akka.cluster.pubsub.DistributedPubSub -import akka.cluster.pubsub.DistributedPubSubMediator.{Put, Send} -import akka.pattern.{ask, AskTimeoutException} -import akka.util.Timeout -import models.Roles import services.StreamActor import services.StreamActor.StreamMessages -import org.elastic4play.controllers._ -import org.elastic4play.services.{AuxSrv, EventSrv, MigrationSrv, UserSrv} -import org.elastic4play.Timed +import scala.collection.immutable +import scala.concurrent.duration.{DurationLong, FiniteDuration} +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Random @Singleton class StreamCtrl( @@ -32,9 +29,7 @@ class StreamCtrl( refresh: FiniteDuration, authenticated: Authenticated, renderer: Renderer, - eventSrv: EventSrv, userSrv: UserSrv, - auxSrv: AuxSrv, migrationSrv: MigrationSrv, components: ControllerComponents, implicit val system: ActorSystem, @@ -46,9 +41,7 @@ class StreamCtrl( configuration: Configuration, authenticated: Authenticated, renderer: Renderer, - eventSrv: EventSrv, userSrv: UserSrv, - auxSrv: AuxSrv, migrationSrv: MigrationSrv, components: ControllerComponents, system: ActorSystem, @@ -59,9 +52,7 @@ class StreamCtrl( configuration.getMillis("stream.longpolling.refresh").millis, authenticated, renderer, - eventSrv, userSrv, - auxSrv, migrationSrv, components, system, @@ -125,7 +116,7 @@ class StreamCtrl( } @Timed("controllers.StreamCtrl.status") - def status = Action { implicit request ⇒ + def status: Action[AnyContent] = Action { implicit request ⇒ val status = authenticated.expirationStatus(request) match { case ExpirationWarning(duration) ⇒ Json.obj("remaining" → duration.toSeconds, "warning" → true) case ExpirationError ⇒ Json.obj("remaining" → 0, "warning" → true) diff --git a/thehive-backend/app/controllers/TaskCtrl.scala b/thehive-backend/app/controllers/TaskCtrl.scala index defb9d4143..730548103e 100644 --- a/thehive-backend/app/controllers/TaskCtrl.scala +++ b/thehive-backend/app/controllers/TaskCtrl.scala @@ -36,7 +36,7 @@ class TaskCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ taskSrv .get(id) .map(task ⇒ renderer.toOutput(OK, task)) diff --git a/thehive-backend/app/controllers/UserCtrl.scala b/thehive-backend/app/controllers/UserCtrl.scala index a785317ad1..2965a74bcb 100644 --- a/thehive-backend/app/controllers/UserCtrl.scala +++ b/thehive-backend/app/controllers/UserCtrl.scala @@ -41,7 +41,7 @@ class UserCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ userSrv .get(id) .map { user ⇒ diff --git a/thehive-backend/app/models/Alert.scala b/thehive-backend/app/models/Alert.scala index b9afab587f..f45c45db98 100644 --- a/thehive-backend/app/models/Alert.scala +++ b/thehive-backend/app/models/Alert.scala @@ -2,32 +2,18 @@ package models import java.util.Date -import scala.concurrent.Future -import scala.util.Try - -import play.api.Logger -import play.api.libs.json._ - -import javax.inject.{Inject, Singleton} +import javax.inject.Singleton import models.JsonFormat.alertStatusFormat -import services.AuditedModel - import org.elastic4play.controllers.JsonInputValue -import org.elastic4play.models.{ - Attribute, - AttributeDef, - BaseEntity, - EntityDef, - HiveEnumeration, - ModelDef, - MultiAttributeFormat, - OptionalAttributeFormat, - AttributeFormat ⇒ F, - AttributeOption ⇒ O -} -import org.elastic4play.services.DBLists +import org.elastic4play.models.{Attribute, AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, MultiAttributeFormat, OptionalAttributeFormat, AttributeFormat => F, AttributeOption => O} import org.elastic4play.utils.Hasher import org.elastic4play.{AttributeCheckingError, InvalidFormatAttributeError} +import play.api.Logger +import play.api.libs.json._ +import services.AuditedModel + +import scala.concurrent.Future +import scala.util.Try object AlertStatus extends Enumeration with HiveEnumeration { type Type = Value @@ -60,7 +46,7 @@ trait AlertAttributes { Attribute("alert", "ioc", OptionalAttributeFormat(F.booleanFmt), Nil, None, "") ) } - + val alertId: A[String] = attribute("_id", F.stringFmt, "Alert id", O.readonly) val tpe: A[String] = attribute("type", F.stringFmt, "Type of the alert", O.readonly) val source: A[String] = attribute("source", F.stringFmt, "Source of the alert", O.readonly) @@ -81,7 +67,7 @@ trait AlertAttributes { } @Singleton -class AlertModel @Inject()(dblists: DBLists) extends ModelDef[AlertModel, Alert]("alert", "Alert", "/alert") with AlertAttributes with AuditedModel { +class AlertModel extends ModelDef[AlertModel, Alert]("alert", "Alert", "/alert") with AlertAttributes with AuditedModel { private[AlertModel] lazy val logger = Logger(getClass) override val defaultSortBy: Seq[String] = Seq("-date") diff --git a/thehive-backend/app/models/Artifact.scala b/thehive-backend/app/models/Artifact.scala index 25264ee27d..beb747afaa 100644 --- a/thehive-backend/app/models/Artifact.scala +++ b/thehive-backend/app/models/Artifact.scala @@ -102,10 +102,9 @@ class ArtifactModel @Inject()( mm.addValue((attrs \ "data").asOpt[JsValue].getOrElse(JsNull)) mm.addValue((attrs \ "dataType").asOpt[JsValue].getOrElse(JsNull)) for { - IOResult(_, done) ← (attrs \ "attachment" \ "filepath") + IOResult(_, _) ← (attrs \ "attachment" \ "filepath") .asOpt[String] .fold(Future.successful(IOResult(0, Success(Done))))(file ⇒ mm.addFile(file)) - _ ← Future.fromTry(done) _ ← (attrs \ "attachment" \ "id") .asOpt[String] .fold(Future.successful(NotUsed: NotUsed)) { fileId ⇒ diff --git a/thehive-backend/app/models/Audit.scala b/thehive-backend/app/models/Audit.scala index 6cd670c8eb..5aa0f32b5a 100644 --- a/thehive-backend/app/models/Audit.scala +++ b/thehive-backend/app/models/Audit.scala @@ -48,7 +48,7 @@ class AuditModel(auditName: String, auditedModels: immutable.Set[AuditedModel], extends ModelDef[AuditModel, Audit](auditName, "Audit", "/audit") with AuditAttributes { - lazy val auxSrv = auxSrvProvider.get() + lazy val auxSrv: AuxSrv = auxSrvProvider.get() @Inject() def this( configuration: Configuration, @@ -134,5 +134,5 @@ class AuditModel(auditName: String, auditedModels: immutable.Set[AuditedModel], } class Audit(model: AuditModel, attributes: JsObject) extends EntityDef[AuditModel, Audit](model, attributes) with AuditAttributes { - def detailsAttributes = Nil + def detailsAttributes: Seq[Attribute[_]] = Nil } diff --git a/thehive-backend/app/models/CaseTemplate.scala b/thehive-backend/app/models/CaseTemplate.scala index dc3f607298..36d941f9ee 100644 --- a/thehive-backend/app/models/CaseTemplate.scala +++ b/thehive-backend/app/models/CaseTemplate.scala @@ -44,5 +44,5 @@ class CaseTemplateModel @Inject()(taskModel: TaskModel) class CaseTemplate(model: CaseTemplateModel, attributes: JsObject) extends EntityDef[CaseTemplateModel, CaseTemplate](model, attributes) with CaseTemplateAttributes { - def taskAttributes = Nil + def taskAttributes: Seq[Attribute[_]] = Nil } diff --git a/thehive-backend/app/models/Log.scala b/thehive-backend/app/models/Log.scala index 611ac378ac..b8e77d5d2e 100644 --- a/thehive-backend/app/models/Log.scala +++ b/thehive-backend/app/models/Log.scala @@ -35,7 +35,7 @@ class LogModel @Inject()(taskModel: TaskModel) extends ChildModelDef[LogModel, Log, TaskModel, Task](taskModel, "case_task_log", "Log", "/case/task/log") with LogAttributes with AuditedModel { - override val defaultSortBy = Seq("-startDate") - override val removeAttribute = Json.obj("status" → LogStatus.Deleted) + override val defaultSortBy: Seq[String] = Seq("-startDate") + override val removeAttribute: JsObject = Json.obj("status" → LogStatus.Deleted) } class Log(model: LogModel, attributes: JsObject) extends EntityDef[LogModel, Log](model, attributes) with LogAttributes diff --git a/thehive-backend/app/models/Migration.scala b/thehive-backend/app/models/Migration.scala index ae6a65ce22..febdd6e875 100644 --- a/thehive-backend/app/models/Migration.scala +++ b/thehive-backend/app/models/Migration.scala @@ -3,26 +3,24 @@ package models import java.nio.file.{Files, Path} import java.util.Date -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} -import scala.math.BigDecimal.int2bigDecimal -import scala.util.Try - -import play.api.libs.json.JsValue.jsValueToJsLookup -import play.api.libs.json._ -import play.api.{Configuration, Environment, Logger} - import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source import javax.inject.{Inject, Singleton} -import services.{AlertSrv, DashboardSrv} - import org.elastic4play.ConflictError import org.elastic4play.controllers.Fields import org.elastic4play.services.JsonFormat.attachmentFormat -import org.elastic4play.services.{IndexType, _} +import org.elastic4play.services._ import org.elastic4play.utils.Hasher +import play.api.libs.json.JsValue.jsValueToJsLookup +import play.api.libs.json._ +import play.api.{Configuration, Environment, Logger} +import services.{AlertSrv, DashboardSrv} + +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +import scala.math.BigDecimal.int2bigDecimal +import scala.util.Try case class UpdateMispAlertArtifact() extends EventMessage @@ -143,8 +141,6 @@ class Migration( } - override def indexType(version: Int): IndexType.Value = if (version > 14) IndexType.indexWithoutMappingTypes else IndexType.indexWithMappingTypes - override val operations: PartialFunction[DatabaseState, Seq[Operation]] = { case DatabaseState(version) if version < 7 ⇒ Nil case DatabaseState(7) ⇒ @@ -248,7 +244,7 @@ class Migration( dataStr ← (artifact \ "data").asOpt[String] dataJson ← Try(Json.parse(dataStr)).toOption dataObj ← dataJson.asOpt[JsObject] - filename ← (dataObj \ "filename").asOpt[String].map(_.split("|").head) + filename ← (dataObj \ "filename").asOpt[String].map(_.split("\\|").head) attributeId ← (dataObj \ "attributeId").asOpt[String] attributeType ← (dataObj \ "attributeType").asOpt[String] } yield Future.successful( @@ -280,7 +276,7 @@ class Migration( } } Source - .fromFuture(artifactsAndData) + .future(artifactsAndData) .mapConcat { ad ⇒ val updatedAlert = alert + ("artifacts" → JsArray(ad.map(_._1))) updatedAlert :: ad.flatMap(_._2) @@ -355,7 +351,7 @@ class Migration( audit } } - Source.fromFuture(updatedAudit) + Source.future(updatedAudit) case audit ⇒ Source.single(audit) } case other ⇒ f(other) @@ -378,6 +374,7 @@ class Migration( ("sequenceCounter" → counter) } ) + case DatabaseState(15) ⇒ Nil } private def generateAlertId(alert: JsObject): String = { diff --git a/thehive-backend/app/models/Roles.scala b/thehive-backend/app/models/Roles.scala index 393b22cc67..9e6e574b31 100644 --- a/thehive-backend/app/models/Roles.scala +++ b/thehive-backend/app/models/Roles.scala @@ -1,16 +1,14 @@ package models -import play.api.libs.json.{JsString, JsValue} - -import com.sksamuel.elastic4s.http.ElasticDsl.keywordField -import com.sksamuel.elastic4s.mappings.KeywordField -import org.scalactic.{Every, Good, One, Or} +import com.sksamuel.elastic4s.ElasticDsl.keywordField +import com.sksamuel.elastic4s.requests.mappings.KeywordField import models.JsonFormat.roleFormat - -import org.elastic4play.{AttributeError, InvalidFormatAttributeError} import org.elastic4play.controllers.{InputValue, JsonInputValue, StringInputValue} import org.elastic4play.models.AttributeFormat import org.elastic4play.services.Role +import org.elastic4play.{AttributeError, InvalidFormatAttributeError} +import org.scalactic.{Every, Good, One, Or} +import play.api.libs.json.{JsString, JsValue} object Roles { object read extends Role("read") diff --git a/thehive-backend/app/models/User.scala b/thehive-backend/app/models/User.scala index 97fd563407..8c88d3465c 100644 --- a/thehive-backend/app/models/User.scala +++ b/thehive-backend/app/models/User.scala @@ -1,15 +1,12 @@ package models import scala.concurrent.Future - import play.api.libs.json.JsValue.jsValueToJsLookup import play.api.libs.json._ - import models.JsonFormat.userStatusFormat import services.AuditedModel - -import org.elastic4play.models.{AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, AttributeFormat ⇒ F, AttributeOption ⇒ O} -import org.elastic4play.services.{User ⇒ EUser} +import org.elastic4play.models.{AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, AttributeFormat => F, AttributeOption => O} +import org.elastic4play.services.{Role, User => EUser} object UserStatus extends Enumeration with HiveEnumeration { type Type = Value @@ -40,8 +37,8 @@ class UserModel extends ModelDef[UserModel, User]("user", "User", "/user") with } class User(model: UserModel, attributes: JsObject) extends EntityDef[UserModel, User](model, attributes) with UserAttributes with EUser { - override def getUserName = userName() - override def getRoles = roles() + override def getUserName: String = userName() + override def getRoles: Seq[Role] = roles() override def toJson: JsObject = super.toJson + diff --git a/thehive-backend/app/models/package.scala b/thehive-backend/app/models/package.scala index 5817120386..92fd3669a9 100644 --- a/thehive-backend/app/models/package.scala +++ b/thehive-backend/app/models/package.scala @@ -1,3 +1,3 @@ package object models { - val modelVersion = 15 + val modelVersion = 16 } diff --git a/thehive-backend/app/services/AlertSrv.scala b/thehive-backend/app/services/AlertSrv.scala index 79755cd5c3..952b5719dd 100644 --- a/thehive-backend/app/services/AlertSrv.scala +++ b/thehive-backend/app/services/AlertSrv.scala @@ -39,7 +39,6 @@ object AlertSrv { @Singleton class AlertSrv( - templates: Map[String, String], alertModel: AlertModel, createSrv: CreateSrv, getSrv: GetSrv, @@ -51,7 +50,6 @@ class AlertSrv( caseTemplateSrv: CaseTemplateSrv, attachmentSrv: AttachmentSrv, connectors: ConnectorRouter, - hashAlg: Seq[String], implicit val ec: ExecutionContext, implicit val mat: Materializer ) extends AlertTransformer { @@ -73,7 +71,6 @@ class AlertSrv( mat: Materializer ) = this( - Map.empty[String, String], alertModel: AlertModel, createSrv, getSrv, @@ -85,7 +82,6 @@ class AlertSrv( caseTemplateSrv, attachmentSrv, connectors, - (configuration.get[String]("datastore.hash.main") +: configuration.get[Seq[String]]("datastore.hash.extra")).distinct, ec, mat ) @@ -204,7 +200,7 @@ class AlertSrv( case None ⇒ updateSrv[AlertModel, Alert](alertModel, alert.id, Fields.empty.set("status", "New"), modifyConfig) } - def getCaseTemplate(alert: Alert, customCaseTemplate: Option[String]): Future[Option[CaseTemplate]] = + def getCaseTemplate(customCaseTemplate: Option[String]): Future[Option[CaseTemplate]] = customCaseTemplate.fold[Future[Option[CaseTemplate]]](Future.successful(None)) { templateName ⇒ caseTemplateSrv .getByName(templateName) @@ -226,7 +222,7 @@ class AlertSrv( } yield caze case _ ⇒ for { - caseTemplate ← getCaseTemplate(alert, customCaseTemplate) + caseTemplate ← getCaseTemplate(customCaseTemplate) caze ← caseSrv.create( Fields .empty diff --git a/thehive-backend/app/services/AuditSrv.scala b/thehive-backend/app/services/AuditSrv.scala index bd4c147c24..ee7e9e380b 100644 --- a/thehive-backend/app/services/AuditSrv.scala +++ b/thehive-backend/app/services/AuditSrv.scala @@ -24,7 +24,7 @@ trait AuditedModel { self: BaseModelDef ⇒ lazy val auditedAttributes: Map[String, Attribute[_]] = attributes.collect { case a if !a.isUnaudited ⇒ a.attributeName → a }.toMap - def selectAuditedAttributes(attrs: JsObject) = JsObject { + def selectAuditedAttributes(attrs: JsObject): JsObject = JsObject { attrs.fields.flatMap { case (attrName, value) ⇒ val attrNames = attrName.split("\\.").toSeq @@ -102,7 +102,7 @@ class AuditActor @Inject()(auditModel: AuditModel, createSrv: CreateSrv, eventSr extends Actor { object EntityExtractor { - def unapply(e: BaseEntity) = Some((e.model, e.id, e.routing)) + def unapply(e: BaseEntity): Some[(BaseModelDef, String, String)] = Some((e.model, e.id, e.routing)) } var currentRequestIds = Set.empty[String] private[AuditActor] lazy val logger = Logger(getClass) diff --git a/thehive-backend/app/services/CustomWSAPI.scala b/thehive-backend/app/services/CustomWSAPI.scala index ae57ec135b..f27e5a066d 100644 --- a/thehive-backend/app/services/CustomWSAPI.scala +++ b/thehive-backend/app/services/CustomWSAPI.scala @@ -60,7 +60,7 @@ object CustomWSAPI { .trustManagerConfig .withTrustStoreConfigs( clientConfig.wsClientConfig.ssl.trustManagerConfig.trustStoreConfigs :+ TrustStoreConfig( - filePath = Some(p.toString), + filePath = Some(p), data = None ) ) diff --git a/thehive-backend/app/services/StreamMessage.scala b/thehive-backend/app/services/StreamMessage.scala index 57b87d1e0b..db679edb6a 100644 --- a/thehive-backend/app/services/StreamMessage.scala +++ b/thehive-backend/app/services/StreamMessage.scala @@ -32,7 +32,7 @@ case class AggregatedAuditMessage(auxSrv: AuxSrv, message: Future[JsObject], sum } object AggregatedAuditMessage { - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) def apply(auxSrv: AuxSrv, operation: AuditOperation)(implicit ec: ExecutionContext): AggregatedAuditMessage = { // First operation of the group diff --git a/thehive-backend/app/services/StreamSrv.scala b/thehive-backend/app/services/StreamSrv.scala index b293f18c39..6cca5fb54b 100644 --- a/thehive-backend/app/services/StreamSrv.scala +++ b/thehive-backend/app/services/StreamSrv.scala @@ -29,7 +29,7 @@ object StreamActor { case class StreamMessages(messages: Seq[JsObject]) extends StreamActorMessage object StreamMessages { - val empty = StreamMessages(Nil) + val empty: StreamMessages = StreamMessages(Nil) } } diff --git a/thehive-backend/app/services/TheHiveAuthSrv.scala b/thehive-backend/app/services/TheHiveAuthSrv.scala index e66c783cdc..a49224aedb 100644 --- a/thehive-backend/app/services/TheHiveAuthSrv.scala +++ b/thehive-backend/app/services/TheHiveAuthSrv.scala @@ -29,7 +29,6 @@ object TheHiveAuthSrv { class TheHiveAuthSrv @Inject()( configuration: Configuration, authModules: immutable.Set[AuthSrv], - userSrv: UserSrv, implicit override val ec: ExecutionContext ) extends MultiAuthSrv( TheHiveAuthSrv.getAuthSrv(configuration.getDeprecated[Option[Seq[String]]]("auth.provider", "auth.type").getOrElse(Seq("local")), authModules), diff --git a/thehive-backend/app/services/mappers/GroupUserMapper.scala b/thehive-backend/app/services/mappers/GroupUserMapper.scala index cf036ce379..cdc76b9c6c 100644 --- a/thehive-backend/app/services/mappers/GroupUserMapper.scala +++ b/thehive-backend/app/services/mappers/GroupUserMapper.scala @@ -4,14 +4,14 @@ import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} import scala.util.parsing.combinator._ - import play.api.{Configuration, Logger} import play.api.libs.json._ import play.api.libs.ws.WSClient - import org.elastic4play.{AuthenticationError, AuthorizationError} import org.elastic4play.controllers.Fields +import scala.util.matching.Regex + class GroupUserMapper( loginAttrName: String, nameAttrName: String, @@ -40,9 +40,9 @@ class GroupUserMapper( private[GroupUserMapper] lazy val logger = Logger(getClass) private class RoleListParser extends RegexParsers { - val str = "[a-zA-Z0-9_]+".r - val strSpc = "[a-zA-Z0-9_ ]+".r - val realStr = ("\""~>strSpc<~"\"" | "'"~>strSpc<~"'" | str) + val str: Regex = "[a-zA-Z0-9_]+".r + val strSpc: Regex = "[a-zA-Z0-9_ ]+".r + val realStr: Parser[String] = "\""~>strSpc<~"\"" | "'"~>strSpc<~"'" | str def expr: Parser[Seq[String]] = { "[" ~ opt(realStr ~ rep("," ~ realStr)) ~ "]" ^^ { @@ -59,39 +59,36 @@ class GroupUserMapper( override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = { groupsUrl match { - case Some(groupsEndpointUrl) ⇒ { + case Some(groupsEndpointUrl) ⇒ logger.debug(s"Retreiving groups from ${groupsEndpointUrl}") val apiCall = authHeader.fold(ws.url(groupsEndpointUrl))(headers ⇒ ws.url(groupsEndpointUrl).addHttpHeaders(headers)) apiCall.get.flatMap { r ⇒ extractGroupsThenBuildUserFields(jsValue, r.json) } - } - case None ⇒ { + case None ⇒ logger.debug(s"Extracting groups from user info") extractGroupsThenBuildUserFields(jsValue, jsValue) - } } } private def extractGroupsThenBuildUserFields(jsValue: JsValue, groupsContainer: JsValue): Future[Fields] = { - (groupsContainer \ groupsAttrName) match { + groupsContainer \ groupsAttrName match { // Groups received as valid JSON array case JsDefined(JsArray(groupsList)) ⇒ mapGroupsAndBuildUserFields(jsValue, groupsList.map(_.as[String]).toList) // Groups list received as string (invalid JSON, for example: "ROLE" or "['Role 1', ROLE2, 'Role_3']") - case JsDefined(JsString(groupsStr)) ⇒ { + case JsDefined(JsString(groupsStr)) ⇒ val parser = new RoleListParser parser.parseAll(parser.expr, groupsStr) match { case parser.Success(result, _) ⇒ mapGroupsAndBuildUserFields(jsValue, result) case err: parser.NoSuccess ⇒ Future.failed(AuthenticationError(s"User info fails: can't parse groups list (${err.msg})")) } - } // Invalid group list case JsDefined(error) ⇒ - Future.failed(AuthenticationError(s"User info fails: invalid groups list received in user info ('${error}' of type ${error.getClass})")) + Future.failed(AuthenticationError(s"User info fails: invalid groups list received in user info ('$error' of type ${error.getClass})")) // Groups field is undefined case _: JsUndefined ⇒ - Future.failed(AuthenticationError(s"User info fails: groups attribute ${groupsAttrName} doesn't exist in user info")) + Future.failed(AuthenticationError(s"User info fails: groups attribute $groupsAttrName doesn't exist in user info")) } } diff --git a/thehive-cortex/app/connectors/cortex/CortexConnector.scala b/thehive-cortex/app/connectors/cortex/CortexConnector.scala index 11ea0e0856..7d25dd9749 100644 --- a/thehive-cortex/app/connectors/cortex/CortexConnector.scala +++ b/thehive-cortex/app/connectors/cortex/CortexConnector.scala @@ -1,13 +1,12 @@ package connectors.cortex -import play.api.libs.concurrent.AkkaGuiceSupport -import play.api.{Configuration, Environment, Logger} - import connectors.ConnectorModule import connectors.cortex.controllers.CortexCtrl import connectors.cortex.services.JobReplicateActor +import play.api.Logger +import play.api.libs.concurrent.AkkaGuiceSupport -class CortexConnector(environment: Environment, configuration: Configuration) extends ConnectorModule with AkkaGuiceSupport { +class CortexConnector extends ConnectorModule with AkkaGuiceSupport { private[CortexConnector] lazy val logger = Logger(getClass) override def configure() { diff --git a/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala b/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala index 64591954ed..c0c463a689 100644 --- a/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala +++ b/thehive-cortex/app/connectors/cortex/controllers/CortexCtrl.scala @@ -3,21 +3,18 @@ package connectors.cortex.controllers import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} - import play.api.{Configuration, Logger} import play.api.http.Status import play.api.libs.json.{JsObject, Json} import play.api.mvc._ -import play.api.routing.SimpleRouter +import play.api.routing.{Router, SimpleRouter} import play.api.routing.sird.{DELETE, GET, PATCH, POST, UrlContext} - import akka.actor.ActorSystem import connectors.Connector import connectors.cortex.models.JsonFormat.{analyzerFormat, responderFormat} import connectors.cortex.services.{CortexActionSrv, CortexAnalyzerSrv, CortexConfig} import javax.inject.{Inject, Singleton} import models.{HealthStatus, Roles} - import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser, Renderer} import org.elastic4play.models.JsonFormat.baseModelEntityWrites import org.elastic4play.services.JsonFormat.{aggReads, queryReads} @@ -114,7 +111,7 @@ class CortexCtrl( override def health: HealthStatus.Type = _health - val router = SimpleRouter { + val router: Router = SimpleRouter { case POST(p"/job") ⇒ createJob case GET(p"/job/$jobId<[^/]*>") ⇒ getJob(jobId) case POST(p"/job/_search") ⇒ findJob @@ -194,33 +191,33 @@ class CortexCtrl( } @Timed - def getAnalyzer(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAnalyzer(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.getAnalyzer(analyzerId).map { analyzer ⇒ renderer.toOutput(OK, analyzer) } } @Timed - def getAnalyzerFor(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAnalyzerFor(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.getAnalyzersFor(dataType).map { analyzers ⇒ renderer.toOutput(OK, analyzers) } } @Timed - def listAnalyzer: Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def listAnalyzer: Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexAnalyzerSrv.listAnalyzer.map { analyzers ⇒ renderer.toOutput(OK, analyzers) } } - def getResponder(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getResponder(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.getResponderById(responderId).map { responder ⇒ renderer.toOutput(OK, responder) } } - def getResponders(entityType: String, entityId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getResponders(entityType: String, entityId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.findResponderFor(entityType, entityId).map { responders ⇒ renderer.toOutput(OK, responders) } @@ -265,7 +262,7 @@ class CortexCtrl( renderer.toOutput(OK, actions, total) } - def getAction(actionId: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getAction(actionId: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ cortexActionSrv.getAction(actionId).map { action ⇒ renderer.toOutput(OK, action) } diff --git a/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala b/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala index 51ff1bf8d5..8c36a0e14d 100644 --- a/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala +++ b/thehive-cortex/app/connectors/cortex/controllers/ReportTemplateCtrl.scala @@ -1,29 +1,26 @@ package connectors.cortex.controllers -import javax.inject.{Inject, Singleton} - -import scala.collection.JavaConverters._ -import scala.concurrent.{ExecutionContext, Future} -import scala.io.Source -import scala.util.control.NonFatal - import akka.stream.Materializer import akka.stream.scaladsl.Sink +import connectors.cortex.services.ReportTemplateSrv +import javax.inject.{Inject, Singleton} +import models.Roles +import net.lingala.zip4j.ZipFile +import net.lingala.zip4j.model.FileHeader +import org.elastic4play.controllers._ +import org.elastic4play.models.JsonFormat.baseModelEntityWrites +import org.elastic4play.services.JsonFormat.queryReads +import org.elastic4play.services.{AuxSrv, QueryDSL, QueryDef} +import org.elastic4play.{BadRequestError, Timed} import play.api.Logger import play.api.http.Status import play.api.libs.json.{JsBoolean, JsFalse, JsObject, JsTrue} import play.api.mvc._ -import org.elastic4play.{BadRequestError, Timed} -import org.elastic4play.controllers._ -import org.elastic4play.models.JsonFormat.baseModelEntityWrites -import org.elastic4play.services.{QueryDSL, QueryDef} -import org.elastic4play.services.AuxSrv -import org.elastic4play.services.JsonFormat.queryReads -import connectors.cortex.services.ReportTemplateSrv -import models.Roles -import net.lingala.zip4j.core.ZipFile -import net.lingala.zip4j.model.FileHeader +import scala.collection.JavaConverters._ +import scala.concurrent.{ExecutionContext, Future} +import scala.io.Source +import scala.util.control.NonFatal @Singleton class ReportTemplateCtrl @Inject()( @@ -48,14 +45,14 @@ class ReportTemplateCtrl @Inject()( } @Timed - def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def get(id: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ reportTemplateSrv .get(id) .map(reportTemplate ⇒ renderer.toOutput(OK, reportTemplate)) } @Timed - def getContent(analyzerId: String, reportType: String): Action[AnyContent] = authenticated(Roles.read).async { implicit request ⇒ + def getContent(analyzerId: String, reportType: String): Action[AnyContent] = authenticated(Roles.read).async { _ ⇒ import org.elastic4play.services.QueryDSL._ val (reportTemplates, total) = reportTemplateSrv.find(and("analyzerId" ~= analyzerId, "reportType" ~= reportType), Some("0-1"), Nil) total.foreach { t ⇒ diff --git a/thehive-cortex/app/connectors/cortex/models/Artifact.scala b/thehive-cortex/app/connectors/cortex/models/Artifact.scala index 0118a355ed..18495d7e71 100644 --- a/thehive-cortex/app/connectors/cortex/models/Artifact.scala +++ b/thehive-cortex/app/connectors/cortex/models/Artifact.scala @@ -6,6 +6,6 @@ import akka.NotUsed import akka.stream.scaladsl.Source import akka.util.ByteString -sealed abstract class CortexArtifact(attributes: JsObject) -case class FileArtifact(data: Source[ByteString, NotUsed], attributes: JsObject) extends CortexArtifact(attributes) -case class DataArtifact(data: String, attributes: JsObject) extends CortexArtifact(attributes) +sealed abstract class CortexArtifact +case class FileArtifact(data: Source[ByteString, NotUsed], attributes: JsObject) extends CortexArtifact +case class DataArtifact(data: String, attributes: JsObject) extends CortexArtifact diff --git a/thehive-cortex/app/connectors/cortex/models/Job.scala b/thehive-cortex/app/connectors/cortex/models/Job.scala index 9e722df139..85ead46bf5 100644 --- a/thehive-cortex/app/connectors/cortex/models/Job.scala +++ b/thehive-cortex/app/connectors/cortex/models/Job.scala @@ -57,7 +57,7 @@ object Job { } class Job(model: JobModel, attributes: JsObject) extends EntityDef[JobModel, Job](model, Job.fixJobAttr(attributes)) with JobAttributes { - override def toJson = super.toJson + ("report" → report().fold[JsValue](JsObject.empty)(r ⇒ Json.parse(r))) // FIXME is parse fails (invalid report) + override def toJson: JsObject = super.toJson + ("report" → report().fold[JsValue](JsObject.empty)(r ⇒ Json.parse(r))) // FIXME is parse fails (invalid report) } case class CortexJob( diff --git a/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala b/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala index 8f7d068f7b..a15f1dae70 100644 --- a/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala +++ b/thehive-cortex/app/connectors/cortex/models/JsonFormat.scala @@ -43,24 +43,21 @@ object JsonFormat { ) private val fileArtifactFormat = OFormat(fileArtifactReads, fileArtifactWrites) private val dataArtifactFormat = Json.format[DataArtifact] - private val artifactReads = Reads[CortexArtifact]( + implicit val artifactReads: Reads[CortexArtifact] = Reads[CortexArtifact]( json ⇒ json.validate[JsObject].flatMap { case a if a.keys.contains("data") ⇒ json.validate[DataArtifact](dataArtifactFormat) case _ ⇒ json.validate[FileArtifact](fileArtifactFormat) } ) - private val artifactWrites = OWrites[CortexArtifact] { + implicit def artifactWrites[A <: CortexArtifact]: OWrites[A] = OWrites[A] { case dataArtifact: DataArtifact ⇒ dataArtifactFormat.writes(dataArtifact) case fileArtifact: FileArtifact ⇒ fileArtifactWrites.writes(fileArtifact) } - implicit val artifactFormat: OFormat[CortexArtifact] = OFormat(artifactReads, artifactWrites) +// implicit def artifactFormat[A <: CortexArtifact]: OFormat[A] = OFormat(artifactReads, artifactWrites) implicit val jobStatusFormat: Format[JobStatus.Type] = enumFormat(JobStatus) - private def filterObject(json: JsObject, attributes: String*): JsObject = - JsObject(attributes.flatMap(a ⇒ (json \ a).asOpt[JsValue].map(a → _))) - implicit val cortexJobReads: Reads[CortexJob] = Reads[CortexJob]( json ⇒ for { @@ -68,7 +65,11 @@ object JsonFormat { analyzerId ← (json \ "workerId").orElse(json \ "analyzerId").validate[String] analyzerName = (json \ "workerName").orElse(json \ "analyzerName").validate[String].getOrElse(analyzerId) analyzerDefinition = (json \ "workerDefinitionId").orElse(json \ "analyzerDefinitionId").validate[String].getOrElse(analyzerId) - attributes = filterObject(json.as[JsObject], "tlp", "message", "parameters") + attributes = JsObject( + (json \ "tlp").asOpt[JsValue].map("tlp" -> _).toList ::: + (json \ "message").asOpt[JsValue].map("message" -> _).toList ::: + (json \ "parameters").asOpt[JsValue].map("parameters" -> _).toList + ) artifact = (json \ "artifact") .validate[CortexArtifact] .getOrElse { diff --git a/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala b/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala index 6db3e1dce6..271e885ba2 100644 --- a/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala +++ b/thehive-cortex/app/connectors/cortex/models/ReportTemplate.scala @@ -28,7 +28,7 @@ trait ReportTemplateAttributes { _: AttributeDef ⇒ class ReportTemplateModel @Inject() extends ModelDef[ReportTemplateModel, ReportTemplate]("reportTemplate", "Report template", "/connector/cortex/reportTemplate") with ReportTemplateAttributes { - override def creationHook(parent: Option[BaseEntity], attrs: JsObject) = { + override def creationHook(parent: Option[BaseEntity], attrs: JsObject): Future[JsObject] = { val maybeId = for { analyzerId ← (attrs \ "analyzerId").asOpt[String] reportType ← (attrs \ "reportType").asOpt[String] diff --git a/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala b/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala index e5961c91c7..451ff4da12 100644 --- a/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala +++ b/thehive-cortex/app/connectors/cortex/services/ActionOperation.scala @@ -99,16 +99,16 @@ case class AssignCase(owner: String, status: ActionOperationStatus.Type = Action } object ActionOperation { - val addTagToCaseWrites = Json.writes[AddTagToCase] - val addTagToArtifactWrites = Json.writes[AddTagToArtifact] - val createTaskWrites = Json.writes[CreateTask] - val addCustomFieldsWrites = Json.writes[AddCustomFields] - val closeTaskWrites = Json.writes[CloseTask] - val markAlertAsReadWrites = Json.writes[MarkAlertAsRead] - val addLogToTaskWrites = Json.writes[AddLogToTask] - val addTagToAlertWrites = Json.writes[AddTagToAlert] - val addArtifactToCaseWrites = Json.writes[AddArtifactToCase] - val assignCaseWrites = Json.writes[AssignCase] + val addTagToCaseWrites: OWrites[AddTagToCase] = Json.writes[AddTagToCase] + val addTagToArtifactWrites: OWrites[AddTagToArtifact] = Json.writes[AddTagToArtifact] + val createTaskWrites: OWrites[CreateTask] = Json.writes[CreateTask] + val addCustomFieldsWrites: OWrites[AddCustomFields] = Json.writes[AddCustomFields] + val closeTaskWrites: OWrites[CloseTask] = Json.writes[CloseTask] + val markAlertAsReadWrites: OWrites[MarkAlertAsRead] = Json.writes[MarkAlertAsRead] + val addLogToTaskWrites: OWrites[AddLogToTask] = Json.writes[AddLogToTask] + val addTagToAlertWrites: OWrites[AddTagToAlert] = Json.writes[AddTagToAlert] + val addArtifactToCaseWrites: OWrites[AddArtifactToCase] = Json.writes[AddArtifactToCase] + val assignCaseWrites: OWrites[AssignCase] = Json.writes[AssignCase] implicit val actionOperationReads: Reads[ActionOperation] = Reads[ActionOperation]( json ⇒ (json \ "type").asOpt[String].fold[JsResult[ActionOperation]](JsError("type is missing in action operation")) { @@ -170,7 +170,7 @@ class ActionOperationSrv @Inject()( implicit val mat: Materializer ) { - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) lazy val alertSrv: AlertSrv = alertSrvProvider.get def findCaseEntity(entity: BaseEntity): Future[Case] = { @@ -294,7 +294,7 @@ class ActionOperationSrv @Inject()( case AddArtifactToCase(data, dataType, dataMessage, _, _) ⇒ for { initialCase ← findCaseEntity(entity) - artifact ← artifactSrv.create(initialCase.id, Fields.empty.set("data", data).set("dataType", dataType).set("message", dataMessage)) + _ ← artifactSrv.create(initialCase.id, Fields.empty.set("data", data).set("dataType", dataType).set("message", dataMessage)) } yield operation.updateStatus(ActionOperationStatus.Success, "") case AssignCase(owner, _, _) ⇒ for { diff --git a/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala b/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala index 18cb243c54..65c49cd9e1 100644 --- a/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala +++ b/thehive-cortex/app/connectors/cortex/services/CortexAnalyzerSrv.scala @@ -263,7 +263,6 @@ class CortexAnalyzerSrv @Inject()( cortex .getAttachment(id) .flatMap(src ⇒ src.runWith(FileIO.toPath(file))) - .flatMap(ioResult ⇒ Future.fromTry(ioResult.status)) .flatMap(_ ⇒ attachmentSrv.save(fiv)) .andThen { case _ ⇒ Files.delete(file) } .map(a ⇒ Some(artifact + ("attachment" → Json.toJson(a)))) diff --git a/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala b/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala index 5748388ddb..766451ece6 100644 --- a/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala +++ b/thehive-cortex/app/connectors/cortex/services/ReportTemplateSrv.scala @@ -1,27 +1,22 @@ package connectors.cortex.services -import javax.inject.{Inject, Singleton} - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.Try - import akka.NotUsed import akka.stream.scaladsl.Source +import connectors.cortex.models.{ReportTemplate, ReportTemplateModel} +import javax.inject.{Inject, Singleton} +import org.elastic4play.controllers.Fields +import org.elastic4play.database.ModifyConfig +import org.elastic4play.services._ import play.api.Logger import play.api.libs.json.JsObject -import org.elastic4play.controllers.Fields -import org.elastic4play.services.{Agg, AuthContext, CreateSrv, DeleteSrv, FindSrv, GetSrv, QueryDef, UpdateSrv} -import connectors.cortex.models.{ReportTemplate, ReportTemplateModel} -import services.ArtifactSrv - -import org.elastic4play.database.ModifyConfig +import scala.concurrent.{ExecutionContext, Future} +import scala.util.Try @Singleton class ReportTemplateSrv @Inject()( reportTemplateModel: ReportTemplateModel, createSrv: CreateSrv, - artifactSrv: ArtifactSrv, getSrv: GetSrv, updateSrv: UpdateSrv, deleteSrv: DeleteSrv, @@ -56,7 +51,4 @@ class ReportTemplateSrv @Inject()( findSrv[ReportTemplateModel, ReportTemplate](reportTemplateModel, queryDef, range, sortBy) def stats(queryDef: QueryDef, aggs: Seq[Agg]): Future[JsObject] = findSrv(reportTemplateModel, queryDef, aggs: _*) - - def getStats(id: String): Future[JsObject] = - Future.successful(JsObject.empty) } diff --git a/thehive-misp/app/connectors/misp/MispConnector.scala b/thehive-misp/app/connectors/misp/MispConnector.scala index a3615b8390..466ed199dd 100644 --- a/thehive-misp/app/connectors/misp/MispConnector.scala +++ b/thehive-misp/app/connectors/misp/MispConnector.scala @@ -1,14 +1,12 @@ package connectors.misp +import connectors.ConnectorModule import javax.inject.Singleton - +import play.api.Logger import play.api.libs.concurrent.AkkaGuiceSupport -import play.api.{Configuration, Environment, Logger} - -import connectors.ConnectorModule @Singleton -class MispConnector(environment: Environment, configuration: Configuration) extends ConnectorModule with AkkaGuiceSupport { +class MispConnector extends ConnectorModule with AkkaGuiceSupport { private[MispConnector] lazy val logger = Logger(getClass) override def configure() { diff --git a/thehive-misp/app/connectors/misp/MispCtrl.scala b/thehive-misp/app/connectors/misp/MispCtrl.scala index 6e5b2456ee..38ebc65c6c 100644 --- a/thehive-misp/app/connectors/misp/MispCtrl.scala +++ b/thehive-misp/app/connectors/misp/MispCtrl.scala @@ -3,20 +3,17 @@ package connectors.misp import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} - import play.api.http.Status import play.api.libs.json.{JsObject, Json} import play.api.mvc._ -import play.api.routing.SimpleRouter +import play.api.routing.{Router, SimpleRouter} import play.api.routing.sird.{GET, POST, UrlContext} import play.api.{Configuration, Logger} - import akka.actor.ActorSystem import connectors.Connector import javax.inject.{Inject, Singleton} import models.{HealthStatus, _} import services.{AlertTransformer, CaseSrv} - import org.elastic4play.JsonFormat.tryWrites import org.elastic4play.controllers.{Authenticated, Renderer} import org.elastic4play.models.JsonFormat.baseModelEntityWrites @@ -116,7 +113,7 @@ class MispCtrl( private[MispCtrl] lazy val logger = Logger(getClass) - val router = SimpleRouter { + val router: Router = SimpleRouter { case GET(p"/_syncAlerts") ⇒ syncAlerts case GET(p"/_syncAllAlerts") ⇒ syncAllAlerts case GET(p"/_syncArtifacts") ⇒ syncArtifacts diff --git a/thehive-misp/app/connectors/misp/MispExport.scala b/thehive-misp/app/connectors/misp/MispExport.scala index db7abf38a1..6ad0c766be 100644 --- a/thehive-misp/app/connectors/misp/MispExport.scala +++ b/thehive-misp/app/connectors/misp/MispExport.scala @@ -3,31 +3,27 @@ package connectors.misp import java.text.SimpleDateFormat import java.util.Date -import javax.inject.{Inject, Provider, Singleton} -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Success, Try} - -import play.api.Logger -import play.api.libs.json._ - +import akka.stream.Materializer import akka.stream.scaladsl.Sink -import connectors.misp.JsonFormat.tlpWrites +import connectors.misp.JsonFormat.{exportedAttributeWrites, tlpWrites} +import javax.inject.{Inject, Provider, Singleton} import models.{Artifact, Case} -import services.{AlertSrv, ArtifactSrv} -import JsonFormat.exportedAttributeWrites -import akka.stream.Materializer - -import org.elastic4play.{BadRequestError, InternalError} import org.elastic4play.controllers.Fields -import org.elastic4play.services.{Attachment, AttachmentSrv, AuthContext} import org.elastic4play.services.JsonFormat.attachmentFormat +import org.elastic4play.services.{Attachment, AttachmentSrv, AuthContext} import org.elastic4play.utils.RichFuture +import org.elastic4play.{BadRequestError, InternalError} +import play.api.Logger +import play.api.libs.json._ +import services.AlertSrv + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Success, Try} @Singleton class MispExport @Inject()( mispConfig: MispConfig, mispSrv: MispSrv, - artifactSrv: ArtifactSrv, alertSrvProvider: Provider[AlertSrv], attachmentSrv: AttachmentSrv, implicit val ec: ExecutionContext, @@ -36,7 +32,7 @@ class MispExport @Inject()( lazy val dateFormat = new SimpleDateFormat("yy-MM-dd") private[misp] lazy val alertSrv = alertSrvProvider.get - lazy val logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) def relatedMispEvent(mispName: String, caseId: String): Future[(Option[String], Option[String])] = { import org.elastic4play.services.QueryDSL._ diff --git a/thehive-misp/app/connectors/misp/MispSrv.scala b/thehive-misp/app/connectors/misp/MispSrv.scala index d2254fea26..4f176debff 100644 --- a/thehive-misp/app/connectors/misp/MispSrv.scala +++ b/thehive-misp/app/connectors/misp/MispSrv.scala @@ -2,28 +2,26 @@ package connectors.misp import java.util.Date -import javax.inject.{Inject, Provider, Singleton} - -import scala.concurrent.{ExecutionContext, Future} -import play.api.Logger -import play.api.libs.json.JsLookupResult.jsLookupResultToJsLookup -import play.api.libs.json.JsValue.jsValueToJsLookup -import play.api.libs.json.Json.toJsFieldJsValueWrapper -import play.api.libs.json._ -import play.api.libs.ws.WSBodyWritables.writeableOf_JsValue import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.{FileIO, Sink, Source} import connectors.misp.JsonFormat._ +import javax.inject.{Inject, Provider, Singleton} import models._ -import net.lingala.zip4j.core.ZipFile +import net.lingala.zip4j.ZipFile import net.lingala.zip4j.exception.ZipException -import net.lingala.zip4j.model.FileHeader -import services._ import org.elastic4play.controllers.{Fields, FileInputValue} import org.elastic4play.services.{Attachment, AuthContext, TempSrv} import org.elastic4play.{InternalError, NotFoundError} +import play.api.Logger +import play.api.libs.json.JsLookupResult.jsLookupResultToJsLookup +import play.api.libs.json.JsValue.jsValueToJsLookup +import play.api.libs.json.Json.toJsFieldJsValueWrapper +import play.api.libs.json._ +import play.api.libs.ws.WSBodyWritables.writeableOf_JsValue +import services._ +import scala.concurrent.{ExecutionContext, Future} import scala.util.Try @Singleton @@ -64,7 +62,7 @@ class MispSrv @Inject()( logger.debug(s"Get MISP events from $fromDate") val date = fromDate.getTime / 1000 Source - .fromFuture { + .future { mispConnection("events/index") .post(Json.obj("searchpublish_timestamp" → date)) } @@ -208,7 +206,7 @@ class MispSrv @Inject()( case Some(id) ⇒ caseSrv.get(id) case None ⇒ for { - caseTemplate ← alertSrv.getCaseTemplate(alert, customCaseTemplate) + caseTemplate ← alertSrv.getCaseTemplate(customCaseTemplate) caze ← caseSrv.create(Fields(alert.toCaseJson), caseTemplate) _ ← importArtifacts(alert, caze) } yield caze @@ -273,10 +271,10 @@ class MispSrv @Inject()( val zipFile = new ZipFile(file.filepath.toFile) if (zipFile.isEncrypted) - zipFile.setPassword("infected") + zipFile.setPassword("infected".toCharArray) // Get the list of file headers from the zip file - val fileHeaders = zipFile.getFileHeaders.asScala.toList.asInstanceOf[List[FileHeader]] + val fileHeaders = zipFile.getFileHeaders.asScala.toList val (fileNameHeaders, contentFileHeaders) = fileHeaders.partition { fileHeader ⇒ fileHeader.getFileName.endsWith(".filename.txt") } @@ -300,7 +298,7 @@ class MispSrv @Inject()( tempFile = tempSrv.newTemporaryFile("misp", "malware") _ = logger.info(s"Extract malware file ${file.filepath} in file $tempFile") - _ = zipFile.extractFile(contentFileHeader, tempFile.getParent.toString, null, tempFile.getFileName.toString) + _ = zipFile.extractFile(contentFileHeader, tempFile.getParent.toString, tempFile.getFileName.toString) } yield FileInputValue(filename, tempFile, "application/octet-stream")).getOrElse(file) } catch { case e: ZipException ⇒ @@ -325,9 +323,7 @@ class MispSrv @Inject()( response .bodyAsSource .runWith(FileIO.toPath(tempFile)) - .map { ioResult ⇒ - if (!ioResult.wasSuccessful) // throw an exception if transfer failed - throw ioResult.getError + .map { _ ⇒ val contentType = response.headers.getOrElse("Content-Type", Seq("application/octet-stream")).head val filename = response .headers diff --git a/thehive-misp/app/connectors/misp/MispSynchro.scala b/thehive-misp/app/connectors/misp/MispSynchro.scala index 95cda14517..1b44a84d2f 100644 --- a/thehive-misp/app/connectors/misp/MispSynchro.scala +++ b/thehive-misp/app/connectors/misp/MispSynchro.scala @@ -43,7 +43,7 @@ class MispSynchro @Inject()( private[misp] lazy val alertSrv = alertSrvProvider.get private[misp] def initScheduler(): Unit = { - val task = system.scheduler.schedule(0.seconds, mispConfig.interval) { + val task = system.scheduler.scheduleWithFixedDelay(0.seconds, mispConfig.interval) {() => if (migrationSrv.isReady) { logger.info("Update of MISP events is starting ...") userSrv From c74d753f92c983186183cc41d6df0982dde6b930 Mon Sep 17 00:00:00 2001 From: To-om Date: Thu, 9 Jul 2020 08:35:15 +0200 Subject: [PATCH 05/14] #1424 Enforce MISP threat level range --- thehive-misp/app/connectors/misp/MispExport.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thehive-misp/app/connectors/misp/MispExport.scala b/thehive-misp/app/connectors/misp/MispExport.scala index 6ad0c766be..4e7bde9e08 100644 --- a/thehive-misp/app/connectors/misp/MispExport.scala +++ b/thehive-misp/app/connectors/misp/MispExport.scala @@ -32,7 +32,7 @@ class MispExport @Inject()( lazy val dateFormat = new SimpleDateFormat("yy-MM-dd") private[misp] lazy val alertSrv = alertSrvProvider.get - lazy val logger: Logger = Logger(getClass) + lazy val logger: Logger = Logger(getClass) def relatedMispEvent(mispName: String, caseId: String): Future[(Option[String], Option[String])] = { import org.elastic4play.services.QueryDSL._ @@ -74,7 +74,7 @@ class MispExport @Inject()( val mispEvent = Json.obj( "Event" → Json.obj( "distribution" → 0, - "threat_level_id" → (4 - severity), + "threat_level_id" → math.min(4, math.max(1, 4 - severity)), "analysis" → 0, "info" → title, "date" → dateFormat.format(date), From a320572bd542f546e552b044babdcf5c2fd7553c Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 11 Aug 2020 16:04:37 +0200 Subject: [PATCH 06/14] #1398 Use a dedicated threadpool for MISP synchronization --- thehive-misp/app/connectors/misp/MispSynchro.scala | 8 +++++++- thehive-misp/conf/reference.conf | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/thehive-misp/app/connectors/misp/MispSynchro.scala b/thehive-misp/app/connectors/misp/MispSynchro.scala index 1b44a84d2f..f7150a6af2 100644 --- a/thehive-misp/app/connectors/misp/MispSynchro.scala +++ b/thehive-misp/app/connectors/misp/MispSynchro.scala @@ -35,12 +35,18 @@ class MispSynchro @Inject()( tempSrv: TempSrv, lifecycle: ApplicationLifecycle, system: ActorSystem, - implicit val ec: ExecutionContext, implicit val mat: Materializer ) { private[misp] lazy val logger = Logger(getClass) private[misp] lazy val alertSrv = alertSrvProvider.get + implicit val ec: ExecutionContext = try { + system.dispatchers.lookup("misp-thread-pools") + } catch { + case e: Throwable => + logger.warn(s"Unable to use MISP specific dispatcher ($e). Fallback to default dispatcher") + system.dispatcher + } private[misp] def initScheduler(): Unit = { val task = system.scheduler.scheduleWithFixedDelay(0.seconds, mispConfig.interval) {() => diff --git a/thehive-misp/conf/reference.conf b/thehive-misp/conf/reference.conf index 790845e182..f254618bf2 100644 --- a/thehive-misp/conf/reference.conf +++ b/thehive-misp/conf/reference.conf @@ -25,3 +25,14 @@ misp { # Interval between two MISP event import interval = 1h } + +misp-thread-pool { + fork-join-executor { + # Min number of threads available for MISP synchronization + parallelism-min = 2 + # Parallelism (threads) ... ceil(available processors * factor) + parallelism-factor = 2.0 + # Max number of threads available for MISP synchronization + parallelism-max = 4 + } +} \ No newline at end of file From bde63bb5bda999de16b9f9fa1f349be4e9e2a8c7 Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 11 Aug 2020 18:04:59 +0200 Subject: [PATCH 07/14] wip --- project/Dependencies.scala | 2 +- thehive-backend/app/services/OAuth2Srv.scala | 351 ++++++++++++++----- 2 files changed, 258 insertions(+), 95 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5c3a9d1e06..2e113c0cbd 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -19,7 +19,7 @@ object Dependencies { val reflections = "org.reflections" % "reflections" % "0.9.11" val zip4j = "net.lingala.zip4j" % "zip4j" % "2.6.0" - val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.0-SNAPSHOT" + val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.0" val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % play.core.PlayVersion.akkaVersion val akkaClusterTyped = "com.typesafe.akka" %% "akka-cluster-typed" % play.core.PlayVersion.akkaVersion val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % play.core.PlayVersion.akkaVersion diff --git a/thehive-backend/app/services/OAuth2Srv.scala b/thehive-backend/app/services/OAuth2Srv.scala index 936a6ad678..8aae6226bd 100644 --- a/thehive-backend/app/services/OAuth2Srv.scala +++ b/thehive-backend/app/services/OAuth2Srv.scala @@ -1,18 +1,24 @@ package services -import javax.inject.{Inject, Singleton} +import java.util.UUID +import javax.inject.{ Inject, Singleton } import akka.stream.Materializer -import org.elastic4play.services.{AuthContext, AuthSrv} -import org.elastic4play.{AuthenticationError, AuthorizationError, OAuth2Redirect} + +import org.elastic4play.services.{ AuthContext, AuthSrv } +import org.elastic4play.{ AuthenticationError, AuthorizationError, BadRequestError, CreateError, NotFoundError, OAuth2Redirect } import play.api.http.Status -import play.api.libs.json.{JsObject, JsValue} +import play.api.libs.json.{ JsObject, JsValue } import play.api.libs.ws.WSClient -import play.api.mvc.RequestHeader -import play.api.{Configuration, Logger} +import play.api.mvc.{ Request, RequestHeader, Result, Results } +import play.api.{ Configuration, Logger } + import services.mappers.UserMapper +import scala.concurrent.{ ExecutionContext, Future } +import scala.util.{ Failure, Success, Try } + +import org.elastic4play.controllers.AuthenticatedRequest -import scala.concurrent.{ExecutionContext, Future} case class OAuth2Config( clientId: String, @@ -24,8 +30,10 @@ case class OAuth2Config( tokenUrl: String, userUrl: String, scope: String, - autocreate: Boolean, - autoupdate: Boolean + authorizationHeader: String, + userIdField: String, + autoupdate: Boolean, +autocreate: Boolean ) object OAuth2Config { @@ -41,6 +49,7 @@ object OAuth2Config { userUrl ← configuration.getOptional[String]("auth.oauth2.userUrl") tokenUrl ← configuration.getOptional[String]("auth.oauth2.tokenUrl") scope ← configuration.getOptional[String]("auth.oauth2.scope") + authorizationHeader = configuration.getOptional[String]("auth.oauth2.authorizationHeader").getOrElse("Bearer") autocreate = configuration.getOptional[Boolean]("auth.sso.autocreate").getOrElse(false) autoupdate = configuration.getOptional[Boolean]("auth.sso.autoupdate").getOrElse(false) } yield OAuth2Config( @@ -53,6 +62,7 @@ object OAuth2Config { tokenUrl, userUrl, scope, + authorizationHeader, autocreate, autoupdate ) @@ -80,103 +90,256 @@ class OAuth2Srv( oauth2Config.fold[Future[A]](Future.failed(AuthenticationError("OAuth2 not configured properly")))(body) override def authenticate()(implicit request: RequestHeader): Future[AuthContext] = - withOAuth2Config { cfg ⇒ + withOAuth2Config { oauth2Config ⇒ + val ssoResponse = oauth2Config.grantType match { + case "authorization_code" => + if (!isSecuredAuthCode(request)) { + logger.debug("Code or state is not provided, redirect to authorizationUrl") + Future.successful(authRedirect(oauth2Config)) + } else { + for { + token <- getToken(oauth2Config, request) + userData <- getUserData(oauth2Config, token) + authReq <- Future + .fromTry(authenticate(request, userData)) + .recoverWith { + case _: NotFoundError => Future.fromTry(createUser(request, userData)) + } + .recoverWith { + case _: CreateError => Future.failed(NotFoundError("User not found")) + } + } yield sessionAuthSrv.setSessionUser(authReq.authContext)(Results.Found(httpContext)) + } + + case x => Future.failed(BadConfigurationError(s"OAuth GrantType $x not supported yet")) + } + ssoResponse.recoverWith { + case error => Future.successful(Results.Redirect(httpContext, Map("error" -> Seq(error.getMessage)))) + } + + + + + request .queryString .get(Oauth2TokenQueryString) .flatMap(_.headOption) - .fold(createOauth2Redirect(cfg.clientId)) { code ⇒ - getAuthTokenAndAuthenticate(cfg.clientId, code) + .fold(createOauth2Redirect(oauth2Config.clientId)) { code ⇒ + getAuthTokenAndAuthenticate(oauth2Config.clientId, code) } } - private def getAuthTokenAndAuthenticate(clientId: String, code: String)(implicit request: RequestHeader): Future[AuthContext] = { - logger.debug("Getting user token with the code from the response") - withOAuth2Config { cfg ⇒ - ws.url(cfg.tokenUrl) - .post( - Map( - "code" → code, - "grant_type" → cfg.grantType, - "client_secret" → cfg.clientSecret, - "redirect_uri" → cfg.redirectUri, - "client_id" → clientId - ) - ) - .recoverWith { - case error ⇒ - logger.error(s"Token verification failure", error) - Future.failed(AuthenticationError("Token verification failure")) - } - .flatMap { r ⇒ - r.status match { - case Status.OK ⇒ - logger.debug("Getting user info using access token") - val accessToken = (r.json \ "access_token").asOpt[String].getOrElse("") - val authHeader = "Authorization" → s"Bearer $accessToken" - ws.url(cfg.userUrl) - .addHttpHeaders(authHeader) - .get() - .flatMap { userResponse ⇒ - if (userResponse.status != Status.OK) { - Future.failed(AuthenticationError(s"Unexpected response from server: ${userResponse.status} ${userResponse.body}")) - } else { - val response = userResponse.json.asInstanceOf[JsObject] - getOrCreateUser(response, authHeader) - } - } - case _ ⇒ - logger.error(s"Unexpected response from server: ${r.status} ${r.body}") - Future.failed(AuthenticationError("Unexpected response from server")) - } - } - } + private def isSecuredAuthCode(request: RequestHeader): Boolean = + request.queryString.contains("code") && request.queryString.contains("state") + + /** + * Filter checking whether we initiate the OAuth2 process + * and redirecting to OAuth2 server if necessary + * @return + */ + private def authRedirect(oauth2Config: OAuth2Config): Result = { + val state = UUID.randomUUID().toString + val queryStringParams = Map[String, Seq[String]]( + "scope" -> Seq(oauth2Config.scope.mkString(" ")), + "response_type" -> Seq("code"), + "redirect_uri" -> Seq(oauth2Config.redirectUri), + "client_id" -> Seq(oauth2Config.clientId), + "state" -> Seq(state) + ) + + logger.debug(s"Redirecting to ${oauth2Config.redirectUri} with $queryStringParams and state $state") + Results + .Redirect(oauth2Config.authorizationUrl, queryStringParams, status = 302) + .withSession("state" -> state) } - private def getOrCreateUser(response: JsValue, authHeader: (String, String))(implicit request: RequestHeader): Future[AuthContext] = - withOAuth2Config { cfg ⇒ - ssoMapper.getUserFields(response, Some(authHeader)).flatMap { userFields ⇒ - val userId = userFields.getString("login").getOrElse("") - userSrv - .get(userId) - .flatMap(user ⇒ { - if (cfg.autoupdate) { - logger.debug(s"Updating OAuth/OIDC user") - userSrv.inInitAuthContext { implicit authContext ⇒ - // Only update name and roles, not login (can't change it) - userSrv - .update(user, userFields.unset("login")) - .flatMap(user ⇒ { - userSrv.getFromUser(request, user, name) - }) - } - } else { - userSrv.getFromUser(request, user, name) + /** + * Enriching the initial request with OAuth2 token gotten + * from OAuth2 code + * @return + */ + private def getToken[A](oauth2Config: OAuth2Config, request: RequestHeader): Future[String] = { + val token = + for { + state <- request.session.get("state") + stateQs <- request.queryString.get("state").flatMap(_.headOption) + if state == stateQs + } yield request.queryString.get("code").flatMap(_.headOption) match { + case Some(code) => + logger.debug(s"Attempting to retrieve OAuth2 token from ${oauth2Config.tokenUrl} with code $code") + getAuthTokenFromCode(oauth2Config, code, state) + .map { t => + logger.trace(s"Got token $t") + t } - }) - .recoverWith { - case authErr: AuthorizationError ⇒ Future.failed(authErr) - case _ if cfg.autocreate ⇒ - logger.debug(s"Creating OAuth/OIDC user") - userSrv.inInitAuthContext { implicit authContext ⇒ - userSrv - .create(userFields) - .flatMap(user ⇒ { - userSrv.getFromUser(request, user, name) - }) - } - } + case None => + Future.failed(AuthenticationError(s"OAuth2 server code missing ${request.queryString.get("error")}")) } - } + token.getOrElse(Future.failed(BadRequestError("OAuth2 states mismatch"))) + } + - private def createOauth2Redirect(clientId: String): Future[AuthContext] = - withOAuth2Config { cfg ⇒ - val queryStringParams = Map[String, Seq[String]]( - "scope" → Seq(cfg.scope), - "response_type" → Seq(cfg.responseType), - "redirect_uri" → Seq(cfg.redirectUri), - "client_id" → Seq(clientId) + /** + * Querying the OAuth2 server for a token + * @param code the previously obtained code + * @return + */ + private def getAuthTokenFromCode(oauth2Config: OAuth2Config, code: String, state: String): Future[String] = { + logger.trace(s""" + |Request to ${oauth2Config.tokenUrl} with + | code: $code + | grant_type: ${oauth2Config.grantType} + | client_secret: ${oauth2Config.clientSecret} + | redirect_uri: ${oauth2Config.redirectUri} + | client_id: ${oauth2Config.clientId} + | state: $state + |""".stripMargin) + ws + .url(oauth2Config.tokenUrl) + .withHttpHeaders("Accept" -> "application/json") + .post( + Map( + "code" -> code, + "grant_type" -> oauth2Config.grantType.toString, + "client_secret" -> oauth2Config.clientSecret, + "redirect_uri" -> oauth2Config.redirectUri, + "client_id" -> oauth2Config.clientId, + "state" -> state + ) ) - Future.failed(OAuth2Redirect(cfg.authorizationUrl, queryStringParams)) + .transform { + case Success(r) if r.status == 200 => Success((r.json \ "access_token").asOpt[String].getOrElse("")) + case Failure(error) => Failure(AuthenticationError(s"OAuth2 token verification failure ${error.getMessage}")) + case Success(r) => Failure(AuthenticationError(s"OAuth2/token unexpected response from server (${r.status} ${r.statusText})")) + } + } + + /** + * Client query for user data with OAuth2 token + * @param token the token + * @return + */ + private def getUserData(oauth2Config: OAuth2Config, token: String): Future[JsObject] = { + logger.trace(s"Request to ${oauth2Config.userUrl} with authorization header: ${oauth2Config.authorizationHeader} $token") + ws + .url(oauth2Config.userUrl) + .addHttpHeaders("Authorization" -> s"${oauth2Config.authorizationHeader} $token") + .get() + .transform { + case Success(r) if r.status == 200 => Success(r.json.as[JsObject]) + case Failure(error) => Failure(AuthenticationError(s"OAuth2 user data fetch failure ${error.getMessage}")) + case Success(r) => Failure(AuthenticationError(s"OAuth2/userinfo unexpected response from server (${r.status} ${r.statusText})")) + } + } + + + + private def authenticate[A](oauth2Config: OAuth2Config, request: Request[A], userData: JsObject): Future[AuthenticatedRequest[A]] = + for { + userId <- getUserId(oauth2Config, userData) + user <- userSrv.get(userId) + authContext <- userSrv.getFromUser(request, user, "oauth2") + } yield new AuthenticatedRequest[A](authContext, request) + + private def getUserId(oauth2Config: OAuth2Config, jsonUser: JsObject): Try[String] = + (jsonUser \ oauth2Config.userIdField).asOpt[String] match { + case Some(userId) => Success(userId) + case None => Failure(BadRequestError(s"OAuth2 user data doesn't contain user ID field (${oauth2Config.userIdField})")) } + + +// +// +// +// +// private def getAuthTokenAndAuthenticate(clientId: String, code: String)(implicit request: RequestHeader): Future[AuthContext] = { +// logger.debug("Getting user token with the code from the response") +// withOAuth2Config { cfg ⇒ +// ws.url(cfg.tokenUrl) +// .post( +// Map( +// "code" → code, +// "grant_type" → cfg.grantType, +// "client_secret" → cfg.clientSecret, +// "redirect_uri" → cfg.redirectUri, +// "client_id" → clientId +// ) +// ) +// .recoverWith { +// case error ⇒ +// logger.error(s"Token verification failure", error) +// Future.failed(AuthenticationError("Token verification failure")) +// } +// .flatMap { r ⇒ +// r.status match { +// case Status.OK ⇒ +// logger.debug("Getting user info using access token") +// val accessToken = (r.json \ "access_token").asOpt[String].getOrElse("") +// val authHeader = "Authorization" → s"Bearer $accessToken" +// ws.url(cfg.userUrl) +// .addHttpHeaders(authHeader) +// .get() +// .flatMap { userResponse ⇒ +// if (userResponse.status != Status.OK) { +// Future.failed(AuthenticationError(s"Unexpected response from server: ${userResponse.status} ${userResponse.body}")) +// } else { +// val response = userResponse.json.asInstanceOf[JsObject] +// getOrCreateUser(response, authHeader) +// } +// } +// case _ ⇒ +// logger.error(s"Unexpected response from server: ${r.status} ${r.body}") +// Future.failed(AuthenticationError("Unexpected response from server")) +// } +// } +// } +// } +// +// private def getOrCreateUser(response: JsValue, authHeader: (String, String))(implicit request: RequestHeader): Future[AuthContext] = +// withOAuth2Config { cfg ⇒ +// ssoMapper.getUserFields(response, Some(authHeader)).flatMap { userFields ⇒ +// val userId = userFields.getString("login").getOrElse("") +// userSrv +// .get(userId) +// .flatMap(user ⇒ { +// if (cfg.autoupdate) { +// logger.debug(s"Updating OAuth/OIDC user") +// userSrv.inInitAuthContext { implicit authContext ⇒ +// // Only update name and roles, not login (can't change it) +// userSrv +// .update(user, userFields.unset("login")) +// .flatMap(user ⇒ { +// userSrv.getFromUser(request, user, name) +// }) +// } +// } else { +// userSrv.getFromUser(request, user, name) +// } +// }) +// .recoverWith { +// case authErr: AuthorizationError ⇒ Future.failed(authErr) +// case _ if cfg.autocreate ⇒ +// logger.debug(s"Creating OAuth/OIDC user") +// userSrv.inInitAuthContext { implicit authContext ⇒ +// userSrv +// .create(userFields) +// .flatMap(user ⇒ { +// userSrv.getFromUser(request, user, name) +// }) +// } +// } +// } +// } +// +// private def createOauth2Redirect(clientId: String): Future[AuthContext] = +// withOAuth2Config { cfg ⇒ +// val queryStringParams = Map[String, Seq[String]]( +// "scope" → Seq(cfg.scope), +// "response_type" → Seq(cfg.responseType), +// "redirect_uri" → Seq(cfg.redirectUri), +// "client_id" → Seq(clientId) +// ) +// Future.failed(OAuth2Redirect(cfg.authorizationUrl, queryStringParams)) +// } } From eb184c80c9da5aa76ede6e120fa08b5085685a93 Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 11:45:35 +0200 Subject: [PATCH 08/14] #1342 #1291 #946 OAuth2 refactoring --- project/Dependencies.scala | 24 +- .../app/controllers/AuthenticationCtrl.scala | 29 +- thehive-backend/app/services/OAuth2Srv.scala | 279 +++++------------- .../services/mappers/GroupUserMapper.scala | 35 +-- .../services/mappers/SimpleUserMapper.scala | 5 +- thehive-backend/conf/routes | 1 + ui/app/views/login.html | 8 +- 7 files changed, 134 insertions(+), 247 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 2e113c0cbd..6e341910f8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -6,22 +6,22 @@ object Dependencies { object Library { object Play { - val ws = "com.typesafe.play" %% "play-ws" % play.core.PlayVersion.current - val ahc = "com.typesafe.play" %% "play-ahc-ws" % play.core.PlayVersion.current - val cache = "com.typesafe.play" %% "play-ehcache" % play.core.PlayVersion.current - val test = "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current - val specs2 = "com.typesafe.play" %% "play-specs2" % play.core.PlayVersion.current + val ws = "com.typesafe.play" %% "play-ws" % play.core.PlayVersion.current + val ahc = "com.typesafe.play" %% "play-ahc-ws" % play.core.PlayVersion.current + val cache = "com.typesafe.play" %% "play-ehcache" % play.core.PlayVersion.current + val test = "com.typesafe.play" %% "play-test" % play.core.PlayVersion.current + val specs2 = "com.typesafe.play" %% "play-specs2" % play.core.PlayVersion.current val filters = "com.typesafe.play" %% "filters-helpers" % play.core.PlayVersion.current - val guice = "com.typesafe.play" %% "play-guice" % play.core.PlayVersion.current + val guice = "com.typesafe.play" %% "play-guice" % play.core.PlayVersion.current } val scalaGuice = "net.codingwell" %% "scala-guice" % "4.2.6" - val reflections = "org.reflections" % "reflections" % "0.9.11" - val zip4j = "net.lingala.zip4j" % "zip4j" % "2.6.0" - val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.0" - val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % play.core.PlayVersion.akkaVersion - val akkaClusterTyped = "com.typesafe.akka" %% "akka-cluster-typed" % play.core.PlayVersion.akkaVersion - val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % play.core.PlayVersion.akkaVersion + val reflections = "org.reflections" % "reflections" % "0.9.11" + val zip4j = "net.lingala.zip4j" % "zip4j" % "2.6.0" + val elastic4play = "org.thehive-project" %% "elastic4play" % "1.12.1" + val akkaCluster = "com.typesafe.akka" %% "akka-cluster" % play.core.PlayVersion.akkaVersion + val akkaClusterTyped = "com.typesafe.akka" %% "akka-cluster-typed" % play.core.PlayVersion.akkaVersion + val akkaClusterTools = "com.typesafe.akka" %% "akka-cluster-tools" % play.core.PlayVersion.akkaVersion } } diff --git a/thehive-backend/app/controllers/AuthenticationCtrl.scala b/thehive-backend/app/controllers/AuthenticationCtrl.scala index e9579cee5c..42a127388b 100644 --- a/thehive-backend/app/controllers/AuthenticationCtrl.scala +++ b/thehive-backend/app/controllers/AuthenticationCtrl.scala @@ -5,7 +5,8 @@ import models.UserStatus import org.elastic4play.controllers.{Authenticated, Fields, FieldsBodyParser} import org.elastic4play.database.DBIndex import org.elastic4play.services.AuthSrv -import org.elastic4play.{AuthorizationError, OAuth2Redirect, Timed} +import org.elastic4play.{AuthorizationError, Timed} +import play.api.Configuration import play.api.mvc._ import services.UserSrv @@ -13,6 +14,7 @@ import scala.concurrent.{ExecutionContext, Future} @Singleton class AuthenticationCtrl @Inject()( + configuration: Configuration, authSrv: AuthSrv, userSrv: UserSrv, authenticated: Authenticated, @@ -44,19 +46,18 @@ class AuthenticationCtrl @Inject()( dbIndex.getIndexStatus.flatMap { case false ⇒ Future.successful(Results.Status(520)) case _ ⇒ - (for { - authContext ← authSrv.authenticate() - user ← userSrv.get(authContext.userId) - } yield { - if (user.status() == UserStatus.Ok) - authenticated.setSessingUser(Ok, authContext) - else - throw AuthorizationError("Your account is locked") - }) recover { - // A bit of a hack with the status code, so that Angular doesn't reject the origin - case OAuth2Redirect(redirectUrl, qp) ⇒ Redirect(redirectUrl, qp, status = OK) - case e ⇒ throw e - } + authSrv + .authenticate() + .flatMap { + case Right(authContext) ⇒ + userSrv.get(authContext.userId).map { user ⇒ + if (user.status() == UserStatus.Ok) + authenticated.setSessingUser(Redirect(configuration.get[String]("play.http.context").stripSuffix("/") + "/index.html"), authContext) + else + throw AuthorizationError("Your account is locked") + } + case Left(result) ⇒ Future.successful(result) + } } } diff --git a/thehive-backend/app/services/OAuth2Srv.scala b/thehive-backend/app/services/OAuth2Srv.scala index 8aae6226bd..d69865369d 100644 --- a/thehive-backend/app/services/OAuth2Srv.scala +++ b/thehive-backend/app/services/OAuth2Srv.scala @@ -2,23 +2,18 @@ package services import java.util.UUID -import javax.inject.{ Inject, Singleton } import akka.stream.Materializer - -import org.elastic4play.services.{ AuthContext, AuthSrv } -import org.elastic4play.{ AuthenticationError, AuthorizationError, BadRequestError, CreateError, NotFoundError, OAuth2Redirect } -import play.api.http.Status -import play.api.libs.json.{ JsObject, JsValue } +import javax.inject.{Inject, Singleton} +import org.elastic4play.services.{AuthContext, AuthSrv} +import org.elastic4play.{AuthenticationError, BadRequestError, NotFoundError} +import play.api.libs.json.JsObject import play.api.libs.ws.WSClient -import play.api.mvc.{ Request, RequestHeader, Result, Results } -import play.api.{ Configuration, Logger } - +import play.api.mvc.{RequestHeader, Result, Results} +import play.api.{Configuration, Logger} import services.mappers.UserMapper -import scala.concurrent.{ ExecutionContext, Future } -import scala.util.{ Failure, Success, Try } - -import org.elastic4play.controllers.AuthenticatedRequest +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} case class OAuth2Config( clientId: String, @@ -31,27 +26,26 @@ case class OAuth2Config( userUrl: String, scope: String, authorizationHeader: String, - userIdField: String, - autoupdate: Boolean, -autocreate: Boolean + autoupdate: Boolean, + autocreate: Boolean ) object OAuth2Config { def apply(configuration: Configuration): Option[OAuth2Config] = for { - clientId ← configuration.getOptional[String]("auth.oauth2.clientId") - clientSecret ← configuration.getOptional[String]("auth.oauth2.clientSecret") - redirectUri ← configuration.getOptional[String]("auth.oauth2.redirectUri") - responseType ← configuration.getOptional[String]("auth.oauth2.responseType") - grantType ← configuration.getOptional[String]("auth.oauth2.grantType") + clientId ← configuration.getOptional[String]("auth.oauth2.clientId") + clientSecret ← configuration.getOptional[String]("auth.oauth2.clientSecret") + redirectUri ← configuration.getOptional[String]("auth.oauth2.redirectUri") + responseType ← configuration.getOptional[String]("auth.oauth2.responseType") + grantType = configuration.getOptional[String]("auth.oauth2.grantType").getOrElse("authorization_code") authorizationUrl ← configuration.getOptional[String]("auth.oauth2.authorizationUrl") - userUrl ← configuration.getOptional[String]("auth.oauth2.userUrl") tokenUrl ← configuration.getOptional[String]("auth.oauth2.tokenUrl") + userUrl ← configuration.getOptional[String]("auth.oauth2.userUrl") scope ← configuration.getOptional[String]("auth.oauth2.scope") authorizationHeader = configuration.getOptional[String]("auth.oauth2.authorizationHeader").getOrElse("Bearer") - autocreate = configuration.getOptional[Boolean]("auth.sso.autocreate").getOrElse(false) - autoupdate = configuration.getOptional[Boolean]("auth.sso.autoupdate").getOrElse(false) + autocreate = configuration.getOptional[Boolean]("auth.sso.autocreate").getOrElse(false) + autoupdate = configuration.getOptional[Boolean]("auth.sso.autoupdate").getOrElse(false) } yield OAuth2Config( clientId, clientSecret, @@ -89,45 +83,20 @@ class OAuth2Srv( private def withOAuth2Config[A](body: OAuth2Config ⇒ Future[A]): Future[A] = oauth2Config.fold[Future[A]](Future.failed(AuthenticationError("OAuth2 not configured properly")))(body) - override def authenticate()(implicit request: RequestHeader): Future[AuthContext] = + override def authenticate()(implicit request: RequestHeader): Future[Either[Result, AuthContext]] = withOAuth2Config { oauth2Config ⇒ - val ssoResponse = oauth2Config.grantType match { - case "authorization_code" => - if (!isSecuredAuthCode(request)) { - logger.debug("Code or state is not provided, redirect to authorizationUrl") - Future.successful(authRedirect(oauth2Config)) - } else { - for { - token <- getToken(oauth2Config, request) - userData <- getUserData(oauth2Config, token) - authReq <- Future - .fromTry(authenticate(request, userData)) - .recoverWith { - case _: NotFoundError => Future.fromTry(createUser(request, userData)) - } - .recoverWith { - case _: CreateError => Future.failed(NotFoundError("User not found")) - } - } yield sessionAuthSrv.setSessionUser(authReq.authContext)(Results.Found(httpContext)) - } - - case x => Future.failed(BadConfigurationError(s"OAuth GrantType $x not supported yet")) - } - ssoResponse.recoverWith { - case error => Future.successful(Results.Redirect(httpContext, Map("error" -> Seq(error.getMessage)))) - } - - - - - - request - .queryString - .get(Oauth2TokenQueryString) - .flatMap(_.headOption) - .fold(createOauth2Redirect(oauth2Config.clientId)) { code ⇒ - getAuthTokenAndAuthenticate(oauth2Config.clientId, code) + if (!isSecuredAuthCode(request)) { + logger.debug("Code or state is not provided, redirect to authorizationUrl") + Future.successful(Left(authRedirect(oauth2Config))) + } else { + (for { + token ← getToken(oauth2Config, request) + userData ← getUserData(oauth2Config, token) + authContext ← authenticate(oauth2Config, request, userData) + } yield Right(authContext)).recoverWith { + case error ⇒ Future.failed(AuthenticationError(s"OAuth2 authentication failure: ${error.getMessage}")) } + } } private def isSecuredAuthCode(request: RequestHeader): Boolean = @@ -141,17 +110,17 @@ class OAuth2Srv( private def authRedirect(oauth2Config: OAuth2Config): Result = { val state = UUID.randomUUID().toString val queryStringParams = Map[String, Seq[String]]( - "scope" -> Seq(oauth2Config.scope.mkString(" ")), - "response_type" -> Seq("code"), - "redirect_uri" -> Seq(oauth2Config.redirectUri), - "client_id" -> Seq(oauth2Config.clientId), - "state" -> Seq(state) + "scope" → Seq(oauth2Config.scope), + "response_type" → Seq(oauth2Config.responseType), + "redirect_uri" → Seq(oauth2Config.redirectUri), + "client_id" → Seq(oauth2Config.clientId), + "state" → Seq(state) ) logger.debug(s"Redirecting to ${oauth2Config.redirectUri} with $queryStringParams and state $state") Results .Redirect(oauth2Config.authorizationUrl, queryStringParams, status = 302) - .withSession("state" -> state) + .withSession("state" → state) } /** @@ -162,24 +131,23 @@ class OAuth2Srv( private def getToken[A](oauth2Config: OAuth2Config, request: RequestHeader): Future[String] = { val token = for { - state <- request.session.get("state") - stateQs <- request.queryString.get("state").flatMap(_.headOption) + state ← request.session.get("state") + stateQs ← request.queryString.get("state").flatMap(_.headOption) if state == stateQs } yield request.queryString.get("code").flatMap(_.headOption) match { - case Some(code) => + case Some(code) ⇒ logger.debug(s"Attempting to retrieve OAuth2 token from ${oauth2Config.tokenUrl} with code $code") getAuthTokenFromCode(oauth2Config, code, state) - .map { t => + .map { t ⇒ logger.trace(s"Got token $t") t } - case None => + case None ⇒ Future.failed(AuthenticationError(s"OAuth2 server code missing ${request.queryString.get("error")}")) } token.getOrElse(Future.failed(BadRequestError("OAuth2 states mismatch"))) } - /** * Querying the OAuth2 server for a token * @param code the previously obtained code @@ -195,23 +163,22 @@ class OAuth2Srv( | client_id: ${oauth2Config.clientId} | state: $state |""".stripMargin) - ws - .url(oauth2Config.tokenUrl) - .withHttpHeaders("Accept" -> "application/json") + ws.url(oauth2Config.tokenUrl) + .withHttpHeaders("Accept" → "application/json") .post( Map( - "code" -> code, - "grant_type" -> oauth2Config.grantType.toString, - "client_secret" -> oauth2Config.clientSecret, - "redirect_uri" -> oauth2Config.redirectUri, - "client_id" -> oauth2Config.clientId, - "state" -> state + "code" → code, + "grant_type" → oauth2Config.grantType, + "client_secret" → oauth2Config.clientSecret, + "redirect_uri" → oauth2Config.redirectUri, + "client_id" → oauth2Config.clientId, + "state" → state ) ) .transform { - case Success(r) if r.status == 200 => Success((r.json \ "access_token").asOpt[String].getOrElse("")) - case Failure(error) => Failure(AuthenticationError(s"OAuth2 token verification failure ${error.getMessage}")) - case Success(r) => Failure(AuthenticationError(s"OAuth2/token unexpected response from server (${r.status} ${r.statusText})")) + case Success(r) if r.status == 200 ⇒ Success((r.json \ "access_token").asOpt[String].getOrElse("")) + case Failure(error) ⇒ Failure(AuthenticationError(s"OAuth2 token verification failure ${error.getMessage}")) + case Success(r) ⇒ Failure(AuthenticationError(s"OAuth2/token unexpected response from server (${r.status} ${r.statusText})")) } } @@ -222,124 +189,40 @@ class OAuth2Srv( */ private def getUserData(oauth2Config: OAuth2Config, token: String): Future[JsObject] = { logger.trace(s"Request to ${oauth2Config.userUrl} with authorization header: ${oauth2Config.authorizationHeader} $token") - ws - .url(oauth2Config.userUrl) - .addHttpHeaders("Authorization" -> s"${oauth2Config.authorizationHeader} $token") + ws.url(oauth2Config.userUrl) + .addHttpHeaders("Authorization" → s"${oauth2Config.authorizationHeader} $token") .get() .transform { - case Success(r) if r.status == 200 => Success(r.json.as[JsObject]) - case Failure(error) => Failure(AuthenticationError(s"OAuth2 user data fetch failure ${error.getMessage}")) - case Success(r) => Failure(AuthenticationError(s"OAuth2/userinfo unexpected response from server (${r.status} ${r.statusText})")) + case Success(r) if r.status == 200 ⇒ Success(r.json.as[JsObject]) + case Failure(error) ⇒ Failure(AuthenticationError(s"OAuth2 user data fetch failure ${error.getMessage}")) + case Success(r) ⇒ Failure(AuthenticationError(s"OAuth2/userinfo unexpected response from server (${r.status} ${r.statusText})")) } } - - - private def authenticate[A](oauth2Config: OAuth2Config, request: Request[A], userData: JsObject): Future[AuthenticatedRequest[A]] = + private def authenticate(oauth2Config: OAuth2Config, request: RequestHeader, userData: JsObject): Future[AuthContext] = for { - userId <- getUserId(oauth2Config, userData) - user <- userSrv.get(userId) - authContext <- userSrv.getFromUser(request, user, "oauth2") - } yield new AuthenticatedRequest[A](authContext, request) - - private def getUserId(oauth2Config: OAuth2Config, jsonUser: JsObject): Try[String] = - (jsonUser \ oauth2Config.userIdField).asOpt[String] match { - case Some(userId) => Success(userId) - case None => Failure(BadRequestError(s"OAuth2 user data doesn't contain user ID field (${oauth2Config.userIdField})")) - } - + userFields ← ssoMapper.getUserFields(userData) + login ← userFields.getString("login").fold(Future.failed[String](AuthenticationError("")))(Future.successful) + user ← userSrv + .get(login) + .flatMap { + case u if oauth2Config.autoupdate ⇒ + logger.debug(s"Updating OAuth/OIDC user") + userSrv.inInitAuthContext { implicit authContext ⇒ + // Only update name and roles, not login (can't change it) + userSrv + .update(u, userFields.unset("login")) -// -// -// -// -// private def getAuthTokenAndAuthenticate(clientId: String, code: String)(implicit request: RequestHeader): Future[AuthContext] = { -// logger.debug("Getting user token with the code from the response") -// withOAuth2Config { cfg ⇒ -// ws.url(cfg.tokenUrl) -// .post( -// Map( -// "code" → code, -// "grant_type" → cfg.grantType, -// "client_secret" → cfg.clientSecret, -// "redirect_uri" → cfg.redirectUri, -// "client_id" → clientId -// ) -// ) -// .recoverWith { -// case error ⇒ -// logger.error(s"Token verification failure", error) -// Future.failed(AuthenticationError("Token verification failure")) -// } -// .flatMap { r ⇒ -// r.status match { -// case Status.OK ⇒ -// logger.debug("Getting user info using access token") -// val accessToken = (r.json \ "access_token").asOpt[String].getOrElse("") -// val authHeader = "Authorization" → s"Bearer $accessToken" -// ws.url(cfg.userUrl) -// .addHttpHeaders(authHeader) -// .get() -// .flatMap { userResponse ⇒ -// if (userResponse.status != Status.OK) { -// Future.failed(AuthenticationError(s"Unexpected response from server: ${userResponse.status} ${userResponse.body}")) -// } else { -// val response = userResponse.json.asInstanceOf[JsObject] -// getOrCreateUser(response, authHeader) -// } -// } -// case _ ⇒ -// logger.error(s"Unexpected response from server: ${r.status} ${r.body}") -// Future.failed(AuthenticationError("Unexpected response from server")) -// } -// } -// } -// } -// -// private def getOrCreateUser(response: JsValue, authHeader: (String, String))(implicit request: RequestHeader): Future[AuthContext] = -// withOAuth2Config { cfg ⇒ -// ssoMapper.getUserFields(response, Some(authHeader)).flatMap { userFields ⇒ -// val userId = userFields.getString("login").getOrElse("") -// userSrv -// .get(userId) -// .flatMap(user ⇒ { -// if (cfg.autoupdate) { -// logger.debug(s"Updating OAuth/OIDC user") -// userSrv.inInitAuthContext { implicit authContext ⇒ -// // Only update name and roles, not login (can't change it) -// userSrv -// .update(user, userFields.unset("login")) -// .flatMap(user ⇒ { -// userSrv.getFromUser(request, user, name) -// }) -// } -// } else { -// userSrv.getFromUser(request, user, name) -// } -// }) -// .recoverWith { -// case authErr: AuthorizationError ⇒ Future.failed(authErr) -// case _ if cfg.autocreate ⇒ -// logger.debug(s"Creating OAuth/OIDC user") -// userSrv.inInitAuthContext { implicit authContext ⇒ -// userSrv -// .create(userFields) -// .flatMap(user ⇒ { -// userSrv.getFromUser(request, user, name) -// }) -// } -// } -// } -// } -// -// private def createOauth2Redirect(clientId: String): Future[AuthContext] = -// withOAuth2Config { cfg ⇒ -// val queryStringParams = Map[String, Seq[String]]( -// "scope" → Seq(cfg.scope), -// "response_type" → Seq(cfg.responseType), -// "redirect_uri" → Seq(cfg.redirectUri), -// "client_id" → Seq(clientId) -// ) -// Future.failed(OAuth2Redirect(cfg.authorizationUrl, queryStringParams)) -// } + } + case u ⇒ Future.successful(u) + } + .recoverWith { + case _: NotFoundError if oauth2Config.autocreate ⇒ + logger.debug(s"Creating OAuth/OIDC user") + userSrv.inInitAuthContext { implicit authContext ⇒ + userSrv.create(userFields.set("login", userFields.getString("login").get.toLowerCase)) + } + } + authContext ← userSrv.getFromUser(request, user, name) + } yield authContext } diff --git a/thehive-backend/app/services/mappers/GroupUserMapper.scala b/thehive-backend/app/services/mappers/GroupUserMapper.scala index cdc76b9c6c..1d3817738f 100644 --- a/thehive-backend/app/services/mappers/GroupUserMapper.scala +++ b/thehive-backend/app/services/mappers/GroupUserMapper.scala @@ -25,7 +25,7 @@ class GroupUserMapper( @Inject() def this(configuration: Configuration, ws: WSClient, ec: ExecutionContext) = this( - configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("sub"), + configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("login"), configuration.getOptional[String]("auth.sso.attributes.name").getOrElse("name"), configuration.getOptional[String]("auth.sso.attributes.groups").getOrElse(""), configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()), @@ -40,36 +40,37 @@ class GroupUserMapper( private[GroupUserMapper] lazy val logger = Logger(getClass) private class RoleListParser extends RegexParsers { - val str: Regex = "[a-zA-Z0-9_]+".r - val strSpc: Regex = "[a-zA-Z0-9_ ]+".r - val realStr: Parser[String] = "\""~>strSpc<~"\"" | "'"~>strSpc<~"'" | str + val str: Regex = "[a-zA-Z0-9_]+".r + val strSpc: Regex = "[a-zA-Z0-9_ ]+".r + val realStr: Parser[String] = "\"" ~> strSpc <~ "\"" | "'" ~> strSpc <~ "'" | str - def expr: Parser[Seq[String]] = { + def expr: Parser[Seq[String]] = "[" ~ opt(realStr ~ rep("," ~ realStr)) ~ "]" ^^ { - case _ ~ Some(firstRole ~ list) ~ _ ⇒ list.foldLeft(Seq(firstRole)) { - case (queue, _ ~ role) ⇒ role +: queue - } - case _ ~ _ ⇒ Seq.empty[String] + case _ ~ Some(firstRole ~ list) ~ _ ⇒ + list.foldLeft(Seq(firstRole)) { + case (queue, _ ~ role) ⇒ role +: queue + } + case _ ~ _ ⇒ Seq.empty[String] } | opt(realStr) ^^ { case Some(role) ⇒ Seq(role) case None ⇒ Seq.empty[String] } - } } - override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = { + override def getUserFields(jsValue: JsValue, authHeader: Option[(String, String)]): Future[Fields] = groupsUrl match { case Some(groupsEndpointUrl) ⇒ - logger.debug(s"Retreiving groups from ${groupsEndpointUrl}") + logger.debug(s"Retreiving groups from $groupsEndpointUrl") val apiCall = authHeader.fold(ws.url(groupsEndpointUrl))(headers ⇒ ws.url(groupsEndpointUrl).addHttpHeaders(headers)) - apiCall.get.flatMap { r ⇒ extractGroupsThenBuildUserFields(jsValue, r.json) } + apiCall.get.flatMap { r ⇒ + extractGroupsThenBuildUserFields(jsValue, r.json) + } case None ⇒ logger.debug(s"Extracting groups from user info") extractGroupsThenBuildUserFields(jsValue, jsValue) } - } - private def extractGroupsThenBuildUserFields(jsValue: JsValue, groupsContainer: JsValue): Future[Fields] = { + private def extractGroupsThenBuildUserFields(jsValue: JsValue, groupsContainer: JsValue): Future[Fields] = groupsContainer \ groupsAttrName match { // Groups received as valid JSON array case JsDefined(JsArray(groupsList)) ⇒ mapGroupsAndBuildUserFields(jsValue, groupsList.map(_.as[String]).toList) @@ -90,7 +91,6 @@ class GroupUserMapper( case _: JsUndefined ⇒ Future.failed(AuthenticationError(s"User info fails: groups attribute $groupsAttrName doesn't exist in user info")) } - } private def mapGroupsAndBuildUserFields(jsValue: JsValue, jsonGroups: Seq[String]): Future[Fields] = { val mappedRoles = jsonGroups.flatMap(mappings.get).flatten.toSet @@ -108,7 +108,8 @@ class GroupUserMapper( } yield Fields(Json.obj("login" → login, "name" → name, "roles" → roles)) fields match { case JsSuccess(f, _) ⇒ Future.successful(f) - case JsError(errors) ⇒ Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) + case JsError(errors) ⇒ + Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) } } } diff --git a/thehive-backend/app/services/mappers/SimpleUserMapper.scala b/thehive-backend/app/services/mappers/SimpleUserMapper.scala index 598d9d2ece..7389909c11 100644 --- a/thehive-backend/app/services/mappers/SimpleUserMapper.scala +++ b/thehive-backend/app/services/mappers/SimpleUserMapper.scala @@ -20,7 +20,7 @@ class SimpleUserMapper( @Inject() def this(configuration: Configuration, ec: ExecutionContext) = this( - configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("sub"), + configuration.getOptional[String]("auth.sso.attributes.login").getOrElse("login"), configuration.getOptional[String]("auth.sso.attributes.name").getOrElse("name"), configuration.getOptional[String]("auth.sso.attributes.roles"), configuration.getOptional[Seq[String]]("auth.sso.defaultRoles").getOrElse(Seq()), @@ -37,7 +37,8 @@ class SimpleUserMapper( } yield Fields(Json.obj("login" → login, "name" → name, "roles" → roles)) fields match { case JsSuccess(f, _) ⇒ Future.successful(f) - case JsError(errors) ⇒ Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) + case JsError(errors) ⇒ + Future.failed(AuthenticationError(s"User info fails: ${errors.map(_._2).map(_.map(_.messages.mkString(", ")).mkString("; ")).mkString}")) } } } diff --git a/thehive-backend/conf/routes b/thehive-backend/conf/routes index f0acbc4687..420300c830 100644 --- a/thehive-backend/conf/routes +++ b/thehive-backend/conf/routes @@ -7,6 +7,7 @@ GET /api/status controllers.StatusCtrl.get GET /api/health controllers.StatusCtrl.health GET /api/logout controllers.AuthenticationCtrl.logout() POST /api/login controllers.AuthenticationCtrl.login() +GET /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() POST /api/ssoLogin controllers.AuthenticationCtrl.ssoLogin() POST /api/_search controllers.SearchCtrl.find() diff --git a/ui/app/views/login.html b/ui/app/views/login.html index 18bc6d7729..7b42eede7c 100644 --- a/ui/app/views/login.html +++ b/ui/app/views/login.html @@ -22,10 +22,10 @@
-
-
- -
+
OR
+ +
From ea498eb665dad8e8c331d1af39ed7242cbcd200b Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 13:12:37 +0200 Subject: [PATCH 09/14] Update drone configuration --- .drone.yml | 200 ++++++++++++++++++----------------------------------- 1 file changed, 69 insertions(+), 131 deletions(-) diff --git a/.drone.yml b/.drone.yml index bec436627a..eec20c7f23 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,16 +1,9 @@ --- kind: pipeline name: default - -# Disable default clone -clone: - disable: true +type: docker steps: - # This clone step doesn't use "root" user - - name: clone - image: plugins/git:next - # Restore cache of downloaded dependencies - name: restore-cache image: drillster/drone-volume-cache @@ -28,7 +21,7 @@ steps: image: thehiveproject/drone-scala-node commands: - . ~/.nvm/nvm.sh - - sbt -Duser.home=$PWD test stage + - sbt -Duser.home=$PWD test # Build packages - name: build-packages @@ -37,9 +30,23 @@ steps: pgp_key: {from_secret: pgp_key} commands: - | + V=$(sbt -no-colors --error "print thehive/version" | tail -1) + if ( echo $V | grep -qi snapshot) + then + exit 1 + fi . ~/.nvm/nvm.sh [ -n "$PLUGIN_PGP_KEY" ] && gpg --batch --import - <<< $PLUGIN_PGP_KEY sbt -Duser.home=$PWD docker:stage debian:packageBin rpm:packageBin universal:packageBin + if ( echo $V | grep -qi rc ) + then + echo $( echo $V | sed -re 's/([0-9]+.[0-9]+.[0-9]+)-RC([0-9]+)-([0-9]+)/\1-RC\2,\1-RC\2-\3/' ) > .tags + else + echo $( echo $V | sed -re 's/([0-9]+).([0-9]+).([0-9]+)-([0-9]+)/\1,\1.\2,\1.\2.\3,\1.\2.\3-\4,latest/' ) > .tags + fi + echo $V > thehive-version.txt + mv target/rpm/RPMS/noarch/thehive*.rpm target/ + mv target/universal/thehive*.zip target/ when: event: [tag] @@ -55,149 +62,80 @@ steps: - ui/bower_components volumes: [{name: cache, path: /cache}] - - name: publish-package - image: thehiveproject/drone-bintray + # Send packages using scp + - name: send packages + image: appleboy/drone-scp settings: - user: {from_secret: bintray_user} - key: {from_secret: bintray_key} - subject: thehive-project - package: thehive - commands: - - | - export PLUGIN_USER - export PLUGIN_KEY - export PLUGIN_SUBJECT - export PLUGIN_PACKAGE - export PLUGIN_VERSION=$(cut -d\" -f2 version.sbt) - echo "Publishing package version $PLUGIN_VERSION" - - if echo $PLUGIN_VERSION | grep -qvi -E \ - -e '^[0-9]+\.[0-9]+\.[0-9]+$' \ - -e '^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$' \ - -e '^[0-9]+\.[0-9]+\.[0-9]+-RC[0-9]+$'; then - echo The version $PLUGIN_VERSION has invalid format - exit 1 - fi - - CHANNEL=stable - if $(echo $PLUGIN_VERSION | grep -qi rc) - then - CHANNEL=beta - V=$(echo $PLUGIN_VERSION | sed -e 's/-\([rR][cC]\)/-0.1\1/') - DEB_FILE=target/thehive_$${V}_all.deb - RPM_FILE=target/rpm/RPMS/noarch/thehive-$${V}.noarch.rpm - else - DEB_FILE=target/thehive_$${PLUGIN_VERSION}_all.deb - RPM_FILE=target/rpm/RPMS/noarch/thehive-$${PLUGIN_VERSION}.noarch.rpm - fi - - ZIP_FILE=target/universal/thehive-$${PLUGIN_VERSION}.zip - - upload \ - --file $DEB_FILE \ - --repo debian-beta \ - --extra-param deb_distribution=any \ - --extra-param deb_component=main \ - --extra-param deb_architecture=all - - [ $CHANNEL = stable ] && upload \ - --file $DEB_FILE \ - --repo debian-stable \ - --extra-param deb_distribution=any \ - --extra-param deb_component=main \ - --extra-param deb_architecture=all - - upload \ - --file $RPM_FILE \ - --repo rpm-beta - - [ $CHANNEL = stable ] && upload \ - --file $RPM_FILE \ - --repo rpm-stable - - upload \ - --file $ZIP_FILE \ - --repo binary - - LATEST_VERSION=latest - [ $CHANNEL = beta ] && LATEST_VERSION=latest-beta - - removeVersion \ - --repo binary \ - --version $LATEST_VERSION + host: {from_secret: scp_host} + username: {from_secret: scp_user} + key: {from_secret: scp_key} + target: {from_secret: incoming_path} + source: + - target/thehive*.deb + - target/thehive*.rpm + - target/thehive*.zip + strip_components: 1 + when: + event: [tag] - upload \ - --file $ZIP_FILE \ - --repo binary \ - --version $LATEST_VERSION \ - --dest-file thehive-$${LATEST_VERSION}.zip + # Publish packages + - name: publish packages + image: appleboy/drone-ssh + settings: + host: {from_secret: scp_host} + user: {from_secret: scp_user} + key: {from_secret: scp_key} + publish_script: {from_secret: publish_script} + commands: + - PLUGIN_SCRIPT="bash $PLUGIN_PUBLISH_SCRIPT thehive $(cat thehive-version.txt)" /bin/drone-ssh when: event: [tag] - # Publish docker image + # Publish docker image on Docker Hub - name: docker image: plugins/docker settings: context: target/docker/stage dockerfile: target/docker/stage/Dockerfile repo: thehiveproject/thehive - auto_tag: true username: {from_secret: docker_username} password: {from_secret: docker_password} when: event: [tag] - # Deploy binaries in integration environment - - name: copy binaries in integration environment - image: appleboy/drone-scp - settings: - host: {from_secret: deploy_beta_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - target: ./thehive-builds/${DRONE_BUILD_NUMBER} - source: target/universal/stage - strip_components: 3 - when: - branch: [develop] - event: {exclude: [pull_request]} - - - name: deploy binaries in integration environment - image: appleboy/drone-ssh - settings: - host: {from_secret: deploy_beta_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - script: - - ./start thehive ${DRONE_BUILD_NUMBER} - when: - branch: [develop] - event: {exclude: [pull_request]} - - # Deploy binaries in staging environment - - name: copy binaries in staging environment - image: appleboy/drone-scp + # Publish docker image on Harbor + - name: harbor + image: plugins/docker settings: - host: {from_secret: deploy_stable_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - target: ./thehive-builds/${DRONE_BUILD_NUMBER} - source: target/universal/stage - strip_components: 3 + context: target/docker/stage + dockerfile: target/docker/stage/Dockerfile + registry: {from_secret: harbor_server} + repo: {from_secret: harbor_repo} + username: {from_secret: harbor_username} + password: {from_secret: harbor_password} when: - branch: [master] - event: {exclude: [pull_request]} + event: [tag] - - name: deploy binaries in staging environment - image: appleboy/drone-ssh + - name: send message + image: thehiveproject/drone_keybase settings: - host: {from_secret: deploy_stable_host} - username: {from_secret: deploy_username} - key: {from_secret: deploy_key} - script: - - ./start thehive ${DRONE_BUILD_NUMBER} + username: {from_secret: keybase_username} + paperkey: {from_secret: keybase_paperkey} + channel: {from_secret: keybase_channel} + commands: + - | + keybase oneshot -u "$PLUGIN_USERNAME" --paperkey "$PLUGIN_PAPERKEY" + URL="$DRONE_SYSTEM_PROTO://$DRONE_SYSTEM_HOST/$DRONE_REPO/$DRONE_BUILD_NUMBER" + if [ $DRONE_BUILD_STATUS = "success" ] + then + keybase chat send "$PLUGIN_CHANNEL" ":white_check_mark: $DRONE_REPO: build succeeded $URL" + else + keybase chat send "$PLUGIN_CHANNEL" ":x: $DRONE_REPO: build failed $URL" + fi when: - branch: [master] - event: {exclude: [pull_request]} + status: + - success + - failure volumes: - name: cache From 8e7671d657b961f97c90cd491a8b7cfee3e58058 Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 17:33:53 +0200 Subject: [PATCH 10/14] Prepare release --- CHANGELOG.md | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ ui/bower.json | 2 +- ui/package.json | 2 +- version.sbt | 2 +- 4 files changed, 160 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f7833080..3477c53989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,162 @@ # Change Log +## [3.5.0-RC1](https://github.com/TheHive-Project/TheHive/milestone/44) (2020-08-12) + +**Implemented enhancements:** + +- Support of ElasticSearch 7 [\#1377](https://github.com/TheHive-Project/TheHive/issues/1377) +- [Enhancement] MISP sync [\#1398](https://github.com/TheHive-Project/TheHive/issues/1398) + +**Closed issues:** + +- OAuth2 not working : Authentication failure [\#946](https://github.com/TheHive-Project/TheHive/issues/946) + +**Fixed bugs:** + +- [Bug] OAuth2/OpenIDC Authentication failure [\#1291](https://github.com/TheHive-Project/TheHive/issues/1291) +- [Feature Request] OAuth support for Basic authentication to authorization server's tokenUrl [\#1294](https://github.com/TheHive-Project/TheHive/issues/1294) +- [Bug] Can't auth with SSO/OAuth with FusionAuth [\#1342](https://github.com/TheHive-Project/TheHive/issues/1342) +- [Bug] TheHive is stalled while importing Alerts with a large number of observables [\#1416](https://github.com/TheHive-Project/TheHive/issues/1416) + +## [4.0.0](https://github.com/TheHive-Project/TheHive/milestone/59) (2020-07-24) + +**Implemented enhancements:** + +- No longer possible to force usage of a case template (ui setting is missing) [\#1239](https://github.com/TheHive-Project/TheHive/issues/1239) +- Make user management list paginable and sortable with default sort of username [\#1332](https://github.com/TheHive-Project/TheHive/issues/1332) +- Cursor is set wrong on new-Case -> severity [\#1373](https://github.com/TheHive-Project/TheHive/issues/1373) +- [Enhancement] Prevent link with "admin" organisation [\#1395](https://github.com/TheHive-Project/TheHive/issues/1395) +- [Enhancement] An user should not be able to lock himself [\#1396](https://github.com/TheHive-Project/TheHive/issues/1396) +- Performance - Don't load stats if not displayed [\#1401](https://github.com/TheHive-Project/TheHive/issues/1401) +- [RBAC] Add routes guard configuration to secure routes [\#1403](https://github.com/TheHive-Project/TheHive/issues/1403) +- [Enhancement] Add checks for database integrity [\#1404](https://github.com/TheHive-Project/TheHive/issues/1404) +- Use Query APIs in list pages [\#1410](https://github.com/TheHive-Project/TheHive/issues/1410) +- Improve autocomplete queries for tags [\#1411](https://github.com/TheHive-Project/TheHive/issues/1411) +- [Enhancement] Add ability to add tasks in case creation API [\#1414](https://github.com/TheHive-Project/TheHive/issues/1414) +- Improve user details caching [\#1418](https://github.com/TheHive-Project/TheHive/issues/1418) +- Add bulk edit in cases list [\#1423](https://github.com/TheHive-Project/TheHive/issues/1423) +- Use a responder selector window instead of dynamic dropdown menues [\#1431](https://github.com/TheHive-Project/TheHive/issues/1431) +- Show sharing summary in task and observable lists [\#1437](https://github.com/TheHive-Project/TheHive/issues/1437) +- Add some quick filters in tasks list [\#1438](https://github.com/TheHive-Project/TheHive/issues/1438) +- Use assignable users API to populate assignee options [\#1444](https://github.com/TheHive-Project/TheHive/issues/1444) +- Migrate the stats widgets on listing pages [\#1446](https://github.com/TheHive-Project/TheHive/issues/1446) + +**Closed issues:** + +- Default Dashboards are missing [\#1240](https://github.com/TheHive-Project/TheHive/issues/1240) + +**Fixed bugs:** + +- [Bug] Migration issues from ES to Cassandra [\#1340](https://github.com/TheHive-Project/TheHive/issues/1340) +- [Bug] Deleting and observable doesn't refresh the list [\#1355](https://github.com/TheHive-Project/TheHive/issues/1355) +- [Bug] Limiting admin rights breaks front end [\#1368](https://github.com/TheHive-Project/TheHive/issues/1368) +- [Bug] Imported Dashboards from TH3 doesn't work [\#1371](https://github.com/TheHive-Project/TheHive/issues/1371) +- [Bug] Top 5 tags in Case -> Stats aren't correctly ordered [\#1372](https://github.com/TheHive-Project/TheHive/issues/1372) +- [Bug] Migration of usernames from ES to Cassandra [\#1374](https://github.com/TheHive-Project/TheHive/issues/1374) +- [Bug] Switching User Organisation failes using header variable authentication [\#1375](https://github.com/TheHive-Project/TheHive/issues/1375) +- [Bug] Tags gets wrong renamed [\#1376](https://github.com/TheHive-Project/TheHive/issues/1376) +- [Bug] MISP integration alert link generated incorrectly [\#1378](https://github.com/TheHive-Project/TheHive/issues/1378) +- [Bug] CustomFields does not appear sorted in the case template [\#1383](https://github.com/TheHive-Project/TheHive/issues/1383) +- [Bug] Users in Admin-Org are not allowed to switch to any other org [\#1385](https://github.com/TheHive-Project/TheHive/issues/1385) +- [Bug] Custom Observable Types can be created multiple-times with the same name [\#1387](https://github.com/TheHive-Project/TheHive/issues/1387) +- [Bug] Issues during Migration - Some Observables are missing [\#1388](https://github.com/TheHive-Project/TheHive/issues/1388) +- [Bug] Proxy configuration is not correctly parsed [\#1392](https://github.com/TheHive-Project/TheHive/issues/1392) +- [Bug] Handle 401 on route failure [\#1402](https://github.com/TheHive-Project/TheHive/issues/1402) +- [Bug] Delete case api fails [\#1405](https://github.com/TheHive-Project/TheHive/issues/1405) +- Fix the filter preview deletion button [\#1412](https://github.com/TheHive-Project/TheHive/issues/1412) +- Fix OAuth redirect handling from Javascript [\#1420](https://github.com/TheHive-Project/TheHive/issues/1420) +- [Bug] Error when exporting a case with severity Critical in MISP [\#1424](https://github.com/TheHive-Project/TheHive/issues/1424) +- [Bug] Cases owned by non-linked organisations visible to all organisations, potential data leakage [\#1427](https://github.com/TheHive-Project/TheHive/issues/1427) +- [Bug] TheHive doesn't start correctly [\#1429](https://github.com/TheHive-Project/TheHive/issues/1429) +- [Bug] Permission is not correctly checked for MISP export [\#1432](https://github.com/TheHive-Project/TheHive/issues/1432) +- Observable type deletion doesn't wait for the confirmation [\#1433](https://github.com/TheHive-Project/TheHive/issues/1433) +- Fix rendering of jobs in search section [\#1434](https://github.com/TheHive-Project/TheHive/issues/1434) +- Remove obsolete options in Search page [\#1436](https://github.com/TheHive-Project/TheHive/issues/1436) +- [Bug] Click on dashboards to access filtered data [\#1445](https://github.com/TheHive-Project/TheHive/issues/1445) +- [Bug] Pivoting from dashboard to search page is loosing the date filter [\#1448](https://github.com/TheHive-Project/TheHive/issues/1448) +- [Bug] Series' filters in dashboard widgets are taken into account [\#1449](https://github.com/TheHive-Project/TheHive/issues/1449) + +## [4.0.0-RC3](https://github.com/TheHive-Project/TheHive/milestone/58) (2020-05-27) + +**Implemented enhancements:** + +- [Feature] Show case sharing information on main case overview page [\#1277](https://github.com/TheHive-Project/TheHive/issues/1277) +- [Feature] Allow users to be part of multiple organisations [\#1316](https://github.com/TheHive-Project/TheHive/issues/1316) +- [Enhancement] Hide multifactor option in user-dialog if Enable Multi-Factor Authentication is disabled. [\#1317](https://github.com/TheHive-Project/TheHive/issues/1317) +- [Feature] Authentication API should return user information [\#1346](https://github.com/TheHive-Project/TheHive/issues/1346) +- [Enhancement] Enrich queries [\#1353](https://github.com/TheHive-Project/TheHive/issues/1353) + +**Fixed bugs:** + +- [Bug] Unable to add new datatypes [\#1288](https://github.com/TheHive-Project/TheHive/issues/1288) +- [Bug] Unable to bulk delete an alert [\#1310](https://github.com/TheHive-Project/TheHive/issues/1310) +- [Bug] importing alert as template not working [\#1311](https://github.com/TheHive-Project/TheHive/issues/1311) +- [Bug] Tasks not displayed when importing alert into case with case template [\#1312](https://github.com/TheHive-Project/TheHive/issues/1312) +- [Bug] WebHook creation does not work [\#1318](https://github.com/TheHive-Project/TheHive/issues/1318) +- [Bug] Opening Analyzer Templates without Cortex brings error message [\#1319](https://github.com/TheHive-Project/TheHive/issues/1319) +- [Bug] Case Statistics does not correctly display top 5 tags [\#1320](https://github.com/TheHive-Project/TheHive/issues/1320) +- [Bug] Importing of some user failes [\#1323](https://github.com/TheHive-Project/TheHive/issues/1323) +- [Bug] invisible dashborards [\#1324](https://github.com/TheHive-Project/TheHive/issues/1324) +- [Bug] Assignee List in Case and Tasks is no longer sorted Alphabetical [\#1327](https://github.com/TheHive-Project/TheHive/issues/1327) +- [Bug] Sorting in Observables of a case does not work [\#1328](https://github.com/TheHive-Project/TheHive/issues/1328) +- [Bug] Read-only has options to edit task-logs [\#1334](https://github.com/TheHive-Project/TheHive/issues/1334) +- [Bug] Adding a custom-field on an open case requires a reload, otherwise field is not visible [\#1336](https://github.com/TheHive-Project/TheHive/issues/1336) +- [Bug] severity change when create new case don't work [\#1338](https://github.com/TheHive-Project/TheHive/issues/1338) +- [Bug] The filter operator "_child" is missing [\#1344](https://github.com/TheHive-Project/TheHive/issues/1344) +- [Bug] Webhook compatibility issues on custom-fields [\#1345](https://github.com/TheHive-Project/TheHive/issues/1345) +- [Bug] Object sent to responder doesn't contain parent [\#1348](https://github.com/TheHive-Project/TheHive/issues/1348) +- [Bug] Show Sharing link to all users [\#1351](https://github.com/TheHive-Project/TheHive/issues/1351) +- [Bug] Unable to create case or alert using integer custom field [\#1356](https://github.com/TheHive-Project/TheHive/issues/1356) +- [Bug] Get observables of a case using API not working [\#1357](https://github.com/TheHive-Project/TheHive/issues/1357) +- [Bug] OAuth2 authentication doesn't redirect to home page on success [\#1360](https://github.com/TheHive-Project/TheHive/issues/1360) +- [Bug] Confusion on same alert on different organisations [\#1361](https://github.com/TheHive-Project/TheHive/issues/1361) +- [Bug] Search link to observable does not work [\#1365](https://github.com/TheHive-Project/TheHive/issues/1365) +- [Bug] Unable to vienw analysis report from observable list [\#1366](https://github.com/TheHive-Project/TheHive/issues/1366) +- [Bug] MISP export succeeds but show an error message [\#1367](https://github.com/TheHive-Project/TheHive/issues/1367) +- [Bug] rc3 migration script failure [\#1369](https://github.com/TheHive-Project/TheHive/issues/1369) +- [Bug] set HTTP redirect correctly when behind a reverse proxy [\#1370](https://github.com/TheHive-Project/TheHive/issues/1370) + +## [4.0.0-RC2](https://github.com/TheHive-Project/TheHive/milestone/54) (2020-05-07) + +**Implemented enhancements:** + +- Custom severity levels for alerts and cases [\#363](https://github.com/TheHive-Project/TheHive/issues/363) +- A (received) Shared Case is displayed as sender/owner [\#1245](https://github.com/TheHive-Project/TheHive/issues/1245) +- FR: Alignment of case custom-fields (metrics) [\#1246](https://github.com/TheHive-Project/TheHive/issues/1246) +- Add information about the age of a Case [\#1257](https://github.com/TheHive-Project/TheHive/issues/1257) +- Providing output details for Responders [\#1293](https://github.com/TheHive-Project/TheHive/issues/1293) +- Add support to multi-factor authentication [\#1303](https://github.com/TheHive-Project/TheHive/issues/1303) +- Add support to webhooks [\#1306](https://github.com/TheHive-Project/TheHive/issues/1306) + +**Closed issues:** + +- [Bug] Attachment stored in thehive but not in configured file-storage [\#1244](https://github.com/TheHive-Project/TheHive/issues/1244) + +**Fixed bugs:** + +- [Bug] TH doesn't find cases related to an alert's artifacts [\#1236](https://github.com/TheHive-Project/TheHive/issues/1236) +- [Bug] Creation of multiple user with same login within same org [\#1237](https://github.com/TheHive-Project/TheHive/issues/1237) +- Date is now a required attribute for generating an Alert [\#1238](https://github.com/TheHive-Project/TheHive/issues/1238) +- [Bug] Case Template default values can't be set during template creation [\#1241](https://github.com/TheHive-Project/TheHive/issues/1241) +- SearchSrv.NotFoundError [\#1242](https://github.com/TheHive-Project/TheHive/issues/1242) +- Assignee is not changeable [\#1243](https://github.com/TheHive-Project/TheHive/issues/1243) +- [Bug] In TheHive, a user is a member of one or more organisations. One user has a profile for each organisation and can have different profiles for different organisations. [\#1247](https://github.com/TheHive-Project/TheHive/issues/1247) +- [Bug] RPM package does not create secret.conf file [\#1248](https://github.com/TheHive-Project/TheHive/issues/1248) +- [Bug] Unable to save new or imported dashboards in 4.0-RC1 [\#1250](https://github.com/TheHive-Project/TheHive/issues/1250) +- [Bug] Header Variable authentication does not work [\#1251](https://github.com/TheHive-Project/TheHive/issues/1251) +- Filtering by custom fields returns no results [\#1252](https://github.com/TheHive-Project/TheHive/issues/1252) +- Cannot Deleted user - Error "OrgUserCtrl: org.thp.thehive.models.User not found" [\#1253](https://github.com/TheHive-Project/TheHive/issues/1253) +- [Bug] Error while importing Alert in TH4 [\#1255](https://github.com/TheHive-Project/TheHive/issues/1255) +- [Bug] Cortex errors [\#1270](https://github.com/TheHive-Project/TheHive/issues/1270) +- [Bug] error when closing a reopened case [\#1271](https://github.com/TheHive-Project/TheHive/issues/1271) +- [Bug] Unable to rename/update case template Name field [\#1275](https://github.com/TheHive-Project/TheHive/issues/1275) +- [Bug] Wrong dataType sent to Cortex (responders) [\#1279](https://github.com/TheHive-Project/TheHive/issues/1279) +- [Bug] Changing task name removes other tasks [\#1281](https://github.com/TheHive-Project/TheHive/issues/1281) +- [Bug] Disable deleting a share with owner = true [\#1283](https://github.com/TheHive-Project/TheHive/issues/1283) +- [Bug] Responder actions not displayed in Case, Task and Observable pages [\#1300](https://github.com/TheHive-Project/TheHive/issues/1300) +- [Bug] Custom field should be readonly [\#1307](https://github.com/TheHive-Project/TheHive/issues/1307) +- [Bug] Unable to display long analyzer report from observables list [\#1309](https://github.com/TheHive-Project/TheHive/issues/1309) + ## [3.4.2](https://github.com/TheHive-Project/TheHive/milestone/57) (2020-04-25) **Implemented enhancements:** diff --git a/ui/bower.json b/ui/bower.json index baf64fcae5..4da35f6d5b 100644 --- a/ui/bower.json +++ b/ui/bower.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.2", + "version": "3.5.0-RC1", "license": "AGPL-3.0", "dependencies": { "jquery": "^3.4.1", diff --git a/ui/package.json b/ui/package.json index 4203f746c1..43d762078a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "thehive", - "version": "3.4.2", + "version": "3.5.0-RC1", "license": "AGPL-3.0", "repository": { "type": "git", diff --git a/version.sbt b/version.sbt index 06daa17521..95b3e51d97 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "3.4.2-1" +version in ThisBuild := "3.5.0-RC1-1" From ee04daa6bf67b2069f745e4cc345701e9dc93e3c Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 17:35:49 +0200 Subject: [PATCH 11/14] Remove obsolete report templates --- contrib/.gitignore | 1 - .../Abuse_Finder_1.0/long.html | 33 -- .../DNSDB_DomainName_1_1/long.html | 46 --- .../DNSDB_DomainName_1_1/short.html | 1 - .../DNSDB_IPHistory_1_0/long.html | 43 -- .../DNSDB_IPHistory_1_0/short.html | 1 - .../DNSDB_NameHistory_1_0/long.html | 42 -- .../DNSDB_NameHistory_1_0/short.html | 1 - .../DomainTools_ReverseIP_1_0/long.html | 30 -- .../DomainTools_ReverseIP_1_0/short.html | 1 - .../long.html | 43 -- .../short.html | 1 - .../DomainTools_ReverseWhois_1_0/long.html | 34 -- .../DomainTools_ReverseWhois_1_0/short.html | 3 - .../DomainTools_WhoisHistory_1_0/long.html | 48 --- .../DomainTools_WhoisHistory_1_0/short.html | 2 - .../DomainTools_WhoisLookup_1_0/long.html | 21 - .../DomainTools_WhoisLookup_1_0/short.html | 2 - .../DomainTools_WhoisLookup_IP_1_0/long.html | 21 - .../DomainTools_WhoisLookup_IP_1_0/short.html | 2 - .../report-templates/File_Info_1_0/long.html | 391 ------------------ .../report-templates/File_Info_1_0/short.html | 7 - .../Fortiguard_URLCategory_1_0/long.html | 26 -- .../Fortiguard_URLCategory_1_0/short.html | 4 - .../report-templates/HippoMore_1_0/long.html | 37 -- .../report-templates/HippoMore_1_0/short.html | 1 - .../report-templates/Hipposcore_1_0/long.html | 30 -- .../Hipposcore_1_0/short.html | 4 - .../MaxMind_GeoIP_2_0/long.html | 20 - .../MaxMind_GeoIP_2_0/short.html | 1 - .../report-templates/Msg_Parser_1_0/long.html | 76 ---- .../Msg_Parser_1_0/short.html | 3 - .../report-templates/OTXQuery_1_0/long.html | 165 -------- .../report-templates/OTXQuery_1_0/short.html | 4 - .../PassiveTotal_Enrichment_1_0/long.html | 48 --- .../PassiveTotal_Malware_1_0/long.html | 45 -- .../PassiveTotal_Malware_1_0/short.html | 7 - .../PassiveTotal_Osint_1_0/long.html | 51 --- .../PassiveTotal_Osint_1_0/short.html | 7 - .../PassiveTotal_Passive_Dns_1_0/long.html | 80 ---- .../PassiveTotal_Passive_Dns_1_0/short.html | 7 - .../long.html | 34 -- .../short.html | 7 - .../long.html | 41 -- .../short.html | 3 - .../long.html | 60 --- .../short.html | 3 - .../PassiveTotal_Whois_Details_1.0/long.html | 118 ------ .../PassiveTotal_Whois_Details_1.0/short.html | 2 - .../PhishTank_CheckURL_1_0/long.html | 44 -- .../PhishTank_CheckURL_1_0/short.html | 14 - .../PhishingInitiative_Lookup_1_0/long.html | 21 - .../PhishingInitiative_Lookup_1_0/short.html | 3 - .../VirusTotal_GetReport_2_0/long.html | 203 --------- .../VirusTotal_GetReport_2_0/short.html | 8 - .../VirusTotal_Scan_2_0/long.html | 78 ---- .../VirusTotal_Scan_2_0/short.html | 3 - 57 files changed, 2032 deletions(-) delete mode 100644 contrib/.gitignore delete mode 100644 contrib/report-templates/Abuse_Finder_1.0/long.html delete mode 100644 contrib/report-templates/DNSDB_DomainName_1_1/long.html delete mode 100644 contrib/report-templates/DNSDB_DomainName_1_1/short.html delete mode 100644 contrib/report-templates/DNSDB_IPHistory_1_0/long.html delete mode 100644 contrib/report-templates/DNSDB_IPHistory_1_0/short.html delete mode 100644 contrib/report-templates/DNSDB_NameHistory_1_0/long.html delete mode 100644 contrib/report-templates/DNSDB_NameHistory_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_ReverseIP_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_ReverseIP_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html delete mode 100644 contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html delete mode 100644 contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html delete mode 100644 contrib/report-templates/File_Info_1_0/long.html delete mode 100644 contrib/report-templates/File_Info_1_0/short.html delete mode 100644 contrib/report-templates/Fortiguard_URLCategory_1_0/long.html delete mode 100644 contrib/report-templates/Fortiguard_URLCategory_1_0/short.html delete mode 100644 contrib/report-templates/HippoMore_1_0/long.html delete mode 100644 contrib/report-templates/HippoMore_1_0/short.html delete mode 100644 contrib/report-templates/Hipposcore_1_0/long.html delete mode 100644 contrib/report-templates/Hipposcore_1_0/short.html delete mode 100644 contrib/report-templates/MaxMind_GeoIP_2_0/long.html delete mode 100644 contrib/report-templates/MaxMind_GeoIP_2_0/short.html delete mode 100644 contrib/report-templates/Msg_Parser_1_0/long.html delete mode 100644 contrib/report-templates/Msg_Parser_1_0/short.html delete mode 100644 contrib/report-templates/OTXQuery_1_0/long.html delete mode 100644 contrib/report-templates/OTXQuery_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Malware_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Malware_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Osint_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Osint_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html delete mode 100644 contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html delete mode 100644 contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html delete mode 100644 contrib/report-templates/PhishTank_CheckURL_1_0/long.html delete mode 100644 contrib/report-templates/PhishTank_CheckURL_1_0/short.html delete mode 100644 contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html delete mode 100644 contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html delete mode 100644 contrib/report-templates/VirusTotal_GetReport_2_0/long.html delete mode 100644 contrib/report-templates/VirusTotal_GetReport_2_0/short.html delete mode 100644 contrib/report-templates/VirusTotal_Scan_2_0/long.html delete mode 100644 contrib/report-templates/VirusTotal_Scan_2_0/short.html diff --git a/contrib/.gitignore b/contrib/.gitignore deleted file mode 100644 index 71d918b0bb..0000000000 --- a/contrib/.gitignore +++ /dev/null @@ -1 +0,0 @@ -report-templates/*.zip diff --git a/contrib/report-templates/Abuse_Finder_1.0/long.html b/contrib/report-templates/Abuse_Finder_1.0/long.html deleted file mode 100644 index ee902b1b50..0000000000 --- a/contrib/report-templates/Abuse_Finder_1.0/long.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
- Abuse Finder Information for {{artifact.data}} -
-
-
-
Names:
-
-
- {{name}} -
-
-
-
-
Abuse addresses:
-
-
- {{abuse}} -
-
-
-
-
- - -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DNSDB_DomainName_1_1/long.html b/contrib/report-templates/DNSDB_DomainName_1_1/long.html deleted file mode 100644 index cb1eb66e8e..0000000000 --- a/contrib/report-templates/DNSDB_DomainName_1_1/long.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB Domain Name History Report (RRSET) -

-
Type Data
- - - - - - - - - - - - - - - - - - - - - - -
#bailiwickcountrdatarrnametime firsttime last
{{$index+1}}{{row.bailiwick}}{{row.count}} -
{{rdata}}
-
{{row.rrname}}{{(row.zone_time_first || row.time_first) | shortDate}}{{(row.zone_time_last || row.time_last) | shortDate}}
- - diff --git a/contrib/report-templates/DNSDB_DomainName_1_1/short.html b/contrib/report-templates/DNSDB_DomainName_1_1/short.html deleted file mode 100644 index f0ae84c374..0000000000 --- a/contrib/report-templates/DNSDB_DomainName_1_1/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB Domain Name: {{content.records}} records diff --git a/contrib/report-templates/DNSDB_IPHistory_1_0/long.html b/contrib/report-templates/DNSDB_IPHistory_1_0/long.html deleted file mode 100644 index 5cb5ced89b..0000000000 --- a/contrib/report-templates/DNSDB_IPHistory_1_0/long.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB IP History Report - (Rdata IP) -

- - - - - - - - - - - - - - - - - - - - - -
#countrdatarrnamerrtypetime firsttime last
{{$index + 1}}{{row.count}}{{row.rdata}} class="wrap"{{row.rrname}}{{row.rrtype}}{{(row.zone_time_first || row.time_first) | shortDate}}{{(row.zone_time_last || row.time_last) | shortDate}}
-
-
diff --git a/contrib/report-templates/DNSDB_IPHistory_1_0/short.html b/contrib/report-templates/DNSDB_IPHistory_1_0/short.html deleted file mode 100644 index 4402a33419..0000000000 --- a/contrib/report-templates/DNSDB_IPHistory_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB IP History: {{content.records}} records diff --git a/contrib/report-templates/DNSDB_NameHistory_1_0/long.html b/contrib/report-templates/DNSDB_NameHistory_1_0/long.html deleted file mode 100644 index d7031c9a69..0000000000 --- a/contrib/report-templates/DNSDB_NameHistory_1_0/long.html +++ /dev/null @@ -1,42 +0,0 @@ -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} - View All ({{::content.records.length}}) -
-
-

- DNSDB Name History Report(Rdata Name) -

- - - - - - - - - - - - - - - - - - - - - -
#countrdatarrnamerrtypetime firsttime last
{{$index + 1}}{{row.count}}{{row.rdata}}{{row.rrname}}{{row.rrtype}}{{row.time_first | shortDate}}{{row.time_last | shortDate}}
-
-
diff --git a/contrib/report-templates/DNSDB_NameHistory_1_0/short.html b/contrib/report-templates/DNSDB_NameHistory_1_0/short.html deleted file mode 100644 index 3271f99b00..0000000000 --- a/contrib/report-templates/DNSDB_NameHistory_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -DNSDB Name History: {{content.records}} records diff --git a/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html b/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html deleted file mode 100644 index 89c1bc2164..0000000000 --- a/contrib/report-templates/DomainTools_ReverseIP_1_0/long.html +++ /dev/null @@ -1,30 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
-
-
Domains count
-
{{content.ip_addresses.domain_count}}
-
-
-
Domain Names
-
-
    -
  • - {{dn}} -
  • -
-
-
-
-
- -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html b/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html deleted file mode 100644 index 8b91e7d98e..0000000000 --- a/contrib/report-templates/DomainTools_ReverseIP_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -{{content.ip.address}}: {{content.ip.domain_count}} domains found diff --git a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html b/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html deleted file mode 100644 index 470227e411..0000000000 --- a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/long.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
-
-
- {{artifact.data | fang}} -
-
-
-
Name Server
-
{{content.name_server.hostname}}
-
-
-
Primary domains
-
- {{content.name_server.primary}}
- Show all -
-
-
-
Secondary domains
-
- {{content.name_server.secondary}}
- Show all -
-
- -
-
Primary Domains List
-
{{content.primary_domains | json}}
-
-
-
Secondary Domains List
-
{{content.secondary_domains | json}}
-
- -
-
diff --git a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html b/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html deleted file mode 100644 index 2f4c374ea0..0000000000 --- a/contrib/report-templates/DomainTools_ReverseNameServer_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -Hostname: {{content.name_server}}, {{content.domain_count}} domains diff --git a/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html b/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html deleted file mode 100644 index b2f8fe07bb..0000000000 --- a/contrib/report-templates/DomainTools_ReverseWhois_1_0/long.html +++ /dev/null @@ -1,34 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
-
-
Current Domains
-
{{content.domain_count.current}}
-
-
-
Historic Domains
-
{{content.domain_count.historic}}
-
-
-
Domains
-
-
    -
  • - {{dn}} -
  • -
-
-
-
-
- -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html b/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html deleted file mode 100644 index 015b9f0fa0..0000000000 --- a/contrib/report-templates/DomainTools_ReverseWhois_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - Domains found: curr:{{content.domain_count.current}}/hist:{{content.domain_count.historic}} - diff --git a/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html b/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html deleted file mode 100644 index 3d235f6000..0000000000 --- a/contrib/report-templates/DomainTools_WhoisHistory_1_0/long.html +++ /dev/null @@ -1,48 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data| fang}} - View All ({{::content.history.length}}) -
-
-
No records found
- - - - - - - - - - - - - - - - - - - - - -
#dateserver namesregistrantregistrarexpirationstatuses
{{$index + 1}}{{row.date}} -
    -
  • {{ns}}
  • -
-
{{row.whois.registrant}}{{row.whois.registration.registrar}}{{row.whois.registration.expires}} -
    -
  • {{st}}
  • -
-
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html b/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html deleted file mode 100644 index 3e748cbb2c..0000000000 --- a/contrib/report-templates/DomainTools_WhoisHistory_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -REGISTRANT: {{content.registrant}} -REGISTRAR: {{content.registrar}} diff --git a/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html b/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html deleted file mode 100644 index 2cdab86513..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data | fang}} -
-
-
-
Date of record
-
{{content.whois.date}}
-
-
{{content.whois.record}}
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html b/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html deleted file mode 100644 index 3e748cbb2c..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -REGISTRANT: {{content.registrant}} -REGISTRAR: {{content.registrar}} diff --git a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html b/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html deleted file mode 100644 index 2cdab86513..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- {{artifact.data | fang}} -
-
- {{content.errorMessage}} -
-
- -
-
- {{artifact.data | fang}} -
-
-
-
Date of record
-
{{content.whois.date}}
-
-
{{content.whois.record}}
-
-
diff --git a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html b/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html deleted file mode 100644 index f95ed23abc..0000000000 --- a/contrib/report-templates/DomainTools_WhoisLookup_IP_1_0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -DT:Whois:REGISTRANT= {{content.registrant}} -DT:Whois:REGISTRAR= {{content.registrar}} diff --git a/contrib/report-templates/File_Info_1_0/long.html b/contrib/report-templates/File_Info_1_0/long.html deleted file mode 100644 index 7d72d879d9..0000000000 --- a/contrib/report-templates/File_Info_1_0/long.html +++ /dev/null @@ -1,391 +0,0 @@ -
- - - -
-
- File Identification -
-
-
-
MD5
-
{{content.Identification['MD5']}}
-
-
-
SHA1
-
{{content.Identification['SHA1']}}
-
-
-
SHA256
-
{{content.Identification['SHA256']}}
-
-
-
impash
-
{{content.Identification['impash']|| "-"}} -
-
-
-
ssdeep
-
{{content.Identification['ssdeep']|| "-"}} -
-
-
-
pehash
-
{{content.Identification['pehash']|| "-"}} -
-
-
-
Operating System
-
{{content.Identification['OperatingSystem']}} -
-
-
-
PE Type
-
{{content.Identification['PEType']}}
-
-
-
Magic literal
-
{{content.Magic}}
-
-
-
MimeType
-
{{content.Mimetype}}
-
-
-
- - -
-
- File Metadata (Exiftool) -
-
-
-
{{k}}
-
{{v}}
-
- -
-
- - -
-
- PE Basic Information -
-
-
-
{{I.Info}}
-
{{I.Value}}
-
-
-
Compilation Timestamp
-
{{content.PE.BasicInformation.CompilationTimestamp}}
-
-
-
File Size
-
{{content.PE.BasicInformation.FileSize}}
-
-
-
Entry Point (EP)
-
{{content.PE.BasicInformation.EntryPoint}}
-
-
-
Target Machine
-
{{content.PE.BasicInformation.TargetMachine}}
-
-
-
- - -
-
- PE Sections -
-
- - - - - - - - - - - - - - - - -
SectionSizeOfRawDataEntroy
- {{section.entryname}} - {{section.SizeOfRawData}}{{section.Entropy}}
-
-
- MD5 -
-
- {{section.MD5}} -
-
-
-
- SHA1 -
-
- {{section.SHA1}} -
-
-
-
- SHA256 -
-
- {{section.SHA256}} -
-
-
-
-
- - - -
-
- PE Import Address Table -
-
-
-
- - - - {{entry.entryname}} -
- -
-
- {{entry.symbols.length}} - items -
-
-
- {{sym}} -
-
-
-
-
-
- - -
-
- Olevba Report -
-
-

Summary

-
-
Olevba version:
-
v{{content.MSOffice.olevba.Version}}
-
- -
-
Olevba detection :
-
{{content.MSOffice.olevba.vba}}
-
- -
-
Olevba scanner :
-
- - - - Not suspicious - Suspicious VBA -   - - Base64 strings -   - - Hex strings - - - - - Not suspicious - - - -
-
- -
-

Detailed Information

- -
-
-
-

OLE stream: - {{stream['OLE stream']}}

-
-
-
Information
-
-
VBA filename:
-
{{stream['VBA filename']}}
-
-
-
Filename:
-
{{stream['Filename']}}
-
- -
-
Olevba analysis
-
- - - - - - - - - - - - - - - -
TypeKeywordDescription
{{result.type}}{{result.keyword}}{{result.description}}
- -
-
- -
- - Show code - Hide code - -
-
- -
-                                      {{stream['VBA code']}}
-                                    
- - - -
-
- -
-
-
-
- -
-
-
- Analysis failure -
-
- {{content.MSOffice.olevba.Error}} -
-
-
-
- -
- - - - -
-
- PDFiD Report -
-
-

Summary

-
-
PDFiD version:
-
v{{content.PDF.pdfid[0].pdfid.version}}
-
- -
-
Suspicious:
-
{{content.PDF.pdfid[0].suspicious}}
-
- -
-
PDFiD detection :
-
- - - - /RichMedia -   - - - - /OpenAction -   - - - - /JavaScript -   - - - - /Launch -   - - - - /ObjStm -   - - -
-
-
- -
-
- - - -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/File_Info_1_0/short.html b/contrib/report-templates/File_Info_1_0/short.html deleted file mode 100644 index f8d67bb184..0000000000 --- a/contrib/report-templates/File_Info_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - {{content.filetype}} - - - - {{content.filetype}}: Suspicious - diff --git a/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html b/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html deleted file mode 100644 index 251350036f..0000000000 --- a/contrib/report-templates/Fortiguard_URLCategory_1_0/long.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
- URL Categories of - {{artifact.data}} -
-
-
-
Fortinet URL Category:
-
{{content.category}}  - - View Full Report - - Request Recategorization -
-
-
-
- -
-
- {{(artifact.data || artifact.attachment.name) | fang}} -
-
- {{content.errorMessage}} -
-
diff --git a/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html b/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html deleted file mode 100644 index e39dbcf7f7..0000000000 --- a/contrib/report-templates/Fortiguard_URLCategory_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - URLCat: - {{content.category}}  - diff --git a/contrib/report-templates/HippoMore_1_0/long.html b/contrib/report-templates/HippoMore_1_0/long.html deleted file mode 100644 index d0f1417e4d..0000000000 --- a/contrib/report-templates/HippoMore_1_0/long.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
- Detailed Information -
-
-
- No records found -
- - - - - - - - - - - - - - - -
SourceFirst seen by sourceLast seen by sourceCategoryDetails
{{source.source}}{{source.first_seen || '-'}}{{source.last_seen || '-'}}{{source.category || '-'}} -
  • First added in DB: {{source.firstAppearance }}
  • -
  • Last added in DB: {{source.lastAppearance }}
  • -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/HippoMore_1_0/short.html b/contrib/report-templates/HippoMore_1_0/short.html deleted file mode 100644 index 5812622426..0000000000 --- a/contrib/report-templates/HippoMore_1_0/short.html +++ /dev/null @@ -1 +0,0 @@ -HippoMore: {{content[artifact.data]}} record(s) diff --git a/contrib/report-templates/Hipposcore_1_0/long.html b/contrib/report-templates/Hipposcore_1_0/long.html deleted file mode 100644 index 3fd59af53e..0000000000 --- a/contrib/report-templates/Hipposcore_1_0/long.html +++ /dev/null @@ -1,30 +0,0 @@ -
    -
    - Detailed Information -
    -
    -
    -
    Hippocamp Score:
    -
    - - {{score}} - - - {{score}} - - - {{score}} - -
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/Hipposcore_1_0/short.html b/contrib/report-templates/Hipposcore_1_0/short.html deleted file mode 100644 index 34ef106075..0000000000 --- a/contrib/report-templates/Hipposcore_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - HippoScore: {{score}} - diff --git a/contrib/report-templates/MaxMind_GeoIP_2_0/long.html b/contrib/report-templates/MaxMind_GeoIP_2_0/long.html deleted file mode 100644 index 5f98f12d29..0000000000 --- a/contrib/report-templates/MaxMind_GeoIP_2_0/long.html +++ /dev/null @@ -1,20 +0,0 @@ -
    -
    - Geolocation of {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    -
    Is anonymous proxy
    -
    Is satellite provider
    - - {{[content.continent.name, content.country.name, content.subdivisions.name, content.city.name].join(' / ')}} -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/MaxMind_GeoIP_2_0/short.html b/contrib/report-templates/MaxMind_GeoIP_2_0/short.html deleted file mode 100644 index 0c79843709..0000000000 --- a/contrib/report-templates/MaxMind_GeoIP_2_0/short.html +++ /dev/null @@ -1 +0,0 @@ -IP location: {{content.country}} / {{content.continent}} diff --git a/contrib/report-templates/Msg_Parser_1_0/long.html b/contrib/report-templates/Msg_Parser_1_0/long.html deleted file mode 100644 index 1291294fcd..0000000000 --- a/contrib/report-templates/Msg_Parser_1_0/long.html +++ /dev/null @@ -1,76 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    - Email message details -
    -
    -
    Is anonymous proxy
    -
    Is satellite provider
    - -
    -
    From
    -
    {{content.displayFrom}} ({{content.sender}})
    -
    -
    -
    To
    -
    {{content.displayTo}} ({{content.receivers}})
    -
    -
    -
    Subject
    -
    {{content.subject || '-'}}
    -
    -
    -
    Topic
    -
    {{content.topic || '-'}}
    -
    -
    -
    Bcc
    -
    {{content.bcc || '-'}}
    -
    -
    -
    Attachments
    -
    -
    This message file includes - -
    - - - - - - - - - - - - - - - -
    FilenameMime TypeExtension
    {{a.filename}}{{a.mime}}{{a.extension}}
    -
    -
    -
    -
    Headers
    -
    -
    {{content.headers}}
    -
    -
    -
    -
    Body
    -
    -
    {{content.body}}
    -
    -
    -
    -
    diff --git a/contrib/report-templates/Msg_Parser_1_0/short.html b/contrib/report-templates/Msg_Parser_1_0/short.html deleted file mode 100644 index 60802dd732..0000000000 --- a/contrib/report-templates/Msg_Parser_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - MsgParser: - diff --git a/contrib/report-templates/OTXQuery_1_0/long.html b/contrib/report-templates/OTXQuery_1_0/long.html deleted file mode 100644 index f1c39b6739..0000000000 --- a/contrib/report-templates/OTXQuery_1_0/long.html +++ /dev/null @@ -1,165 +0,0 @@ -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    - OTX Report -
    -
    - -
    -
    ERROR:
    -
    {{content.errortext}} 
    -
    - -
    -
    Related Pulses Found:
    -
    {{content.pulse_count}}
    -
    - -
    -
    Related Pulses:
    -
    -
    - Name: {{::pulse.name}}
    - Author: {{::pulse.author.username}}
    - Modified: {{::pulse.modified_text}} @ {{::pulse.modified}}
    - Subscribers: {{::pulse.subscriber_count}}
    - Subscribed: {{::pulse.is_subscribing}}
    - Industries: {{::pulse.industries}}
    - Indicators: {{::pulse.indicator_count}} -
    -
    -
    -
    - -
    -
    Submit Pulse:
    -
    - Create a Pulse  -
    -
    - -
    -
    Malware Samples:
    -
    -
    - {{::sample.sample}} -
    -
    -
    - -
    -
    Malware:
    -
    {{content.malware}} 
    -
    - -
    -
    SHA1:
    -
    {{content.sha1}} 
    -
    - -
    -
    SHA256:
    -
    {{content.sha256}} 
    -
    - -
    -
    MD5:
    -
    {{content.md5}} 
    -
    - -
    -
    Page Type:
    -
    {{content.page_type}} 
    -
    - -
    -
    File Class:
    -
    {{content.file_class}} 
    -
    - -
    -
    File Type:
    -
    {{content.file_type}} 
    -
    - -
    -
    File Size:
    -
    {{content.filesize}} 
    -
    - -
    -
    SSDEEP:
    -
    {{content.ssdeep}} 
    -
    - -
    -
    Related URLs:
    -
    -
    - URL: {{::url.url}}
    - Date: {{::url.date}}
    - HTTP Code: {{::url.httpcode}}
    - IP: {{::url.result.urlworker.ip}} -
    -
    -
    -
    - -
    -
    Passive DNS:
    -
    -
    - Hostname: {{::dns.hostname}}
    - IP: {{::dns.address}}
    - First/Last seen: {{::dns.first}} / {{::dns.last}}
    - Locale: {{::dns.flag_title}} -
    -
    -
    -
    - -
    -
    Whois:
    -
    - Whois Query  -
    -
    - -
    -
    Alexa:
    -
    - Alexa Report  -
    -
    - -
    -
    City:
    -
    {{content.city}} 
    -
    - -
    -
    Country Code:
    -
    {{content.country_code}} 
    -
    - -
    -
    Country:
    -
    {{content.country_name}} 
    -
    - -
    -
    ASN:
    -
    {{content.asn}} 
    -
    - -
    -
    diff --git a/contrib/report-templates/OTXQuery_1_0/short.html b/contrib/report-templates/OTXQuery_1_0/short.html deleted file mode 100644 index 43a71a067f..0000000000 --- a/contrib/report-templates/OTXQuery_1_0/short.html +++ /dev/null @@ -1,4 +0,0 @@ - - OTX: - Pulses({{content.pulse_count}})  - diff --git a/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html b/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html deleted file mode 100644 index 694882d8de..0000000000 --- a/contrib/report-templates/PassiveTotal_Enrichment_1_0/long.html +++ /dev/null @@ -1,48 +0,0 @@ -
    - - - -
    -
    - PassiveTotal Enrichment Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    -
    -
    - {{tag}} -
    -
    - {{subd}} -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Malware_1_0/long.html b/contrib/report-templates/PassiveTotal_Malware_1_0/long.html deleted file mode 100644 index f95a957ac1..0000000000 --- a/contrib/report-templates/PassiveTotal_Malware_1_0/long.html +++ /dev/null @@ -1,45 +0,0 @@ -
    - - -
    -
    - PassiveTotal OSINT -
    -
    -
    - No records found -
    -
    -
    -
    -
    -
    {{key}}:
    -
    -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Malware_1_0/short.html b/contrib/report-templates/PassiveTotal_Malware_1_0/short.html deleted file mode 100644 index 733f0f9b6e..0000000000 --- a/contrib/report-templates/PassiveTotal_Malware_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:MALWARE=False - - - - PT:MALWARE=True - diff --git a/contrib/report-templates/PassiveTotal_Osint_1_0/long.html b/contrib/report-templates/PassiveTotal_Osint_1_0/long.html deleted file mode 100644 index 8715acf54c..0000000000 --- a/contrib/report-templates/PassiveTotal_Osint_1_0/long.html +++ /dev/null @@ -1,51 +0,0 @@ -
    - - -
    -
    - PassiveTotal OSINT -
    -
    -
    - No records found -
    -
    -
    -
    -
    -
    {{key}}:
    -
    -
    -
    - {{v}} -
    -
    - {{v}} -
    -
    - {{value || None}} -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Osint_1_0/short.html b/contrib/report-templates/PassiveTotal_Osint_1_0/short.html deleted file mode 100644 index 0c7db25dbd..0000000000 --- a/contrib/report-templates/PassiveTotal_Osint_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:OSINT=False - - - - PT:OSINT=True - diff --git a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html b/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html deleted file mode 100644 index d9b74792cd..0000000000 --- a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/long.html +++ /dev/null @@ -1,80 +0,0 @@ - -
    - - - -
    -
    - PassiveTotal PassiveDNS Report -
    -
    -
    - No records found -
    -
    -
    - Summary Information -
    -
    -
    -
    Value:
    -
    {{content.queryValue}}
    -
    -
    -
    Total Records:
    -
    {{content.totalRecords}}
    -
    -
    -
    First seen:
    -
    {{content.firstSeen}}
    -
    -
    -
    Last seen:
    -
    {{content.lastSeen}}
    -
    -
    -
    -
    -
    - Records -
    -
    - - - - - - - - - - - - - -
    SourceResolveFirst seenLast seen
    -
    - {{s}} -
    -
    {{c.resolve || 'None'}}{{c.firstSeen || 'None'}}{{c.lastSeen || 'None'}}
    -
    -
    - -
    -
    - -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html b/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html deleted file mode 100644 index 9de1bbc44c..0000000000 --- a/contrib/report-templates/PassiveTotal_Passive_Dns_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:PassiveDNS= {{content.total}} record - - - - PT:PassiveDNS= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html deleted file mode 100644 index 33a4d38445..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/long.html +++ /dev/null @@ -1,34 +0,0 @@ -
    - - -
    -
    - PassiveTotal SSL Certificate Information -
    -
    -
    - No records found -
    -
    -
    -
    {{k}}:
    -
    {{v}}
    -
    -
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html deleted file mode 100644 index 6f79d3155b..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_Details_1_0/short.html +++ /dev/null @@ -1,7 +0,0 @@ - - PT:SSL=False - - - - PT:SSL=True - diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html deleted file mode 100644 index 0ef8fe306b..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/long.html +++ /dev/null @@ -1,41 +0,0 @@ - -
    - - -
    -
    - Detailed Information -
    -
    -
    - No records found -
    - - - - - - - - - - - -
    SHA1First seenLast seen
    {{c.sha1}}{{c.firstSeen || 'None'}}{{c.lastSeen || 'None'}}
    -
    -
    -
    - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html b/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html deleted file mode 100644 index 128ecbc3b0..0000000000 --- a/contrib/report-templates/PassiveTotal_Ssl_Certificate_History_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PT:SSLCertHistory= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html b/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html deleted file mode 100644 index d7b0b64f5e..0000000000 --- a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/long.html +++ /dev/null @@ -1,60 +0,0 @@ -
    - -
    -
    - PassiveTotal unique resolution -
    -
    -
    - No records found -
    -
    -
    -
    Query Type:
    -
    {{content.queryType}}
    -
    -
    -
    Query:
    -
    {{content.queryValue}}
    -
    -
    -
    - - -    - {{content.queryValue}} :
    -
    -
    - {{content.total}} result(s) - -
    -
    - {{content.results[0]}} -
    -
    -
    - - {{r}} -
    -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html b/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html deleted file mode 100644 index 54e803b3ae..0000000000 --- a/contrib/report-templates/PassiveTotal_Unique_Resolutions_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PT:UniqueResolution= {{content.total}} record(s) - diff --git a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html b/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html deleted file mode 100644 index 4d3b96114d..0000000000 --- a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/long.html +++ /dev/null @@ -1,118 +0,0 @@ -
    - - -
    -
    - PassiveTotal Whois Summary -
    -
    -
    - No records found -
    -
    -
    -
    Domain:
    -
    {{content.domain|| "-"}}
    -
    -
    -
    Contact email:
    -
    {{content.contactEmail || "-"}}
    -
    -
    -
    Nameservers:
    -
    {{ns}}
    -
    -
    -
    Whois server:
    -
    {{content.whoisServer || "-"}}
    -
    -
    -
    Registered date:
    -
    {{content.registered}}
    -
    -
    -
    Registry updated at:
    -
    {{content.registryUpdatedAt}}
    -
    -
    -
    Last load at:
    -
    {{content.lastLoadedAt}}
    -
    -
    -
    Expires at:
    -
    {{content.expiresAt}}
    -
    -
    -
    Registrar:
    -
    {{content.registrar}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Admin Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Tech Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    - -
    -
    - PassiveTotal Whois Registrant Info -
    -
    -
    - No records found -
    -
    -
    -
    {{key}}
    -
    {{value}}
    -
    -
    -
    -
    -
    - - -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html b/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html deleted file mode 100644 index 7353be9a41..0000000000 --- a/contrib/report-templates/PassiveTotal_Whois_Details_1.0/short.html +++ /dev/null @@ -1,2 +0,0 @@ -PT:Whois:REGISTRANT= {{content.registrant}} -PT:Whois:REGISTRAR= {{content.registrar}} diff --git a/contrib/report-templates/PhishTank_CheckURL_1_0/long.html b/contrib/report-templates/PhishTank_CheckURL_1_0/long.html deleted file mode 100644 index c10bf30991..0000000000 --- a/contrib/report-templates/PhishTank_CheckURL_1_0/long.html +++ /dev/null @@ -1,44 +0,0 @@ -
    -
    - PhishTank Report for {{artifact.data}} -
    -
    -
    -
    ERROR:
    -
    {{content.errortext}} 
    -
    -
    -
    In database:
    -
    {{content.in_database}} 
    -
    -
    -
    Verified:
    -
    {{content.verified}} 
    -
    -
    -
    Verified at:
    -
    {{content.verified_at}} 
    -
    -
    -
    Phish Detail Page:
    -
    - {{content.phish_detail_page}} -
    -
    -
    -
    Submit to PhishTank:
    -
    - Click here to submit this site to PhishTank -
    -
    -
    -
    - -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PhishTank_CheckURL_1_0/short.html b/contrib/report-templates/PhishTank_CheckURL_1_0/short.html deleted file mode 100644 index b90f191fcb..0000000000 --- a/contrib/report-templates/PhishTank_CheckURL_1_0/short.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - PhishTank: - - {{millis | amDurationFormat : 'milliseconds'}} - - - - PhishTank: {{content.in_database}}  - - - - diff --git a/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html b/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html deleted file mode 100644 index 1f573461e5..0000000000 --- a/contrib/report-templates/PhishingInitiative_Lookup_1_0/long.html +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    - PhishingInitiative Report for {{artifact.data | fang}} -
    -
    -
    -
    Status:
    -
    - {{content.tag_label}} -
    -
    -
    -
    -
    -
    - {{artifact.data | fang}} -
    -
    - {{content.errorMessage}} -
    -
    diff --git a/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html b/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html deleted file mode 100644 index d200285563..0000000000 --- a/contrib/report-templates/PhishingInitiative_Lookup_1_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - PhishingInitiative: {{content.status}} - diff --git a/contrib/report-templates/VirusTotal_GetReport_2_0/long.html b/contrib/report-templates/VirusTotal_GetReport_2_0/long.html deleted file mode 100644 index fdcb8194fe..0000000000 --- a/contrib/report-templates/VirusTotal_GetReport_2_0/long.html +++ /dev/null @@ -1,203 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - -
    -
    -
    -
    - Summary -
    -
    -
    -
    Score
    -
    {{content.positives || 0}}/{{content.total}}
    -
    -
    -
    Last analysis date
    -
    {{content.scan_date}}
    -
    -
    -
    Autonomous System
    -
    {{content.as_owner}}
    -
    -
    -
    Categories
    -
    {{content.categories.join(', ')}}
    -
    -
    -
    Sub domains
    -
    {{content.subdomains.join(', ')}}
    -
    -
    -
    Resolutions
    -
    -
    - This domain has been seen to resolve to the following IP addresses. -
    -
    - The following domains resolved to the given IP address. -
    -
    - {{::resolution.last_resolved | amDateFormat:'DD-MM-YYYY'}}: - {{(resolution.ip_address | fang) || (resolution.hostname | fang)}} -
    -
    -
    -
    -
    Virus Total
    -
    - - - - View Full Report - - - - - View Full Report - - - - - View Full Report - -
    -
    -
    -
    - -
    -
    - Latest detected URLs - - View All ({{::content.detected_urls.length}}) - -
    -
    -

    Latest URLs hosted in this IP address - detected by at least one URL scanner or malicious URL dataset. -

    - - - - - - - - - - - -
    ScoreScan DateURL
    - {{::url.positives}}/{{::url.total}} - {{url.scan_date}}{{url.url | fang}}
    -
    -
    - -
    -
    - Latest detected files that were downloaded from this IP address - - View All ({{::content.detected_downloaded_samples.length}}) - -
    -
    -

    Latest files that are - detected by at least one antivirus solution and were downloaded by VirusTotal from the IP address provided. -

    - - - - - - - - - - - -
    ScoreDateSHA256
    - {{hash.positives}}/{{hash.total}} - {{hash.date}}{{hash.sha256}}
    -
    -
    - -
    -
    - Latest detected files that embed this IP address in their strings - - View All ({{::content.detected_referrer_samples.length}}) - -
    -
    -

    Latest files that are - detected by at least one antivirus solution and embed URL pattern strings with the IP address provided. -

    - - - - - - - - - -
    ScoreSHA256
    - {{hash.positives}}/{{hash.total}} - {{hash.sha256}}
    -
    -
    - -
    -
    - Scans -
    -
    - - - - - - - - - - - - - - - - - - -
    ScannerDetectedResultDetailsUpdateVersion
    - {{scanner}} - - - {{result.result}} - - - View details - {{result.update}}{{result.version}}
    -
    -
    - -
    -
    -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.verbose_msg}} -
    -
    -
    -
    diff --git a/contrib/report-templates/VirusTotal_GetReport_2_0/short.html b/contrib/report-templates/VirusTotal_GetReport_2_0/short.html deleted file mode 100644 index a713b763d4..0000000000 --- a/contrib/report-templates/VirusTotal_GetReport_2_0/short.html +++ /dev/null @@ -1,8 +0,0 @@ - - VT: - {{content.positives}}/{{content.total}} - Scans({{content.scans}}) - Resolutions({{content.resolutions}}) - Url detections({{content.detected_urls}}) - files({{content.detected_downloaded_samples}}) - diff --git a/contrib/report-templates/VirusTotal_Scan_2_0/long.html b/contrib/report-templates/VirusTotal_Scan_2_0/long.html deleted file mode 100644 index 8bdaee6579..0000000000 --- a/contrib/report-templates/VirusTotal_Scan_2_0/long.html +++ /dev/null @@ -1,78 +0,0 @@ -
    -
    - {{(artifact.data || artifact.attachment.name) | fang}} -
    -
    - {{content.errorMessage}} -
    -
    - - -
    -
    -
    -
    Summary
    -
    -
    -
    Score
    -
    {{content.positives}}/{{content.total}}
    -
    -
    -
    Last analysis date
    -
    {{content.scan_date}}
    -
    -
    -
    Virus Total
    -
    - - - - View Full Report - -
    -
    -
    -
    - -
    -
    Scans
    -
    - - - - - - - - - - - - - - - - - - -
    ScannerDetectedResultDetailsUpdateVersion
    {{scanner}} - - {{result.result}} - View details - {{result.update}}{{result.version}}
    -
    -
    - - -
    -
    -
    -
    - {{(artifact.data || artifact.attachment.name)| fang}} -
    -
    - {{content.verbose_msg}} -
    -
    -
    -
    diff --git a/contrib/report-templates/VirusTotal_Scan_2_0/short.html b/contrib/report-templates/VirusTotal_Scan_2_0/short.html deleted file mode 100644 index 7da455c08f..0000000000 --- a/contrib/report-templates/VirusTotal_Scan_2_0/short.html +++ /dev/null @@ -1,3 +0,0 @@ - - VT: {{content.positives}}/{{content.total}} - From cdcd47df79aa85062783a98ddbd2123dbebe744e Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 18:01:12 +0200 Subject: [PATCH 12/14] Remove changelog entries from 4.0.0 --- CHANGELOG.md | 139 --------------------------------------------------- build.sbt | 3 ++ 2 files changed, 3 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3477c53989..8825d1ec63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,145 +18,6 @@ - [Bug] Can't auth with SSO/OAuth with FusionAuth [\#1342](https://github.com/TheHive-Project/TheHive/issues/1342) - [Bug] TheHive is stalled while importing Alerts with a large number of observables [\#1416](https://github.com/TheHive-Project/TheHive/issues/1416) -## [4.0.0](https://github.com/TheHive-Project/TheHive/milestone/59) (2020-07-24) - -**Implemented enhancements:** - -- No longer possible to force usage of a case template (ui setting is missing) [\#1239](https://github.com/TheHive-Project/TheHive/issues/1239) -- Make user management list paginable and sortable with default sort of username [\#1332](https://github.com/TheHive-Project/TheHive/issues/1332) -- Cursor is set wrong on new-Case -> severity [\#1373](https://github.com/TheHive-Project/TheHive/issues/1373) -- [Enhancement] Prevent link with "admin" organisation [\#1395](https://github.com/TheHive-Project/TheHive/issues/1395) -- [Enhancement] An user should not be able to lock himself [\#1396](https://github.com/TheHive-Project/TheHive/issues/1396) -- Performance - Don't load stats if not displayed [\#1401](https://github.com/TheHive-Project/TheHive/issues/1401) -- [RBAC] Add routes guard configuration to secure routes [\#1403](https://github.com/TheHive-Project/TheHive/issues/1403) -- [Enhancement] Add checks for database integrity [\#1404](https://github.com/TheHive-Project/TheHive/issues/1404) -- Use Query APIs in list pages [\#1410](https://github.com/TheHive-Project/TheHive/issues/1410) -- Improve autocomplete queries for tags [\#1411](https://github.com/TheHive-Project/TheHive/issues/1411) -- [Enhancement] Add ability to add tasks in case creation API [\#1414](https://github.com/TheHive-Project/TheHive/issues/1414) -- Improve user details caching [\#1418](https://github.com/TheHive-Project/TheHive/issues/1418) -- Add bulk edit in cases list [\#1423](https://github.com/TheHive-Project/TheHive/issues/1423) -- Use a responder selector window instead of dynamic dropdown menues [\#1431](https://github.com/TheHive-Project/TheHive/issues/1431) -- Show sharing summary in task and observable lists [\#1437](https://github.com/TheHive-Project/TheHive/issues/1437) -- Add some quick filters in tasks list [\#1438](https://github.com/TheHive-Project/TheHive/issues/1438) -- Use assignable users API to populate assignee options [\#1444](https://github.com/TheHive-Project/TheHive/issues/1444) -- Migrate the stats widgets on listing pages [\#1446](https://github.com/TheHive-Project/TheHive/issues/1446) - -**Closed issues:** - -- Default Dashboards are missing [\#1240](https://github.com/TheHive-Project/TheHive/issues/1240) - -**Fixed bugs:** - -- [Bug] Migration issues from ES to Cassandra [\#1340](https://github.com/TheHive-Project/TheHive/issues/1340) -- [Bug] Deleting and observable doesn't refresh the list [\#1355](https://github.com/TheHive-Project/TheHive/issues/1355) -- [Bug] Limiting admin rights breaks front end [\#1368](https://github.com/TheHive-Project/TheHive/issues/1368) -- [Bug] Imported Dashboards from TH3 doesn't work [\#1371](https://github.com/TheHive-Project/TheHive/issues/1371) -- [Bug] Top 5 tags in Case -> Stats aren't correctly ordered [\#1372](https://github.com/TheHive-Project/TheHive/issues/1372) -- [Bug] Migration of usernames from ES to Cassandra [\#1374](https://github.com/TheHive-Project/TheHive/issues/1374) -- [Bug] Switching User Organisation failes using header variable authentication [\#1375](https://github.com/TheHive-Project/TheHive/issues/1375) -- [Bug] Tags gets wrong renamed [\#1376](https://github.com/TheHive-Project/TheHive/issues/1376) -- [Bug] MISP integration alert link generated incorrectly [\#1378](https://github.com/TheHive-Project/TheHive/issues/1378) -- [Bug] CustomFields does not appear sorted in the case template [\#1383](https://github.com/TheHive-Project/TheHive/issues/1383) -- [Bug] Users in Admin-Org are not allowed to switch to any other org [\#1385](https://github.com/TheHive-Project/TheHive/issues/1385) -- [Bug] Custom Observable Types can be created multiple-times with the same name [\#1387](https://github.com/TheHive-Project/TheHive/issues/1387) -- [Bug] Issues during Migration - Some Observables are missing [\#1388](https://github.com/TheHive-Project/TheHive/issues/1388) -- [Bug] Proxy configuration is not correctly parsed [\#1392](https://github.com/TheHive-Project/TheHive/issues/1392) -- [Bug] Handle 401 on route failure [\#1402](https://github.com/TheHive-Project/TheHive/issues/1402) -- [Bug] Delete case api fails [\#1405](https://github.com/TheHive-Project/TheHive/issues/1405) -- Fix the filter preview deletion button [\#1412](https://github.com/TheHive-Project/TheHive/issues/1412) -- Fix OAuth redirect handling from Javascript [\#1420](https://github.com/TheHive-Project/TheHive/issues/1420) -- [Bug] Error when exporting a case with severity Critical in MISP [\#1424](https://github.com/TheHive-Project/TheHive/issues/1424) -- [Bug] Cases owned by non-linked organisations visible to all organisations, potential data leakage [\#1427](https://github.com/TheHive-Project/TheHive/issues/1427) -- [Bug] TheHive doesn't start correctly [\#1429](https://github.com/TheHive-Project/TheHive/issues/1429) -- [Bug] Permission is not correctly checked for MISP export [\#1432](https://github.com/TheHive-Project/TheHive/issues/1432) -- Observable type deletion doesn't wait for the confirmation [\#1433](https://github.com/TheHive-Project/TheHive/issues/1433) -- Fix rendering of jobs in search section [\#1434](https://github.com/TheHive-Project/TheHive/issues/1434) -- Remove obsolete options in Search page [\#1436](https://github.com/TheHive-Project/TheHive/issues/1436) -- [Bug] Click on dashboards to access filtered data [\#1445](https://github.com/TheHive-Project/TheHive/issues/1445) -- [Bug] Pivoting from dashboard to search page is loosing the date filter [\#1448](https://github.com/TheHive-Project/TheHive/issues/1448) -- [Bug] Series' filters in dashboard widgets are taken into account [\#1449](https://github.com/TheHive-Project/TheHive/issues/1449) - -## [4.0.0-RC3](https://github.com/TheHive-Project/TheHive/milestone/58) (2020-05-27) - -**Implemented enhancements:** - -- [Feature] Show case sharing information on main case overview page [\#1277](https://github.com/TheHive-Project/TheHive/issues/1277) -- [Feature] Allow users to be part of multiple organisations [\#1316](https://github.com/TheHive-Project/TheHive/issues/1316) -- [Enhancement] Hide multifactor option in user-dialog if Enable Multi-Factor Authentication is disabled. [\#1317](https://github.com/TheHive-Project/TheHive/issues/1317) -- [Feature] Authentication API should return user information [\#1346](https://github.com/TheHive-Project/TheHive/issues/1346) -- [Enhancement] Enrich queries [\#1353](https://github.com/TheHive-Project/TheHive/issues/1353) - -**Fixed bugs:** - -- [Bug] Unable to add new datatypes [\#1288](https://github.com/TheHive-Project/TheHive/issues/1288) -- [Bug] Unable to bulk delete an alert [\#1310](https://github.com/TheHive-Project/TheHive/issues/1310) -- [Bug] importing alert as template not working [\#1311](https://github.com/TheHive-Project/TheHive/issues/1311) -- [Bug] Tasks not displayed when importing alert into case with case template [\#1312](https://github.com/TheHive-Project/TheHive/issues/1312) -- [Bug] WebHook creation does not work [\#1318](https://github.com/TheHive-Project/TheHive/issues/1318) -- [Bug] Opening Analyzer Templates without Cortex brings error message [\#1319](https://github.com/TheHive-Project/TheHive/issues/1319) -- [Bug] Case Statistics does not correctly display top 5 tags [\#1320](https://github.com/TheHive-Project/TheHive/issues/1320) -- [Bug] Importing of some user failes [\#1323](https://github.com/TheHive-Project/TheHive/issues/1323) -- [Bug] invisible dashborards [\#1324](https://github.com/TheHive-Project/TheHive/issues/1324) -- [Bug] Assignee List in Case and Tasks is no longer sorted Alphabetical [\#1327](https://github.com/TheHive-Project/TheHive/issues/1327) -- [Bug] Sorting in Observables of a case does not work [\#1328](https://github.com/TheHive-Project/TheHive/issues/1328) -- [Bug] Read-only has options to edit task-logs [\#1334](https://github.com/TheHive-Project/TheHive/issues/1334) -- [Bug] Adding a custom-field on an open case requires a reload, otherwise field is not visible [\#1336](https://github.com/TheHive-Project/TheHive/issues/1336) -- [Bug] severity change when create new case don't work [\#1338](https://github.com/TheHive-Project/TheHive/issues/1338) -- [Bug] The filter operator "_child" is missing [\#1344](https://github.com/TheHive-Project/TheHive/issues/1344) -- [Bug] Webhook compatibility issues on custom-fields [\#1345](https://github.com/TheHive-Project/TheHive/issues/1345) -- [Bug] Object sent to responder doesn't contain parent [\#1348](https://github.com/TheHive-Project/TheHive/issues/1348) -- [Bug] Show Sharing link to all users [\#1351](https://github.com/TheHive-Project/TheHive/issues/1351) -- [Bug] Unable to create case or alert using integer custom field [\#1356](https://github.com/TheHive-Project/TheHive/issues/1356) -- [Bug] Get observables of a case using API not working [\#1357](https://github.com/TheHive-Project/TheHive/issues/1357) -- [Bug] OAuth2 authentication doesn't redirect to home page on success [\#1360](https://github.com/TheHive-Project/TheHive/issues/1360) -- [Bug] Confusion on same alert on different organisations [\#1361](https://github.com/TheHive-Project/TheHive/issues/1361) -- [Bug] Search link to observable does not work [\#1365](https://github.com/TheHive-Project/TheHive/issues/1365) -- [Bug] Unable to vienw analysis report from observable list [\#1366](https://github.com/TheHive-Project/TheHive/issues/1366) -- [Bug] MISP export succeeds but show an error message [\#1367](https://github.com/TheHive-Project/TheHive/issues/1367) -- [Bug] rc3 migration script failure [\#1369](https://github.com/TheHive-Project/TheHive/issues/1369) -- [Bug] set HTTP redirect correctly when behind a reverse proxy [\#1370](https://github.com/TheHive-Project/TheHive/issues/1370) - -## [4.0.0-RC2](https://github.com/TheHive-Project/TheHive/milestone/54) (2020-05-07) - -**Implemented enhancements:** - -- Custom severity levels for alerts and cases [\#363](https://github.com/TheHive-Project/TheHive/issues/363) -- A (received) Shared Case is displayed as sender/owner [\#1245](https://github.com/TheHive-Project/TheHive/issues/1245) -- FR: Alignment of case custom-fields (metrics) [\#1246](https://github.com/TheHive-Project/TheHive/issues/1246) -- Add information about the age of a Case [\#1257](https://github.com/TheHive-Project/TheHive/issues/1257) -- Providing output details for Responders [\#1293](https://github.com/TheHive-Project/TheHive/issues/1293) -- Add support to multi-factor authentication [\#1303](https://github.com/TheHive-Project/TheHive/issues/1303) -- Add support to webhooks [\#1306](https://github.com/TheHive-Project/TheHive/issues/1306) - -**Closed issues:** - -- [Bug] Attachment stored in thehive but not in configured file-storage [\#1244](https://github.com/TheHive-Project/TheHive/issues/1244) - -**Fixed bugs:** - -- [Bug] TH doesn't find cases related to an alert's artifacts [\#1236](https://github.com/TheHive-Project/TheHive/issues/1236) -- [Bug] Creation of multiple user with same login within same org [\#1237](https://github.com/TheHive-Project/TheHive/issues/1237) -- Date is now a required attribute for generating an Alert [\#1238](https://github.com/TheHive-Project/TheHive/issues/1238) -- [Bug] Case Template default values can't be set during template creation [\#1241](https://github.com/TheHive-Project/TheHive/issues/1241) -- SearchSrv.NotFoundError [\#1242](https://github.com/TheHive-Project/TheHive/issues/1242) -- Assignee is not changeable [\#1243](https://github.com/TheHive-Project/TheHive/issues/1243) -- [Bug] In TheHive, a user is a member of one or more organisations. One user has a profile for each organisation and can have different profiles for different organisations. [\#1247](https://github.com/TheHive-Project/TheHive/issues/1247) -- [Bug] RPM package does not create secret.conf file [\#1248](https://github.com/TheHive-Project/TheHive/issues/1248) -- [Bug] Unable to save new or imported dashboards in 4.0-RC1 [\#1250](https://github.com/TheHive-Project/TheHive/issues/1250) -- [Bug] Header Variable authentication does not work [\#1251](https://github.com/TheHive-Project/TheHive/issues/1251) -- Filtering by custom fields returns no results [\#1252](https://github.com/TheHive-Project/TheHive/issues/1252) -- Cannot Deleted user - Error "OrgUserCtrl: org.thp.thehive.models.User not found" [\#1253](https://github.com/TheHive-Project/TheHive/issues/1253) -- [Bug] Error while importing Alert in TH4 [\#1255](https://github.com/TheHive-Project/TheHive/issues/1255) -- [Bug] Cortex errors [\#1270](https://github.com/TheHive-Project/TheHive/issues/1270) -- [Bug] error when closing a reopened case [\#1271](https://github.com/TheHive-Project/TheHive/issues/1271) -- [Bug] Unable to rename/update case template Name field [\#1275](https://github.com/TheHive-Project/TheHive/issues/1275) -- [Bug] Wrong dataType sent to Cortex (responders) [\#1279](https://github.com/TheHive-Project/TheHive/issues/1279) -- [Bug] Changing task name removes other tasks [\#1281](https://github.com/TheHive-Project/TheHive/issues/1281) -- [Bug] Disable deleting a share with owner = true [\#1283](https://github.com/TheHive-Project/TheHive/issues/1283) -- [Bug] Responder actions not displayed in Case, Task and Observable pages [\#1300](https://github.com/TheHive-Project/TheHive/issues/1300) -- [Bug] Custom field should be readonly [\#1307](https://github.com/TheHive-Project/TheHive/issues/1307) -- [Bug] Unable to display long analyzer report from observables list [\#1309](https://github.com/TheHive-Project/TheHive/issues/1309) - ## [3.4.2](https://github.com/TheHive-Project/TheHive/milestone/57) (2020-04-25) **Implemented enhancements:** diff --git a/build.sbt b/build.sbt index c97fc2ebac..992f41566f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ import Common._ import Dependencies._ +import org.thp.ghcl.Milestone lazy val thehiveBackend = (project in file("thehive-backend")) .enablePlugins(PlayScala) @@ -106,6 +107,8 @@ rpmReleaseFile := { rpmFile } +milestoneFilter := ((milestone: Milestone) ⇒ milestone.title.head < '4') + bintrayOrganization := Some("thehive-project") // Front-end // From 6be318efb0ead1fd3ad588d8c735ea19a1657ba3 Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 18:01:25 +0200 Subject: [PATCH 13/14] Fix package version --- debian.sbt | 15 ++++++++------- docker.sbt | 11 ++++++----- project/Common.scala | 39 +++++++++++++++++++++++---------------- rpm.sbt | 37 +++++++++++++++++++++---------------- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/debian.sbt b/debian.sbt index 2f08116b02..7a40c44ea7 100644 --- a/debian.sbt +++ b/debian.sbt @@ -1,12 +1,13 @@ -import Common.{stableVersion, snapshotVersion, betaVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} -linuxPackageMappings in Debian += packageMapping(file("LICENSE") -> "/usr/share/doc/thehive/copyright").withPerms("644") +linuxPackageMappings in Debian += packageMapping(file("LICENSE") → "/usr/share/doc/thehive/copyright").withPerms("644") version in Debian := { version.value match { - case stableVersion(_, _) => version.value - case betaVersion(v1, v2) => v1 + "-0.1RC" + v2 - case snapshotVersion(_, _) => version.value + "-SNAPSHOT" - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(_, _) ⇒ version.value + case betaVersion(v1, v2, v3) ⇒ v1 + "-0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(v1, v2)) ⇒ v1 + "-" + v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(v1, v2, v3)) ⇒ v1 + "-0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } debianPackageRecommends := Seq("elasticsearch") @@ -16,4 +17,4 @@ maintainerScripts in Debian := maintainerScriptsFromDirectory( Seq(DebianConstants.Postinst, DebianConstants.Prerm, DebianConstants.Postrm) ) linuxEtcDefaultTemplate in Debian := (baseDirectory.value / "package" / "etc_default_thehive").asURL -linuxMakeStartScript in Debian := None \ No newline at end of file +linuxMakeStartScript in Debian := None diff --git a/docker.sbt b/docker.sbt index e96488b4cf..4619e82c53 100644 --- a/docker.sbt +++ b/docker.sbt @@ -1,12 +1,13 @@ -import Common.{betaVersion, snapshotVersion, stableVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} import com.typesafe.sbt.packager.docker.{Cmd, ExecCmd} version in Docker := { version.value match { - case stableVersion(_, _) ⇒ version.value - case betaVersion(v1, v2) ⇒ v1 + "-0.1RC" + v2 - case snapshotVersion(_, _) ⇒ version.value + "-SNAPSHOT" - case _ ⇒ sys.error("Invalid version: " + version.value) + case stableVersion(_, _) ⇒ version.value + case betaVersion(v1, v2, v3) ⇒ v1 + "-0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(v1, v2)) ⇒ v1 + "-" + v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(v1, v2, v3)) ⇒ v1 + "-0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } defaultLinuxInstallLocation in Docker := "/opt/thehive" diff --git a/project/Common.scala b/project/Common.scala index a382ef5ee9..afa8373da3 100644 --- a/project/Common.scala +++ b/project/Common.scala @@ -15,15 +15,15 @@ object Common { scalaVersion := Dependencies.scalaVersion, scalacOptions ++= Seq( "-deprecation", // Emit warning and location for usages of deprecated APIs. - "-feature", // Emit warning and location for usages of features that should be imported explicitly. - "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-feature", // Emit warning and location for usages of features that should be imported explicitly. + "-unchecked", // Enable additional warnings where generated code depends on assumptions. //"-Xfatal-warnings", // Fail the compilation if there are any warnings. - "-Xlint", // Enable recommended additional warnings. - "-Ywarn-adapted-args", // Warn if an argument list is modified to match the receiver. - "-Ywarn-dead-code", // Warn when dead code is identified. - "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. + "-Xlint", // Enable recommended additional warnings. + "-Ywarn-adapted-args", // Warn if an argument list is modified to match the receiver. + "-Ywarn-dead-code", // Warn when dead code is identified. + "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. "-Ywarn-nullary-override", // Warn when non-nullary overrides nullary, e.g. def foo() over def foo. - "-Ywarn-numeric-widen" // Warn when numerics are widened. + "-Ywarn-numeric-widen" // Warn when numerics are widened. ), scalacOptions in Test ~= { options ⇒ options filterNot (_ == "-Ywarn-dead-code") // Allow dead code in tests (to support using mockito). @@ -31,21 +31,28 @@ object Common { parallelExecution in Test := false, fork in Test := true, javaOptions += "-Xmx1G", - // Redirect logs from ElasticSearch (which uses log4j2) to slf4j libraryDependencies += "org.apache.logging.log4j" % "log4j-to-slf4j" % "2.9.1", excludeDependencies += "org.apache.logging.log4j" % "log4j-core" ) val stableVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-(\\d+)".r - val betaVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-[Rr][Cc](\\d+)".r + val betaVersion: Regex = "(\\d+\\.\\d+\\.\\d+)-[Rr][Cc](\\d+)-(\\d+)".r + object snapshotVersion { - def unapplySeq(version: String): Option[List[String]] = { - if (version.endsWith("-SNAPSHOT")) { - val v = version.dropRight(9) - stableVersion.unapplySeq(v) orElse betaVersion.unapplySeq(v) - } + + def unapply(version: String): Option[String] = + if (version.endsWith("-SNAPSHOT")) Some(version.dropRight(9)) else None - } } -} \ No newline at end of file + + def versionUsage(version: String): Nothing = + sys.error( + s"Invalid version: $version\n" + + "The accepted formats for version are:\n" + + " - 1.2.3-4\n" + + " - 1.2.3-RC4-5\n" + + " - 1.2.3-4-SNAPSHOT\n" + + " - 1.2.3-RC4-5-SNAPSHOT" + ) +} diff --git a/rpm.sbt b/rpm.sbt index d65451081f..fa852a9bd6 100644 --- a/rpm.sbt +++ b/rpm.sbt @@ -1,21 +1,24 @@ -import Common.{stableVersion, snapshotVersion, betaVersion} +import Common.{betaVersion, snapshotVersion, stableVersion, versionUsage} version in Rpm := { version.value match { - case stableVersion(v1, v2) => v1 - case betaVersion(v1, v2) => v1 - case snapshotVersion(v1, v2) => v1 - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(v1, _) ⇒ v1 + case betaVersion(v1, _, _) ⇒ v1 + case snapshotVersion(stableVersion(v1, _)) ⇒ v1 + case snapshotVersion(betaVersion(v1, _, _)) ⇒ v1 + case _ ⇒ versionUsage(version.value) } } rpmRelease := { version.value match { - case stableVersion(_, v2) => v2 - case betaVersion(v1, v2) => "0.1RC" + v2 - case snapshotVersion(v1, v2) => v2 + "-SNAPSHOT" - case _ => sys.error("Invalid version: " + version.value) + case stableVersion(_, v2) ⇒ v2 + case betaVersion(_, v2, v3) ⇒ "0." + v3 + "RC" + v2 + case snapshotVersion(stableVersion(_, v2)) ⇒ v2 + "-SNAPSHOT" + case snapshotVersion(betaVersion(_, v2, v3)) ⇒ "0." + v3 + "RC" + v2 + "-SNAPSHOT" + case _ ⇒ versionUsage(version.value) } } + rpmVendor := organizationName.value rpmUrl := organizationHomepage.value.map(_.toString) rpmLicense := Some("AGPL") @@ -35,12 +38,14 @@ linuxPackageMappings in Rpm := configWithNoReplace((linuxPackageMappings in Rpm) packageBin in Rpm := { import scala.sys.process._ val rpmFile = (packageBin in Rpm).value - Process("rpm" :: - "--define" :: "_gpg_name TheHive Project" :: - "--define" :: "_signature gpg" :: - "--define" :: "__gpg_check_password_cmd /bin/true" :: - "--define" :: "__gpg_sign_cmd %{__gpg} gpg --batch --no-verbose --no-armor --use-agent --no-secmem-warning -u \"%{_gpg_name}\" -sbo %{__signature_filename} %{__plaintext_filename}" :: - "--addsign" :: rpmFile.toString :: - Nil).!! + Process( + "rpm" :: + "--define" :: "_gpg_name TheHive Project" :: + "--define" :: "_signature gpg" :: + "--define" :: "__gpg_check_password_cmd /bin/true" :: + "--define" :: "__gpg_sign_cmd %{__gpg} gpg --batch --no-verbose --no-armor --use-agent --no-secmem-warning -u \"%{_gpg_name}\" -sbo %{__signature_filename} %{__plaintext_filename}" :: + "--addsign" :: rpmFile.toString :: + Nil + ).!! rpmFile } From c51265ddc1a15eb6665cbcb0c3962d2906a665d4 Mon Sep 17 00:00:00 2001 From: To-om Date: Wed, 12 Aug 2020 18:40:45 +0200 Subject: [PATCH 14/14] Fix drone configuration --- .drone.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index eec20c7f23..1d73a49aa7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -20,8 +20,7 @@ steps: - name: run-tests image: thehiveproject/drone-scala-node commands: - - . ~/.nvm/nvm.sh - - sbt -Duser.home=$PWD test + - sbt -Duser.home=$PWD test:compile test # Build packages - name: build-packages @@ -55,9 +54,11 @@ steps: image: drillster/drone-volume-cache settings: rebuild: true + backend: "filesystem" mount: - .sbt - .ivy2 + - .cache - ui/node_modules - ui/bower_components volumes: [{name: cache, path: /cache}] @@ -66,9 +67,9 @@ steps: - name: send packages image: appleboy/drone-scp settings: - host: {from_secret: scp_host} - username: {from_secret: scp_user} - key: {from_secret: scp_key} + host: {from_secret: package_host} + username: {from_secret: package_user} + key: {from_secret: package_key} target: {from_secret: incoming_path} source: - target/thehive*.deb @@ -82,9 +83,9 @@ steps: - name: publish packages image: appleboy/drone-ssh settings: - host: {from_secret: scp_host} - user: {from_secret: scp_user} - key: {from_secret: scp_key} + host: {from_secret: package_host} + user: {from_secret: package_user} + key: {from_secret: package_key} publish_script: {from_secret: publish_script} commands: - PLUGIN_SCRIPT="bash $PLUGIN_PUBLISH_SCRIPT thehive $(cat thehive-version.txt)" /bin/drone-ssh @@ -109,7 +110,7 @@ steps: settings: context: target/docker/stage dockerfile: target/docker/stage/Dockerfile - registry: {from_secret: harbor_server} + registry: {from_secret: harbor_registry} repo: {from_secret: harbor_repo} username: {from_secret: harbor_username} password: {from_secret: harbor_password}