From 29a804fce71d2c500cec9ab8db21de84f9016c95 Mon Sep 17 00:00:00 2001 From: patlo-iog <108713642+patlo-iog@users.noreply.github.com> Date: Fri, 27 Jan 2023 16:18:09 +0700 Subject: [PATCH] feat(prism-agent): add deactivate DID endpoint (#326) * fix(castor): id in DIDDoc match a given DID * fix(castor): fix did doc id must match DID resolved * feat(prism-agent): expose DID deactivation endpoint * feat(prism-agent): update OAS did metadata * feat(prism-agent): add DID deactivation check before performing DID update * pr cleanup --- .../service/api/http/castor/schemas.yaml | 7 ++++ .../api/http/prism-agent-openapi-spec.yaml | 23 +++++++++++ .../service/project/Dependencies.scala | 4 +- .../server/http/marshaller/JsonSupport.scala | 2 +- .../http/model/OASDomainModelHelper.scala | 6 ++- .../service/DIDRegistrarApiServiceImpl.scala | 17 ++++++++ .../model/error/UpdateManagedDIDError.scala | 4 +- .../walletapi/service/ManagedDIDService.scala | 39 ++++++++++++++++++- 8 files changed, 95 insertions(+), 7 deletions(-) diff --git a/prism-agent/service/api/http/castor/schemas.yaml b/prism-agent/service/api/http/castor/schemas.yaml index 350cf44153..f8f34ea226 100644 --- a/prism-agent/service/api/http/castor/schemas.yaml +++ b/prism-agent/service/api/http/castor/schemas.yaml @@ -63,6 +63,12 @@ components: deactivated: type: boolean description: If a DID has been deactivated, DID document metadata MUST include this property with the boolean value true. If a DID has not been deactivated, this property is OPTIONAL, but if included, MUST have the boolean value false. + versionId: + type: string + description: A hexstring representing the last operation applied to the DID Document + canonicalId: + type: string + description: A DID in canonical form VerificationMethod: type: object @@ -198,6 +204,7 @@ components: example: did:prism:abc:123 status: type: string + description: A status indicating whether this is published DID or not. Does not represent DID full lifecyle (e.g. deactivated, recovered, updated). enum: - CREATED - PUBLICATION_PENDING diff --git a/prism-agent/service/api/http/prism-agent-openapi-spec.yaml b/prism-agent/service/api/http/prism-agent-openapi-spec.yaml index ff10f207e1..bfd3ebc0b4 100644 --- a/prism-agent/service/api/http/prism-agent-openapi-spec.yaml +++ b/prism-agent/service/api/http/prism-agent-openapi-spec.yaml @@ -160,6 +160,29 @@ paths: schema: $ref: "./castor/schemas.yaml#/components/schemas/ErrorResponse" + /did-registrar/dids/{didRef}/deactivations: + post: + tags: ["DID Registrar"] + operationId: deactivateManagedDid + summary: Deactivate DID in PrismAgent's wallet and post deactivate operation to blockchain + description: | + Deactivate DID in PrismAgent's wallet and post deactivate operation to blockchain. + parameters: + - $ref: "./castor/parameters.yaml#/components/parameters/didRefInPath" + responses: + "202": + description: Publishing DID to Blockchain. + content: + application/json: + schema: + $ref: "./castor/schemas.yaml#/components/schemas/DIDOperationResponse" + "422": + description: The DID deactivation failed. + content: + application/json: + schema: + $ref: "./castor/schemas.yaml#/components/schemas/ErrorResponse" + # ---------------------------------- # Pollux # ---------------------------------- diff --git a/prism-agent/service/project/Dependencies.scala b/prism-agent/service/project/Dependencies.scala index a690cfa743..0398cb06e2 100644 --- a/prism-agent/service/project/Dependencies.scala +++ b/prism-agent/service/project/Dependencies.scala @@ -8,8 +8,8 @@ object Dependencies { val zioInteropCats = "3.3.0" // scala-steward:off val akka = "2.6.20" val akkaHttp = "10.2.9" - val castor = "0.6.0" - val pollux = "0.18.0" + val castor = "0.7.0" + val pollux = "0.19.0" val connect = "0.6.0" val bouncyCastle = "1.70" val logback = "1.4.5" diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/JsonSupport.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/JsonSupport.scala index 40ba1f6ce8..fa90f919e5 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/JsonSupport.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/JsonSupport.scala @@ -25,7 +25,7 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { ) given RootJsonFormat[CreateManagedDIDResponse] = jsonFormat1(CreateManagedDIDResponse.apply) given RootJsonFormat[DID] = jsonFormat8(DID.apply) - given RootJsonFormat[DIDDocumentMetadata] = jsonFormat1(DIDDocumentMetadata.apply) + given RootJsonFormat[DIDDocumentMetadata] = jsonFormat3(DIDDocumentMetadata.apply) given RootJsonFormat[DIDOperationResponse] = jsonFormat1(DIDOperationResponse.apply) given RootJsonFormat[DidOperationSubmission] = jsonFormat2(DidOperationSubmission.apply) given RootJsonFormat[DIDResponse] = jsonFormat2(DIDResponse.apply) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/model/OASDomainModelHelper.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/model/OASDomainModelHelper.scala index cd9135a919..3fa5ed74cd 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/model/OASDomainModelHelper.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/model/OASDomainModelHelper.scala @@ -240,7 +240,11 @@ trait OASDomainModelHelper { capabilityInvocation = Some(didDoc.capabilityInvocation.map(_.toOAS)), service = Some(didDoc.service.map(_.toOAS)) ), - metadata = DIDDocumentMetadata(deactivated = metadata.deactivated) + metadata = DIDDocumentMetadata( + deactivated = metadata.deactivated, + versionId = Some(metadata.versionId), + canonicalId = Some(metadata.canonicalId) + ) ) } } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/DIDRegistrarApiServiceImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/DIDRegistrarApiServiceImpl.scala index ab3a5fadcd..0db6c816c2 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/DIDRegistrarApiServiceImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/DIDRegistrarApiServiceImpl.scala @@ -39,6 +39,23 @@ class DIDRegistrarApiServiceImpl(service: ManagedDIDService)(using runtime: Runt } } + override def deactivateManagedDid(didRef: String)(implicit + toEntityMarshallerDIDOperationResponse: ToEntityMarshaller[DIDOperationResponse], + toEntityMarshallerErrorResponse: ToEntityMarshaller[ErrorResponse] + ): Route = { + val result = for { + prismDID <- ZIO.fromEither(PrismDID.fromString(didRef)).mapError(HttpServiceError.InvalidPayload.apply) + outcome <- service + .deactivateManagedDID(prismDID.asCanonical) + .mapError(HttpServiceError.DomainError[UpdateManagedDIDError].apply) + } yield outcome + + onZioSuccess(result.mapBoth(_.toOAS, _.toOAS).either) { + case Left(error) => complete(error.status -> error) + case Right(result) => deactivateManagedDid202(result) + } + } + override def listManagedDid()(implicit toEntityMarshallerListManagedDIDResponseInnerarray: ToEntityMarshaller[Seq[ListManagedDIDResponseInner]], toEntityMarshallerErrorResponse: ToEntityMarshaller[ErrorResponse] diff --git a/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/model/error/UpdateManagedDIDError.scala b/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/model/error/UpdateManagedDIDError.scala index dd367b6d47..0e8ee6ac15 100644 --- a/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/model/error/UpdateManagedDIDError.scala +++ b/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/model/error/UpdateManagedDIDError.scala @@ -1,16 +1,18 @@ package io.iohk.atala.agent.walletapi.model.error import io.iohk.atala.castor.core.model.did.CanonicalPrismDID -import io.iohk.atala.castor.core.model.error.DIDOperationError +import io.iohk.atala.castor.core.model.error.{DIDOperationError, DIDResolutionError} sealed trait UpdateManagedDIDError object UpdateManagedDIDError { final case class DIDNotFound(did: CanonicalPrismDID) extends UpdateManagedDIDError final case class DIDNotPublished(did: CanonicalPrismDID) extends UpdateManagedDIDError + final case class DIDAlreadyDeactivated(did: CanonicalPrismDID) extends UpdateManagedDIDError final case class InvalidArgument(msg: String) extends UpdateManagedDIDError final case class KeyGenerationError(cause: Throwable) extends UpdateManagedDIDError final case class WalletStorageError(cause: Throwable) extends UpdateManagedDIDError final case class OperationError(cause: DIDOperationError) extends UpdateManagedDIDError + final case class ResolutionError(cause: DIDResolutionError) extends UpdateManagedDIDError final case class CryptographyError(cause: Throwable) extends UpdateManagedDIDError } diff --git a/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/service/ManagedDIDService.scala b/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/service/ManagedDIDService.scala index 304df39167..e602d6d6fb 100644 --- a/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/service/ManagedDIDService.scala +++ b/prism-agent/service/wallet-api/src/main/scala/io/iohk/atala/agent/walletapi/service/ManagedDIDService.scala @@ -3,12 +3,12 @@ package io.iohk.atala.agent.walletapi.service import io.iohk.atala.agent.walletapi.crypto.{ECWrapper, KeyGeneratorWrapper} import io.iohk.atala.agent.walletapi.model.{ DIDPublicKeyTemplate, + DIDUpdateLineage, ECKeyPair, ManagedDIDDetail, ManagedDIDState, ManagedDIDTemplate, - UpdateManagedDIDAction, - DIDUpdateLineage + UpdateManagedDIDAction } import io.iohk.atala.agent.walletapi.model.ECCoordinates.* import io.iohk.atala.agent.walletapi.model.error.{*, given} @@ -28,6 +28,7 @@ import io.iohk.atala.agent.walletapi.util.{ import io.iohk.atala.castor.core.model.did.{ CanonicalPrismDID, DID, + DIDMetadata, EllipticCurve, InternalKeyPurpose, InternalPublicKey, @@ -199,6 +200,11 @@ final class ManagedDIDService private[walletapi] ( .mapError(UpdateManagedDIDError.WalletStorageError.apply) .someOrFail(UpdateManagedDIDError.DIDNotFound(did)) .collect(UpdateManagedDIDError.DIDNotPublished(did)) { case s: ManagedDIDState.Published => s } + _ <- didService + .resolveDID(did) + .mapError(UpdateManagedDIDError.ResolutionError.apply) + .someOrFail(UpdateManagedDIDError.DIDNotFound(did)) + .filterOrFail { case (metaData, _) => !metaData.deactivated }(UpdateManagedDIDError.DIDAlreadyDeactivated(did)) previousOperationHash <- getPreviousOperationHash[UpdateManagedDIDError](did, didState.createOperation) generated <- generateUpdateOperation(did, previousOperationHash, actions) (updateOperation, secret) = generated @@ -207,6 +213,35 @@ final class ManagedDIDService private[walletapi] ( } yield outcome } + def deactivateManagedDID(did: CanonicalPrismDID): IO[UpdateManagedDIDError, ScheduleDIDOperationOutcome] = { + def doDeactivate(operation: PrismDIDOperation.Deactivate) = { + for { + signedOperation <- signOperationWithMasterKey[UpdateManagedDIDError](operation) + outcome <- submitSignedOperation[UpdateManagedDIDError](signedOperation) + } yield outcome + } + + for { + _ <- computeNewDIDStateFromDLTAndPersist[UpdateManagedDIDError](did) + didState <- nonSecretStorage + .getManagedDIDState(did) + .mapError(UpdateManagedDIDError.WalletStorageError.apply) + .someOrFail(UpdateManagedDIDError.DIDNotFound(did)) + .collect(UpdateManagedDIDError.DIDNotPublished(did)) { case s: ManagedDIDState.Published => s } + _ <- didService + .resolveDID(did) + .mapError(UpdateManagedDIDError.ResolutionError.apply) + .someOrFail(UpdateManagedDIDError.DIDNotFound(did)) + .filterOrFail { case (metaData, _) => !metaData.deactivated }(UpdateManagedDIDError.DIDAlreadyDeactivated(did)) + previousOperationHash <- getPreviousOperationHash[UpdateManagedDIDError](did, didState.createOperation) + deactivateOperation = PrismDIDOperation.Deactivate(did, ArraySeq.from(previousOperationHash)) + _ <- ZIO + .fromEither(didOpValidator.validate(deactivateOperation)) + .mapError(UpdateManagedDIDError.OperationError.apply) + outcome <- doDeactivate(deactivateOperation) + } yield outcome + } + /** return hash of previous operation. Currently support only last confirmed operation */ private def getPreviousOperationHash[E]( did: CanonicalPrismDID,