mirror of
https://github.com/corda/corda.git
synced 2025-04-06 19:07:08 +00:00
CORDA-3018 Whitelisting attachments by public key - relax signer restrictions (#5358)
* CORDA-3018 Allow loading an untrusted contract jar if another attachment exists that was signed with the same keys and uploaded by a trusted uploader `TransactionUtils.isAttachmentTrusted` requirements have been relaxed to allow an untrusted attachment to be loaded as long as another attachment exists that is signed by the same keys and was uploaded by a trusted uploader. The requirement of containing the same contract classes has been removed. Therefore the contents of the existing trusted attachment no longer matters. * CORDA-3018 Allow a subset/intersection of signers in `isAttachmentTrusted` Allow a subset/intersection of signers to satisfy the signer requirements of `isAttachmentTrusted`. This allows an "untrusted" attachment that is signed by one or more keys to be "trusted" as long as another trusted attachment already exists that is signed by at least one of the "untrusted" attachments signers. A cache of trusted and untrusted public keys is now held (replacing the previous cache of `List<PublicKey>`. Tests have been added to `NodeAttachmentServiceTest` to confirm that an attachment query using an `EQUAL` statement will actually return attachments that are signed by any of the keys passed into the query. Confirming this allowed an `EQUAL` query to satisfy the search that had to be done as part of this change. `MockAttachmentStorage`'s query criteria was updated to better match the real `NodeAttachmentService` implementation. * CORDA-3018 Update cache name and kdoc on `isAttachmentTrusted` * CORDA-3018 Verify that chains of trust do not occur * CORDA-3018 Switch keys around to improve chain of trust tests
This commit is contained in:
parent
44428b6048
commit
fc265ee472
@ -3,6 +3,7 @@ package net.corda.coretests.transactions
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.core.internal.inputStream
|
||||
@ -199,4 +200,178 @@ class AttachmentsClassLoaderTests {
|
||||
private fun importAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
|
||||
return jar.use { storage.importAttachment(jar, uploader, filename) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Allow loading an untrusted contract jar if another attachment exists that was signed with the same keys and uploaded by a trusted uploader`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone trusted"
|
||||
).inputStream()
|
||||
classJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"rpc",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = untrustedClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
make(arrayOf(untrustedAttachment).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Allow loading an untrusted contract jar if another attachment exists that was signed by a trusted uploader - intersection of keys match existing attachment`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val keyPairC = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone trusted"
|
||||
).inputStream()
|
||||
classJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"rpc",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairC.public)
|
||||
)
|
||||
}
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = untrustedClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
make(arrayOf(untrustedAttachment).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = untrustedClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
make(arrayOf(untrustedAttachment).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys and uploaded by a trusted uploader`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted with the same keys"
|
||||
).inputStream()
|
||||
classJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = untrustedClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
}
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
make(arrayOf(untrustedAttachment).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Attachments with inherited trust do not grant trust to attachments being loaded (no chain of trust)`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val keyPairC = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted with the same keys"
|
||||
).inputStream()
|
||||
classJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"app",
|
||||
it,
|
||||
signers = listOf(keyPairA.public)
|
||||
)
|
||||
}
|
||||
|
||||
val inheritedTrustClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone who inherits trust"
|
||||
).inputStream()
|
||||
val inheritedTrustAttachment = inheritedTrustClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairB.public, keyPairA.public)
|
||||
)
|
||||
}
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = untrustedClassJar.use {
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
it,
|
||||
signers = listOf(keyPairB.public, keyPairC.public)
|
||||
)
|
||||
}
|
||||
|
||||
make(arrayOf(inheritedTrustAttachment).map { storage.openAttachment(it)!! })
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
make(arrayOf(untrustedAttachment).map { storage.openAttachment(it)!! })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -184,10 +184,9 @@ fun FlowLogic<*>.checkParameterHash(networkParametersHash: SecureHash?) {
|
||||
// For now we don't check whether the attached network parameters match the current ones.
|
||||
}
|
||||
|
||||
private data class AttachmentAttributeKey(val signers: List<PublicKey>, val contractClasses: List<ContractClassName>?)
|
||||
|
||||
// A cache for caching whether a particular attachment ID is trusted
|
||||
private val attachmentTrustedCache: MutableMap<AttachmentAttributeKey, Boolean> = createSimpleCache<AttachmentAttributeKey, Boolean>(100).toSynchronised()
|
||||
// A cache for caching whether a particular set of signers are trusted
|
||||
private val trustedKeysCache: MutableMap<PublicKey, Boolean> =
|
||||
createSimpleCache<PublicKey, Boolean>(100).toSynchronised()
|
||||
|
||||
/**
|
||||
* Establishes whether an attachment should be trusted. This logic is required in order to verify transactions, as transaction
|
||||
@ -195,8 +194,8 @@ private val attachmentTrustedCache: MutableMap<AttachmentAttributeKey, Boolean>
|
||||
*
|
||||
* Attachments are trusted if one of the following is true:
|
||||
* - They are uploaded by a trusted uploader
|
||||
* - There is another attachment in the attachment store signed by the same keys (and with the same contract classes if the attachment is a
|
||||
* contract attachment) that is trusted
|
||||
* - There is another attachment in the attachment store, that is trusted and is signed by at least one key that the input
|
||||
* attachment is also signed with
|
||||
*/
|
||||
fun isAttachmentTrusted(attachment: Attachment, service: AttachmentStorage?): Boolean {
|
||||
val trustedByUploader = when (attachment) {
|
||||
@ -208,22 +207,14 @@ fun isAttachmentTrusted(attachment: Attachment, service: AttachmentStorage?): Bo
|
||||
if (trustedByUploader) return true
|
||||
|
||||
return if (service != null && attachment.signerKeys.isNotEmpty()) {
|
||||
val signers = attachment.signerKeys
|
||||
val contractClasses = if (attachment is ContractAttachment) {
|
||||
attachment.allContracts.toList()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val key = AttachmentAttributeKey(signers, contractClasses)
|
||||
|
||||
attachmentTrustedCache.computeIfAbsent(key) {
|
||||
val contractClassCondition = it.contractClasses?.let { classes -> Builder.equal(classes) }
|
||||
val queryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(
|
||||
contractClassNamesCondition = contractClassCondition,
|
||||
signersCondition = Builder.equal(signers),
|
||||
attachment.signerKeys.any { signer ->
|
||||
trustedKeysCache.computeIfAbsent(signer) {
|
||||
val queryCriteria = AttachmentQueryCriteria.AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(signer)),
|
||||
uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS)
|
||||
)
|
||||
service.queryAttachments(queryCriteria).isNotEmpty()
|
||||
)
|
||||
service.queryAttachments(queryCriteria).isNotEmpty()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
|
@ -14,10 +14,8 @@ import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappImpl.Companion.DEFAULT_CORDAPP_VERSION
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.AttachmentQueryCriteria.AttachmentsQueryCriteria
|
||||
import net.corda.core.node.services.vault.AttachmentSort
|
||||
import net.corda.core.node.services.vault.Builder
|
||||
import net.corda.core.node.services.vault.Sort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.services.transactions.PersistentUniquenessProvider
|
||||
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
|
||||
@ -801,7 +799,7 @@ class NodeAttachmentServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar not trusted if same key but different contract`() {
|
||||
fun `jar trusted if same key but different contract`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val alias = "testAlias"
|
||||
val password = "testPassword"
|
||||
@ -817,7 +815,114 @@ class NodeAttachmentServiceTest {
|
||||
// Sanity check.
|
||||
assertEquals(key1, key2, "Different public keys used to sign jars")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v1Id)!!, storage), "Initial contract $v1Id should be trusted")
|
||||
assertFalse(isAttachmentTrusted(storage.openAttachment(v2Id)!!, storage), "Upgraded contract $v2Id should not be trusted")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v2Id)!!, storage), "Upgraded contract $v2Id should be trusted")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar trusted if the signing keys are a subset of an existing trusted jar's signers`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val alias = "testAlias"
|
||||
val password = "testPassword"
|
||||
val alias2 = "anotherTestAlias"
|
||||
file.path.generateKey(alias, password)
|
||||
file.path.generateKey(alias2, password)
|
||||
|
||||
val jarV1 = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
file.path.signJar(jarV1.toAbsolutePath().toString(), alias, password)
|
||||
file.path.signJar(jarV1.toAbsolutePath().toString(), alias2, password)
|
||||
|
||||
val jarV2 = makeTestContractJar(file.path, "foo.bar.DifferentContract", version = 2)
|
||||
file.path.signJar(jarV2.toAbsolutePath().toString(), alias, password)
|
||||
|
||||
val v1Id = jarV1.read { storage.privilegedImportAttachment(it, "app", "dummy-contract.jar") }
|
||||
val v2Id = jarV2.read { storage.privilegedImportAttachment(it, "untrusted", "dummy-contract.jar") }
|
||||
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v1Id)!!, storage), "Initial contract $v1Id should be trusted")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v2Id)!!, storage), "Upgraded contract $v2Id should be trusted")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar trusted if the signing keys are an intersection of an existing trusted jar's signers`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val alias = "testAlias"
|
||||
val password = "testPassword"
|
||||
val alias2 = "anotherTestAlias"
|
||||
val alias3 = "yetAnotherTestAlias"
|
||||
file.path.generateKey(alias, password)
|
||||
file.path.generateKey(alias2, password)
|
||||
file.path.generateKey(alias3, password)
|
||||
|
||||
val jarV1 = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
file.path.signJar(jarV1.toAbsolutePath().toString(), alias, password)
|
||||
file.path.signJar(jarV1.toAbsolutePath().toString(), alias2, password)
|
||||
|
||||
val jarV2 = makeTestContractJar(file.path, "foo.bar.DifferentContract", version = 2)
|
||||
file.path.signJar(jarV2.toAbsolutePath().toString(), alias, password)
|
||||
file.path.signJar(jarV2.toAbsolutePath().toString(), alias3, password)
|
||||
|
||||
val v1Id = jarV1.read { storage.privilegedImportAttachment(it, "app", "dummy-contract.jar") }
|
||||
val v2Id = jarV2.read { storage.privilegedImportAttachment(it, "untrusted", "dummy-contract.jar") }
|
||||
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v1Id)!!, storage), "Initial contract $v1Id should be trusted")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v2Id)!!, storage), "Upgraded contract $v2Id should be trusted")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar trusted if the signing keys are a superset of an existing trusted jar's signers`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val alias = "testAlias"
|
||||
val password = "testPassword"
|
||||
val alias2 = "anotherTestAlias"
|
||||
file.path.generateKey(alias, password)
|
||||
file.path.generateKey(alias2, password)
|
||||
|
||||
val jarV1 = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
file.path.signJar(jarV1.toAbsolutePath().toString(), alias, password)
|
||||
|
||||
val jarV2 = makeTestContractJar(file.path, "foo.bar.DifferentContract", version = 2)
|
||||
file.path.signJar(jarV2.toAbsolutePath().toString(), alias, password)
|
||||
file.path.signJar(jarV2.toAbsolutePath().toString(), alias2, password)
|
||||
|
||||
val v1Id = jarV1.read { storage.privilegedImportAttachment(it, "app", "dummy-contract.jar") }
|
||||
val v2Id = jarV2.read { storage.privilegedImportAttachment(it, "untrusted", "dummy-contract.jar") }
|
||||
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v1Id)!!, storage), "Initial contract $v1Id should be trusted")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(v2Id)!!, storage), "Upgraded contract $v2Id should be trusted")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jar with inherited trust does not grant trust to other jars (no chain of trust)`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val aliasA = "Daredevil"
|
||||
val aliasB = "The Punisher"
|
||||
val aliasC = "Jessica Jones"
|
||||
val password = "i am a netflix series"
|
||||
file.path.generateKey(aliasA, password)
|
||||
file.path.generateKey(aliasB, password)
|
||||
file.path.generateKey(aliasC, password)
|
||||
|
||||
val jarSignedByA = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
file.path.signJar(jarSignedByA.toAbsolutePath().toString(), aliasA, password)
|
||||
|
||||
val jarSignedByAB = makeTestContractJar(file.path, "foo.bar.DifferentContract", version = 2)
|
||||
file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasB, password)
|
||||
file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasA, password)
|
||||
|
||||
val jarSignedByBC = makeTestContractJar(file.path, "foo.bar.AnotherContract", version = 2)
|
||||
file.path.signJar(jarSignedByBC.toAbsolutePath().toString(), aliasB, password)
|
||||
file.path.signJar(jarSignedByBC.toAbsolutePath().toString(), aliasC, password)
|
||||
|
||||
val attachmentA = jarSignedByA.read { storage.privilegedImportAttachment(it, "app", "dummy-contract.jar") }
|
||||
val attachmentB = jarSignedByAB.read { storage.privilegedImportAttachment(it, "untrusted", "dummy-contract.jar") }
|
||||
val attachmentC = jarSignedByBC.read { storage.privilegedImportAttachment(it, "untrusted", "dummy-contract.jar") }
|
||||
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(attachmentA)!!, storage), "Contract $attachmentA should be trusted")
|
||||
assertTrue(isAttachmentTrusted(storage.openAttachment(attachmentB)!!, storage), "Contract $attachmentB should inherit trust")
|
||||
assertFalse(isAttachmentTrusted(storage.openAttachment(attachmentC)!!, storage), "Contract $attachmentC should not be trusted (no chain of trust)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -940,6 +1045,147 @@ class NodeAttachmentServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `attachments can be queried by providing a intersection of signers using an EQUAL statement - EQUAL containing a single public key`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val aliasA = "Luke Skywalker"
|
||||
val aliasB = "Han Solo"
|
||||
val aliasC = "Chewbacca"
|
||||
val aliasD = "Princess Leia"
|
||||
val password = "may the force be with you"
|
||||
file.path.generateKey(aliasA, password)
|
||||
file.path.generateKey(aliasB, password)
|
||||
file.path.generateKey(aliasC, password)
|
||||
file.path.generateKey(aliasD, password)
|
||||
|
||||
val jarSignedByA = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
val keyA = file.path.signJar(jarSignedByA.toAbsolutePath().toString(), aliasA, password)
|
||||
|
||||
val jarSignedByAB = makeTestContractJar(file.path, "foo.bar.DifferentContract")
|
||||
file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasA, password)
|
||||
val keyB = file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasB, password)
|
||||
|
||||
val jarSignedByABC = makeTestContractJar(file.path, "foo.bar.ExpensiveContract")
|
||||
file.path.signJar(jarSignedByABC.toAbsolutePath().toString(), aliasA, password)
|
||||
file.path.signJar(jarSignedByABC.toAbsolutePath().toString(), aliasB, password)
|
||||
val keyC = file.path.signJar(jarSignedByABC.toAbsolutePath().toString(), aliasC, password)
|
||||
|
||||
val jarSignedByABCD = makeTestContractJar(file.path, "foo.bar.DidNotReadThisContract")
|
||||
file.path.signJar(jarSignedByABCD.toAbsolutePath().toString(), aliasA, password)
|
||||
file.path.signJar(jarSignedByABCD.toAbsolutePath().toString(), aliasB, password)
|
||||
file.path.signJar(jarSignedByABCD.toAbsolutePath().toString(), aliasC, password)
|
||||
val keyD = file.path.signJar(jarSignedByABCD.toAbsolutePath().toString(), aliasD, password)
|
||||
|
||||
val attachmentA = jarSignedByA.read { storage.privilegedImportAttachment(it, "app", "A.jar") }
|
||||
val attachmentB = jarSignedByAB.read { storage.privilegedImportAttachment(it, "app", "B.jar") }
|
||||
val attachmentC = jarSignedByABC.read { storage.privilegedImportAttachment(it, "app", "C.jar") }
|
||||
val attachmentD = jarSignedByABCD.read { storage.privilegedImportAttachment(it, "app", "D.jar") }
|
||||
|
||||
assertEquals(
|
||||
listOf(attachmentA, attachmentB, attachmentC, attachmentD),
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyA))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(attachmentB, attachmentC, attachmentD),
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyB))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(attachmentC, attachmentD),
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyC))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
listOf(attachmentD),
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyD))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `attachments can be queried by providing a intersection of signers using an EQUAL statement - EQUAL containing multiple public keys`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val aliasA = "Ironman"
|
||||
val aliasB = "Captain America"
|
||||
val aliasC = "Blackwidow"
|
||||
val aliasD = "Thor"
|
||||
val password = "avengers, assemble!"
|
||||
file.path.generateKey(aliasA, password)
|
||||
file.path.generateKey(aliasB, password)
|
||||
file.path.generateKey(aliasC, password)
|
||||
file.path.generateKey(aliasD, password)
|
||||
|
||||
val jarSignedByA = makeTestContractJar(file.path, "foo.bar.DummyContract")
|
||||
val keyA = file.path.signJar(jarSignedByA.toAbsolutePath().toString(), aliasA, password)
|
||||
|
||||
val jarSignedByAB = makeTestContractJar(file.path, "foo.bar.DifferentContract")
|
||||
file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasA, password)
|
||||
val keyB = file.path.signJar(jarSignedByAB.toAbsolutePath().toString(), aliasB, password)
|
||||
|
||||
val jarSignedByBC = makeTestContractJar(file.path, "foo.bar.ExpensiveContract")
|
||||
file.path.signJar(jarSignedByBC.toAbsolutePath().toString(), aliasB, password)
|
||||
val keyC = file.path.signJar(jarSignedByBC.toAbsolutePath().toString(), aliasC, password)
|
||||
|
||||
val jarSignedByCD = makeTestContractJar(file.path, "foo.bar.DidNotReadThisContract")
|
||||
file.path.signJar(jarSignedByCD.toAbsolutePath().toString(), aliasC, password)
|
||||
val keyD = file.path.signJar(jarSignedByCD.toAbsolutePath().toString(), aliasD, password)
|
||||
|
||||
val attachmentA = jarSignedByA.read { storage.privilegedImportAttachment(it, "app", "A.jar") }
|
||||
val attachmentB = jarSignedByAB.read { storage.privilegedImportAttachment(it, "app", "B.jar") }
|
||||
val attachmentC = jarSignedByBC.read { storage.privilegedImportAttachment(it, "app", "C.jar") }
|
||||
val attachmentD = jarSignedByCD.read { storage.privilegedImportAttachment(it, "app", "D.jar") }
|
||||
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyA, keyC))
|
||||
)
|
||||
).let { result ->
|
||||
assertEquals(4, result.size)
|
||||
assertEquals(
|
||||
listOf(attachmentA, attachmentB, attachmentC, attachmentD),
|
||||
result
|
||||
)
|
||||
}
|
||||
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyA, keyB))
|
||||
)
|
||||
).let { result ->
|
||||
// made a [Set] due to [NodeAttachmentService.queryAttachments] not returning distinct results
|
||||
assertEquals(3, result.toSet().size)
|
||||
assertEquals(setOf(attachmentA, attachmentB, attachmentC), result.toSet())
|
||||
}
|
||||
|
||||
storage.queryAttachments(
|
||||
AttachmentsQueryCriteria(
|
||||
signersCondition = Builder.equal(listOf(keyB, keyC, keyD))
|
||||
)
|
||||
).let { result ->
|
||||
// made a [Set] due to [NodeAttachmentService.queryAttachments] not returning distinct results
|
||||
assertEquals(3, result.toSet().size)
|
||||
assertEquals(setOf(attachmentB, attachmentC, attachmentD), result.toSet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not the real FetchAttachmentsFlow!
|
||||
private class FetchAttachmentsFlow : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
|
@ -47,7 +47,16 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
|
||||
// and not all predicate types are covered here.
|
||||
private fun <C> criteriaFilter(metadata: C, predicate: ColumnPredicate<C>?): Boolean {
|
||||
return when (predicate) {
|
||||
is ColumnPredicate.EqualityComparison -> predicate.rightLiteral == metadata
|
||||
// real implementation allows an intersection of signers to return results from the query
|
||||
is ColumnPredicate.EqualityComparison -> {
|
||||
val rightLiteral = predicate.rightLiteral
|
||||
when (rightLiteral) {
|
||||
is Collection<*> -> rightLiteral.any { value ->
|
||||
(metadata as Collection<*>).contains(value)
|
||||
}
|
||||
else -> rightLiteral == metadata
|
||||
}
|
||||
}
|
||||
is ColumnPredicate.CollectionExpression -> predicate.rightLiteral.contains(metadata)
|
||||
else -> true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user