diff --git a/ScalliGraph b/ScalliGraph index 4aa2d6084e..e7cf9fc714 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit 4aa2d6084eaca86a8dbc7e078a53a103f0d63c8c +Subproject commit e7cf9fc7141ab4d12b8c06b5db427fb009a4f19c diff --git a/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala index 35baed7a5a..5dd56482af 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/CaseCtrl.scala @@ -275,9 +275,7 @@ class PublicCase @Inject() ( }) .property("customFields", UMapping.jsonNative)(_.subSelect { case (FPathElem(_, FPathElem(idOrName, _)), caseSteps) => - caseSteps - .customFields(EntityIdOrName(idOrName)) - .jsonValue + caseSteps.customFieldJsonValue(customFieldSrv, EntityIdOrName(idOrName)) case (_, caseSteps) => caseSteps.customFields.nameJsonValue.fold.domainMap(JsObject(_)) } .filter[JsValue] { diff --git a/thehive/app/org/thp/thehive/controllers/v0/CaseTemplateCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/CaseTemplateCtrl.scala index a0c435e2d9..832b808f56 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/CaseTemplateCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/CaseTemplateCtrl.scala @@ -16,7 +16,7 @@ import org.thp.thehive.services.TaskOps._ import org.thp.thehive.services.UserOps._ import org.thp.thehive.services._ import play.api.Logger -import play.api.libs.json.{JsObject, Json} +import play.api.libs.json.{JsObject, JsValue, Json} import play.api.mvc.{Action, AnyContent, Results} import javax.inject.{Inject, Named, Singleton} @@ -134,22 +134,32 @@ class PublicCaseTemplate @Inject() ( .property("summary", UMapping.string.optional)(_.field.updatable) .property("user", UMapping.string)(_.field.updatable) .property("customFields", UMapping.jsonNative)(_.subSelect { - case (FPathElem(_, FPathElem(name, _)), caseTemplateSteps) => caseTemplateSteps.customFields(name).jsonValue + case (FPathElem(_, FPathElem(name, _)), caseTemplateSteps) => caseTemplateSteps.customFieldJsonValue(customFieldSrv, EntityIdOrName(name)) case (_, caseTemplateSteps) => caseTemplateSteps.customFields.nameJsonValue.fold.domainMap(JsObject(_)) - }.custom { - case (FPathElem(_, FPathElem(name, _)), value, vertex, graph, authContext) => - for { - c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") - _ <- caseTemplateSrv.setOrCreateCustomField(c, name, Some(value), None)(graph, authContext) - } yield Json.obj(s"customFields.$name" -> value) - case (FPathElem(_, FPathEmpty), values: JsObject, vertex, graph, authContext) => - for { - c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") - cfv <- values.fields.toTry { case (n, v) => customFieldSrv.getOrFail(EntityIdOrName(n))(graph).map(_ -> v) } - _ <- caseTemplateSrv.updateCustomField(c, cfv)(graph, authContext) - } yield Json.obj("customFields" -> values) - case _ => Failure(BadRequestError("Invalid custom fields format")) - }) + } + .filter[JsValue] { + case (FPathElem(_, FPathElem(name, _)), caseTemplateTraversal, _, predicate) => + predicate match { + case Right(predicate) => caseTemplateTraversal.customFieldFilter(customFieldSrv, EntityIdOrName(name), predicate) + case Left(true) => caseTemplateTraversal.hasCustomField(customFieldSrv, EntityIdOrName(name)) + case Left(false) => caseTemplateTraversal.hasNotCustomField(customFieldSrv, EntityIdOrName(name)) + } + case (_, caseTraversal, _, _) => caseTraversal.empty + } + .custom { + case (FPathElem(_, FPathElem(name, _)), value, vertex, graph, authContext) => + for { + c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") + _ <- caseTemplateSrv.setOrCreateCustomField(c, EntityIdOrName(name), Some(value), None)(graph, authContext) + } yield Json.obj(s"customField.$name" -> value) + case (FPathElem(_, FPathEmpty), values: JsObject, vertex, graph, authContext) => + for { + c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") + cfv <- values.fields.toTry { case (n, v) => customFieldSrv.getOrFail(EntityIdOrName(n))(graph).map(cf => (cf, v, None)) } + _ <- caseTemplateSrv.updateCustomField(c, cfv)(graph, authContext) + } yield Json.obj("customFields" -> values) + case _ => Failure(BadRequestError("Invalid custom fields format")) + }) .property("tasks", UMapping.jsonNative.sequence)( _.select(_.tasks.richTaskWithoutActionRequired.domainMap(_.toJson)).custom { // FIXME select the correct mapping (_, value, vertex, graph, authContext) => diff --git a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala index 819844c133..5a82f5ad7a 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala @@ -106,8 +106,7 @@ class Properties @Inject() ( .property("customFields", UMapping.jsonNative)(_.subSelect { case (FPathElem(_, FPathElem(idOrName, _)), alerts) => alerts - .customFields(EntityIdOrName(idOrName)) - .jsonValue + .customFieldJsonValue(customFieldSrv, EntityIdOrName(idOrName)) case (_, alerts) => alerts.customFields.nameJsonValue.fold.domainMap(JsObject(_)) } .filter[JsValue] { @@ -209,8 +208,7 @@ class Properties @Inject() ( .property("customFields", UMapping.jsonNative)(_.subSelect { case (FPathElem(_, FPathElem(idOrName, _)), caseSteps) => caseSteps - .customFields(EntityIdOrName(idOrName)) - .jsonValue + .customFieldJsonValue(customFieldSrv, EntityIdOrName(idOrName)) case (_, caseSteps) => caseSteps.customFields.nameJsonValue.fold.domainMap(JsObject(_)) } .filter[JsValue] { @@ -273,16 +271,34 @@ class Properties @Inject() ( .property("summary", UMapping.string.optional)(_.field.updatable) .property("user", UMapping.string)(_.field.updatable) .property("customFields", UMapping.jsonNative)(_.subSelect { - case (FPathElem(_, FPathElem(name, _)), alertSteps) => alertSteps.customFields(name).jsonValue - case (_, alertSteps) => alertSteps.customFields.nameJsonValue.fold.domainMap(JsObject(_)) - }.custom { - case (FPathElem(_, FPathElem(name, _)), value, vertex, graph, authContext) => - for { - c <- caseTemplateSrv.getOrFail(vertex)(graph) - _ <- caseTemplateSrv.setOrCreateCustomField(c, name, Some(value), None)(graph, authContext) - } yield Json.obj(s"customField.$name" -> value) - case _ => Failure(BadRequestError("Invalid custom fields format")) - }) + case (FPathElem(_, FPathElem(idOrName, _)), caseTemplateSteps) => + caseTemplateSteps + .customFieldJsonValue(customFieldSrv, EntityIdOrName(idOrName)) + case (_, caseTemplateSteps) => caseTemplateSteps.customFields.nameJsonValue.fold.domainMap(JsObject(_)) + } + .filter[JsValue] { + case (FPathElem(_, FPathElem(name, _)), caseTemplateTraversal, _, predicate) => + predicate match { + case Right(predicate) => caseTemplateTraversal.customFieldFilter(customFieldSrv, EntityIdOrName(name), predicate) + case Left(true) => caseTemplateTraversal.hasCustomField(customFieldSrv, EntityIdOrName(name)) + case Left(false) => caseTemplateTraversal.hasNotCustomField(customFieldSrv, EntityIdOrName(name)) + } + case (_, caseTraversal, _, _) => caseTraversal.empty + } + .custom { + case (FPathElem(_, FPathElem(idOrName, _)), value, vertex, graph, authContext) => + for { + c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") + _ <- caseTemplateSrv.setOrCreateCustomField(c, EntityIdOrName(idOrName), Some(value), None)(graph, authContext) + } yield Json.obj(s"customField.$idOrName" -> value) + case (FPathElem(_, FPathEmpty), values: JsObject, vertex, graph, authContext) => + for { + c <- caseTemplateSrv.get(vertex)(graph).getOrFail("CaseTemplate") + cfv <- values.fields.toTry { case (n, v) => customFieldSrv.getOrFail(EntityIdOrName(n))(graph).map(cf => (cf, v, None)) } + _ <- caseTemplateSrv.updateCustomField(c, cfv)(graph, authContext) + } yield Json.obj("customFields" -> values) + case _ => Failure(BadRequestError("Invalid custom fields format")) + }) .build lazy val organisation: PublicProperties = diff --git a/thehive/app/org/thp/thehive/models/CustomField.scala b/thehive/app/org/thp/thehive/models/CustomField.scala index fb4edb5241..e30b8710c6 100644 --- a/thehive/app/org/thp/thehive/models/CustomField.scala +++ b/thehive/app/org/thp/thehive/models/CustomField.scala @@ -3,6 +3,9 @@ package org.thp.thehive.models import org.apache.tinkerpop.gremlin.structure.Edge import org.thp.scalligraph._ import org.thp.scalligraph.models._ +import org.thp.scalligraph.traversal.Traversal +import org.thp.scalligraph.traversal.Traversal.{Domain, E} +import org.thp.scalligraph.traversal.TraversalOps._ import play.api.libs.json._ import java.util.{Date, NoSuchElementException} @@ -91,6 +94,10 @@ sealed abstract class CustomFieldType[T] { def getJsonValue(ccf: CustomFieldValue[_]): JsValue = getValue(ccf).fold[JsValue](JsNull)(writes.writes) + def getValue[C <: CustomFieldValue[_]](traversal: Traversal.E[C]): Traversal.Domain[T] + + def getJsonValue[C <: CustomFieldValue[_]](traversal: Traversal.E[C]): Traversal.Domain[JsValue] = getValue(traversal).domainMap(writes.writes) + override def toString: String = name protected def setValueFailure(value: Any): Failure[Nothing] = @@ -117,6 +124,8 @@ object CustomFieldString extends CustomFieldType[String] { } override def getValue(ccf: CustomFieldValue[_]): Option[String] = ccf.stringValue + + override def getValue[C <: CustomFieldValue[_]](traversal: E[C]): Traversal.Domain[String] = traversal.value(_.stringValue).castDomain } object CustomFieldBoolean extends CustomFieldType[Boolean] { @@ -137,6 +146,8 @@ object CustomFieldBoolean extends CustomFieldType[Boolean] { } override def getValue(ccf: CustomFieldValue[_]): Option[Boolean] = ccf.booleanValue + + override def getValue[C <: CustomFieldValue[_]](traversal: E[C]): Domain[Boolean] = traversal.value(_.booleanValue).castDomain } object CustomFieldInteger extends CustomFieldType[Int] { @@ -158,6 +169,8 @@ object CustomFieldInteger extends CustomFieldType[Int] { } override def getValue(ccf: CustomFieldValue[_]): Option[Int] = ccf.integerValue + + override def getValue[C <: CustomFieldValue[_]](traversal: E[C]): Domain[Int] = traversal.value(_.integerValue).castDomain } object CustomFieldFloat extends CustomFieldType[Double] { @@ -178,6 +191,8 @@ object CustomFieldFloat extends CustomFieldType[Double] { } override def getValue(ccf: CustomFieldValue[_]): Option[Double] = ccf.floatValue + + override def getValue[C <: CustomFieldValue[_]](traversal: E[C]): Domain[Double] = traversal.value(_.floatValue).castDomain } object CustomFieldDate extends CustomFieldType[Date] { @@ -199,6 +214,8 @@ object CustomFieldDate extends CustomFieldType[Date] { } override def getValue(ccf: CustomFieldValue[_]): Option[Date] = ccf.dateValue + + override def getValue[C <: CustomFieldValue[_]](traversal: E[C]): Domain[Date] = traversal.value(_.dateValue).castDomain } @DefineIndex(IndexType.unique, "name") diff --git a/thehive/app/org/thp/thehive/services/AlertSrv.scala b/thehive/app/org/thp/thehive/services/AlertSrv.scala index c39e6387fd..eebc4a9d91 100644 --- a/thehive/app/org/thp/thehive/services/AlertSrv.scala +++ b/thehive/app/org/thp/thehive/services/AlertSrv.scala @@ -469,6 +469,14 @@ object AlertOps { def customFields: Traversal.E[AlertCustomField] = traversal.outE[AlertCustomField] + def customFieldJsonValue(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName): Traversal.Domain[JsValue] = + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map(t => CustomFieldType.map(t).getJsonValue(traversal.customFields(customField))) + .getOrElse(traversal.empty.castDomain) + def richCustomFields: Traversal[RichCustomField, JMap[String, Any], Converter[RichCustomField, JMap[String, Any]]] = traversal .outE[AlertCustomField] diff --git a/thehive/app/org/thp/thehive/services/CaseSrv.scala b/thehive/app/org/thp/thehive/services/CaseSrv.scala index 094978ef53..0d7b4d5cd1 100644 --- a/thehive/app/org/thp/thehive/services/CaseSrv.scala +++ b/thehive/app/org/thp/thehive/services/CaseSrv.scala @@ -494,6 +494,14 @@ object CaseOps { name => customFields.filter(_.inV.v[CustomField].has(_.name, name)) ) + def customFieldJsonValue(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName): Traversal.Domain[JsValue] = + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map(t => CustomFieldType.map(t).getJsonValue(traversal.customFields(customField))) + .getOrElse(traversal.empty.castDomain) + def richCustomFields: Traversal[RichCustomField, JMap[String, Any], Converter[RichCustomField, JMap[String, Any]]] = customFields .project(_.by.by(_.inV.v[CustomField])) diff --git a/thehive/app/org/thp/thehive/services/CaseTemplateSrv.scala b/thehive/app/org/thp/thehive/services/CaseTemplateSrv.scala index cda9c8d8e1..d5f208f698 100644 --- a/thehive/app/org/thp/thehive/services/CaseTemplateSrv.scala +++ b/thehive/app/org/thp/thehive/services/CaseTemplateSrv.scala @@ -4,6 +4,7 @@ import akka.actor.ActorRef import org.apache.tinkerpop.gremlin.process.traversal.P import org.thp.scalligraph.auth.{AuthContext, Permission} import org.thp.scalligraph.models.{Database, Entity} +import org.thp.scalligraph.query.PredicateOps.PredicateOpsDefs import org.thp.scalligraph.query.PropertyUpdater import org.thp.scalligraph.services._ import org.thp.scalligraph.traversal.TraversalOps._ @@ -16,9 +17,9 @@ import org.thp.thehive.services.CustomFieldOps._ import org.thp.thehive.services.OrganisationOps._ import org.thp.thehive.services.TaskOps._ import org.thp.thehive.services.UserOps._ -import play.api.libs.json.{JsObject, Json} +import play.api.libs.json.{JsObject, JsValue, Json} -import java.util.{Map => JMap} +import java.util.{Date, Map => JMap} import javax.inject.{Inject, Named} import scala.util.{Failure, Success, Try} @@ -61,7 +62,9 @@ class CaseTemplateSrv @Inject() ( _ <- caseTemplateOrganisationSrv.create(CaseTemplateOrganisation(), createdCaseTemplate, organisation) createdTasks <- tasks.toTry(createTask(createdCaseTemplate, _)) _ <- caseTemplate.tags.toTry(tagSrv.getOrCreate(_).flatMap(t => caseTemplateTagSrv.create(CaseTemplateTag(), createdCaseTemplate, t))) - cfs <- customFields.zipWithIndex.toTry { case ((name, value), order) => createCustomField(createdCaseTemplate, name, value, Some(order + 1)) } + cfs <- customFields.zipWithIndex.toTry { + case ((name, value), order) => createCustomField(createdCaseTemplate, EntityIdOrName(name), value, Some(order + 1)) + } richCaseTemplate = RichCaseTemplate(createdCaseTemplate, organisation.name, createdTasks, cfs) _ <- auditSrv.caseTemplate.create(createdCaseTemplate, richCaseTemplate.toJson) } yield richCaseTemplate @@ -101,12 +104,12 @@ class CaseTemplateSrv @Inject() ( def addTags(caseTemplate: CaseTemplate with Entity, tags: Set[String])(implicit graph: Graph, authContext: AuthContext): Try[Unit] = updateTags(caseTemplate, tags ++ caseTemplate.tags).map(_ => ()) - def getCustomField(caseTemplate: CaseTemplate with Entity, customFieldName: String)(implicit graph: Graph): Option[RichCustomField] = - get(caseTemplate).customFields(customFieldName).richCustomField.headOption + def getCustomField(caseTemplate: CaseTemplate with Entity, customFieldIdOrName: EntityIdOrName)(implicit graph: Graph): Option[RichCustomField] = + get(caseTemplate).customFields(customFieldIdOrName).richCustomField.headOption def updateCustomField( caseTemplate: CaseTemplate with Entity, - customFieldValues: Seq[(CustomField, Any)] + customFieldValues: Seq[(CustomField, Any, Option[Int])] )(implicit graph: Graph, authContext: AuthContext): Try[Unit] = { val customFieldNames = customFieldValues.map(_._1.name) get(caseTemplate) @@ -114,32 +117,32 @@ class CaseTemplateSrv @Inject() ( .richCustomField .toIterator .filterNot(rcf => customFieldNames.contains(rcf.name)) - .foreach(rcf => get(caseTemplate).customFields(rcf.name).remove()) + .foreach(rcf => get(caseTemplate).customFields(EntityName(rcf.name)).remove()) customFieldValues - .zipWithIndex - .toTry { case ((cf, v), o) => setOrCreateCustomField(caseTemplate, cf.name, Some(v), Some(o + 1)) } + .toTry { case (cf, v, o) => setOrCreateCustomField(caseTemplate, EntityName(cf.name), Some(v), o) } .map(_ => ()) } - def setOrCreateCustomField(caseTemplate: CaseTemplate with Entity, customFieldName: String, value: Option[Any], order: Option[Int])(implicit + def setOrCreateCustomField(caseTemplate: CaseTemplate with Entity, customFieldIdOrName: EntityIdOrName, value: Option[Any], order: Option[Int])( + implicit graph: Graph, authContext: AuthContext ): Try[Unit] = { - val cfv = get(caseTemplate).customFields(customFieldName) + val cfv = get(caseTemplate).customFields(customFieldIdOrName) if (cfv.clone().exists) cfv.setValue(value) else - createCustomField(caseTemplate, customFieldName, value, order).map(_ => ()) + createCustomField(caseTemplate, customFieldIdOrName, value, order).map(_ => ()) } def createCustomField( caseTemplate: CaseTemplate with Entity, - customFieldName: String, + customFieldIdOrName: EntityIdOrName, customFieldValue: Option[Any], order: Option[Int] )(implicit graph: Graph, authContext: AuthContext): Try[RichCustomField] = for { - cf <- customFieldSrv.getOrFail(EntityIdOrName(customFieldName)) + cf <- customFieldSrv.getOrFail(customFieldIdOrName) ccf <- CustomFieldType.map(cf.`type`).setValue(CaseTemplateCustomField(order = order), customFieldValue) ccfe <- caseTemplateCustomFieldSrv.create(ccf, caseTemplate, cf) } yield RichCustomField(cf, ccfe) @@ -197,11 +200,71 @@ object CaseTemplateOps { def tags: Traversal.V[Tag] = traversal.out[CaseTemplateTag].v[Tag] - def customFields(name: String): Traversal.E[CaseTemplateCustomField] = - traversal.outE[CaseTemplateCustomField].filter(_.inV.v[CustomField].has(_.name, name)) + def customFields(idOrName: EntityIdOrName): Traversal.E[CaseTemplateCustomField] = + idOrName + .fold( + id => customFields.filter(_.inV.getByIds(id)), + name => customFields.filter(_.inV.v[CustomField].has(_.name, name)) + ) def customFields: Traversal.E[CaseTemplateCustomField] = traversal.outE[CaseTemplateCustomField] + + def customFieldJsonValue(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName): Traversal.Domain[JsValue] = + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map(t => CustomFieldType.map(t).getJsonValue(traversal.customFields(customField))) + .getOrElse(traversal.empty.castDomain) + + def customFieldFilter(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName, predicate: P[JsValue]): Traversal.V[CaseTemplate] = + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map { + case CustomFieldType.boolean => traversal.filter(_.customFields(customField).has(_.booleanValue, predicate.mapValue(_.as[Boolean]))) + case CustomFieldType.date => traversal.filter(_.customFields(customField).has(_.dateValue, predicate.mapValue(_.as[Date]))) + case CustomFieldType.float => traversal.filter(_.customFields(customField).has(_.floatValue, predicate.mapValue(_.as[Double]))) + case CustomFieldType.integer => traversal.filter(_.customFields(customField).has(_.integerValue, predicate.mapValue(_.as[Int]))) + case CustomFieldType.string => traversal.filter(_.customFields(customField).has(_.stringValue, predicate.mapValue(_.as[String]))) + } + .getOrElse(traversal.empty) + + def hasCustomField(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName): Traversal.V[CaseTemplate] = { + val cfFilter = (t: Traversal.V[CustomField]) => customField.fold(id => t.hasId(id), name => t.has(_.name, name)) + + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map { + case CustomFieldType.boolean => traversal.filter(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.booleanValue).inV.v[CustomField])) + case CustomFieldType.date => traversal.filter(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.dateValue).inV.v[CustomField])) + case CustomFieldType.float => traversal.filter(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.floatValue).inV.v[CustomField])) + case CustomFieldType.integer => traversal.filter(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.integerValue).inV.v[CustomField])) + case CustomFieldType.string => traversal.filter(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.stringValue).inV.v[CustomField])) + } + .getOrElse(traversal.empty) + } + + def hasNotCustomField(customFieldSrv: CustomFieldSrv, customField: EntityIdOrName): Traversal.V[CaseTemplate] = { + val cfFilter = (t: Traversal.V[CustomField]) => customField.fold(id => t.hasId(id), name => t.has(_.name, name)) + + customFieldSrv + .get(customField)(traversal.graph) + .value(_.`type`) + .headOption + .map { + case CustomFieldType.boolean => traversal.filterNot(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.booleanValue).inV.v[CustomField])) + case CustomFieldType.date => traversal.filterNot(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.dateValue).inV.v[CustomField])) + case CustomFieldType.float => traversal.filterNot(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.floatValue).inV.v[CustomField])) + case CustomFieldType.integer => traversal.filterNot(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.integerValue).inV.v[CustomField])) + case CustomFieldType.string => traversal.filterNot(t => cfFilter(t.outE[CaseTemplateCustomField].has(_.stringValue).inV.v[CustomField])) + } + .getOrElse(traversal.empty) + } } implicit class CaseTemplateCustomFieldsOpsDefs(traversal: Traversal.E[CaseTemplateCustomField]) extends CustomFieldValueOpsDefs(traversal) diff --git a/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala b/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala index 63ef590903..a744266871 100644 --- a/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala +++ b/thehive/test/org/thp/thehive/services/CaseTemplateSrvTest.scala @@ -128,7 +128,10 @@ class CaseTemplateSrvTest extends PlaySpecification with TestAppBuilder { bool1 <- app[CustomFieldSrv].getOrFail(EntityName("boolean1")) integer1 <- app[CustomFieldSrv].getOrFail(EntityName("integer1")) caseTemplate <- app[CaseTemplateSrv].getOrFail(EntityName("spam")) - _ <- app[CaseTemplateSrv].updateCustomField(caseTemplate, Seq((string1, JsString("hate")), (bool1, JsTrue), (integer1, JsNumber(1)))) + _ <- app[CaseTemplateSrv].updateCustomField( + caseTemplate, + Seq((string1, JsString("hate"), None), (bool1, JsTrue, None), (integer1, JsNumber(1), None)) + ) } yield () } must beSuccessfulTry