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

hyperledger / identus-cloud-agent / 8998019047

08 May 2024 07:31AM UTC coverage: 47.171% (-0.7%) from 47.829%
8998019047

Pull #1021

patlo-iog
chore: pr cleanup
Pull Request #1021: feat: oidc4vc credential configuration and metadata endpoints [WIP]

2 of 253 new or added lines in 12 files covered. (0.79%)

186 existing lines in 51 files now uncovered.

7388 of 15662 relevant lines covered (47.17%)

0.47 hits per line

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

56.67
/castor/src/main/scala/org/hyperledger/identus/castor/core/service/DIDService.scala
1
package org.hyperledger.identus.castor.core.service
2

3
import org.hyperledger.identus.castor.core.model.ProtoModelHelper
4
import org.hyperledger.identus.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 org.hyperledger.identus.castor.core.model.error.OperationValidationError
17
import org.hyperledger.identus.castor.core.model.error.{DIDOperationError, DIDResolutionError}
18
import org.hyperledger.identus.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 org.hyperledger.identus.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] =
37
    ZLayer.fromFunction(DIDServiceImpl(_, _))
1✔
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

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 {
1✔
97
      unpublishedDidData <- did match {
98
        case _: CanonicalPrismDID => ZIO.none
1✔
99
        case d: LongFormPrismDID  => extractUnpublishedDIDData(d).asSome
1✔
100
      }
101
      result <- ZIO
1✔
102
        .fromFuture(_ => nodeClient.getDidDocument(request))
1✔
103
        .logError("Error resolving DID document from Node")
104
        .mapError(DIDResolutionError.DLTProxyError.apply)
105
      publishedDidData <- ZIO
1✔
106
        .fromOption(result.document)
107
        .foldZIO(
108
          _ => ZIO.none,
109
          didDataProto =>
110
            didDataProto.filterRevokedKeysAndServices
1✔
111
              .flatMap(didData => ZIO.fromEither(didData.toDomain))
1✔
112
              .mapError(DIDResolutionError.UnexpectedDLTResult.apply)
113
              .map { didData =>
114
                val (created, updated) = getMinMaxLedgerTime(didDataProto)
1✔
115
                val metadata = DIDMetadata(
116
                  lastOperationHash = ArraySeq.from(result.lastUpdateOperation.toByteArray),
1✔
117
                  canonicalId =
118
                    unpublishedDidData.map(_ => canonicalDID), // only shows canonicalId if long-form and published
1✔
119
                  deactivated = didData.internalKeys.isEmpty && didData.publicKeys.isEmpty,
1✔
120
                  created = created,
121
                  updated = updated
122
                )
123
                metadata -> didData
1✔
124
              }
125
              .asSome
126
        )
127
    } yield publishedDidData.orElse(unpublishedDidData)
1✔
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.
132
  private def getMinMaxLedgerTime(didData: node_models.DIDData): (Option[Instant], Option[Instant]) = {
1✔
133
    val ledgerTimes = didData.publicKeys.flatMap(_.addedOn) ++
1✔
134
      didData.publicKeys.flatMap(_.revokedOn) ++
1✔
135
      didData.services.flatMap(_.addedOn) ++
1✔
136
      didData.services.flatMap(_.deletedOn)
1✔
UNCOV
137
    val instants = ledgerTimes.flatMap(_.toInstant)
×
138
    (instants.minOption, instants.maxOption)
1✔
139
  }
140

141
  private def extractUnpublishedDIDData(did: LongFormPrismDID): IO[DIDResolutionError, (DIDMetadata, DIDData)] = {
1✔
142
    ZIO
1✔
143
      .fromEither(did.createOperation)
1✔
144
      .mapError(e => DIDResolutionError.ValidationError(OperationValidationError.InvalidArgument(e)))
145
      .flatMap { op =>
146
        // unpublished CreateOperation (if exists) must be validated before the resolution
147
        ZIO
1✔
148
          .fromEither(didOpValidator.validate(op))
1✔
149
          .mapError(DIDResolutionError.ValidationError.apply)
150
          .as(op)
151
      }
152
      .map { op =>
153
        val metadata =
154
          DIDMetadata(
155
            lastOperationHash = ArraySeq.from(did.stateHash.toByteArray),
1✔
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(
162
          id = did.asCanonical,
1✔
163
          publicKeys = op.publicKeys.collect { case pk: PublicKey => pk },
1✔
164
          services = op.services,
165
          internalKeys = op.publicKeys.collect { case pk: InternalPublicKey => pk },
1✔
166
          context = op.context
167
        )
168
        metadata -> didData
1✔
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