Skip to content

Commit

Permalink
#1670 Importing exisiting taxonomies updates them
Browse files Browse the repository at this point in the history
  • Loading branch information
rriclet committed Feb 10, 2021
1 parent cfa0ca0 commit bb1fa57
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 33 deletions.
10 changes: 8 additions & 2 deletions thehive/app/org/thp/thehive/controllers/v1/TaxonomyCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class TaxonomyCtrl @Inject() (
case Failure(e) =>
Json.obj("namespace" -> taxo.namespace, "status" -> "Failure", "message" -> e.getMessage)
case Success(t) =>
Json.obj("namespace" -> t.namespace, "status" -> "Success", "tagsImported" -> t.tags.size)
Json.obj("namespace" -> t.namespace, "status" -> "Success", "numberOfTags" -> t.tags.size)
}
array :+ res
}
Expand Down Expand Up @@ -126,8 +126,14 @@ class TaxonomyCtrl @Inject() (
else if (inputTaxo.namespace.startsWith("_freetags"))
Failure(BadRequestError(s"Namespace _freetags is restricted for TheHive"))
else if (taxonomySrv.startTraversal.alreadyImported(inputTaxo.namespace))
Failure(BadRequestError(s"A taxonomy with namespace '${inputTaxo.namespace}' already exists in this organisation"))
// Update the taxonomy, update exisiting tags & create others
for {
_ <- allTags.toTry(t => taxonomySrv.updateOrCreateTag(inputTaxo.namespace, t))
taxonomy <- taxonomySrv.get(EntityIdOrName(inputTaxo.namespace)).getOrFail("Taxonomy")
updatedTaxo <- taxonomySrv.update(taxonomy, inputTaxo.toTaxonomy)
} yield updatedTaxo
else
// Create the taxonomy and all its tags
for {
tagsEntities <- allTags.toTry(t => tagSrv.create(t))
richTaxonomy <- taxonomySrv.create(inputTaxo.toTaxonomy, tagsEntities)
Expand Down
2 changes: 1 addition & 1 deletion thehive/app/org/thp/thehive/services/PatternSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class PatternSrv @Inject() (
def update(
pattern: Pattern with Entity,
input: Pattern
)(implicit graph: Graph, authContext: AuthContext): Try[Pattern with Entity] =
)(implicit graph: Graph): Try[Pattern with Entity] =
for {
updatedPattern <- get(pattern)
.when(pattern.patternId != input.patternId)(_.update(_.patternId, input.patternId))
Expand Down
17 changes: 16 additions & 1 deletion thehive/app/org/thp/thehive/services/TagSrv.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package org.thp.thehive.services

import akka.actor.ActorRef
import javax.inject.{Inject, Named, Singleton}
import org.apache.tinkerpop.gremlin.structure.{Graph, Vertex}
import org.thp.scalligraph.auth.AuthContext
import org.thp.scalligraph.models.{Database, Entity}
import org.thp.scalligraph.services.config.{ApplicationConfig, ConfigItem}
import org.thp.scalligraph.services.{IntegrityCheckOps, VertexSrv}
import org.thp.scalligraph.traversal.TraversalOps._
import org.thp.scalligraph.traversal.{Converter, Traversal}
import org.thp.scalligraph.utils.FunctionalCondition.When
import org.thp.thehive.models.{AlertTag, CaseTag, ObservableTag, Tag}
import org.thp.thehive.services.TagOps._

import javax.inject.{Inject, Named, Singleton}
import scala.util.{Success, Try}

@Singleton
Expand Down Expand Up @@ -46,6 +47,9 @@ class TagSrv @Inject() (appConfig: ApplicationConfig, @Named("integrity-check-ac
}
}

def getOrCreate(tag: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] =
getTag(tag).getOrFail("Tag").recoverWith { case _ => create(tag) }

override def createEntity(e: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = {
integrityCheckActor ! EntityAdded("Tag")
super.createEntity(e)
Expand All @@ -54,6 +58,17 @@ class TagSrv @Inject() (appConfig: ApplicationConfig, @Named("integrity-check-ac
def create(tag: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = createEntity(tag)

override def exists(e: Tag)(implicit graph: Graph): Boolean = startTraversal.getByName(e.namespace, e.predicate, e.value).exists

def update(
tag: Tag with Entity,
input: Tag
)(implicit graph: Graph): Try[Tag with Entity] =
for {
updatedTag <- get(tag)
.when(tag.description != input.description)(_.update(_.description, input.description))
.when(tag.colour != input.colour)(_.update(_.colour, input.colour))
.getOrFail("Tag")
} yield updatedTag
}

object TagOps {
Expand Down
31 changes: 30 additions & 1 deletion thehive/app/org/thp/thehive/services/TaxonomySrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ import org.thp.scalligraph.services.{EdgeSrv, VertexSrv}
import org.thp.scalligraph.traversal.Converter.Identity
import org.thp.scalligraph.traversal.TraversalOps.TraversalOpsDefs
import org.thp.scalligraph.traversal.{Converter, Traversal}
import org.thp.scalligraph.utils.FunctionalCondition.When
import org.thp.scalligraph.{BadRequestError, EntityId, EntityIdOrName, RichSeq}
import org.thp.thehive.models._
import org.thp.thehive.services.OrganisationOps._
import org.thp.thehive.services.TaxonomyOps._
import org.thp.thehive.services.TagOps._

import scala.util.{Failure, Success, Try}

@Singleton
class TaxonomySrv @Inject() (
organisationSrv: OrganisationSrv
organisationSrv: OrganisationSrv,
tagSrv: TagSrv
)(implicit @Named("with-thehive-schema") db: Database)
extends VertexSrv[Taxonomy] {

Expand All @@ -46,6 +49,30 @@ class TaxonomySrv @Inject() (
override def getByName(name: String)(implicit graph: Graph): Traversal.V[Taxonomy] =
Try(startTraversal.getByNamespace(name)).getOrElse(startTraversal.limit(0))

def update(taxonomy: Taxonomy with Entity, input: Taxonomy)(implicit graph: Graph): Try[RichTaxonomy] =
for {
updatedTaxonomy <-
get(taxonomy)
.when(taxonomy.namespace != input.namespace)(_.update(_.namespace, input.namespace))
.when(taxonomy.description != input.description)(_.update(_.description, input.description))
.when(taxonomy.version != input.version)(_.update(_.version, input.version))
.richTaxonomy
.getOrFail("Taxonomy")
} yield updatedTaxonomy

def updateOrCreateTag(namespace: String, t: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] =
if (getByName(namespace).doesTagExists(t))
for {
tag <- tagSrv.getTag(t).getOrFail("Tag")
updatedTag <- tagSrv.update(tag, t)
} yield updatedTag
else
for {
tag <- tagSrv.create(t)
taxo <- getByName(namespace).getOrFail("Taxonomy")
_ <- taxonomyTagSrv.create(TaxonomyTag(), taxo, tag)
} yield tag

def activate(taxonomyId: EntityIdOrName)(implicit graph: Graph, authContext: AuthContext): Try[Unit] =
for {
taxo <- get(taxonomyId).getOrFail("Taxonomy")
Expand Down Expand Up @@ -97,6 +124,8 @@ object TaxonomyOps {

def tags: Traversal.V[Tag] = traversal.out[TaxonomyTag].v[Tag]

def doesTagExists(tag: Tag): Boolean = traversal.tags.getTag(tag).exists

def richTaxonomy: Traversal[RichTaxonomy, JMap[String, Any], Converter[RichTaxonomy, JMap[String, Any]]] =
traversal
.project(
Expand Down
62 changes: 34 additions & 28 deletions thehive/test/org/thp/thehive/controllers/v1/TaxonomyCtrlTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ case class TestTaxonomy(
namespace: String,
description: String,
version: Int,
tags: List[OutputTag]
tags: Set[OutputTag]
)

object TestTaxonomy {
Expand All @@ -22,7 +22,7 @@ object TestTaxonomy {
outputTaxonomy.namespace,
outputTaxonomy.description,
outputTaxonomy.version,
outputTaxonomy.tags.toList
outputTaxonomy.tags.toSet
)
}

Expand Down Expand Up @@ -50,6 +50,15 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
)
)

val updateTaxo = InputTaxonomy(
"taxonomy1",
"Updated The taxonomy 1",
2,
None,
List(InputPredicate("pred1", None, None, None, None)),
List(InputValue("pred1", List(InputEntry("value2", None, Some("#fba800"), None, None))))
)

"create a valid taxonomy" in testApp { app =>
val request = FakeRequest("POST", "/api/v1/taxonomy")
.withJsonBody(Json.toJson(inputTaxo))
Expand All @@ -64,7 +73,7 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
"test-taxo",
"A test taxonomy",
1,
List(
Set(
OutputTag("test-taxo", "pred1", Some("entry1"), None, "#ffa800"),
OutputTag("test-taxo", "pred2", Some("entry2"), None, "#00ad1c"),
OutputTag("test-taxo", "pred2", Some("entry21"), None, "#00ad1c")
Expand All @@ -82,19 +91,6 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
(contentAsJson(result) \ "type").as[String] must beEqualTo("AuthorizationError")
}

"return error if namespace is present in database" in testApp { app =>
val alreadyInDatabase = inputTaxo.copy(namespace = "taxonomy1")

val request = FakeRequest("POST", "/api/v1/taxonomy")
.withJsonBody(Json.toJson(alreadyInDatabase))
.withHeaders("user" -> "[email protected]")

val result = app[TaxonomyCtrl].create(request)
status(result) must beEqualTo(400).updateMessage(s => s"$s\n${contentAsString(result)}")
(contentAsJson(result) \ "type").as[String] must beEqualTo("BadRequest")
(contentAsJson(result) \ "message").as[String] must contain("already exists")
}

"return error if namespace is empty" in testApp { app =>
val emptyNamespace = inputTaxo.copy(namespace = "")

Expand All @@ -120,7 +116,7 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
"taxonomy1",
"The taxonomy 1",
1,
List(OutputTag("taxonomy1", "pred1", Some("value1"), None, "#00f300"))
Set(OutputTag("taxonomy1", "pred1", Some("value1"), None, "#00f300"))
)
}

Expand Down Expand Up @@ -169,17 +165,6 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
contentAsJson(result).as[JsArray].value.size must beEqualTo(1)
}

"return error if zip file contains an already present taxonomy" in testApp { app =>
val request = FakeRequest("POST", "/api/v1/taxonomy/import-zip")
.withHeaders("user" -> "[email protected]")
.withBody(AnyContentAsMultipartFormData(multipartZipFile("machinetag-present.zip")))

val result = app[TaxonomyCtrl].importZip(request)
status(result) must beEqualTo(201).updateMessage(s => s"$s\n${contentAsString(result)}")
contentAsString(result) must contain("Failure")
contentAsJson(result).as[JsArray].value.size must beEqualTo(2)
}

"return error if zip file contains a bad formatted taxonomy" in testApp { app =>
val request = FakeRequest("POST", "/api/v1/taxonomy/import-zip")
.withHeaders("user" -> "[email protected]")
Expand All @@ -191,6 +176,27 @@ class TaxonomyCtrlTest extends PlaySpecification with TestAppBuilder {
(contentAsJson(result) \ "message").as[String] must contain("formatting")
}

"update a taxonomies and their tags" in testApp { app =>
val request = FakeRequest("POST", "/api/v1/taxonomy")
.withJsonBody(Json.toJson(updateTaxo))
.withHeaders("user" -> "[email protected]")

val result = app[TaxonomyCtrl].create(request)
status(result) must beEqualTo(201).updateMessage(s => s"$s\n${contentAsString(result)}")

val resultTaxo = contentAsJson(result).as[OutputTaxonomy]

TestTaxonomy(resultTaxo) must_=== TestTaxonomy(
"taxonomy1",
"Updated The taxonomy 1",
2,
Set(
OutputTag("taxonomy1", "pred1", Some("value2"), None, "#fba800"),
OutputTag("taxonomy1", "pred1", Some("value1"), None, "#00f300")
)
)
}

"activate a taxonomy" in testApp { app =>
val request1 = FakeRequest("GET", "/api/v1/taxonomy/taxonomy2")
.withHeaders("user" -> "[email protected]")
Expand Down

0 comments on commit bb1fa57

Please sign in to comment.