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

[JSON-API] Add get & delete user endpoint #12332

Merged
merged 1 commit into from
Jan 11, 2022
Merged
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 @@ -274,6 +274,155 @@ class HttpServiceIntegrationTestUserManagementNoAuth
} yield users.map(_.userId) should contain allElementsOf usernames
}

"getting information about a specific user should be possible via the user endpoint" in withHttpServiceAndClient(
participantAdminJwt
) { (uri, _, _, _, _) =>
import spray.json._
import spray.json.DefaultJsonProtocol._
val alice = getUniqueParty("Alice")
val createUserRequest = domain.CreateUserRequest(
getUniqueUserName("nice.user"),
Some(alice.unwrap),
List(alice),
List.empty,
isAdmin = true,
)
for {
(status1, output1) <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = authorizationHeader(participantAdminJwt),
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1).convertTo[Boolean] shouldBe true
}
(status2, output2) <- postRequest(
uri.withPath(Uri.Path(s"/v1/user")),
domain.GetUserRequest(createUserRequest.userId).toJson,
headers = authorizationHeader(participantAdminJwt),
)
} yield {
status2 shouldBe StatusCodes.OK
getResult(output2).convertTo[UserDetails] shouldBe UserDetails(
createUserRequest.userId,
createUserRequest.primaryParty,
)
}
}

"getting information about the current user should be possible via the user endpoint" in withHttpServiceAndClient(
participantAdminJwt
) { (uri, _, _, _, _) =>
import spray.json._
import spray.json.DefaultJsonProtocol._
val alice = getUniqueParty("Alice")
val createUserRequest = domain.CreateUserRequest(
getUniqueUserName("nice.user"),
Some(alice.unwrap),
List(alice),
List.empty,
isAdmin = true,
)
for {
(status1, output1) <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = authorizationHeader(participantAdminJwt),
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1).convertTo[Boolean] shouldBe true
}
(status2, output2) <- getRequest(
uri.withPath(Uri.Path(s"/v1/user")),
headers = headersWithUserAuth(createUserRequest.userId, admin = true),
)
} yield {
status2 shouldBe StatusCodes.OK
getResult(output2).convertTo[UserDetails] shouldBe UserDetails(
createUserRequest.userId,
createUserRequest.primaryParty,
)
}
}

"deleting a specific user should be possible via the user/delete endpoint" in withHttpServiceAndClient(
participantAdminJwt
) { (uri, _, _, _, _) =>
import spray.json._
import spray.json.DefaultJsonProtocol._
val alice = getUniqueParty("Alice")
val createUserRequest = domain.CreateUserRequest(
getUniqueUserName("nice.user"),
Some(alice.unwrap),
List(alice),
List.empty,
isAdmin = true,
)
for {
(status1, output1) <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = authorizationHeader(participantAdminJwt),
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1).convertTo[Boolean] shouldBe true
}
(status2, _) <- postRequest(
uri.withPath(Uri.Path(s"/v1/user/delete")),
domain.DeleteUserRequest(createUserRequest.userId).toJson,
headers = authorizationHeader(participantAdminJwt),
)
_ = status2 shouldBe StatusCodes.OK
(status3, output3) <- getRequest(
uri.withPath(Uri.Path("/v1/users")),
headers = authorizationHeader(participantAdminJwt),
)
} yield {
status3 shouldBe StatusCodes.OK
getResult(output3).convertTo[List[UserDetails]] should not contain createUserRequest.userId
}
}

"deleting the current user should be possible via the user/delete endpoint" in withHttpServiceAndClient(
participantAdminJwt
) { (uri, _, _, _, _) =>
import spray.json._
import spray.json.DefaultJsonProtocol._
val alice = getUniqueParty("Alice")
val createUserRequest = domain.CreateUserRequest(
getUniqueUserName("nice.user"),
Some(alice.unwrap),
List(alice),
List.empty,
isAdmin = true,
)
for {
(status1, output1) <- postRequest(
uri.withPath(Uri.Path("/v1/user/create")),
createUserRequest.toJson,
headers = authorizationHeader(participantAdminJwt),
)
_ <- {
status1 shouldBe StatusCodes.OK
getResult(output1).convertTo[Boolean] shouldBe true
}
(status2, _) <- getRequest(
uri.withPath(Uri.Path(s"/v1/user/delete")),
headers = headersWithUserAuth(createUserRequest.userId),
)
_ = status2 shouldBe StatusCodes.OK
(status3, output3) <- getRequest(
uri.withPath(Uri.Path("/v1/users")),
headers = authorizationHeader(participantAdminJwt),
)
} yield {
status3 shouldBe StatusCodes.OK
getResult(output3).convertTo[List[UserDetails]] should not contain createUserRequest.userId
}
}
}

