CORDA-2112 Update the uploader field for Attachments loaded upon Node CorDapp startup (#4127)

* CORDA-2112 Update the `uploader` field upon Node startup for CorDapps where an Attachment is already in a nodes attachment store (from an untrusted source).

* Removed incorrect assertion.

* Fix broken unit test.

* Reverted back throw FileAlreadyExistsException API semantics on public API.

* De-duplicate DuplicateAttachmentException

* Revert original Exception handling logic.
This commit is contained in:
josecoll
2018-10-30 15:44:29 +00:00
committed by GitHub
parent 13815d252e
commit e064800173
3 changed files with 54 additions and 8 deletions

View File

@ -2,6 +2,7 @@ package net.corda.node.services.persistence
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.nodeapi.exceptions.DuplicateAttachmentException
import java.io.InputStream import java.io.InputStream
interface AttachmentStorageInternal : AttachmentStorage { interface AttachmentStorageInternal : AttachmentStorage {
@ -10,4 +11,9 @@ interface AttachmentStorageInternal : AttachmentStorage {
* and is only for the node. * and is only for the node.
*/ */
fun privilegedImportAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId fun privilegedImportAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
/**
* Similar to above but returns existing [AttachmentId] instead of throwing [DuplicateAttachmentException]
*/
fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId
} }

View File

@ -6,13 +6,11 @@ import com.google.common.hash.HashCode
import com.google.common.hash.Hashing import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream import com.google.common.io.CountingInputStream
import net.corda.core.ClientRelevantError
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.contracts.ContractAttachment import net.corda.core.contracts.ContractAttachment
import net.corda.core.contracts.ContractClassName import net.corda.core.contracts.ContractClassName
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
@ -286,6 +284,14 @@ class NodeAttachmentService(
return import(jar, uploader, filename) return import(jar, uploader, filename)
} }
override fun privilegedImportOrGetAttachment(jar: InputStream, uploader: String, filename: String?): AttachmentId {
return try {
import(jar, uploader, filename)
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!)
}
}
override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction { override fun hasAttachment(attachmentId: AttachmentId): Boolean = database.transaction {
currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null currentDBSession().find(NodeAttachmentService.DBAttachment::class.java, attachmentId.toString()) != null
} }
@ -306,9 +312,7 @@ class NodeAttachmentService(
val id = bytes.sha256() val id = bytes.sha256()
if (!hasAttachment(id)) { if (!hasAttachment(id)) {
checkIsAValidJAR(bytes.inputStream()) checkIsAValidJAR(bytes.inputStream())
val jarSigners = getSigners(bytes) val jarSigners = getSigners(bytes)
val session = currentDBSession() val session = currentDBSession()
val attachment = NodeAttachmentService.DBAttachment( val attachment = NodeAttachmentService.DBAttachment(
attId = id.toString(), attId = id.toString(),
@ -318,14 +322,24 @@ class NodeAttachmentService(
contractClassNames = contractClassNames, contractClassNames = contractClassNames,
signers = jarSigners signers = jarSigners
) )
session.save(attachment) session.save(attachment)
attachmentCount.inc() attachmentCount.inc()
log.info("Stored new attachment $id") log.info("Stored new attachment $id")
id return@withContractsInJar id
} else {
throw DuplicateAttachmentException(id.toString())
} }
if (isUploaderTrusted(uploader)) {
val session = currentDBSession()
val attachment = session.get(NodeAttachmentService.DBAttachment::class.java, id.toString())
// update the `upLoader` field (as the existing attachment may have been resolved from a peer)
if (attachment.uploader != uploader) {
attachment.uploader = uploader
session.saveOrUpdate(attachment)
log.info("Updated attachment $id with uploader $uploader")
attachmentCache.invalidate(id)
attachmentContentCache.invalidate(id)
}
}
throw DuplicateAttachmentException(id.toString())
} }
} }
} }

View File

@ -9,6 +9,7 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.JarSignatureTestUtils.createJar import net.corda.core.JarSignatureTestUtils.createJar
import net.corda.core.JarSignatureTestUtils.generateKey import net.corda.core.JarSignatureTestUtils.generateKey
import net.corda.core.JarSignatureTestUtils.signJar import net.corda.core.JarSignatureTestUtils.signJar
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -47,6 +48,7 @@ import javax.tools.StandardLocation
import javax.tools.ToolProvider import javax.tools.ToolProvider
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNull import kotlin.test.assertNull
@ -123,6 +125,30 @@ class NodeAttachmentServiceTest {
} }
} }
@Test
fun `insert contract attachment as an untrusted uploader and then as trusted CorDapp uploader`() {
val contractJarName = makeTestContractJar("com.example.MyContract")
val testJar = dir.resolve(contractJarName)
val expectedHash = testJar.readAll().sha256()
// PRIVILEGED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER, P2P_UPLOADER, UNKNOWN_UPLOADER)
// TRUSTED_UPLOADERS = listOf(DEPLOYED_CORDAPP_UPLOADER, RPC_UPLOADER)
database.transaction {
val id = testJar.read { storage.privilegedImportOrGetAttachment(it, P2P_UPLOADER, null) }
assertEquals(expectedHash, id)
val attachment1 = storage.openAttachment(expectedHash)
val id2 = testJar.read { storage.privilegedImportOrGetAttachment(it, DEPLOYED_CORDAPP_UPLOADER, null) }
assertEquals(expectedHash, id2)
val attachment2 = storage.openAttachment(expectedHash)
assertNotEquals(attachment1, attachment2)
assertEquals(P2P_UPLOADER, (attachment1 as ContractAttachment).uploader)
assertEquals(DEPLOYED_CORDAPP_UPLOADER, (attachment2 as ContractAttachment).uploader)
}
}
@Test @Test
fun `missing is not cached`() { fun `missing is not cached`() {
val (testJar, expectedHash) = makeTestJar() val (testJar, expectedHash) = makeTestJar()