From 868899102f597d82e16f882db85d9cbbd6e52478 Mon Sep 17 00:00:00 2001 From: Cody Worsnop Date: Thu, 19 May 2022 09:08:27 -0700 Subject: [PATCH] Audience Updates + Object Store Use Cases (#20) * swagger / updating endpoint for audiences on contract execute * changing how audiences work * Running tests on build + workflow updates to publish test results. * cleanup * updating lint * disabling test * using the right annotation * EOS endpoint updates * File download, configurable parser * test fixes * ignoring test * Member ids for test * fixing parsing for configurable types --- .github/workflows/build.yml | 40 ++++++++- .github/workflows/publish-test-results.yml | 41 ++++++++++ buildSrc/src/main/kotlin/Dependencies.kt | 4 +- .../api/models/cee/ContractConfig.kt | 1 + .../provenance/api/models/cee/ParserConfig.kt | 6 ++ .../api/models/eos/GetFileRequest.kt | 6 ++ ...{GetAssetRequest.kt => GetProtoRequest.kt} | 6 +- .../api/models/eos/StoreAssetRequest.kt | 13 --- .../api/models/eos/StoreProtoRequest.kt | 10 +++ .../io/provenance/api/models/p8e/Audience.kt | 13 +++ .../api/models/p8e/PermissionInfo.kt | 2 +- service/build.gradle.kts | 5 +- .../onboarding/domain/cee/ContractParser.kt | 1 + .../onboarding/domain/cee/InputParser.kt | 2 +- .../domain/extensions/ByteExtensions.kt | 27 ++++++ .../domain/objectStore/ObjectStore.kt | 4 +- .../usecase/cee/common/client/CreateClient.kt | 12 ++- .../client/model/CreateClientRequest.kt | 4 +- .../usecase/cee/execute/ExecuteContract.kt | 73 +++++++++-------- .../common/originator/DefaultAudience.kt | 6 ++ .../common/originator/EntityManager.kt | 75 +++++++++++++++++ .../common/originator/GetOriginator.kt | 39 --------- .../domain/usecase/objectStore/get/GetFile.kt | 28 +++++++ .../usecase/objectStore/get/GetProto.kt | 19 +++++ .../{GetAsset.kt => RetrieveAndDecrypt.kt} | 21 +++-- .../get/models/GetAssetRequestWrapper.kt | 4 +- .../get/models/GetFileRequestWrapper.kt | 9 ++ .../get/models/RetrieveAndDecryptRequest.kt | 9 ++ .../objectStore/snapshot/SnapshotAsset.kt | 55 ------------- .../models/SnapshotAssetRequestWrapper.kt | 9 -- .../usecase/objectStore/store/StoreAsset.kt | 57 ------------- .../usecase/objectStore/store/StoreFile.kt | 60 ++++++++++++++ .../usecase/objectStore/store/StoreProto.kt | 36 ++++++++ .../store/models/StoreAssetRequestWrapper.kt | 9 -- .../store/models/StoreFileRequestWrapper.kt | 9 ++ .../store/models/StoreProtoRequestWrapper.kt | 9 ++ .../usecase/provenance/account/GetSigner.kt | 6 +- .../domain/usecase/provenance/tx/CreateTx.kt | 22 ++--- .../frameworks/cee/ContractParserService.kt | 3 + .../cee/parsers/JsonMessageParser.kt | 27 ++++++ .../frameworks/cee/parsers/MessageParser.kt | 2 +- .../onboarding/frameworks/config/AppConfig.kt | 1 - .../frameworks/config/ProvenanceProperties.kt | 13 +++ .../config/ServiceKeysProperties.kt | 14 ---- .../objectStore/AudienceKeyManager.kt | 24 ------ .../frameworks/objectStore/DefaultAudience.kt | 6 -- .../objectStore/ObjectStoreService.kt | 5 +- .../frameworks/web/SuccessResponses.kt | 6 ++ .../frameworks/web/config/SecurityConfig.kt | 4 +- .../external/objectStore/ObjectStoreApi.kt | 15 ++-- .../objectStore/ObjectStoreHandler.kt | 36 ++++---- .../objectStore/InternalObjectStoreHandler.kt | 8 +- .../frameworks/web/misc/ServerResponseExt.kt | 8 +- .../application-container.properties | 8 +- .../application-development.properties | 8 +- .../usecase/objectstore/StoreAssetTest.kt | 76 +++++++---------- .../usecase/provenance/tx/CreateTxTest.kt | 23 ++---- .../frameworks/cee/P8eContractServiceSpec.kt | 82 +++++++++++++++++++ 58 files changed, 702 insertions(+), 419 deletions(-) create mode 100644 .github/workflows/publish-test-results.yml create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/ParserConfig.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/eos/GetFileRequest.kt rename models/src/main/kotlin/io/provenance/api/models/eos/{GetAssetRequest.kt => GetProtoRequest.kt} (54%) delete mode 100644 models/src/main/kotlin/io/provenance/api/models/eos/StoreAssetRequest.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/eos/StoreProtoRequest.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/p8e/Audience.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/extensions/ByteExtensions.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/DefaultAudience.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/EntityManager.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/GetOriginator.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetFile.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetProto.kt rename service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/{GetAsset.kt => RetrieveAndDecrypt.kt} (67%) create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetFileRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/RetrieveAndDecryptRequest.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/SnapshotAsset.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/models/SnapshotAssetRequestWrapper.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreAsset.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreFile.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreProto.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreAssetRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreFileRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreProtoRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/JsonMessageParser.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ServiceKeysProperties.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/AudienceKeyManager.kt delete mode 100644 service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/DefaultAudience.kt create mode 100644 service/src/test/kotlin/io/provenance/onboarding/frameworks/cee/P8eContractServiceSpec.kt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f82888cd..95c13738 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,6 +15,26 @@ jobs: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/main'" + linting: + name: Linting + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + - name: Linting + run: ./gradlew clean ktlint --parallel + build: runs-on: ubuntu-latest steps: @@ -28,4 +48,22 @@ jobs: java-version: '11' distribution: 'adopt' - name: Gradle Build - run: ./gradlew build -i --parallel + run: ./gradlew build -i --parallel -x ktlint + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v2 + with: + name: Test Results + path: | + **/build/test-results/**/*.xml + + event_file: + name: "Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@v2 + with: + name: Event File + path: ${{ github.event_path }} diff --git a/.github/workflows/publish-test-results.yml b/.github/workflows/publish-test-results.yml new file mode 100644 index 00000000..ffe0aeaa --- /dev/null +++ b/.github/workflows/publish-test-results.yml @@ -0,0 +1,41 @@ +name: Test Results + +on: + workflow_run: + workflows: ["Build"] + types: + - completed +permissions: {} + +jobs: + test-results: + name: Test Results + runs-on: ubuntu-latest + permissions: + actions: read + checks: write + if: github.event.workflow_run.conclusion != 'skipped' + + steps: + - name: Download and Extract Artifacts + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: | + mkdir -p artifacts && cd artifacts + + artifacts_url=${{ github.event.workflow_run.artifacts_url }} + + gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact + do + IFS=$'\t' read name url <<< "$artifact" + gh api $url > "$name.zip" + unzip -d "$name" "$name.zip" + done + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v1 + with: + commit: ${{ github.event.workflow_run.head_sha }} + event_file: artifacts/Event File/event.json + event_name: ${{ github.event.workflow_run.event }} + files: "artifacts/**/*.xml" diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 73403955..ff796578 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -36,7 +36,7 @@ object Versions { const val ProvenanceClient = "1.1.1" const val Unirest = "3.13.6" const val KeyAccessLib = "0.2.15" - const val LoanPackage = "0.1.13" + const val LoanPackage = "0.1.14" const val Grpc = "1.45.0" const val ProvenanceProto = "1.8.0" const val Reflections = "0.9.10" @@ -185,11 +185,11 @@ object Dependencies { val Kotest = DependencySpec("io.kotest:kotest-runner-junit5-jvm", Versions.Kotest) val KotestAssertions = DependencySpec("io.kotest:kotest-assertions-core-jvm", Versions.Kotest) val KotestAssertionsArrow = DependencySpec("io.kotest.extensions:kotest-assertions-arrow", Versions.KotestExtensionsArrow) + val KotestProperty = DependencySpec("io.kotest:kotest-property", Versions.Kotest) val Hamkrest = DependencySpec("com.natpryce:hamkrest", Versions.Hamkrest) val Redisson = DependencySpec("org.redisson:redisson", Versions.Redisson) val SpringMockk = DependencySpec("com.ninja-squad:springmockk", Versions.SpringMockk) val KotlinFaker = DependencySpec("io.github.serpro69:kotlin-faker", Versions.KotlinFaker) - object Swagger { val Annotations = DependencySpec("io.swagger:swagger-annotations", Versions.Swagger) } diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt b/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt index 7a270fa1..8110b6c4 100644 --- a/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt +++ b/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt @@ -7,4 +7,5 @@ data class ContractConfig( val scopeUuid: UUID, val sessionUuid: UUID?, val scopeSpecificationName: String, + val parserConfig: ParserConfig?, ) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/ParserConfig.kt b/models/src/main/kotlin/io/provenance/api/models/cee/ParserConfig.kt new file mode 100644 index 00000000..90359483 --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/ParserConfig.kt @@ -0,0 +1,6 @@ +package io.provenance.api.models.cee + +data class ParserConfig( + val name: String, + val descriptors: List +) diff --git a/models/src/main/kotlin/io/provenance/api/models/eos/GetFileRequest.kt b/models/src/main/kotlin/io/provenance/api/models/eos/GetFileRequest.kt new file mode 100644 index 00000000..6bf60b8a --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/eos/GetFileRequest.kt @@ -0,0 +1,6 @@ +package io.provenance.api.models.eos + +data class GetFileRequest( + val hash: String, + val objectStoreAddress: String +) diff --git a/models/src/main/kotlin/io/provenance/api/models/eos/GetAssetRequest.kt b/models/src/main/kotlin/io/provenance/api/models/eos/GetProtoRequest.kt similarity index 54% rename from models/src/main/kotlin/io/provenance/api/models/eos/GetAssetRequest.kt rename to models/src/main/kotlin/io/provenance/api/models/eos/GetProtoRequest.kt index 1047ad15..62063965 100644 --- a/models/src/main/kotlin/io/provenance/api/models/eos/GetAssetRequest.kt +++ b/models/src/main/kotlin/io/provenance/api/models/eos/GetProtoRequest.kt @@ -1,9 +1,7 @@ package io.provenance.api.models.eos -import java.util.UUID - -data class GetAssetRequest( - val originatorUuid: UUID, +data class GetProtoRequest( val hash: String, val objectStoreAddress: String, + val type: String ) diff --git a/models/src/main/kotlin/io/provenance/api/models/eos/StoreAssetRequest.kt b/models/src/main/kotlin/io/provenance/api/models/eos/StoreAssetRequest.kt deleted file mode 100644 index 67c2333e..00000000 --- a/models/src/main/kotlin/io/provenance/api/models/eos/StoreAssetRequest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.provenance.api.models.eos - -import io.provenance.api.models.account.AccountInfo -import io.provenance.api.models.p8e.PermissionInfo -import java.util.UUID - -data class StoreAssetRequest( - val account: AccountInfo = AccountInfo(), - val objectStoreAddress: String, - val permissions: PermissionInfo?, - val assetId: UUID, - val asset: String, -) diff --git a/models/src/main/kotlin/io/provenance/api/models/eos/StoreProtoRequest.kt b/models/src/main/kotlin/io/provenance/api/models/eos/StoreProtoRequest.kt new file mode 100644 index 00000000..338a89e3 --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/eos/StoreProtoRequest.kt @@ -0,0 +1,10 @@ +package io.provenance.api.models.eos + +import io.provenance.api.models.p8e.PermissionInfo + +data class StoreProtoRequest( + val objectStoreAddress: String, + val permissions: PermissionInfo?, + val message: Any, + val type: String, +) diff --git a/models/src/main/kotlin/io/provenance/api/models/p8e/Audience.kt b/models/src/main/kotlin/io/provenance/api/models/p8e/Audience.kt new file mode 100644 index 00000000..316e8e8b --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/p8e/Audience.kt @@ -0,0 +1,13 @@ +package io.provenance.api.models.p8e + +import java.util.UUID + +data class Audience( + val uuid: UUID?, + var keys: AudienceKeyPair? +) + +data class AudienceKeyPair( + val encryptionKey: String, + val signingKey: String +) diff --git a/models/src/main/kotlin/io/provenance/api/models/p8e/PermissionInfo.kt b/models/src/main/kotlin/io/provenance/api/models/p8e/PermissionInfo.kt index 830e9fbe..2ea6fbe0 100644 --- a/models/src/main/kotlin/io/provenance/api/models/p8e/PermissionInfo.kt +++ b/models/src/main/kotlin/io/provenance/api/models/p8e/PermissionInfo.kt @@ -1,7 +1,7 @@ package io.provenance.api.models.p8e data class PermissionInfo( - val audiences: Set = emptySet(), + val audiences: Set = emptySet(), val permissionDart: Boolean = false, val permissionPortfolioManager: Boolean = false, ) diff --git a/service/build.gradle.kts b/service/build.gradle.kts index e6fb8d26..216aca8c 100644 --- a/service/build.gradle.kts +++ b/service/build.gradle.kts @@ -79,15 +79,14 @@ dependencies { Dependencies.Kotest, Dependencies.KotestAssertions, Dependencies.KotestAssertionsArrow, + Dependencies.KotestProperty, ).forEach { testDep -> testDep.testImplementation(this) } } tasks.withType { - useJUnitPlatform { - includeEngines("junit-jupiter") - } + useJUnitPlatform() testLogging { events("passed", "skipped", "failed") } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/cee/ContractParser.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/cee/ContractParser.kt index b6bed016..3c2fd005 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/cee/ContractParser.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/cee/ContractParser.kt @@ -4,4 +4,5 @@ import com.google.protobuf.Message interface ContractParser { fun parseInput(input: Any, type: Class<*>): Message + fun getParser(name: String): InputParser? } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/cee/InputParser.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/cee/InputParser.kt index 60a7e140..6ac98e36 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/cee/InputParser.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/cee/InputParser.kt @@ -4,5 +4,5 @@ import com.google.protobuf.Message interface InputParser { val type: Class<*> - fun parse(input: Any, type: Class<*>): Message + fun parse(input: Any, type: Class<*>, includeTypes: List = emptyList()): Message } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/extensions/ByteExtensions.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/extensions/ByteExtensions.kt new file mode 100644 index 00000000..2f948015 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/extensions/ByteExtensions.kt @@ -0,0 +1,27 @@ +package io.provenance.onboarding.domain.extensions + +import org.springframework.http.ContentDisposition +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.InvalidMediaTypeException +import org.springframework.http.MediaType + +@Suppress("SwallowedException") +fun ByteArray.toByteResponse( + filename: String, + contentType: String +) = let { bytes -> + bytes to HttpHeaders().also { + it.contentDisposition = ContentDisposition.builder("inline").filename(filename).build() + it.contentLength = bytes.size.toLong() + + contentType.run { + try { + MediaType.parseMediaType(contentType) + } catch (e: InvalidMediaTypeException) { + // ignored + MediaType.APPLICATION_OCTET_STREAM + } + }.run { it.contentType = this } + } +}.let { (bytes, headers) -> HttpEntity(bytes, headers) } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/objectStore/ObjectStore.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/objectStore/ObjectStore.kt index 97816f0b..1e1ed5a6 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/objectStore/ObjectStore.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/objectStore/ObjectStore.kt @@ -1,9 +1,9 @@ package io.provenance.onboarding.domain.objectStore +import com.google.protobuf.Message import io.provenance.api.models.eos.StoreAssetResponse import io.provenance.scope.encryption.proto.Encryption import io.provenance.scope.objectstore.client.OsClient -import tech.figure.asset.v1beta1.Asset import java.security.PrivateKey import java.security.PublicKey @@ -12,5 +12,5 @@ interface ObjectStore { fun retrieve(client: OsClient, hash: ByteArray, publicKey: PublicKey): ByteArray fun retrieveWithDIME(client: OsClient, hash: ByteArray, publicKey: PublicKey): Pair fun retrieveAndDecrypt(client: OsClient, hash: ByteArray, publicKey: PublicKey, privateKey: PrivateKey): ByteArray - fun storeAsset(client: OsClient, asset: Asset, publicKey: PublicKey, additionalAudiences: Set): StoreAssetResponse + fun storeAsset(client: OsClient, message: Message, publicKey: PublicKey, additionalAudiences: Set): StoreAssetResponse } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/CreateClient.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/CreateClient.kt index 9c629775..b9bcfa0c 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/CreateClient.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/CreateClient.kt @@ -1,9 +1,8 @@ package io.provenance.onboarding.domain.usecase.cee.common.client -import io.provenance.core.KeyType import io.provenance.onboarding.domain.usecase.AbstractUseCase import io.provenance.onboarding.domain.usecase.cee.common.client.model.CreateClientRequest -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager import io.provenance.onboarding.frameworks.config.ProvenanceProperties import io.provenance.scope.encryption.model.DirectKeyRef import io.provenance.scope.encryption.util.toJavaPublicKey @@ -20,11 +19,11 @@ import java.util.concurrent.TimeUnit @Component class CreateClient( - private val getOriginator: GetOriginator, private val provenanceProperties: ProvenanceProperties, + private val entityManager: EntityManager, ) : AbstractUseCase() { override suspend fun execute(args: CreateClientRequest): Client { - val originator = getOriginator.execute(args.uuid) + val originator = entityManager.getEntity(args.uuid) val affiliate = Affiliate( signingKeyRef = DirectKeyRef(KeyPair(originator.signingPublicKey() as PublicKey, originator.signingPrivateKey() as PrivateKey)), encryptionKeyRef = DirectKeyRef(KeyPair(originator.encryptionPublicKey() as PublicKey, originator.encryptionPrivateKey() as PrivateKey)), @@ -48,9 +47,8 @@ class CreateClient( } ) ).also { client -> - args.affiliates.forEach { - val keys = getOriginator.execute(it.uuid).keys - client.affiliateRepository.addAffiliate(keys[KeyType.SIGNING_PUBLIC_KEY].toString().toJavaPublicKey(), keys[KeyType.ENCRYPTION_PUBLIC_KEY].toString().toJavaPublicKey()) + args.affiliates.forEach { kp -> + client.affiliateRepository.addAffiliate(kp.signingKey.toJavaPublicKey(), kp.encryptionKey.toJavaPublicKey()) } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/model/CreateClientRequest.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/model/CreateClientRequest.kt index 9c885f4f..804bd465 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/model/CreateClientRequest.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/common/client/model/CreateClientRequest.kt @@ -1,13 +1,13 @@ package io.provenance.onboarding.domain.usecase.cee.common.client.model import io.provenance.api.models.account.AccountInfo -import io.provenance.api.models.account.Participant import io.provenance.api.models.eos.ObjectStoreConfig +import io.provenance.api.models.p8e.AudienceKeyPair import java.util.UUID data class CreateClientRequest( val uuid: UUID, val account: AccountInfo = AccountInfo(), val client: ObjectStoreConfig, - val affiliates: List = emptyList(), + val affiliates: Set = emptySet() ) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/execute/ExecuteContract.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/execute/ExecuteContract.kt index 884e3655..88da4db2 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/execute/ExecuteContract.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/cee/execute/ExecuteContract.kt @@ -2,7 +2,7 @@ package io.provenance.onboarding.domain.usecase.cee.execute import com.google.protobuf.Message import io.provenance.api.models.cee.ContractExecutionResponse -import io.provenance.api.models.p8e.PermissionInfo +import io.provenance.api.models.cee.ParserConfig import io.provenance.api.models.p8e.TxResponse import io.provenance.client.protobuf.extensions.isSet import io.provenance.metadata.v1.ScopeResponse @@ -13,17 +13,14 @@ import io.provenance.onboarding.domain.usecase.AbstractUseCase import io.provenance.onboarding.domain.usecase.cee.common.client.CreateClient import io.provenance.onboarding.domain.usecase.cee.common.client.model.CreateClientRequest import io.provenance.onboarding.domain.usecase.cee.execute.model.ExecuteContractRequestWrapper -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager import io.provenance.onboarding.domain.usecase.provenance.account.GetSigner -import io.provenance.onboarding.frameworks.objectStore.AudienceKeyManager -import io.provenance.onboarding.frameworks.objectStore.DefaultAudience import io.provenance.onboarding.frameworks.provenance.SingleTx import io.provenance.scope.contract.annotations.Input import io.provenance.scope.contract.spec.P8eContract import io.provenance.scope.encryption.util.toJavaPublicKey import io.provenance.scope.sdk.FragmentResult import io.provenance.scope.sdk.SignedResult -import java.security.PublicKey import mu.KotlinLogging import org.springframework.stereotype.Component import java.util.Base64 @@ -39,31 +36,48 @@ class ExecuteContract( private val getSigner: GetSigner, private val contractParser: ContractParser, private val createClient: CreateClient, - private val getOriginator: GetOriginator, - private val audienceKeyManager: AudienceKeyManager, + private val entityManager: EntityManager, ) : AbstractUseCase() { override suspend fun execute(args: ExecuteContractRequestWrapper): ContractExecutionResponse { val signer = getSigner.execute(args.uuid) - val client = createClient.execute(CreateClientRequest(args.uuid, args.request.config.account, args.request.config.client, args.request.participants)) + val audiences = entityManager.hydrateKeys(args.request.permissions) + val client = createClient.execute(CreateClientRequest(args.uuid, args.request.config.account, args.request.config.client, audiences)) val contract = contractService.getContract(args.request.config.contract.contractName) - val records = getRecords(args.request.records, contract) - val audiences = getAudiences(args.request.permissions) + val records = getRecords(args.request.records, contract, args.request.config.contract.parserConfig) val participants = args.request.participants.associate { - it.partyType to getOriginator.execute(it.uuid) + it.partyType to entityManager.getEntity(it.uuid) } val scope = provenanceService.getScope(args.request.config.provenanceConfig, args.request.config.contract.scopeUuid) val scopeToUse: ScopeResponse? = if (scope.scope.scope.isSet() && !scope.scope.scope.scopeId.isEmpty) scope else null - val session = contractService.setupContract(client, contract, records, args.request.config.contract.scopeUuid, args.request.config.contract.sessionUuid, participants, scopeToUse, args.request.config.contract.scopeSpecificationName, audiences) + val session = contractService.setupContract( + client, + contract, + records, + args.request.config.contract.scopeUuid, + args.request.config.contract.sessionUuid, + participants, + scopeToUse, + args.request.config.contract.scopeSpecificationName, + audiences.map { it.encryptionKey.toJavaPublicKey() }.toSet() + ) return when (val result = contractService.executeContract(client, session)) { is SignedResult -> { provenanceService.buildContractTx(args.request.config.provenanceConfig, SingleTx(result))?.let { provenanceService.executeTransaction(args.request.config.provenanceConfig, it, signer).let { pbResponse -> - - ContractExecutionResponse(false, null, TxResponse(pbResponse.txhash, pbResponse.gasWanted.toString(), pbResponse.gasUsed.toString(), pbResponse.height.toString())) + ContractExecutionResponse( + false, + null, + TxResponse( + pbResponse.txhash, + pbResponse.gasWanted.toString(), + pbResponse.gasUsed.toString(), + pbResponse.height.toString() + ) + ) } } ?: throw IllegalStateException("Failed to build contract for execution output.") } @@ -75,26 +89,8 @@ class ExecuteContract( } } - private fun getAudiences(permissions: PermissionInfo?): Set { - val additionalAudiences: MutableSet = mutableSetOf() - - permissions?.audiences?.forEach { - additionalAudiences.add(it.toJavaPublicKey()) - } - - if (permissions?.permissionDart == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.DART)) - } - - if (permissions?.permissionPortfolioManager == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.PORTFOLIO_MANAGER)) - } - - return additionalAudiences - } - @Suppress("TooGenericExceptionCaught") - private fun getRecords(records: Map, contract: Class): Map { + private fun getRecords(records: Map, contract: Class, parserConfig: ParserConfig?): Map { val contractRecords = mutableMapOf() try { @@ -103,7 +99,16 @@ class ExecuteContract( (param.annotations.firstOrNull { it is Input } as? Input)?.let { input -> val parameterClass = Class.forName(param.type.toClassNameString()) records.getOrDefault(input.name, null)?.let { - val record = contractParser.parseInput(it, parameterClass) + + val record = when (val parser = parserConfig?.name?.let { name -> contractParser.getParser(name) }) { + null -> { + contractParser.parseInput(it, parameterClass) + } + else -> { + parser.parse(it, parameterClass, parserConfig.descriptors) + } + } + contractRecords[input.name] = record } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/DefaultAudience.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/DefaultAudience.kt new file mode 100644 index 00000000..faeb4941 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/DefaultAudience.kt @@ -0,0 +1,6 @@ +package io.provenance.onboarding.domain.usecase.common.originator + +enum class DefaultAudience { + DART, + PORTFOLIO_MANAGER, +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/EntityManager.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/EntityManager.kt new file mode 100644 index 00000000..b5b39750 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/EntityManager.kt @@ -0,0 +1,75 @@ +package io.provenance.onboarding.domain.usecase.common.originator + +import io.provenance.api.models.p8e.AudienceKeyPair +import io.provenance.api.models.p8e.PermissionInfo +import io.provenance.core.KeyType +import io.provenance.core.OriginatorManager +import io.provenance.onboarding.frameworks.config.ProvenanceProperties +import io.provenance.onboarding.frameworks.config.VaultProperties +import io.provenance.plugins.vault.VaultPlugin +import io.provenance.plugins.vault.VaultSpec +import mu.KotlinLogging +import org.springframework.stereotype.Component +import java.io.File +import java.util.UUID + +@Component +class EntityManager( + private val vaultProperties: VaultProperties, + private val provenanceProperties: ProvenanceProperties, +) { + private val log = KotlinLogging.logger { } + private var manager: OriginatorManager = OriginatorManager() + private var token: String + + init { + manager.register(VaultPlugin()) + + val tokenPath = if (System.getenv("ENVIRONMENT").isNullOrBlank()) { + log.info("Retrieving token from ${System.getProperty("user.home") + vaultProperties.tokenPath}") + File(System.getProperty("user.home")).resolve(vaultProperties.tokenPath) + } else { + log.info("Retrieving token from ${vaultProperties.tokenPath} on environment: ${vaultProperties.tokenPath}}") + File(vaultProperties.tokenPath) + } + + token = tokenPath.readText(Charsets.UTF_8) + } + + fun getEntity(args: UUID) = + manager.get(args, VaultSpec(args, "${vaultProperties.address}/$args", token)) + + fun hydrateKeys(permissions: PermissionInfo?): Set { + val additionalAudiences: MutableSet = mutableSetOf() + + permissions?.audiences?.forEach { + it.uuid?.let { entity -> + val originator = getEntity(entity) + additionalAudiences.add( + AudienceKeyPair( + originator.keys[KeyType.ENCRYPTION_PUBLIC_KEY].toString(), + originator.keys[KeyType.SIGNING_PUBLIC_KEY].toString(), + ) + ) + } ?: apply { + it.keys?.let { keys -> + additionalAudiences.add(keys) + } + } + } + + if (permissions?.permissionPortfolioManager == true) additionalAudiences.add(getMemberKeyPair(DefaultAudience.PORTFOLIO_MANAGER)) + if (permissions?.permissionDart == true) additionalAudiences.add(getMemberKeyPair(DefaultAudience.DART)) + + return additionalAudiences + } + + private fun getMemberKeyPair(audience: DefaultAudience): AudienceKeyPair = + provenanceProperties.members.firstOrNull { it.name == audience }?.let { + val entity = getEntity(it.uuid) + AudienceKeyPair( + entity.keys[KeyType.ENCRYPTION_PUBLIC_KEY].toString(), + entity.keys[KeyType.SIGNING_PUBLIC_KEY].toString(), + ) + } ?: throw IllegalStateException("Failed to find requested aud") +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/GetOriginator.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/GetOriginator.kt deleted file mode 100644 index dc7da11c..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/common/originator/GetOriginator.kt +++ /dev/null @@ -1,39 +0,0 @@ -package io.provenance.onboarding.domain.usecase.common.originator - -import io.provenance.core.Originator -import io.provenance.core.OriginatorManager -import io.provenance.onboarding.domain.usecase.AbstractUseCase -import io.provenance.onboarding.frameworks.config.VaultProperties -import io.provenance.plugins.vault.VaultPlugin -import io.provenance.plugins.vault.VaultSpec -import mu.KotlinLogging -import org.springframework.stereotype.Component -import java.io.File -import java.util.UUID - -@Component -class GetOriginator( - private val vaultProperties: VaultProperties -) : AbstractUseCase() { - - private val log = KotlinLogging.logger { } - private var manager: OriginatorManager = OriginatorManager() - private var token: String - - init { - manager.register(VaultPlugin()) - - val tokenPath = if (System.getenv("ENVIRONMENT").isNullOrBlank()) { - log.info("Retrieving token from ${System.getProperty("user.home") + vaultProperties.tokenPath}") - File(System.getProperty("user.home")).resolve(vaultProperties.tokenPath) - } else { - log.info("Retrieving token from ${vaultProperties.tokenPath} on environment: ${vaultProperties.tokenPath}}") - File(vaultProperties.tokenPath) - } - - token = tokenPath.readText(Charsets.UTF_8) - } - - override suspend fun execute(args: UUID) = - manager.get(args, VaultSpec(args, "${vaultProperties.address}/$args", token)) -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetFile.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetFile.kt new file mode 100644 index 00000000..e7e3e6cc --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetFile.kt @@ -0,0 +1,28 @@ +package io.provenance.onboarding.domain.usecase.objectStore.get + +import com.google.protobuf.BytesValue +import com.google.protobuf.StringValue +import io.provenance.client.protobuf.extensions.isSet +import io.provenance.onboarding.domain.extensions.toByteResponse +import io.provenance.onboarding.domain.usecase.AbstractUseCase +import io.provenance.onboarding.domain.usecase.objectStore.get.models.GetFileRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.get.models.RetrieveAndDecryptRequest +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>() { + override suspend fun execute(args: GetFileRequestWrapper): HttpEntity { + Asset.parseFrom(retrieveAndDecrypt.execute(RetrieveAndDecryptRequest(args.uuid, args.request.objectStoreAddress, args.request.hash))) + .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.") + } +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetProto.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetProto.kt new file mode 100644 index 00000000..6897b558 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetProto.kt @@ -0,0 +1,19 @@ +package io.provenance.onboarding.domain.usecase.objectStore.get + +import com.google.protobuf.Message +import io.provenance.onboarding.domain.usecase.AbstractUseCase +import io.provenance.onboarding.domain.usecase.objectStore.get.models.GetAssetRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.get.models.RetrieveAndDecryptRequest +import io.provenance.onboarding.util.toPrettyJson +import org.springframework.stereotype.Component + +@Component +class GetProto( + private val retrieveAndDecrypt: RetrieveAndDecrypt, +) : AbstractUseCase() { + override suspend fun execute(args: GetAssetRequestWrapper): String { + val message = retrieveAndDecrypt.execute(RetrieveAndDecryptRequest(args.uuid, args.request.objectStoreAddress, args.request.hash)) + val builder = Class.forName(args.request.type).getMethod("newBuilder").invoke(null) as Message.Builder + return builder.mergeFrom(message).build().toPrettyJson() + } +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetAsset.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/RetrieveAndDecrypt.kt similarity index 67% rename from service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetAsset.kt rename to service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/RetrieveAndDecrypt.kt index d2ae9f35..9b1d0a93 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/GetAsset.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/RetrieveAndDecrypt.kt @@ -2,33 +2,32 @@ package io.provenance.onboarding.domain.usecase.objectStore.get import io.provenance.onboarding.domain.objectStore.ObjectStore import io.provenance.onboarding.domain.usecase.AbstractUseCase -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator -import io.provenance.onboarding.domain.usecase.objectStore.get.models.GetAssetRequestWrapper +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager +import io.provenance.onboarding.domain.usecase.objectStore.get.models.RetrieveAndDecryptRequest import io.provenance.onboarding.frameworks.config.ObjectStoreConfig import io.provenance.scope.objectstore.client.OsClient import io.provenance.scope.objectstore.util.base64Decode -import org.springframework.stereotype.Component import java.lang.IllegalStateException import java.net.URI import java.security.PrivateKey import java.security.PublicKey +import org.springframework.stereotype.Component @Component -class GetAsset( +class RetrieveAndDecrypt( private val objectStore: ObjectStore, - private val getOriginator: GetOriginator, + private val entityManager: EntityManager, private val objectStoreConfig: ObjectStoreConfig, -) : AbstractUseCase() { - override suspend fun execute(args: GetAssetRequestWrapper): String { - val originator = getOriginator.execute(args.uuid) - val osClient = OsClient(URI.create(args.request.objectStoreAddress), objectStoreConfig.timeoutMs) +) : AbstractUseCase() { + override suspend fun execute(args: RetrieveAndDecryptRequest): ByteArray { + val originator = entityManager.getEntity(args.uuid) + val osClient = OsClient(URI.create(args.objectStoreAddress), objectStoreConfig.timeoutMs) val publicKey = (originator.encryptionPublicKey() as? PublicKey) ?: throw IllegalStateException("Public key was not present for originator: ${args.uuid}") val privateKey = (originator.encryptionPrivateKey() as? PrivateKey) ?: throw IllegalStateException("Private key was not present for originator: ${args.uuid}") - val asset = objectStore.retrieveAndDecrypt(osClient, args.request.hash.base64Decode(), publicKey, privateKey) - return asset.decodeToString() + return objectStore.retrieveAndDecrypt(osClient, args.hash.base64Decode(), publicKey, privateKey) } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetAssetRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetAssetRequestWrapper.kt index 60bbf8d9..c017515b 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetAssetRequestWrapper.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetAssetRequestWrapper.kt @@ -1,9 +1,9 @@ package io.provenance.onboarding.domain.usecase.objectStore.get.models -import io.provenance.api.models.eos.GetAssetRequest +import io.provenance.api.models.eos.GetProtoRequest import java.util.UUID data class GetAssetRequestWrapper( val uuid: UUID, - val request: GetAssetRequest + val request: GetProtoRequest ) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetFileRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetFileRequestWrapper.kt new file mode 100644 index 00000000..a4a9ce71 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/GetFileRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.onboarding.domain.usecase.objectStore.get.models + +import io.provenance.api.models.eos.GetFileRequest +import java.util.UUID + +data class GetFileRequestWrapper( + val uuid: UUID, + val request: GetFileRequest +) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/RetrieveAndDecryptRequest.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/RetrieveAndDecryptRequest.kt new file mode 100644 index 00000000..bd07209f --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/get/models/RetrieveAndDecryptRequest.kt @@ -0,0 +1,9 @@ +package io.provenance.onboarding.domain.usecase.objectStore.get.models + +import java.util.UUID + +data class RetrieveAndDecryptRequest( + val uuid: UUID, + val objectStoreAddress: String, + val hash: String, +) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/SnapshotAsset.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/SnapshotAsset.kt deleted file mode 100644 index 23edcc88..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/SnapshotAsset.kt +++ /dev/null @@ -1,55 +0,0 @@ -package io.provenance.onboarding.domain.usecase.objectStore.snapshot - -import io.provenance.onboarding.domain.objectStore.ObjectStore -import io.provenance.onboarding.domain.usecase.AbstractUseCase -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator -import io.provenance.onboarding.domain.usecase.objectStore.store.StoreAsset -import io.provenance.onboarding.domain.usecase.objectStore.snapshot.models.SnapshotAssetRequestWrapper -import io.provenance.api.models.eos.StoreAssetRequest -import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreAssetRequestWrapper -import io.provenance.api.models.eos.StoreAssetResponse -import io.provenance.onboarding.frameworks.config.ObjectStoreConfig -import io.provenance.scope.objectstore.client.OsClient -import io.provenance.scope.objectstore.util.base64Decode -import org.springframework.stereotype.Component -import tech.figure.asset.v1beta1.Asset -import java.lang.IllegalStateException -import java.net.URI -import java.security.PrivateKey -import java.security.PublicKey -import java.util.UUID - -@Component -class SnapshotAsset( - private val objectStore: ObjectStore, - private val getOriginator: GetOriginator, - private val objectStoreConfig: ObjectStoreConfig, - private val storeAsset: StoreAsset -) : AbstractUseCase() { - override suspend fun execute(args: SnapshotAssetRequestWrapper): StoreAssetResponse { - - val originator = getOriginator.execute(args.uuid) - val osClient = OsClient(URI.create(args.request.objectStoreAddress), objectStoreConfig.timeoutMs) - val publicKey = (originator.signingPublicKey() as? PublicKey) - ?: throw IllegalStateException("Public key was not present for originator: ${args.uuid}") - - val privateKey = (originator.signingPrivateKey() as? PrivateKey) - ?: throw IllegalStateException("Private key was not present for originator: ${args.uuid}") - - val asset = objectStore.retrieveAndDecrypt(osClient, args.request.hash.base64Decode(), publicKey, privateKey) - val snapshot = Asset.parseFrom(asset) - - return storeAsset.execute( - StoreAssetRequestWrapper( - args.uuid, - StoreAssetRequest( - args.request.account, - args.request.objectStoreAddress, - args.request.permissions, - UUID.fromString(snapshot.id.value), - snapshot.toByteArray().toString() - ) - ) - ) - } -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/models/SnapshotAssetRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/models/SnapshotAssetRequestWrapper.kt deleted file mode 100644 index 5956a18d..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/snapshot/models/SnapshotAssetRequestWrapper.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.provenance.onboarding.domain.usecase.objectStore.snapshot.models - -import io.provenance.api.models.eos.SnapshotAssetRequest -import java.util.UUID - -data class SnapshotAssetRequestWrapper( - val uuid: UUID, - val request: SnapshotAssetRequest -) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreAsset.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreAsset.kt deleted file mode 100644 index deb38935..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreAsset.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.provenance.onboarding.domain.usecase.objectStore.store - -import io.provenance.onboarding.domain.objectStore.ObjectStore -import io.provenance.onboarding.domain.usecase.AbstractUseCase -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator -import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreAssetRequestWrapper -import io.provenance.api.models.eos.StoreAssetResponse -import io.provenance.onboarding.frameworks.config.ObjectStoreConfig -import io.provenance.onboarding.frameworks.objectStore.AudienceKeyManager -import io.provenance.onboarding.frameworks.objectStore.DefaultAudience -import io.provenance.scope.encryption.util.toJavaPublicKey -import io.provenance.scope.objectstore.client.OsClient -import io.provenance.scope.objectstore.util.base64Decode -import org.springframework.stereotype.Component -import tech.figure.asset.v1beta1.AssetOuterClassBuilders -import tech.figure.proto.util.FileNFT -import tech.figure.proto.util.toProtoAny -import java.lang.IllegalStateException -import java.net.URI -import java.security.PublicKey - -@Component -class StoreAsset( - private val objectStore: ObjectStore, - private val objectStoreConfig: ObjectStoreConfig, - private val audienceKeyManager: AudienceKeyManager, - private val getOriginator: GetOriginator, -) : AbstractUseCase() { - override suspend fun execute(args: StoreAssetRequestWrapper): StoreAssetResponse { - val originator = getOriginator.execute(args.uuid) - val osClient = OsClient(URI.create(args.request.objectStoreAddress), objectStoreConfig.timeoutMs) - val additionalAudiences: MutableSet = mutableSetOf() - - args.request.permissions?.audiences?.forEach { - additionalAudiences.add(it.toJavaPublicKey()) - } - - if (args.request.permissions?.permissionDart == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.DART)) - } - - if (args.request.permissions?.permissionPortfolioManager == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.PORTFOLIO_MANAGER)) - } - - val publicKey = (originator.encryptionPublicKey() as? PublicKey) - ?: throw IllegalStateException("Public key was not present for originator: ${args.uuid}") - - val asset = AssetOuterClassBuilders.Asset { - idBuilder.value = args.request.assetId.toString() - type = FileNFT.ASSET_TYPE - putKv(FileNFT.KEY_BYTES, args.request.asset.base64Decode().toProtoAny()) - } - - return objectStore.storeAsset(osClient, asset, publicKey, additionalAudiences) - } -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreFile.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreFile.kt new file mode 100644 index 00000000..609d8342 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreFile.kt @@ -0,0 +1,60 @@ +package io.provenance.onboarding.domain.usecase.objectStore.store + +import com.google.gson.Gson +import io.provenance.api.models.eos.StoreAssetResponse +import io.provenance.api.models.p8e.AudienceKeyPair +import io.provenance.api.models.p8e.PermissionInfo +import io.provenance.onboarding.domain.objectStore.ObjectStore +import io.provenance.onboarding.domain.usecase.AbstractUseCase +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreFileRequestWrapper +import io.provenance.onboarding.frameworks.config.ObjectStoreConfig +import io.provenance.onboarding.util.awaitAllBytes +import io.provenance.scope.encryption.util.toJavaPublicKey +import io.provenance.scope.objectstore.client.OsClient +import java.net.URI +import java.security.PublicKey +import java.util.UUID +import org.springframework.http.codec.multipart.FilePart +import org.springframework.http.codec.multipart.FormFieldPart +import org.springframework.http.codec.multipart.Part +import org.springframework.stereotype.Component +import tech.figure.asset.v1beta1.AssetOuterClassBuilders +import tech.figure.proto.util.FileNFT +import tech.figure.proto.util.toProtoAny + +@Component +class StoreFile( + private val objectStore: ObjectStore, + private val objectStoreConfig: ObjectStoreConfig, + private val entityManager: EntityManager, +) : AbstractUseCase() { + override suspend fun execute(args: StoreFileRequestWrapper): StoreAssetResponse { + val originator = entityManager.getEntity(args.uuid) + var additionalAudiences = emptySet() + val osClient = OsClient(URI.create(args.request.getAsType("objectStoreAddress").value()), objectStoreConfig.timeoutMs) + val file = args.request.getAsType("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 = UUID.randomUUID().toString() + type = FileNFT.ASSET_TYPE + description = file.name() + + 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.storeAsset(osClient, asset, originator.encryptionPublicKey() as PublicKey, additionalAudiences.map { it.encryptionKey.toJavaPublicKey() }.toSet()) + } + + private inline fun Map.getAsType(key: String): T = + T::class.java.cast(get(key)) + ?: throw IllegalArgumentException("Failed to retrieve and cast provided argument.") +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreProto.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreProto.kt new file mode 100644 index 00000000..8e11902e --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/StoreProto.kt @@ -0,0 +1,36 @@ +package io.provenance.onboarding.domain.usecase.objectStore.store + +import io.provenance.onboarding.domain.objectStore.ObjectStore +import io.provenance.onboarding.domain.usecase.AbstractUseCase +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreProtoRequestWrapper +import io.provenance.api.models.eos.StoreAssetResponse +import io.provenance.onboarding.frameworks.cee.parsers.MessageParser +import io.provenance.onboarding.frameworks.config.ObjectStoreConfig +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 + +@Component +class StoreProto( + private val objectStore: ObjectStore, + private val objectStoreConfig: ObjectStoreConfig, + private val entityManager: EntityManager, + private val parser: MessageParser +) : AbstractUseCase() { + override suspend fun execute(args: StoreProtoRequestWrapper): StoreAssetResponse { + val originator = entityManager.getEntity(args.uuid) + val osClient = OsClient(URI.create(args.request.objectStoreAddress), objectStoreConfig.timeoutMs) + val additionalAudiences = entityManager.hydrateKeys(args.request.permissions) + + val asset = parser.parse(args.request.message, Class.forName(args.request.type)) + + val publicKey = (originator.encryptionPublicKey() as? PublicKey) + ?: throw IllegalStateException("Public key was not present for originator: ${args.uuid}") + + return objectStore.storeAsset(osClient, asset, publicKey, additionalAudiences.map { it.encryptionKey.toJavaPublicKey() }.toSet()) + } +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreAssetRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreAssetRequestWrapper.kt deleted file mode 100644 index 23e927f0..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreAssetRequestWrapper.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.provenance.onboarding.domain.usecase.objectStore.store.models - -import io.provenance.api.models.eos.StoreAssetRequest -import java.util.UUID - -data class StoreAssetRequestWrapper( - val uuid: UUID, - val request: StoreAssetRequest, -) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreFileRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreFileRequestWrapper.kt new file mode 100644 index 00000000..e4a3fdf6 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreFileRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.onboarding.domain.usecase.objectStore.store.models + +import java.util.UUID +import org.springframework.http.codec.multipart.Part + +data class StoreFileRequestWrapper( + val uuid: UUID, + val request: Map, +) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreProtoRequestWrapper.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreProtoRequestWrapper.kt new file mode 100644 index 00000000..78bfa6fe --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/objectStore/store/models/StoreProtoRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.onboarding.domain.usecase.objectStore.store.models + +import io.provenance.api.models.eos.StoreProtoRequest +import java.util.UUID + +data class StoreProtoRequestWrapper( + val uuid: UUID, + val request: StoreProtoRequest, +) diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/account/GetSigner.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/account/GetSigner.kt index ff40c8c6..d8b59128 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/account/GetSigner.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/account/GetSigner.kt @@ -2,7 +2,7 @@ package io.provenance.onboarding.domain.usecase.provenance.account import io.provenance.client.grpc.Signer import io.provenance.onboarding.domain.usecase.AbstractUseCase -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager import io.provenance.onboarding.frameworks.config.ProvenanceProperties import io.provenance.onboarding.frameworks.provenance.utility.ProvenanceUtils import org.springframework.stereotype.Component @@ -12,13 +12,13 @@ import java.util.UUID @Component class GetSigner( - private val getOriginator: GetOriginator, + private val entityManager: EntityManager, private val provenanceProperties: ProvenanceProperties, ) : AbstractUseCase() { override suspend fun execute(args: UUID): Signer { val utils = ProvenanceUtils() - val originator = getOriginator.execute(args) + val originator = entityManager.getEntity(args) return originator.signingPublicKey().let { public -> originator.signingPrivateKey().let { private -> diff --git a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTx.kt b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTx.kt index 5f260d3d..b859b376 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTx.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTx.kt @@ -3,18 +3,18 @@ package io.provenance.onboarding.domain.usecase.provenance.tx import io.provenance.onboarding.domain.usecase.AbstractUseCase import io.provenance.onboarding.domain.usecase.common.model.ScopeConfig import io.provenance.api.models.p8e.TxBody +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager import io.provenance.onboarding.domain.usecase.provenance.account.GetSigner import io.provenance.onboarding.domain.usecase.provenance.tx.model.CreateTxRequestWrapper import io.provenance.onboarding.frameworks.config.ProvenanceProperties -import io.provenance.onboarding.frameworks.objectStore.AudienceKeyManager -import io.provenance.onboarding.frameworks.objectStore.DefaultAudience import io.provenance.onboarding.frameworks.provenance.utility.ProvenanceUtils import io.provenance.scope.encryption.util.getAddress +import io.provenance.scope.encryption.util.toJavaPublicKey import org.springframework.stereotype.Component @Component class CreateTx( - private val audienceKeyManager: AudienceKeyManager, + private val entityManager: EntityManager, private val getSigner: GetSigner, private val provenanceProperties: ProvenanceProperties, ) : AbstractUseCase() { @@ -22,19 +22,7 @@ class CreateTx( val utils = ProvenanceUtils() val account = getSigner.execute(args.uuid) - val additionalAudiences: MutableSet = mutableSetOf() - - args.request.permissions?.audiences?.forEach { - additionalAudiences.add(it) - } - - if (args.request.permissions?.permissionDart == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.DART).getAddress(provenanceProperties.mainnet)) - } - - if (args.request.permissions?.permissionPortfolioManager == true) { - additionalAudiences.add(audienceKeyManager.get(DefaultAudience.PORTFOLIO_MANAGER).getAddress(provenanceProperties.mainnet)) - } + val additionalAudiences = entityManager.hydrateKeys(args.request.permissions) return utils.createScopeTx( ScopeConfig( @@ -44,7 +32,7 @@ class CreateTx( ), args.request.contractInput, account.address(), - additionalAudiences + additionalAudiences.map { it.signingKey.toJavaPublicKey().getAddress(provenanceProperties.mainnet) }.toSet() ) } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/ContractParserService.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/ContractParserService.kt index bedc7a0a..15f1fbec 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/ContractParserService.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/ContractParserService.kt @@ -13,4 +13,7 @@ class ContractParserService( override fun parseInput(input: Any, type: Class<*>): Message = parsers.firstOrNull { type.kotlin.isSubclassOf(it.type.kotlin) }?.parse(input, type) ?: throw IllegalStateException("Failed to find parser for contract input.") + + override fun getParser(name: String): InputParser? = + parsers.firstOrNull { it.javaClass.name == name } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/JsonMessageParser.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/JsonMessageParser.kt new file mode 100644 index 00000000..dfb06c27 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/JsonMessageParser.kt @@ -0,0 +1,27 @@ +package io.provenance.onboarding.frameworks.cee.parsers + +import com.google.protobuf.Descriptors +import com.google.protobuf.Message +import com.google.protobuf.TypeRegistry +import com.google.protobuf.util.JsonFormat +import io.provenance.onboarding.domain.cee.InputParser +import io.provenance.onboarding.util.toPrettyJson +import org.springframework.stereotype.Component + +@Component +class JsonMessageParser : InputParser { + override val type: Class<*> = Message::class.java + + override fun parse(input: Any, type: Class<*>, includeTypes: List): Message { + val builder = type.getMethod("newBuilder").invoke(null) as Message.Builder + val typeRegistry = TypeRegistry.newBuilder() + + includeTypes.forEach { + val descriptor = Class.forName(it).getMethod("getDescriptor").invoke(null) as Descriptors.Descriptor + typeRegistry.add(descriptor) + } + + JsonFormat.parser().usingTypeRegistry(typeRegistry.build()).merge(input.toPrettyJson(), builder) + return builder.build() + } +} diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/MessageParser.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/MessageParser.kt index d81ab2ed..9bf98cfb 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/MessageParser.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/cee/parsers/MessageParser.kt @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component class MessageParser : InputParser { override val type: Class<*> = Message::class.java - override fun parse(input: Any, type: Class<*>): Message = ObjectMapper() + override fun parse(input: Any, type: Class<*>, includeTypes: List): Message = ObjectMapper() .registerModule(ProtobufModule()) .registerModule(JavaTimeModule()) .readValue(input.toPrettyJson(), type) as Message diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/AppConfig.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/AppConfig.kt index ea35588f..981fea2d 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/AppConfig.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/AppConfig.kt @@ -13,7 +13,6 @@ import org.springframework.context.annotation.Primary ServiceProps::class, ObjectStoreConfig::class, VaultProperties::class, - ServiceKeysProperties::class, ProvenanceProperties::class ] ) diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ProvenanceProperties.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ProvenanceProperties.kt index 92065669..9088d3f9 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ProvenanceProperties.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ProvenanceProperties.kt @@ -1,5 +1,8 @@ package io.provenance.onboarding.frameworks.config +import io.provenance.onboarding.domain.usecase.common.originator.DefaultAudience +import java.util.UUID +import javax.validation.constraints.NotNull import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.validation.annotation.Validated @@ -7,4 +10,14 @@ import org.springframework.validation.annotation.Validated @Validated class ProvenanceProperties { var mainnet: Boolean = false + + var members: List = emptyList() + + class Member { + @NotNull + lateinit var uuid: UUID + + @NotNull + lateinit var name: DefaultAudience + } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ServiceKeysProperties.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ServiceKeysProperties.kt deleted file mode 100644 index 224c6922..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/config/ServiceKeysProperties.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.provenance.onboarding.frameworks.config - -import org.springframework.boot.context.properties.ConfigurationProperties -import javax.validation.constraints.NotNull - -@ConfigurationProperties(prefix = "service-keys") -class ServiceKeysProperties : LoggableProperties() { - - @NotNull - lateinit var portfolioManager: String - - @NotNull - lateinit var dart: String -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/AudienceKeyManager.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/AudienceKeyManager.kt deleted file mode 100644 index c16036db..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/AudienceKeyManager.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.provenance.onboarding.frameworks.objectStore - -import com.google.common.io.BaseEncoding -import io.provenance.onboarding.frameworks.config.ServiceKeysProperties -import io.provenance.scope.encryption.ecies.ECUtils -import org.springframework.stereotype.Component -import java.security.PublicKey - -@Component -class AudienceKeyManager( - private val serviceKeys: ServiceKeysProperties -) { - // This simple map of default audience members could be extended into: - // 1) a mutable map where each new audience member is stored in memory after the first use to reduce calls to decode/convert string to key on subsequent use - // 2) a service that fetches audience keys from an external storage location much like the OriginatorManager - private val audienceMembers: Map = mapOf( - DefaultAudience.DART to ECUtils.convertBytesToPublicKey(BaseEncoding.base64().decode(serviceKeys.dart)), - DefaultAudience.PORTFOLIO_MANAGER to ECUtils.convertBytesToPublicKey(BaseEncoding.base64().decode(serviceKeys.portfolioManager)), - ) - - fun get(audience: DefaultAudience): PublicKey { - return audienceMembers[audience] ?: throw IllegalArgumentException("${audience.name} is not a supported audience.") - } -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/DefaultAudience.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/DefaultAudience.kt deleted file mode 100644 index 62aeec3d..00000000 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/DefaultAudience.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.provenance.onboarding.frameworks.objectStore - -enum class DefaultAudience { - DART, - PORTFOLIO_MANAGER, -} diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/ObjectStoreService.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/ObjectStoreService.kt index 08e5acb2..a7457283 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/ObjectStoreService.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/objectStore/ObjectStoreService.kt @@ -14,7 +14,6 @@ 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 tech.figure.asset.v1beta1.Asset import java.security.PrivateKey import java.security.PublicKey import java.util.concurrent.TimeUnit @@ -87,8 +86,8 @@ class ObjectStoreService( override fun storeAsset( client: OsClient, - asset: Asset, + message: Message, publicKey: PublicKey, additionalAudiences: Set - ): StoreAssetResponse = encryptAndStore(client, asset, publicKey, additionalAudiences).toModel() + ): StoreAssetResponse = encryptAndStore(client, message, publicKey, additionalAudiences).toModel() } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/SuccessResponses.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/SuccessResponses.kt index 75e658c0..ad0a1415 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/SuccessResponses.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/SuccessResponses.kt @@ -1,5 +1,6 @@ package io.provenance.onboarding.frameworks.web +import org.springframework.http.HttpHeaders import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.bodyValueAndAwait import org.springframework.web.reactive.function.server.buildAndAwait @@ -9,4 +10,9 @@ object SuccessResponses { suspend fun noContent() = ServerResponse.noContent().buildAndAwait() suspend fun ok(body: Any) = ServerResponse.ok().bodyValueAndAwait(body) + + suspend fun okWithHeaders(headers: HttpHeaders, body: Any) = + ServerResponse.ok().headers { + it.addAll(headers) + }.bodyValueAndAwait(body) } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/config/SecurityConfig.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/config/SecurityConfig.kt index 5d2600e5..5c650602 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/config/SecurityConfig.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/config/SecurityConfig.kt @@ -19,8 +19,8 @@ class SecurityConfig { fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { http.authorizeExchange { it.pathMatchers("${Routes.MANAGE_BASE}/**", "${Routes.EXTERNAL_BASE}/**", "${Routes.INTERNAL_BASE}/**", "${Routes.DOCS_BASE}/**").permitAll() - } - .csrf().disable() + }.csrf().disable() + return http.build() } } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreApi.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreApi.kt index f8af18f5..b05d49c5 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreApi.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreApi.kt @@ -1,9 +1,9 @@ package io.provenance.onboarding.frameworks.web.external.objectStore -import io.provenance.api.models.eos.GetAssetRequest +import io.provenance.api.models.eos.GetProtoRequest import io.provenance.api.models.eos.SnapshotAssetRequest import io.provenance.onboarding.domain.usecase.objectStore.replication.models.EnableReplicationRequest -import io.provenance.api.models.eos.StoreAssetRequest +import io.provenance.api.models.eos.StoreProtoRequest import io.provenance.api.models.eos.StoreAssetResponse import io.provenance.onboarding.frameworks.web.Routes import io.provenance.onboarding.frameworks.web.logging.logExchange @@ -47,7 +47,7 @@ class ObjectStoreApi { ], requestBody = RequestBody( required = true, - content = [Content(schema = Schema(implementation = StoreAssetRequest::class))] + content = [Content(schema = Schema(implementation = StoreProtoRequest::class))] ), responses = [ ApiResponse( @@ -105,7 +105,7 @@ class ObjectStoreApi { ], requestBody = RequestBody( required = true, - content = [Content(schema = Schema(implementation = GetAssetRequest::class))] + content = [Content(schema = Schema(implementation = GetProtoRequest::class))] ), responses = [ ApiResponse( @@ -141,9 +141,10 @@ class ObjectStoreApi { logExchange(log) Routes.EXTERNAL_BASE_V1.nest { "/eos".nest { - POST("", handler::store) - POST("/snapshot", handler::snapshot) - GET("", handler::getAsset) + POST("/file", handler::storeFile) + POST("", handler::storeProto) + GET("/file", handler::getFile) + GET("", handler::getProto) } POST("/config/replication/enable", handler::enableReplication) } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreHandler.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreHandler.kt index b61709bf..78260b47 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreHandler.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/external/objectStore/ObjectStoreHandler.kt @@ -1,36 +1,44 @@ package io.provenance.onboarding.frameworks.web.external.objectStore +import io.provenance.onboarding.domain.usecase.objectStore.get.GetFile import io.provenance.onboarding.domain.usecase.objectStore.replication.EnableReplication -import io.provenance.onboarding.domain.usecase.objectStore.get.GetAsset +import io.provenance.onboarding.domain.usecase.objectStore.get.GetProto import io.provenance.onboarding.domain.usecase.objectStore.get.models.GetAssetRequestWrapper -import io.provenance.onboarding.domain.usecase.objectStore.snapshot.SnapshotAsset -import io.provenance.onboarding.domain.usecase.objectStore.snapshot.models.SnapshotAssetRequestWrapper -import io.provenance.onboarding.domain.usecase.objectStore.store.StoreAsset -import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreAssetRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.get.models.GetFileRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.store.StoreFile +import io.provenance.onboarding.domain.usecase.objectStore.store.StoreProto +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreProtoRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreFileRequestWrapper import io.provenance.onboarding.frameworks.web.misc.foldToServerResponse import io.provenance.onboarding.frameworks.web.misc.getUser import org.springframework.stereotype.Component import org.springframework.web.reactive.function.server.ServerRequest import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.awaitBody +import org.springframework.web.reactive.function.server.awaitMultipartData @Component class ObjectStoreHandler( - private val storeAsset: StoreAsset, - private val getAsset: GetAsset, - private val snapshotAsset: SnapshotAsset, + private val storeProto: StoreProto, + private val storeFile: StoreFile, + private val getProto: GetProto, + private val getFile: GetFile, private val enableReplication: EnableReplication, ) { - suspend fun store(req: ServerRequest): ServerResponse = runCatching { - storeAsset.execute(StoreAssetRequestWrapper(req.getUser(), req.awaitBody())) + suspend fun storeProto(req: ServerRequest): ServerResponse = runCatching { + storeProto.execute(StoreProtoRequestWrapper(req.getUser(), req.awaitBody())) }.foldToServerResponse() - suspend fun snapshot(req: ServerRequest): ServerResponse = runCatching { - snapshotAsset.execute(SnapshotAssetRequestWrapper(req.getUser(), req.awaitBody())) + suspend fun storeFile(req: ServerRequest): ServerResponse = runCatching { + storeFile.execute(StoreFileRequestWrapper(req.getUser(), req.awaitMultipartData().toSingleValueMap())) }.foldToServerResponse() - suspend fun getAsset(req: ServerRequest): ServerResponse = runCatching { - getAsset.execute(GetAssetRequestWrapper(req.getUser(), req.awaitBody())) + suspend fun getProto(req: ServerRequest): ServerResponse = runCatching { + getProto.execute(GetAssetRequestWrapper(req.getUser(), req.awaitBody())) + }.foldToServerResponse() + + suspend fun getFile(req: ServerRequest): ServerResponse = runCatching { + getFile.execute(GetFileRequestWrapper(req.getUser(), req.awaitBody())) }.foldToServerResponse() suspend fun enableReplication(req: ServerRequest): ServerResponse = runCatching { diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/internal/objectStore/InternalObjectStoreHandler.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/internal/objectStore/InternalObjectStoreHandler.kt index 78372fbe..e143b959 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/internal/objectStore/InternalObjectStoreHandler.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/internal/objectStore/InternalObjectStoreHandler.kt @@ -1,7 +1,7 @@ package io.provenance.onboarding.frameworks.web.internal.objectStore -import io.provenance.onboarding.domain.usecase.objectStore.store.StoreAsset -import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreAssetRequestWrapper +import io.provenance.onboarding.domain.usecase.objectStore.store.StoreProto +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreProtoRequestWrapper import io.provenance.onboarding.frameworks.web.misc.foldToServerResponse import io.provenance.onboarding.frameworks.web.misc.getUser import org.springframework.stereotype.Component @@ -11,9 +11,9 @@ import org.springframework.web.reactive.function.server.awaitBody @Component class InternalObjectStoreHandler( - private val storeAsset: StoreAsset + private val storeAsset: StoreProto ) { suspend fun store(req: ServerRequest): ServerResponse = runCatching { - storeAsset.execute(StoreAssetRequestWrapper(req.getUser(), req.awaitBody())) + storeAsset.execute(StoreProtoRequestWrapper(req.getUser(), req.awaitBody())) }.foldToServerResponse() } diff --git a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/misc/ServerResponseExt.kt b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/misc/ServerResponseExt.kt index b7da942b..aa8c1e21 100644 --- a/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/misc/ServerResponseExt.kt +++ b/service/src/main/kotlin/io/provenance/onboarding/frameworks/web/misc/ServerResponseExt.kt @@ -3,6 +3,7 @@ package io.provenance.onboarding.frameworks.web.misc import io.provenance.onboarding.frameworks.web.ErrorResponses import io.provenance.onboarding.frameworks.web.SuccessResponses import mu.KotlinLogging +import org.springframework.http.HttpEntity import org.springframework.web.reactive.function.server.ServerResponse private val log = KotlinLogging.logger {} @@ -13,7 +14,12 @@ suspend fun Result.foldToServerResponse(): ServerResponse = if (it is Unit) { SuccessResponses.noContent() } else { - SuccessResponses.ok(it) + when (it) { + is HttpEntity<*> -> { + SuccessResponses.okWithHeaders(it.headers, it.body!!) + } + else -> SuccessResponses.ok(it) + } } }, onFailure = { diff --git a/service/src/main/resources/application-container.properties b/service/src/main/resources/application-container.properties index f69d9072..ffe03b83 100644 --- a/service/src/main/resources/application-container.properties +++ b/service/src/main/resources/application-container.properties @@ -8,8 +8,8 @@ object-store.timeoutMs=${OBJECT_STORE_TIMEOUT_MS:60000} vault.address=${VAULT_ADDRESS} vault.tokenPath=${VAULT_TOKEN_PATH:vault/secrets/token} -# Service Keys -service-keys.portfolioManager=${PORTFOLIO_MANAGER_PUBLIC_KEY} -service-keys.dart=${DART_PUBLIC_KEY} - p8e.mainnet=${IS_MAIN_NET:false} +p8e.members[0].uuid=${DART_UUID} +p8e.members[0].name=DART +p8e.members[1].uuid=${PORTFOLIO_MANAGER_UUID} +p8e.members[1].name=PORTFOLIO_MANAGER diff --git a/service/src/main/resources/application-development.properties b/service/src/main/resources/application-development.properties index 4c059862..82c51645 100644 --- a/service/src/main/resources/application-development.properties +++ b/service/src/main/resources/application-development.properties @@ -10,8 +10,8 @@ object-store.timeoutMs=20000 vault.address=${VAULT_ADDRESS:http://127.0.0.1:8200/v1/kv2_originations/data/originators} vault.tokenPath=${VAULT_TOKEN_PATH:.vault-token} -# Service Keys -service-keys.portfolioManager=AoXp62d+/MNs9Jokzemb4NjrQy5caucVED0iRR4oGCAl -service-keys.dart=Akf4FbRggw8xHsz4NidwH0H/EdITZ9OA/SjaCsjDNd1U - p8e.mainnet=false +p8e.members[0].uuid=deadbeef-face-479b-860c-facefaceface +p8e.members[0].name=DART +p8e.members[1].uuid=deadbeef-face-2222-860c-facefaceface +p8e.members[1].name=PORTFOLIO_MANAGER diff --git a/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/objectstore/StoreAssetTest.kt b/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/objectstore/StoreAssetTest.kt index 89a36bb1..7a4321ea 100644 --- a/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/objectstore/StoreAssetTest.kt +++ b/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/objectstore/StoreAssetTest.kt @@ -8,23 +8,23 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.verify -import io.provenance.api.models.account.AccountInfo import io.provenance.api.models.p8e.PermissionInfo import io.provenance.core.Originator import io.provenance.onboarding.domain.objectStore.ObjectStore -import io.provenance.onboarding.domain.usecase.common.originator.GetOriginator -import io.provenance.onboarding.domain.usecase.objectStore.store.StoreAsset -import io.provenance.api.models.eos.StoreAssetRequest -import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreAssetRequestWrapper +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager +import io.provenance.onboarding.domain.usecase.objectStore.store.StoreProto +import io.provenance.api.models.eos.StoreProtoRequest +import io.provenance.onboarding.domain.usecase.objectStore.store.models.StoreProtoRequestWrapper import io.provenance.api.models.eos.StoreAssetResponse +import io.provenance.api.models.p8e.Audience +import io.provenance.api.models.p8e.AudienceKeyPair +import io.provenance.onboarding.frameworks.cee.parsers.MessageParser import io.provenance.onboarding.frameworks.config.ObjectStoreConfig -import io.provenance.onboarding.frameworks.objectStore.AudienceKeyManager -import io.provenance.onboarding.frameworks.objectStore.DefaultAudience import io.provenance.scope.encryption.util.toJavaPublicKey import io.provenance.scope.util.toUuid import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import java.security.PublicKey +import tech.figure.asset.v1beta1.Asset const val ADD_ASSET_OBJECT_STORE_ADDRESS = "grpc://localhost:5005" const val ADD_ASSET_AUDIENCE_PUBLIC_KEY = @@ -33,45 +33,33 @@ val ASSET_ID = "20141790-6de2-4d11-b3ad-9a1e16a8b38e".toUuid() const val ASSET = "am9l" val REQUEST_UUID = "11141790-6de2-4d11-b3ad-9a1e16a8b3aa".toUuid() -val ACCOUNT_INFO = AccountInfo() - class StoreAssetTest : FunSpec({ val mockObjectStoreConfig = mockk() val mockObjectStore = mockk() - val mockAudienceKeyManager = mockk() - val mockGetOriginator = mockk() + val mockEntityManager = mockk() val mockOriginator = mockk() val mockOriginatorPublicKey = mockk() val mockAddAssetAudiencePublicKey = mockk() - val mockDartAudiencePublicKey = mockk() - val mockPortfolioManagerAudiencePublicKey = mockk() + val mockParser = mockk() - val storeAsset = StoreAsset( + val storeAsset = StoreProto( mockObjectStore, mockObjectStoreConfig, - mockAudienceKeyManager, - mockGetOriginator + mockEntityManager, + mockParser, ) beforeTest { every { mockObjectStoreConfig.timeoutMs } answers { OBECT_STORE_TIMEOUT_CONFIG } - coEvery { mockGetOriginator.execute(any()) } returns mockOriginator + coEvery { mockEntityManager.getEntity(any()) } returns mockOriginator mockkStatic(String::toJavaPublicKey) every { ADD_ASSET_AUDIENCE_PUBLIC_KEY.toJavaPublicKey() } returns mockAddAssetAudiencePublicKey - - every { - mockAudienceKeyManager.get(DefaultAudience.DART) - } returns mockDartAudiencePublicKey - - every { - mockAudienceKeyManager.get(DefaultAudience.PORTFOLIO_MANAGER) - } returns mockPortfolioManagerAudiencePublicKey } afterTest { @@ -82,23 +70,23 @@ class StoreAssetTest : FunSpec({ val storeAssetResponse = StoreAssetResponse("HASH", "URI", "BUCKET", "NAME") every { mockObjectStore.storeAsset(any(), any(), any(), any()) } returns storeAssetResponse - + every { mockEntityManager.hydrateKeys(any()) } returns emptySet() every { mockOriginator.encryptionPublicKey() } returns mockOriginatorPublicKey + every { mockParser.parse(any(), any()) } returns Asset.getDefaultInstance() // Execute enable replication code val response = storeAsset.execute( - StoreAssetRequestWrapper( + StoreProtoRequestWrapper( REQUEST_UUID, - StoreAssetRequest( - ACCOUNT_INFO, + StoreProtoRequest( ADD_ASSET_OBJECT_STORE_ADDRESS, PermissionInfo( - setOf(ADD_ASSET_AUDIENCE_PUBLIC_KEY), + setOf(Audience(null, AudienceKeyPair(ADD_ASSET_AUDIENCE_PUBLIC_KEY, ADD_ASSET_AUDIENCE_PUBLIC_KEY))), permissionDart = true, permissionPortfolioManager = true ), - ASSET_ID, - ASSET + ASSET, + String::class.java.canonicalName ) ) ) @@ -108,34 +96,28 @@ class StoreAssetTest : FunSpec({ verify { mockObjectStore.storeAsset( any(), - withArg { - assertEquals(ASSET_ID.toString(), it.id.value) - }, + any(), mockOriginatorPublicKey, - withArg { - assertEquals(3, it.size) - assertTrue(it.contains(mockAddAssetAudiencePublicKey)) - assertTrue(it.contains(mockPortfolioManagerAudiencePublicKey)) - assertTrue(it.contains(mockDartAudiencePublicKey)) - } + any() ) } } test("exception when public key is not set") { every { mockOriginator.encryptionPublicKey() } returns FakeKey() + every { mockEntityManager.hydrateKeys(any()) } returns emptySet() + every { mockParser.parse(any(), any()) } returns Asset.getDefaultInstance() // Execute enable replication code shouldThrow { storeAsset.execute( - StoreAssetRequestWrapper( + StoreProtoRequestWrapper( REQUEST_UUID, - StoreAssetRequest( - ACCOUNT_INFO, + StoreProtoRequest( ADD_ASSET_OBJECT_STORE_ADDRESS, PermissionInfo(emptySet()), - ASSET_ID, - ASSET + ASSET, + String::class.java.canonicalName ) ) ) diff --git a/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTxTest.kt b/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTxTest.kt index 9487d2fe..88f0dea7 100644 --- a/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTxTest.kt +++ b/service/src/test/kotlin/io/provenance/onboarding/domain/usecase/provenance/tx/CreateTxTest.kt @@ -7,16 +7,16 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.provenance.api.models.account.AccountInfo +import io.provenance.api.models.p8e.Audience +import io.provenance.api.models.p8e.AudienceKeyPair import io.provenance.api.models.p8e.CreateTxRequest import io.provenance.api.models.p8e.PermissionInfo import io.provenance.client.grpc.Signer import io.provenance.core.Originator +import io.provenance.onboarding.domain.usecase.common.originator.EntityManager import io.provenance.onboarding.domain.usecase.provenance.account.GetSigner import io.provenance.onboarding.domain.usecase.provenance.tx.model.CreateTxRequestWrapper import io.provenance.onboarding.frameworks.config.ProvenanceProperties -import io.provenance.onboarding.frameworks.objectStore.AudienceKeyManager -import io.provenance.onboarding.frameworks.objectStore.DefaultAudience -import io.provenance.scope.encryption.util.getAddress import io.provenance.scope.encryption.util.toJavaPublicKey import io.provenance.scope.util.toUuid import org.junit.jupiter.api.Assertions.assertNotNull @@ -37,7 +37,7 @@ val ACCOUNT_INFO = AccountInfo() val REQUEST_UUID = "11141790-6de2-4d11-b3ad-9a1e16a8b3aa".toUuid() class CreateTxTest : FunSpec({ - val mockAudienceKeyManager = mockk() + val mockEntityManager = mockk() val mockOriginator = mockk() val mockSigner = mockk() val mockGetSigner = mockk() @@ -45,9 +45,9 @@ class CreateTxTest : FunSpec({ val mockProvenanceProperties = mockk() val createTx = CreateTx( - mockAudienceKeyManager, + mockEntityManager, mockGetSigner, - mockProvenanceProperties + mockProvenanceProperties, ) beforeTest { @@ -59,14 +59,6 @@ class CreateTxTest : FunSpec({ mockProvenanceProperties.mainnet } returns !IS_TEST_NET - every { - mockAudienceKeyManager.get(DefaultAudience.DART).getAddress(!IS_TEST_NET) - } returns DART_AUDIENCE_PUBLIC_KEY_STR - - every { - mockAudienceKeyManager.get(DefaultAudience.PORTFOLIO_MANAGER).getAddress(!IS_TEST_NET) - } returns PORTFOLIO_MANAGER_AUDIENCE_PUBLICKEY_STR - every { mockSigner.address() } returns MOCK_SIGNER_ADDRESS } @@ -76,6 +68,7 @@ class CreateTxTest : FunSpec({ test("happy path") { every { mockOriginator.encryptionPublicKey() } returns mockOriginatorPublicKey + every { mockEntityManager.hydrateKeys(any()) } returns emptySet() // Execute enable replication code val response = createTx.execute( @@ -84,7 +77,7 @@ class CreateTxTest : FunSpec({ CreateTxRequest( ACCOUNT_INFO, PermissionInfo( - setOf(ADD_ASSET_AUDIENCE_PUBLIC_KEY), + setOf(Audience(null, AudienceKeyPair(ADD_ASSET_AUDIENCE_PUBLIC_KEY, ADD_ASSET_AUDIENCE_PUBLIC_KEY))), permissionDart = true, permissionPortfolioManager = true ), diff --git a/service/src/test/kotlin/io/provenance/onboarding/frameworks/cee/P8eContractServiceSpec.kt b/service/src/test/kotlin/io/provenance/onboarding/frameworks/cee/P8eContractServiceSpec.kt new file mode 100644 index 00000000..bc640b4e --- /dev/null +++ b/service/src/test/kotlin/io/provenance/onboarding/frameworks/cee/P8eContractServiceSpec.kt @@ -0,0 +1,82 @@ +package io.provenance.onboarding.frameworks.cee + +import com.google.protobuf.Message +import io.kotest.core.annotation.Ignored +import io.kotest.core.spec.style.FunSpec +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.provenance.scope.contract.spec.P8eContract +import io.provenance.scope.contract.spec.P8eScopeSpecification +import io.provenance.scope.encryption.util.toJavaPublicKey +import io.provenance.scope.sdk.Client +import io.provenance.scope.sdk.Session +import io.provenance.scope.util.toUuid + +val SCOPE_UUID = "11141790-6de2-4d11-b3ad-9a1e16a8b3aa".toUuid() +val SESSION_UUID = "22141790-6de2-4d11-b3ad-9a1e16a8b3bb".toUuid() + +/* +There were intermittent failures on the mockk verify line starting with: + every { mockSessionBuilder.setSessionUuid(SESSION_UUID) + +The errors reported was: + Missing mocked calls inside everyMissing mocked calls inside every { ... } block: make sure the object inside the block is a mock + +It appears that separating the mockk() and the mockk() has prevented the intermittent failure. +If this intermittent error resurfaces, we can mark this test as @Ignored and either solve this issue or find an alternate solution. + */ +@Ignored +class P8eContractServiceTest : FunSpec({ + + val mockSessionBuilder = mockk() + val p8eContractService = P8eContractService() + val mockClient = mockk() + val mockSession = mockk() + + beforeTest { + } + + afterTest { + clearAllMocks() + } + + test("setupContract null scope") { + + val audienceSet = + setOf("0A41042C52EB79307D248B6CFB2A4AF562E403D4826BB0F540F024BBC3937528F6EB0B7FFA7A6585B751DBA25C173E658F3FEAAB0F05980C76E985CE0D55294F3600D7".toJavaPublicKey()) + val records: Map = emptyMap() + val contract = p8eContractService.getContract(TestP8eContract::class.qualifiedName!!) + + every { + mockClient.newSession( + contract, + TestP8eScopeSpecification()::class.java + ) + } answers { mockSessionBuilder } + every { mockSessionBuilder.addDataAccessKeys(audienceSet) } answers { mockSessionBuilder } + every { mockSessionBuilder.setScopeUuid(SCOPE_UUID) } answers { mockSessionBuilder } + every { mockSessionBuilder.build() } answers { mockSession } + every { mockSession.scopeUuid } answers { SCOPE_UUID } + every { mockSession.sessionUuid } answers { SESSION_UUID } + every { mockSessionBuilder.setSessionUuid(SESSION_UUID) } answers { mockSessionBuilder } + p8eContractService.setupContract( + mockClient, + contract, + records, + SCOPE_UUID, + SESSION_UUID, + null, + null, + TestP8eScopeSpecification::class.qualifiedName!!, + audienceSet + ) + verify(exactly = 1) { mockSessionBuilder.addDataAccessKeys(audienceSet) } + verify(exactly = 1) { mockSessionBuilder.build() } + } +}) + +class TestP8eScopeSpecification : P8eScopeSpecification() + +class TestP8eContract : P8eContract()