• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / atala-prism-building-blocks / 7295755663

22 Dec 2023 03:08AM UTC coverage: 30.564% (-0.5%) from 31.05%
7295755663

Pull #829

web-flow
Merge branch 'epic/ATL-4095-revocation-for-jwt-creds' into ATL-6136-get-credential-status-list-cred-endpoint
Pull Request #829: feat(pollux): Implement endpoint that returns status list credential

1 of 92 new or added lines in 10 files covered. (1.09%)

376 existing lines in 112 files now uncovered.

4061 of 13287 relevant lines covered (30.56%)

0.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.0
/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/service/DIDService.scala
1
package io.iohk.atala.castor.core.service
2

3
import io.iohk.atala.castor.core.model.ProtoModelHelper
4
import io.iohk.atala.castor.core.model.did.{
5
  CanonicalPrismDID,
6
  DIDData,
7
  DIDMetadata,
8
  InternalPublicKey,
9
  LongFormPrismDID,
10
  PrismDID,
11
  PublicKey,
12
  ScheduleDIDOperationOutcome,
13
  ScheduledDIDOperationDetail,
14
  SignedPrismDIDOperation
15
}
16
import io.iohk.atala.castor.core.model.error.OperationValidationError
17
import io.iohk.atala.castor.core.model.error.{DIDOperationError, DIDResolutionError}
18
import io.iohk.atala.castor.core.util.DIDOperationValidator
19
import io.iohk.atala.prism.protos.node_api.NodeServiceGrpc.NodeService
20
import io.iohk.atala.prism.protos.node_models.OperationOutput.OperationMaybe
21
import io.iohk.atala.prism.protos.{node_api, node_models}
22
import io.iohk.atala.shared.models.HexString
23
import java.time.Instant
24
import scala.collection.immutable.ArraySeq
25
import zio.*
26

27
trait DIDService {
28
  def scheduleOperation(operation: SignedPrismDIDOperation): IO[DIDOperationError, ScheduleDIDOperationOutcome]
29
  def getScheduledDIDOperationDetail(
30
      operationId: Array[Byte]
31
  ): IO[DIDOperationError, Option[ScheduledDIDOperationDetail]]
32
  def resolveDID(did: PrismDID): IO[DIDResolutionError, Option[(DIDMetadata, DIDData)]]
33
}
34

35
object DIDServiceImpl {
36
  val layer: URLayer[NodeService & DIDOperationValidator, DIDService] =
1✔
37
    ZLayer.fromFunction(DIDServiceImpl(_, _))
38
}
39

40
private class DIDServiceImpl(didOpValidator: DIDOperationValidator, nodeClient: NodeService)
41
    extends DIDService,
