From 73b0fb2f5249bb09cf02e34028952e1fc51c284b Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Mon, 20 Jan 2025 19:33:04 +0700 Subject: [PATCH 1/4] test: AnonCreds e2e tests (#1502) Signed-off-by: Yurii Shynbuiev --- .../controller/IssueControllerImpl.scala | 11 +- .../CreateIssueCredentialRecordRequest.scala | 19 +--- infrastructure/local/.env | 2 +- .../single-tenant-testing-stack/.env | 2 +- tests/integration-tests/README.md | 8 +- tests/integration-tests/build.gradle.kts | 2 +- .../common/CreateCredentialOfferAPIVersion.kt | 101 +++++++++++++++++- .../test/kotlin/common/CredentialClaims.kt | 7 ++ .../kotlin/steps/credentials/AnoncredSteps.kt | 37 +++++++ .../steps/credentials/CredentialSteps.kt | 7 +- .../anoncred/issuance-refactoring.feature | 23 ++++ 11 files changed, 190 insertions(+), 29 deletions(-) create mode 100644 tests/integration-tests/src/test/resources/features/credential/anoncred/issuance-refactoring.feature diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala index 3e8a35505c..b293a3b5bb 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala @@ -176,7 +176,11 @@ class IssueControllerImpl( for { issuingDID <- getIssuingDIDFromAnonCredsProperties(request) credentialDefinitionGUID <- ZIO - .fromOption(request.credentialDefinitionId) + .fromOption( + request.anoncredsVcPropertiesV1 + .map(_.credentialDefinitionId) + .orElse(request.credentialDefinitionId) + ) .mapError(_ => ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId")) ) @@ -219,6 +223,9 @@ class IssueControllerImpl( claims <- ZIO .fromOption(request.anoncredsVcPropertiesV1.map(_.claims).orElse(request.claims)) .orElseFail(ErrorResponse.badRequest(detail = Some("Missing request parameter: claims"))) + validityPeriod = request.anoncredsVcPropertiesV1 + .flatMap(_.validityPeriod) + .orElse(request.validityPeriod) record <- credentialService .createAnonCredsIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, @@ -227,7 +234,7 @@ class IssueControllerImpl( credentialDefinitionGUID = credentialDefinitionGUID, credentialDefinitionId = credentialDefinitionId, claims = claims, - validityPeriod = request.validityPeriod, + validityPeriod = validityPeriod, automaticIssuance = request.automaticIssuance.orElse(Some(true)), goalCode = offerContext.goalCode, goal = offerContext.goal, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala index 5f11d2080b..3a28256d9f 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala @@ -192,15 +192,15 @@ case class AnonCredsVCPropertiesV1( @description(annotations.issuingDID.description) @encodedExample(annotations.issuingDID.example) issuingDID: String, - @description(AnonCredsVCPropertiesV1.annotations.schemaId.description) - @encodedExample(AnonCredsVCPropertiesV1.annotations.schemaId.example) - schemaId: String, @description(AnonCredsVCPropertiesV1.annotations.credentialDefinitionId.description) @encodedExample(AnonCredsVCPropertiesV1.annotations.credentialDefinitionId.example) - credentialDefinitionId: String, + credentialDefinitionId: UUID, @description(AnonCredsVCPropertiesV1.annotations.claims.description) @encodedExample(AnonCredsVCPropertiesV1.annotations.claims.example) - claims: zio.json.ast.Json + claims: zio.json.ast.Json, + @description(annotations.validityPeriod.description) + @encodedExample(annotations.validityPeriod.example) + validityPeriod: Option[Double] ) object AnonCredsVCPropertiesV1 { @@ -209,15 +209,6 @@ object AnonCredsVCPropertiesV1 { given decoder: JsonDecoder[AnonCredsVCPropertiesV1] = DeriveJsonDecoder.gen object annotations { - object schemaId - extends Annotation[String]( - description = """ - |The URL or DIDURL pointing to the AnonCreds schema that will be used for this offer. - |When dereferenced, the returned content should be a JSON schema compliant with the '[AnonCreds v1.0 schema](https://hyperledger.github.io/anoncreds-spec/#term:schema)' version of the specification. - |""".stripMargin, - example = - "https://agent-host.com/cloud-agent/schema-registry/schemas/d9569cec-c81e-4779-aa86-0d5994d82676/schema" - ) object credentialDefinitionId extends Annotation[UUID]( description = """ diff --git a/infrastructure/local/.env b/infrastructure/local/.env index 61994ac146..f65ee0066a 100644 --- a/infrastructure/local/.env +++ b/infrastructure/local/.env @@ -1,3 +1,3 @@ AGENT_VERSION=1.40.0 -PRISM_NODE_VERSION=2.3.0 +PRISM_NODE_VERSION=2.5.0 VAULT_DEV_ROOT_TOKEN_ID=root diff --git a/infrastructure/single-tenant-testing-stack/.env b/infrastructure/single-tenant-testing-stack/.env index 7e727716b8..06e30ab885 100644 --- a/infrastructure/single-tenant-testing-stack/.env +++ b/infrastructure/single-tenant-testing-stack/.env @@ -1,3 +1,3 @@ AGENT_VERSION=1.25.0 -PRISM_NODE_VERSION=2.3.0 +PRISM_NODE_VERSION=2.5.0 VAULT_DEV_ROOT_TOKEN_ID=root diff --git a/tests/integration-tests/README.md b/tests/integration-tests/README.md index 4b0e925bcd..a49b3fcd26 100644 --- a/tests/integration-tests/README.md +++ b/tests/integration-tests/README.md @@ -397,7 +397,7 @@ The following variables must be set before running the tests: * `AGENT_VERSION`: version of the ICA docker image to use. ```shell -TESTS_CONFIG=/configs/basic.conf PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew test +TESTS_CONFIG=/configs/basic.conf PRISM_NODE_VERSION=2.5.0 AGENT_VERSION=1.36.1 ./gradlew test ``` > Please note: there is no need to pass environment variables if you're using already running agents. @@ -414,13 +414,13 @@ To simplify the execution, each configuration file creates a new `gradle` task. It's possible to execute the configuration file as ```shell -PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew test_basic +PRISM_NODE_VERSION=2.5.0 AGENT_VERSION=1.36.1 ./gradlew test_basic ``` Also, it's possible to execute the integration tests to all configurations files. The task is named `regression`, it should take a long time to execute. ```shell -PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew regression +PRISM_NODE_VERSION=2.5.0 AGENT_VERSION=1.36.1 ./gradlew regression ``` #### Regression report @@ -432,7 +432,7 @@ To run all scenarios, even if there's a failure, it's required to add `--continu Example ```bash -AGENT_VERSION=v1.36.1 PRISM_NODE_VERSION=v2.3.0 ./gradlew regression --continue +AGENT_VERSION=v1.36.1 PRISM_NODE_VERSION=v2.5.0 ./gradlew regression --continue ``` Each `context` is based on the configuration used for the current execution and will be displayed in the diff --git a/tests/integration-tests/build.gradle.kts b/tests/integration-tests/build.gradle.kts index 2c4973cc9e..7ed76b81cd 100644 --- a/tests/integration-tests/build.gradle.kts +++ b/tests/integration-tests/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { testImplementation("io.ktor:ktor-server-netty:2.3.0") testImplementation("io.ktor:ktor-client-apache:2.3.0") // RestAPI client - testImplementation("org.hyperledger.identus:cloud-agent-client-kotlin:1.40.1-8363d1e") + testImplementation("org.hyperledger.identus:cloud-agent-client-kotlin:1.40.1-1766228") // Test helpers library testImplementation("io.iohk.atala:atala-automation:0.4.0") // Hoplite for configuration diff --git a/tests/integration-tests/src/test/kotlin/common/CreateCredentialOfferAPIVersion.kt b/tests/integration-tests/src/test/kotlin/common/CreateCredentialOfferAPIVersion.kt index a081747eca..a9e2e202ad 100644 --- a/tests/integration-tests/src/test/kotlin/common/CreateCredentialOfferAPIVersion.kt +++ b/tests/integration-tests/src/test/kotlin/common/CreateCredentialOfferAPIVersion.kt @@ -5,7 +5,7 @@ import java.util.UUID enum class CreateCredentialOfferAPIVersion { V0 { - override fun buildCredentialOfferRequest( + override fun buildJWTCredentialOfferRequest( credentialType: CredentialType, did: String, assertionKey: String, @@ -23,10 +23,46 @@ enum class CreateCredentialOfferAPIVersion { credentialFormat = credentialType.format, automaticIssuance = false, ) + + override fun buildSDJWTCredentialOfferRequest( + credentialType: CredentialType, + did: String, + assertionKey: String, + schemaUrl: String?, + claims: Map, + connectionId: UUID, + validityPeriod: Double?, + ): CreateIssueCredentialRecordRequest = CreateIssueCredentialRecordRequest( + schemaId = schemaUrl, + claims = claims, + issuingDID = did, + issuingKid = assertionKey, + connectionId = connectionId, + validityPeriod = validityPeriod ?: 3600.0, + credentialFormat = credentialType.format, + automaticIssuance = false, + ) + + override fun buildAnonCredsCredentialOfferRequest( + credentialType: CredentialType, + did: String, + credentialDefinitionId: UUID, + claims: Map, + connectionId: UUID, + validityPeriod: Double?, + ) = CreateIssueCredentialRecordRequest( + credentialDefinitionId = credentialDefinitionId, + claims = claims, + issuingDID = did, + connectionId = connectionId, + validityPeriod = validityPeriod ?: 3600.0, + credentialFormat = credentialType.format, + automaticIssuance = false, + ) }, V1 { - override fun buildCredentialOfferRequest( + override fun buildJWTCredentialOfferRequest( credentialType: CredentialType, did: String, assertionKey: String, @@ -53,8 +89,25 @@ enum class CreateCredentialOfferAPIVersion { validityPeriod = validityPeriod ?: 3600.0, ) } else { - null + throw IllegalArgumentException("Unsupported credential type: $credentialType") }, + ) + + override fun buildSDJWTCredentialOfferRequest( + credentialType: CredentialType, + did: String, + assertionKey: String, + schemaUrl: String?, + claims: Map, + connectionId: UUID, + validityPeriod: Double?, + ): CreateIssueCredentialRecordRequest = CreateIssueCredentialRecordRequest( + issuingKid = assertionKey, + connectionId = connectionId, + credentialFormat = credentialType.format, + automaticIssuance = false, + claims = null, + issuingDID = null, sdJwtVcPropertiesV1 = if (credentialType == CredentialType.SD_JWT_VCDM_1_1) { SDJWTVCPropertiesV1( credentialSchema = CredentialSchemaRef( @@ -67,13 +120,42 @@ enum class CreateCredentialOfferAPIVersion { validityPeriod = validityPeriod ?: 3600.0, ) } else { - null + throw IllegalArgumentException("Unsupported credential type: $credentialType") }, ) + + override fun buildAnonCredsCredentialOfferRequest( + credentialType: CredentialType, + did: String, + credentialDefinitionId: UUID, + claims: Map, + connectionId: UUID, + validityPeriod: Double?, + ) = CreateIssueCredentialRecordRequest( + connectionId = connectionId, + credentialFormat = credentialType.format, + automaticIssuance = false, + anoncredsVcPropertiesV1 = AnonCredsVCPropertiesV1( + issuingDID = did, + credentialDefinitionId = credentialDefinitionId, + claims = claims, + validityPeriod = validityPeriod ?: 3600.0, + ), + ) }, ; - abstract fun buildCredentialOfferRequest( + abstract fun buildJWTCredentialOfferRequest( + credentialType: CredentialType, + did: String, + assertionKey: String, + schemaUrl: String?, + claims: Map, + connectionId: UUID, + validityPeriod: Double? = null, + ): CreateIssueCredentialRecordRequest + + abstract fun buildSDJWTCredentialOfferRequest( credentialType: CredentialType, did: String, assertionKey: String, @@ -82,4 +164,13 @@ enum class CreateCredentialOfferAPIVersion { connectionId: UUID, validityPeriod: Double? = null, ): CreateIssueCredentialRecordRequest + + abstract fun buildAnonCredsCredentialOfferRequest( + credentialType: CredentialType, + did: String, + credentialDefinitionId: UUID, + claims: Map, + connectionId: UUID, + validityPeriod: Double? = null, + ): CreateIssueCredentialRecordRequest } diff --git a/tests/integration-tests/src/test/kotlin/common/CredentialClaims.kt b/tests/integration-tests/src/test/kotlin/common/CredentialClaims.kt index dc11f219ac..c6c44ba6e5 100644 --- a/tests/integration-tests/src/test/kotlin/common/CredentialClaims.kt +++ b/tests/integration-tests/src/test/kotlin/common/CredentialClaims.kt @@ -13,6 +13,13 @@ enum class CredentialClaims { "lastName" to "Doe", ) }, + ANONCREDS_STUDENT_CLAIMS { + override val claims: Map = linkedMapOf( + "name" to "Bob", + "age" to "21", + "sex" to "M", + ) + }, ; abstract val claims: Map diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt index 2cddddb2b6..e8c2b71286 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt @@ -1,5 +1,7 @@ package steps.credentials +import common.CreateCredentialOfferAPIVersion +import common.CredentialType import interactions.Post import interactions.body import io.cucumber.java.en.When @@ -47,4 +49,39 @@ class AnoncredSteps { issuer.remember("thid", credentialRecord.thid) holder.remember("thid", credentialRecord.thid) } + + @When("{actor} sends the prepared anoncreds credential offer to {actor}") + fun issuerSendsThePreparedAnonCredsOfferToHolder(issuer: Actor, holder: Actor) { + val api = issuer.recall("currentAPI") + val credentialType = issuer.recall("currentCredentialType") + val did = issuer.recall("currentDID") + val claims = issuer.recall>("currentClaims") + val connectionId = issuer.recall("connection-with-${holder.name}").connectionId + val credentialDefinitionId = issuer.recall("anoncredsCredentialDefinition").guid + + val request = api.buildAnonCredsCredentialOfferRequest(credentialType, did, credentialDefinitionId, claims, connectionId) + + val credentialOfferRequest = CreateIssueCredentialRecordRequest( + credentialDefinitionId = issuer.recall("anoncredsCredentialDefinition").guid, + claims = linkedMapOf( + "name" to "Bob", + "age" to "21", + "sex" to "M", + ), + issuingDID = issuer.recall("shortFormDid"), + connectionId = issuer.recall("connection-with-${holder.name}").connectionId, + validityPeriod = 3600.0, + credentialFormat = "AnonCreds", + automaticIssuance = false, + ) + + issuer.attemptsTo( + Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + + val credentialRecord = SerenityRest.lastResponse().get() + issuer.remember("thid", credentialRecord.thid) + holder.remember("thid", credentialRecord.thid) + } } diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt index 1ad3fa1c07..4692c37a2f 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt @@ -78,6 +78,7 @@ class CredentialSteps { val api = issuer.recall("currentAPI") val credentialType = issuer.recall("currentCredentialType") val did = issuer.recall("currentDID") + val assertionKey = issuer.recall("currentAssertionKey") val schema = issuer.recall("currentSchema") val schemaGuid = issuer.recall(schema.name) @@ -89,7 +90,11 @@ class CredentialSteps { "${issuer.recall("baseUrl")}/schema-registry/schemas/$it" } - val credentialOfferRequest = api.buildCredentialOfferRequest(credentialType, did, assertionKey, schemaUrl!!, claims, connectionId) + val credentialOfferRequest = when (credentialType) { + CredentialType.JWT_VCDM_1_1 -> api.buildJWTCredentialOfferRequest(credentialType, did, assertionKey, schemaUrl!!, claims, connectionId) + CredentialType.SD_JWT_VCDM_1_1 -> api.buildSDJWTCredentialOfferRequest(credentialType, did, assertionKey, schemaUrl!!, claims, connectionId) + else -> throw IllegalArgumentException("Unsupported credential type: $credentialType") + } issuer.attemptsTo( Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest), diff --git a/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance-refactoring.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance-refactoring.feature new file mode 100644 index 0000000000..316ffba424 --- /dev/null +++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance-refactoring.feature @@ -0,0 +1,23 @@ +@anoncred @issuance @refactoring +Feature: Issue Anoncreds credential using different REST API request versions + + Background: + Given Issuer and Holder have an existing connection + And Issuer has a published DID for 'ANONCRED' + And Issuer has an anoncred schema definition + And Holder has an unpublished DID for 'ANONCRED' + + Scenario Outline: Issuing jwt credential using different API version + When Issuer prepares the credential in 'ANONCREDS_V1' format using the '' API + And Issuer prepares to use a 'short' form of DID with key id 'assertion-1' + And Issuer prepares the claims '' for the credential + And Issuer sends the prepared anoncreds credential offer to Holder + And Holder receives the credential offer + And Holder accepts anoncred credential offer + And Issuer issues the credential + Then Holder receives the issued credential + Examples: + | createCredentialOfferApiVersion | claims | + | V0 | ANONCREDS_STUDENT_CLAIMS | + | V1 | ANONCREDS_STUDENT_CLAIMS | + From d436bd193f7bd7fd21aa27e65015d78272811915 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Mon, 20 Jan 2025 20:40:20 +0700 Subject: [PATCH 2/4] ci: add conventionalcommits preset to the semantic-release (#1390) Signed-off-by: Yurii Shynbuiev --- release.config.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release.config.mjs b/release.config.mjs index 9b8a9923d4..4851a2295f 100644 --- a/release.config.mjs +++ b/release.config.mjs @@ -5,7 +5,9 @@ export default { { name: 'beta', prerelease: true } ], plugins: [ - '@semantic-release/commit-analyzer', + ['@semantic-release/commit-analyzer', { + "preset": "conventionalcommits" + }], ["@semantic-release/exec", { "prepareCmd": "echo ${nextRelease.version} > .release-version" }], From 0b8ea4bb67a3045a529f397224268eceedad9e02 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:11:56 +0000 Subject: [PATCH 3/4] build: sbt and plugins dependency update (#1393) Signed-off-by: Hyperledger Bot Co-authored-by: Hyperledger Bot Co-authored-by: Yurii Shynbuiev --- project/plugins.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f81f6ad876..f610b7852d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,8 +3,8 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.1") addSbtPlugin("com.github.sbt" % "sbt-release" % "1.4.0") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.0") -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.13") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.1") +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.14") addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.6") // In order to import proper version of com.google.protobuf.ByteString we need to add this dependency From b791ad2479692af33179ffe28be86497f40643b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:48:08 +0000 Subject: [PATCH 4/4] build: zio dependency updates (#1391) Signed-off-by: Hyperledger Bot Co-authored-by: Hyperledger Bot --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 968a0cbe20..05612d6d67 100644 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,7 @@ lazy val V = new { val munitZio = "0.2.0" // https://mvnrepository.com/artifact/dev.zio/zio - val zio = "2.1.9" + val zio = "2.1.11" val zioConfig = "4.0.2" val zioLogging = "2.3.1" val zioJson = "0.7.3" @@ -54,7 +54,7 @@ lazy val V = new { val zioCatsInterop = "3.3.0" // TODO "23.1.0.2" // https://mvnrepository.com/artifact/dev.zio/zio-interop-cats val zioMetricsConnector = "2.3.1" val zioMock = "1.0.0-RC12" - val zioKafka = "2.7.5" + val zioKafka = "2.8.2" val mockito = "3.2.18.0" val monocle = "3.2.0"