diff --git a/dto/src/main/scala/org/thp/thehive/dto/v1/Pattern.scala b/dto/src/main/scala/org/thp/thehive/dto/v1/Pattern.scala index 655047b559..e905a9444d 100644 --- a/dto/src/main/scala/org/thp/thehive/dto/v1/Pattern.scala +++ b/dto/src/main/scala/org/thp/thehive/dto/v1/Pattern.scala @@ -1,6 +1,6 @@ package org.thp.thehive.dto.v1 -import play.api.libs.json.{Format, Json, Reads, Writes} +import play.api.libs.json.{Format, JsObject, Json, Reads, Writes} import java.util.Date @@ -121,7 +121,7 @@ case class OutputPattern( remoteSupport: Boolean, systemRequirements: Seq[String], version: Option[String], - parent: Option[String] + extraData: JsObject ) object OutputPattern { diff --git a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala index 990b5b96a2..e602a49715 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala @@ -511,15 +511,26 @@ object Conversion { .transform } - implicit val richPatternRenderer: Renderer.Aux[RichPattern, OutputPattern] = + implicit val patternOutput: Renderer.Aux[RichPattern, OutputPattern] = Renderer.toJson[RichPattern, OutputPattern]( _.into[OutputPattern] .withFieldComputed(_._id, _._id.toString) .withFieldConst(_._type, "Pattern") - .withFieldComputed(_.parent, _.parent.map(_.patternId)) + .withFieldConst(_.extraData, JsObject.empty) .transform ) + implicit val patternWithStatsOutput: Renderer.Aux[(RichPattern, JsObject), OutputPattern] = + Renderer.toJson[(RichPattern, JsObject), OutputPattern] { patternWithExtraData => + patternWithExtraData + ._1 + .into[OutputPattern] + .withFieldComputed(_._id, _._id.toString) + .withFieldConst(_._type, "Pattern") + .withFieldConst(_.extraData, patternWithExtraData._2) + .transform + } + implicit class InputProcedureOps(inputProcedure: InputProcedure) { def toProcedure: Procedure = inputProcedure diff --git a/thehive/app/org/thp/thehive/controllers/v1/LogRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/LogRenderer.scala index 5d234c9523..312951eb7d 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/LogRenderer.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/LogRenderer.scala @@ -16,12 +16,12 @@ import play.api.libs.json._ trait LogRenderer extends BaseRenderer[Log] { - def caseParent(implicit + private def caseParent(implicit authContext: AuthContext ): Traversal.V[Log] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = _.`case`.richCase.fold.domainMap(_.headOption.fold[JsValue](JsNull)(_.toJson)) - def taskParent(implicit + private def taskParent(implicit authContext: AuthContext ): Traversal.V[Log] => Traversal[JsValue, JMap[String, Any], Converter[JsValue, JMap[String, Any]]] = _.task.project(_.by(_.richTask.fold).by(_.`case`.richCase.fold)).domainMap { @@ -29,21 +29,25 @@ trait LogRenderer extends BaseRenderer[Log] { task.headOption.fold[JsValue](JsNull)(_.toJson.as[JsObject] + ("case" -> case0.headOption.fold[JsValue](JsNull)(_.toJson))) } - def taskParentId: Traversal.V[Log] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = + private def taskParentId: Traversal.V[Log] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = _.task.fold.domainMap(_.headOption.fold[JsValue](JsNull)(c => JsString(c._id.toString))) - def actionCount: Traversal.V[Log] => Traversal[JsValue, JLong, Converter[JsValue, JLong]] = + private def actionCount: Traversal.V[Log] => Traversal[JsValue, JLong, Converter[JsValue, JLong]] = _.in("ActionContext").count.domainMap(JsNumber(_)) - def logStatsRenderer(extraData: Set[String])( - implicit authContext: AuthContext + def logStatsRenderer(extraData: Set[String])(implicit + authContext: AuthContext ): Traversal.V[Log] => JsTraversal = { implicit traversal => - baseRenderer(extraData, traversal, { - case (f, "case") => addData("case", f)(caseParent) - case (f, "task") => addData("task", f)(taskParent) - case (f, "taskId") => addData("taskId", f)(taskParentId) - case (f, "actionCount") => addData("actionCount", f)(actionCount) - case (f, _) => f - }) + baseRenderer( + extraData, + traversal, + { + case (f, "case") => addData("case", f)(caseParent) + case (f, "task") => addData("task", f)(taskParent) + case (f, "taskId") => addData("taskId", f)(taskParentId) + case (f, "actionCount") => addData("actionCount", f)(actionCount) + case (f, _) => f + } + ) } } diff --git a/thehive/app/org/thp/thehive/controllers/v1/PatternCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/PatternCtrl.scala index fff3edf948..5baaa1cfb5 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/PatternCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/PatternCtrl.scala @@ -26,7 +26,8 @@ class PatternCtrl @Inject() ( properties: Properties, patternSrv: PatternSrv, @Named("with-thehive-schema") implicit val db: Database -) extends QueryableCtrl { +) extends QueryableCtrl + with PatternRenderer { override val entityName: String = "pattern" override val publicProperties: PublicProperties = properties.pattern override val initialQuery: Query = Query.init[Traversal.V[Pattern]]( @@ -38,7 +39,10 @@ class PatternCtrl @Inject() ( override val pageQuery: ParamQuery[OutputParam] = Query.withParam[OutputParam, Traversal.V[Pattern], IteratorOutput]( "page", FieldsParser[OutputParam], - (range, patternSteps, _) => patternSteps.richPage(range.from, range.to, range.extraData.contains("total"))(_.richPattern) + { + case (OutputParam(from, to, extraData), patternSteps, _) => + patternSteps.richPage(from, to, extraData.contains("total"))(_.richPatternWithCustomRenderer(patternRenderer(extraData - "total"))) + } ) override val outputQuery: Query = Query.output[RichPattern, Traversal.V[Pattern]](_.richPattern) override val getQuery: ParamQuery[EntityIdOrName] = Query.initWithParam[EntityIdOrName, Traversal.V[Pattern]]( diff --git a/thehive/app/org/thp/thehive/controllers/v1/PatternRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/PatternRenderer.scala new file mode 100644 index 0000000000..3ceb219e5d --- /dev/null +++ b/thehive/app/org/thp/thehive/controllers/v1/PatternRenderer.scala @@ -0,0 +1,31 @@ +package org.thp.thehive.controllers.v1 + +import org.thp.scalligraph.traversal.TraversalOps._ +import org.thp.scalligraph.traversal.{Converter, Traversal} +import org.thp.thehive.controllers.v1.Conversion._ +import org.thp.thehive.models.Pattern +import org.thp.thehive.services.PatternOps._ +import play.api.libs.json._ + +import java.util.{List => JList, Map => JMap} + +trait PatternRenderer extends BaseRenderer[Pattern] { + + private def parent: Traversal.V[Pattern] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = + _.parent.richPattern.fold.domainMap(_.headOption.fold[JsValue](JsNull)(_.toJson)) + + private def children: Traversal.V[Pattern] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = + _.children.richPattern.fold.domainMap(_.toJson) + + def patternRenderer(extraData: Set[String]): Traversal.V[Pattern] => JsTraversal = { implicit traversal => + baseRenderer( + extraData, + traversal, + { + case (f, "children") => addData("children", f)(children) + case (f, "parent") => addData("parent", f)(parent) + case (f, _) => f + } + ) + } +} diff --git a/thehive/app/org/thp/thehive/controllers/v1/ProcedureCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/ProcedureCtrl.scala index ba10c6ce90..bd4e18219b 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/ProcedureCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/ProcedureCtrl.scala @@ -3,7 +3,7 @@ package org.thp.thehive.controllers.v1 import org.thp.scalligraph.EntityIdOrName import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} import org.thp.scalligraph.models.Database -import org.thp.scalligraph.query.{ParamQuery, PublicProperties, Query} +import org.thp.scalligraph.query.{ParamQuery, PropertyUpdater, PublicProperties, Query} import org.thp.scalligraph.traversal.TraversalOps.TraversalOpsDefs import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} import org.thp.thehive.controllers.v1.Conversion._ @@ -62,6 +62,19 @@ class ProcedureCtrl @Inject() ( .map(richProcedure => Results.Ok(richProcedure.toJson)) } + def update(procedureId: String): Action[AnyContent] = + entrypoint("update procedure") + .extract("procedure", FieldsParser.update("procedure", properties.procedure)) + .authTransaction(db) { implicit request => implicit graph => + val propertyUpdaters: Seq[PropertyUpdater] = request.body("procedure") + procedureSrv + .update( + _.get(EntityIdOrName(procedureId)), + propertyUpdaters + ) + .map(_ => Results.NoContent) + } + def delete(procedureId: String): Action[AnyContent] = entrypoint("delete procedure") .authPermittedTransaction(db, Permissions.manageProcedure) { implicit request => implicit graph => diff --git a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala index 9a876aaeb5..037d3d2aa1 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala @@ -345,8 +345,13 @@ class Properties @Inject() ( .property("tactics", UMapping.string.set)(_.field.readonly) .property("url", UMapping.string)(_.field.updatable) .property("patternType", UMapping.string)(_.field.readonly) - .property("platforms", UMapping.string.sequence)(_.field.readonly) + .property("revoked", UMapping.boolean)(_.field.readonly) .property("dataSources", UMapping.string.sequence)(_.field.readonly) + .property("defenseBypassed", UMapping.string.sequence)(_.field.readonly) + .property("detection", UMapping.string.optional)(_.field.readonly) + .property("platforms", UMapping.string.sequence)(_.field.readonly) + .property("remoteSupport", UMapping.boolean)(_.field.readonly) + .property("systemRequirements", UMapping.string.sequence)(_.field.readonly) .property("version", UMapping.string.optional)(_.field.readonly) .build diff --git a/thehive/app/org/thp/thehive/controllers/v1/TaskRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/TaskRenderer.scala index 14b3d37f0e..b38ec57259 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TaskRenderer.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TaskRenderer.scala @@ -17,46 +17,51 @@ import play.api.libs.json._ trait TaskRenderer extends BaseRenderer[Task] { - def caseParent(implicit + private def caseParent(implicit authContext: AuthContext ): Traversal.V[Task] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = _.`case`.richCase.fold.domainMap(_.headOption.fold[JsValue](JsNull)(_.toJson)) - def caseParentId: Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = + private def caseParentId: Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = _.`case`.fold.domainMap(_.headOption.fold[JsValue](JsNull)(c => JsString(c._id.toString))) - def caseTemplateParent: Traversal.V[Task] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = + private def caseTemplateParent: Traversal.V[Task] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = _.caseTemplate.richCaseTemplate.fold.domainMap(_.headOption.fold[JsValue](JsNull)(_.toJson)) - def caseTemplateParentId: Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = + private def caseTemplateParentId: Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = _.caseTemplate.fold.domainMap(_.headOption.fold[JsValue](JsNull)(ct => JsString(ct._id.toString))) - def shareCount: Traversal.V[Task] => Traversal[JsValue, JLong, Converter[JsValue, JLong]] = + private def shareCount: Traversal.V[Task] => Traversal[JsValue, JLong, Converter[JsValue, JLong]] = _.organisations.count.domainMap(count => JsNumber(count - 1)) - def isOwner(implicit authContext: AuthContext): Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = + private def isOwner(implicit authContext: AuthContext): Traversal.V[Task] => Traversal[JsValue, JList[Vertex], Converter[JsValue, JList[Vertex]]] = _.origin.get(authContext.organisation).fold.domainMap(l => JsBoolean(l.nonEmpty)) - def actionRequired(implicit authContext: AuthContext): Traversal.V[Task] => Traversal[JsValue, JBoolean, Converter[JsValue, JBoolean]] = + private def actionRequired(implicit authContext: AuthContext): Traversal.V[Task] => Traversal[JsValue, JBoolean, Converter[JsValue, JBoolean]] = _.actionRequired.domainMap(JsBoolean(_)) - def actionRequiredMap(implicit authContext: AuthContext): - Traversal.V[Task] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = + private def actionRequiredMap(implicit + authContext: AuthContext + ): Traversal.V[Task] => Traversal[JsValue, JList[JMap[String, Any]], Converter[JsValue, JList[JMap[String, Any]]]] = _.actionRequiredMap.fold.domainMap(_.toMap.toJson) - def taskStatsRenderer(extraData: Set[String])( - implicit authContext: AuthContext + def taskStatsRenderer(extraData: Set[String])(implicit + authContext: AuthContext ): Traversal.V[Task] => JsTraversal = { implicit traversal => - baseRenderer(extraData, traversal, { - case (f, "case") => addData("case", f)(caseParent) - case (f, "caseId") => addData("caseId", f)(caseParentId) - case (f, "caseTemplate") => addData("caseTemplate", f)(caseTemplateParent) - case (f, "caseTemplateId") => addData("caseTemplateId", f)(caseTemplateParentId) - case (f, "isOwner") => addData("isOwner", f)(isOwner) - case (f, "shareCount") => addData("shareCount", f)(shareCount) - case (f, "actionRequired") => addData("actionRequired", f)(actionRequired) - case (f, "actionRequiredMap") => addData("actionRequiredMap", f)(actionRequiredMap) - case (f, _) => f - }) + baseRenderer( + extraData, + traversal, + { + case (f, "case") => addData("case", f)(caseParent) + case (f, "caseId") => addData("caseId", f)(caseParentId) + case (f, "caseTemplate") => addData("caseTemplate", f)(caseTemplateParent) + case (f, "caseTemplateId") => addData("caseTemplateId", f)(caseTemplateParentId) + case (f, "isOwner") => addData("isOwner", f)(isOwner) + case (f, "shareCount") => addData("shareCount", f)(shareCount) + case (f, "actionRequired") => addData("actionRequired", f)(actionRequired) + case (f, "actionRequiredMap") => addData("actionRequiredMap", f)(actionRequiredMap) + case (f, _) => f + } + ) } } diff --git a/thehive/app/org/thp/thehive/controllers/v1/TaxonomyCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/TaxonomyCtrl.scala index e546e974ef..c491fdf7aa 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TaxonomyCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TaxonomyCtrl.scala @@ -46,10 +46,8 @@ class TaxonomyCtrl @Inject() ( "page", FieldsParser[OutputParam], { - case (OutputParam(from, to, extraData), taxoSteps, authContext) => - taxoSteps.richPage(from, to, extraData.contains("total")) { - _.richTaxonomyWithCustomRenderer(taxoStatsRenderer(extraData - "total")) - } + case (OutputParam(from, to, extraData), taxoSteps, _) => + taxoSteps.richPage(from, to, extraData.contains("total"))(_.richTaxonomyWithCustomRenderer(taxoStatsRenderer(extraData - "total"))) } ) override val outputQuery: Query = diff --git a/thehive/app/org/thp/thehive/controllers/v1/TaxonomyRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/TaxonomyRenderer.scala index b5b45a8b89..8a90025f56 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TaxonomyRenderer.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TaxonomyRenderer.scala @@ -1,43 +1,23 @@ package org.thp.thehive.controllers.v1 -import org.thp.scalligraph.traversal.TraversalOps._ import org.thp.scalligraph.traversal.{Converter, Traversal} import org.thp.thehive.models.Taxonomy import org.thp.thehive.services.TaxonomyOps._ import play.api.libs.json._ -import java.util.{Map => JMap} - -trait TaxonomyRenderer { +trait TaxonomyRenderer extends BaseRenderer[Taxonomy] { def enabledStats: Traversal.V[Taxonomy] => Traversal[JsValue, Boolean, Converter[JsValue, Boolean]] = _.enabled.domainMap(l => JsBoolean(l)) - def taxoStatsRenderer(extraData: Set[String]): - Traversal.V[Taxonomy] => Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]] = { traversal => - def addData[G]( - name: String - )(f: Traversal.V[Taxonomy] => Traversal[JsValue, G, Converter[JsValue, G]]): Traversal[JsObject, JMap[String, Any], Converter[ - JsObject, - JMap[String, Any] - ]] => Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]] = { t => - val dataTraversal = f(traversal.start) - t.onRawMap[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]](_.by(dataTraversal.raw)) { jmap => - t.converter(jmap) + (name -> dataTraversal.converter(jmap.get(name).asInstanceOf[G])) - } - } - - if (extraData.isEmpty) traversal.constant2[JsObject, JMap[String, Any]](JsObject.empty) - else { - val dataName = extraData.toSeq - dataName.foldLeft[Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]]]( - traversal.onRawMap[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]](_.project(dataName.head, dataName.tail: _*))(_ => - JsObject.empty - ) - ) { - case (f, "enabled") => addData("enabled")(enabledStats)(f) + def taxoStatsRenderer(extraData: Set[String]): Traversal.V[Taxonomy] => JsTraversal = { implicit traversal => + baseRenderer( + extraData, + traversal, + { + case (f, "enabled") => addData("enabled", f)(enabledStats) case (f, _) => f } - } + ) } } diff --git a/thehive/app/org/thp/thehive/services/PatternSrv.scala b/thehive/app/org/thp/thehive/services/PatternSrv.scala index efb827937c..f61112af40 100644 --- a/thehive/app/org/thp/thehive/services/PatternSrv.scala +++ b/thehive/app/org/thp/thehive/services/PatternSrv.scala @@ -5,7 +5,7 @@ import org.thp.scalligraph.EntityIdOrName import org.thp.scalligraph.auth.AuthContext import org.thp.scalligraph.models.{Database, Entity} import org.thp.scalligraph.services._ -import org.thp.scalligraph.traversal.TraversalOps._ +import org.thp.scalligraph.traversal.TraversalOps.TraversalOpsDefs import org.thp.scalligraph.traversal.{Converter, Traversal} import org.thp.thehive.models._ import org.thp.thehive.services.CaseOps._ @@ -52,11 +52,15 @@ class PatternSrv @Inject() ( object PatternOps { implicit class PatternOpsDefs(traversal: Traversal.V[Pattern]) { + def getByPatternId(patternId: String): Traversal.V[Pattern] = traversal.has(_.patternId, patternId) def parent: Traversal.V[Pattern] = traversal.in[PatternPattern].v[Pattern] + def children: Traversal.V[Pattern] = + traversal.out[PatternPattern].v[Pattern] + def procedure: Traversal.V[Procedure] = traversal.in[ProcedurePattern].v[Procedure] @@ -74,5 +78,19 @@ object PatternOps { RichPattern(pattern, parent.headOption) } + def richPatternWithCustomRenderer[D, G, C <: Converter[D, G]]( + entityRenderer: Traversal.V[Pattern] => Traversal[D, G, C] + ): Traversal[(RichPattern, D), JMap[String, Any], Converter[(RichPattern, D), JMap[String, Any]]] = + traversal + .project( + _.by + .by(_.in[PatternPattern].v[Pattern].fold) + .by(entityRenderer) + ) + .domainMap { + case (pattern, parent, renderedEntity) => + RichPattern(pattern, parent.headOption) -> renderedEntity + } + } } diff --git a/thehive/app/org/thp/thehive/services/ProcedureSrv.scala b/thehive/app/org/thp/thehive/services/ProcedureSrv.scala index 72b408cbb8..9b9e8f3007 100644 --- a/thehive/app/org/thp/thehive/services/ProcedureSrv.scala +++ b/thehive/app/org/thp/thehive/services/ProcedureSrv.scala @@ -4,12 +4,14 @@ import org.apache.tinkerpop.gremlin.structure.Graph import org.thp.scalligraph.EntityIdOrName import org.thp.scalligraph.auth.AuthContext import org.thp.scalligraph.models.{Database, Entity} +import org.thp.scalligraph.query.PropertyUpdater import org.thp.scalligraph.services._ import org.thp.scalligraph.traversal.TraversalOps.TraversalOpsDefs import org.thp.scalligraph.traversal.{Converter, StepLabel, Traversal} import org.thp.thehive.controllers.v1.Conversion._ -import org.thp.thehive.services.ProcedureOps._ import org.thp.thehive.models._ +import org.thp.thehive.services.ProcedureOps._ +import play.api.libs.json.JsObject import java.util.{Map => JMap} import javax.inject.{Inject, Named, Singleton} @@ -19,7 +21,6 @@ import scala.util.Try class ProcedureSrv @Inject() ( auditSrv: AuditSrv, caseSrv: CaseSrv, - organisationSrv: OrganisationSrv, patternSrv: PatternSrv )(implicit @Named("with-thehive-schema") db: Database @@ -41,6 +42,17 @@ class ProcedureSrv @Inject() ( override def get(idOrName: EntityIdOrName)(implicit graph: Graph): Traversal.V[Procedure] = idOrName.fold(getByIds(_), _ => startTraversal.limit(0)) + override def update( + traversal: Traversal.V[Procedure], + propertyUpdaters: Seq[PropertyUpdater] + )(implicit graph: Graph, authContext: AuthContext): Try[(Traversal.V[Procedure], JsObject)] = + auditSrv.mergeAudits(super.update(traversal, propertyUpdaters)) { + case (procedureSteps, updatedFields) => + procedureSteps.clone().project(_.by.by(_.caze)).getOrFail("Procedure").flatMap { + case (procedure, caze) => auditSrv.procedure.update(procedure, caze, updatedFields) + } + } + def remove(procedure: Procedure with Entity)(implicit graph: Graph, authContext: AuthContext): Try[Unit] = for { caze <- get(procedure).caze.getOrFail("Case") @@ -58,6 +70,9 @@ object ProcedureOps { def caze: Traversal.V[Case] = traversal.in[CaseProcedure].v[Case] + def get(idOrName: EntityIdOrName): Traversal.V[Procedure] = + idOrName.fold(traversal.getByIds(_), _ => traversal.limit(0)) + def richProcedure: Traversal[RichProcedure, JMap[String, Any], Converter[RichProcedure, JMap[String, Any]]] = { val procedure = StepLabel.v[Procedure] val pattern = StepLabel.v[Pattern] diff --git a/thehive/app/org/thp/thehive/services/TaxonomySrv.scala b/thehive/app/org/thp/thehive/services/TaxonomySrv.scala index 2c57ba30e4..01dd9f071e 100644 --- a/thehive/app/org/thp/thehive/services/TaxonomySrv.scala +++ b/thehive/app/org/thp/thehive/services/TaxonomySrv.scala @@ -20,11 +20,11 @@ import scala.util.{Failure, Success, Try} @Singleton class TaxonomySrv @Inject() ( - organisationSrv: OrganisationSrv -)(implicit @Named("with-thehive-schema") db: Database -) extends VertexSrv[Taxonomy] { + organisationSrv: OrganisationSrv +)(implicit @Named("with-thehive-schema") db: Database) + extends VertexSrv[Taxonomy] { - val taxonomyTagSrv = new EdgeSrv[TaxonomyTag, Taxonomy, Tag] + val taxonomyTagSrv = new EdgeSrv[TaxonomyTag, Taxonomy, Tag] val organisationTaxonomySrv = new EdgeSrv[OrganisationTaxonomy, Organisation, Taxonomy] def create(taxo: Taxonomy, tags: Seq[Tag with Entity])(implicit graph: Graph, authContext: AuthContext): Try[RichTaxonomy] = @@ -49,21 +49,24 @@ class TaxonomySrv @Inject() ( def activate(taxonomyId: EntityIdOrName)(implicit graph: Graph, authContext: AuthContext): Try[Unit] = for { taxo <- get(taxonomyId).getOrFail("Taxonomy") - _ <- if (taxo.namespace.startsWith("_freetags")) Failure(BadRequestError("Cannot activate a freetags taxonomy")) - else Success(()) - _ <- organisationSrv.startTraversal - .filterNot(_.out[OrganisationTaxonomy].v[Taxonomy].has(_.namespace, taxo.namespace)) - .toSeq - .toTry(o => organisationTaxonomySrv.create(OrganisationTaxonomy(), o, taxo)) + _ <- + if (taxo.namespace.startsWith("_freetags")) Failure(BadRequestError("Cannot activate a freetags taxonomy")) + else Success(()) + _ <- + organisationSrv + .startTraversal + .filterNot(_.out[OrganisationTaxonomy].v[Taxonomy].has(_.namespace, taxo.namespace)) + .toSeq + .toTry(o => organisationTaxonomySrv.create(OrganisationTaxonomy(), o, taxo)) } yield () - def deactivate(taxonomyId: EntityIdOrName)(implicit graph: Graph): Try[Unit] = { + def deactivate(taxonomyId: EntityIdOrName)(implicit graph: Graph): Try[Unit] = for { taxo <- getOrFail(taxonomyId) - _ <- if (taxo.namespace.startsWith("_freetags")) Failure(BadRequestError("Cannot deactivate a freetags taxonomy")) - else Success(()) + _ <- + if (taxo.namespace.startsWith("_freetags")) Failure(BadRequestError("Cannot deactivate a freetags taxonomy")) + else Success(()) } yield get(taxonomyId).inE[OrganisationTaxonomy].remove() - } } @@ -75,12 +78,11 @@ object TaxonomyOps { def getByNamespace(namespace: String): Traversal.V[Taxonomy] = traversal.has(_.namespace, namespace) - def visible(implicit authContext: AuthContext): Traversal.V[Taxonomy] = { + def visible(implicit authContext: AuthContext): Traversal.V[Taxonomy] = if (authContext.isPermitted(Permissions.manageTaxonomy)) noFreetags else traversal.filter(_.organisations.get(authContext.organisation)) - } private def noFreetags: Traversal.V[Taxonomy] = traversal.filterNot(_.has(_.namespace, TextP.startingWith("_freetags"))) @@ -103,17 +105,17 @@ object TaxonomyOps { ) .domainMap { case (taxonomy, tags) => RichTaxonomy(taxonomy, tags) } - def richTaxonomyWithCustomRenderer[D, G, C <: Converter[D, G]](entityRenderer: Traversal.V[Taxonomy] => Traversal[D, G, C]): - Traversal[(RichTaxonomy, D), JMap[String, Any], Converter[(RichTaxonomy, D), JMap[String, Any]]] = + def richTaxonomyWithCustomRenderer[D, G, C <: Converter[D, G]]( + entityRenderer: Traversal.V[Taxonomy] => Traversal[D, G, C] + ): Traversal[(RichTaxonomy, D), JMap[String, Any], Converter[(RichTaxonomy, D), JMap[String, Any]]] = traversal .project( _.by .by(_.tags.fold) - .by(_.enabled) .by(entityRenderer) ) .domainMap { - case (taxo, tags, _, renderedEntity) => + case (taxo, tags, renderedEntity) => RichTaxonomy( taxo, tags diff --git a/thehive/test/org/thp/thehive/controllers/v1/ProcedureCtrlTest.scala b/thehive/test/org/thp/thehive/controllers/v1/ProcedureCtrlTest.scala index 0b7d6fdf89..f718f66be4 100644 --- a/thehive/test/org/thp/thehive/controllers/v1/ProcedureCtrlTest.scala +++ b/thehive/test/org/thp/thehive/controllers/v1/ProcedureCtrlTest.scala @@ -46,6 +46,9 @@ class ProcedureCtrlTest extends PlaySpecification with TestAppBuilder { ) } + // TODO test update of fields + // description + "delete a procedure" in testApp { app => val request1 = FakeRequest("POST", "/api/v1/procedure/testProcedure3") .withJsonBody(