class HttpServiceIntegrationTestUserManagement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ class Endpoints(
),
path("query") & withTimer(queryMatchingTimer) apply toRoute(query(req)),
path("fetch") & withFetchTimer apply toRoute(fetch(req)),
path("user" / "create") & withFetchTimer apply toRoute(allocateUser(req)),
path("user") apply toRoute(getUser(req)),
path("user" / "create") apply toRoute(allocateUser(req)),
path("user" / "delete") apply toRoute(deleteUser(req)),
path("parties") & withFetchTimer apply toRoute(parties(req)),
path("parties" / "allocate") & withTimer(
allocatePartyTimer
Expand All @@ -229,9 +231,12 @@ class Endpoints(
get apply concat(
path("query") & withTimer(queryAllTimer) apply
toRoute(retrieveAll(req)),
path("user") & withFetchTimer apply toRoute(getUser(req)),
path("user" / "rights") & withFetchTimer apply toRoute(listUserRights(req)),
path("users") & withFetchTimer apply toRoute(listUsers(req)),
path("user") apply toRoute(getAuthenticatedUser(req)),
path("user" / "delete") apply toRoute(deleteAuthenticatedUser(req)),
path("user" / "rights") apply toRoute(
listAuthenticatedUserRights(req)
),
path("users") apply toRoute(listUsers(req)),
path("parties") & withTimer(getPartyTimer) apply
toRoute(allParties(req)),
path("packages") apply toRoute(listPackages(req)),
Expand Down Expand Up @@ -475,6 +480,31 @@ class Endpoints(
proxyWithoutCommand((jwt, _) => partiesService.allParties(jwt))(req)
.flatMap(pd => either(pd map (domain.OkResponse(_))))

def deleteUser(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[Boolean]] =
proxyWithCommandET { (jwt, deleteUserRequest: domain.DeleteUserRequest) =>
import scalaz.syntax.std.either._
import com.daml.lf.data.Ref
for {
userId <- either(
Ref.UserId.fromString(deleteUserRequest.userId).disjunction.leftMap(InvalidUserInput)
): ET[
Ref.UserId
]
_ <- EitherT.rightT(userManagementClient.deleteUser(userId, Some(jwt.value)))
} yield domain.OkResponse(true): domain.SyncResponse[Boolean]
}(req)

def deleteAuthenticatedUser(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[Boolean]] =
for {
jwt <- eitherT(input(req)).bimap(identity[Error], _._1)
userId <- decodeAndParseUserIdFromToken(jwt, decodeJwt).leftMap(identity[Error])
_ <- EitherT.rightT(userManagementClient.deleteUser(userId, Some(jwt.value)))
} yield domain.OkResponse(true)

def listUsers(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[List[domain.UserDetails]]] =
Expand All @@ -485,7 +515,7 @@ class Endpoints(
)
} yield domain.OkResponse(users.map(domain.UserDetails.fromUser).toList)

def listUserRights(req: HttpRequest)(implicit
def listAuthenticatedUserRights(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[domain.UserRights]] =
for {
Expand All @@ -498,6 +528,24 @@ class Endpoints(

def getUser(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[domain.UserDetails]] =
proxyWithCommandET { (jwt, getUserRequest: domain.GetUserRequest) =>
import scalaz.syntax.std.either._
import com.daml.lf.data.Ref
for {
userId <- either(
Ref.UserId.fromString(getUserRequest.userId).disjunction.leftMap(InvalidUserInput)
): ET[
Ref.UserId
]
user <- EitherT.rightT(userManagementClient.getUser(userId, Some(jwt.value)))
} yield domain.OkResponse(
domain.UserDetails(user.id, user.primaryParty)
): domain.SyncResponse[domain.UserDetails]
}(req)

def getAuthenticatedUser(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[domain.SyncResponse[domain.UserDetails]] =
for {
jwt <- eitherT(input(req)).bimap(identity[Error], _._1)
Expand Down Expand Up @@ -845,6 +893,12 @@ class Endpoints(
a <- either(SprayJson.decode[A](reqBody).liftErr(InvalidUserInput)): ET[A]
b <- eitherT(handleFutureEitherFailure(fn(jwt, a))): ET[R]
} yield b

private def proxyWithCommandET[A: JsonReader, R](
fn: (Jwt, A) => ET[R]
)(req: HttpRequest)(implicit
lc: LoggingContextOf[InstanceUUID with RequestID]
): ET[R] = proxyWithCommand((jwt, a: A) => fn(jwt, a).run)(req)
}

object Endpoints {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ object domain extends com.daml.fetchcontracts.domain.Aliases {
isAdmin: Boolean,
)

final case class GetUserRequest(userId: String)

final case class DeleteUserRequest(userId: String)

final case class AllocatePartyRequest(identifierHint: Option[Party], displayName: Option[String])

final case class CommandMeta(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ object JsonProtocol extends JsonProtocolLow {
implicit val CreateUserRequest: JsonFormat[domain.CreateUserRequest] =
jsonFormat5(domain.CreateUserRequest)

implicit val GetUserRequest: JsonFormat[domain.GetUserRequest] =
jsonFormat1(domain.GetUserRequest)

implicit val DeleteUserRequest: JsonFormat[domain.DeleteUserRequest] =
jsonFormat1(domain.DeleteUserRequest)

implicit val AllocatePartyRequest: JsonFormat[domain.AllocatePartyRequest] =
jsonFormat2(domain.AllocatePartyRequest)

Expand Down