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 diff --git a/README.md b/README.md index 455080b730..a67d8cd58b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- + identus-logo
@@ -10,7 +10,7 @@ Unit tests End-to-end tests Performance tests - + Scala Steward badge @@ -58,7 +58,7 @@ All documentation, tutorials and API references for the Identus ecosystem can be Before starting to use the Cloud Agent, it is important to understand the basic concepts of self-sovereign identity (SSI). The following resources provide a good introduction to SSI: -* [Identus SSI introduction](https://docs.atalaprism.io/docs/category/concepts/) +* [Identus SSI introduction](https://hyperledger.github.io/identus-docs/docs/category/concepts) * [Linux Foundation Course: Getting Started with SSI](https://www.edx.org/learn/computer-programming/the-linux-foundation-getting-started-with-self-sovereign-identity) ### Architecture @@ -109,7 +109,7 @@ The Cloud Agent can be configured to use different types of ledger, secret stora To start playing with Cloud Agent, we recommend using the Dev configuration. Pre-production and production configurations are intended for real-world use cases and require additional more complex configurations of the Distributed Ledger stack setup. -> If you're interested in a hosted version of Cloud Agent, please, contact us via the [Identus site](https://www.hyperledger.org/projects/identus). +> If you're interested in a hosted version of Cloud Agent, please, contact us via the [Identus site](https://www.lfdecentralizedtrust.org/projects/identus). #### System requirements @@ -128,12 +128,21 @@ If the Cloud Agent is started successfully, all the running containers should ac * `http://localhost:8080/cloud-agent` for the `issuer` instance * `http://localhost:8090/cloud-agent` for the `holder` instance -You can check the status of the running containers using the [health endpoint](https://docs.atalaprism.io/agent-api/#tag/System/operation/systemHealth): +You can check the status of the running containers using the [health endpoint](https://hyperledger.github.io/identus-docs/agent-api/#tag/System/operation/systemHealth): ```bash $ curl http://localhost:8080/cloud-agent/_system/health {"version":"1.19.1"} ``` +#### Simple docker compose for running the Identus Platform + +The Identus Platform is a set of services that work together to provide a complete SSI solution. +The following services are included in the Identus Platform: +- Cloud Agent +- Mediator + +The docker compose file and documentation for running the full stack with the simplest configuration (single tenant without authentication) is available [here](https://github.com/hyperledger/identus/blob/main/identus-docker/dockerize-identus.md) + #### Compatibility between Cloud Agent and PRISM Node There could be some incompatibilities between the most latest versions of Cloud Agent and PRISM Node. Please, use the following table to check the compatibility between the versions: @@ -149,10 +158,10 @@ There could be some incompatibilities between the most latest versions of Cloud The following tutorials will help you get started with the Cloud Agent and issue your first credentials: -* [Creating, updating and deactivating Decentralized Identifiers (DIDs)](https://docs.atalaprism.io/tutorials/category/dids/) -* [Setting up connections between agents using out-of-band (OOB) protocol](https://docs.atalaprism.io/tutorials/connections/connection) -* [Issuing verifiable credentials (VCs)](https://docs.atalaprism.io/tutorials/credentials/issue) -* [Presenting VC proofs](https://docs.atalaprism.io/tutorials/credentials/present-proof) +* [Creating, updating and deactivating Decentralized Identifiers (DIDs)](https://hyperledger.github.io/identus-docs/tutorials/category/dids/) +* [Setting up connections between agents using out-of-band (OOB) protocol](https://hyperledger.github.io/identus-docs/tutorials/connections/connection) +* [Issuing verifiable credentials (VCs)](https://hyperledger.github.io/identus-docs/tutorials/credentials/didcomm/issue) +* [Presenting VC proofs](https://hyperledger.github.io/identus-docs/tutorials/credentials/didcomm/present-proof) ## Contributing 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/http/DocModels.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala index 96cae26a88..9f31588bc9 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala @@ -65,7 +65,7 @@ object DocModels { |It supports DID (Decentralized Identifiers) management, verifiable credential exchange, and secure messaging based on DIDComm standards. |The API is designed to be interoperable with various blockchain and DLT (Distributed Ledger Technology) platforms, ensuring wide compatibility and flexibility. |Key features include connection management, credential issuance and verification, and secure, privacy-preserving communication between entities. - |Additional information and the full list of capabilities can be found in the [Open Enterprise Agent documentation](https://docs.atalaprism.io/docs/category/prism-cloud-agent) + |Additional information and the full list of capabilities can be found in the [Open Enterprise Agent documentation](https://hyperledger.github.io/identus-docs/docs/category/prism-cloud-agent) |""".stripMargin), termsOfService = None, contact = None, 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 217acd2aa9..e027b96902 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 @@ -119,6 +119,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/castor/controller/DIDRegistrarEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarEndpoints.scala index bd0a2e42aa..9ad9516009 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarEndpoints.scala @@ -28,12 +28,12 @@ object DIDRegistrarEndpoints { s""" |The __${tagName}__ endpoints facilitate the management of [PRISM DIDs](https://github.com/input-output-hk/prism-did-method-spec) hosted in the cloud agent. | - |Implentation of [DID management](https://docs.atalaprism.io/docs/atala-prism/prism-cloud-agent/did-management/) in the cloud agent. + |Implentation of [DID management](https://hyperledger.github.io/identus-docs/docs/atala-prism/prism-cloud-agent/did-management/) in the cloud agent. |The agent securely manages and stores DIDs along with their keys in its secret storage. |These endpoints allow users to create, read, update, deactivate, and publish without direct exposure to the key material. |These DIDs can be utilized for various operations during issuance and verification processes. | - |More examples and tutorials can be found in this [documentation](https://docs.atalaprism.io/tutorials/category/dids/). + |More examples and tutorials can be found in this [documentation](https://hyperledger.github.io/identus-docs/tutorials/category/dids/). |""".stripMargin val tag = Tag(tagName, Some(tagDescription)) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala index 96e400f62a..42cc71a10b 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala @@ -96,7 +96,7 @@ object StatusListCredential { object `type` extends Annotation[String]( - description = "Always equals to constnat value - StatusList2021", + description = "Always equals to constant value - StatusList2021", example = "StatusList2021" ) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/event/controller/EventEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/event/controller/EventEndpoints.scala index 6081fd5926..c2d8a998e0 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/event/controller/EventEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/event/controller/EventEndpoints.scala @@ -30,7 +30,7 @@ object EventEndpoints { |- Issuance protocol notifications |- Presentation protocol notifications | - |For more detailed information regarding event notifications, please refer to this [documentation](https://docs.atalaprism.io/tutorials/webhooks/webhook). + |For more detailed information regarding event notifications, please refer to this [documentation](https://hyperledger.github.io/identus-docs/tutorials/webhooks/webhook). |""".stripMargin val tag = Tag(tagName, Some(tagDescription)) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/EntityEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/EntityEndpoints.scala index ce29855cca..3c2e60ae30 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/EntityEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/entity/http/EntityEndpoints.scala @@ -20,7 +20,7 @@ object EntityEndpoints { private val tagName = "Identity and Access Management" private val tagDescription = s""" - |The __${tagName}__ endpoints allow [agent administrators](https://docs.atalaprism.io/docs/concepts/glossary#administrator) + |The __${tagName}__ endpoints allow [agent administrators](https://hyperledger.github.io/identus-docs/docs/concepts/glossary#administrator) |to manage identity and access management for the agent's tenants. |It provides basic built-in IAM capabilities as an alternative to more feature rich external IAM solutions. | @@ -30,7 +30,7 @@ object EntityEndpoints { |Additionally, the administrator can create API keys for entities and provide them to the tenants out-of-band. |These API keys can then be used for authorization to access specific wallets. | - |For more detailed information related to the agent IAM and its usage, please refer to this [documentation](https://docs.atalaprism.io/docs/atala-prism/prism-cloud-agent/authentication). + |For more detailed information related to the agent IAM and its usage, please refer to this [documentation](https://hyperledger.github.io/identus-docs/docs/atala-prism/prism-cloud-agent/authentication). |""".stripMargin val tag = Tag(tagName, Some(tagDescription)) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/WalletManagementEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/WalletManagementEndpoints.scala index 5bb23c1726..5e325ae817 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/WalletManagementEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/WalletManagementEndpoints.scala @@ -26,15 +26,15 @@ object WalletManagementEndpoints { private val tagName = "Wallet Management" private val tagDescription = s""" - |The __${tagName}__ endpoints enable both users and administrators to manage [wallets](https://docs.atalaprism.io/docs/concepts/multi-tenancy#wallet). + |The __${tagName}__ endpoints enable both users and administrators to manage [wallets](https://hyperledger.github.io/identus-docs/docs/concepts/multi-tenancy#wallet). | |In a multitenant agent, wallet is a container for various resources (e.g. Connections, DIDs) and it isolates the access based on the authorization settings. - |[Admnistrator](https://docs.atalaprism.io/docs/concepts/glossary#administrator) can utilize the endpoints to manage and onboard [tenants](https://docs.atalaprism.io/docs/concepts/glossary#tenant). - |See [this example](https://docs.atalaprism.io/tutorials/multitenancy/tenant-onboarding-ext-iam) for instructions how to utilize the endpoints for administrator. + |[Admnistrator](https://hyperledger.github.io/identus-docs/docs/concepts/glossary#administrator) can utilize the endpoints to manage and onboard [tenants](https://hyperledger.github.io/identus-docs/docs/concepts/glossary#tenant). + |See [this example](https://hyperledger.github.io/identus-docs/tutorials/multitenancy/tenant-onboarding-ext-iam) for instructions how to utilize the endpoints for administrator. |Tenants can also manage and onboard their own wallets using these endpoints depending on the configuration. - |See [this document](https://docs.atalaprism.io/tutorials/multitenancy/tenant-onboarding-ext-iam) for a detailed example for self-service tenants onboarding. + |See [this document](https://hyperledger.github.io/identus-docs/tutorials/multitenancy/tenant-onboarding-ext-iam) for a detailed example for self-service tenants onboarding. | - |Wallet permissions are controlled by [UMA](https://docs.atalaprism.io/docs/concepts/glossary#uma) configuration which the agent + |Wallet permissions are controlled by [UMA](https://hyperledger.github.io/identus-docs/docs/concepts/glossary#uma) configuration which the agent |exposes endpoints to easily configure wallet access using `uma-permissions` resource. |The permissions can also be configured out-of-band directly on the external IAM provider that supports the UMA standard. |""".stripMargin 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/pollux/credentialdefinition/CredentialDefinitionRegistryEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionRegistryEndpoints.scala index 664981ba6f..89bb9e2e11 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionRegistryEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionRegistryEndpoints.scala @@ -48,7 +48,7 @@ object CredentialDefinitionRegistryEndpoints { |""".stripMargin private val tagExternalDocumentation = ExternalDocumentation( - url = "https://docs.atalaprism.io/tutorials/category/credential-definition", + url = "https://hyperledger.github.io/identus-docs/tutorials/category/credential-definition", description = Some("Credential Definition documentation") ) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialschema/SchemaRegistryEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialschema/SchemaRegistryEndpoints.scala index 1fae593271..4660d24da3 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialschema/SchemaRegistryEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/credentialschema/SchemaRegistryEndpoints.scala @@ -52,7 +52,7 @@ object SchemaRegistryEndpoints { |""".stripMargin private val tagExternalDocumentation = ExternalDocumentation( - url = "https://docs.atalaprism.io/tutorials/schemas/credential-schema", + url = "https://hyperledger.github.io/identus-docs/tutorials/schemas/credential-schema", description = Some("Credential Schema documentation") ) 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/docs/docusaurus/schemas/credential-schema.md b/docs/docusaurus/schemas/credential-schema.md index 641de1011f..bd66e3f2fc 100644 --- a/docs/docusaurus/schemas/credential-schema.md +++ b/docs/docusaurus/schemas/credential-schema.md @@ -15,7 +15,7 @@ semantic interoperability of the Credential. The Identus Platform supports the following specifications of the credential schemas: -- [Verifiable Credentials JSON Schema 2022](https://w3c-ccg.github.io/vc-json-schemas/) +- [Verifiable Credentials JSON Schema 2022](https://www.w3.org/TR/vc-json-schema/) - [AnonCreds Schema](https://hyperledger.github.io/anoncreds-spec/#term:schemas) The signed credential schema allows doing following verifications: @@ -320,5 +320,5 @@ The proof field is a JOSE object containing the credential schema's signature, i ## References -- [Verifiable Credentials JSON Schema 2022](https://w3c-ccg.github.io/vc-json-schemas/) +- [Verifiable Credentials JSON Schema 2022](https://www.w3.org/TR/vc-json-schema/) - [Verifiable Credential Data Integrity 1.0](https://www.w3.org/TR/vc-data-integrity/) 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..a9e00b7fd7 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 # Default is false depends_on: postgres: condition: service_healthy