Skip to content

Commit

Permalink
#110 Add responder APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jul 6, 2018
1 parent 81d2a78 commit 4555bf3
Show file tree
Hide file tree
Showing 27 changed files with 840 additions and 497 deletions.
7 changes: 3 additions & 4 deletions app/org/thp/cortex/controllers/AnalyzerConfigCtrl.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package org.thp.cortex.controllers

import javax.inject.{ Inject, Singleton }

import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.JsObject
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }

import org.thp.cortex.models.Roles
import org.thp.cortex.services.{ AnalyzerConfigSrv, BaseConfig, UserSrv }
import org.thp.cortex.models.{ BaseConfig, Roles }
import org.thp.cortex.services.{ AnalyzerConfigSrv, UserSrv }

import org.elastic4play.BadRequestError
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
Expand All @@ -29,7 +28,7 @@ class AnalyzerConfigCtrl @Inject() (
}

def list(): Action[AnyContent] = authenticated(Roles.orgAdmin).async { request
analyzerConfigSrv.listForUser(request.userId)
analyzerConfigSrv.listConfigForUser(request.userId)
.map { bc
renderer.toOutput(OK, bc.sortWith {
case (BaseConfig("global", _, _, _), _) true
Expand Down
44 changes: 23 additions & 21 deletions app/org/thp/cortex/controllers/AnalyzerCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package org.thp.cortex.controllers
import javax.inject.{ Inject, Singleton }
import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.{ JsNumber, JsObject, Json }
import play.api.libs.json.{ JsNumber, JsObject, JsString, Json }
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }

import akka.stream.Materializer
import akka.stream.scaladsl.Sink
import org.thp.cortex.models.{ Analyzer, AnalyzerDefinition, Roles }
import org.thp.cortex.services.{ AnalyzerSrv, UserSrv }
import org.thp.cortex.models.{ Roles, Worker, WorkerDefinition }
import org.thp.cortex.services.{ UserSrv, WorkerSrv }

import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
import org.elastic4play.models.JsonFormat.baseModelEntityWrites
Expand All @@ -18,7 +18,7 @@ import org.elastic4play.services.{ QueryDSL, QueryDef }

@Singleton
class AnalyzerCtrl @Inject() (
analyzerSrv: AnalyzerSrv,
workerSrv: WorkerSrv,
userSrv: UserSrv,
authenticated: Authenticated,
fieldsBodyParser: FieldsBodyParser,
Expand All @@ -32,14 +32,14 @@ class AnalyzerCtrl @Inject() (
val range = request.body.getString("range")
val sort = request.body.getStrings("sort").getOrElse(Nil)
val isAdmin = request.roles.contains(Roles.orgAdmin)
val (analyzers, analyzerTotal) = analyzerSrv.findForUser(request.userId, query, range, sort)
val (analyzers, analyzerTotal) = workerSrv.findAnalyzersForUser(request.userId, query, range, sort)
val enrichedAnalyzers = analyzers.mapAsync(2)(analyzerJson(isAdmin))
renderer.toOutput(OK, enrichedAnalyzers, analyzerTotal)
}

def get(analyzerId: String): Action[AnyContent] = authenticated(Roles.read).async { request
val isAdmin = request.roles.contains(Roles.orgAdmin)
analyzerSrv.getForUser(request.userId, analyzerId)
workerSrv.getForUser(request.userId, analyzerId)
.flatMap(analyzerJson(isAdmin))
.map(renderer.toOutput(OK, _))
}
Expand All @@ -52,7 +52,7 @@ class AnalyzerCtrl @Inject() (
"url" -> "unknown",
"license" -> "unknown")

private def analyzerJson(analyzer: Analyzer, analyzerDefinition: Option[AnalyzerDefinition]) = {
private def analyzerJson(analyzer: Worker, analyzerDefinition: Option[WorkerDefinition]) = {
analyzer.toJson ++ analyzerDefinition.fold(emptyAnalyzerDefinitionJson) { ad
Json.obj(
"maxTlp" -> (analyzer.config \ "max_tlp").asOpt[JsNumber],
Expand All @@ -63,11 +63,11 @@ class AnalyzerCtrl @Inject() (
"url" -> ad.url,
"license" -> ad.license,
"baseConfig" -> ad.baseConfiguration)
}
} + ("analyzerDefinitionId" -> JsString(analyzer.workerDefinitionId())) // For compatibility reason
}

private def analyzerJson(isAdmin: Boolean)(analyzer: Analyzer): Future[JsObject] = {
analyzerSrv.getDefinition(analyzer.analyzerDefinitionId())
private def analyzerJson(isAdmin: Boolean)(analyzer: Worker): Future[JsObject] = {
workerSrv.getDefinition(analyzer.workerDefinitionId())
.map(analyzerDefinition analyzerJson(analyzer, Some(analyzerDefinition)))
.recover { case _ analyzerJson(analyzer, None) }
.map {
Expand All @@ -78,10 +78,10 @@ class AnalyzerCtrl @Inject() (

def listForType(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { request
import org.elastic4play.services.QueryDSL._
analyzerSrv.findForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
workerSrv.findAnalyzersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
._1
.mapAsyncUnordered(2) { analyzer
analyzerSrv.getDefinition(analyzer.analyzerDefinitionId())
workerSrv.getDefinition(analyzer.workerDefinitionId())
.map(ad analyzerJson(analyzer, Some(ad)))
}
.runWith(Sink.seq)
Expand All @@ -91,31 +91,33 @@ class AnalyzerCtrl @Inject() (
def create(analyzerDefinitionId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
for {
organizationId userSrv.getOrganizationId(request.userId)
analyzer analyzerSrv.create(organizationId, analyzerDefinitionId, request.body)
} yield renderer.toOutput(CREATED, analyzer)
workerDefinition workerSrv.getDefinition(analyzerDefinitionId)
analyzer workerSrv.create(organizationId, workerDefinition, request.body)
} yield renderer.toOutput(CREATED, analyzerJson(analyzer, Some(workerDefinition)))
}

def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
val (analyzers, analyzerTotal) = analyzerSrv.listDefinitions
val (analyzers, analyzerTotal) = workerSrv.listAnalyzerDefinitions
renderer.toOutput(OK, analyzers, analyzerTotal)
}

def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { implicit request
analyzerSrv.rescan()
workerSrv.rescan()
NoContent
}

def delete(analyzerId: String): Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
for {
analyzer analyzerSrv.getForUser(request.userId, analyzerId)
_ analyzerSrv.delete(analyzer)
analyzer workerSrv.getForUser(request.userId, analyzerId)
_ workerSrv.delete(analyzer)
} yield NoContent
}

def update(analyzerId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
for {
analyzer analyzerSrv.getForUser(request.userId, analyzerId)
updatedAnalyzer analyzerSrv.update(analyzer, request.body)
} yield renderer.toOutput(OK, updatedAnalyzer)
analyzer workerSrv.getForUser(request.userId, analyzerId)
updatedAnalyzer workerSrv.update(analyzer, request.body)
updatedAnalyzerJson analyzerJson(isAdmin = true)(updatedAnalyzer)
} yield renderer.toOutput(OK, updatedAnalyzerJson)
}
}
4 changes: 2 additions & 2 deletions app/org/thp/cortex/controllers/MispCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.thp.cortex.controllers
import javax.inject.Inject
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
import org.thp.cortex.models.Roles
import org.thp.cortex.services.{ AnalyzerSrv, MispSrv }
import org.thp.cortex.services.{ WorkerSrv, MispSrv }
import play.api.Logger
import play.api.libs.json.{ JsObject, JsValue }
import play.api.mvc._
Expand All @@ -12,7 +12,7 @@ import scala.concurrent.{ ExecutionContext, Future }

class MispCtrl @Inject() (
mispSrv: MispSrv,
analyzerSrv: AnalyzerSrv,
analyzerSrv: WorkerSrv,
authenticated: Authenticated,
fieldsBodyParser: FieldsBodyParser,
renderer: Renderer,
Expand Down
48 changes: 48 additions & 0 deletions app/org/thp/cortex/controllers/ResponderConfigCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.thp.cortex.controllers

import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.JsObject
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }

import javax.inject.{ Inject, Singleton }
import org.thp.cortex.models.{ BaseConfig, Roles }
import org.thp.cortex.services.{ AnalyzerConfigSrv, UserSrv }

import org.elastic4play.BadRequestError
import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }

@Singleton
class ResponderConfigCtrl @Inject() (
analyzerConfigSrv: AnalyzerConfigSrv,
userSrv: UserSrv,
authenticated: Authenticated,
fieldsBodyParser: FieldsBodyParser,
renderer: Renderer,
components: ControllerComponents,
implicit val ec: ExecutionContext) extends AbstractController(components) {

def get(analyzerConfigName: String): Action[AnyContent] = authenticated(Roles.orgAdmin).async { request
analyzerConfigSrv.getForUser(request.userId, analyzerConfigName)
.map(renderer.toOutput(OK, _))
}

def list(): Action[AnyContent] = authenticated(Roles.orgAdmin).async { request
analyzerConfigSrv.listConfigForUser(request.userId)
.map { bc
renderer.toOutput(OK, bc.sortWith {
case (BaseConfig("global", _, _, _), _) true
case (_, BaseConfig("global", _, _, _)) false
case (BaseConfig(a, _, _, _), BaseConfig(b, _, _, _)) a.compareTo(b) < 0
})
}
}

def update(analyzerConfigName: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
request.body.getValue("config").flatMap(_.asOpt[JsObject]) match {
case Some(config) analyzerConfigSrv.updateOrCreate(request.userId, analyzerConfigName, config)
.map(renderer.toOutput(OK, _))
case None Future.failed(BadRequestError("attribute config has invalid format"))
}
}
}
123 changes: 123 additions & 0 deletions app/org/thp/cortex/controllers/ResponderCtrl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package org.thp.cortex.controllers

import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.{ JsNumber, JsObject, Json }
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }

import akka.stream.Materializer
import akka.stream.scaladsl.Sink
import javax.inject.{ Inject, Singleton }
import org.thp.cortex.models.{ Roles, Worker, WorkerDefinition }
import org.thp.cortex.services.{ UserSrv, WorkerSrv }

import org.elastic4play.controllers.{ Authenticated, Fields, FieldsBodyParser, Renderer }
import org.elastic4play.models.JsonFormat.baseModelEntityWrites
import org.elastic4play.services.JsonFormat.queryReads
import org.elastic4play.services.{ QueryDSL, QueryDef }

@Singleton
class ResponderCtrl @Inject() (
workerSrv: WorkerSrv,
userSrv: UserSrv,
authenticated: Authenticated,
fieldsBodyParser: FieldsBodyParser,
renderer: Renderer,
components: ControllerComponents,
implicit val ec: ExecutionContext,
implicit val mat: Materializer) extends AbstractController(components) {

def find: Action[Fields] = authenticated(Roles.read).async(fieldsBodyParser) { request
val query = request.body.getValue("query").fold[QueryDef](QueryDSL.any)(_.as[QueryDef])
val range = request.body.getString("range")
val sort = request.body.getStrings("sort").getOrElse(Nil)
val isAdmin = request.roles.contains(Roles.orgAdmin)
val (responders, responderTotal) = workerSrv.findRespondersForUser(request.userId, query, range, sort)
val enrichedResponders = responders.mapAsync(2)(responderJson(isAdmin))
renderer.toOutput(OK, enrichedResponders, responderTotal)
}

def get(responderId: String): Action[AnyContent] = authenticated(Roles.read).async { request
val isAdmin = request.roles.contains(Roles.orgAdmin)
workerSrv.getForUser(request.userId, responderId)
.flatMap(responderJson(isAdmin))
.map(renderer.toOutput(OK, _))
}

private val emptyResponderDefinitionJson = Json.obj(
"version" -> "0.0",
"description" -> "unknown",
"dataTypeList" -> Nil,
"author" -> "unknown",
"url" -> "unknown",
"license" -> "unknown")

private def responderJson(responder: Worker, responderDefinition: Option[WorkerDefinition]) = {
responder.toJson ++ responderDefinition.fold(emptyResponderDefinitionJson) { ad
Json.obj(
"maxTlp" -> (responder.config \ "max_tlp").asOpt[JsNumber],
"maxPap" -> (responder.config \ "max_pap").asOpt[JsNumber],
"version" -> ad.version,
"description" -> ad.description,
"author" -> ad.author,
"url" -> ad.url,
"license" -> ad.license,
"baseConfig" -> ad.baseConfiguration)
}
}

private def responderJson(isAdmin: Boolean)(responder: Worker): Future[JsObject] = {
workerSrv.getDefinition(responder.workerDefinitionId())
.map(responderDefinition responderJson(responder, Some(responderDefinition)))
.recover { case _ responderJson(responder, None) }
.map {
case a if isAdmin a + ("configuration" -> Json.parse(responder.configuration()))
case a a
}
}

def listForType(dataType: String): Action[AnyContent] = authenticated(Roles.read).async { request
import org.elastic4play.services.QueryDSL._
workerSrv.findRespondersForUser(request.userId, "dataTypeList" ~= dataType, Some("all"), Nil)
._1
.mapAsyncUnordered(2) { responder
workerSrv.getDefinition(responder.workerDefinitionId())
.map(ad responderJson(responder, Some(ad)))
}
.runWith(Sink.seq)
.map(responders renderer.toOutput(OK, responders))
}

def create(responderDefinitionId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
for {
organizationId userSrv.getOrganizationId(request.userId)
workerDefinition workerSrv.getDefinition(responderDefinitionId)
responder workerSrv.create(organizationId, workerDefinition, request.body)
} yield renderer.toOutput(CREATED, responderJson(responder, Some(workerDefinition)))
}

def listDefinitions: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
val (responders, responderTotal) = workerSrv.listResponderDefinitions
renderer.toOutput(OK, responders, responderTotal)
}

def scan: Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin) { implicit request
workerSrv.rescan()
NoContent
}

def delete(responderId: String): Action[AnyContent] = authenticated(Roles.orgAdmin, Roles.superAdmin).async { implicit request
for {
responder workerSrv.getForUser(request.userId, responderId)
_ workerSrv.delete(responder)
} yield NoContent
}

def update(responderId: String): Action[Fields] = authenticated(Roles.orgAdmin).async(fieldsBodyParser) { implicit request
for {
responder workerSrv.getForUser(request.userId, responderId)
updatedResponder workerSrv.update(responder, request.body)
updatedResponderJson responderJson(isAdmin = true)(updatedResponder)
} yield renderer.toOutput(OK, updatedResponderJson)
}
}
4 changes: 2 additions & 2 deletions app/org/thp/cortex/controllers/StatusCtrl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import play.api.libs.json.Json.toJsFieldJsValueWrapper
import play.api.mvc.{ AbstractController, Action, AnyContent, ControllerComponents }

import com.sksamuel.elastic4s.ElasticDsl
import org.thp.cortex.models.Analyzer
import org.thp.cortex.models.Worker

import org.elastic4play.database.DBIndex
import org.elastic4play.services.AuthSrv
Expand All @@ -31,7 +31,7 @@ class StatusCtrl @Inject() (
dbIndex.clusterVersions.map { versions
Ok(Json.obj(
"versions" Json.obj(
"Cortex" getVersion(classOf[Analyzer]),
"Cortex" getVersion(classOf[Worker]),
"Elastic4Play" getVersion(classOf[AuthSrv]),
"Play" getVersion(classOf[AbstractController]),
"Elastic4s" getVersion(classOf[ElasticDsl]),
Expand Down
22 changes: 0 additions & 22 deletions app/org/thp/cortex/models/AnalyzerConfig.scala

This file was deleted.

Loading

0 comments on commit 4555bf3

Please sign in to comment.