Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1872 Added quota for user creation #1874

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ class Output @Inject() (
override def createUser(inputUser: InputUser): Try[IdMapping] =
authTransaction(inputUser.metaData.createdBy) { implicit graph => implicit authContext =>
logger.debug(s"Create user ${inputUser.user.login}")
userSrv.checkUser(inputUser.user).flatMap(userSrv.createEntity).map { createdUser =>
userSrv.checkUserLogin(inputUser.user).flatMap(userSrv.createEntity).map { createdUser =>
updateMetaData(createdUser, inputUser.metaData)
inputUser
.avatar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class OrganisationCtrl @Inject() (
val inputOrganisation: InputOrganisation = request.body("organisation")
for {
user <- userSrv.current.getOrFail("User")
organisation <- organisationSrv.create(inputOrganisation.toOrganisation, user)
organisation <- organisationSrv.createWithUserAsOrgadmin(inputOrganisation.toOrganisation, user)
} yield Results.Created(organisation.toJson)
}

Expand Down
17 changes: 17 additions & 0 deletions thehive/app/org/thp/thehive/services/AlertSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import org.thp.thehive.services.CaseOps._
import org.thp.thehive.services.CaseTemplateOps._
import org.thp.thehive.services.CustomFieldOps._
import org.thp.thehive.services.ObservableOps._
import org.thp.thehive.services.OrganisationOps._
import play.api.Configuration
import play.api.libs.json.{JsObject, JsValue, Json}

import java.lang.{Long => JLong}
Expand All @@ -28,6 +30,7 @@ import scala.util.{Failure, Success, Try}

@Singleton
class AlertSrv @Inject() (
configuration: Configuration,
caseSrv: CaseSrv,
tagSrv: TagSrv,
organisationSrv: OrganisationSrv,
Expand Down Expand Up @@ -79,6 +82,7 @@ class AlertSrv @Inject() (
Failure(CreateError(s"Alert ${alert.`type`}:${alert.source}:${alert.sourceRef} already exist in organisation ${organisation.name}"))
else
for {
_ <- checkAlertQuota(organisation)
createdAlert <- createEntity(alert.copy(organisationId = organisation._id))
_ <- alertOrganisationSrv.create(AlertOrganisation(), createdAlert, organisation)
_ <- caseTemplate.map(ct => alertCaseTemplateSrv.create(AlertCaseTemplate(), createdAlert, ct)).flip
Expand All @@ -89,6 +93,19 @@ class AlertSrv @Inject() (
} yield richAlert
}

private def checkAlertQuota(organisation: Organisation with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[Unit] = {
val alertQuota = configuration.getOptional[Long]("quota.organisation.alert.count")
val alertCount = organisationSrv.get(organisation).alerts.getCount

alertQuota.fold[Try[Unit]](Success(()))(quota =>
if (alertCount < quota) Success(())
else Failure(BadRequestError(s"Alert quota is reached, this organisation cannot have more alerts"))
)
}

override def update(
traversal: Traversal.V[Alert],
propertyUpdaters: Seq[PropertyUpdater]
Expand Down
18 changes: 17 additions & 1 deletion thehive/app/org/thp/thehive/services/CaseTemplateSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import org.thp.scalligraph.query.PropertyUpdater
import org.thp.scalligraph.services._
import org.thp.scalligraph.traversal.TraversalOps._
import org.thp.scalligraph.traversal.{Converter, Graph, StepLabel, Traversal}
import org.thp.scalligraph.{CreateError, EntityIdOrName, EntityName, RichSeq}
import org.thp.scalligraph.{BadRequestError, CreateError, EntityIdOrName, EntityName, RichSeq}
import org.thp.thehive.controllers.v1.Conversion._
import org.thp.thehive.models._
import org.thp.thehive.services.CaseTemplateOps._
import org.thp.thehive.services.CustomFieldOps._
import org.thp.thehive.services.OrganisationOps._
import org.thp.thehive.services.TaskOps._
import org.thp.thehive.services.UserOps._
import play.api.Configuration
import play.api.libs.json.{JsObject, Json}

import java.util.{Map => JMap}
import javax.inject.{Inject, Named}
import scala.util.{Failure, Success, Try}

class CaseTemplateSrv @Inject() (
configuration: Configuration,
customFieldSrv: CustomFieldSrv,
organisationSrv: OrganisationSrv,
tagSrv: TagSrv,
Expand Down Expand Up @@ -57,6 +59,7 @@ class CaseTemplateSrv @Inject() (
Failure(CreateError(s"""The case template "${caseTemplate.name}" already exists"""))
else
for {
_ <- checkCaseTemplateQuota(organisation)
createdCaseTemplate <- createEntity(caseTemplate)
_ <- caseTemplateOrganisationSrv.create(CaseTemplateOrganisation(), createdCaseTemplate, organisation)
createdTasks <- tasks.toTry(createTask(createdCaseTemplate, _))
Expand All @@ -66,6 +69,19 @@ class CaseTemplateSrv @Inject() (
_ <- auditSrv.caseTemplate.create(createdCaseTemplate, richCaseTemplate.toJson)
} yield richCaseTemplate

private def checkCaseTemplateQuota(organisation: Organisation with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[Unit] = {
val caseTemplateQuota = configuration.getOptional[Long]("quota.organisation.caseTemplate.count")
val caseTemplateCount = organisationSrv.get(organisation).caseTemplates.getCount

caseTemplateQuota.fold[Try[Unit]](Success(()))(quota =>
if (caseTemplateCount < quota) Success(())
else Failure(BadRequestError(s"Case template quota is reached, this organisation cannot have more case templates"))
)
}

def createTask(caseTemplate: CaseTemplate with Entity, task: Task)(implicit graph: Graph, authContext: AuthContext): Try[RichTask] =
for {
assignee <- task.assignee.map(u => organisationSrv.current.users(Permissions.manageTask).getByName(u).getOrFail("User")).flip
Expand Down
20 changes: 18 additions & 2 deletions thehive/app/org/thp/thehive/services/CustomFieldSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ import org.thp.scalligraph.query.PropertyUpdater
import org.thp.scalligraph.services.{IntegrityCheckOps, VertexSrv}
import org.thp.scalligraph.traversal.TraversalOps._
import org.thp.scalligraph.traversal._
import org.thp.scalligraph.{EntityIdOrName, RichSeq}
import org.thp.scalligraph.{BadRequestError, EntityIdOrName, RichSeq}
import org.thp.thehive.controllers.v1.Conversion._
import org.thp.thehive.models._
import org.thp.thehive.services.CustomFieldOps._
import play.api.Configuration
import play.api.cache.SyncCacheApi
import play.api.libs.json.{JsObject, JsValue}

import java.util.{Map => JMap}
import javax.inject.{Inject, Named, Singleton}
import scala.util.{Success, Try}
import scala.util.{Failure, Success, Try}

@Singleton
class CustomFieldSrv @Inject() (
configuration: Configuration,
auditSrv: AuditSrv,
organisationSrv: OrganisationSrv,
@Named("integrity-check-actor") integrityCheckActor: ActorRef,
Expand All @@ -36,10 +38,24 @@ class CustomFieldSrv @Inject() (

def create(e: CustomField)(implicit graph: Graph, authContext: AuthContext): Try[CustomField with Entity] =
for {
_ <- checkCustomFieldQuota
created <- createEntity(e)
_ <- auditSrv.customField.create(created, created.toJson)
} yield created

private def checkCustomFieldQuota(implicit
graph: Graph,
authContext: AuthContext
): Try[Unit] = {
val customFieldQuota = configuration.getOptional[Long]("quota.customField.count")
val customFieldCount = startTraversal.getCount

customFieldQuota.fold[Try[Unit]](Success(()))(quota =>
if (customFieldCount < quota) Success(())
else Failure(BadRequestError(s"Custom field quota is reached, no more custom fields can be created"))
)
}

override def exists(e: CustomField)(implicit graph: Graph): Boolean = startTraversal.getByName(e.name).exists

def delete(c: CustomField with Entity, force: Boolean)(implicit graph: Graph, authContext: AuthContext): Try[Unit] = {
Expand Down
21 changes: 20 additions & 1 deletion thehive/app/org/thp/thehive/services/OrganisationSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.thp.thehive.models._
import org.thp.thehive.services.OrganisationOps._
import org.thp.thehive.services.RoleOps._
import org.thp.thehive.services.UserOps._
import play.api.Configuration
import play.api.cache.SyncCacheApi
import play.api.libs.json.JsObject

Expand All @@ -22,6 +23,7 @@ import scala.util.{Failure, Success, Try}

@Singleton
class OrganisationSrv @Inject() (
configuration: Configuration,
taxonomySrvProvider: Provider[TaxonomySrv],
roleSrv: RoleSrv,
profileSrv: ProfileSrv,
Expand All @@ -42,7 +44,10 @@ class OrganisationSrv @Inject() (

override def getByName(name: String)(implicit graph: Graph): Traversal.V[Organisation] = startTraversal.getByName(name)

def create(organisation: Organisation, user: User with Entity)(implicit graph: Graph, authContext: AuthContext): Try[Organisation with Entity] =
def createWithUserAsOrgadmin(organisation: Organisation, user: User with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[Organisation with Entity] =
for {
createdOrganisation <- create(organisation)
_ <- roleSrv.create(user, createdOrganisation, profileSrv.orgAdmin)
Expand All @@ -51,13 +56,27 @@ class OrganisationSrv @Inject() (
def create(e: Organisation)(implicit graph: Graph, authContext: AuthContext): Try[Organisation with Entity] = {
val activeTaxos = getByName("admin").taxonomies.toSeq
for {
_ <- checkOrganisationQuota
newOrga <- createEntity(e)
_ <- taxonomySrv.createFreetagTaxonomy(newOrga)
_ <- activeTaxos.toTry(t => organisationTaxonomySrv.create(OrganisationTaxonomy(), newOrga, t))
_ <- auditSrv.organisation.create(newOrga, newOrga.toJson)
} yield newOrga
}

private def checkOrganisationQuota(implicit
graph: Graph,
authContext: AuthContext
): Try[Unit] = {
val organisationQuota = configuration.getOptional[Long]("quota.organisation.count")
val organisationCount = startTraversal.getCount

organisationQuota.fold[Try[Unit]](Success(()))(quota =>
if (organisationCount < quota) Success(())
else Failure(BadRequestError(s"Organisation quota is reached, no more organisations can be created"))
)
}

def current(implicit graph: Graph, authContext: AuthContext): Traversal.V[Organisation] = get(authContext.organisation)

def currentId(implicit graph: Graph, authContext: AuthContext): EntityId =
Expand Down
51 changes: 34 additions & 17 deletions thehive/app/org/thp/thehive/services/UserSrv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import play.api.libs.json.{JsObject, Json}

import java.util.regex.Pattern
import java.util.{List => JList, Map => JMap}
import javax.inject.{Inject, Named, Singleton}
import javax.inject.{Inject, Named, Provider, Singleton}
import scala.util.{Failure, Success, Try}

@Singleton
Expand All @@ -32,14 +32,33 @@ class UserSrv @Inject() (
roleSrv: RoleSrv,
auditSrv: AuditSrv,
attachmentSrv: AttachmentSrv,
organisationSrvProvider: Provider[OrganisationSrv],
@Named("integrity-check-actor") integrityCheckActor: ActorRef
) extends VertexSrv[User] {
lazy val organisationSrv: OrganisationSrv = organisationSrvProvider.get

val defaultUserDomain: Option[String] = configuration.getOptional[String]("auth.defaultUserDomain")
val fullUserNameRegex: Pattern = "[\\p{Graph}&&[^@.]](?:[\\p{Graph}&&[^@]]*)*@\\p{Alnum}+(?:[\\p{Alnum}-.])*".r.pattern

val userAttachmentSrv = new EdgeSrv[UserAttachment, User, Attachment]

def checkUser(user: User): Try[User] = {
def addOrCreateUser(user: User, avatar: Option[FFile], organisation: Organisation with Entity, profile: Profile with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[RichUser] =
getByName(user.login)
.getOrFail("User")
.orElse {
for {
validUser <- checkUserLogin(user)
_ <- checkUserQuota(organisation)
createdUser <- createEntity(validUser)
_ <- avatar.map(setAvatar(createdUser, _)).flip
} yield createdUser
}
.flatMap(addUserToOrganisation(_, organisation, profile))

def checkUserLogin(user: User): Try[User] = {
val login =
if (!user.login.contains('@') && defaultUserDomain.isDefined) s"${user.login}@${defaultUserDomain.get}".toLowerCase
else user.login.toLowerCase
Expand All @@ -48,6 +67,19 @@ class UserSrv @Inject() (
else Failure(BadRequestError(s"User login is invalid, it must be an email address (found: ${user.login})"))
}

private def checkUserQuota(organisation: Organisation with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[Unit] = {
val userQuota = configuration.getOptional[Long]("quota.organisation.user.count")
val userCount = organisationSrv.get(organisation).users.getCount

userQuota.fold[Try[Unit]](Success(()))(quota =>
if (userCount < quota) Success(())
else Failure(BadRequestError(s"User quota is reached, this organisation cannot have more users"))
)
}

// TODO return Try[Unit]
def addUserToOrganisation(user: User with Entity, organisation: Organisation with Entity, profile: Profile with Entity)(implicit
graph: Graph,
Expand All @@ -64,21 +96,6 @@ class UserSrv @Inject() (
} yield richUser
}

def addOrCreateUser(user: User, avatar: Option[FFile], organisation: Organisation with Entity, profile: Profile with Entity)(implicit
graph: Graph,
authContext: AuthContext
): Try[RichUser] =
getByName(user.login)
.getOrFail("User")
.orElse {
for {
validUser <- checkUser(user)
createdUser <- createEntity(validUser)
_ <- avatar.map(setAvatar(createdUser, _)).flip
} yield createdUser
}
.flatMap(addUserToOrganisation(_, organisation, profile))

def canSetPassword(user: User with Entity)(implicit graph: Graph, authContext: AuthContext): Boolean = {
val userOrganisations = get(user).organisations.value(_.name).toSet
val operatorOrganisations = current.organisations(Permissions.manageUser).value(_.name).toSeq
Expand Down