Skip to content

Commit

Permalink
#1670 fixed tag creation issue
Browse files Browse the repository at this point in the history
  • Loading branch information
rriclet committed Feb 18, 2021
1 parent 052b09c commit a0567b5
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
27 changes: 0 additions & 27 deletions thehive/app/org/thp/thehive/models/Tag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
}
12 changes: 9 additions & 3 deletions thehive/app/org/thp/thehive/services/OrganisationSrv.scala
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
66 changes: 46 additions & 20 deletions thehive/app/org/thp/thehive/services/TagSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion thehive/app/org/thp/thehive/services/TaxonomySrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")))
Expand Down
47 changes: 0 additions & 47 deletions thehive/test/org/thp/thehive/models/TagTest.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
56 changes: 56 additions & 0 deletions thehive/test/org/thp/thehive/services/TagSrvTest.scala
Original file line number Diff line number Diff line change
@@ -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 = "[email protected]", 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))
}
}
}

}
}

0 comments on commit a0567b5

Please sign in to comment.