Skip to content

Commit

Permalink
#1404 Move initial values from service to model layer
Browse files Browse the repository at this point in the history
  • Loading branch information
To-om committed Jun 25, 2020
1 parent d0f68b4 commit 71266e1
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package org.thp.thehive.connector.cortex.services

import org.thp.cortex.client.{CortexClient, TestCortexClientProvider}
import org.thp.scalligraph.AppBuilder
import org.thp.scalligraph.models.{Database, Schema}
import org.thp.scalligraph.steps.StepsOps._
import org.thp.thehive.TestAppBuilder
import org.thp.thehive.{BasicDatabaseProvider, TestAppBuilder}
import org.thp.thehive.connector.cortex.models.TheHiveCortexSchemaProvider
import org.thp.thehive.models.Organisation
import org.thp.thehive.services._
import play.api.test.PlaySpecification

import org.thp.scalligraph.AppBuilder
import org.thp.thehive.connector.cortex.models.TheHiveCortexSchemaProvider

class ServiceHelperTest extends PlaySpecification with TestAppBuilder {
override val databaseName: String = "thehiveCortex"
override def appConfigure: AppBuilder =
Expand All @@ -23,6 +22,7 @@ class ServiceHelperTest extends PlaySpecification with TestAppBuilder {
.bind[Connector, TestConnector]
.bindToProvider[Schema, TheHiveCortexSchemaProvider]
)
.bindNamedToProvider[Database, BasicDatabaseProvider]("with-thehive-cortex-schema")

"service helper" should {
"filter properly organisations according to supplied config" in testApp { app =>
Expand All @@ -35,7 +35,7 @@ class ServiceHelperTest extends PlaySpecification with TestAppBuilder {
)
.toList
}
r must contain(OrganisationSrv.administration)
r must contain(Organisation.administration)

val r2 = app[Database].roTransaction { implicit graph =>
app[ServiceHelper]
Expand All @@ -46,12 +46,12 @@ class ServiceHelperTest extends PlaySpecification with TestAppBuilder {
)
.toList
}
r2 must contain(OrganisationSrv.administration, Organisation("cert", "cert"))
r2 must contain(Organisation.administration, Organisation("cert", "cert"))
}

"return the correct filtered CortexClient list" in testApp { app =>
val client = app[CortexClient]
val r = app[ServiceHelper].availableCortexClients(Seq(client), OrganisationSrv.administration.name)
val r = app[ServiceHelper].availableCortexClients(Seq(client), Organisation.administration.name)

r must contain(client)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.thp.thehive.connector.cortex.models.{Action, Job, JobStatus}
import org.thp.thehive.controllers.v0
import org.thp.thehive.migration.dto._
import org.thp.thehive.models._
import org.thp.thehive.services.{OrganisationSrv, ProfileSrv, UserSrv}
import play.api.libs.functional.syntax._
import play.api.libs.json._

Expand Down Expand Up @@ -63,8 +62,10 @@ trait Conversion {
status <- (json \ "status").validate[CaseStatus.Value]
summary <- (json \ "summary").validateOpt[String]
user <- (json \ "owner").validateOpt[String]
tags = (json \ "tags").asOpt[Set[String]].getOrElse(Set.empty)
metrics = (json \ "metrics").asOpt[JsObject].getOrElse(JsObject.empty)
tags = (json \ "tags").asOpt[Set[String]].getOrElse(Set.empty)
metrics = (json \ "metrics").asOpt[JsObject].getOrElse(JsObject.empty)
resolutionStatus = (json \ "resolutionStatus").asOpt[String]
impactStatus = (json \ "impactStatus").asOpt[String]
metricsValue = metrics.value.map {
case (name, value) => name -> Some(value)
}
Expand All @@ -76,10 +77,12 @@ trait Conversion {
} yield InputCase(
Case(number, title, description, severity, startDate, endDate, flag, tlp, pap, status, summary),
user.map(normaliseLogin),
Map(mainOrganisation -> ProfileSrv.orgAdmin.name),
Map(mainOrganisation -> Profile.orgAdmin.name),
tags,
(metricsValue ++ customFieldsValue).toMap,
None,
resolutionStatus,
impactStatus,
metaData
)
}
Expand Down Expand Up @@ -254,18 +257,18 @@ trait Conversion {
locked = status == "Locked"
password <- (json \ "password").validateOpt[String]
role <- (json \ "roles").validateOpt[Seq[String]].map(_.getOrElse(Nil))
profile = if (role.contains("admin")) ProfileSrv.admin.name
else if (role.contains("write")) ProfileSrv.analyst.name
else if (role.contains("read")) ProfileSrv.readonly.name
else ProfileSrv.readonly.name
organisationProfiles = if (role.contains("admin"))
Map(Organisation.administration.name -> Profile.admin.name, mainOrganisation -> Profile.analyst.name)
else if (role.contains("write")) Map(mainOrganisation -> Profile.analyst.name)
else if (role.contains("read")) Map(mainOrganisation -> Profile.readonly.name)
else Map(mainOrganisation -> Profile.readonly.name)
avatar = (json \ "avatar")
.asOpt[String]
.map { base64 =>
val data = Base64.getDecoder.decode(base64)
InputAttachment(s"$login.avatar", data.size.toLong, "image/png", Nil, Source.single(ByteString(data)))
}
organisation = if (profile == ProfileSrv.admin.name) OrganisationSrv.administration.name else mainOrganisation
} yield InputUser(metaData, User(normaliseLogin(login), name, apikey, locked, password, None), Map(organisation -> profile), avatar)
} yield InputUser(metaData, User(normaliseLogin(login), name, apikey, locked, password, None), organisationProfiles, avatar)
}

