Skip to content

Commit

Permalink
Improve evidence of testing (digital-asset#11428)
Browse files Browse the repository at this point in the history
* fine grained test evidence for authorization

* fine grained test evidence for privacy

* fine grained test evidence for input-validation (typing)

* fix exit code of security/update.sh script (set -euo pipefail)

* add security evidence test category: Input Validation

* regenerate security-evidence.md

CHANGELOG_BEGIN
CHANGELOG_END

* fix bug in securoty evidence generation (must sort before group, or else we loose lines)

* evidence for input validation of commands

* address comments

* cleanup: remove backticks from evidence free text
  • Loading branch information
nickchapman-da authored Oct 28, 2021
1 parent 6126fc2 commit 68f4432
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import org.scalatest.matchers.should.Matchers

import scala.language.implicitConversions

// TEST_EVIDENCE: Authorization: Engine level tests for _authorization_ check.
class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with BazelRunfiles {

implicit private def toName(s: String): Name = Name.assertFromString(s)
Expand Down Expand Up @@ -209,6 +208,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz

"Exercise(Choice1 of T1 to create T2)" - {

// TEST_EVIDENCE: Authorization: well-authorized exercise/create is accepted
"ok (Alice signed contract; Bob exercised Choice)" in {
val command: ApiCommand =
ExerciseCommand(
Expand All @@ -230,6 +230,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
interpretResult shouldBe a[Right[_, _]]
}

// TEST_EVIDENCE: Authorization: badly-authorized exercise/create (exercise is unauthorized) is rejected
"fail: ExerciseMissingAuthorization" in {
val command: ApiCommand =
ExerciseCommand(
Expand Down Expand Up @@ -261,6 +262,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
}
}

// TEST_EVIDENCE: Authorization: badly-authorized exercise/create (create is unauthorized) is rejected
"fail: CreateMissingAuthorization" in {
val command: ApiCommand =
ExerciseCommand(
Expand Down Expand Up @@ -314,13 +316,12 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
}
}

// TEST_EVIDENCE: Authorization: Exercise within exercise: No implicit authorization from outer exercise.

"Exercise (within exercise)" - {

// Test that an inner exercise has only the authorization of the signatories and
// controllers; with no implicit authorization of signatories of the outer exercise.

// TEST_EVIDENCE: Authorization: badly-authorized exercise/exercise (no implicit authority from outer exercise) is rejected
"fail (no implicit authority from outer exercise's contract's signatories)" in {
val command: ApiCommand =
ExerciseCommand(
Expand Down Expand Up @@ -363,6 +364,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
}
}

// TEST_EVIDENCE: Authorization: well-authorized exercise/exercise is accepted
"ok" in {
val command: ApiCommand =
ExerciseCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import org.scalatest.matchers.should.Matchers

import org.scalatest.Inside

// TEST_EVIDENCE: Authorization: Unit test _authorization_ computations in: `CheckAuthorization`.
class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {

// Test the various forms of FailedAuthorization which can be returned from CheckAuthorization
Expand All @@ -41,12 +40,14 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
)

"create" - {
// TEST_EVIDENCE: Authorization: well-authorized create is accepted
"ok" in {
val createNode = makeCreateNode()
val auth = Authorize(Set("Alice", "Bob", "Mary"))
val fails = CheckAuthorization.authorizeCreate(optLocation = None, createNode)(auth)
fails shouldBe Nil
}
// TEST_EVIDENCE: Authorization: create with no signatories is rejected
"NoSignatories" in {
val createNode = makeCreateNode(signatories = Nil, maintainers = Nil)
val auth = Authorize(Set("Alice", "Bob", "Mary"))
Expand All @@ -56,6 +57,7 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
}
}
}
// TEST_EVIDENCE: Authorization: badly-authorized create is rejected
"CreateMissingAuthorization" in {
val createNode = makeCreateNode()
val auth = Authorize(Set("Alice"))
Expand All @@ -67,6 +69,7 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
}
}
}
// TEST_EVIDENCE: Authorization: create with non-signatory maintainers is rejected
"MaintainersNotSubsetOfSignatories" in {
val createNode = makeCreateNode(maintainers = Seq("Alice", "Mary"))
val auth = Authorize(Set("Alice", "Bob", "Mary"))
Expand All @@ -83,11 +86,13 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
"fetch" - {
val contract = makeCreateNode()
val fetchNode = builder.fetch(contract)
// TEST_EVIDENCE: Authorization: well-authorized fetch is accepted
"ok" in {
val auth = Authorize(Set("Alice", "Mary", "Nigel"))
val fails = CheckAuthorization.authorizeFetch(optLocation = None, fetchNode)(auth)
fails shouldBe Nil
}
// TEST_EVIDENCE: Authorization: badly-authorized fetch is rejected
"FetchMissingAuthorization" in {
val auth = Authorize(Set("Mary", "Nigel"))
val fails = CheckAuthorization.authorizeFetch(optLocation = None, fetchNode)(auth)
Expand All @@ -103,11 +108,13 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
"lookup-by-key" - {
val contract = makeCreateNode(maintainers = Seq("Alice", "Bob"))
val lookupNode = builder.lookupByKey(contract, found = true)
// TEST_EVIDENCE: Authorization: well-authorized lookup is accepted
"ok" in {
val auth = Authorize(Set("Alice", "Bob", "Mary"))
val fails = CheckAuthorization.authorizeLookupByKey(optLocation = None, lookupNode)(auth)
fails shouldBe Nil
}
// TEST_EVIDENCE: Authorization: badly-authorized lookup is rejected
"LookupByKeyMissingAuthorization" in {
val auth = Authorize(Set("Alice", "Mary"))
val fails = CheckAuthorization.authorizeLookupByKey(optLocation = None, lookupNode)(auth)
Expand All @@ -131,12 +138,14 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
argument = ValueRecord(None, ImmArray.empty),
)
}
// TEST_EVIDENCE: Authorization: well-authorized exercise is accepted
"ok" in {
val auth = Authorize(Set("Alice", "John", "Mary"))
val exeNode = makeExeNode()
val fails = CheckAuthorization.authorizeExercise(optLocation = None, exeNode)(auth)
fails shouldBe Nil
}
// TEST_EVIDENCE: Authorization: exercise with no controllers is rejected
"NoControllers" in {
val exeNode = makeExeNode(actingParties = Nil)
val auth = Authorize(Set("Alice", "John", "Mary"))
Expand All @@ -146,6 +155,7 @@ class AuthorizationSpec extends AnyFreeSpec with Matchers with Inside {
}
}
}
// TEST_EVIDENCE: Authorization: badly-authorized exercise is rejected
"ExerciseMissingAuthorization" in {
val exeNode = makeExeNode()
val auth = Authorize(Set("Alice", "John"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.daml.lf.value.Value.ValueRecord
import org.scalatest.matchers.should.Matchers
import org.scalatest.freespec.AnyFreeSpec

// TEST_EVIDENCE: Privacy: Unit test _blinding_ computation: `Blinding.blind`.
class BlindingSpec extends AnyFreeSpec with Matchers {

import TransactionBuilder.Implicits._
Expand All @@ -30,6 +29,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
}

"blind" - {
// TEST_EVIDENCE: Privacy: ensure correct privacy for create node
"create" in {
val builder = TransactionBuilder()
val (_, createNode) = create(builder)
Expand All @@ -40,6 +40,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
divulgence = Map.empty,
)
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for exercise node (consuming)
"consuming exercise" in {
val builder = TransactionBuilder()
val (cid, createNode) = create(builder)
Expand All @@ -58,6 +59,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
divulgence = Map(cid -> Set("ChoiceObserver")),
)
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for exercise node (non-consuming)
"non-consuming exercise" in {
val builder = TransactionBuilder()
val (cid, createNode) = create(builder)
Expand All @@ -77,6 +79,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
)
}
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for fetch node
"fetch" in {
val builder = TransactionBuilder()
val (_, createNode) = create(builder)
Expand All @@ -88,6 +91,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
divulgence = Map.empty,
)
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for lookup-by-key node (found)
"lookupByKey found" in {
val builder = TransactionBuilder()
val cid = builder.newCid
Expand All @@ -108,6 +112,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
divulgence = Map.empty,
)
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for lookup-by-key node (not-found)
"lookupByKey not found" in {
val builder = TransactionBuilder()
val cid = builder.newCid
Expand All @@ -129,6 +134,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
)
}

// TEST_EVIDENCE: Privacy: ensure correct privacy for exercise subtree
"exercise with children" in {
val builder = TransactionBuilder()
val cid1 = builder.newCid
Expand Down Expand Up @@ -190,6 +196,7 @@ class BlindingSpec extends AnyFreeSpec with Matchers {
),
)
}
// TEST_EVIDENCE: Privacy: ensure correct privacy for rollback subtree
"rollback" in {
val builder = TransactionBuilder()
val cid1 = builder.newCid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,36 +80,43 @@ class CommandPreprocessorSpec

"reject improperly typed commands" in {

// TEST_EVIDENCE: Input Validation: well formed create command is accepted
val validCreate = CreateCommand(
"Mod:Record",
ValueRecord("", ImmArray("owners" -> valueParties, "data" -> ValueInt64(42))),
)
// TEST_EVIDENCE: Input Validation: well formed exercise command is accepted
val validExe = ExerciseCommand(
"Mod:Record",
newCid,
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
// TEST_EVIDENCE: Input Validation: well formed exercise-by-key command is accepted
val validExeByKey = ExerciseByKeyCommand(
"Mod:Record",
valueParties,
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
// TEST_EVIDENCE: Input Validation: well formed create-and-exercise command is accepted
val validCreateAndExe = CreateAndExerciseCommand(
"Mod:Record",
ValueRecord("", ImmArray("owners" -> valueParties, "data" -> ValueInt64(42))),
"Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
)
// TEST_EVIDENCE: Input Validation: well formed fetch command is accepted
val validFetch = FetchCommand(
"Mod:Record",
newCid,
)
// TEST_EVIDENCE: Input Validation: well formed fetch-by-key command is accepted
val validFetchByKey = FetchByKeyCommand(
"Mod:Record",
valueParties,
)
// TEST_EVIDENCE: Input Validation: well formed lookup command is accepted
val validLookup = LookupByKeyCommand(
"Mod:Record",
valueParties,
Expand All @@ -127,16 +134,19 @@ class CommandPreprocessorSpec

val errorTestCases = Table[Command, ResultOfATypeInvocation[_]](
("command", "error"),
// TEST_EVIDENCE: Input Validation: ill-formed create command is rejected
validCreate.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validCreate.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed exercise command is rejected
validExe.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validExe.copy(choiceId = "Undefined") ->
a[Error.Preprocessing.Lookup],
validExe.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed exercise-by-key command is rejected
validExeByKey.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validExeByKey.copy(contractKey = ValueList(FrontStack(ValueInt64(42)))) ->
Expand All @@ -145,6 +155,7 @@ class CommandPreprocessorSpec
a[Error.Preprocessing.Lookup],
validExeByKey.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed create-and-exercise command is rejected
validCreateAndExe.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validCreateAndExe.copy(createArgument =
Expand All @@ -157,12 +168,15 @@ class CommandPreprocessorSpec
ValueRecord("", ImmArray("content" -> ValueInt64(42)))
) ->
a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed fetch command is rejected
validFetch.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
// TEST_EVIDENCE: Input Validation: ill-formed fetch-by-key command is rejected
validFetchByKey.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validFetchByKey.copy(key = ValueList(FrontStack(ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed lookup command is rejected
validLookup.copy(templateId = "Mod:Undefined") ->
a[Error.Preprocessing.Lookup],
validLookup.copy(contractKey = ValueList(FrontStack(ValueInt64(42)))) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.scalatest.wordspec.AnyWordSpec
class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matchers {

"Checker.checkKind" should {
// TEST_EVIDENCE: Input Validation: ill-formed kinds are rejected
"reject invalid kinds" in {

val negativeTestCases = Table(
Expand Down Expand Up @@ -43,6 +44,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher

"Checker.kindOf" should {

// TEST_EVIDENCE: Input Validation: ensure builtin operators have the correct type
"infers the proper kind for builtin types (but ContractId)" in {
val testCases = Table(
"builtin type" -> "expected kind",
Expand Down Expand Up @@ -94,13 +96,16 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
}
}

// TEST_EVIDENCE: Input Validation: ill-formed types are rejected
"reject ill-formed types" in {
an[ENatKindRightOfArrow] shouldBe thrownBy(env.kindOf(T"""∀ (τ: ⋆ → nat). Unit """))
}
}

"Checker.typeOf" should {

// TEST_EVIDENCE: Input Validation: ensure expression forms have the correct type

"infers the proper type for expression" in {
// The part of the expression that corresponds to the expression
// defined by the given rule should be wrapped in double
Expand Down Expand Up @@ -400,6 +405,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
}
}

// TEST_EVIDENCE: Input Validation: ill-formed expressions are rejected
"reject ill formed terms" in {

// In the following test cases we use the variable `nothing` when we
Expand Down Expand Up @@ -805,6 +811,8 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher

//TODO add check for interface definitions.
//TODO add check for interface implementations.

// TEST_EVIDENCE: Input Validation: ill-formed templates are rejected
"reject ill formed template definition" in {

val pkg =
Expand Down Expand Up @@ -1067,6 +1075,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher

}

// TEST_EVIDENCE: Input Validation: ill-formed exception definitions are rejected
"reject ill formed exception definitions" in {

val pkg =
Expand Down Expand Up @@ -1187,6 +1196,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
}
}

// TEST_EVIDENCE: Input Validation: ill-formed type synonyms applications are rejected
"reject ill formed type synonym application" in {
val testCases = Table(
"badly formed type synonym application",
Expand All @@ -1207,6 +1217,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
}
}

// TEST_EVIDENCE: Input Validation: ill-formed records are rejected
"reject ill formed type record definitions" in {

def checkModule(mod: Module) = {
Expand All @@ -1229,6 +1240,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
forEvery(positiveTestCases)(mod => a[ValidationError] should be thrownBy checkModule(mod))
}

// TEST_EVIDENCE: Input Validation: ill-formed variants are rejected
"reject ill formed type variant definitions" in {

def checkModule(mod: Module) = {
Expand All @@ -1251,6 +1263,7 @@ class TypingSpec extends AnyWordSpec with TableDrivenPropertyChecks with Matcher
forEvery(positiveTestCases)(mod => a[ValidationError] should be thrownBy checkModule(mod))
}

// TEST_EVIDENCE: Input Validation: ill-formed type synonyms definitions are rejected
"reject ill formed type synonym definitions" in {

def checkModule(mod: Module) = {
Expand Down
Loading

0 comments on commit 68f4432

Please sign in to comment.