diff --git a/dto/src/main/scala/org/thp/thehive/dto/v1/User.scala b/dto/src/main/scala/org/thp/thehive/dto/v1/User.scala index 1dc2313358..d67a1494f4 100644 --- a/dto/src/main/scala/org/thp/thehive/dto/v1/User.scala +++ b/dto/src/main/scala/org/thp/thehive/dto/v1/User.scala @@ -1,7 +1,7 @@ package org.thp.thehive.dto.v1 import org.thp.scalligraph.controllers.FFile -import play.api.libs.json.{Json, OFormat, Writes} +import play.api.libs.json.{JsObject, Json, OFormat, Writes} import java.util.Date @@ -32,7 +32,8 @@ case class OutputUser( permissions: Set[String], organisation: String, avatar: Option[String], - organisations: Seq[OutputOrganisationProfile] + organisations: Seq[OutputOrganisationProfile], + extraData: JsObject ) object OutputUser { diff --git a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala index c36e9ce395..78d1857fd3 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala @@ -37,10 +37,23 @@ class UserCtrl @Inject() ( auditSrv: AuditSrv, attachmentSrv: AttachmentSrv, implicit val db: Database -) extends QueryableCtrl { +) extends QueryableCtrl + with UserRenderer { override val entityName: String = "user" override val publicProperties: PublicProperties = properties.user + lazy val localPasswordAuthSrv: Try[LocalPasswordAuthSrv] = { + def getLocalPasswordAuthSrv(authSrv: AuthSrv): Option[LocalPasswordAuthSrv] = + authSrv match { + case lpas: LocalPasswordAuthSrv => Some(lpas) + case mas: MultiAuthSrv => mas.authProviders.flatMap(getLocalPasswordAuthSrv).headOption + case _ => None + } + getLocalPasswordAuthSrv(authSrv) match { + case Some(lpas) => Success(lpas) + case None => Failure(NotSupportedError("The local password authentication is not enabled")) + } + } override val initialQuery: Query = Query.init[Traversal.V[User]]("listUser", (graph, authContext) => organisationSrv.get(authContext.organisation)(graph).users) @@ -53,12 +66,32 @@ class UserCtrl @Inject() ( override def pageQuery(limitedCountThreshold: Long): ParamQuery[UserOutputParam] = Query.withParam[UserOutputParam, Traversal.V[User], IteratorOutput]( "page", - (params, userSteps, authContext) => - params - .organisation - .fold(userSteps.richUser(authContext))(org => userSteps.richUser(authContext, EntityIdOrName(org))) - .page(params.from, params.to, params.extraData.contains("total"), limitedCountThreshold) + { + case (UserOutputParam(from, to, extraData, organisation), userSteps, authContext) => + userSteps.richPage(from, to, extraData.contains("total"), limitedCountThreshold) { + _.richUserWithCustomRenderer( + organisation.fold(authContext.organisation)(EntityIdOrName(_)), + userStatsRenderer(extraData - "Total", localPasswordAuthSrv.toOption)(authContext) + )(authContext) + } + } ) +// +// authContext +// }))(org => userSteps.richUser(authContext, EntityIdOrName(org))) +// .page(params.from, params.to, params.extraData.contains("total"), limitedCountThreshold) +// ) +// override def pageQuery(limitedCountThreshold: Long): ParamQuery[OutputParam] = +// Query.withParam[OutputParam, Traversal.V[Case], IteratorOutput]( +// "page", +// { +// case (OutputParam(from, to, extraData), caseSteps, authContext) => +// caseSteps.richPage(from, to, extraData.contains("total"), limitedCountThreshold) { +// _.richCaseWithCustomRenderer(caseStatsRenderer(extraData - "total")(authContext))(authContext) +// } +// } +// ) + override val outputQuery: Query = Query.outputWithContext[RichUser, Traversal.V[User]]((userSteps, authContext) => userSteps.richUser(authContext)) @@ -123,18 +156,6 @@ class UserCtrl @Inject() ( } yield Results.NoContent } - lazy val localPasswordAuthSrv: Try[LocalPasswordAuthSrv] = { - def getLocalPasswordAuthSrv(authSrv: AuthSrv): Option[LocalPasswordAuthSrv] = - authSrv match { - case lpas: LocalPasswordAuthSrv => Some(lpas) - case mas: MultiAuthSrv => mas.authProviders.flatMap(getLocalPasswordAuthSrv).headOption - } - getLocalPasswordAuthSrv(authSrv) match { - case Some(lpas) => Success(lpas) - case None => Failure(NotSupportedError("The local password authentication is not enabled")) - } - } - def resetFailedAttempts(userIdOrName: String): Action[AnyContent] = entrypoint("reset user") .authTransaction(db) { implicit request => implicit graph => diff --git a/thehive/app/org/thp/thehive/controllers/v1/UserRenderer.scala b/thehive/app/org/thp/thehive/controllers/v1/UserRenderer.scala new file mode 100644 index 0000000000..40fe019043 --- /dev/null +++ b/thehive/app/org/thp/thehive/controllers/v1/UserRenderer.scala @@ -0,0 +1,42 @@ +package org.thp.thehive.controllers.v1 + +import org.thp.scalligraph.auth.AuthContext +import org.thp.scalligraph.traversal.TraversalOps._ +import org.thp.scalligraph.traversal.{Converter, Traversal} +import org.thp.thehive.models.{Permissions, User} +import org.thp.thehive.services.LocalPasswordAuthSrv +import org.thp.thehive.services.OrganisationOps._ +import org.thp.thehive.services.UserOps._ +import play.api.libs.json._ + +import java.util.{Map => JMap} + +trait UserRenderer extends BaseRenderer[User] { + + def lockout( + localPasswordAuthSrv: Option[LocalPasswordAuthSrv] + )(implicit authContext: AuthContext): Traversal.V[User] => Traversal[JsObject, JMap[String, Any], Converter[JsObject, JMap[String, Any]]] = + _.project(_.by.by(_.organisations.users(Permissions.manageUser).current.option)) + .domainMap { + case (user, Some(_)) => + Json.obj( + "lastFailed" -> user.lastFailed, + "failedAttempts" -> user.failedAttempts, + "lockedUntil" -> localPasswordAuthSrv.flatMap(_.lockedUntil(user)) + ) + case _ => JsObject.empty + } + + def userStatsRenderer(extraData: Set[String], authSrv: Option[LocalPasswordAuthSrv])(implicit + authContext: AuthContext + ): Traversal.V[User] => JsTraversal = { implicit traversal => + baseRenderer( + extraData, + traversal, + { + case (f, "lockout") => addData("lockout", f)(lockout(authSrv)) + case (f, _) => f + } + ) + } +}