Skip to content

Commit

Permalink
#2149 Add global search API
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jul 29, 2021
1 parent 8d3541d commit c3057fe
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
4 changes: 3 additions & 1 deletion thehive/app/org/thp/thehive/controllers/v1/Router.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Router(
profileCtrl: ProfileCtrl,
tagCtrl: TagCtrl,
taskCtrl: TaskCtrl,
searchCtrl: SearchCtrl,
shareCtrl: ShareCtrl,
taxonomyCtrl: TaxonomyCtrl,
// shareCtrl: ShareCtrl,
Expand Down Expand Up @@ -186,6 +187,7 @@ class Router(
case POST(p"/observable/type") => observableTypeCtrl.create
case DELETE(p"/observable/type/$idOrName") => observableTypeCtrl.delete(idOrName)

case GET(p"/monitor/disk") => monitoringCtrl.diskUsage
case GET(p"/search" ? q"query=$query") => searchCtrl.search(query)
case GET(p"/monitor/disk") => monitoringCtrl.diskUsage
}
}
90 changes: 90 additions & 0 deletions thehive/app/org/thp/thehive/controllers/v1/SearchCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.thp.thehive.controllers.v1

import org.apache.tinkerpop.gremlin.process.traversal.P
import org.apache.tinkerpop.gremlin.structure.Vertex
import org.thp.scalligraph.controllers.Entrypoint
import org.thp.scalligraph.models._
import org.thp.scalligraph.traversal.{IdentityConverter, Traversal}
import org.thp.thehive.controllers.v1.Conversion._
import org.thp.thehive.models._
import org.thp.thehive.services.{CustomFieldSrv, OrganisationSrv, TheHiveOps}
import play.api.libs.json.JsValue
import play.api.mvc.{Action, AnyContent, Results}

import scala.util.Success

class SearchCtrl(
entrypoint: Entrypoint,
schema: Schema,
db: Database,
override val organisationSrv: OrganisationSrv,
override val customFieldSrv: CustomFieldSrv
) extends TheHiveOps {

private def isStringField(fieldName: String): Boolean =
schema.modelList.exists(_.fields.get(fieldName).exists(_.graphTypeClass == classOf[String]))

lazy val (stdFields, fullTextFields, fullTextOnlyFields) = schema
.modelList
.flatMap(_.indexes)
.foldLeft((Set.empty[String], Set.empty[String], Set.empty[String])) {
case ((std, ft, fto), (IndexType.standard, f)) => (std ++ f.filter(isStringField), ft, fto)
case ((std, ft, fto), (IndexType.fulltext, f)) => (std, ft ++ f.filter(isStringField), fto)
case ((std, ft, fto), (IndexType.fulltextOnly, f)) => (std, ft, fto ++ f.filter(isStringField))
case (i, _) => i
}

def search(query: String): Action[AnyContent] =
entrypoint("search").auth { implicit request =>
val results = db.source[JsValue] { implicit graph =>
val filters = stdFields
.toSeq
.flatMap(f =>
Seq(
(_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, TextPredicate.startsWith(query)),
(_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, P.eq(query))
)
) ++
fullTextFields
.toSeq
.flatMap(f =>
Seq(
(_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, FullTextPredicate.contains(query)),
(_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, TextPredicate.startsWith(query)),
(_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, P.eq(query))
)
) ++
fullTextOnlyFields.toSeq.map(f => (_: Traversal[Vertex, Vertex, IdentityConverter[Vertex]]).unsafeHas(f, FullTextPredicate.contains(query)))

if (filters.isEmpty) Iterator.empty
else
graph
.VV()
.or(filters: _*)
.chooseValue(
_.on(_.label)
.option("Alert", _.v[Alert].visible.richAlert.domainMap(_.toJson))
.option("Audit", _.v[Audit].visible.richAudit.domainMap(_.toJson))
.option("Case", _.v[Case].visible.richCase.domainMap(_.toJson))
.option("CaseTemplate", _.v[CaseTemplate].visible.richCaseTemplate.domainMap(_.toJson))
.option("CustomField", _.v[CustomField].domainMap(_.toJson))
// .option("Dashboard", _.v[Dashboard].domainMap(_.toJson))
.option("Log", _.v[Log].visible.richLog.domainMap(_.toJson))
.option("Observable", _.v[Observable].visible.richObservable.domainMap(_.toJson))
.option("ObservableType", _.v[ObservableType].domainMap(_.toJson))
.option("Organisation", _.v[Organisation].visible.richOrganisation.domainMap(_.toJson))
// // pageCtrl,
.option("Pattern", _.v[Pattern].richPattern.domainMap(_.toJson))
.option("Procedure", _.v[Procedure].visible.richProcedure.domainMap(_.toJson))
.option("Profile", _.v[Profile].domainMap(_.toJson))
.option("Share", _.v[Share].visible.richShare.domainMap(_.toJson))
.option("Tag", _.v[Tag].visible.domainMap(_.toJson))
.option("Task", _.v[Task].visible.richTask.domainMap(_.toJson))
.option("Taxonomy", _.v[Taxonomy].visible.richTaxonomy.domainMap(_.toJson))
.option("User", _.v[User].visible.richUser.domainMap(_.toJson))
)
.toIterator
}
Success(Results.Ok.chunked(results.map(_.toString).intersperse("[", ",", "]"), Some("application/json")))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class TheHiveModuleV1(app: ScalligraphApplication) extends ScalligraphModule {
lazy val adminCtrl: AdminCtrl = wire[AdminCtrl]
lazy val taxonomyCtrl: TaxonomyCtrl = wire[TaxonomyCtrl]
lazy val monitoringCtrl: MonitoringCtrl = wire[MonitoringCtrl]
lazy val searchCtrl: SearchCtrl = wire[SearchCtrl]
lazy val theHiveModelDescription: TheHiveModelDescription = wire[TheHiveModelDescription]
theHiveModule.entityDescriptions += (1 -> theHiveModelDescription)

Expand Down

0 comments on commit c3057fe

Please sign in to comment.