Skip to content

Commit

Permalink
Navigator supports user management (digital-asset#12187)
Browse files Browse the repository at this point in the history
Add basic support for user management to navigator:
log in as a user, act/read as its primary party.

When user management is supported & enabled, you can
only log in as a user (and that user must have a
primary party, which is what you'll actually be
acting/reading as).

The above is the default behavior. It can be disabled
using a feature flag (`--feature-user-management`),
and you can also still specify parties explicitly 
in the config file.


CHANGELOG_BEGIN
Navigator supports user management by default. To disable,
use `--feature-user-management false` or specify parties 
explicitly in `daml.yaml`.
CHANGELOG_END



Co-authored-by: Robert Autenrieth <31539813+rautenrieth-da@users.noreply.github.com>
Co-authored-by: Stefano Baghino <43749967+stefanobaghino-da@users.noreply.github.com>
Co-authored-by: Victor Peter Rouven Müller <mueller.vpr@gmail.com>
  • Loading branch information
4 people authored Feb 9, 2022
1 parent d3ec383 commit 46c3228
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package com.daml.navigator

import com.daml.navigator.config.UserConfig
import com.daml.navigator.model.PartyState
import scalaz.Tag

Expand All @@ -15,6 +14,7 @@ import scala.reflect.ClassTag
final case class User(
id: String,
party: PartyState,
// TODO: where is `role` used? frontend has some references, but doesn't seem to impact anything?
role: Option[String] = None,
canAdvanceTime: Boolean = true,
)
Expand Down Expand Up @@ -43,10 +43,10 @@ object Session {
def open(
sessionId: String,
userId: String,
userConfig: UserConfig,
userRole: Option[String],
state: PartyState,
): Session = {
val user = Session(User(userId, state, userConfig.role))
val user = Session(User(userId, state, userRole))
sessions += sessionId -> user
user
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ abstract class UIBackend extends LazyLogging with ApplicationInfoJsonSupport {
getAppState: () => Future[ApplicationStateInfo],
): Route = {

def openSession(userId: String, userConfig: UserConfig, state: PartyState): Route = {
def openSession(userId: String, userRole: Option[String], state: PartyState): Route = {
val sessionId = UUID.randomUUID().toString
setCookie(HttpCookie("session-id", sessionId, path = Some("/"))) {
complete(Session.open(sessionId, userId, userConfig, state))
complete(Session.open(sessionId, userId, userRole, state))
}
}

Expand Down Expand Up @@ -136,7 +136,7 @@ abstract class UIBackend extends LazyLogging with ApplicationInfoJsonSupport {
case Some(resp) =>
resp match {
case PartyActorRunning(info) =>
openSession(request.userId, info.state.config, info.state)
openSession(request.userId, info.state.userRole, info.state)
case Store.PartyActorUnresponsive =>
complete(
SignIn(SignInSelect(partyActors.keySet), Some(Unresponsive))
Expand Down Expand Up @@ -251,8 +251,10 @@ abstract class UIBackend extends LazyLogging with ApplicationInfoJsonSupport {
arguments.time,
applicationInfo,
arguments.ledgerInboundMessageSizeMax,
arguments.enableUserManagement,
)
)
// TODO: usermgmt switching: for now we just poll both user and party mgmt
// If no parties are specified, we periodically poll from the party management service.
// If parties are specified, we only use those. This allows users to use custom display names
// if they are non-unique or use only a subset of parties for performance reasons.
Expand All @@ -261,11 +263,15 @@ abstract class UIBackend extends LazyLogging with ApplicationInfoJsonSupport {
val partyRefresh: Option[Cancellable] =
if (config.users.isEmpty || arguments.ignoreProjectParties) {
Some(
system.scheduler.scheduleWithFixedDelay(Duration.Zero, 1.seconds, store, UpdateParties)
system.scheduler
.scheduleWithFixedDelay(Duration.Zero, 1.seconds, store, UpdateUsersOrParties)
)
} else {
config.users.foreach { case (displayName, config) =>
store ! Subscribe(displayName, config)
store ! Subscribe(
displayName,
new PartyState(config.party, config.role, config.useDatabase),
)
}
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ case class Arguments(
useDatabase: Boolean = false,
ledgerInboundMessageSizeMax: Int = 50 * 1024 * 1024, // 50 MiB
ignoreProjectParties: Boolean = false,
enableUserManagement: Boolean = true,
)

trait ArgumentsHelper { self: OptionParser[Arguments] =>
Expand Down Expand Up @@ -173,6 +174,13 @@ object Arguments {
)
.action((_, arguments) => arguments.copy(ignoreProjectParties = true))

opt[Boolean]("feature-user-management")
.optional()
.text(
"By default, the login screen is now populated by quering the user mgmt service. Disable to query party mgmt instead (the pre-2.0 default)."
)
.action((enabled, arguments) => arguments.copy(enableUserManagement = enabled))

cmd("server")
.text("serve data from platform")
.action((_, arguments) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@
package com.daml.navigator.model

import java.util.concurrent.atomic.AtomicReference

import com.daml.lf.{iface => DamlLfIface}
import com.daml.ledger.api.refinements.ApiTypes
import com.daml.navigator.config.UserConfig

import scala.collection.immutable.LazyList
import scalaz.Tag

import java.net.URLEncoder

case class State(ledger: Ledger, packageRegistry: PackageRegistry)

/** A DA party and its ledger view(s). */
class PartyState(val config: UserConfig) {
val name = config.party
val useDatabase = config.useDatabase
class PartyState(
val name: ApiTypes.Party,
// A named role specified in our config. The role is an arbitrary string chosen by the user. It gets passed to the frontend, which can display a custom theme or custom views depending on the role.
val userRole: Option[String] = None,
val useDatabase: Boolean = false,
) {
def actorName: String =
"party-" + URLEncoder.encode(ApiTypes.Party.unwrap(name), "UTF-8")

private val stateRef: AtomicReference[State] = new AtomicReference(
State(Ledger(name, None, useDatabase), new PackageRegistry)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
package com.daml.navigator.store

import java.time.Instant

import com.daml.ledger.api.domain.PartyDetails
import com.daml.ledger.api.domain.{PartyDetails, User}
import com.daml.navigator.model._
import com.daml.ledger.api.refinements.ApiTypes
import com.daml.navigator.config.UserConfig
import com.daml.navigator.time.TimeProviderWithType

trait ActorStatus
Expand All @@ -30,12 +28,13 @@ object Store {
/** Reinitialize the platform connection and reset all local state `Unit` */
case object ResetConnection

case object UpdateParties
case object UpdateUsersOrParties
case class UpdatedUsers(details: Seq[User])

case class UpdatedParties(details: List[PartyDetails])

/** Request to subscribe a party to the store (without response to sender). */
case class Subscribe(displayName: String, config: UserConfig)
case class Subscribe(displayName: String, partyState: PartyState)

/** Request to create a contract instance for a template and respond with a `scala.util.Try[CommandId]`. */
case class CreateContract(party: PartyState, templateId: TemplateStringId, argument: ApiRecord)
Expand Down Expand Up @@ -86,6 +85,7 @@ object Store {
applicationId: String,
ledgerId: String,
ledgerTime: TimeProviderWithType,
// `partyActors`'s keys are passed to the frontend as possible user ids to log in as
partyActors: Map[String, PartyActorResponse],
) extends ApplicationStateInfo

Expand Down
Loading

0 comments on commit 46c3228

Please sign in to comment.