From 50c98936861bdf1cd74537d68ec837819e960cde Mon Sep 17 00:00:00 2001 From: To-om Date: Tue, 12 May 2020 11:14:57 +0200 Subject: [PATCH] #1316 Include organisation list in current user API --- ScalliGraph | 2 +- .../scala/org/thp/thehive/dto/v1/User.scala | 8 +++- .../thehive/controllers/v1/Conversion.scala | 15 ++++++- .../thp/thehive/controllers/v1/UserCtrl.scala | 3 +- .../org/thp/thehive/services/UserSrv.scala | 39 ++++++++++++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/ScalliGraph b/ScalliGraph index 4d4e1108f9..c5f48bc785 160000 --- a/ScalliGraph +++ b/ScalliGraph @@ -1 +1 @@ -Subproject commit 4d4e1108f96e649742346ad505ac982054be9193 +Subproject commit c5f48bc7855bd572189eef0ae137c3ef767682f4 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 de7cbfd3d3..77042220a0 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 @@ -12,6 +12,11 @@ object InputUser { implicit val writes: Writes[InputUser] = Json.writes[InputUser] } +case class OutputOrganisationRole(organisation: String, role: String) +object OutputOrganisationRole { + implicit val format: OFormat[OutputOrganisationRole] = Json.format[OutputOrganisationRole] +} + case class OutputUser( _id: String, _createdBy: String, @@ -27,7 +32,8 @@ case class OutputUser( profile: String, permissions: Set[String], organisation: String, - avatar: Option[String] + avatar: Option[String], + organisations: Seq[OutputOrganisationRole] ) object OutputUser { diff --git a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala index a346e4c1fe..ea607bf247 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala @@ -187,12 +187,23 @@ object Conversion { _.into[OutputUser] .withFieldComputed(_.permissions, _.permissions.asInstanceOf[Set[String]]) .withFieldComputed(_.hasKey, _.apikey.isDefined) - // .withFieldComputed(_.permissions, _.permissions) - // .withFieldConst(_.permissions, Set.empty[String] + .withFieldConst(_.organisations, Nil) .withFieldComputed(_.avatar, user => user.avatar.map(avatar => s"/api/v1/user/${user._id}/avatar/$avatar")) .transform ) + implicit val userWithOrganisationOutput: Outputer.Aux[(RichUser, Seq[(String, String)]), OutputUser] = + Outputer[(RichUser, Seq[(String, String)]), OutputUser] { userWithOrganisations => + val (user, organisations) = userWithOrganisations + user + .into[OutputUser] + .withFieldComputed(_.permissions, _.permissions.asInstanceOf[Set[String]]) + .withFieldComputed(_.hasKey, _.apikey.isDefined) + .withFieldConst(_.organisations, organisations.map { case (org, role) => OutputOrganisationRole(org, role) }) + .withFieldComputed(_.avatar, user => user.avatar.map(avatar => s"/api/v1/user/${user._id}/avatar/$avatar")) + .transform + } + implicit val profileOutput: Outputer.Aux[Profile with Entity, OutputProfile] = Outputer[Profile with Entity, OutputProfile](profile => profile .asInstanceOf[Profile] diff --git a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala index 14c6b5173f..983dea345b 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala @@ -18,6 +18,7 @@ import play.api.http.HttpEntity import play.api.libs.json.{JsNull, JsObject, Json} import play.api.mvc._ +import scala.collection.JavaConverters._ import scala.concurrent.ExecutionContext import scala.util.{Failure, Success, Try} @@ -63,7 +64,7 @@ class UserCtrl @Inject() ( .authRoTransaction(db) { implicit request => implicit graph => userSrv .current - .richUser(request.organisation) + .richUserWithCustomRenderer(request.organisation, _.organisationWithRole.map(_.asScala.toSeq)) .getOrFail() .map(user => Results.Ok(user.toJson)) } diff --git a/thehive/app/org/thp/thehive/services/UserSrv.scala b/thehive/app/org/thp/thehive/services/UserSrv.scala index 1502558818..c31e9b7578 100644 --- a/thehive/app/org/thp/thehive/services/UserSrv.scala +++ b/thehive/app/org/thp/thehive/services/UserSrv.scala @@ -1,5 +1,6 @@ package org.thp.thehive.services +import java.util.{List => JList} import java.util.regex.Pattern import gremlin.scala._ @@ -10,7 +11,7 @@ import org.thp.scalligraph.models._ import org.thp.scalligraph.query.PropertyUpdater import org.thp.scalligraph.services._ import org.thp.scalligraph.steps.StepsOps._ -import org.thp.scalligraph.steps.{Traversal, VertexSteps} +import org.thp.scalligraph.steps.{Traversal, TraversalLike, VertexSteps} import org.thp.scalligraph.{BadRequestError, EntitySteps, RichOptionTry} import org.thp.thehive.controllers.v1.Conversion._ import org.thp.thehive.models._ @@ -190,6 +191,15 @@ class UserSteps(raw: GremlinScala[Vertex])(implicit db: Database, graph: Graph) else organisations0(requiredPermission) } + def organisationWithRole: Traversal[JList[(String, String)], JList[(String, String)]] = + this + .outTo[UserRole] + .project( + _.apply(By(__[Vertex].outTo[RoleOrganisation].value[String]("name"))) + .and(By(__[Vertex].outTo[RoleProfile].value[String]("name"))) + ) + .fold + def config: ConfigSteps = new ConfigSteps(raw.outTo[UserConfig]) def getAuthContext(requestId: String, organisation: Option[String]): Traversal[AuthContext, AuthContext] = { @@ -251,6 +261,33 @@ class UserSteps(raw: GremlinScala[Vertex])(implicit db: Database, graph: Graph) RichUser(user.as[User], avatar, "", Set.empty, organisation) } + def richUserWithCustomRenderer[A](organisation: String, entityRenderer: UserSteps => TraversalLike[_, A])( + implicit authContext: AuthContext + ): Traversal[(RichUser, A), (RichUser, A)] = + this + .project( + _.apply(By[Vertex]()) + .and( + By( + __[Vertex].coalesce( + _.outTo[UserRole].filter(_.outTo[RoleOrganisation].has(Key("name") of organisation)).outTo[RoleProfile].fold(), + _.constant(List.empty[Vertex].asJava) + ) + ) + ) + .and(By(__[Vertex].outTo[UserAttachment].fold())) + .and(By(entityRenderer(newInstance(__[Vertex])).raw)) + ) + .collect { + case (user, profiles, attachment, renderedEntity) if profiles.size() == 1 => + val profile = profiles.get(0).as[Profile] + val avatar = atMostOneOf[Vertex](attachment).map(_.as[Attachment].attachmentId) + RichUser(user.as[User], avatar, profile.name, profile.permissions, organisation) -> renderedEntity + case (user, _, attachment, renderedEntity) => + val avatar = atMostOneOf[Vertex](attachment).map(_.as[Attachment].attachmentId) + RichUser(user.as[User], avatar, "", Set.empty, organisation) -> renderedEntity + } + def role: RoleSteps = new RoleSteps(raw.outTo[UserRole]) def avatar: AttachmentSteps = new AttachmentSteps(raw.outTo[UserAttachment])