From 37a8f41d444b0dc8fea503949a761f6d40036642 Mon Sep 17 00:00:00 2001 From: Shailesh Patil Date: Fri, 10 Jan 2025 12:04:15 +0000 Subject: [PATCH 1/3] docs: api spec documentation (#1493) Signed-off-by: mineme0110 --- .../controller/http/IssueCredentialRecord.scala | 12 ++++++++---- .../controller/http/PresentationStatus.scala | 8 ++++++-- .../controller/http/RequestPresentationInput.scala | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/IssueCredentialRecord.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/IssueCredentialRecord.scala index b28d25e3d4..8caa773af5 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/IssueCredentialRecord.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/IssueCredentialRecord.scala @@ -262,15 +262,19 @@ object IssueCredentialRecord { object goalcode extends Annotation[String]( - description = - "A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message.", + description = """ + |A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message. + |The goalCode is optional and can be included when the credential offer originates from an invitation for connectionless issuance + |""".stripMargin, example = "issue-vc" ) object goal extends Annotation[String]( - description = - "A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message.", + description = """ + |A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message. + |The goal is optional and can be included when the credential offer originates from an invitation for connectionless issuance + |""".stripMargin, example = "To issue a Faber College Graduate credential" ) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala index 301b6c8321..04c542e60d 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala @@ -203,14 +203,18 @@ object PresentationStatus { object goalcode extends Annotation[String]( description = - "A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message.", + """A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message. + |The goalcode is optional and can be included when the presentation request originates from an invitation for connectionless proof request + |""".stripMargin, example = "present-vp" ) object goal extends Annotation[String]( description = - "A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message.", + """A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message. + |The goal is optional and can be included when the presentation request originates from an invitation for connectionless proof request + |""".stripMargin, example = "To verify a Peter College Graduate credential" ) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala index 695094e2e1..5575fab297 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala @@ -59,7 +59,7 @@ object RequestPresentationInput { extends Annotation[Option[String]]( description = """ | A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message. - | goalcode is optional and can be provided when the presentation request is from invitation for connectionless verification. + | goalcode is optional and can be included when the presentation request is from invitation for connectionless verification. |""".stripMargin, example = Some("present-vp") ) @@ -68,7 +68,7 @@ object RequestPresentationInput { extends Annotation[Option[String]]( description = """ | A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message. - | goal is optional and can be provided when the presentation request is from invitation for connectionless verification. + | goal is optional and can be included when the presentation request is from invitation for connectionless verification. |""".stripMargin, example = Some("Request proof of vaccine") ) From 54b7b5b77950f00d30f9bdade88c2ce8e9525bde Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Mon, 13 Jan 2025 17:13:38 +0000 Subject: [PATCH 2/3] feat: add feature flag to enable/disable support for Anoncred (backend job and API) (#1492) Add a configuration feature flag to enable or disable support for certain type of credentials like Anomcred Related with #1491 Signed-off-by: FabioPinheiro Signed-off-by: Allain Magyar Co-authored-by: Allain Magyar --- build.sbt | 12 +- .../src/main/resources/application.conf | 5 + .../identus/agent/server/MainApp.scala | 6 + .../agent/server/config/AppConfig.scala | 29 ++++ .../server/jobs/IssueBackgroundJobs.scala | 144 ++++++++++-------- .../server/jobs/PresentBackgroundJobs.scala | 32 ++-- .../identus/api/http/ErrorResponse.scala | 8 + .../controller/IssueControllerImpl.scala | 20 +++ .../PresentProofControllerImpl.scala | 22 +++ .../identus/mercury/model/Conversions.scala | 2 +- .../core/model/error/PresentationError.scala | 5 + .../jwt/VerifiablePresentationPayload.scala | 4 +- .../src/test/resources/containers/agent.yml | 1 + 13 files changed, 210 insertions(+), 80 deletions(-) diff --git a/build.sbt b/build.sbt index 5efc89ffd8..968a0cbe20 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ inThisBuild( // scalacOptions += "-Ysafe-init", // scalacOptions += "-Werror", // <=> "-Xfatal-warnings" scalacOptions += "-Dquill.macro.log=false", // disable quill macro logs // TODO https://github.com/zio/zio-protoquill/issues/470, - scalacOptions ++= Seq("-Xmax-inlines", "50") + scalacOptions ++= Seq("-Xmax-inlines", "50") // increase above 32 (https://github.com/circe/circe/issues/2162) ) ) @@ -104,7 +104,8 @@ lazy val D = new { val zioConcurrent: ModuleID = "dev.zio" %% "zio-concurrent" % V.zio val zioHttp: ModuleID = "dev.zio" %% "zio-http" % V.zioHttp val zioKafka: ModuleID = "dev.zio" %% "zio-kafka" % V.zioKafka excludeAll ( - ExclusionRule("dev.zio", "zio_3"), ExclusionRule("dev.zio", "zio-streams_3") + ExclusionRule("dev.zio", "zio_3"), + ExclusionRule("dev.zio", "zio-streams_3") ) val zioCatsInterop: ModuleID = "dev.zio" %% "zio-interop-cats" % V.zioCatsInterop val zioMetricsConnectorMicrometer: ModuleID = "dev.zio" %% "zio-metrics-connectors-micrometer" % V.zioMetricsConnector @@ -112,7 +113,10 @@ lazy val D = new { val micrometer: ModuleID = "io.micrometer" % "micrometer-registry-prometheus" % V.micrometer val micrometerPrometheusRegistry = "io.micrometer" % "micrometer-core" % V.micrometer val scalaUri = Seq( - "io.lemonlabs" %% "scala-uri" % V.scalaUri exclude ("org.typelevel", "cats-parse_3"), // Exclude cats-parse to avoid deps conflict + "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 ) @@ -905,7 +909,7 @@ lazy val cloudAgentServer = project Docker / maintainer := "atala-coredid@iohk.io", Docker / dockerUsername := Some("hyperledger"), // https://github.com/hyperledger Docker / dockerRepository := Some("ghcr.io"), - dockerExposedPorts := Seq(8080, 8085, 8090), + dockerExposedPorts := Seq(8085, 8090), // Official docker image for openjdk 21 with curl and bash dockerBaseImage := "openjdk:21-jdk", buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion), diff --git a/cloud-agent/service/server/src/main/resources/application.conf b/cloud-agent/service/server/src/main/resources/application.conf index 6b15792004..a9a01086db 100644 --- a/cloud-agent/service/server/src/main/resources/application.conf +++ b/cloud-agent/service/server/src/main/resources/application.conf @@ -9,6 +9,11 @@ prismNode { } } +featureFlag { + enableAnoncred = false + enableAnoncred = ${?ENABLE_ANONCRED} +} + pollux { database { host = "localhost" 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 a8f76d1656..6d57b988a6 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 @@ -149,6 +149,12 @@ object MainApp extends ZIOAppDefault { .ignore appConfig <- ZIO.service[AppConfig].provide(SystemModule.configLayer) + flags = appConfig.featureFlag + _ <- Console.printLine(s"""### Feature Flags: #### + | - Support for the credential type JWT VC is ${if (flags.enableJWT) "ENABLED" else "DISABLED"} + | - Support for the credential type SD JWT VC is ${if (flags.enableSDJWT) "ENABLED" else "DISABLED"} + | - Support for the credential type Anoncred is ${if (flags.enableAnoncred) "ENABLED" else "DISABLED"} + |""") // these services are added to any DID document by default when they are created. defaultDidDocumentServices = Set( DidDocumentService( diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala index 89b01e3b66..b85ce08c08 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala @@ -7,6 +7,7 @@ import org.hyperledger.identus.shared.db.DbConfig import org.hyperledger.identus.shared.messaging.MessagingServiceConfig import zio.config.magnolia.* import zio.Config +import zio.ZIO import java.net.URL import java.time.Duration @@ -17,6 +18,7 @@ final case class AppConfig( agent: AgentConfig, connect: ConnectConfig, prismNode: PrismNodeConfig, + featureFlag: FeatureFlagConfig ) { def validate: Either[String, Unit] = for { @@ -39,6 +41,33 @@ object AppConfig { } +final case class FeatureFlagConfig( + enableAnoncred: Boolean +) { + def enableJWT: Boolean = true // Hardcoded for now // TODO FeatureNotImplemented + def enableSDJWT: Boolean = true // Hardcoded for now // TODO FeatureNotImplemented + + def ifJWTIsEnabled[R, E, A](program: ZIO[R, E, A]) = + if (enableJWT) program else ZIO.logWarning(FeatureFlagConfig.messageIfDisableForJWT) + def ifSDJWTIsEnabled[R, E, A](program: ZIO[R, E, A]) = + if (enableSDJWT) program else ZIO.logWarning(FeatureFlagConfig.messageIfDisableForSDJWT) + def ifAnoncredIsEnabled[R, E, A](program: ZIO[R, E, A]) = + if (enableAnoncred) program else ZIO.logWarning(FeatureFlagConfig.messageIfDisableForAnoncred) + + def ifJWTIsDisable[R, E, A](program: ZIO[R, E, A]) = + if (!enableJWT) ZIO.logWarning(FeatureFlagConfig.messageIfDisableForJWT) *> program else ZIO.unit + def ifSDJWTIsDisable[R, E, A](program: ZIO[R, E, A]) = + if (!enableSDJWT) ZIO.logWarning(FeatureFlagConfig.messageIfDisableForSDJWT) *> program else ZIO.unit + def ifAnoncredIsDisable[R, E, A](program: ZIO[R, E, A]) = + if (!enableAnoncred) ZIO.logWarning(FeatureFlagConfig.messageIfDisableForAnoncred) *> program else ZIO.unit +} + +object FeatureFlagConfig { + def messageIfDisableForJWT = "Feature Disabled: Credential format JWT VC" + def messageIfDisableForSDJWT = "Feature Disabled: Credential format SD JWT VC" + def messageIfDisableForAnoncred = "Feature Disabled: Credential format Anoncred" +} + final case class VaultConfig( address: String, token: Option[String], diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala index 68c1c1d5d9..4f32b271a5 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala @@ -273,17 +273,20 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _ ) => val holderPendingToGeneratedFlow = for { - walletAccessContext <- ZIO - .fromOption(offer.to) - .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) - .flatMap(buildWalletAccessContextLayer) - result <- for { - credentialService <- ZIO.service[CredentialService] - _ <- credentialService - .generateJWTCredentialRequest(id) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield () - } yield result + flags <- ZIO.service[AppConfig].map(_.featureFlag) + ret <- flags.ifJWTIsEnabled( + for { + walletAccessContext <- ZIO + .fromOption(offer.to) + .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) + .flatMap(buildWalletAccessContextLayer) + credentialService <- ZIO.service[CredentialService] + _ <- credentialService + .generateJWTCredentialRequest(id) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield () + ) + } yield ret holderPendingToGeneratedFlow @@ HolderPendingToGeneratedSuccess.trackSuccess @@ HolderPendingToGeneratedFailed.trackError @@ -319,18 +322,20 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _ ) => val holderPendingToGeneratedFlow = for { - walletAccessContext <- ZIO - .fromOption(offer.to) - .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) - .flatMap(buildWalletAccessContextLayer) - result <- for { - credentialService <- ZIO.service[CredentialService] - _ <- credentialService - .generateSDJWTCredentialRequest(id) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield () - } yield result - + flags <- ZIO.service[AppConfig].map(_.featureFlag) + ret <- flags.ifSDJWTIsEnabled( + for { + walletAccessContext <- ZIO + .fromOption(offer.to) + .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) + .flatMap(buildWalletAccessContextLayer) + credentialService <- ZIO.service[CredentialService] + _ <- credentialService + .generateSDJWTCredentialRequest(id) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield () + ) + } yield ret holderPendingToGeneratedFlow @@ HolderPendingToGeneratedSuccess.trackSuccess @@ HolderPendingToGeneratedFailed.trackError @@ HolderPendingToGeneratedAll @@ -365,19 +370,20 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _ ) => val holderPendingToGeneratedFlow = for { - walletAccessContext <- ZIO - .fromOption(offer.to) - .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) - .flatMap(buildWalletAccessContextLayer) - - result <- for { - credentialService <- ZIO.service[CredentialService] - _ <- credentialService - .generateAnonCredsCredentialRequest(id) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield () - } yield result - + flags <- ZIO.service[AppConfig].map(_.featureFlag) + ret <- flags.ifAnoncredIsEnabled( + for { + walletAccessContext <- ZIO + .fromOption(offer.to) + .mapError(_ => CredentialServiceError.CredentialOfferMissingField(id.value, "recipient")) + .flatMap(buildWalletAccessContextLayer) + credentialService <- ZIO.service[CredentialService] + _ <- credentialService + .generateAnonCredsCredentialRequest(id) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield () + ) + } yield ret holderPendingToGeneratedFlow @@ HolderPendingToGeneratedSuccess.trackSuccess @@ HolderPendingToGeneratedFailed.trackError @@ HolderPendingToGeneratedAll @@ -517,15 +523,18 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { // Set ProtocolState to CredentialGenerated // TODO Move all logic to service val issuerPendingToGeneratedFlow = for { - walletAccessContext <- buildWalletAccessContextLayer(issue.from) - result <- (for { - credentialService <- ZIO.service[CredentialService] - config <- ZIO.service[AppConfig] - _ <- credentialService - .generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl.toExternalForm) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield ()).mapError(e => (walletAccessContext, e)) - } yield result + config <- ZIO.service[AppConfig] + ret <- config.featureFlag.ifJWTIsEnabled( + for { + walletAccessContext <- buildWalletAccessContextLayer(issue.from) + credentialService <- ZIO.service[CredentialService] + _ <- credentialService + .generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl.toExternalForm) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + .mapError(e => (walletAccessContext, e)) + } yield () + ) + } yield ret issuerPendingToGeneratedFlow @@ IssuerPendingToGeneratedSuccess.trackSuccess @@ IssuerPendingToGeneratedFailed.trackError @@ -565,15 +574,20 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { // Set ProtocolState to CredentialGenerated // TODO Move all logic to service val issuerPendingToGeneratedFlow = for { - walletAccessContext <- buildWalletAccessContextLayer(issue.from) - result <- (for { - credentialService <- ZIO.service[CredentialService] - config <- ZIO.service[AppConfig] - _ <- credentialService - .generateSDJWTCredential(id, config.pollux.credentialSdJwtExpirationTime) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield ()).mapError(e => (walletAccessContext, e)) - } yield result + config <- ZIO.service[AppConfig] + ret <- config.featureFlag.ifSDJWTIsEnabled( + for { + walletAccessContext <- buildWalletAccessContextLayer(issue.from) + result <- (for { + credentialService <- ZIO.service[CredentialService] + config <- ZIO.service[AppConfig] + _ <- credentialService + .generateSDJWTCredential(id, config.pollux.credentialSdJwtExpirationTime) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield ()).mapError(e => (walletAccessContext, e)) + } yield result + ) + } yield ret issuerPendingToGeneratedFlow @@ IssuerPendingToGeneratedSuccess.trackSuccess @@ IssuerPendingToGeneratedFailed.trackError @@ -609,14 +623,20 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, ) => val issuerPendingToGeneratedFlow = for { - walletAccessContext <- buildWalletAccessContextLayer(issue.from) - result <- (for { - credentialService <- ZIO.service[CredentialService] - _ <- credentialService - .generateAnonCredsCredential(id) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield ()).mapError(e => (walletAccessContext, e)) - } yield result + config <- ZIO.service[AppConfig] + ret <- + config.featureFlag.ifAnoncredIsEnabled( + for { + walletAccessContext <- buildWalletAccessContextLayer(issue.from) + result <- (for { + credentialService <- ZIO.service[CredentialService] + _ <- credentialService + .generateAnonCredsCredential(id) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + } yield ()).mapError(e => (walletAccessContext, e)) + } yield result + ) + } yield ret issuerPendingToGeneratedFlow @@ IssuerPendingToGeneratedSuccess.trackSuccess @@ IssuerPendingToGeneratedFailed.trackError diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index c79d84121b..df2458cf3f 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -658,16 +658,21 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { requestPresentation: RequestPresentation, credentialFormat: CredentialFormat ): ZIO[ - CredentialService & DIDService & COMMON_RESOURCES, + AppConfig & CredentialService & DIDService & COMMON_RESOURCES, ERROR, Unit ] = { - val result = - credentialFormat match { - case CredentialFormat.JWT => handle_JWT_VC(id, credentialsToUse, requestPresentation) - case CredentialFormat.SDJWT => handle_SD_JWT_VC(id, credentialsToUse, requestPresentation) - case CredentialFormat.AnonCreds => handleAnoncred(id, maybeCredentialsToUseJson, requestPresentation) + val result = for { + flags <- ZIO.service[AppConfig].map(_.featureFlag) + ret <- credentialFormat match { + case CredentialFormat.JWT => + flags.ifJWTIsEnabled(handle_JWT_VC(id, credentialsToUse, requestPresentation)) + case CredentialFormat.SDJWT => + flags.ifSDJWTIsEnabled(handle_SD_JWT_VC(id, credentialsToUse, requestPresentation)) + case CredentialFormat.AnonCreds => + flags.ifSDJWTIsEnabled(handleAnoncred(id, maybeCredentialsToUseJson, requestPresentation)) } + } yield ret result @@ metric } @@ -1067,12 +1072,17 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { Failure, Unit ] = { - val result = - credentialFormat match { - case CredentialFormat.JWT => handleJWT(id, requestPresentation, presentation, invitation) - case CredentialFormat.SDJWT => handleSDJWT(id, presentation, invitation) - case CredentialFormat.AnonCreds => handleAnoncred(id, requestPresentation, presentation, invitation) + val result = for { + flags <- ZIO.service[AppConfig].map(_.featureFlag) + ret <- credentialFormat match { + case CredentialFormat.JWT => + flags.ifJWTIsEnabled(handleJWT(id, requestPresentation, presentation, invitation)) + case CredentialFormat.SDJWT => + flags.ifSDJWTIsEnabled(handleSDJWT(id, presentation, invitation)) + case CredentialFormat.AnonCreds => + flags.ifAnoncredIsEnabled(handleAnoncred(id, requestPresentation, presentation, invitation)) } + } yield ret result @@ metric } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/api/http/ErrorResponse.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/api/http/ErrorResponse.scala index 219e8fa58f..314cf60c37 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/api/http/ErrorResponse.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/api/http/ErrorResponse.scala @@ -111,6 +111,14 @@ object ErrorResponse { detail = detail ) + def badRequestDisabled(detail: String) = + ErrorResponse( + StatusCode.BadRequest.code, + `type` = "BadRequest", + title = "BadRequest_FeatureDisabled", + detail = Some(detail) + ) + def unprocessableEntity(title: String = "UnprocessableEntity", detail: Option[String] = None) = ErrorResponse( StatusCode.UnprocessableEntity.code, 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 c594f9d791..edc4d051bf 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 @@ -1,6 +1,7 @@ package org.hyperledger.identus.issue.controller import org.hyperledger.identus.agent.server.config.AppConfig +import org.hyperledger.identus.agent.server.config.FeatureFlagConfig import org.hyperledger.identus.agent.server.ControllerHelper import org.hyperledger.identus.agent.walletapi.model.PublicationState import org.hyperledger.identus.agent.walletapi.model.PublicationState.{Created, PublicationPending, Published} @@ -46,6 +47,22 @@ class IssueControllerImpl( expirationDuration: Option[Duration] ) + private def checkFeatureFlag(credentialFormat: CredentialFormat) = for { + _ <- credentialFormat match // Fail if feature is disabled + case JWT => + appConfig.featureFlag.ifJWTIsDisable( + ZIO.fail(ErrorResponse.badRequestDisabled(FeatureFlagConfig.messageIfDisableForJWT)) + ) + case SDJWT => + appConfig.featureFlag.ifSDJWTIsDisable( + ZIO.fail(ErrorResponse.badRequestDisabled(FeatureFlagConfig.messageIfDisableForSDJWT)) + ) + case AnonCreds => + appConfig.featureFlag.ifAnoncredIsDisable( + ZIO.fail(ErrorResponse.badRequestDisabled(FeatureFlagConfig.messageIfDisableForAnoncred)) + ) + } yield () + private def createCredentialOfferRecord( request: CreateIssueCredentialRecordRequest, offerContext: OfferContext @@ -59,6 +76,7 @@ class IssueControllerImpl( credentialFormat <- ZIO.succeed( request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT) ) + _ <- checkFeatureFlag(credentialFormat) outcome <- credentialFormat match case JWT => @@ -185,6 +203,7 @@ class IssueControllerImpl( connectionId <- ZIO .fromOption(request.connectionId) .mapError(_ => ErrorResponse.badRequest(detail = Some("Missing connectionId for credential offer"))) + _ <- checkFeatureFlag(request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT)) didIdPair <- getPairwiseDIDs(connectionId).provideSomeLayer(ZLayer.succeed(connectionService)) offerContext = OfferContext( pairwiseIssuerDID = didIdPair.myDID, @@ -202,6 +221,7 @@ class IssueControllerImpl( )(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, IssueCredentialRecord] = { for { peerDid <- managedDIDService.createAndStorePeerDID(appConfig.agent.didCommEndpoint.publicEndpointUrl) + _ <- checkFeatureFlag(request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT)) offerContext = OfferContext( pairwiseIssuerDID = peerDid.did, pairwiseHolderDID = None, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala index 0d1a8ff81b..25e2e0694a 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala @@ -1,6 +1,7 @@ package org.hyperledger.identus.presentproof.controller import org.hyperledger.identus.agent.server.config.AppConfig +import org.hyperledger.identus.agent.server.config.FeatureFlagConfig import org.hyperledger.identus.agent.server.ControllerHelper import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} @@ -11,7 +12,9 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.protocol.presentproof.{PresentCredentialRequestFormat, ProofType} import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, PresentationRecord} import org.hyperledger.identus.pollux.core.model.error.PresentationError +import org.hyperledger.identus.pollux.core.model.error.PresentationError.UnsupportedCredentialFormatBecauseDisabled import org.hyperledger.identus.pollux.core.model.presentation.Options +import org.hyperledger.identus.pollux.core.model.CredentialFormat.{AnonCreds, JWT, SDJWT} import org.hyperledger.identus.pollux.core.service.serdes.AnoncredPresentationRequestV1 import org.hyperledger.identus.pollux.core.service.PresentationService import org.hyperledger.identus.presentproof.controller.http.* @@ -31,11 +34,29 @@ class PresentProofControllerImpl( appConfig: AppConfig ) extends PresentProofController with ControllerHelper { + + private def checkFeatureFlag(credentialFormat: CredentialFormat) = for { + _ <- credentialFormat match // Fail if feature is disabled + case JWT => + appConfig.featureFlag.ifJWTIsDisable( + ZIO.fail(UnsupportedCredentialFormatBecauseDisabled(FeatureFlagConfig.messageIfDisableForJWT)) + ) + case SDJWT => + appConfig.featureFlag.ifSDJWTIsDisable( + ZIO.fail(UnsupportedCredentialFormatBecauseDisabled(FeatureFlagConfig.messageIfDisableForSDJWT)) + ) + case AnonCreds => + appConfig.featureFlag.ifAnoncredIsDisable( + ZIO.fail(UnsupportedCredentialFormatBecauseDisabled(FeatureFlagConfig.messageIfDisableForAnoncred)) + ) + } yield () + override def requestPresentation(request: RequestPresentationInput)(implicit rc: RequestContext ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] = { val result: ZIO[WalletAccessContext, ConnectionServiceError | PresentationError, PresentationStatus] = for { + _ <- checkFeatureFlag(request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT)) connectionId <- ZIO .fromOption(request.connectionId) .mapError(_ => PresentationError.MissingConnectionIdForPresentationRequest) @@ -54,6 +75,7 @@ class PresentProofControllerImpl( rc: RequestContext ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] = { val result: ZIO[WalletAccessContext, ConnectionServiceError | PresentationError, PresentationStatus] = for { + _ <- checkFeatureFlag(request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT)) peerDid <- managedDIDService.createAndStorePeerDID(appConfig.agent.didCommEndpoint.publicEndpointUrl) record <- createRequestPresentation( verifierDID = peerDid.did, diff --git a/mercury/agent-didcommx/src/main/scala/org/hyperledger/identus/mercury/model/Conversions.scala b/mercury/agent-didcommx/src/main/scala/org/hyperledger/identus/mercury/model/Conversions.scala index 754d19711d..a12b992791 100644 --- a/mercury/agent-didcommx/src/main/scala/org/hyperledger/identus/mercury/model/Conversions.scala +++ b/mercury/agent-didcommx/src/main/scala/org/hyperledger/identus/mercury/model/Conversions.scala @@ -37,7 +37,7 @@ given Conversion[Message, org.didcommx.didcomm.message.Message] with { msg.pleaseAck.foreach { seq => // https://identity.foundation/didcomm-messaging/spec/#acks aux.pleaseAck(true) // NOTE lib limitation the field pleaseAck MUST be a Array of string } - msg.ack.flatMap(_.headOption).foreach(str => aux.ack(str)) // NOTE: headOption becuase DidCommx only support one ack + msg.ack.flatMap(_.headOption).foreach(str => aux.ack(str)) // NOTE: headOption because DidCommx only support one ack msg.thid.foreach(str => aux.thid(str)) msg.pthid.foreach(str => aux.pthid(str)) aux.build() diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala index 4ea28a3359..3ce80a10f4 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala @@ -97,6 +97,11 @@ object PresentationError { StatusCode.BadRequest, s"The Credential format '$vcFormat' is not Unsupported" ) + final case class UnsupportedCredentialFormatBecauseDisabled(detail: String) + extends PresentationError( + StatusCode.BadRequest, + userFacingMessage = detail + ) final case class InvalidAnoncredPresentationRequest(error: String) extends PresentationError( diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala index f08496efd2..84986c2880 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiablePresentationPayload.scala @@ -270,12 +270,12 @@ object JwtPresentationPayload { } //FIXME THIS WILL NOT WORK like that -case class AnomcredVp( +case class AnoncredVp( `@context`: IndexedSeq[String], `type`: IndexedSeq[String], verifiableCredential: IndexedSeq[VerifiableCredentialPayload] ) -case class AnomcredPresentationPayload( +case class AnoncredPresentationPayload( iss: String, vp: JwtVp, maybeNbf: Option[Instant], diff --git a/tests/integration-tests/src/test/resources/containers/agent.yml b/tests/integration-tests/src/test/resources/containers/agent.yml index f2363cd58c..e639230e0d 100644 --- a/tests/integration-tests/src/test/resources/containers/agent.yml +++ b/tests/integration-tests/src/test/resources/containers/agent.yml @@ -57,6 +57,7 @@ services: KEYCLOAK_UMA_AUTO_UPGRADE_RPT: true # no configurable at the moment # Kafka Messaging Service DEFAULT_KAFKA_ENABLED: true + ENABLE_ANONCRED: true depends_on: postgres: condition: service_healthy From 4199fca58ae62df70402ae8132b2314643d18208 Mon Sep 17 00:00:00 2001 From: Ry Jones Date: Mon, 13 Jan 2025 09:28:55 -0800 Subject: [PATCH 3/3] chore: update github actions (#1490) Signed-off-by: Ry Jones Co-authored-by: Fabio Pinheiro --- .github/workflows/build.yml | 2 +- .github/workflows/release-clients.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 74797a30e1..9b0f365998 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,7 @@ jobs: - name: Publish Cloud-Agent Open API Specification id: upload-oas - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cloud-agent-openapi-spec-${{ env.OAS_CHECKSUM}} path: ./cloud-agent-openapi-spec-${{ env.REVISION_VERSION}}.yaml diff --git a/.github/workflows/release-clients.yml b/.github/workflows/release-clients.yml index ec9c5e34a2..8d8330408c 100644 --- a/.github/workflows/release-clients.yml +++ b/.github/workflows/release-clients.yml @@ -81,7 +81,7 @@ jobs: - name: Download OpenAPI specification if: ${{ !inputs.releaseTag }} - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: cloud-agent-openapi-spec-${{ inputs.check_sum }} path: ./cloud-agent/service/api/http