From a0567b5b4acfe03b5379df313a52e8260997b905 Mon Sep 17 00:00:00 2001 From: Robin Riclet Date: Thu, 18 Feb 2021 16:06:29 +0100 Subject: [PATCH] #1670 fixed tag creation issue --- .../thp/thehive/migration/th4/Output.scala | 2 +- thehive/app/org/thp/thehive/models/Tag.scala | 27 -------- .../thehive/services/OrganisationSrv.scala | 12 +++- .../app/org/thp/thehive/services/TagSrv.scala | 66 +++++++++++++------ .../thp/thehive/services/TaxonomySrv.scala | 2 +- .../test/org/thp/thehive/models/TagTest.scala | 47 ------------- .../services/CaseTemplateSrvTest.scala | 7 +- .../org/thp/thehive/services/TagSrvTest.scala | 56 ++++++++++++++++ 8 files changed, 117 insertions(+), 102 deletions(-) delete mode 100644 thehive/test/org/thp/thehive/models/TagTest.scala create mode 100644 thehive/test/org/thp/thehive/services/TagSrvTest.scala diff --git a/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala b/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala index d6575d9963..28355f72ba 100644 --- a/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala +++ b/migration/src/main/scala/org/thp/thehive/migration/th4/Output.scala @@ -284,7 +284,7 @@ class Output @Inject() ( } def getTag(tagName: String)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = - cache.getOrElseUpdate(s"tag-$tagName")(tagSrv.createEntity(Tag.fromString(tagName, tagSrv.defaultNamespace, tagSrv.defaultColour))) + cache.getOrElseUpdate(s"tag-$tagName")(tagSrv.getOrCreate(tagName)) override def organisationExists(inputOrganisation: InputOrganisation): Boolean = organisations.contains(inputOrganisation.organisation.name) diff --git a/thehive/app/org/thp/thehive/models/Tag.scala b/thehive/app/org/thp/thehive/models/Tag.scala index 96e74923dc..5c01c90769 100644 --- a/thehive/app/org/thp/thehive/models/Tag.scala +++ b/thehive/app/org/thp/thehive/models/Tag.scala @@ -2,9 +2,6 @@ package org.thp.thehive.models import org.thp.scalligraph.BuildVertexEntity import org.thp.scalligraph.models.{DefineIndex, IndexType} -import play.api.Logger - -import scala.util.matching.Regex @DefineIndex(IndexType.unique, "namespace", "predicate", "value") @BuildVertexEntity @@ -30,27 +27,3 @@ case class Tag( (if (predicate.headOption.getOrElse('_') == '_') "" else predicate) + value.fold("")(v => f"""="$v"""") // #$colour%06X } - -object Tag { - lazy val logger: Logger = Logger(getClass) - val tagColour: Regex = "(.*)(#\\p{XDigit}{6})".r - val namespacePredicateValue: Regex = "([^\".:=]+)[.:]([^\".=]+)=\"?([^\"]+)\"?".r - val namespacePredicate: Regex = "([^\".:=]+)[.:]([^\".=]+)".r - val PredicateValue: Regex = "([^\".:=]+)[=:]\"?([^\"]+)\"?".r - val predicate: Regex = "([^\".:=]+)".r - - def fromString(tagName: String, defaultNamespace: String, defaultColour: String = "#000000"): Tag = { - val (name, colour) = tagName match { - case tagColour(n, c) => n -> c - case _ => tagName -> defaultColour - } - name match { - case namespacePredicateValue(namespace, predicate, value) if value.exists(_ != '=') => - Tag(namespace.trim, predicate.trim, Some(value.trim), None, colour) - case namespacePredicate(namespace, predicate) => Tag(namespace.trim, predicate.trim, None, None, colour) - case PredicateValue(predicate, value) => Tag(defaultNamespace, predicate.trim, Some(value.trim), None, colour) - case predicate(predicate) => Tag(defaultNamespace, predicate.trim, None, None, colour) - case _ => Tag(defaultNamespace, name, None, None, colour) - } - } -} diff --git a/thehive/app/org/thp/thehive/services/OrganisationSrv.scala b/thehive/app/org/thp/thehive/services/OrganisationSrv.scala index f801eca003..8918ae70b9 100644 --- a/thehive/app/org/thp/thehive/services/OrganisationSrv.scala +++ b/thehive/app/org/thp/thehive/services/OrganisationSrv.scala @@ -1,9 +1,6 @@ package org.thp.thehive.services -import java.util.{Map => JMap} - import akka.actor.ActorRef -import javax.inject.{Inject, Named, Provider, Singleton} import org.apache.tinkerpop.gremlin.structure.Graph import org.thp.scalligraph.auth.{AuthContext, Permission} import org.thp.scalligraph.models._ @@ -17,8 +14,11 @@ import org.thp.thehive.models._ import org.thp.thehive.services.OrganisationOps._ import org.thp.thehive.services.RoleOps._ import org.thp.thehive.services.UserOps._ +import play.api.cache._ import play.api.libs.json.JsObject +import java.util.{Map => JMap} +import javax.inject.{Inject, Named, Provider, Singleton} import scala.util.{Failure, Success, Try} @Singleton @@ -28,6 +28,7 @@ class OrganisationSrv @Inject() ( profileSrv: ProfileSrv, auditSrv: AuditSrv, userSrv: UserSrv, + cache: SyncCacheApi, @Named("integrity-check-actor") integrityCheckActor: ActorRef )(implicit @Named("with-thehive-schema") db: Database @@ -62,6 +63,11 @@ class OrganisationSrv @Inject() ( def current(implicit graph: Graph, authContext: AuthContext): Traversal.V[Organisation] = get(authContext.organisation) + def currentId(implicit graph: Graph, authContext: AuthContext): EntityId = + authContext + .organisation + .fold(identity, oid => cache.getOrElseUpdate(s"organisation-$oid")(getByName(oid)._id.getOrFail("Organisation").get)) + def visibleOrganisation(implicit graph: Graph, authContext: AuthContext): Traversal.V[Organisation] = userSrv.current.organisations.visibleOrganisationsFrom diff --git a/thehive/app/org/thp/thehive/services/TagSrv.scala b/thehive/app/org/thp/thehive/services/TagSrv.scala index 49f2db7a1a..d9032ce5ea 100644 --- a/thehive/app/org/thp/thehive/services/TagSrv.scala +++ b/thehive/app/org/thp/thehive/services/TagSrv.scala @@ -9,54 +9,80 @@ 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.models._ import org.thp.thehive.services.TagOps._ import javax.inject.{Inject, Named, Singleton} +import scala.util.matching.Regex import scala.util.{Success, Try} @Singleton -class TagSrv @Inject() (appConfig: ApplicationConfig, @Named("integrity-check-actor") integrityCheckActor: ActorRef)(implicit +class TagSrv @Inject() ( + appConfig: ApplicationConfig, + @Named("integrity-check-actor") integrityCheckActor: ActorRef, + organisationSrv: OrganisationSrv +)(implicit @Named("with-thehive-schema") db: Database ) extends VertexSrv[Tag] { private val autoCreateConfig: ConfigItem[Boolean, Boolean] = appConfig.item[Boolean]("tags.autocreate", "If true, create automatically tag if it doesn't exist") - def autoCreate: Boolean = autoCreateConfig.get - private val defaultNamespaceConfig: ConfigItem[String, String] = appConfig.item[String]("tags.defaultNamespace", "Default namespace of the automatically created tags") - def defaultNamespace: String = defaultNamespaceConfig.get - private val defaultColourConfig: ConfigItem[String, String] = appConfig.item[String]("tags.defaultColour", "Default colour of the automatically created tags") - def defaultColour: String = defaultColourConfig.get + def autoCreate: Boolean = autoCreateConfig.get + def defaultNamespace: String = defaultNamespaceConfig.get + def defaultColour: String = defaultColourConfig.get - def parseString(tagName: String): Tag = - Tag.fromString(tagName, defaultNamespace, defaultColour) + private def freeTag(tagName: String)(implicit graph: Graph, authContext: AuthContext): Tag = + Tag(freeTagNamespace, tagName, None, None, defaultColour) - def getTag(tag: Tag)(implicit graph: Graph): Traversal.V[Tag] = startTraversal.getTag(tag) + private def freeTagNamespace(implicit graph: Graph, authContext: AuthContext): String = + s"_freetags_${organisationSrv.currentId(graph, authContext).value}" - def getOrCreate(tagName: String)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = { - val tag = parseString(tagName) - getTag(tag).getOrFail("Tag").recoverWith { - case _ if autoCreate => create(tag) + def fromString(tagName: String): Option[(String, String, Option[String])] = { + val namespacePredicateValue: Regex = "([^\".:=]+)[.:]([^\".=]+)=\"?([^\"]+)\"?".r + val namespacePredicate: Regex = "([^\".:=]+)[.:]([^\".=]+)".r + + tagName match { + case namespacePredicateValue(namespace, predicate, value) if value.exists(_ != '=') => + Some((namespace.trim, predicate.trim, Some(value.trim))) + case namespacePredicate(namespace, predicate) => + Some((namespace.trim, predicate.trim, None)) + case _ => None } } - def getOrCreate(tag: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = - getTag(tag).getOrFail("Tag").recoverWith { case _ => create(tag) } + def getTag(tag: Tag)(implicit graph: Graph): Traversal.V[Tag] = startTraversal.getTag(tag) - override def createEntity(e: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = { + def getOrCreate(tagName: String)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = + fromString(tagName) match { + case Some((ns, pred, v)) => + startTraversal + .getByName(ns, pred, v) + .getOrFail("Tag") + .orElse( + startTraversal + .getByName(freeTagNamespace, ns + pred + v.getOrElse(""), None) + .getOrFail("Tag") + .orElse(create(freeTag(tagName))) + ) + case None => + startTraversal + .getByName(freeTagNamespace, tagName, None) + .getOrFail("Tag") + .orElse(create(freeTag(tagName))) + } + + def create(tag: Tag)(implicit graph: Graph, authContext: AuthContext): Try[Tag with Entity] = { integrityCheckActor ! EntityAdded("Tag") - super.createEntity(e) + super.createEntity(tag) } - 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( diff --git a/thehive/app/org/thp/thehive/services/TaxonomySrv.scala b/thehive/app/org/thp/thehive/services/TaxonomySrv.scala index e0c20f629a..efc6551d88 100644 --- a/thehive/app/org/thp/thehive/services/TaxonomySrv.scala +++ b/thehive/app/org/thp/thehive/services/TaxonomySrv.scala @@ -109,7 +109,7 @@ object TaxonomyOps { if (authContext.isPermitted(Permissions.manageTaxonomy)) noFreetags else - traversal.filter(_.organisations.get(authContext.organisation)) + noFreetags.filter(_.organisations.get(authContext.organisation)) private def noFreetags: Traversal.V[Taxonomy] = traversal.filterNot(_.has(_.namespace, TextP.startingWith("_freetags"))) diff --git a/thehive/test/org/thp/thehive/models/TagTest.scala b/thehive/test/org/thp/thehive/models/TagTest.scala deleted file mode 100644 index 24a4c59a58..0000000000 --- a/thehive/test/org/thp/thehive/models/TagTest.scala +++ /dev/null @@ -1,47 +0,0 @@ -package org.thp.thehive.models - -import play.api.test.PlaySpecification - -class TagTest extends PlaySpecification { - val defaultNamespace: String = "_default_namespace_" - val defaultColour: String = "#ffff00" - - def parseTag(s: String): Tag = Tag.fromString(s, defaultNamespace, defaultColour) - "tag" should { - "be parsed from key:value" in { - val tag = parseTag("Module:atest_blah_blah") - tag must beEqualTo(Tag(defaultNamespace, "Module", Some("atest_blah_blah"), None, defaultColour)) - tag.toString must beEqualTo("Module=\"atest_blah_blah\"") - } - - "be parsed from key:value=" in { - val tag = parseTag("Id:7SeUoB3IBABD+tMh2PjVJYg==") - tag must beEqualTo(Tag(defaultNamespace, "Id", Some("7SeUoB3IBABD+tMh2PjVJYg=="), None, defaultColour)) - tag.toString must beEqualTo("Id=\"7SeUoB3IBABD+tMh2PjVJYg==\"") - } - - "be parsed from key: value" in { - val tag = parseTag("domain: google.com") - tag must beEqualTo(Tag(defaultNamespace, "domain", Some("google.com"), None, defaultColour)) - tag.toString must beEqualTo("domain=\"google.com\"") - } - - "be parsed from key: a.b.c.d" in { - val tag = parseTag("ip: 8.8.8.8") - tag must beEqualTo(Tag(defaultNamespace, "ip", Some("8.8.8.8"), None, defaultColour)) - tag.toString must beEqualTo("ip=\"8.8.8.8\"") - } - - "be parsed with colour" in { - val tag = parseTag("ip:8.8.8.8#FF00FF") - tag must beEqualTo(Tag(defaultNamespace, "ip", Some("8.8.8.8"), None, "#FF00FF")) - tag.toString must beEqualTo("ip=\"8.8.8.8\"") - } - - "be parsed with hash sign and colour" in { - val tag = parseTag("case:#42#FF00FF") - tag must beEqualTo(Tag(defaultNamespace, "case", Some("#42"), None, "#FF00FF")) - tag.toString must beEqualTo("case=\"#42\"") - } - } -} diff --git a/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala b/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala index 5e4b3cb102..2c45dfd034 100644 --- a/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala +++ b/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala @@ -6,8 +6,8 @@ import org.thp.scalligraph.models._ import org.thp.scalligraph.traversal.TraversalOps._ import org.thp.thehive.TestAppBuilder import org.thp.thehive.models._ -import org.thp.thehive.services.CaseTemplateOps._ import org.thp.thehive.services.TagOps._ +import org.thp.thehive.services.CaseTemplateOps._ import play.api.libs.json.{JsNumber, JsString, JsTrue, JsValue} import play.api.test.PlaySpecification @@ -42,7 +42,8 @@ class CaseTemplateSrvTest extends PlaySpecification with TestAppBuilder { } must beASuccessfulTry app[Database].roTransaction { implicit graph => - app[TagSrv].startTraversal.getByName("testNamespace", "testPredicate", Some("newOne")).exists must beTrue + val orgId = app[OrganisationSrv].currentId.value + app[TagSrv].startTraversal.getByName(s"_freetags_$orgId", "testNamespace:testPredicate=\"newOne\"", None).exists must beTrue app[TaskSrv].startTraversal.has(_.title, "task case template case template test 1").exists must beTrue val richCT = app[CaseTemplateSrv].startTraversal.getByName("case template test 1").richCaseTemplate.getOrFail("CaseTemplate").get richCT.customFields.length shouldEqual 2 @@ -69,7 +70,7 @@ class CaseTemplateSrvTest extends PlaySpecification with TestAppBuilder { caseTemplate <- app[CaseTemplateSrv].getOrFail(EntityName("spam")) _ <- app[CaseTemplateSrv].updateTagNames( caseTemplate, - Set("""testNamespace:testPredicate="t2"""", """testNamespace:testPredicate="newOne2"""", """newNspc.newPred="newOne3"""") + Set("""testNamespace:testPredicate="t2"""", """testNamespace:testPredicate="newOne2"""", """newNspc:newPred="newOne3"""") ) } yield () } must beSuccessfulTry diff --git a/thehive/test/org/thp/thehive/services/TagSrvTest.scala b/thehive/test/org/thp/thehive/services/TagSrvTest.scala new file mode 100644 index 0000000000..a24aa35b95 --- /dev/null +++ b/thehive/test/org/thp/thehive/services/TagSrvTest.scala @@ -0,0 +1,56 @@ +package org.thp.thehive.services + +import org.thp.scalligraph.auth.AuthContext +import org.thp.scalligraph.models.{Database, DummyUserSrv} +import org.thp.thehive.TestAppBuilder +import org.thp.thehive.models.Profile +import play.api.test.PlaySpecification + +import scala.util.Success + +class TagSrvTest extends PlaySpecification with TestAppBuilder { + + implicit val authContext: AuthContext = + DummyUserSrv(userId = "certuser@thehive.local", organisation = "cert", permissions = Profile.analyst.permissions).authContext + + "tag service" should { + "fromString" should { + "be parsed from namespace:predicate" in testApp { app => + app[TagSrv].fromString("namespace:predicate") must beEqualTo(Some("namespace", "predicate", None)) + } + + "be parsed from namespace:predicate=" in testApp { app => + app[TagSrv].fromString("namespace:predicate=") must beEqualTo(None) + } + + "be parsed from namespace: predicate" in testApp { app => + app[TagSrv].fromString("namespace: predicate") must beEqualTo(Some("namespace", "predicate", None)) + } + + "be parsed from namespace:predicate=value" in testApp { app => + app[TagSrv].fromString("namespace:predicate=value") must beEqualTo(Some("namespace", "predicate", Some("value"))) + } + } + + "getOrCreate" should { + "get a tag from a taxonomy" in testApp { app => + app[Database].roTransaction { implicit graph => + val tag = app[TagSrv].getOrCreate("taxonomy1:pred1=value1") + tag.map(_.toString) must beEqualTo(Success("taxonomy1:pred1=\"value1\"")) + } + } + + "get a _freetag tag" in testApp { app => + app[Database].transaction { implicit graph => + val orgId = app[OrganisationSrv].currentId.value + val tag = app[TagSrv].getOrCreate("afreetag") + tag.map(_.namespace) must beEqualTo(Success(s"_freetags_$orgId")) + tag.map(_.predicate) must beEqualTo(Success("afreetag")) + tag.map(_.predicate) must beEqualTo(Success("afreetag")) + tag.map(_.colour) must beEqualTo(Success(app[TagSrv].defaultColour)) + } + } + } + + } +}