diff --git a/build.sbt b/build.sbt index 0df784ff60..9307ab6f20 100644 --- a/build.sbt +++ b/build.sbt @@ -61,7 +61,7 @@ lazy val V = new { // https://mvnrepository.com/artifact/io.circe/circe-core val circe = "0.14.7" - val tapir = "1.6.4" // scala-steward:off // TODO "1.10.5" + val tapir = "1.11.7" // scala-steward:off // TODO "1.10.5" val http4sBlaze = "0.23.15" // scala-steward:off // TODO "0.23.16" val typesafeConfig = "1.4.3" @@ -90,7 +90,7 @@ lazy val V = new { // [error] org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaSpec val vaultDriver = "6.2.0" - val micrometer = "1.11.11" + val micrometer = "1.13.6" val nimbusJwt = "9.37.3" val keycloak = "23.0.7" // scala-steward:off //TODO 24.0.3 // update all quay.io/keycloak/keycloak @@ -114,7 +114,10 @@ lazy val D = new { val tapirPrometheusMetrics: ModuleID = "com.softwaremill.sttp.tapir" %% "tapir-prometheus-metrics" % V.tapir val micrometer: ModuleID = "io.micrometer" % "micrometer-registry-prometheus" % V.micrometer val micrometerPrometheusRegistry = "io.micrometer" % "micrometer-core" % V.micrometer - val scalaUri = "io.lemonlabs" %% "scala-uri" % V.scalaUri + val scalaUri = Seq( + "io.lemonlabs" %% "scala-uri" % V.scalaUri exclude ("org.typelevel", "cats-parse_3"), // Exclude cats-parse to avoid deps conflict + "org.typelevel" % "cats-parse_3" % "1.0.0", // Replace with version 1.0.0 + ) val zioConfig: ModuleID = "dev.zio" %% "zio-config" % V.zioConfig val zioConfigMagnolia: ModuleID = "dev.zio" %% "zio-config-magnolia" % V.zioConfig @@ -194,13 +197,12 @@ lazy val D_Shared = new { D.zioConcurrent, D.zioHttp, D.zioKafka, - D.scalaUri, D.zioPrelude, // FIXME: split shared DB stuff as subproject? D.doobieHikari, D.doobiePostgres, D.zioCatsInterop, - ) + ) ++ D.scalaUri } lazy val D_SharedJson = new { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/notification/WebhookPublisher.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/notification/WebhookPublisher.scala index c4af9c4016..4c50697e03 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/notification/WebhookPublisher.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/notification/WebhookPublisher.scala @@ -101,7 +101,7 @@ class WebhookPublisher( _ <- ZIO.logDebug(s"Sending event: $event to HTTP webhook URL: $url.") url <- ZIO.fromEither(URL.decode(url)).orDie response <- Client - .request( + .streaming( Request( url = url, method = Method.POST, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala index 922593389a..a8f76d1656 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala @@ -1,7 +1,7 @@ package org.hyperledger.identus.agent.server import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton -import io.micrometer.prometheus.{PrometheusConfig, PrometheusMeterRegistry} +import io.micrometer.prometheusmetrics.{PrometheusConfig, PrometheusMeterRegistry} import org.hyperledger.identus.agent.server.config.AppConfig import org.hyperledger.identus.agent.server.http.ZioHttpClient import org.hyperledger.identus.agent.server.sql.Migrations as AgentMigrations diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/CustomServerInterceptors.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/CustomServerInterceptors.scala index 44ffa1cea8..8da72798ec 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/CustomServerInterceptors.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/CustomServerInterceptors.scala @@ -66,7 +66,7 @@ object CustomServerInterceptors { ) ) - def tapirDecodeFailureHandler: DecodeFailureHandler = (ctx: DecodeFailureContext) => { + def tapirDecodeFailureHandler[F[_]]: DecodeFailureHandler[F] = DecodeFailureHandler.pure[F](ctx => { /** As per the Tapir Decode Failures documentation: * @@ -100,7 +100,7 @@ object CustomServerInterceptors { ) ) case None => None - } + }) def http4sServiceErrorHandler: ServiceErrorHandler[Task] = (req: Request[Task]) => { case t: Throwable => val res = tapirDefectHandler( diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/ZHttp4sBlazeServer.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/ZHttp4sBlazeServer.scala index 1293185891..bb15ac4648 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/ZHttp4sBlazeServer.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/ZHttp4sBlazeServer.scala @@ -3,15 +3,13 @@ package org.hyperledger.identus.agent.server.http import io.circe.* import io.circe.generic.semiauto.* import io.circe.syntax.* -import io.micrometer.prometheus.PrometheusMeterRegistry +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry import org.http4s.* import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router -import org.hyperledger.identus.api.http.ErrorResponse import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.json.Json import org.hyperledger.identus.system.controller.SystemEndpoints -import sttp.tapir.* import sttp.tapir.model.ServerRequest import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter import sttp.tapir.server.http4s.Http4sServerOptions diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/system/controller/SystemControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/system/controller/SystemControllerImpl.scala index 3e8850bc68..611d099dce 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/system/controller/SystemControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/system/controller/SystemControllerImpl.scala @@ -1,6 +1,6 @@ package org.hyperledger.identus.system.controller -import io.micrometer.prometheus.PrometheusMeterRegistry +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry import org.hyperledger.identus.agent.server.buildinfo.BuildInfo import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} import org.hyperledger.identus.system.controller.http.HealthInfo diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/system/controller/SystemControllerTestTools.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/system/controller/SystemControllerTestTools.scala index 80f15ce237..f01acea610 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/system/controller/SystemControllerTestTools.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/system/controller/SystemControllerTestTools.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.system.controller -import io.micrometer.prometheus.{PrometheusConfig, PrometheusMeterRegistry} -import org.hyperledger.identus.agent.server.config.AppConfig +import io.micrometer.prometheusmetrics.{PrometheusConfig, PrometheusMeterRegistry} import org.hyperledger.identus.agent.server.http.CustomServerInterceptors import org.hyperledger.identus.agent.server.SystemModule.configLayer import org.hyperledger.identus.system.controller.http.HealthInfo diff --git a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt index 627281d15a..24f2e281ac 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt @@ -1,5 +1,6 @@ package steps.connectionless +import com.google.gson.JsonObject import interactions.Post import interactions.body import io.cucumber.java.en.* @@ -78,4 +79,94 @@ class ConnectionLessSteps { holder.remember("recordId", holderIssueCredentialRecord.recordId) holder.remember("thid", holderIssueCredentialRecord.thid) } + + @When("{actor} creates a OOB Invitation request for JWT proof presentation") + fun verifierCreatesARequestForJwtProofPresentationOfferInvitation(verifier: Actor) { + val presentationRequest = RequestPresentationInput( + goalCode = "present-vp", + goal = "Request proof of vaccine", + options = Options( + challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", + domain = "https://example-verifier.com", + ), + proofs = listOf( + ProofRequestAux( + schemaId = "https://schema.org/Person", + trustIssuers = listOf("did:web:atalaprism.io/users/testUser"), + ), + ), + ) + + verifier.attemptsTo( + Post.to("/present-proof/presentations/invitation").body(presentationRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + val presentationStatus = SerenityRest.lastResponse().get() + + verifier.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + Ensure.that(presentationStatus.status).isEqualTo(PresentationStatus.Status.INVITATION_GENERATED), + Ensure.that(presentationStatus.role).isEqualTo(PresentationStatus.Role.VERIFIER), + ) + + verifier.remember("presentationStatus", presentationStatus) + verifier.remember("thid", presentationStatus.thid) + } + + @And("{actor} accepts the OOB invitation request for JWT proof presentation from {actor}") + fun holderAcceptsJwtProofPresentationOfferInvitation(holder: Actor, verifier: Actor) { + val verifierPresentationStatusRecord = verifier.recall("presentationStatus") + holder.attemptsTo( + Post.to("/present-proof/presentations/accept-invitation") + .with { + it.body( + AcceptRequestPresentationInvitation( + verifierPresentationStatusRecord.invitation?.invitationUrl?.split("=")?.getOrNull(1) + ?: throw IllegalStateException("Invalid invitation URL format"), + ), + ) + }, + ) + val holderPresentationStatusRecord = SerenityRest.lastResponse().get() + + holder.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), + Ensure.that(holderPresentationStatusRecord.status).isEqualTo(PresentationStatus.Status.REQUEST_RECEIVED), + Ensure.that(holderPresentationStatusRecord.role).isEqualTo(PresentationStatus.Role.PROVER), + ) + holder.remember("recordId", holderPresentationStatusRecord.presentationId) + holder.remember("thid", holderPresentationStatusRecord.thid) + } + + @When("{actor} creates a OOB Invitation request for sd-jwt proof presentation requesting [{}] claims") + fun verifierCreatesARequestForSdJwtProofPresentationInvitation(verifier: Actor, keys: String) { + val claims = JsonObject() + for (key in keys.split(",")) { + claims.addProperty(key, "{}") + } + val presentationRequest = RequestPresentationInput( + options = Options( + challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf", + domain = "https://example-verifier.com", + ), + proofs = listOf(), + credentialFormat = "SDJWT", + claims = claims, + ) + + verifier.attemptsTo( + Post.to("/present-proof/presentations/invitation").body(presentationRequest), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + ) + val presentationStatus = SerenityRest.lastResponse().get() + + verifier.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), + Ensure.that(presentationStatus.status).isEqualTo(PresentationStatus.Status.INVITATION_GENERATED), + Ensure.that(presentationStatus.role).isEqualTo(PresentationStatus.Role.VERIFIER), + ) + + verifier.remember("presentationStatus", presentationStatus) + verifier.remember("thid", presentationStatus.thid) + } } diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature index 5b0f46564c..43612873d6 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature @@ -25,3 +25,10 @@ Feature: Present Proof Protocol And Holder rejects the proof Then Holder sees the proof is rejected + Scenario: Connectionless Verification Holder presents jwt credential proof to verifier + Given Holder has a jwt issued credential from Issuer + When Verifier creates a OOB Invitation request for JWT proof presentation + And Holder accepts the OOB invitation request for JWT proof presentation from Verifier + And Holder receives the presentation proof request + And Holder makes the jwt presentation of the proof + Then Verifier has the proof verified \ No newline at end of file diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature index e5d273bf37..96e8f4e961 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature @@ -25,6 +25,18 @@ Feature: Present SD-JWT Proof Protocol | Verifier | | Issuer | + Scenario Outline: Holder presents sd-jwt proof to + Given Holder has a sd-jwt issued credential from Issuer + When creates a OOB Invitation request for sd-jwt proof presentation requesting [firstName] claims + And Holder accepts the OOB invitation request for JWT proof presentation from + And Holder receives the presentation proof request + And Holder makes the sd-jwt presentation of the proof disclosing [firstName] claims + Then has the proof verified + Examples: + | verifier | + | Verifier | + | Issuer | + # Scenario: Holder presents sd-jwt proof with different claims from requested # Given Verifier and Holder have an existing connection # And Holder has a bound sd-jwt issued credential from Issuer