From d7813ba9b3d5a1c422d3d7e7fb57af4dabcb0d54 Mon Sep 17 00:00:00 2001 From: bvoiturier Date: Fri, 10 Mar 2023 17:35:15 +0100 Subject: [PATCH] feat(prism-agent): move subjectId field from issuer to holder (#435) * feat(prism-agent): move subjectId field from create credential offer (issuer) to accept credential offer (holder) * test: update e2e tests --------- Co-authored-by: Anton Baliasnikov --- .github/workflows/e2e-tests.yml | 3 +- docs/docusaurus/credentials/issue.md | 17 +- .../service/api/http/pollux/schemas.yaml | 13 +- .../api/http/prism-agent-openapi-spec.yaml | 7 + .../service/project/Dependencies.scala | 2 +- .../io/iohk/atala/agent/server/Modules.scala | 8 +- ...CredentialsProtocolApiMarshallerImpl.scala | 4 + .../server/http/marshaller/JsonSupport.scala | 1 + ...sueCredentialsProtocolApiServiceImpl.scala | 10 +- .../agent/server/jobs/BackgroundJobs.scala | 188 +++++++++++------- .../kotlin/api_models/PresentationProof.kt | 1 + .../IssueCredentialsSteps.kt | 37 +--- .../issue_credentials.feature | 11 - 13 files changed, 170 insertions(+), 132 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 176da1a447..b392a75be8 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -12,7 +12,7 @@ on: - ".github/workflows/e2e-tests.yml" - "infrastructure/shared/docker-compose.yml" - "tests/e2e-tests/**" - - "prism-agent/service" + - "prism-agent/service/**" push: branches: - "main" @@ -161,7 +161,6 @@ jobs: if: github.ref_name == 'main' uses: rtCamp/action-slack-notify@v2 env: - SLACK_CHANNEL: atala-qa-results SLACK_COLOR: ${{ steps.analyze_test_results.outputs.conclusion }} SLACK_MESSAGE: | Total: ${{ steps.analyze_test_results.outputs.tests }} diff --git a/docs/docusaurus/credentials/issue.md b/docs/docusaurus/credentials/issue.md index 545a9c473d..519222c386 100644 --- a/docs/docusaurus/credentials/issue.md +++ b/docs/docusaurus/credentials/issue.md @@ -62,9 +62,8 @@ This section describes the Issuer role's available interactions with the PRISM A To start the process, the issuer needs to create a credential offer. To do this, make a `POST` request to the [`/issue-credentials/credential-offers`](/agent-api/#tag/Issue-Credentials-Protocol/operation/createCredentialOffer) endpoint with a JSON payload that includes the following information: -1. `subjectId`: This field represents the unique identifier for the subject of the verifiable credential. It is a DID (Decentralized Identifier) string, such as `did:prism:subjectIdentifier`. -2. `schemaId`: This is an identifier for a schema, which defines the structure and format of the data in a verifiable credential. The schema identifier must be unique and typically a URL or a URN. -3. `claims`: The data stored in a verifiable credential. Claims get expressed in a key-value format and must conform to the structure and format defined in the schema. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. +1. `schemaId`: This is an identifier for a schema, which defines the structure and format of the data in a verifiable credential. The schema identifier must be unique and typically a URL or a URN. +2. `claims`: The data stored in a verifiable credential. Claims get expressed in a key-value format and must conform to the structure and format defined in the schema. The claims contain the data that the issuer attests to, such as name, address, date of birth, and so on. Once the request initiates, a new credential record for the issuer gets created with a unique ID. The state of this record is now `OfferPending`. @@ -147,11 +146,19 @@ curl "http://localhost:8090/prism-agent/issue-credentials/records" \ ### Approving the VC Offer -To accept the offer, the **Holder** can make a `POST` request to the [`/issue-credentials/records/{recordId}/accept-offer`](/agent-api/#tag/Issue-Credentials-Protocol/operation/acceptCredentialOffer) endpoint: +To accept the offer, the **Holder** can make a `POST` request to the [`/issue-credentials/records/{recordId}/accept-offer`](/agent-api/#tag/Issue-Credentials-Protocol/operation/acceptCredentialOffer) endpoint with a JSON payload that includes the following information: + +1. `holder_record_id`: The unique identifier of the issue credential record known by the holder PRISM Agent. +2. `subjectId`: This field represents the unique identifier for the subject of the verifiable credential. It is a short-form PRISM DID (Decentralized Identifier) string, such as `did:prism:subjectIdentifier`. + ```shell # Holder POST request to accept the credential offer curl -X POST "http://localhost:8090/prism-agent/issue-credentials/records/$holder_record_id/accept-offer" \ - -H "Content-Type: application/json" + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "subjectId": "did:prism:subjectIdentifier" + }' ``` This request will change the state of the record to `RequestPending`. diff --git a/prism-agent/service/api/http/pollux/schemas.yaml b/prism-agent/service/api/http/pollux/schemas.yaml index 276aca4f50..b446ef701d 100644 --- a/prism-agent/service/api/http/pollux/schemas.yaml +++ b/prism-agent/service/api/http/pollux/schemas.yaml @@ -178,7 +178,6 @@ components: IssueCredentialRecordBase: required: - - subjectId - claims properties: schemaId: @@ -222,6 +221,17 @@ components: type: string description: The unique identifier of a DIDComm connection that already exists between the issuer and the holder, and that will be used to execute the issue credential protocol. + AcceptCredentialOfferRequest: + description: A request to accept a credential offer received from an issuer. + type: object + required: + - subjectId + properties: + subjectId: + type: string + description: The short-form subject Prism DID to which the verifiable credential should be issued. + example: did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f + IssueCredentialRecord: description: An issue credential record that stores the state of the protocol execution. type: object @@ -260,6 +270,7 @@ components: - OfferSent - OfferReceived - RequestPending + - RequestGenerated - RequestSent - RequestReceived - ProblemReportPending 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 1b98e13252..d9c84994a3 100644 --- a/prism-agent/service/api/http/prism-agent-openapi-spec.yaml +++ b/prism-agent/service/api/http/prism-agent-openapi-spec.yaml @@ -541,6 +541,13 @@ paths: description: Accepts a credential offer received from a VC issuer and sends back a credential request. parameters: - $ref: "./pollux/parameters.yaml#/components/parameters/issueCredentialRecordIdInPath" + requestBody: + description: The accept credential offer request object. + required: true + content: + application/json: + schema: + $ref: ./pollux/schemas.yaml#/components/schemas/AcceptCredentialOfferRequest responses: "200": description: The issue credential offer was successfully accepted. diff --git a/prism-agent/service/project/Dependencies.scala b/prism-agent/service/project/Dependencies.scala index ea6d0d66dd..9a38a3d124 100644 --- a/prism-agent/service/project/Dependencies.scala +++ b/prism-agent/service/project/Dependencies.scala @@ -10,7 +10,7 @@ object Dependencies { val akka = "2.6.20" val akkaHttp = "10.2.9" val castor = "0.8.1" - val pollux = "0.39.0" + val pollux = "0.40.0" val connect = "0.11.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/Modules.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Modules.scala index 6a20424336..328c4b094d 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Modules.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/Modules.scala @@ -449,7 +449,7 @@ object AppModule { (didOpValidatorLayer ++ didServiceLayer ++ secretStorageLayer ++ nonSecretStorageLayer) >>> ManagedDIDService.layer } - val credentialServiceLayer: RLayer[DidOps & DidAgent, CredentialService] = + val credentialServiceLayer: RLayer[DidOps & DidAgent & JwtDidResolver, CredentialService] = (GrpcModule.layers ++ RepoModule.credentialRepoLayer) >>> CredentialServiceImpl.layer def presentationServiceLayer = @@ -508,8 +508,10 @@ object HttpModule { (apiServiceLayer ++ apiMarshallerLayer) >>> ZLayer.fromFunction(new DIDRegistrarApi(_, _)) } - val issueCredentialsProtocolApiLayer - : RLayer[DidOps & DidAgent & ManagedDIDService & ConnectionService & AppConfig, IssueCredentialsProtocolApi] = { + val issueCredentialsProtocolApiLayer: RLayer[ + DidOps & DidAgent & ManagedDIDService & ConnectionService & AppConfig & JwtDidResolver, + IssueCredentialsProtocolApi + ] = { val serviceLayer = AppModule.credentialServiceLayer val apiServiceLayer = serviceLayer >>> IssueCredentialsProtocolApiServiceImpl.layer val apiMarshallerLayer = IssueCredentialsProtocolApiMarshallerImpl.layer diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/IssueCredentialsProtocolApiMarshallerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/IssueCredentialsProtocolApiMarshallerImpl.scala index 9928e1735e..eaa2cc5a56 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/IssueCredentialsProtocolApiMarshallerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/marshaller/IssueCredentialsProtocolApiMarshallerImpl.scala @@ -16,6 +16,10 @@ object IssueCredentialsProtocolApiMarshallerImpl extends JsonSupport { : FromEntityUnmarshaller[CreateIssueCredentialRecordRequest] = summon[RootJsonFormat[CreateIssueCredentialRecordRequest]] + implicit def fromEntityUnmarshallerAcceptCredentialOfferRequest + : FromEntityUnmarshaller[AcceptCredentialOfferRequest] = + summon[RootJsonFormat[AcceptCredentialOfferRequest]] + implicit def toEntityMarshallerIssueCredentialRecord: ToEntityMarshaller[IssueCredentialRecord] = summon[RootJsonFormat[IssueCredentialRecord]] 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 bba1a37998..3afed1c0c0 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 @@ -70,6 +70,7 @@ trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { } // Issue given RootJsonFormat[CreateIssueCredentialRecordRequest] = jsonFormat7(CreateIssueCredentialRecordRequest.apply) + given RootJsonFormat[AcceptCredentialOfferRequest] = jsonFormat1(AcceptCredentialOfferRequest.apply) given RootJsonFormat[IssueCredentialRecord] = jsonFormat12(IssueCredentialRecord.apply) given RootJsonFormat[IssueCredentialRecordPage] = jsonFormat6(IssueCredentialRecordPage.apply) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/IssueCredentialsProtocolApiServiceImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/IssueCredentialsProtocolApiServiceImpl.scala index 9819a5daed..052cdff52b 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/IssueCredentialsProtocolApiServiceImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/service/IssueCredentialsProtocolApiServiceImpl.scala @@ -50,16 +50,11 @@ class IssueCredentialsProtocolApiServiceImpl( .fromEither(PrismDID.fromString(request.issuingDID)) .mapError(HttpServiceError.InvalidPayload.apply) .mapError(_.toOAS) - subjectId <- ZIO - .fromEither(PrismDID.fromString(request.subjectId)) - .mapError(HttpServiceError.InvalidPayload.apply) - .mapError(_.toOAS) outcome <- credentialService .createIssueCredentialRecord( pairwiseIssuerDID = didIdPair.myDID, pairwiseHolderDID = didIdPair.theirDid, thid = DidCommID(), - subjectId = subjectId.toString, schemaId = request.schemaId, claims = request.claims, validityPeriod = request.validityPeriod, @@ -121,14 +116,15 @@ class IssueCredentialsProtocolApiServiceImpl( } } - override def acceptCredentialOffer(recordId: String)(implicit + override def acceptCredentialOffer(recordId: String, request: AcceptCredentialOfferRequest)(implicit toEntityMarshallerIssueCredentialRecord: ToEntityMarshaller[IssueCredentialRecord], toEntityMarshallerErrorResponse: ToEntityMarshaller[ErrorResponse] ): Route = { val result = for { id <- recordId.toDidCommID + prismDID <- ZIO.fromEither(PrismDID.fromString(request.subjectId)).mapError(HttpServiceError.InvalidPayload.apply) outcome <- credentialService - .acceptCredentialOffer(id) + .acceptCredentialOffer(id, request.subjectId) .mapError(HttpServiceError.DomainError[CredentialServiceError].apply) } yield outcome diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/BackgroundJobs.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/BackgroundJobs.scala index c8fdf6bca3..2e684dfceb 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/BackgroundJobs.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/BackgroundJobs.scala @@ -1,62 +1,64 @@ package io.iohk.atala.agent.server.jobs -import scala.jdk.CollectionConverters.* -import zio.* -import io.iohk.atala.pollux.core.service.CredentialService -import io.iohk.atala.pollux.core.model.IssueCredentialRecord -import io.iohk.atala.pollux.core.model.error.CredentialServiceError -import io.iohk.atala.pollux.core.service.CredentialService -import io.iohk.atala.pollux.vc.jwt.W3cCredentialPayload -import zio.Duration -import java.time.Instant -import java.time.Clock -import java.time.ZoneId +import cats.syntax.all._ +import com.ionspin.kotlin.bignum.integer.BigInteger +import com.ionspin.kotlin.bignum.integer.Sign +import io.circe.Json +import io.circe.parser._ +import io.circe.syntax._ +import io.iohk.atala.agent.server.config.AppConfig +import io.iohk.atala.agent.server.http.model.InvalidState +import io.iohk.atala.agent.server.http.model.NotImplemented +import io.iohk.atala.agent.walletapi.model._ +import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.KeyNotFoundError +import io.iohk.atala.agent.walletapi.model.error._ +import io.iohk.atala.agent.walletapi.service.ManagedDIDService +import io.iohk.atala.castor.core.model.did._ +import io.iohk.atala.castor.core.service.DIDService import io.iohk.atala.mercury._ import io.iohk.atala.mercury.model._ import io.iohk.atala.mercury.model.error._ import io.iohk.atala.mercury.protocol.issuecredential._ import io.iohk.atala.mercury.protocol.presentproof._ import io.iohk.atala.mercury.protocol.reportproblem.v2._ -import io.iohk.atala.resolvers.DIDResolver -import io.iohk.atala.resolvers.UniversalDidResolver -import java.io.IOException -import io.iohk.atala.pollux.vc.jwt._ -import io.iohk.atala.pollux.vc.jwt.W3CCredential +import io.iohk.atala.pollux.core.model.IssueCredentialRecord import io.iohk.atala.pollux.core.model._ -import io.iohk.atala.pollux.core.service.PresentationService +import io.iohk.atala.pollux.core.model.error.CredentialServiceError import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.error.PresentationError._ -import io.iohk.atala.agent.server.http.model.{InvalidState, NotImplemented} -import org.didcommx.didcomm.DIDComm -import io.iohk.atala.agent.walletapi.model._ -import io.iohk.atala.agent.walletapi.model.error._ -import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.KeyNotFoundError -import io.iohk.atala.agent.walletapi.service.ManagedDIDService +import io.iohk.atala.pollux.core.service.CredentialService +import io.iohk.atala.pollux.core.service.PresentationService import io.iohk.atala.pollux.vc.jwt.ES256KSigner -import io.iohk.atala.castor.core.model.did._ -import java.security.KeyFactory -import java.security.spec.EncodedKeySpec -import java.security.spec.ECPrivateKeySpec -import org.bouncycastle.jce.spec.ECNamedCurveSpec -import org.bouncycastle.jce.ECNamedCurveTable -import com.ionspin.kotlin.bignum.integer.BigInteger -import com.ionspin.kotlin.bignum.integer.Sign -import io.iohk.atala.pollux.vc.jwt.Issuer -import java.security.spec.ECPublicKeySpec -import java.security.spec.ECPoint -import org.bouncycastle.jce.provider.BouncyCastleProvider -import io.circe.Json -import io.circe.syntax._ import io.iohk.atala.pollux.vc.jwt.JWT +import io.iohk.atala.pollux.vc.jwt.W3CCredential +import io.iohk.atala.pollux.vc.jwt.W3cCredentialPayload +import io.iohk.atala.pollux.vc.jwt.JwtPresentation +import io.iohk.atala.pollux.vc.jwt.CredentialVerification import io.iohk.atala.pollux.vc.jwt.{DidResolver => JwtDidResolver} -import io.iohk.atala.agent.server.config.AppConfig -import io.circe.parser._ +import io.iohk.atala.pollux.vc.jwt.{Issuer => JwtIssuer} +import io.iohk.atala.resolvers.DIDResolver +import io.iohk.atala.resolvers.UniversalDidResolver +import org.bouncycastle.jce.ECNamedCurveTable +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.jce.spec.ECNamedCurveSpec +import org.didcommx.didcomm.DIDComm +import zio.Duration +import zio.* import zio.prelude.AssociativeBothOps import zio.prelude.Validation import zio.prelude.ZValidation._ -import cats.syntax.all._ -import io.iohk.atala.castor.core.service.DIDService + +import java.io.IOException +import java.security.KeyFactory +import java.security.spec.ECPoint +import java.security.spec.ECPrivateKeySpec +import java.security.spec.ECPublicKeySpec +import java.security.spec.EncodedKeySpec +import java.time.Clock +import java.time.Instant +import java.time.ZoneId import java.util.UUID +import scala.jdk.CollectionConverters.* object BackgroundJobs { @@ -68,6 +70,7 @@ object BackgroundJobs { .getIssueCredentialRecordsByStates( IssueCredentialRecord.ProtocolState.OfferPending, IssueCredentialRecord.ProtocolState.RequestPending, + IssueCredentialRecord.ProtocolState.RequestGenerated, IssueCredentialRecord.ProtocolState.RequestReceived, IssueCredentialRecord.ProtocolState.CredentialPending, IssueCredentialRecord.ProtocolState.CredentialGenerated @@ -151,13 +154,48 @@ object BackgroundJobs { _, _, Role.Holder, - _, + Some(subjectId), _, _, _, RequestPending, _, _, + None, + _, + _, + _, + _, + _, + _ + ) => + for { + credentialService <- ZIO.service[CredentialService] + subjectDID <- ZIO + .fromEither(PrismDID.fromString(subjectId)) + .mapError(_ => CredentialServiceError.UnsupportedDidFormat(subjectId)) + longFormPrismDID <- getLongForm(subjectDID, true) + jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication) + presentationPayload <- credentialService.createPresentationPayload(id, jwtIssuer) + signedPayload = JwtPresentation.encodeJwt(presentationPayload.toJwtPresentationPayload, jwtIssuer) + _ <- credentialService.generateCredentialRequest(id, signedPayload) + } yield () + + // Request should be sent from Holder to Issuer + case IssueCredentialRecord( + id, + _, + _, + _, + _, + Role.Holder, + _, + _, + _, + _, + RequestGenerated, + _, + _, Some(request), _, _, @@ -224,7 +262,7 @@ object BackgroundJobs { _, Some(issue), _, - Some(issuingDID), + Some(issuerDID), _, _, _, @@ -234,13 +272,14 @@ object BackgroundJobs { // Set PublicationState to PublicationPending for { credentialService <- ZIO.service[CredentialService] - issuer <- createPrismDIDIssuer(issuingDID) + longFormPrismDID <- getLongForm(issuerDID, false) + jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.AssertionMethod) w3Credential <- credentialService.createCredentialPayloadFromRecord( record, - issuer, + jwtIssuer, Instant.now() ) - signedJwtCredential = W3CCredential.toEncodedJwt(w3Credential, issuer) + signedJwtCredential = W3CCredential.toEncodedJwt(w3Credential, jwtIssuer) issueCredential = IssueCredential.build( fromDID = issue.from, toDID = issue.to, @@ -350,49 +389,56 @@ object BackgroundJobs { } - // TODO: Improvements needed here: - // - For now, we include the long form in the JWT credential to facilitate validation on client-side, but resolution should be used instead. - // - Improve consistency in error handling (ATL-3210) - private[this] def createPrismDIDIssuer( - issuingDID: PrismDID, - verificationRelationship: VerificationRelationship = VerificationRelationship.AssertionMethod, + private[this] def getLongForm( + did: PrismDID, allowUnpublishedIssuingDID: Boolean = false - ): ZIO[DIDService & ManagedDIDService, Throwable, Issuer] = { + ): ZIO[ManagedDIDService, Throwable, LongFormPrismDID] = { for { managedDIDService <- ZIO.service[ManagedDIDService] - didService <- ZIO.service[DIDService] didState <- managedDIDService - .getManagedDIDState(issuingDID.asCanonical) - .mapError(e => RuntimeException(s"Error occured while getting did from wallet: ${e.toString}")) - .someOrFail(RuntimeException(s"Issuer DID does not exist in the wallet: $issuingDID")) + .getManagedDIDState(did.asCanonical) + .mapError(e => RuntimeException(s"Error occurred while getting did from wallet: ${e.toString}")) + .someOrFail(RuntimeException(s"Issuer DID does not exist in the wallet: $did")) .flatMap { case s: ManagedDIDState.Published => ZIO.succeed(s) case s if allowUnpublishedIssuingDID => ZIO.succeed(s) - case _ => ZIO.fail(RuntimeException(s"Issuer DID must be published: $issuingDID")) + case _ => ZIO.fail(RuntimeException(s"Issuer DID must be published: $did")) } longFormPrismDID = PrismDID.buildLongFormFromOperation(didState.createOperation) + } yield longFormPrismDID + } + + // TODO: Improvements needed here: + // - Improve consistency in error handling (ATL-3210) + private[this] def createJwtIssuer( + jwtIssuerDID: PrismDID, + verificationRelationship: VerificationRelationship + ): ZIO[DIDService & ManagedDIDService, Throwable, JwtIssuer] = { + for { + managedDIDService <- ZIO.service[ManagedDIDService] + didService <- ZIO.service[DIDService] // Automatically infer keyId to use by resolving DID and choose the corresponding VerificationRelationship issuingKeyId <- didService - .resolveDID(issuingDID) + .resolveDID(jwtIssuerDID) .mapError(e => RuntimeException(s"Error occured while resolving Issuing DID during VC creation: ${e.toString}")) .someOrFail(RuntimeException(s"Issuing DID resolution result is not found")) .map { case (_, didData) => didData.publicKeys.find(_.purpose == verificationRelationship).map(_.id) } .someOrFail( - RuntimeException(s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $issuingDID") + RuntimeException(s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID") ) ecKeyPair <- managedDIDService - .javaKeyPairWithDID(issuingDID.asCanonical, issuingKeyId) + .javaKeyPairWithDID(jwtIssuerDID.asCanonical, issuingKeyId) .mapError(e => RuntimeException(s"Error occurred while getting issuer key-pair: ${e.toString}")) .someOrFail( - RuntimeException(s"Issuer key-pair does not exist in the wallet: ${issuingDID.toString}#$issuingKeyId") + RuntimeException(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId") ) (privateKey, publicKey) = ecKeyPair - issuer = Issuer( - io.iohk.atala.pollux.vc.jwt.DID(longFormPrismDID.toString), + jwtIssuer = JwtIssuer( + io.iohk.atala.pollux.vc.jwt.DID(jwtIssuerDID.toString), ES256KSigner(privateKey), publicKey ) - } yield issuer + } yield jwtIssuer } private[this] def createPrismDIDIssuerFromPresentationCredentials( @@ -415,6 +461,9 @@ object BackgroundJobs { .getIssueCredentialRecord(credentialRecordUuid) .someOrFail(CredentialServiceError.RecordIdNotFound(credentialRecordUuid)) .map(_.subjectId) + .someOrFail( + CredentialServiceError.UnexpectedError(s"VC SubjectId not found in credential record: $credentialRecordUuid") + ) proverDID <- ZIO .fromEither(PrismDID.fromString(vcSubjectId)) .mapError(e => @@ -423,12 +472,9 @@ object BackgroundJobs { s"One of the credential(s) subject is not a valid Prism DID: ${vcSubjectId}" ) ) - prover <- createPrismDIDIssuer( - proverDID, - verificationRelationship = VerificationRelationship.Authentication, - allowUnpublishedIssuingDID = true - ) - } yield prover + longFormPrismDID <- getLongForm(proverDID, true) + jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication) + } yield jwtIssuer private[this] def performPresentation( record: PresentationRecord diff --git a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt index 0b23468774..600aff74ba 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt @@ -3,6 +3,7 @@ package api_models data class PresentationProof( var presentationId: String? = null, var status: String? = null, + var connectionId: String? = null, var proofs: List? = null, var data: List? = null, ) diff --git a/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt b/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt index 8d1d45fd3a..66c0d2a2d6 100644 --- a/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt @@ -19,7 +19,6 @@ class IssueCredentialsSteps { fun acmeOffersACredential(issuer: Actor, holder: Actor) { val newCredential = Credential( schemaId = "schema:1234", - subjectId = holder.recall("longFormDid"), validityPeriod = 3600, automaticIssuance = false, awaitConfirmation = false, @@ -44,35 +43,6 @@ class IssueCredentialsSteps { ) } - @When("{actor} offers a credential with unpublished did to {actor}") - fun acmeOffersACredentialWithUnpublishedDids(issuer: Actor, holder: Actor) { - val newCredential = Credential( - schemaId = "schema:1234", - subjectId = holder.recall("longFormDid"), - validityPeriod = 3600, - automaticIssuance = false, - awaitConfirmation = false, - claims = linkedMapOf( - "firstName" to "FirstName", - "lastName" to "LastName", - ), - issuingDID = issuer.recall("longFormDid"), - connectionId = issuer.recall("connection-with-${holder.name}").connectionId, - ) - - issuer.attemptsTo( - Post.to("/issue-credentials/credential-offers") - .with { - it.body(newCredential) - }, - ) - issuer.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_CREATED) - }, - ) - } - @When("{actor} receives the credential offer and accepts") fun bobRequestsTheCredential(holder: Actor) { wait( @@ -95,7 +65,12 @@ class IssueCredentialsSteps { holder.remember("recordId", recordId) holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer"), + Post.to("/issue-credentials/records/$recordId/accept-offer") + .with { + it.body(""" + { "subjectId": "${holder.recall("longFormDid")}" } + """.trimIndent()) + }, ) holder.should( ResponseConsequence.seeThatResponse("Accept offer") { diff --git a/tests/e2e-tests/src/test/resources/features/issue_credentials/issue_credentials.feature b/tests/e2e-tests/src/test/resources/features/issue_credentials/issue_credentials.feature index f3cd3e66e9..ab45bec527 100644 --- a/tests/e2e-tests/src/test/resources/features/issue_credentials/issue_credentials.feature +++ b/tests/e2e-tests/src/test/resources/features/issue_credentials/issue_credentials.feature @@ -10,14 +10,3 @@ Scenario: Issuing credential with published PRISM DID to unpublished PRISM DID And Bob receives the credential offer and accepts And Acme issues the credential Then Bob receives the issued credential - -Scenario: Issuing credential with published PRISM DID to published PRISM DID - Given Acme and Bob have an existing connection - When Acme creates unpublished DID - And He publishes DID to ledger - And Bob creates unpublished DID - And He publishes DID to ledger - And Acme offers a credential to Bob - And Bob receives the credential offer and accepts - And Acme issues the credential - Then Bob receives the issued credential