diff --git a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/controllers/v0/CortexQueryExecutor.scala b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/controllers/v0/CortexQueryExecutor.scala index 2c91da551c..e03607fbb7 100644 --- a/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/controllers/v0/CortexQueryExecutor.scala +++ b/cortex/connector/src/main/scala/org/thp/thehive/connector/cortex/controllers/v0/CortexQueryExecutor.scala @@ -1,21 +1,20 @@ package org.thp.thehive.connector.cortex.controllers.v0 -import scala.reflect.runtime.{currentMirror => rm, universe => ru} - import javax.inject.{Inject, Singleton} -import org.scalactic.Accumulation._ import org.scalactic.Good +import org.thp.scalligraph.BadRequestError import org.thp.scalligraph.auth.AuthContext -import org.thp.scalligraph.controllers.{FSeq, FieldsParser} +import org.thp.scalligraph.controllers.FieldsParser import org.thp.scalligraph.models._ -import org.thp.scalligraph.query.InputFilter.{and, not, or} import org.thp.scalligraph.query._ import org.thp.scalligraph.steps.StepsOps._ import org.thp.scalligraph.steps.{BaseTraversal, BaseVertexSteps} -import org.thp.thehive.connector.cortex.services.JobSteps +import org.thp.thehive.connector.cortex.services.{JobSteps, RichObservableSteps} import org.thp.thehive.controllers.v0._ import org.thp.thehive.services.ObservableSteps +import scala.reflect.runtime.{universe => ru} + @Singleton class CortexQueryExecutor @Inject() ( jobCtrl: JobCtrl, @@ -27,8 +26,7 @@ class CortexQueryExecutor @Inject() ( override lazy val publicProperties: List[PublicProperty[_, _]] = jobCtrl.publicProperties ++ reportCtrl.publicProperties ++ actionCtrl.publicProperties override lazy val queries: Seq[ParamQuery[_]] = - new CortexParentFilterQuery(db, publicProperties) :: - actionCtrl.initialQuery :: + actionCtrl.initialQuery :: actionCtrl.pageQuery :: actionCtrl.outputQuery :: jobCtrl.initialQuery :: @@ -38,13 +36,43 @@ class CortexQueryExecutor @Inject() ( reportCtrl.pageQuery :: reportCtrl.outputQuery :: Nil - override lazy val filterQuery = new CortexParentFilterQuery(db, publicProperties) + + val childTypes: PartialFunction[(ru.Type, String), ru.Type] = { + case (tpe, "case_artifact_job") if SubType(tpe, ru.typeOf[ObservableSteps]) => ru.typeOf[ObservableSteps] + } + val parentTypes: PartialFunction[ru.Type, ru.Type] = { + case tpe if SubType(tpe, ru.typeOf[JobSteps]) => ru.typeOf[ObservableSteps] + } + + override val customFilterQuery: FilterQuery = FilterQuery(db, publicProperties) { (tpe, globalParser) => + FieldsParser("parentChildFilter") { + case (_, FObjOne("_parent", ParentIdFilter(_, parentId))) if parentTypes.isDefinedAt(tpe) => + Good(new CortexParentIdInputFilter(parentId)) + case (path, FObjOne("_parent", ParentQueryFilter(_, parentFilterField))) if parentTypes.isDefinedAt(tpe) => + globalParser(parentTypes(tpe)).apply(path, parentFilterField).map(query => new CortexParentQueryInputFilter(query)) + case (path, FObjOne("_child", ChildQueryFilter(childType, childQueryField))) if childTypes.isDefinedAt((tpe, childType)) => + globalParser(childTypes((tpe, childType))).apply(path, childQueryField).map(query => new CortexChildQueryInputFilter(childType, query)) + } + } + override val version: (Int, Int) = 0 -> 0 val job: QueryCtrl = queryCtrlBuilder.apply(jobCtrl, this) val report: QueryCtrl = queryCtrlBuilder.apply(reportCtrl, this) val action: QueryCtrl = queryCtrlBuilder.apply(actionCtrl, this) } +class CortexParentIdInputFilter(parentId: String) extends InputFilter { + override def apply[S <: BaseTraversal]( + db: Database, + publicProperties: List[PublicProperty[_, _]], + stepType: ru.Type, + step: S, + authContext: AuthContext + ): S = + if (stepType =:= ru.typeOf[JobSteps]) step.asInstanceOf[JobSteps].filter(_.observable.getByIds(parentId)).asInstanceOf[S] + else throw BadRequestError(s"$stepType hasn't parent") +} + /** * The parent query parser traversing properly to appropriate parent * @@ -60,37 +88,18 @@ class CortexParentQueryInputFilter(parentFilter: InputFilter) extends InputFilte ): S = if (stepType =:= ru.typeOf[JobSteps]) step.filter(t => parentFilter.apply(db, publicProperties, ru.typeOf[ObservableSteps], t.asInstanceOf[JobSteps].observable, authContext)) - else ??? + else throw BadRequestError(s"$stepType hasn't parent") } -/** - * Query parser for parent traversing heavily relaying on the v0/TheHiveQueryExecutor ParentFilterQuery and dependencies - * - * @param publicProperties the models properties - */ -class CortexParentFilterQuery(db: Database, publicProperties: List[PublicProperty[_, _]]) extends FilterQuery(db, publicProperties) { - override val name: String = "filter" - - override def paramParser(tpe: ru.Type, properties: Seq[PublicProperty[_, _]]): FieldsParser[InputFilter] = - FieldsParser("parentIdFilter") { - case (path, FObjOne("_and", FSeq(fields))) => - fields.zipWithIndex.validatedBy { case (field, index) => paramParser(tpe, properties)((path :/ "_and").toSeq(index), field) }.map(and) - case (path, FObjOne("_or", FSeq(fields))) => - fields.zipWithIndex.validatedBy { case (field, index) => paramParser(tpe, properties)((path :/ "_or").toSeq(index), field) }.map(or) - case (path, FObjOne("_not", field)) => paramParser(tpe, properties)(path :/ "_not", field).map(not) - case (_, FObjOne("_parent", ParentIdFilter(_, parentId))) => Good(new ParentIdInputFilter(parentId)) - case (path, FObjOne("_parent", ParentQueryFilter(_, queryField))) => - paramParser(tpe, properties).apply(path, queryField).map(query => new CortexParentQueryInputFilter(query)) - }.orElse(InputFilter.fieldsParser(tpe, properties)) - - override def checkFrom(t: ru.Type): Boolean = SubType(t, ru.typeOf[JobSteps]) - override def toType(t: ru.Type): ru.Type = t - override def apply(inputFilter: InputFilter, from: Any, authContext: AuthContext): Any = - inputFilter( - db, - publicProperties, - rm.classSymbol(from.getClass).toType, - from.asInstanceOf[BaseVertexSteps], - authContext - ) +class CortexChildQueryInputFilter(childType: String, childFilter: InputFilter) extends InputFilter { + override def apply[S <: BaseVertexSteps]( + db: Database, + publicProperties: List[PublicProperty[_, _]], + stepType: ru.Type, + step: S, + authContext: AuthContext + ): S = + if (stepType =:= ru.typeOf[ObservableSteps] && childType == "case_artifact_job") + step.filter(t => childFilter.apply(db, publicProperties, ru.typeOf[JobSteps], t.asInstanceOf[ObservableSteps].jobs, authContext)) + else throw BadRequestError(s"$stepType hasn't child of type $childType") } diff --git a/thehive/app/org/thp/thehive/controllers/v0/QueryCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/QueryCtrl.scala index 507ebbb868..472a1afdd3 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/QueryCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/QueryCtrl.scala @@ -40,16 +40,16 @@ class QueryCtrl(entrypoint: Entrypoint, db: Database, ctrl: QueryableCtrl, query lazy val logger: Logger = Logger(getClass) val publicProperties: List[PublicProperty[_, _]] = queryExecutor.publicProperties - val filterQuery: FilterQuery = new FilterQuery(db, publicProperties) + val filterQuery: FilterQuery = queryExecutor.filterQuery val queryType: ru.Type = ctrl.initialQuery.toType(ru.typeOf[Graph]) val inputFilterParser: FieldsParser[InputFilter] = queryExecutor .filterQuery - .paramParser(queryType, publicProperties) + .paramParser(queryType) val aggregationParser: FieldsParser[GroupAggregation[_, _, _]] = queryExecutor .aggregationQuery - .paramParser(queryType, publicProperties) + .paramParser(queryType) val sortParser: FieldsParser[InputSort] = FieldsParser("sort") { case (_, FAny(s)) => Good(s) diff --git a/thehive/app/org/thp/thehive/controllers/v0/TheHiveQueryExecutor.scala b/thehive/app/org/thp/thehive/controllers/v0/TheHiveQueryExecutor.scala index 8f010a1d88..2803f5aedc 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/TheHiveQueryExecutor.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/TheHiveQueryExecutor.scala @@ -1,17 +1,17 @@ package org.thp.thehive.controllers.v0 import javax.inject.{Inject, Singleton} -import org.scalactic.Accumulation._ import org.scalactic.Good +import org.thp.scalligraph.BadRequestError import org.thp.scalligraph.auth.AuthContext -import org.thp.scalligraph.controllers.{FSeq, Field, FieldsParser} +import org.thp.scalligraph.controllers.{Field, FieldsParser} import org.thp.scalligraph.models._ import org.thp.scalligraph.query.{InputFilter, _} import org.thp.scalligraph.steps.StepsOps._ import org.thp.scalligraph.steps.{BaseTraversal, BaseVertexSteps} import org.thp.thehive.services.{ObservableSteps, _} -import scala.reflect.runtime.{currentMirror => rm, universe => ru} +import scala.reflect.runtime.{universe => ru} case class OutputParam(from: Long, to: Long, withStats: Boolean, withParents: Int) @@ -38,7 +38,27 @@ class TheHiveQueryExecutor @Inject() ( lazy val controllers: List[QueryableCtrl] = caseCtrl :: taskCtrl :: logCtrl :: observableCtrl :: alertCtrl :: userCtrl :: caseTemplateCtrl :: dashboardCtrl :: organisationCtrl :: auditCtrl :: profileCtrl :: tagCtrl :: pageCtrl :: observableTypeCtrl :: Nil override lazy val publicProperties: List[PublicProperty[_, _]] = controllers.flatMap(_.publicProperties) - override lazy val filterQuery = new ParentFilterQuery(db, publicProperties) + + val childTypes: PartialFunction[(ru.Type, String), ru.Type] = { + case (tpe, "case_task_log") if SubType(tpe, ru.typeOf[TaskSteps]) => ru.typeOf[LogSteps] + case (tpe, "case_task") if SubType(tpe, ru.typeOf[CaseSteps]) => ru.typeOf[TaskSteps] + case (tpe, "case_artifact") if SubType(tpe, ru.typeOf[CaseSteps]) => ru.typeOf[ObservableSteps] + } + val parentTypes: PartialFunction[ru.Type, ru.Type] = { + case tpe if SubType(tpe, ru.typeOf[TaskSteps]) => ru.typeOf[CaseSteps] + case tpe if SubType(tpe, ru.typeOf[ObservableSteps]) => ru.typeOf[CaseSteps] + case tpe if SubType(tpe, ru.typeOf[LogSteps]) => ru.typeOf[ObservableSteps] + } + override val customFilterQuery: FilterQuery = FilterQuery(db, publicProperties) { (tpe, globalParser) => + FieldsParser.debug("parentChildFilter") { + case (_, FObjOne("_parent", ParentIdFilter(_, parentId))) if parentTypes.isDefinedAt(tpe) => + Good(new ParentIdInputFilter(parentId)) + case (path, FObjOne("_parent", ParentQueryFilter(_, parentFilterField))) if parentTypes.isDefinedAt(tpe) => + globalParser(parentTypes(tpe)).apply(path, parentFilterField).map(query => new ParentQueryInputFilter(query)) + case (path, FObjOne("_child", ChildQueryFilter(childType, childQueryField))) if childTypes.isDefinedAt((tpe, childType)) => + globalParser(childTypes((tpe, childType))).apply(path, childQueryField).map(query => new ChildQueryInputFilter(childType, query)) + } + } override lazy val queries: Seq[ParamQuery[_]] = controllers.map(_.initialQuery) ::: @@ -85,7 +105,7 @@ class ParentIdInputFilter(parentId: String) extends InputFilter { if (stepType =:= ru.typeOf[TaskSteps]) step.asInstanceOf[TaskSteps].filter(_.`case`.getByIds(parentId)).asInstanceOf[S] else if (stepType =:= ru.typeOf[ObservableSteps]) step.asInstanceOf[ObservableSteps].filter(_.`case`.getByIds(parentId)).asInstanceOf[S] else if (stepType =:= ru.typeOf[LogSteps]) step.asInstanceOf[LogSteps].filter(_.task.getByIds(parentId)).asInstanceOf[S] - else ??? + else throw BadRequestError(s"$stepType hasn't parent") } object ParentQueryFilter { @@ -113,51 +133,34 @@ class ParentQueryInputFilter(parentFilter: InputFilter) extends InputFilter { step.filter(t => parentFilter.apply(db, publicProperties, ru.typeOf[CaseSteps], t.asInstanceOf[ObservableSteps].`case`, authContext)) else if (stepType =:= ru.typeOf[LogSteps]) step.filter(t => parentFilter.apply(db, publicProperties, ru.typeOf[TaskSteps], t.asInstanceOf[LogSteps].task, authContext)) - else ??? - -// vertexSteps -// .filter { s => -// if (stepType =:= ru.typeOf[TaskSteps]) -// parentFilter.apply(db, publicProperties, ru.typeOf[CaseSteps], new TaskSteps(s)(db, graph).`case`, authContext).raw -// else if (stepType =:= ru.typeOf[ObservableSteps]) -// parentFilter.apply(db, publicProperties, ru.typeOf[CaseSteps], new ObservableSteps(s)(db, graph).`case`, authContext).raw -// else if (stepType =:= ru.typeOf[LogSteps]) -// parentFilter.apply(db, publicProperties, ru.typeOf[TaskSteps], new LogSteps(s)(db, graph).task, authContext).raw -// else ??? -// } -// .asInstanceOf[S] + else throw BadRequestError(s"$stepType hasn't parent") } -class ParentFilterQuery(db: Database, publicProperties: List[PublicProperty[_, _]]) extends FilterQuery(db, publicProperties) { - override val name: String = "filter" - - override def paramParser(tpe: ru.Type, properties: Seq[PublicProperty[_, _]]): FieldsParser[InputFilter] = - FieldsParser("parentIdFilter") { - case (path, FObjOne("_and", FSeq(fields))) => - fields - .zipWithIndex - .validatedBy { case (field, index) => paramParser(tpe, properties)((path :/ "_and").toSeq(index), field) } - .map(InputFilter.and) - case (path, FObjOne("_or", FSeq(fields))) => - fields - .zipWithIndex - .validatedBy { case (field, index) => paramParser(tpe, properties)((path :/ "_or").toSeq(index), field) } - .map(InputFilter.or) - case (path, FObjOne("_not", field)) => paramParser(tpe, properties)(path :/ "_not", field).map(InputFilter.not) - case (_, FObjOne("_parent", ParentIdFilter(_, parentId))) => Good(new ParentIdInputFilter(parentId)) - case (path, FObjOne("_parent", ParentQueryFilter(_, queryField))) => - paramParser(tpe, properties).apply(path, queryField).map(query => new ParentQueryInputFilter(query)) - }.orElse(InputFilter.fieldsParser(tpe, properties)) +object ChildQueryFilter { + def unapply(field: Field): Option[(String, Field)] = + FieldsParser + .string + .on("_type") + .map("childQuery")(childType => (childType, field.get("_query"))) + .apply(field) + .fold(Some(_), _ => None) +} - override def checkFrom(t: ru.Type): Boolean = - SubType(t, ru.typeOf[TaskSteps]) || SubType(t, ru.typeOf[ObservableSteps]) || SubType(t, ru.typeOf[LogSteps]) - override def toType(t: ru.Type): ru.Type = t - override def apply(inputFilter: InputFilter, from: Any, authContext: AuthContext): Any = - inputFilter( - db, - publicProperties, - rm.classSymbol(from.getClass).toType, - from.asInstanceOf[BaseVertexSteps], - authContext - ) +class ChildQueryInputFilter(childType: String, childFilter: InputFilter) extends InputFilter { + override def apply[S <: BaseVertexSteps]( + db: Database, + publicProperties: List[PublicProperty[_, _]], + stepType: ru.Type, + step: S, + authContext: AuthContext + ): S = + if (stepType =:= ru.typeOf[CaseSteps] && childType == "case_task") + step.filter(t => childFilter.apply(db, publicProperties, ru.typeOf[TaskSteps], t.asInstanceOf[CaseSteps].tasks(authContext), authContext)) + else if (stepType =:= ru.typeOf[CaseSteps] && childType == "case_artifact") + step.filter(t => + childFilter.apply(db, publicProperties, ru.typeOf[ObservableSteps], t.asInstanceOf[CaseSteps].observables(authContext), authContext) + ) + else if (stepType =:= ru.typeOf[TaskSteps] && childType == "case_task_log") + step.filter(t => childFilter.apply(db, publicProperties, ru.typeOf[LogSteps], t.asInstanceOf[TaskSteps].logs, authContext)) + else throw BadRequestError(s"$stepType hasn't child of type $childType") }