Skip to content

Commit 0cd94f9

Browse files
committed
Merge branch 'hotfix/4.1.18'
2 parents b7b196c + 1528d60 commit 0cd94f9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1327
-1079
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Change Log
22

3+
## [4.1.18](https://github.com/TheHive-Project/TheHive/milestone/88) (2022-02-07)
4+
5+
**Implemented enhancements:**
6+
7+
- [Enhancement] Integrity check improvement [\#2334](https://github.com/TheHive-Project/TheHive/issues/2334)
8+
- [Enhancement] Improve migration tool [\#2335](https://github.com/TheHive-Project/TheHive/issues/2335)
9+
10+
**Fixed bugs:**
11+
12+
- [Bug] "Character 8211 cannot match AsciiSet because it is out of range" error when downloading a report [\#1534](https://github.com/TheHive-Project/TheHive/issues/1534)
13+
- [Bug] Can add a "space" as observable [\#2324](https://github.com/TheHive-Project/TheHive/issues/2324)
14+
- [Bug]- Migration from Hive 3.4.4 to Hive 4.1.17 not working [\#2331](https://github.com/TheHive-Project/TheHive/issues/2331)
15+
- [Bug] Duplicated entities after "db.janusgraph.forceDropAndRebuildIndex: true" with Elasticsearch index [\#2333](https://github.com/TheHive-Project/TheHive/issues/2333)
16+
- [Bug] Query with parendId filter doesn't work (v0) [\#2336](https://github.com/TheHive-Project/TheHive/issues/2336)
17+
318
## [4.1.17](https://github.com/TheHive-Project/TheHive/milestone/87) (2022-01-24)
419

520
**Implemented enhancements:**

build.sbt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Dependencies._
22
import com.typesafe.sbt.packager.Keys.bashScriptDefines
33
import org.thp.ghcl.Milestone
44

5-
val thehiveVersion = "4.1.17-1"
5+
val thehiveVersion = "4.1.18-1"
66
val scala212 = "2.12.13"
77
val scala213 = "2.13.1"
88
val supportedScalaVersions = List(scala212, scala213)
@@ -165,7 +165,8 @@ lazy val thehiveCore = (project in file("thehive"))
165165
pbkdf2,
166166
commonCodec,
167167
scalaGuice,
168-
reflections
168+
reflections,
169+
quartzScheduler
169170
)
170171
)
171172

cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/controllers/v0/CortexQueryExecutor.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class CortexQueryExecutor @Inject() (
5050

5151
override val customFilterQuery: FilterQuery = FilterQuery(publicProperties) { (tpe, globalParser) =>
5252
FieldsParser("parentChildFilter") {
53-
case (_, FObjOne("_parent", ParentIdFilter(_, parentId))) if parentTypes.isDefinedAt(tpe) =>
53+
case (_, FObjOne("_parent", ParentIdFilter(parentId, _))) if parentTypes.isDefinedAt(tpe) =>
5454
Good(new CortexParentIdInputFilter(parentId))
5555
case (path, FObjOne("_parent", ParentQueryFilter(_, parentFilterField))) if parentTypes.isDefinedAt(tpe) =>
5656
globalParser(parentTypes(tpe)).apply(path, parentFilterField).map(query => new CortexParentQueryInputFilter(query))

frontend/bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thehive",
3-
"version": "4.1.17-1",
3+
"version": "4.1.18-1",
44
"license": "AGPL-3.0",
55
"dependencies": {
66
"jquery": "^3.4.1",

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thehive",
3-
"version": "4.1.17-1",
3+
"version": "4.1.18-1",
44
"license": "AGPL-3.0",
55
"repository": {
66
"type": "git",

migration/src/main/resources/reference.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ output {
4343
caseNumberShift: 0
4444
resume: false
4545
removeData: false
46+
integrityCheck.enabled: false
4647
db {
4748
provider: janusgraph
4849
janusgraph {

migration/src/main/scala/org/thp/thehive/cloner/IntegrityCheckApp.scala

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import javax.inject.Inject
2020
import scala.collection.JavaConverters._
2121
import scala.collection.immutable
2222
import scala.concurrent.ExecutionContext
23+
import scala.concurrent.duration.DurationInt
2324
import scala.util.Success
2425

2526
trait IntegrityCheckApp {
@@ -43,22 +44,22 @@ trait IntegrityCheckApp {
4344
bindActor[DummyActor]("integrity-check-actor")
4445
bind[ActorRef[CaseNumberActor.Request]].toProvider[CaseNumberActorProvider]
4546

46-
val integrityCheckOpsBindings = ScalaMultibinder.newSetBinder[GenIntegrityCheckOps](binder)
47-
integrityCheckOpsBindings.addBinding.to[AlertIntegrityCheckOps]
48-
integrityCheckOpsBindings.addBinding.to[CaseIntegrityCheckOps]
49-
integrityCheckOpsBindings.addBinding.to[CaseTemplateIntegrityCheckOps]
50-
integrityCheckOpsBindings.addBinding.to[CustomFieldIntegrityCheckOps]
51-
integrityCheckOpsBindings.addBinding.to[DataIntegrityCheckOps]
52-
integrityCheckOpsBindings.addBinding.to[ImpactStatusIntegrityCheckOps]
53-
integrityCheckOpsBindings.addBinding.to[LogIntegrityCheckOps]
54-
integrityCheckOpsBindings.addBinding.to[ObservableIntegrityCheckOps]
55-
integrityCheckOpsBindings.addBinding.to[ObservableTypeIntegrityCheckOps]
56-
integrityCheckOpsBindings.addBinding.to[OrganisationIntegrityCheckOps]
57-
integrityCheckOpsBindings.addBinding.to[ProfileIntegrityCheckOps]
58-
integrityCheckOpsBindings.addBinding.to[ResolutionStatusIntegrityCheckOps]
59-
integrityCheckOpsBindings.addBinding.to[TagIntegrityCheckOps]
60-
integrityCheckOpsBindings.addBinding.to[TaskIntegrityCheckOps]
61-
integrityCheckOpsBindings.addBinding.to[UserIntegrityCheckOps]
47+
val integrityCheckOpsBindings = ScalaMultibinder.newSetBinder[IntegrityCheck](binder)
48+
integrityCheckOpsBindings.addBinding.to[AlertIntegrityCheck]
49+
integrityCheckOpsBindings.addBinding.to[CaseIntegrityCheck]
50+
integrityCheckOpsBindings.addBinding.to[CaseTemplateIntegrityCheck]
51+
integrityCheckOpsBindings.addBinding.to[CustomFieldIntegrityCheck]
52+
integrityCheckOpsBindings.addBinding.to[DataIntegrityCheck]
53+
integrityCheckOpsBindings.addBinding.to[ImpactStatusIntegrityCheck]
54+
integrityCheckOpsBindings.addBinding.to[LogIntegrityCheck]
55+
integrityCheckOpsBindings.addBinding.to[ObservableIntegrityCheck]
56+
integrityCheckOpsBindings.addBinding.to[ObservableTypeIntegrityCheck]
57+
integrityCheckOpsBindings.addBinding.to[OrganisationIntegrityCheck]
58+
integrityCheckOpsBindings.addBinding.to[ProfileIntegrityCheck]
59+
integrityCheckOpsBindings.addBinding.to[ResolutionStatusIntegrityCheck]
60+
integrityCheckOpsBindings.addBinding.to[TagIntegrityCheck]
61+
integrityCheckOpsBindings.addBinding.to[TaskIntegrityCheck]
62+
integrityCheckOpsBindings.addBinding.to[UserIntegrityCheck]
6263

6364
bind[Environment].toInstance(Environment.simple())
6465
bind[ApplicationLifecycle].to[DefaultApplicationLifecycle]
@@ -77,25 +78,29 @@ trait IntegrityCheckApp {
7778
buildApp(configuration, db).getInstance(classOf[IntegrityChecks]).runChecks()
7879
}
7980

80-
class IntegrityChecks @Inject() (db: Database, checks: immutable.Set[GenIntegrityCheckOps], userSrv: UserDB) extends MapMerger {
81+
class IntegrityChecks @Inject() (db: Database, checks: immutable.Set[IntegrityCheck], userSrv: UserDB) extends MapMerger {
8182
def runChecks(): Unit = {
8283
implicit val authContext: AuthContext = userSrv.getSystemAuthContext
8384
checks.foreach { c =>
84-
db.tryTransaction { implicit graph =>
85-
println(s"Running check on ${c.name} ...")
86-
c.initialCheck()
87-
val stats = c.duplicationCheck() <+> c.globalCheck()
88-
val statsStr = stats
89-
.collect {
90-
case (k, v) if v != 0 => s"$k:$v"
91-
}
92-
.mkString(" ")
93-
if (statsStr.isEmpty)
94-
println(" no change needed")
95-
else
96-
println(s" $statsStr")
97-
Success(())
85+
println(s"Running check on ${c.name} ...")
86+
val desupStats = c match {
87+
case dc: DedupCheck[_] => dc.dedup(KillSwitch.alwaysOn)
88+
case _ => Map.empty[String, Long]
9889
}
90+
val globalStats = c match {
91+
case gc: GlobalCheck[_] => gc.runGlobalCheck(24.hours, KillSwitch.alwaysOn)
92+
case _ => Map.empty[String, Long]
93+
}
94+
val statsStr = (desupStats <+> globalStats)
95+
.collect {
96+
case (k, v) if v != 0 => s"$k:$v"
97+
}
98+
.mkString(" ")
99+
if (statsStr.isEmpty)
100+
println(" no change needed")
101+
else
102+
println(s" $statsStr")
103+
99104
}
100105
}
101106
}

migration/src/main/scala/org/thp/thehive/migration/Input.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ trait Input {
120120
def listJobObservables(caseId: String): Source[Try[(String, InputObservable)], NotUsed]
121121
def countAction(filter: Filter): Future[Long]
122122
def listActions(entityIds: Seq[String]): Source[Try[(String, InputAction)], NotUsed]
123-
def countAudit(filter: Filter): Future[Long]
123+
def countAudits(filter: Filter): Future[Long]
124124
def listAudits(entityIds: Seq[String], filter: Filter): Source[Try[(String, InputAudit)], NotUsed]
125+
def countDashboards(filter: Filter): Future[Long]
126+
def listDashboards(filter: Filter): Source[Try[InputDashboard], NotUsed]
125127
}

migration/src/main/scala/org/thp/thehive/migration/Migrate.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ object Migrate extends App with MigrationOps {
9191
opt[Int]('t', "thread-count")
9292
.text("number of threads")
9393
.action((t, c) => addConfig(c, "threadCount", t)),
94+
opt[Unit]('k', "integrity-checks")
95+
.text("run integrity checks after the migration")
96+
.action((_, c) => addConfig(c, "output.integrityCheck.enabled", true)),
9497
/* case age */
9598
opt[String]("max-case-age")
9699
.valueName("<duration>")

migration/src/main/scala/org/thp/thehive/migration/MigrationOps.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class MigrationStats() {
6868
def setTotal(v: Long): Unit = total = v
6969

7070
override def toString: String = {
71-
val totalTxt = if (total < 0) s"/${nSuccess + nFailure}" else s"/${total / 1000}"
71+
val totalTxt = if (total < 0) s"/${nSuccess + nFailure}" else s"/$total"
7272
val avg = if (global.isEmpty) "" else s" avg:${global}µs"
7373
val failureAndExistTxt = if (nFailure > 0 || nExist > 0) {
7474
val failureTxt = if (nFailure > 0) s"$nFailure failures" else ""
@@ -454,7 +454,7 @@ trait MigrationOps {
454454
input.countJobs(filter).foreach(count => migrationStats.setTotal("Job", count))
455455
input.countJobObservables(filter).foreach(count => migrationStats.setTotal("Job/Observable", count))
456456
input.countAction(filter).foreach(count => migrationStats.setTotal("Action", count))
457-
input.countAudit(filter).foreach(count => migrationStats.setTotal("Audit", count))
457+
input.countAudits(filter).foreach(count => migrationStats.setTotal("Audit", count))
458458

459459
migrationStats.stage = "Prepare database"
460460
output.startMigration().flatMap { _ =>
@@ -474,6 +474,8 @@ trait MigrationOps {
474474
migrate(output)("ObservableType", input.listObservableTypes(filter), output.createObservableTypes, output.observableTypeExists)
475475
migrationStats.stage = "Migrate case templates"
476476
migrateWholeCaseTemplates(input, output, filter)
477+
migrationStats.stage = "Migrate dashboards"
478+
migrate(output)("Dashboard", input.listDashboards(filter), output.createDashboard, output.dashboardExists)
477479
migrationStats.stage = "Migrate cases and alerts"
478480
migrateCasesAndAlerts(input, output, filter)
479481
migrationStats.stage = "Finalisation"

migration/src/main/scala/org/thp/thehive/migration/Output.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,6 @@ trait Output[TX] {
3939
def createAlertObservable(tx: TX, alertId: EntityId, inputObservable: InputObservable): Try[IdMapping]
4040
def createAction(tx: TX, objectId: EntityId, inputAction: InputAction): Try[IdMapping]
4141
def createAudit(tx: TX, contextId: EntityId, inputAudit: InputAudit): Try[Unit]
42+
def dashboardExists(tx: TX, inputDashboard: InputDashboard): Boolean
43+
def createDashboard(tx: TX, inputDashboard: InputDashboard): Try[IdMapping]
4244
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.thp.thehive.migration.dto
2+
3+
import org.thp.thehive.models.Dashboard
4+
5+
case class InputDashboard(metaData: MetaData, organisation: Option[(String, Boolean)], dashboard: Dashboard)

migration/src/main/scala/org/thp/thehive/migration/th3/Conversion.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ trait Conversion {
347347
implicit val customFieldReads: Reads[InputCustomField] = Reads[InputCustomField] { json =>
348348
for {
349349
// metaData <- json.validate[MetaData]
350-
valueJson <- (json \ "value").validate[String].map(truncateString)
350+
valueJson <- (json \ "value").validate[String]
351351
value = Json.parse(valueJson)
352352
displayName <- (value \ "name").validate[String].map(truncateString)
353353
name <- (value \ "reference").validate[String].map(truncateString)
@@ -584,4 +584,14 @@ trait Conversion {
584584
)
585585
)
586586
}
587+
implicit val dashboardReads: Reads[InputDashboard] = Reads[InputDashboard] { json =>
588+
for {
589+
metaData <- json.validate[MetaData]
590+
title <- (json \ "title").validate[String]
591+
description <- (json \ "description").validate[String]
592+
definitionString <- (json \ "definition").validate[String]
593+
definition <- Json.parse(definitionString).validate[JsObject]
594+
status <- (json \ "status").validate[String]
595+
} yield InputDashboard(metaData, if (status == "Shared") Some(mainOrganisation -> true) else None, Dashboard(title, description, definition))
596+
}
587597
}

migration/src/main/scala/org/thp/thehive/migration/th3/ElasticClient.scala

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import java.net.{URI, URLEncoder}
1919
import javax.inject.{Inject, Provider, Singleton}
2020
import scala.concurrent.duration.{Duration, DurationInt, DurationLong, FiniteDuration}
2121
import scala.concurrent.{Await, ExecutionContext, Future}
22+
import scala.util.Try
2223

2324
@Singleton
2425
class ElasticClientProvider @Inject() (
@@ -196,17 +197,22 @@ class ElasticConfig(
196197
)
197198
.status == 200
198199

199-
def isSingleType(indexName: String): Boolean = {
200-
val response = Await
201-
.result(
202-
authentication(ws.url(stripUrl(s"$esUri/$indexName")))
203-
.get(),
204-
10.seconds
205-
)
206-
if (response.status != 200)
207-
throw InternalError(s"Unexpected response from Elasticsearch: ${response.status} ${response.statusText}\n${response.body}")
208-
(response.json \ indexName \ "settings" \ "index" \ "mapping" \ "single_type").asOpt[String].fold(version.head > '6')(_.toBoolean)
209-
}
200+
def isSingleType(indexName: String): Boolean =
201+
indexName
202+
.split('_')
203+
.lastOption
204+
.flatMap(version => Try(version.toInt).toOption)
205+
.fold {
206+
val response = Await
207+
.result(
208+
authentication(ws.url(stripUrl(s"$esUri/$indexName")))
209+
.get(),
210+
10.seconds
211+
)
212+
if (response.status != 200)
213+
throw InternalError(s"Unexpected response from Elasticsearch: ${response.status} ${response.statusText}\n${response.body}")
214+
(response.json \ indexName \ "settings" \ "index" \ "mapping" \ "single_type").asOpt[String].fold(version.head > '6')(_.toBoolean)
215+
}(version => version >= 15)
210216

211217
def version: String = {
212218
val response = Await.result(authentication(ws.url(stripUrl(esUri))).get(), 10.seconds)

0 commit comments

Comments
 (0)