Skip to content

Commit

Permalink
Support authentication in DAML script (digital-asset#4637)
Browse files Browse the repository at this point in the history
changelog_begin

- [DAML Script - Experimental] Support running DAML scripts against an
authenticated ledger. The token is passed via ``--access-token-file``.

changelog_end
  • Loading branch information
cocreature authored Feb 20, 2020
1 parent 24cfd1e commit 2fb5349
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 19 deletions.
1 change: 1 addition & 0 deletions daml-script/runner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ da_scala_library(
"//ledger/ledger-api-common",
"//ledger/participant-state",
"//ledger/sandbox",
"//libs-scala/auth-utils",
"//libs-scala/resources",
"@maven//:com_github_scopt_scopt_2_12",
"@maven//:com_typesafe_akka_akka_stream_2_12",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.digitalasset.daml.lf.engine.script

import java.nio.file.{Path, Paths}
import java.io.File
import java.time.Duration

Expand All @@ -17,6 +18,7 @@ case class RunnerConfig(
timeProviderType: TimeProviderType,
commandTtl: Duration,
inputFile: Option[File],
accessTokenFile: Option[Path],
)

object RunnerConfig {
Expand Down Expand Up @@ -72,6 +74,12 @@ object RunnerConfig {
}
.text("Path to a file containing the input value for the script in JSON format.")

opt[String]("access-token-file")
.action { (f, c) =>
c.copy(accessTokenFile = Some(Paths.get(f)))
}
.text("File from which the access token will be read, required to interact with an authenticated ledger")

help("help").text("Print this usage text")

checkConfig(c => {
Expand Down Expand Up @@ -100,6 +108,7 @@ object RunnerConfig {
timeProviderType = null,
commandTtl = Duration.ofSeconds(30L),
inputFile = None,
accessTokenFile = None,
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.digitalasset.ledger.client.configuration.{
}
import com.digitalasset.ledger.client.services.commands.CommandUpdater
import com.digitalasset.platform.services.time.TimeProviderType
import com.digitalasset.auth.TokenHolder

object RunnerMain {

Expand All @@ -44,11 +45,16 @@ object RunnerMain {
Identifier(dar.main._1, QualifiedName.assertFromString(config.scriptIdentifier))

val applicationId = ApplicationId("Script Runner")
// Note (MK): For now, we only support using a single-token for everything.
// We might want to extend this to allow for multiple tokens, e.g., one token per party +
// one admin token for allocating parties.
val tokenHolder = config.accessTokenFile.map(new TokenHolder(_))
val clientConfig = LedgerClientConfiguration(
applicationId = ApplicationId.unwrap(applicationId),
ledgerIdRequirement = LedgerIdRequirement("", enabled = false),
commandClient = CommandClientConfiguration.default,
sslContext = None
sslContext = None,
token = tokenHolder.flatMap(_.token),
)
val timeProvider: TimeProvider =
config.timeProviderType match {
Expand Down
46 changes: 45 additions & 1 deletion daml-script/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ da_scala_library(
"//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge",
"//ledger/ledger-api-common",
"//libs-scala/auth-utils",
"@maven//:com_github_scopt_scopt_2_12",
"@maven//:com_typesafe_akka_akka_stream_2_12",
"@maven//:io_spray_spray_json_2_12",
Expand All @@ -69,7 +70,10 @@ da_scala_library(
da_scala_binary(
name = "test_client_single_participant",
main_class = "com.digitalasset.daml.lf.engine.script.test.SingleParticipant",
deps = [":test-lib"],
deps = [
":test-lib",
"//libs-scala/auth-utils",
],
)

da_scala_binary(
Expand Down Expand Up @@ -102,6 +106,46 @@ client_server_test(
server_files = ["$(rootpath :script-test.dar)"],
)

AUTH_TOKEN = "I_CAN_HAZ_AUTH"

# This is a genrule so we can replace it by something nicer that actually generates the token
# from some readable input so we can change it more easily.
# For now, this corresponds to a token that has admin set to false
# and actAs to Alice, Bob
genrule(
name = "test-auth-token",
outs = ["test-auth-token.jwt"],
cmd = """
cat <<EOF > $(location test-auth-token.jwt)
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2RhbWwuY29tL2xlZGdlci1hcGkiOnsiYWRtaW4iOmZhbHNlLCJhY3RBcyI6WyJBbGljZSIsIkJvYiJdfX0.UYogXMZvmhzzjmsmKayaDsI2hGKh1T2Sz5l9h4tdgGM
EOF
""",
)

client_server_test(
name = "test_wallclock_time_authenticated",
client = ":test_client_single_participant",
client_args = [
"-w",
"--access-token-file",
],
client_files = [
"$(rootpath :test-auth-token.jwt)",
"$(rootpath :script-test.dar)",
],
data = [
":script-test.dar",
":test-auth-token.jwt",
],
server = "//ledger/sandbox:sandbox-binary",
server_args = [
"-w",
"--port=0",
"--auth-jwt-hs256-unsafe={}".format(AUTH_TOKEN),
],
server_files = ["$(rootpath :script-test.dar)"],
)

client_server_test(
name = "test_multiparticipant",
client = ":test_client_multi_participant",
Expand Down
6 changes: 6 additions & 0 deletions daml-script/test/daml/ScriptTest.daml
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,9 @@ partyIdHintTest = do
carol <- allocatePartyWithHint "carol" (PartyIdHint "carol")
dan <- allocatePartyWithHint "dan" (PartyIdHint "dan")
pure (carol, dan)

auth : (Party, Party) -> Script ()
auth (alice, bob) = do
proposal <- submit alice $ createCmd (TProposal alice bob)
_ <- submit bob $ exerciseCmd proposal Accept
pure ()
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ object MultiParticipant {
Map.empty
)

val runner = new TestRunner(participantParams, dar, config.wallclockTime)
val runner = new TestRunner(participantParams, dar, config.wallclockTime, None)
MultiTest(dar, runner).runTests()
MultiPartyIdHintTest(dar, runner).runTests()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

package com.digitalasset.daml.lf.engine.script.test

import java.nio.file.{Path, Paths}
import java.io.File
import java.time.Duration
import scalaz.syntax.traverse._
import spray.json._

import com.digitalasset.auth.TokenHolder
import com.digitalasset.daml.lf.archive.Dar
import com.digitalasset.daml.lf.archive.DarReader
import com.digitalasset.daml.lf.archive.Decode
Expand All @@ -21,7 +23,11 @@ import com.digitalasset.ledger.api.refinements.ApiTypes.{ApplicationId}

import com.digitalasset.daml.lf.engine.script._

case class Config(ledgerPort: Int, darPath: File, wallclockTime: Boolean)
case class Config(
ledgerPort: Int,
darPath: File,
wallclockTime: Boolean,
accessTokenFile: Option[Path])

case class Test0(dar: Dar[(PackageId, Package)], runner: TestRunner) {
val scriptId = Identifier(dar.main._1, QualifiedName.assertFromString("ScriptTest:test0"))
Expand Down Expand Up @@ -264,6 +270,20 @@ case class ScriptExample(dar: Dar[(PackageId, Package)], runner: TestRunner) {
}
}

case class TestAuth(dar: Dar[(PackageId, Package)], runner: TestRunner) {
val scriptId = Identifier(dar.main._1, QualifiedName.assertFromString("ScriptTest:auth"))
def runTests(): Unit = {
runner.genericTest(
"auth",
scriptId,
Some(JsObject(("_1", JsString("Alice")), ("_2", JsString("Bob")))), {
case SUnit => Right(())
case v => Left(s"Expected SInt but got $v")
}
)
}
}

object SingleParticipant {

private val configParser = new scopt.OptionParser[Config]("daml_script_test") {
Expand All @@ -282,12 +302,16 @@ object SingleParticipant {
c.copy(wallclockTime = true)
}
.text("Use wall clock time (UTC). When not provided, static time is used.")
opt[String]("access-token-file")
.action { (f, c) =>
c.copy(accessTokenFile = Some(Paths.get(f)))
}
}

private val applicationId = ApplicationId("DAML Script Tests")

def main(args: Array[String]): Unit = {
configParser.parse(args, Config(0, null, false)) match {
configParser.parse(args, Config(0, null, false, None)) match {
case None =>
sys.exit(1)
case Some(config) =>
Expand All @@ -300,18 +324,28 @@ object SingleParticipant {
val participantParams =
Participants(Some(ApiParameters("localhost", config.ledgerPort)), Map.empty, Map.empty)

val runner = new TestRunner(participantParams, dar, config.wallclockTime)
Test0(dar, runner).runTests()
Test1(dar, runner).runTests()
Test2(dar, runner).runTests()
Test3(dar, runner).runTests()
Test4(dar, runner).runTests()
TestKey(dar, runner).runTests()
TestCreateAndExercise(dar, runner).runTests()
Time(dar, runner).runTests()
Sleep(dar, runner).runTests()
PartyIdHintTest(dar, runner).runTests()
ScriptExample(dar, runner).runTests()
val tokenHolder = config.accessTokenFile.map(new TokenHolder(_))

val runner =
new TestRunner(participantParams, dar, config.wallclockTime, tokenHolder.flatMap(_.token))
config.accessTokenFile match {
case None =>
Test0(dar, runner).runTests()
Test1(dar, runner).runTests()
Test2(dar, runner).runTests()
Test3(dar, runner).runTests()
Test4(dar, runner).runTests()
TestKey(dar, runner).runTests()
TestCreateAndExercise(dar, runner).runTests()
Time(dar, runner).runTests()
Sleep(dar, runner).runTests()
PartyIdHintTest(dar, runner).runTests()
ScriptExample(dar, runner).runTests()
case Some(_) =>
// We can’t test much with auth since most of our tests rely on party allocation and being
// able to act as the corresponding party.
TestAuth(dar, runner).runTests()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,17 @@ object TestRunner {
class TestRunner(
val participantParams: Participants[ApiParameters],
val dar: Dar[(PackageId, Package)],
val wallclockTime: Boolean)
val wallclockTime: Boolean,
val token: Option[String])
extends StrictLogging {
val applicationId = ApplicationId("DAML Script Test Runner")

val clientConfig = LedgerClientConfiguration(
applicationId = applicationId.unwrap,
ledgerIdRequirement = LedgerIdRequirement("", enabled = false),
commandClient = CommandClientConfiguration.default,
sslContext = None
sslContext = None,
token = token,
)
val ttl = java.time.Duration.ofSeconds(30)
val timeProvider: TimeProvider =
Expand Down

0 comments on commit 2fb5349

Please sign in to comment.