diff --git a/compiler/damlc/lib/DA/Cli/Damlc.hs b/compiler/damlc/lib/DA/Cli/Damlc.hs index 5968614844..2fb1b6af24 100644 --- a/compiler/damlc/lib/DA/Cli/Damlc.hs +++ b/compiler/damlc/lib/DA/Cli/Damlc.hs @@ -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 diff --git a/compiler/damlc/tests/src/DA/Test/Repl/FuncTests.hs b/compiler/damlc/tests/src/DA/Test/Repl/FuncTests.hs index a4895e31ea..4187356bc1 100644 --- a/compiler/damlc/tests/src/DA/Test/Repl/FuncTests.hs +++ b/compiler/damlc/tests/src/DA/Test/Repl/FuncTests.hs @@ -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 diff --git a/compiler/repl-service/client/src/DA/Daml/LF/ReplClient.hs b/compiler/repl-service/client/src/DA/Daml/LF/ReplClient.hs index 7fc834dcca..255e68a3f1 100644 --- a/compiler/repl-service/client/src/DA/Daml/LF/ReplClient.hs +++ b/compiler/repl-service/client/src/DA/Daml/LF/ReplClient.hs @@ -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 diff --git a/compiler/repl-service/server/src/main/scala/com/digitalasset/daml/lf/ReplServiceMain.scala b/compiler/repl-service/server/src/main/scala/com/digitalasset/daml/lf/ReplServiceMain.scala index c51312d9a0..9673942486 100644 --- a/compiler/repl-service/server/src/main/scala/com/digitalasset/daml/lf/ReplServiceMain.scala +++ b/compiler/repl-service/server/src/main/scala/com/digitalasset/daml/lf/ReplServiceMain.scala @@ -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) diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/LedgerInteraction.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/LedgerInteraction.scala index bcf3a7123a..46af7304ae 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/LedgerInteraction.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/LedgerInteraction.scala @@ -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 diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala index e9d7ba546a..ce43f6952a 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala @@ -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)) + } } } diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerConfig.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerConfig.scala index 8d064a4ae3..cd5cdad4b7 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerConfig.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerConfig.scala @@ -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, ) ) } diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerMain.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerMain.scala index 089c492064..65cbae0e95 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerMain.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/RunnerMain.scala @@ -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 { diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestMain.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestMain.scala index 43060442de..b1226937f5 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestMain.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/TestMain.scala @@ -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, ) diff --git a/daml-script/test/BUILD.bazel b/daml-script/test/BUILD.bazel index e675b01a1f..594eab9c42 100644 --- a/daml-script/test/BUILD.bazel +++ b/daml-script/test/BUILD.bazel @@ -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", diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/JsonApiIt.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/JsonApiIt.scala index 70e68fcc0f..edc4572e3b 100644 --- a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/JsonApiIt.scala +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/JsonApiIt.scala @@ -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")) diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/MultiParticipant.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/MultiParticipant.scala index e8aa4bda1a..f5ee9fcbc7 100644 --- a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/MultiParticipant.scala +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/MultiParticipant.scala @@ -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 ) diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala index 6d64c1c305..1dd396a186 100644 --- a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala @@ -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) } diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/TestRunner.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/TestRunner.scala index f64643698e..ab995a4da5 100644 --- a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/TestRunner.scala +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/TestRunner.scala @@ -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 diff --git a/docs/source/daml-script/participants-example.json b/docs/source/daml-script/participants-example.json index 706866b44e..5dccd3fe2f 100644 --- a/docs/source/daml-script/participants-example.json +++ b/docs/source/daml-script/participants-example.json @@ -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"} }