val metricsReads: Reads[InputCustomField] = Reads[InputCustomField] { json =>
Expand All @@ -276,7 +279,7 @@ trait Conversion {
// title <- (value \ "title").validate[String]
description <- (value \ "description").validate[String]
} yield InputCustomField(
MetaData(name, UserSrv.init.login, new Date, None, None),
MetaData(name, User.init.login, new Date, None, None),
CustomField(name, name, description, CustomFieldType.integer, mandatory = true, Nil)
)
}
Expand All @@ -300,7 +303,7 @@ trait Conversion {
}
options = (value \ "options").asOpt[Seq[JsValue]].getOrElse(Nil)
} yield InputCustomField(
MetaData(name, UserSrv.init.login, new Date, None, None),
MetaData(name, User.init.login, new Date, None, None),
CustomField(name, displayName, description, customFieldType, mandatory = false, options)
)
} orElse metricsReads
Expand All @@ -311,7 +314,7 @@ trait Conversion {
valueJson <- (json \ "value").validate[String]
value = Json.parse(valueJson)
name <- value.validate[String]
} yield InputObservableType(MetaData(name, UserSrv.init.login, new Date, None, None), ObservableType(name, name == "file"))
} yield InputObservableType(MetaData(name, User.init.login, new Date, None, None), ObservableType(name, name == "file"))
}

