Skip to content

Commit

Permalink
Updating EOS file endpoint to send raw input (#43)
Browse files Browse the repository at this point in the history
* Updating EOS file endpoint to send raw input

* cleanup

* Updating get file

* doc updates

* optional raw byte

* defaulting raw bytes to false

* get file updates

* minor cleanup

* build fixes

* more swagger updates
  • Loading branch information
cworsnop-figure authored Jul 12, 2022
1 parent bb66eb0 commit 65efc06
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package io.provenance.api.domain.objectStore

import com.google.protobuf.Message
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.scope.encryption.proto.Encryption
import io.provenance.scope.objectstore.client.OsClient
import java.security.PrivateKey
import java.security.PublicKey

interface ObjectStore {
fun getDIME(client: OsClient, hash: ByteArray, publicKey: PublicKey): Encryption.DIME
fun retrieve(client: OsClient, hash: ByteArray, publicKey: PublicKey): ByteArray
fun retrieveWithDIME(client: OsClient, hash: ByteArray, publicKey: PublicKey): Pair<Encryption.DIME, ByteArray>
fun retrieveAndDecrypt(client: OsClient, hash: ByteArray, publicKey: PublicKey, privateKey: PrivateKey): ByteArray
fun storeMessage(client: OsClient, message: Message, publicKey: PublicKey, additionalAudiences: Set<PublicKey>): StoreProtoResponse
fun <T> store(client: OsClient, message: T, publicKey: PublicKey, additionalAudiences: Set<PublicKey>): StoreProtoResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ExecuteContract(
) : AbstractUseCase<ExecuteContractRequestWrapper, ContractExecutionResponse>() {

override suspend fun execute(args: ExecuteContractRequestWrapper): ContractExecutionResponse {

val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.config.account))
contractUtilities.createClient(args.uuid, args.request.permissions, args.request.participants, args.request.config).use { client ->
val session = contractUtilities.createSession(args.uuid, client, args.request.permissions, args.request.participants, args.request.config, args.request.records, listOf(args.request.scope)).single()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,32 @@ package io.provenance.api.domain.usecase.objectStore.get

import com.google.protobuf.BytesValue
import com.google.protobuf.StringValue
import io.provenance.api.domain.usecase.AbstractUseCase
import io.provenance.client.protobuf.extensions.isSet
import io.provenance.api.domain.extensions.toByteResponse
import io.provenance.api.domain.usecase.AbstractUseCase
import io.provenance.api.domain.usecase.objectStore.get.models.GetFileRequestWrapper
import io.provenance.api.domain.usecase.objectStore.get.models.RetrieveAndDecryptRequest
import io.provenance.client.protobuf.extensions.isSet
import org.springframework.stereotype.Component
import tech.figure.asset.v1beta1.Asset
import tech.figure.proto.util.FileNFT
import org.springframework.http.HttpEntity

@Component
class GetFile(
private val retrieveAndDecrypt: RetrieveAndDecrypt,
) : AbstractUseCase<GetFileRequestWrapper, HttpEntity<ByteArray>>() {
override suspend fun execute(args: GetFileRequestWrapper): HttpEntity<ByteArray> {
Asset.parseFrom(retrieveAndDecrypt.execute(RetrieveAndDecryptRequest(args.uuid, args.request.objectStoreAddress, args.request.hash, args.request.accountInfo.keyManagementConfig)))
.takeIf { it.isSet() && it.type == FileNFT.ASSET_TYPE }
?.let {
val fileName = it.getKvOrThrow(FileNFT.KEY_FILENAME).unpack(StringValue::class.java).value
val contentType = it.getKvOrThrow(FileNFT.KEY_CONTENT_TYPE).unpack(StringValue::class.java).value
return it.getKvOrThrow(FileNFT.KEY_BYTES).unpack(BytesValue::class.java).value.toByteArray().toByteResponse(fileName, contentType)
} ?: throw IllegalArgumentException("Provided hash is not an Asset.")
) : AbstractUseCase<GetFileRequestWrapper, Any>() {
override suspend fun execute(args: GetFileRequestWrapper): Any {
retrieveAndDecrypt.execute(RetrieveAndDecryptRequest(args.uuid, args.request.objectStoreAddress, args.request.hash, args.request.accountInfo.keyManagementConfig)).let {
if (args.rawBytes) {
return it
} else {
Asset.parseFrom(it)
.takeIf { asset -> asset.isSet() && asset.type == FileNFT.ASSET_TYPE }
?.let { asset ->
val fileName = asset.getKvOrThrow(FileNFT.KEY_FILENAME).unpack(StringValue::class.java).value
val contentType = asset.getKvOrThrow(FileNFT.KEY_CONTENT_TYPE).unpack(StringValue::class.java).value
return asset.getKvOrThrow(FileNFT.KEY_BYTES).unpack(BytesValue::class.java).value.toByteArray().toByteResponse(fileName, contentType)
} ?: throw IllegalArgumentException("Provided hash is not an Asset.")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import java.util.UUID

data class GetFileRequestWrapper(
val uuid: UUID,
val request: GetFileRequest
val request: GetFileRequest,
val rawBytes: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ package io.provenance.api.domain.usecase.objectStore.store
import com.google.gson.Gson
import io.provenance.api.domain.objectStore.ObjectStore
import io.provenance.api.domain.usecase.AbstractUseCase
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.api.models.p8e.AudienceKeyPair
import io.provenance.api.models.p8e.PermissionInfo
import io.provenance.api.domain.usecase.common.originator.EntityManager
import io.provenance.api.domain.usecase.common.originator.models.KeyManagementConfigWrapper
import io.provenance.api.domain.usecase.objectStore.store.models.StoreFileRequestWrapper
import io.provenance.api.frameworks.config.ObjectStoreConfig
import io.provenance.api.models.account.AccountInfo
import io.provenance.api.models.account.KeyManagementConfig
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.api.models.p8e.AudienceKeyPair
import io.provenance.api.models.p8e.PermissionInfo
import io.provenance.api.util.awaitAllBytes
import io.provenance.api.domain.usecase.objectStore.store.models.StoreFileRequestWrapper
import io.provenance.scope.encryption.util.toJavaPublicKey
import io.provenance.scope.objectstore.client.OsClient
import java.io.ByteArrayInputStream
import java.net.URI
import java.security.PublicKey
import org.springframework.http.codec.multipart.FilePart
Expand All @@ -32,43 +33,48 @@ class StoreFile(
private val entityManager: EntityManager,
) : AbstractUseCase<StoreFileRequestWrapper, StoreProtoResponse>() {
override suspend fun execute(args: StoreFileRequestWrapper): StoreProtoResponse {

var additionalAudiences = emptySet<AudienceKeyPair>()
var keyConfig: KeyManagementConfig? = null

args.request["account"]?.let {
keyConfig = Gson().fromJson((it as FormFieldPart).value(), AccountInfo::class.java).keyManagementConfig
}

val originator = entityManager.getEntity(KeyManagementConfigWrapper(args.uuid, keyConfig))
var additionalAudiences = emptySet<AudienceKeyPair>()
if (!args.request.containsKey("id") || args.request.getAsType<FormFieldPart>("id").value().isEmpty()) {
throw IllegalArgumentException("Request must provide the 'id' field for the file")
}
val file = args.request.getAsType<FilePart>("file")

args.request["permissions"]?.let {
val permissions = Gson().fromJson((it as FormFieldPart).value(), PermissionInfo::class.java)
additionalAudiences = entityManager.hydrateKeys(permissions)
}

val asset = AssetOuterClassBuilders.Asset {
idBuilder.value = args.request.getAsType<FormFieldPart>("id").value()
type = FileNFT.ASSET_TYPE
description = file.filename()
val originator = entityManager.getEntity(KeyManagementConfigWrapper(args.uuid, keyConfig))
val file = args.request.getAsType<FilePart>("file")
var message: Any = ByteArrayInputStream(file.awaitAllBytes())

putKv(FileNFT.KEY_FILENAME, file.filename().toProtoAny())
putKv(FileNFT.KEY_BYTES, file.awaitAllBytes().toProtoAny())
putKv(FileNFT.KEY_SIZE, file.awaitAllBytes().size.toString().toProtoAny())
putKv(FileNFT.KEY_CONTENT_TYPE, file.headers().contentType.toString().toProtoAny())
}
OsClient(
URI.create(args.request.getAsType<FormFieldPart>("objectStoreAddress").value()),
objectStoreConfig.timeoutMs,
).use { osClient ->
return objectStore.storeMessage(
if (!args.storeRawBytes) {
message = AssetOuterClassBuilders.Asset {
idBuilder.value = args.request.getAsType<FormFieldPart>("id").value()
type = FileNFT.ASSET_TYPE
description = file.filename()

putKv(FileNFT.KEY_FILENAME, file.filename().toProtoAny())
putKv(FileNFT.KEY_BYTES, file.awaitAllBytes().toProtoAny())
putKv(FileNFT.KEY_SIZE, file.awaitAllBytes().size.toString().toProtoAny())
putKv(FileNFT.KEY_CONTENT_TYPE, file.headers().contentType.toString().toProtoAny())
}
}

return objectStore.store(
osClient,
asset,
message,
originator.encryptionPublicKey() as PublicKey,
additionalAudiences.map { it.encryptionKey.toJavaPublicKey() }.toSet(),
additionalAudiences.map { it.encryptionKey.toJavaPublicKey() }.toSet()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import io.provenance.api.frameworks.config.ObjectStoreConfig
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.scope.encryption.util.toJavaPublicKey
import io.provenance.scope.objectstore.client.OsClient
import org.springframework.stereotype.Component
import java.lang.IllegalStateException
import java.net.URI
import java.security.PublicKey
import org.springframework.stereotype.Component

@Component
class StoreProto(
Expand All @@ -32,7 +31,7 @@ class StoreProto(
?: throw IllegalStateException("Public key was not present for originator: ${args.uuid}")

OsClient(URI.create(args.request.objectStoreAddress), objectStoreConfig.timeoutMs).use { osClient ->
return objectStore.storeMessage(
return objectStore.store(
osClient,
asset,
publicKey,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package io.provenance.api.domain.usecase.objectStore.store.models

import java.util.UUID
import org.springframework.http.HttpEntity
import org.springframework.http.codec.multipart.FilePart
import org.springframework.http.codec.multipart.Part

data class StoreFileRequestWrapper(
val uuid: UUID,
val request: Map<String, Part>,
val storeRawBytes: Boolean = false,
)

data class SwaggerStoreFileRequestWrapper(
val objectStoreAddress: String,
val id: UUID,
val file: FilePart,
val storeRawBytes: Boolean,
)

data class SwaggerGetFileResponse(
val value: HttpEntity<ByteArray>
val value: ByteArray
)
Original file line number Diff line number Diff line change
@@ -1,77 +1,25 @@
package io.provenance.api.frameworks.objectStore

import com.google.protobuf.Message
import io.provenance.objectstore.proto.Objects
import io.provenance.api.domain.objectStore.ObjectStore
import io.provenance.api.models.eos.store.toModel
import io.provenance.api.frameworks.config.ObjectStoreConfig
import io.provenance.api.frameworks.provenance.extensions.getEncryptedPayload
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.api.models.eos.store.toModel
import io.provenance.scope.encryption.crypto.Pen
import io.provenance.scope.encryption.domain.inputstream.DIMEInputStream
import io.provenance.scope.encryption.ecies.ProvenanceKeyGenerator
import io.provenance.scope.encryption.model.DirectKeyRef
import io.provenance.scope.encryption.proto.Encryption
import io.provenance.scope.objectstore.client.OsClient
import org.springframework.stereotype.Component
import java.io.InputStream
import java.security.PrivateKey
import java.security.PublicKey
import java.util.concurrent.TimeUnit
import org.springframework.stereotype.Component

@Component
class ObjectStoreService(
private val osConfig: ObjectStoreConfig
) : ObjectStore {

// Encrypt and store a protobuf message using a random keypair for the signer
private fun encryptAndStore(
client: OsClient,
message: Message,
encryptPublicKey: PublicKey,
additionalAudiences: Set<PublicKey>
): Objects.ObjectResponse {
val future = client.put(
message,
encryptPublicKey,
Pen(ProvenanceKeyGenerator.generateKeyPair(encryptPublicKey)),
additionalAudiences
)
return future.get(osConfig.timeoutMs, TimeUnit.MILLISECONDS)
}

// Get a DIME by its hash and public key
override fun getDIME(
client: OsClient,
hash: ByteArray,
publicKey: PublicKey
): Encryption.DIME {
val future = client.get(hash, publicKey)
val res: DIMEInputStream = future.get(osConfig.timeoutMs, TimeUnit.MILLISECONDS)
return res.dime
}

// Retrieve an encrypted asset as a byte array by its hash and public key
override fun retrieve(
client: OsClient,
hash: ByteArray,
publicKey: PublicKey
): ByteArray {
val future = client.get(hash, publicKey)
val res: DIMEInputStream = future.get(osConfig.timeoutMs, TimeUnit.MILLISECONDS)
return res.getEncryptedPayload()
}

// Retrieve an encrypted asset as a byte array with its DIME by its hash and public key
override fun retrieveWithDIME(
client: OsClient,
hash: ByteArray,
publicKey: PublicKey
): Pair<Encryption.DIME, ByteArray> {
val future = client.get(hash, publicKey)
val res: DIMEInputStream = future.get(osConfig.timeoutMs, TimeUnit.MILLISECONDS)
return Pair(res.dime, res.getEncryptedPayload())
}

// Retrieve the asset as a byte array and decrypt using the provided keypair
override fun retrieveAndDecrypt(
client: OsClient,
Expand All @@ -84,10 +32,26 @@ class ObjectStoreService(
return res.getDecryptedPayload(DirectKeyRef(publicKey, privateKey)).readAllBytes()
}

override fun storeMessage(
client: OsClient,
message: Message,
publicKey: PublicKey,
additionalAudiences: Set<PublicKey>
): StoreProtoResponse = encryptAndStore(client, message, publicKey, additionalAudiences).toModel()
override fun <T> store(client: OsClient, message: T, publicKey: PublicKey, additionalAudiences: Set<PublicKey>): StoreProtoResponse {
val future = when (message) {
is InputStream -> client.put(
message,
publicKey,
Pen(ProvenanceKeyGenerator.generateKeyPair(publicKey)),
message.available().toLong(),
additionalAudiences
)
is Message -> client.put(
message,
publicKey,
Pen(ProvenanceKeyGenerator.generateKeyPair(publicKey)),
additionalAudiences
)
else -> {
throw IllegalArgumentException("Not supported file type to store against EOS!")
}
}

return future.get(osConfig.timeoutMs, TimeUnit.MILLISECONDS).toModel()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.provenance.api.domain.usecase.objectstore

import com.google.protobuf.Message
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.mockk.clearAllMocks
Expand All @@ -8,8 +9,6 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import io.provenance.api.models.p8e.PermissionInfo
import io.provenance.core.Originator
import io.provenance.api.domain.objectStore.ObjectStore
import io.provenance.api.domain.usecase.common.originator.EntityManager
import io.provenance.api.domain.usecase.objectStore.store.StoreProto
Expand All @@ -21,10 +20,12 @@ import io.provenance.api.models.eos.store.StoreProtoRequest
import io.provenance.api.models.eos.store.StoreProtoResponse
import io.provenance.api.models.p8e.Audience
import io.provenance.api.models.p8e.AudienceKeyPair
import io.provenance.api.models.p8e.PermissionInfo
import io.provenance.core.Originator
import io.provenance.scope.encryption.util.toJavaPublicKey
import io.provenance.scope.util.toUuid
import org.junit.jupiter.api.Assertions.assertEquals
import java.security.PublicKey
import org.junit.jupiter.api.Assertions.assertEquals
import tech.figure.asset.v1beta1.Asset

const val ADD_ASSET_OBJECT_STORE_ADDRESS = "grpc://localhost:5005"
Expand Down Expand Up @@ -70,7 +71,7 @@ class StoreAssetTest : FunSpec({
test("happy path") {
val storeAssetResponse = StoreProtoResponse("HASH", "URI", "BUCKET", "NAME")

every { mockObjectStore.storeMessage(any(), any(), any(), any()) } returns storeAssetResponse
every { mockObjectStore.store(any(), any<Message>(), any(), any()) } returns storeAssetResponse
every { mockEntityManager.hydrateKeys(any()) } returns emptySet()
every { mockOriginator.encryptionPublicKey() } returns mockOriginatorPublicKey
every { mockParser.parse(any(), any()) } returns Asset.getDefaultInstance()
Expand All @@ -96,9 +97,9 @@ class StoreAssetTest : FunSpec({
assertEquals(response, storeAssetResponse)

verify {
mockObjectStore.storeMessage(
any(),
mockObjectStore.store(
any(),
any<Message>(),
mockOriginatorPublicKey,
any()
)
Expand Down

0 comments on commit 65efc06

Please sign in to comment.