42
      ProtoModelHelper {
43

×
44
  override def scheduleOperation(
45
      signedOperation: SignedPrismDIDOperation
46
  ): IO[DIDOperationError, ScheduleDIDOperationOutcome] = {
×
47
    val operationRequest = node_api.ScheduleOperationsRequest(
×
48
      signedOperations = Seq(signedOperation.toProto)
49
    )
×
50
    for {
×
51
      _ <- ZIO
×
52
        .fromEither(didOpValidator.validate(signedOperation.operation))
53
        .mapError(DIDOperationError.ValidationError.apply)
×
54
      operationOutput <- ZIO
×
55
        .fromFuture(_ => nodeClient.scheduleOperations(operationRequest))
56
        .logError("Error scheduling Node operation")
×
57
        .mapBoth(DIDOperationError.DLTProxyError.apply, _.outputs.toList)
58
        .map {
×
59
          case output :: Nil => Right(output)
×
60
          case _ => Left(DIDOperationError.UnexpectedDLTResult("operation result is expected to have exactly 1 output"))
61
        }
62
        .absolve
×
63
      operationId <- ZIO.fromEither {
64
        operationOutput.operationMaybe match {
×
65
          case OperationMaybe.OperationId(id) => Right(id.toByteArray)
×
66
          case OperationMaybe.Empty =>
×
67
            Left(DIDOperationError.UnexpectedDLTResult("operation result does not contain operation detail"))
×
68
          case OperationMaybe.Error(e) =>
×
69
            Left(DIDOperationError.UnexpectedDLTResult(s"operation result was not successful: $e"))
70
        }
71
      }
72
    } yield ScheduleDIDOperationOutcome(
×
73
      did = signedOperation.operation.did,
74
      operation = signedOperation.operation,
×
75
      operationId = ArraySeq.from(operationId)
76
    )
77
  }
78

×
79
  override def getScheduledDIDOperationDetail(
80
      operationId: Array[Byte]
81
  ): IO[DIDOperationError, Option[ScheduledDIDOperationDetail]] = {
×
82
    for {
×
83
      result <- ZIO
×
84
        .fromFuture(_ => nodeClient.getOperationInfo(node_api.GetOperationInfoRequest(operationId.toProto)))
85
        .logError("Error getting Node operation information")
86
        .mapError(DIDOperationError.DLTProxyError.apply)
×
87
      detail <- ZIO
×
88
        .fromEither(result.toDomain)
89
        .mapError(DIDOperationError.UnexpectedDLTResult.apply)
90
    } yield detail
91
  }
92

1✔
93
  override def resolveDID(did: PrismDID): IO[DIDResolutionError, Option[(DIDMetadata, DIDData)]] = {
1✔
94
    val canonicalDID = did.asCanonical
1✔
95
    val request = node_api.GetDidDocumentRequest(did = canonicalDID.toString)
1✔
96
    for {
97
      unpublishedDidData <- did match {
×
98
        case _: CanonicalPrismDID => ZIO.none
×
99
        case d: LongFormPrismDID  => extractUnpublishedDIDData(d).asSome
100
      }
1✔
101
      result <- ZIO
×
102
        .fromFuture(_ => nodeClient.getDidDocument(request))
103
        .logError("Error resolving DID document from Node")
104
        .mapError(DIDResolutionError.DLTProxyError.apply)
1✔
105
      publishedDidData <- ZIO
106
        .fromOption(result.document)
107
        .foldZIO(
108
          _ => ZIO.none,
109
          didDataProto =>
×
110
            didDataProto.filterRevokedKeysAndServices
×
111
              .flatMap(didData => ZIO.fromEither(didData.toDomain))
112
              .mapError(DIDResolutionError.UnexpectedDLTResult.apply)
113
              .map { didData =>
×
114
                val (created, updated) = getMinMaxLedgerTime(didDataProto)
115
                val metadata = DIDMetadata(
×
116
                  lastOperationHash = ArraySeq.from(result.lastUpdateOperation.toByteArray),
117
                  canonicalId =
×
118
                    unpublishedDidData.map(_ => canonicalDID), // only shows canonicalId if long-form and published
×
119
                  deactivated = didData.internalKeys.isEmpty && didData.publicKeys.isEmpty,
120
                  created = created,
121
                  updated = updated
122
                )
×
123
                metadata -> didData
124
              }
125
              .asSome
126
        )
1✔
127
    } yield publishedDidData.orElse(unpublishedDidData)
128
  }
129

130
  // FIXME: This doesn't play well detecting timestamp context and revoked service due to
131
  // the response from Node missing the ledger data for those items.
1✔
132
  private def getMinMaxLedgerTime(didData: node_models.DIDData): (Option[Instant], Option[Instant]) = {
×
133
    val ledgerTimes = didData.publicKeys.flatMap(_.addedOn) ++
×
134
      didData.publicKeys.flatMap(_.revokedOn) ++
×
135
      didData.services.flatMap(_.addedOn) ++
1✔
UNCOV
136
      didData.services.flatMap(_.deletedOn)
×
137
    val instants = ledgerTimes.flatMap(_.toInstant)
1✔
138
    (instants.minOption, instants.maxOption)
139
  }
140

1✔
141
  private def extractUnpublishedDIDData(did: LongFormPrismDID): IO[DIDResolutionError, (DIDMetadata, DIDData)] = {
1✔
142
    ZIO
×
143
      .fromEither(did.createOperation)
144
      .mapError(e => DIDResolutionError.ValidationError(OperationValidationError.InvalidArgument(e)))
145
      .flatMap { op =>
146
        // unpublished CreateOperation (if exists) must be validated before the resolution
×
147
        ZIO
×
148
          .fromEither(didOpValidator.validate(op))
149
          .mapError(DIDResolutionError.ValidationError.apply)
150
          .as(op)
151
      }
152
      .map { op =>
153
        val metadata =
154
          DIDMetadata(
1✔
155
            lastOperationHash = ArraySeq.from(did.stateHash.toByteArray),
156
            canonicalId = None, // unpublished DID must not contain canonicalId
157
            deactivated = false, // unpublished DID cannot be deactivated
158
            created = None, // unpublished DID cannot have timestamp
159
            updated = None // unpublished DID cannot have timestamp
160
          )
161
        val didData = DIDData(
1✔
162
          id = did.asCanonical,
1✔
163
          publicKeys = op.publicKeys.collect { case pk: PublicKey => pk },
164
          services = op.services,
1✔
165
          internalKeys = op.publicKeys.collect { case pk: InternalPublicKey => pk },
166
          context = op.context
167
        )
1✔
168
        metadata -> didData
169
      }
170
  }
171

172
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc