Skip to content

Commit

Permalink
feat(prism-agent): move subjectId field from issuer to holder (#435)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
bvoiturier and Anton Baliasnikov authored Mar 10, 2023
1 parent 4377d58 commit d7813ba
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 132 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 }}
Expand Down
17 changes: 12 additions & 5 deletions docs/docusaurus/credentials/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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`.
Expand Down
13 changes: 12 additions & 1 deletion prism-agent/service/api/http/pollux/schemas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ components:

IssueCredentialRecordBase:
required:
- subjectId
- claims
properties:
schemaId:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -260,6 +270,7 @@ components:
- OfferSent
- OfferReceived
- RequestPending
- RequestGenerated
- RequestSent
- RequestReceived
- ProblemReportPending
Expand Down
7 changes: 7 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 @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion prism-agent/service/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
Loading

0 comments on commit d7813ba

Please sign in to comment.