Skip to content

Commit

Permalink
feat(prism-agent): add deactivate DID endpoint (#326)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
patlo-iog authored Jan 27, 2023
1 parent 93aa6e9 commit 29a804f
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 7 deletions.
7 changes: 7 additions & 0 deletions prism-agent/service/api/http/castor/schemas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions prism-agent/service/api/http/prism-agent-openapi-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ----------------------------------
Expand Down
4 changes: 2 additions & 2 deletions prism-agent/service/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down

0 comments on commit 29a804f

Please sign in to comment.