mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
Make ApplicationId in DAML Script configurable (#7074)
This is set per participant since it is similar to the token file. fixes #7029 changelog_begin - [DAML Script/DAML REPL] You can now configure the application id via `--application-id` or the `--participant-config`. This is primarily useful if you are working against a ledger with authentication and need to match the application id in your token. changelog_end
This commit is contained in:
parent
23a46e55ec
commit
e1236d42ed
@ -269,6 +269,13 @@ cmdRepl numProcessors =
|
||||
<*> strOption (long "ledger-port" <> help "Port of the ledger API")
|
||||
)
|
||||
<*> accessTokenFileFlag
|
||||
<*> optional
|
||||
(ReplClient.ApplicationId <$>
|
||||
strOption
|
||||
(long "application-id" <>
|
||||
help "Application ID used for command submissions"
|
||||
)
|
||||
)
|
||||
<*> sslConfig
|
||||
<*> optional
|
||||
(option auto $
|
||||
@ -603,11 +610,12 @@ execRepl
|
||||
-> FilePath -> [FilePath] -> [(LF.PackageName, Maybe LF.PackageVersion)]
|
||||
-> Maybe (String, String)
|
||||
-> Maybe FilePath
|
||||
-> Maybe ReplClient.ApplicationId
|
||||
-> Maybe ReplClient.ClientSSLConfig
|
||||
-> Maybe ReplClient.MaxInboundMessageSize
|
||||
-> ReplClient.ReplTimeMode
|
||||
-> Command
|
||||
execRepl projectOpts opts scriptDar dars importPkgs mbLedgerConfig mbAuthToken mbSslConf mbMaxInboundMessageSize timeMode = Command Repl (Just projectOpts) effect
|
||||
execRepl projectOpts opts scriptDar dars importPkgs mbLedgerConfig mbAuthToken mbAppId mbSslConf mbMaxInboundMessageSize timeMode = Command Repl (Just projectOpts) effect
|
||||
where effect = do
|
||||
-- We change directory so make this absolute
|
||||
dars <- mapM makeAbsolute dars
|
||||
@ -618,7 +626,7 @@ execRepl projectOpts opts scriptDar dars importPkgs mbLedgerConfig mbAuthToken m
|
||||
logger <- getLogger opts "repl"
|
||||
runfilesDir <- locateRunfiles (mainWorkspace </> "compiler/repl-service/server")
|
||||
let jar = runfilesDir </> "repl-service.jar"
|
||||
ReplClient.withReplClient (ReplClient.Options jar mbLedgerConfig mbAuthToken mbSslConf mbMaxInboundMessageSize timeMode Inherit) $ \replHandle _stdout _ph ->
|
||||
ReplClient.withReplClient (ReplClient.Options jar mbLedgerConfig mbAuthToken mbAppId mbSslConf mbMaxInboundMessageSize timeMode Inherit) $ \replHandle _stdout _ph ->
|
||||
withTempDir $ \dir ->
|
||||
withCurrentDirectory dir $ do
|
||||
sdkVer <- fromMaybe SdkVersion.sdkVersion <$> lookupEnv sdkVersionEnvVar
|
||||
|
@ -63,7 +63,7 @@ main = do
|
||||
withTempFile $ \portFile ->
|
||||
withBinaryFile nullDevice WriteMode $ \devNull ->
|
||||
bracket (createSandbox portFile devNull defaultSandboxConf { dars = testDars }) destroySandbox $ \SandboxResource{sandboxPort} ->
|
||||
ReplClient.withReplClient (ReplClient.Options replJar (Just ("localhost", show sandboxPort)) Nothing Nothing Nothing ReplClient.ReplWallClock CreatePipe) $ \replHandle mbServiceOut processHandle ->
|
||||
ReplClient.withReplClient (ReplClient.Options replJar (Just ("localhost", show sandboxPort)) Nothing Nothing Nothing Nothing ReplClient.ReplWallClock CreatePipe) $ \replHandle mbServiceOut processHandle ->
|
||||
-- TODO We could share some of this setup with the actual repl code in damlc.
|
||||
withTempDir $ \dir ->
|
||||
withCurrentDirectory dir $ do
|
||||
|
@ -5,6 +5,7 @@
|
||||
{-# LANGUAGE GADTs #-}
|
||||
module DA.Daml.LF.ReplClient
|
||||
( Options(..)
|
||||
, ApplicationId(..)
|
||||
, MaxInboundMessageSize(..)
|
||||
, ReplTimeMode(..)
|
||||
, Handle
|
||||
@ -41,10 +42,13 @@ newtype MaxInboundMessageSize = MaxInboundMessageSize Int
|
||||
|
||||
data ReplTimeMode = ReplWallClock | ReplStatic
|
||||
|
||||
newtype ApplicationId = ApplicationId String
|
||||
|
||||
data Options = Options
|
||||
{ optServerJar :: FilePath
|
||||
, optLedgerConfig :: Maybe (String, String)
|
||||
, optMbAuthTokenFile :: Maybe FilePath
|
||||
, optMbApplicationId :: Maybe ApplicationId
|
||||
, optMbSslConfig :: Maybe ClientSSLConfig
|
||||
, optMaxInboundMessageSize :: Maybe MaxInboundMessageSize
|
||||
, optTimeMode :: ReplTimeMode
|
||||
@ -87,6 +91,7 @@ withReplClient opts@Options{..} f = withTempFile $ \portFile -> do
|
||||
| Just (host, port) <- [optLedgerConfig]
|
||||
]
|
||||
, [ "--access-token-file=" <> tokenFile | Just tokenFile <- [optMbAuthTokenFile] ]
|
||||
, [ "--application-id=" <> appId | Just (ApplicationId appId) <- [ optMbApplicationId] ]
|
||||
, do Just tlsConf <- [ optMbSslConfig ]
|
||||
"--tls" :
|
||||
concat
|
||||
|
@ -33,6 +33,7 @@ object ReplServiceMain extends App {
|
||||
ledgerHost: Option[String],
|
||||
ledgerPort: Option[Int],
|
||||
accessTokenFile: Option[Path],
|
||||
applicationId: Option[ApplicationId],
|
||||
maxInboundMessageSize: Int,
|
||||
tlsConfig: TlsConfiguration,
|
||||
// optional so we can detect if both --static-time and --wall-clock-time are passed.
|
||||
@ -68,6 +69,12 @@ object ReplServiceMain extends App {
|
||||
c.copy(accessTokenFile = Some(Paths.get(tokenFile)))
|
||||
}
|
||||
|
||||
opt[String]("application-id")
|
||||
.optional()
|
||||
.action { (appId, c) =>
|
||||
c.copy(applicationId = Some(ApplicationId(appId)))
|
||||
}
|
||||
|
||||
TlsConfigurationCli.parse(this, colSpacer = " ")((f, c) =>
|
||||
c copy (tlsConfig = f(c.tlsConfig)))
|
||||
|
||||
@ -109,6 +116,7 @@ object ReplServiceMain extends App {
|
||||
tlsConfig = TlsConfiguration(false, None, None, None),
|
||||
maxInboundMessageSize = RunnerConfig.DefaultMaxInboundMessageSize,
|
||||
timeMode = None,
|
||||
applicationId = None,
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -129,15 +137,15 @@ object ReplServiceMain extends App {
|
||||
|
||||
val tokenHolder = config.accessTokenFile.map(new TokenHolder(_))
|
||||
val defaultParticipant = (config.ledgerHost, config.ledgerPort) match {
|
||||
case (Some(host), Some(port)) => Some(ApiParameters(host, port, tokenHolder.flatMap(_.token)))
|
||||
case (Some(host), Some(port)) =>
|
||||
Some(ApiParameters(host, port, tokenHolder.flatMap(_.token), config.applicationId))
|
||||
case _ => None
|
||||
}
|
||||
val participantParams =
|
||||
Participants(defaultParticipant, Map.empty, Map.empty)
|
||||
val applicationId = ApplicationId("daml repl")
|
||||
val clients = Await.result(
|
||||
Runner
|
||||
.connect(participantParams, applicationId, config.tlsConfig, config.maxInboundMessageSize),
|
||||
.connect(participantParams, config.tlsConfig, config.maxInboundMessageSize),
|
||||
30.seconds)
|
||||
val timeMode = config.timeMode.getOrElse(ScriptTimeMode.WallClock)
|
||||
|
||||
|
@ -513,7 +513,7 @@ class JsonLedgerClient(
|
||||
case -\/(e) => throw new IllegalArgumentException(e.toString)
|
||||
case \/-(a) => a
|
||||
}
|
||||
private val tokenPayload: AuthServiceJWTPayload =
|
||||
private[script] val tokenPayload: AuthServiceJWTPayload =
|
||||
AuthServiceJWTCodec.readFromString(decodedJwt.payload) match {
|
||||
case Failure(e) => throw e
|
||||
case Success(s) => s
|
||||
|
@ -51,7 +51,11 @@ object LfValueCodec extends ApiCodecCompressed[ContractId](false, false)
|
||||
|
||||
case class Participant(participant: String)
|
||||
case class Party(party: String)
|
||||
case class ApiParameters(host: String, port: Int, access_token: Option[String])
|
||||
case class ApiParameters(
|
||||
host: String,
|
||||
port: Int,
|
||||
access_token: Option[String],
|
||||
application_id: Option[ApplicationId])
|
||||
case class Participants[+T](
|
||||
default_participant: Option[T],
|
||||
participants: Map[Participant, T],
|
||||
@ -121,7 +125,14 @@ object ParticipantsJsonProtocol extends DefaultJsonProtocol {
|
||||
case _ => deserializationError("ContractId must be a string")
|
||||
}
|
||||
}
|
||||
implicit val apiParametersFormat = jsonFormat3(ApiParameters)
|
||||
implicit object ApplicationIdFormat extends JsonFormat[ApplicationId] {
|
||||
def read(value: JsValue) = value match {
|
||||
case JsString(s) => ApplicationId(s)
|
||||
case _ => deserializationError("Expected ApplicationId string")
|
||||
}
|
||||
def write(id: ApplicationId) = JsString(ApplicationId.unwrap(id))
|
||||
}
|
||||
implicit val apiParametersFormat = jsonFormat4(ApiParameters)
|
||||
implicit val participantsFormat = jsonFormat3(Participants[ApiParameters])
|
||||
}
|
||||
|
||||
@ -165,11 +176,11 @@ object Runner {
|
||||
val DEFAULT_APPLICATION_ID: ApplicationId = ApplicationId("daml-script")
|
||||
private def connectApiParameters(
|
||||
params: ApiParameters,
|
||||
applicationId: ApplicationId,
|
||||
tlsConfig: TlsConfiguration,
|
||||
maxInboundMessageSize: Int)(
|
||||
implicit ec: ExecutionContext,
|
||||
seq: ExecutionSequencerFactory): Future[GrpcLedgerClient] = {
|
||||
val applicationId = params.application_id.getOrElse(Runner.DEFAULT_APPLICATION_ID)
|
||||
val clientConfig = LedgerClientConfiguration(
|
||||
applicationId = ApplicationId.unwrap(applicationId),
|
||||
ledgerIdRequirement = LedgerIdRequirement.none,
|
||||
@ -189,16 +200,15 @@ object Runner {
|
||||
// We might want to have one config per participant at some point but for now this should be sufficient.
|
||||
def connect(
|
||||
participantParams: Participants[ApiParameters],
|
||||
applicationId: ApplicationId,
|
||||
tlsConfig: TlsConfiguration,
|
||||
maxInboundMessageSize: Int)(
|
||||
implicit ec: ExecutionContext,
|
||||
seq: ExecutionSequencerFactory): Future[Participants[GrpcLedgerClient]] = {
|
||||
for {
|
||||
defaultClient <- participantParams.default_participant.traverse(x =>
|
||||
connectApiParameters(x, applicationId, tlsConfig, maxInboundMessageSize))
|
||||
connectApiParameters(x, tlsConfig, maxInboundMessageSize))
|
||||
participantClients <- participantParams.participants.traverse(v =>
|
||||
connectApiParameters(v, applicationId, tlsConfig, maxInboundMessageSize))
|
||||
connectApiParameters(v, tlsConfig, maxInboundMessageSize))
|
||||
} yield Participants(defaultClient, participantClients, participantParams.party_participants)
|
||||
}
|
||||
|
||||
@ -211,7 +221,13 @@ object Runner {
|
||||
case None =>
|
||||
Future.failed(new RuntimeException(s"The JSON API always requires access tokens"))
|
||||
case Some(token) =>
|
||||
Future.successful(new JsonLedgerClient(uri, Jwt(token), envIface, system))
|
||||
val client = new JsonLedgerClient(uri, Jwt(token), envIface, system)
|
||||
if (params.application_id.isDefined && params.application_id != client.tokenPayload.applicationId) {
|
||||
Future.failed(new RuntimeException(
|
||||
s"ApplicationId specified in token ${client.tokenPayload.applicationId} must match ${params.application_id}"))
|
||||
} else {
|
||||
Future.successful(new JsonLedgerClient(uri, Jwt(token), envIface, system))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.nio.file.{Path, Paths}
|
||||
import java.io.File
|
||||
import java.time.Duration
|
||||
|
||||
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
|
||||
import com.daml.ledger.api.tls.{TlsConfiguration, TlsConfigurationCli}
|
||||
|
||||
case class RunnerConfig(
|
||||
@ -24,6 +25,10 @@ case class RunnerConfig(
|
||||
tlsConfig: TlsConfiguration,
|
||||
jsonApi: Boolean,
|
||||
maxInboundMessageSize: Int,
|
||||
// While we do have a default application id, we
|
||||
// want to differentiate between not specifying the application id
|
||||
// and specifying the default for better error messages.
|
||||
applicationId: Option[ApplicationId],
|
||||
)
|
||||
|
||||
object RunnerConfig {
|
||||
@ -110,6 +115,12 @@ object RunnerConfig {
|
||||
.text(
|
||||
s"Optional max inbound message size in bytes. Defaults to $DefaultMaxInboundMessageSize")
|
||||
|
||||
opt[String]("application-id")
|
||||
.action((x, c) => c.copy(applicationId = Some(ApplicationId(x))))
|
||||
.optional()
|
||||
.text(
|
||||
s"Application ID used to interact with the ledger. Defaults to ${Runner.DEFAULT_APPLICATION_ID}")
|
||||
|
||||
help("help").text("Print this usage text")
|
||||
|
||||
checkConfig(c => {
|
||||
@ -154,6 +165,7 @@ object RunnerConfig {
|
||||
tlsConfig = TlsConfiguration(false, None, None, None),
|
||||
jsonApi = false,
|
||||
maxInboundMessageSize = DefaultMaxInboundMessageSize,
|
||||
applicationId = None,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ object RunnerMain {
|
||||
val scriptId: Identifier =
|
||||
Identifier(dar.main._1, QualifiedName.assertFromString(config.scriptIdentifier))
|
||||
|
||||
val applicationId = Runner.DEFAULT_APPLICATION_ID
|
||||
val timeMode: ScriptTimeMode = config.timeMode.getOrElse(RunnerConfig.DefaultTimeMode)
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem("ScriptRunner")
|
||||
@ -61,10 +60,9 @@ object RunnerMain {
|
||||
|
||||
val participantParams = config.participantConfig match {
|
||||
case Some(file) => {
|
||||
// To avoid a breaking change, we allow specifying
|
||||
// --access-token-file and --participant-config
|
||||
// together and use the token file as the default for all participants
|
||||
// that do not specify an explicit token.
|
||||
// We allow specifying --access-token-file/--application-id together with
|
||||
// --participant-config and use the values as the default for
|
||||
// all participants that do not specify an explicit token.
|
||||
val source = Source.fromFile(file)
|
||||
val fileContent = try {
|
||||
source.mkString
|
||||
@ -76,7 +74,11 @@ object RunnerMain {
|
||||
import ParticipantsJsonProtocol._
|
||||
jsVal
|
||||
.convertTo[Participants[ApiParameters]]
|
||||
.map(params => params.copy(access_token = params.access_token.orElse(token)))
|
||||
.map(
|
||||
params =>
|
||||
params.copy(
|
||||
access_token = params.access_token.orElse(token),
|
||||
application_id = params.application_id.orElse(config.applicationId)))
|
||||
}
|
||||
case None =>
|
||||
val tokenHolder = config.accessTokenFile.map(new TokenHolder(_))
|
||||
@ -85,7 +87,8 @@ object RunnerMain {
|
||||
ApiParameters(
|
||||
config.ledgerHost.get,
|
||||
config.ledgerPort.get,
|
||||
tokenHolder.flatMap(_.token))),
|
||||
tokenHolder.flatMap(_.token),
|
||||
config.applicationId)),
|
||||
participants = Map.empty,
|
||||
party_participants = Map.empty
|
||||
)
|
||||
@ -97,11 +100,7 @@ object RunnerMain {
|
||||
val envIface = EnvironmentInterface.fromReaderInterfaces(ifaceDar)
|
||||
Runner.jsonClients(participantParams, envIface)
|
||||
} else {
|
||||
Runner.connect(
|
||||
participantParams,
|
||||
applicationId,
|
||||
config.tlsConfig,
|
||||
config.maxInboundMessageSize)
|
||||
Runner.connect(participantParams, config.tlsConfig, config.maxInboundMessageSize)
|
||||
}
|
||||
result <- Runner.run(dar, scriptId, inputValue, clients, timeMode)
|
||||
_ <- Future {
|
||||
|
@ -54,7 +54,6 @@ object TestMain extends StrictLogging {
|
||||
case (pkgId, pkgArchive) => Decode.readArchivePayload(pkgId, pkgArchive)
|
||||
}
|
||||
|
||||
val applicationId = Runner.DEFAULT_APPLICATION_ID
|
||||
val system: ActorSystem = ActorSystem("ScriptTest")
|
||||
implicit val sequencer: ExecutionSequencerFactory =
|
||||
new AkkaExecutionSequencerPool("ScriptTestPool")(system)
|
||||
@ -85,10 +84,10 @@ object TestMain extends StrictLogging {
|
||||
val sandboxResource = SandboxServer.owner(sandboxConfig).acquire()
|
||||
val sandboxPort =
|
||||
Await.result(sandboxResource.asFuture.flatMap(_.portF).map(_.value), Duration.Inf)
|
||||
(ApiParameters("localhost", sandboxPort, None), () => sandboxResource.release())
|
||||
(ApiParameters("localhost", sandboxPort, None, None), () => sandboxResource.release())
|
||||
} else {
|
||||
(
|
||||
ApiParameters(config.ledgerHost.get, config.ledgerPort.get, None),
|
||||
ApiParameters(config.ledgerHost.get, config.ledgerPort.get, None, None),
|
||||
() => Future.successful(()),
|
||||
)
|
||||
}
|
||||
@ -120,7 +119,6 @@ object TestMain extends StrictLogging {
|
||||
val flow: Future[Boolean] = for {
|
||||
clients <- Runner.connect(
|
||||
participantParams,
|
||||
applicationId,
|
||||
TlsConfiguration(false, None, None, None),
|
||||
config.maxInboundMessageSize,
|
||||
)
|
||||
|
@ -97,10 +97,9 @@ da_scala_library(
|
||||
"//language-support/scala/bindings",
|
||||
"//language-support/scala/bindings-akka",
|
||||
"//ledger-api/rs-grpc-bridge",
|
||||
"//ledger/ledger-api-common",
|
||||
# "//libs-scala/auth-utils",
|
||||
"//ledger/ledger-api-auth",
|
||||
"//ledger-service/jwt",
|
||||
"//ledger/ledger-api-auth",
|
||||
"//ledger/ledger-api-common",
|
||||
"@maven//:com_github_scopt_scopt_2_12",
|
||||
"@maven//:com_typesafe_akka_akka_stream_2_12",
|
||||
"@maven//:io_spray_spray_json_2_12",
|
||||
@ -163,6 +162,7 @@ da_scala_test_suite(
|
||||
"//libs-scala/auth-utils",
|
||||
"//libs-scala/ports",
|
||||
"//libs-scala/resources",
|
||||
"@maven//:com_auth0_java_jwt",
|
||||
"@maven//:com_github_scopt_scopt_2_12",
|
||||
"@maven//:com_google_guava_guava",
|
||||
"@maven//:com_typesafe_akka_akka_http_core_2_12",
|
||||
|
@ -8,15 +8,18 @@ import akka.http.scaladsl.Http.ServerBinding
|
||||
import akka.stream.Materializer
|
||||
import io.grpc.Channel
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import scala.util.control.NonFatal
|
||||
import scalaz.{-\/, \/-}
|
||||
import scalaz.syntax.traverse._
|
||||
import spray.json._
|
||||
import com.daml.bazeltools.BazelRunfiles._
|
||||
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
|
||||
import com.daml.lf.archive.{Dar, DarReader}
|
||||
import com.daml.lf.archive.Decode
|
||||
import com.daml.lf.data.Ref._
|
||||
@ -37,7 +40,7 @@ import com.daml.lf.speedy.SValue
|
||||
import com.daml.lf.speedy.SValue._
|
||||
import com.daml.grpc.adapter.{AkkaExecutionSequencerPool, ExecutionSequencerFactory}
|
||||
import com.daml.http.HttpService
|
||||
import com.daml.jwt.JwtSigner
|
||||
import com.daml.jwt.{JwtSigner, HMAC256Verifier}
|
||||
import com.daml.jwt.domain.DecodedJwt
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.testing.utils.{
|
||||
@ -46,7 +49,7 @@ import com.daml.ledger.api.testing.utils.{
|
||||
SuiteResourceManagementAroundAll,
|
||||
Resource => TestResource
|
||||
}
|
||||
import com.daml.ledger.api.auth.{AuthServiceJWTCodec, AuthServiceJWTPayload}
|
||||
import com.daml.ledger.api.auth.{AuthServiceJWT, AuthServiceJWTCodec, AuthServiceJWTPayload}
|
||||
import com.daml.ledger.api.tls.TlsConfiguration
|
||||
import com.daml.platform.apiserver.services.GrpcClientResource
|
||||
import com.daml.platform.common.LedgerIdMode
|
||||
@ -68,8 +71,13 @@ trait JsonApiFixture
|
||||
override protected def channel: Channel = suiteResource.value._2
|
||||
override protected def config: SandboxConfig =
|
||||
super.config
|
||||
.copy(ledgerIdMode = LedgerIdMode.Static(LedgerId("MyLedger")))
|
||||
.copy(
|
||||
ledgerIdMode = LedgerIdMode.Static(LedgerId("MyLedger")),
|
||||
authService = Some(AuthServiceJWT(HMAC256Verifier(secret).valueOr(err =>
|
||||
sys.error(s"Failed to create HMAC256 verifierd $err")))),
|
||||
)
|
||||
def httpPort: Int = suiteResource.value._3.localAddress.getPort
|
||||
protected val secret: String = "secret"
|
||||
|
||||
// We have to use a different actorsystem for the JSON API since package reloading
|
||||
// blocks everything so it will timeout as sandbox cannot make progres simultaneously.
|
||||
@ -77,14 +85,38 @@ trait JsonApiFixture
|
||||
private val jsonApiMaterializer: Materializer = Materializer(system)
|
||||
private val jsonApiExecutionSequencerFactory: ExecutionSequencerFactory =
|
||||
new AkkaExecutionSequencerPool(poolName = "json-api", actorCount = 1)
|
||||
private val jsonAccessTokenFile = Files.createTempFile("http-jsn", "auth")
|
||||
|
||||
override protected def afterAll(): Unit = {
|
||||
jsonApiExecutionSequencerFactory.close()
|
||||
materializer.shutdown()
|
||||
Await.result(jsonApiActorSystem.terminate(), 30.seconds)
|
||||
try {
|
||||
Files.delete(jsonAccessTokenFile)
|
||||
} catch {
|
||||
case NonFatal(_) =>
|
||||
}
|
||||
super.afterAll()
|
||||
}
|
||||
|
||||
protected def getToken(parties: List[String], admin: Boolean): String = {
|
||||
val payload = AuthServiceJWTPayload(
|
||||
ledgerId = Some("MyLedger"),
|
||||
participantId = None,
|
||||
exp = None,
|
||||
applicationId = Some("foobar"),
|
||||
actAs = parties,
|
||||
admin = admin,
|
||||
readAs = List()
|
||||
)
|
||||
val header = """{"alg": "HS256", "typ": "JWT"}"""
|
||||
val jwt = DecodedJwt[String](header, AuthServiceJWTCodec.writeToString(payload))
|
||||
JwtSigner.HMAC256.sign(jwt, secret) match {
|
||||
case -\/(e) => throw new IllegalStateException(e.toString)
|
||||
case \/-(a) => a.value
|
||||
}
|
||||
}
|
||||
|
||||
override protected lazy val suiteResource
|
||||
: TestResource[(SandboxServer, Channel, ServerBinding)] = {
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
@ -98,6 +130,7 @@ trait JsonApiFixture
|
||||
httpService <- new ResourceOwner[ServerBinding] {
|
||||
override def acquire()(implicit ec: ExecutionContext): Resource[ServerBinding] = {
|
||||
Resource[ServerBinding] {
|
||||
Files.write(jsonAccessTokenFile, getToken(List(), false).getBytes())
|
||||
val config = new HttpService.DefaultStartSettings {
|
||||
override val ledgerHost = "localhost"
|
||||
override val ledgerPort = server.port.value
|
||||
@ -107,7 +140,7 @@ trait JsonApiFixture
|
||||
override val portFile = None
|
||||
override val tlsConfig = TlsConfiguration(enabled = false, None, None, None)
|
||||
override val wsConfig = None
|
||||
override val accessTokenFile = None
|
||||
override val accessTokenFile = Some(jsonAccessTokenFile)
|
||||
override val allowNonHttps = true
|
||||
}
|
||||
HttpService
|
||||
@ -148,40 +181,31 @@ final class JsonApiIt
|
||||
val (dar, envIface) = readDar(darFile)
|
||||
val (darNoLedger, envIfaceNoLedger) = readDar(darFileNoLedger)
|
||||
|
||||
def getToken(parties: List[String], admin: Boolean): String = {
|
||||
val payload = AuthServiceJWTPayload(
|
||||
ledgerId = Some("MyLedger"),
|
||||
participantId = None,
|
||||
exp = None,
|
||||
applicationId = Some("foobar"),
|
||||
actAs = parties,
|
||||
admin = admin,
|
||||
readAs = List()
|
||||
)
|
||||
val header = """{"alg": "HS256", "typ": "JWT"}"""
|
||||
val jwt = DecodedJwt[String](header, AuthServiceJWTCodec.writeToString(payload))
|
||||
JwtSigner.HMAC256.sign(jwt, "secret") match {
|
||||
case -\/(e) => throw new IllegalStateException(e.toString)
|
||||
case \/-(a) => a.value
|
||||
}
|
||||
}
|
||||
|
||||
private def getClients(
|
||||
parties: List[String] = List(party),
|
||||
defaultParty: Option[String] = None,
|
||||
admin: Boolean = false,
|
||||
applicationId: Option[ApplicationId] = None,
|
||||
envIface: EnvironmentInterface = envIface) = {
|
||||
// We give the default participant some nonsense party so the checks for party mismatch fail
|
||||
// due to the mismatch and not because the token does not allow inferring a party
|
||||
val defaultParticipant =
|
||||
ApiParameters("http://localhost", httpPort, Some(getToken(defaultParty.toList, true)))
|
||||
ApiParameters(
|
||||
"http://localhost",
|
||||
httpPort,
|
||||
Some(getToken(defaultParty.toList, true)),
|
||||
applicationId)
|
||||
val partyMap = parties.map(p => (ScriptParty(p), Participant(p))).toMap
|
||||
val participantMap = parties
|
||||
.map(
|
||||
p =>
|
||||
(
|
||||
Participant(p),
|
||||
ApiParameters("http://localhost", httpPort, Some(getToken(List(p), admin)))))
|
||||
ApiParameters(
|
||||
"http://localhost",
|
||||
httpPort,
|
||||
Some(getToken(List(p), admin)),
|
||||
applicationId)))
|
||||
.toMap
|
||||
val participantParams = Participants(Some(defaultParticipant), participantMap, partyMap)
|
||||
Runner.jsonClients(participantParams, envIface)
|
||||
@ -246,6 +270,22 @@ final class JsonApiIt
|
||||
exception.getMessage === "Tried to submit a command as Bob but token is only valid for Alice")
|
||||
}
|
||||
}
|
||||
"application id mismatch" in {
|
||||
for {
|
||||
exception <- recoverToExceptionIf[RuntimeException](
|
||||
getClients(applicationId = Some(ApplicationId("wrong"))))
|
||||
} yield
|
||||
assert(
|
||||
exception.getMessage === "ApplicationId specified in token Some(foobar) must match Some(wrong)")
|
||||
}
|
||||
"application id correct" in {
|
||||
for {
|
||||
clients <- getClients(
|
||||
defaultParty = Some("Alice"),
|
||||
applicationId = Some(ApplicationId("foobar")))
|
||||
r <- run(clients, QualifiedName.assertFromString("ScriptTest:jsonCreateAndExercise"))
|
||||
} yield assert(r == SInt64(42))
|
||||
}
|
||||
"query with party mismatch fails" in {
|
||||
for {
|
||||
clients <- getClients(defaultParty = Some("Alice"))
|
||||
|
@ -148,8 +148,10 @@ object MultiParticipant {
|
||||
val participantParams = Participants(
|
||||
None,
|
||||
Seq(
|
||||
(Participant("one"), ApiParameters("localhost", config.ledgerPort, None)),
|
||||
(Participant("two"), ApiParameters("localhost", config.extraParticipantPort, None))
|
||||
(Participant("one"), ApiParameters("localhost", config.ledgerPort, None, None)),
|
||||
(
|
||||
Participant("two"),
|
||||
ApiParameters("localhost", config.extraParticipantPort, None, None))
|
||||
).toMap,
|
||||
Map.empty
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.speedy.SValue._
|
||||
import com.daml.daml_lf_dev.DamlLf
|
||||
import com.daml.ledger.api.auth.{AuthServiceJWTCodec, AuthServiceJWTPayload}
|
||||
import com.daml.ledger.api.refinements.ApiTypes.ApplicationId
|
||||
import com.daml.jwt.JwtSigner
|
||||
import com.daml.jwt.domain.DecodedJwt
|
||||
|
||||
@ -416,7 +417,8 @@ object SingleParticipant {
|
||||
ledgerId = None,
|
||||
participantId = None,
|
||||
exp = None,
|
||||
applicationId = None,
|
||||
// Set the application id to make sure it is set correctly.
|
||||
applicationId = Some("daml-script-test"),
|
||||
actAs = parties,
|
||||
admin = admin,
|
||||
readAs = List()
|
||||
@ -449,10 +451,15 @@ object SingleParticipant {
|
||||
ApiParameters(
|
||||
"localhost",
|
||||
config.ledgerPort,
|
||||
Some(getToken(List("Alice"), false)))),
|
||||
Some(getToken(List("Alice"), false)),
|
||||
Some(ApplicationId("daml-script-test")))),
|
||||
(
|
||||
Participant("bob"),
|
||||
ApiParameters("localhost", config.ledgerPort, Some(getToken(List("Bob"), false))))
|
||||
ApiParameters(
|
||||
"localhost",
|
||||
config.ledgerPort,
|
||||
Some(getToken(List("Bob"), false)),
|
||||
Some(ApplicationId("daml-script-test"))))
|
||||
).toMap,
|
||||
List(
|
||||
(ScriptParty("Alice"), Participant("alice")),
|
||||
@ -460,7 +467,7 @@ object SingleParticipant {
|
||||
)
|
||||
} else {
|
||||
Participants(
|
||||
Some(ApiParameters("localhost", config.ledgerPort, None)),
|
||||
Some(ApiParameters("localhost", config.ledgerPort, None, None)),
|
||||
Map.empty,
|
||||
Map.empty)
|
||||
}
|
||||
|
@ -91,11 +91,7 @@ class TestRunner(
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
val clientsF =
|
||||
Runner.connect(
|
||||
participantParams,
|
||||
Runner.DEFAULT_APPLICATION_ID,
|
||||
tlsConfig,
|
||||
maxInboundMessageSize)
|
||||
Runner.connect(participantParams, tlsConfig, maxInboundMessageSize)
|
||||
|
||||
val testFlow: Future[Unit] = for {
|
||||
clients <- clientsF
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"default_participant": {"host": "localhost", "port": 6866, "access_token": "default_jwt"},
|
||||
"default_participant": {"host": "localhost", "port": 6866, "access_token": "default_jwt", "application_id": "myapp"},
|
||||
"participants": {
|
||||
"one": {"host": "localhost", "port": 6865, "access_token": "jwt_for_alice"},
|
||||
"two": {"host": "localhost", "port": 6865, "access_token": "jwt_for_bob"}
|
||||
"one": {"host": "localhost", "port": 6865, "access_token": "jwt_for_alice", "application_id": "myapp"},
|
||||
"two": {"host": "localhost", "port": 6865, "access_token": "jwt_for_bob", "application_id": "myapp"}
|
||||
},
|
||||
"party_participants": {"alice": "one", "bob": "two"}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user