implicit val caseTemplateReads: Reads[InputCaseTemplate] = Reads[InputCaseTemplate] { json =>
Expand Down
3 changes: 1 addition & 2 deletions thehive/app/org/thp/thehive/controllers/v0/Conversion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.thp.scalligraph.controllers.Renderer
import org.thp.scalligraph.models.Entity
import org.thp.thehive.dto.v0._
import org.thp.thehive.models._
import org.thp.thehive.services.ProfileSrv
import play.api.libs.json.{JsObject, JsValue, Json, Writes}

object Conversion {
Expand Down Expand Up @@ -486,7 +485,7 @@ object Conversion {
.withFieldConst(_.createdBy, profile._createdBy)
.withFieldConst(_._type, "profile")
.withFieldComputed(_.permissions, _.permissions.asInstanceOf[Set[String]].toSeq.sorted) // Permission is String
.withFieldComputed(_.editable, ProfileSrv.isEditable)
.withFieldComputed(_.editable, _.isEditable)
.withFieldComputed(_.isAdmin, p => Permissions.containsRestricted(p.permissions))
.transform
)
Expand Down
3 changes: 1 addition & 2 deletions thehive/app/org/thp/thehive/controllers/v1/Conversion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.thp.scalligraph.controllers.Renderer
import org.thp.scalligraph.models.Entity
import org.thp.thehive.dto.v1._
import org.thp.thehive.models._
import org.thp.thehive.services.ProfileSrv
import play.api.libs.json.{JsObject, JsValue}

object Conversion {
Expand Down Expand Up @@ -224,7 +223,7 @@ object Conversion {
.withFieldConst(_._createdBy, profile._createdBy)
.withFieldConst(_._type, "Profile")
.withFieldComputed(_.permissions, _.permissions.asInstanceOf[Set[String]].toSeq.sorted) // Permission is String
.withFieldComputed(_.editable, ProfileSrv.isEditable)
.withFieldComputed(_.editable, _.isEditable)
.withFieldComputed(_.isAdmin, p => Permissions.containsRestricted(p.permissions))
.transform
)
Expand Down
20 changes: 18 additions & 2 deletions thehive/app/org/thp/thehive/models/Case.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package org.thp.thehive.models

import java.util.Date

import play.api.libs.json.{Format, Json}

import org.thp.scalligraph._
import org.thp.scalligraph.auth.Permission
import org.thp.scalligraph.models.{DefineIndex, Entity, IndexType, Model}
import play.api.libs.json.{Format, Json}

object CaseStatus extends Enumeration {
val Open, Resolved, Deleted, Duplicated = Value
Expand All @@ -19,6 +18,16 @@ case class ResolutionStatus(value: String) {
require(!value.isEmpty, "ResolutionStatus can't be empty")
}

object ResolutionStatus {
val indeterminate: ResolutionStatus = ResolutionStatus("Indeterminate")
val falsePositive: ResolutionStatus = ResolutionStatus("FalsePositive")
val truePositive: ResolutionStatus = ResolutionStatus("TruePositive")
val other: ResolutionStatus = ResolutionStatus("Other")
val duplicated: ResolutionStatus = ResolutionStatus("Duplicated")

val initialValues = Seq(indeterminate, falsePositive, truePositive, other, duplicated)
}

@EdgeEntity[Case, ResolutionStatus]
case class CaseResolutionStatus()

Expand All @@ -27,6 +36,13 @@ case class ImpactStatus(value: String) {
require(!value.isEmpty, "ImpactStatus can't be empty")
}

object ImpactStatus {
val noImpact: ImpactStatus = ImpactStatus("NoImpact")
val withImpact: ImpactStatus = ImpactStatus("WithImpact")
val notApplicable: ImpactStatus = ImpactStatus("NotApplicable")
val initialValues: Seq[ImpactStatus] = Seq(noImpact, withImpact, notApplicable)
}

@EdgeEntity[Case, ImpactStatus]
case class CaseImpactStatus()

Expand Down
21 changes: 21 additions & 0 deletions thehive/app/org/thp/thehive/models/ObservableType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,24 @@ case class ObservableObservableType()

@VertexEntity
case class ObservableType(name: String, isAttachment: Boolean)

object ObservableType {
val initialValues: Seq[ObservableType] = Seq(
ObservableType("url", isAttachment = false),
ObservableType("other", isAttachment = false),
ObservableType("user-agent", isAttachment = false),
ObservableType("regexp", isAttachment = false),
ObservableType("mail-subject", isAttachment = false),
ObservableType("registry", isAttachment = false),
ObservableType("mail", isAttachment = false),
ObservableType("autonomous-system", isAttachment = false),
ObservableType("domain", isAttachment = false),
ObservableType("ip", isAttachment = false),
ObservableType("uri_path", isAttachment = false),
ObservableType("filename", isAttachment = false),
ObservableType("hash", isAttachment = false),
ObservableType("file", isAttachment = true),
ObservableType("fqdn", isAttachment = false),
ObservableType("hostname", isAttachment = false)
)
}
5 changes: 5 additions & 0 deletions thehive/app/org/thp/thehive/models/Organisation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import org.thp.scalligraph.{EdgeEntity, VertexEntity}
@DefineIndex(IndexType.unique, "name")
case class Organisation(name: String, description: String)

object Organisation {
val administration: Organisation = Organisation("admin", "organisation for administration")
val initialValues: Seq[Organisation] = Seq(administration)
}

@EdgeEntity[Organisation, Share]
case class OrganisationShare()

Expand Down
25 changes: 24 additions & 1 deletion thehive/app/org/thp/thehive/models/Role.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,30 @@ case class Role()

@DefineIndex(IndexType.unique, "name")
@VertexEntity
case class Profile(name: String, permissions: Set[Permission])
case class Profile(name: String, permissions: Set[Permission]) {
def isEditable: Boolean = name != Profile.admin.name && name != Profile.orgAdmin.name
}

object Profile {
val admin: Profile = Profile("admin", Permissions.adminPermissions)

val analyst: Profile = Profile(
"analyst",
Set(
Permissions.manageCase,
Permissions.manageObservable,
Permissions.manageAlert,
Permissions.manageTask,
Permissions.manageAction,
Permissions.manageShare,
Permissions.manageAnalyse,
Permissions.managePage
)
)
val readonly: Profile = Profile("read-only", Set.empty)
val orgAdmin: Profile = Profile("org-admin", Permissions.forScope("organisation"))
val initialValues: Seq[Profile] = Seq(admin, orgAdmin, analyst, readonly)
}

@EdgeEntity[Role, Profile]
case class RoleProfile()
Expand Down
19 changes: 19 additions & 0 deletions thehive/app/org/thp/thehive/models/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.util.Date
import org.thp.scalligraph.auth.{Permission, User => ScalligraphUser}
import org.thp.scalligraph.models._
import org.thp.scalligraph.{EdgeEntity, VertexEntity}
import org.thp.thehive.services.LocalPasswordAuthSrv

@EdgeEntity[User, Role]
case class UserRole()
Expand All @@ -22,6 +23,24 @@ case class User(login: String, name: String, apikey: Option[String], locked: Boo
override def toString: String = s"User($login,$name,$locked)"
}

object User {
val initPassword: String = "secret"

val init: User = User(
login = "[email protected]",
name = "Default admin user",
apikey = None,
locked = false,
password = Some(LocalPasswordAuthSrv.hashPassword(initPassword)),
totpSecret = None
)

val system: User =
User(login = "[email protected]", name = "TheHive system user", apikey = None, locked = false, password = None, totpSecret = None)

val initialValues: Seq[User] = Seq(init, system)
}

// avatar: Array[Byte],
// preference: JsObject)

Expand Down
45 changes: 27 additions & 18 deletions thehive/app/org/thp/thehive/services/ImpactStatusSrv.scala
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
package org.thp.thehive.services

import akka.actor.ActorRef
import gremlin.scala._
import javax.inject.{Inject, Singleton}
import javax.inject.{Inject, Named, Singleton}
import org.thp.scalligraph.EntitySteps
import org.thp.scalligraph.auth.AuthContext
import org.thp.scalligraph.models.{Database, Entity}
import org.thp.scalligraph.services.VertexSrv
import org.thp.scalligraph.services.{IntegrityCheckOps, VertexSrv}
import org.thp.scalligraph.steps.StepsOps._
import org.thp.scalligraph.steps.VertexSteps
import org.thp.thehive.models.ImpactStatus

import scala.util.Try

object ImpactStatusSrv {
val noImpact: ImpactStatus = ImpactStatus("NoImpact")
val withImpact: ImpactStatus = ImpactStatus("WithImpact")
val notApplicable: ImpactStatus = ImpactStatus("NotApplicable")

}
import scala.util.{Success, Try}

@Singleton
class ImpactStatusSrv @Inject() (implicit db: Database) extends VertexSrv[ImpactStatus, ImpactStatusSteps] {

override val initialValues: Seq[ImpactStatus] = Seq(
ImpactStatusSrv.noImpact,
ImpactStatusSrv.withImpact,
ImpactStatusSrv.notApplicable
)
class ImpactStatusSrv @Inject() (@Named("integrity-check-actor") integrityCheckActor: ActorRef)(
implicit @Named("with-thehive-schema") db: Database
) extends VertexSrv[ImpactStatus, ImpactStatusSteps] {

override def steps(raw: GremlinScala[Vertex])(implicit graph: Graph): ImpactStatusSteps = new ImpactStatusSteps(raw)

override def get(idOrName: String)(implicit graph: Graph): ImpactStatusSteps =
if (db.isValidId(idOrName)) getByIds(idOrName)
else initSteps.getByName(idOrName)

override def createEntity(e: ImpactStatus)(implicit graph: Graph, authContext: AuthContext): Try[ImpactStatus with Entity] = {
integrityCheckActor ! IntegrityCheckActor.EntityAdded("ImpactStatus")
super.createEntity(e)
}

def create(impactStatus: ImpactStatus)(implicit graph: Graph, authContext: AuthContext): Try[ImpactStatus with Entity] = createEntity(impactStatus)

override def exists(e: ImpactStatus)(implicit graph: Graph): Boolean = initSteps.getByName(e.value).exists()
}

@EntitySteps[ImpactStatus]
class ImpactStatusSteps(raw: GremlinScala[Vertex])(implicit db: Database, graph: Graph) extends VertexSteps[ImpactStatus](raw) {
class ImpactStatusSteps(raw: GremlinScala[Vertex])(implicit @Named("with-thehive-schema") db: Database, graph: Graph)
extends VertexSteps[ImpactStatus](raw) {
override def newInstance(newRaw: GremlinScala[Vertex]): ImpactStatusSteps = new ImpactStatusSteps(newRaw)
override def newInstance(): ImpactStatusSteps = new ImpactStatusSteps(raw.clone())

Expand All @@ -48,3 +46,14 @@ class ImpactStatusSteps(raw: GremlinScala[Vertex])(implicit db: Database, graph:

def getByName(name: String): ImpactStatusSteps = new ImpactStatusSteps(raw.has(Key("value") of name))
}

class ImpactStatusIntegrityCheckOps @Inject() (@Named("with-thehive-schema") val db: Database, val service: ImpactStatusSrv)
extends IntegrityCheckOps[ImpactStatus] {
override def resolve(entities: List[ImpactStatus with Entity])(implicit graph: Graph): Try[Unit] = entities match {
case head :: tail =>
tail.foreach(copyEdge(_, head))
tail.foreach(service.get(_).remove())
Success(())
case _ => Success(())
}
}
Loading

0 comments on commit 71266e1

Please sign in to comment.