diff --git a/dto/src/main/scala/org/thp/thehive/dto/v0/CustomFieldValue.scala b/dto/src/main/scala/org/thp/thehive/dto/v0/CustomFieldValue.scala index 3931d5b4dd..d7eb91b277 100644 --- a/dto/src/main/scala/org/thp/thehive/dto/v0/CustomFieldValue.scala +++ b/dto/src/main/scala/org/thp/thehive/dto/v0/CustomFieldValue.scala @@ -70,20 +70,21 @@ object InputCustomFieldValue { case (_, FObject(fields)) => fields .toSeq + .zipWithIndex .validatedBy { - case (name, FString(value)) => Good(InputCustomFieldValue(name, Some(value), None)) - case (name, FNumber(value)) => Good(InputCustomFieldValue(name, Some(value), None)) - case (name, FBoolean(value)) => Good(InputCustomFieldValue(name, Some(value), None)) - case (name, FAny(value :: _)) => Good(InputCustomFieldValue(name, Some(value), None)) - case (name, FNull) => Good(InputCustomFieldValue(name, None, None)) - case (name, obj: FObject) => + case ((name, FString(value)), i) => Good(InputCustomFieldValue(name, Some(value), Some(i))) + case ((name, FNumber(value)), i) => Good(InputCustomFieldValue(name, Some(value), Some(i))) + case ((name, FBoolean(value)), i) => Good(InputCustomFieldValue(name, Some(value), Some(i))) + case ((name, FAny(value :: _)), i) => Good(InputCustomFieldValue(name, Some(value), Some(i))) + case ((name, FNull), i) => Good(InputCustomFieldValue(name, None, Some(i))) + case ((name, obj: FObject), i) => getStringCustomField(name, obj) orElse getIntegerCustomField(name, obj) orElse getFloatCustomField(name, obj) orElse getDateCustomField(name, obj) orElse getBooleanCustomField(name, obj) getOrElse Good(InputCustomFieldValue(name, None, None)) - case (name, other) => + case ((name, other), i) => Bad( One( InvalidFormatAttributeError(name, "CustomFieldValue", Set("field: string", "field: number", "field: boolean", "field: date"), other) diff --git a/dto/src/main/scala/org/thp/thehive/dto/v1/CustomFieldValue.scala b/dto/src/main/scala/org/thp/thehive/dto/v1/CustomFieldValue.scala index 00159322de..17888b43e4 100644 --- a/dto/src/main/scala/org/thp/thehive/dto/v1/CustomFieldValue.scala +++ b/dto/src/main/scala/org/thp/thehive/dto/v1/CustomFieldValue.scala @@ -48,12 +48,15 @@ object InputCustomFieldValue { case (_, FObject(fields)) => fields .toSeq + .zipWithIndex .validatedBy { - case (name, valueField) => valueParser(valueField).map(v => InputCustomFieldValue(name, v, None)) + case ((name, valueField), i) => valueParser(valueField).map(v => InputCustomFieldValue(name, v, Some(i))) } .map(_.toSeq) case (_, FSeq(list)) => - list.zipWithIndex.validatedBy { + list + .zipWithIndex + .validatedBy { case (cf: FObject, i) => val order = FieldsParser.int(cf.get("order")).getOrElse(i) for { @@ -63,7 +66,7 @@ object InputCustomFieldValue { case (other, i) => Bad( One( - InvalidFormatAttributeError(s"customFild[$i]", "CustomFieldValue", Set.empty, other) + InvalidFormatAttributeError(s"customField[$i]", "CustomFieldValue", Set.empty, other) ) ) } 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 d320cbc0d8..5323d43318 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 @@ -678,7 +678,7 @@ class Output @Inject() ( _ <- alertSrv.alertOrganisationSrv.create(AlertOrganisation(), alert, organisation) _ <- caseTemplate.map(ct => alertSrv.alertCaseTemplateSrv.create(AlertCaseTemplate(), alert, ct)).flip _ <- tags.toTry(t => alertSrv.alertTagSrv.create(AlertTag(), alert, t)) - _ <- inputAlert.customFields.toTry { case (name, value) => alertSrv.createCustomField(alert, name, value) } + _ <- inputAlert.customFields.toTry { case (name, value) => alertSrv.createCustomField(alert, name, value, None) } _ = updateMetaData(alert, inputAlert.metaData) _ = inputAlert.caseId.flatMap(c => getCase(EntityId.read(c)).toOption).foreach(alertSrv.alertCaseSrv.create(AlertCase(), alert, _)) } yield IdMapping(inputAlert.metaData.id, alert._id) diff --git a/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispExportSrv.scala b/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispExportSrv.scala index 58c00318cd..ee69a055e2 100644 --- a/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispExportSrv.scala +++ b/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispExportSrv.scala @@ -150,7 +150,7 @@ class MispExportSrv @Inject() ( ) } org <- organisationSrv.getOrFail(authContext.organisation) - createdAlert <- alertSrv.create(alert.copy(lastSyncDate = new Date(0L)), org, Seq.empty[Tag with Entity], Map.empty[String, Option[Any]], None) + createdAlert <- alertSrv.create(alert.copy(lastSyncDate = new Date(0L)), org, Seq.empty[Tag with Entity], Seq(), None) _ <- alertSrv.alertCaseSrv.create(AlertCase(), createdAlert.alert, `case`) } yield createdAlert diff --git a/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispImportSrv.scala b/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispImportSrv.scala index 1f57a57a3d..7497b0dc35 100644 --- a/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispImportSrv.scala +++ b/misp/connector/src/main/scala/org/thp/thehive/connector/misp/services/MispImportSrv.scala @@ -354,7 +354,7 @@ class MispImportSrv @Inject() ( case None => // if the related alert doesn't exist, create it logger.debug(s"Event ${client.name}#${event.id} has no related alert for organisation ${organisation.name}") alertSrv - .create(alert, organisation, event.tags.map(_.name).toSet, Map.empty[String, Option[Any]], caseTemplate) + .create(alert, organisation, event.tags.map(_.name).toSet, Seq(), caseTemplate) .map(_.alert) case Some(richAlert) => logger.debug(s"Event ${client.name}#${event.id} have already been imported for organisation ${organisation.name}, updating the alert") diff --git a/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala index 37662eaaa4..565cb287b5 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/AlertCtrl.scala @@ -52,7 +52,7 @@ class AlertCtrl @Inject() ( val caseTemplateName: Option[String] = request.body("caseTemplate") val inputAlert: InputAlert = request.body("alert") val observables: Seq[InputObservable] = request.body("observables") - val customFields = inputAlert.customFields.map(c => c.name -> c.value).toMap + val customFields = inputAlert.customFields.map(c => (c.name, c.value, c.order)) val caseTemplate = caseTemplateName.flatMap(ct => caseTemplateSrv.get(EntityIdOrName(ct)).visible.headOption) for { organisation <- @@ -428,7 +428,7 @@ class PublicAlert @Inject() ( case (FPathElem(_, FPathElem(name, _)), value, vertex, _, graph, authContext) => for { c <- alertSrv.getByIds(EntityId(vertex.id))(graph).getOrFail("Alert") - _ <- alertSrv.setOrCreateCustomField(c, name, Some(value))(graph, authContext) + _ <- alertSrv.setOrCreateCustomField(c, name, Some(value), None)(graph, authContext) } yield Json.obj(s"customField.$name" -> value) case (FPathElem(_, FPathEmpty), values: JsObject, vertex, _, graph, authContext) => for { diff --git a/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala index d4ca260509..8e5b651c48 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/AlertCtrl.scala @@ -84,7 +84,7 @@ class AlertCtrl @Inject() ( val caseTemplate = caseTemplateName.flatMap(ct => caseTemplateSrv.get(EntityIdOrName(ct)).visible.headOption) for { organisation <- userSrv.current.organisations(Permissions.manageAlert).getOrFail("Organisation") - customFields = inputAlert.customFieldValue.map(cf => cf.name -> cf.value).toMap + customFields = inputAlert.customFieldValue.map(cf => (cf.name, cf.value, cf.order)) richAlert <- alertSrv.create(inputAlert.toAlert, organisation, inputAlert.tags, customFields, caseTemplate) } yield Results.Created(richAlert.toJson) } diff --git a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala index 5151fb2b4e..83a582d466 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala @@ -87,7 +87,7 @@ class Properties @Inject() ( case (FPathElem(_, FPathElem(name, _)), value, vertex, _, graph, authContext) => for { c <- alertSrv.getOrFail(vertex)(graph) - _ <- alertSrv.setOrCreateCustomField(c, name, Some(value))(graph, authContext) + _ <- alertSrv.setOrCreateCustomField(c, name, Some(value), None)(graph, authContext) } yield Json.obj(s"customField.$name" -> value) case _ => Failure(BadRequestError("Invalid custom fields format")) }) diff --git a/thehive/app/org/thp/thehive/services/AlertSrv.scala b/thehive/app/org/thp/thehive/services/AlertSrv.scala index 0ac7644654..8a7f60d05a 100644 --- a/thehive/app/org/thp/thehive/services/AlertSrv.scala +++ b/thehive/app/org/thp/thehive/services/AlertSrv.scala @@ -55,7 +55,7 @@ class AlertSrv @Inject() ( alert: Alert, organisation: Organisation with Entity, tagNames: Set[String], - customFields: Map[String, Option[Any]], + customFields: Seq[(String, Option[Any], Option[Int])], caseTemplate: Option[CaseTemplate with Entity] )(implicit graph: Graph, @@ -67,7 +67,7 @@ class AlertSrv @Inject() ( alert: Alert, organisation: Organisation with Entity, tags: Seq[Tag with Entity], - customFields: Map[String, Option[Any]], + customFields: Seq[(String, Option[Any], Option[Int])], caseTemplate: Option[CaseTemplate with Entity] )(implicit graph: Graph, @@ -82,7 +82,7 @@ class AlertSrv @Inject() ( _ <- alertOrganisationSrv.create(AlertOrganisation(), createdAlert, organisation) _ <- caseTemplate.map(ct => alertCaseTemplateSrv.create(AlertCaseTemplate(), createdAlert, ct)).flip _ <- tags.toTry(t => alertTagSrv.create(AlertTag(), createdAlert, t)) - cfs <- customFields.toTry { case (name, value) => createCustomField(createdAlert, name, value) } + cfs <- customFields.toTry { case (name, value, order) => createCustomField(createdAlert, name, value, order) } richAlert = RichAlert(createdAlert, organisation.name, tags, cfs, None, caseTemplate.map(_.name), 0) _ <- auditSrv.alert.create(createdAlert, richAlert.toJson) } yield richAlert @@ -170,16 +170,17 @@ class AlertSrv @Inject() ( def createCustomField( alert: Alert with Entity, - customFieldName: String, - customFieldValue: Option[Any] + name: String, + value: Option[Any], + order: Option[Int] )(implicit graph: Graph, authContext: AuthContext): Try[RichCustomField] = for { - cf <- customFieldSrv.getOrFail(EntityIdOrName(customFieldName)) - ccf <- CustomFieldType.map(cf.`type`).setValue(AlertCustomField(), customFieldValue) + cf <- customFieldSrv.getOrFail(EntityIdOrName(name)) + ccf <- CustomFieldType.map(cf.`type`).setValue(AlertCustomField(), value).map(_.order_=(order)) ccfe <- alertCustomFieldSrv.create(ccf, alert, cf) } yield RichCustomField(cf, ccfe) - def setOrCreateCustomField(alert: Alert with Entity, customFieldName: String, value: Option[Any])(implicit + def setOrCreateCustomField(alert: Alert with Entity, customFieldName: String, value: Option[Any], order: Option[Int])(implicit graph: Graph, authContext: AuthContext ): Try[Unit] = { @@ -187,7 +188,7 @@ class AlertSrv @Inject() ( if (cfv.clone().exists) cfv.setValue(value) else - createCustomField(alert, customFieldName, value).map(_ => ()) + createCustomField(alert, customFieldName, value, order).map(_ => ()) } def getCustomField(alert: Alert with Entity, customFieldName: String)(implicit graph: Graph): Option[RichCustomField] = @@ -205,7 +206,7 @@ class AlertSrv @Inject() ( .filterNot(rcf => customFieldNames.contains(rcf.name)) .foreach(rcf => get(alert).customFields(rcf.name).remove()) customFieldValues - .toTry { case (cf, v) => setOrCreateCustomField(alert, cf.name, Some(v)) } + .toTry { case (cf, v) => setOrCreateCustomField(alert, cf.name, Some(v), None) } .map(_ => ()) } diff --git a/thehive/test/org/thp/thehive/services/AlertSrvTest.scala b/thehive/test/org/thp/thehive/services/AlertSrvTest.scala index f63bc0e3e6..c8192d2b49 100644 --- a/thehive/test/org/thp/thehive/services/AlertSrvTest.scala +++ b/thehive/test/org/thp/thehive/services/AlertSrvTest.scala @@ -39,7 +39,7 @@ class AlertSrvTest extends PlaySpecification with TestAppBuilder { ), app[OrganisationSrv].getOrFail(EntityName("cert")).get, Set("tag1", "tag2"), - Map("string1" -> Some("lol")), + Seq(("string1", Some("lol"), None)), Some(app[CaseTemplateSrv].getOrFail(EntityName("spam")).get) ) }