diff --git a/README.md b/README.md index 53f4fa70c..9a2c90247 100644 --- a/README.md +++ b/README.md @@ -30,48 +30,50 @@ The Business Partner Agent is built on top of the Hyperledger Self-Sovereign Ide ## Features in Detail -| Role/Feature | Flow | Protocol Version | -|------------------|-------------------------------------------------------------------------|-----------------------------------| -| Issuer | | | -| | auto: issue credential | indy: v1, v2 | -| | manual: send credential offer to holder | indy: v1, v2 | -| | manual: receive credential proposal from holder | indy: v1, v2 | -| | manual: decline credential proposal from holder and provide reason | indy: v1, v2 | -| | revoke issued credential (requires tails server) | n/a | -| Holder | | | -| | auto: receive credential | indy: v1, v2 | -| | manual: send credential proposal to issuer (based on document) | indy: v1, v2 | -| | manual: receive credential offer from issuer | indy: v1, v2 | -| | manual: decline credential offer from issuer | indy: v1, v2 | -| | scheduled revocation check on all received credentials | n/a | -| Prover | | | -| | auto: send presentation to verifier | indy: v1, v2 | -| | auto: answer presentation request | indy: v1, v2 | -| | manual: accept/decline presentation request and provide reason | indy: v1, v2 | -| Verifier | | | -| | auto: request presentation from prover based on proof template | indy: v1, v2 | -| | auto: receive and verify presentation from prover | indy: v1, v2 | -| Connection | | | -| | connect by did:sov, did:web (if endpoint is aca-py) | did-exchange | -| | receive invitation by URL | connection-protocol, OOB | -| | create invitation (barcode or URL) | connection-protocol, OOB | -| | auto: accept incoming connection | did-exchange, connection-protocol | -| | manual: accept incoming connection | did-exchange, connection-protocol | -| | optional: scheduled trust ping to check connection status | n/a | -| | tag a connection, e.g. as trusted issuer | n/a | -| Ledger | | | -| | send schema to the ledger (requires endorser role) | n/a | -| | create a credential definition on the ledger (requires endorser role) | n/a | -| Basic Message | | | -| | send and receive basic messages via chat window | n/a | -| Tasks/Activities | | | -| | list of tasks that need attention, and list of past activities | n/a | -| TAA | | | -| | if ledger is configured with a TAA, show it and give option to accept | n/a | -| Read Only Ledger | | | -| | if mode is set to web only | n/a | -| Public Profile | | | -| | web accessible (self signed) imprint based on credentials or documents | n/a | +| Role/Feature | Flow | Protocol Version | +|------------------|------------------------------------------------------------------------|-----------------------------------| +| Issuer | | | +| | auto: issue credential | indy: v1, v2
w3c: v2 | +| | manual: send credential offer to holder | indy: v1, v2
w3c: v2 | +| | manual: receive credential proposal from holder | indy: v1, v2
w3c: v2 | +| | manual: decline credential proposal from holder and provide reason | indy: v1, v2
w3c: v2 | +| | revoke issued credential (requires tails server) | indy: v1, v2
w3c: n/a | +| | send revocation notification | indy: v1, v2
w3c: n/a | +| Holder | | | +| | auto: receive credential | indy: v1, v2
w3c: v2 | +| | manual: send credential proposal to issuer (based on document) | indy: v1, v2
w3c: v2 | +| | manual: receive credential offer from issuer | indy: v1, v2
w3c: v2 | +| | manual: decline credential offer from issuer | indy: v1, v2
w3c: v2 | +| | scheduled revocation check on all received credentials | indy: v1, v2
w3c: n/a | +| | receive revocation notification | indy: v1, v2
w3c: n/a | +| Prover | | | +| | auto: send presentation to verifier | indy: v1, v2 | +| | auto: answer presentation request | indy: v1, v2 | +| | manual: accept/decline presentation request and provide reason | indy: v1, v2 | +| Verifier | | | +| | auto: request presentation from prover based on proof template | indy: v1, v2 | +| | auto: receive and verify presentation from prover | indy: v1, v2 | +| Connection | | | +| | connect by did:sov, did:web (if endpoint is aca-py) | did-exchange | +| | receive invitation by URL | connection-protocol, OOB | +| | create invitation (barcode or URL) | connection-protocol, OOB | +| | auto: accept incoming connection | did-exchange, connection-protocol | +| | manual: accept incoming connection | did-exchange, connection-protocol | +| | optional: scheduled trust ping to check connection status | n/a | +| | tag a connection, e.g. as trusted issuer | n/a | +| Ledger | | | +| | send schema to the ledger (requires endorser role) | n/a | +| | create a credential definition on the ledger (requires endorser role) | n/a | +| Basic Message | | | +| | send and receive basic messages via chat window | n/a | +| Tasks/Activities | | | +| | list of tasks that need attention, and list of past activities | n/a | +| TAA | | | +| | if ledger is configured with a TAA, show it and give option to accept | n/a | +| Read Only Ledger | | | +| | if mode is set to web only | n/a | +| Public Profile | | | +| | web accessible (self signed) imprint based on credentials or documents | n/a | ## Upcoming Features @@ -113,7 +115,7 @@ Learn how to contribute in [Contributing](CONTRIBUTING.md). You can also start b Regarding release process, we do not follow a strict process yet, nevertheless we follow the guidelines described in [Publishing](PUBLISHING.md). -Learn what aries protocols can be controlled by the BPA in [aca-py-args](scripts/aca-py-args.md) +Learn what aries protocols can be controlled by the BPA in [aca-py-args](scripts/acapy-static-args.yml) ## Business Partner Agent in Action - [COP26 Presented by BC Goverment and OpenEarth Foundation](https://www.youtube.com/watch?v=q0Jml3isSh8) diff --git a/backend/business-partner-agent/pom.xml b/backend/business-partner-agent/pom.xml index 06500a82a..936e6d58c 100644 --- a/backend/business-partner-agent/pom.xml +++ b/backend/business-partner-agent/pom.xml @@ -128,7 +128,7 @@ network.idu.acapy aries-client-python - 0.7.20 + 0.7.22 org.hyperledger.business-partner-agent @@ -170,7 +170,7 @@ io.micronaut.email micronaut-email-mailjet - 1.0.0 + 1.0.1 javax.activation diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/AriesCredential.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/AriesCredential.java index 3a390f209..4c2ea8da5 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/AriesCredential.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/AriesCredential.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils; import org.hyperledger.aries.api.ExchangeVersion; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; +import org.hyperledger.bpa.impl.aries.jsonld.LDContextHelper; import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import java.util.Map; @@ -52,12 +53,17 @@ public class AriesCredential { public static AriesCredential fromBPACredentialExchange(@NonNull BPACredentialExchange c, @Nullable String typeLabel) { AriesCredentialBuilder b = AriesCredential.builder(); - if (c.getCredential() != null) { + if (c.typeIsIndy() && c.getIndyCredential() != null) { b - .schemaId(c.getCredential().getSchemaId()) - .credentialDefinitionId(c.getCredential().getCredentialDefinitionId()) - .revocable(StringUtils.isNotEmpty(c.getCredential().getRevRegId())) - .credentialData(c.getCredential().getAttrs()); + .schemaId(c.getIndyCredential().getSchemaId()) + .credentialDefinitionId(c.getIndyCredential().getCredentialDefinitionId()) + .revocable(StringUtils.isNotEmpty(c.getIndyCredential().getRevRegId())); + } else if (c.typeIsJsonLd()) { + b + .schemaId(LDContextHelper.findSchemaId( + c.exchangePayloadByState() != null ? c.exchangePayloadByState().getLdProof() : null)) + .revocable(false) // not supported with ld-credentials + ; } return b .id(c.getId()) @@ -70,6 +76,7 @@ public static AriesCredential fromBPACredentialExchange(@NonNull BPACredentialEx .label(c.getLabel()) .typeLabel(typeLabel) .exchangeVersion(c.getExchangeVersion()) + .credentialData(c.attributesByState()) .build(); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/SchemaAPI.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/SchemaAPI.java index e776012e9..c9270eb20 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/SchemaAPI.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/api/aries/SchemaAPI.java @@ -52,6 +52,8 @@ public class SchemaAPI { private Set schemaAttributeNames; + private String defaultAttributeName; + private List trustedIssuer; // ld only @@ -103,6 +105,7 @@ public static SchemaAPI from(BPASchema s, boolean includeRestrictions, boolean i .label(s.getLabel()) .schemaId(s.getSchemaId()) .schemaAttributeNames(s.getSchemaAttributeNames() != null ? s.getSchemaAttributeNames() : Set.of()) + .defaultAttributeName(s.getDefaultAttributeName()) .build(); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/AdminController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/AdminController.java index 218e9f098..1a0a3a1c0 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/AdminController.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/AdminController.java @@ -40,6 +40,7 @@ import org.hyperledger.bpa.impl.aries.schema.SchemaService; import org.hyperledger.bpa.impl.mode.indy.EndpointService; +import javax.validation.Valid; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -77,7 +78,7 @@ public HttpResponse> listSchemas() { } /** - * Get a configured schema + * Get a configured schema by id * * @param id {@link UUID} the schema id * @return {@link HttpResponse} @@ -89,13 +90,13 @@ public HttpResponse getSchema(@PathVariable UUID id) { } /** - * Add a schema configuration + * Import an existing indy or json-ld schema as schema configuration * * @param req {@link AddSchemaRequest} * @return {@link HttpResponse} */ @Post("/schema") - public HttpResponse addSchema(@Body AddSchemaRequest req) { + public HttpResponse addSchema(@Body @Valid AddSchemaRequest req) { if (req instanceof AddSchemaRequest.AddIndySchema) { return HttpResponse.ok(schemaService.addIndySchema(req.getSchemaId(), req.getLabel(), req.getDefaultAttributeName(), ((AddSchemaRequest.AddIndySchema) req).getTrustedIssuer())); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/IssuerController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/IssuerController.java index f856e4c1c..db7117134 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/IssuerController.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/IssuerController.java @@ -34,13 +34,13 @@ import org.hyperledger.bpa.api.aries.SchemaAPI; import org.hyperledger.bpa.controller.api.invitation.APICreateInvitationResponse; import org.hyperledger.bpa.controller.api.issuer.*; -import org.hyperledger.bpa.impl.aries.credential.IssuerCredentialManager; +import org.hyperledger.bpa.impl.aries.creddef.CredDefManager; +import org.hyperledger.bpa.impl.aries.credential.IssuerManager; import org.hyperledger.bpa.impl.aries.credential.OOBCredentialOffer; import org.hyperledger.bpa.impl.aries.schema.SchemaService; import javax.validation.Valid; import java.util.List; -import java.util.Optional; import java.util.UUID; import static org.hyperledger.bpa.controller.IssuerController.ISSUER_CONTROLLER_BASE_URL; @@ -55,7 +55,10 @@ public class IssuerController { public static final String ISSUER_CONTROLLER_BASE_URL = "/api/issuer"; @Inject - IssuerCredentialManager im; + IssuerManager im; + + @Inject + CredDefManager credDef; @Inject OOBCredentialOffer connectionLess; @@ -64,42 +67,17 @@ public class IssuerController { SchemaService schemaService; /** - * List configured schemas - * - * @return list of {@link SchemaAPI} - */ - @Get("/schema") - public HttpResponse> listSchemas() { - return HttpResponse.ok(schemaService.listSchemas()); - } - - /** - * Create a new schema configuration + * Create a new schema on the indy ledger and import it * * @param req {@link CreateSchemaRequest} - * @return {@link HttpResponse} + * @return {@link SchemaAPI} */ @Post("/schema") - public HttpResponse createSchema(@Body CreateSchemaRequest req) { + public HttpResponse createSchema(@Body @Valid CreateSchemaRequest req) { return HttpResponse.ok(schemaService.createSchema(req.getSchemaName(), req.getSchemaVersion(), req.getAttributes(), req.getSchemaLabel(), req.getDefaultAttributeName())); } - /** - * Get a configured schema by id - * - * @param id {@link UUID} the schema id - * @return {@link HttpResponse} - */ - @Get("/schema/{id}") - public HttpResponse readSchema(@PathVariable UUID id) { - final Optional schema = schemaService.getSchema(id); - if (schema.isPresent()) { - return HttpResponse.ok(schema.get()); - } - return HttpResponse.notFound(); - } - /** * List credential definitions, items that I can issue * @@ -107,45 +85,44 @@ public HttpResponse readSchema(@PathVariable UUID id) { */ @Get("/creddef") public HttpResponse> listCredDefs() { - return HttpResponse.ok(im.listCredDefs()); + return HttpResponse.ok(credDef.listCredDefs()); } /** - * Create a new credential definition + * Create a new indy credential definition, and send it to the ledger * * @param req {@link CreateCredDefRequest} - * @return {@link HttpResponse} + * @return {@link CredDef} */ @Post("/creddef") public HttpResponse createCredDef(@Body CreateCredDefRequest req) { - return HttpResponse.ok(im.createCredDef(req.getSchemaId(), req.getTag(), req.isSupportRevocation())); + return HttpResponse.ok(credDef.createCredDef(req.getSchemaId(), req.getTag(), req.isSupportRevocation())); } /** - * Delete a credential definition + * Delete a indy credential definition (will not delete it from the ledger) * * @param id {@link UUID} the cred def id * @return {@link HttpResponse} */ @Delete("/creddef/{id}") public HttpResponse deleteCredDef(@PathVariable UUID id) { - im.deleteCredDef(id); + credDef.deleteCredDef(id); return HttpResponse.ok(); } /** * Auto credential exchange: Issuer sends credential to holder * - * @param req {@link IssueCredentialSendRequest} + * @param req {@link IssueCredentialRequest} * @return {@link HttpResponse} */ @Post("/issue-credential/send") - public HttpResponse issueCredentialSend(@Valid @Body IssueCredentialSendRequest req) { - String exchange = im.issueCredential( - IssuerCredentialManager.IssueCredentialRequest.from(req)); + public HttpResponse issueCredential(@Valid @Body IssueCredentialRequest req) { + String exchangeId = im.issueCredential(req); // just return the id and not the full Aries Object. // Event handlers will create the db cred ex records - return HttpResponse.ok(exchange); + return HttpResponse.ok(exchangeId); } /** @@ -176,7 +153,7 @@ public HttpResponse handleConnectionLess(@PathVariable UUID id) { } /** - * List issued credentials + * List issued or received credentials * * @return list of {@link CredEx} */ @@ -194,18 +171,18 @@ public HttpResponse> listCredentialExchanges( */ @Get("/exchanges/{id}") public HttpResponse getCredentialExchange(@PathVariable UUID id) { - return HttpResponse.ok(im.getCredEx(id)); + return HttpResponse.ok(im.findCredentialExchangeById(id)); } /** * Revoke an issued credential * * @param id credential exchange id - * @return {@link HttpResponse} + * @return {@link CredEx} */ @Put("/exchanges/{id}/revoke") public HttpResponse revokeCredential(@PathVariable UUID id) { - return HttpResponse.ok(im.revokeCredentialExchange(id)); + return HttpResponse.ok(im.revokeCredential(id)); } /** @@ -230,7 +207,8 @@ public HttpResponse reIssueCredential(@PathVariable UUID id) { * @return {@link CredEx} */ @Put("/exchanges/{id}/send-offer") - public HttpResponse sendCredentialOffer(@PathVariable UUID id, @Body CredentialOfferRequest counterOffer) { + public HttpResponse sendCredentialOffer(@PathVariable UUID id, + @Body @Valid CredentialOfferRequest counterOffer) { return HttpResponse.ok(im.sendCredentialOffer(id, counterOffer)); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/PartnerController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/PartnerController.java index 4247f35f6..10331dfae 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/PartnerController.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/PartnerController.java @@ -42,7 +42,7 @@ import org.hyperledger.bpa.impl.activity.PartnerLookup; import org.hyperledger.bpa.impl.aries.chat.ChatMessageManager; import org.hyperledger.bpa.impl.aries.chat.ChatMessageService; -import org.hyperledger.bpa.impl.aries.credential.HolderCredentialManager; +import org.hyperledger.bpa.impl.aries.credential.HolderManager; import org.hyperledger.bpa.impl.aries.proof.ProofManager; import org.hyperledger.bpa.impl.aries.prooftemplates.ProofTemplateManager; import org.hyperledger.bpa.persistence.model.ChatMessage; @@ -66,7 +66,7 @@ public class PartnerController { PartnerLookup partnerLookup; @Inject - HolderCredentialManager credM; + HolderManager credM; @Inject ProofManager proofM; @@ -224,7 +224,7 @@ public HttpResponse refreshPartner(@PathVariable UUID id) { public HttpResponse requestCredential( @PathVariable UUID id, @Body RequestCredentialRequest credReq) { - credM.sendCredentialRequest( + credM.sendCredentialProposal( id, UUID.fromString(credReq.getDocumentId()), credReq.getExchangeVersion()); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/WalletController.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/WalletController.java index 488a99356..1755a11b7 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/WalletController.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/WalletController.java @@ -36,7 +36,7 @@ import org.hyperledger.bpa.controller.api.wallet.WalletCredentialRequest; import org.hyperledger.bpa.controller.api.wallet.WalletDocumentRequest; import org.hyperledger.bpa.impl.MyDocumentManager; -import org.hyperledger.bpa.impl.aries.credential.HolderCredentialManager; +import org.hyperledger.bpa.impl.aries.credential.HolderManager; import java.util.List; import java.util.Optional; @@ -53,7 +53,7 @@ public class WalletController { MyDocumentManager docMgmt; @Inject - HolderCredentialManager holderCredMgmt; + HolderManager holderCredMgmt; // ------------------------------------- // Document Management @@ -134,11 +134,13 @@ public HttpResponse deleteDocument( /** * Aries: List wallet credentials * + * @param types {@link CredentialType} multi value list of types to filter * @return list of {@link AriesCredential} */ @Get("/credential") - public HttpResponse> getCredentials() { - return HttpResponse.ok(holderCredMgmt.listCredentials()); + public HttpResponse> getCredentials(@Parameter( + description = "types filter") @Nullable @QueryValue @Format("MULTI") List types) { + return HttpResponse.ok(holderCredMgmt.listHeldCredentials(types)); } /** @@ -149,7 +151,7 @@ public HttpResponse> getCredentials() { */ @Get("/credential/{id}") public HttpResponse getCredentialById(@PathVariable UUID id) { - return HttpResponse.ok(holderCredMgmt.getCredentialById(id)); + return HttpResponse.ok(holderCredMgmt.findHeldCredentialById(id)); } /** diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivitySearchParameters.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivitySearchParameters.java index bebbab9fb..02c8a34f7 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivitySearchParameters.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/activity/ActivitySearchParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -20,13 +20,11 @@ import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.Nullable; import io.micronaut.http.annotation.QueryValue; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor -@AllArgsConstructor @Introspected public class ActivitySearchParameters { @Nullable diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddSchemaRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddSchemaRequest.java index 6305eed16..c1c16f1ec 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddSchemaRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddSchemaRequest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.Nullable; import lombok.AllArgsConstructor; import lombok.Data; @@ -27,6 +28,7 @@ import lombok.experimental.SuperBuilder; import org.hyperledger.bpa.api.CredentialType; +import javax.validation.constraints.NotEmpty; import java.util.List; import java.util.Set; @@ -48,6 +50,7 @@ public abstract class AddSchemaRequest { @Nullable private String label; + @NotEmpty private String schemaId; @Nullable @@ -74,6 +77,7 @@ public boolean typeIsJsonLD() { @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) + @Introspected public static final class AddIndySchema extends AddSchemaRequest { @Nullable private List trustedIssuer; @@ -87,8 +91,11 @@ public AddIndySchema() { @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) + @Introspected public static final class AddJsonLDSchema extends AddSchemaRequest { + @NotEmpty private Set attributes; + @NotEmpty private String ldType; public AddJsonLDSchema() { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddTagRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddTagRequest.java index 2d04c8fff..6d7e4f117 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddTagRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/admin/AddTagRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -17,15 +17,11 @@ */ package org.hyperledger.bpa.controller.api.admin; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -@Builder @Data @NoArgsConstructor -@AllArgsConstructor public class AddTagRequest { private String name; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/APICreateInvitationResponse.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/APICreateInvitationResponse.java index baaa87f23..903fecefd 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/APICreateInvitationResponse.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/APICreateInvitationResponse.java @@ -17,7 +17,10 @@ */ package org.hyperledger.bpa.controller.api.invitation; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; @Data @Builder diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/AcceptInvitationRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/AcceptInvitationRequest.java index cda7d9118..64c7f282d 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/AcceptInvitationRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/invitation/AcceptInvitationRequest.java @@ -17,8 +17,6 @@ */ package org.hyperledger.bpa.controller.api.invitation; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hyperledger.bpa.persistence.model.Tag; @@ -27,8 +25,6 @@ @Data @NoArgsConstructor -@AllArgsConstructor -@Builder public class AcceptInvitationRequest { private String invitationBlock; private String alias; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateCredDefRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateCredDefRequest.java index 6743bcd02..b19d1bf3a 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateCredDefRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateCredDefRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -17,15 +17,11 @@ */ package org.hyperledger.bpa.controller.api.issuer; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -@Builder @Data @NoArgsConstructor -@AllArgsConstructor public class CreateCredDefRequest { // aries cred def fields diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateSchemaRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateSchemaRequest.java index abfc5ea1c..3c003cfa1 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateSchemaRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CreateSchemaRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -17,17 +17,16 @@ */ package org.hyperledger.bpa.controller.api.issuer; -import lombok.AllArgsConstructor; -import lombok.Builder; +import io.micronaut.core.annotation.Introspected; import lombok.Data; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotBlank; import java.util.List; -@Builder @Data @NoArgsConstructor -@AllArgsConstructor +@Introspected public class CreateSchemaRequest { // BPA fields private String schemaLabel; @@ -35,10 +34,13 @@ public class CreateSchemaRequest { private String defaultAttributeName; // aries schema fields... + @NotBlank private String schemaName; + @NotBlank private String schemaVersion; + @NotBlank private List attributes; } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredDef.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredDef.java index e6853c268..cce0e9501 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredDef.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredDef.java @@ -36,7 +36,6 @@ public class CredDef { private String credentialDefinitionId; private String tag; private Boolean supportRevocation; - private Integer revocationRegistrySize; private Long createdAt; private SchemaAPI schema; private String displayText; @@ -48,11 +47,10 @@ public static CredDef from(BPACredentialDefinition db) { .builder() .id(db.getId()) .schemaId(db.getSchema().getSchemaId()) - .schema(SchemaAPI.from(db.getSchema(), true, false)) .credentialDefinitionId(db.getCredentialDefinitionId()) + .schema(SchemaAPI.from(db.getSchema(), true, false)) .tag(db.getTag()) .supportRevocation(db.getIsSupportRevocation()) - .revocationRegistrySize(db.getRevocationRegistrySize()) .createdAt(db.getCreatedAt().toEpochMilli()) .displayText(displayText) .build(); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredEx.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredEx.java index 6b4bf802c..e1fa2e3bd 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredEx.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredEx.java @@ -70,7 +70,10 @@ public static CredEx from(@NonNull BPACredentialExchange db, PartnerAPI partner) : CredDef.builder().schema(schemaAPI).build(); String displayText = null; if (schemaAPI != null) { - displayText = String.format("%s (%s)", schemaAPI.getLabel(), schemaAPI.getVersion()); + displayText = String.format("%s", schemaAPI.getLabel()); + if (StringUtils.isNotEmpty(schemaAPI.getVersion())) { + displayText = displayText + String.format(" (%s)", schemaAPI.getVersion()); + } if (StringUtils.isNotBlank(credDef.getTag())) { displayText = displayText + String.format(" - %s", credDef.getTag()); } @@ -120,8 +123,8 @@ public static CredEx from(@NonNull BPACredentialExchange db, PartnerAPI partner) } private static Boolean checkIfRevocable(@NonNull BPACredentialExchange db) { - if (db.roleIsHolder() && db.getCredential() != null) { - return StringUtils.isNotEmpty(db.getCredential().getRevRegId()); + if (db.roleIsHolder() && db.getIndyCredential() != null) { + return StringUtils.isNotEmpty(db.getIndyCredential().getRevRegId()); } return StringUtils.isNotEmpty(db.getRevRegId()); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredentialOfferRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredentialOfferRequest.java index 0e786baa7..f6dc0f30a 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredentialOfferRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/CredentialOfferRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -17,18 +17,23 @@ */ package org.hyperledger.bpa.controller.api.issuer; +import io.micronaut.core.annotation.Introspected; import lombok.Data; import lombok.NoArgsConstructor; import org.hyperledger.aries.api.credentials.CredentialAttributes; +import javax.validation.constraints.NotEmpty; import java.util.List; import java.util.Map; @Data @NoArgsConstructor +@Introspected public class CredentialOfferRequest { private Boolean acceptProposal; private String credDefId; + private String schemaId; + @NotEmpty private Map attributes; public boolean acceptAll() { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialSendRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialRequest.java similarity index 72% rename from backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialSendRequest.java rename to backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialRequest.java index 564c11876..2187bdb85 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialSendRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueCredentialRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -26,29 +26,40 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.hyperledger.aries.api.ExchangeVersion; -import org.hyperledger.bpa.impl.verification.ValidUUID; +import org.hyperledger.bpa.api.CredentialType; import javax.validation.constraints.NotBlank; +import java.util.UUID; @Introspected -@Builder @Data @NoArgsConstructor @AllArgsConstructor -public class IssueCredentialSendRequest { +@Builder +public class IssueCredentialRequest { // bpa ids @NotBlank - @ValidUUID - private String credDefId; + private UUID credDefId; + private UUID schemaId; @NotBlank - @ValidUUID - private String partnerId; + private UUID partnerId; /** credential exchange api version */ private ExchangeVersion exchangeVersion; + /** credential exchange type */ + private CredentialType type; + /** credential body key value pairs */ @JsonRawValue @Schema(example = "{}") private JsonNode document; + + public boolean exchangeIsV1() { + return exchangeVersion == null || ExchangeVersion.V1.equals(exchangeVersion); + } + + public boolean typeIsIndy() { + return type == null || CredentialType.INDY.equals(type); + } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueOOBCredentialRequest.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueOOBCredentialRequest.java index 0c4f9e5f2..942161345 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueOOBCredentialRequest.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/issuer/IssueOOBCredentialRequest.java @@ -21,8 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import io.micronaut.core.annotation.Introspected; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hyperledger.bpa.persistence.model.Tag; @@ -32,10 +30,8 @@ import java.util.UUID; @Introspected -@Builder @Data @NoArgsConstructor -@AllArgsConstructor public class IssueOOBCredentialRequest { private String alias; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/proof/PresentationRequestVersion.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/proof/PresentationRequestVersion.java index 1a6d08333..fd14fa318 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/proof/PresentationRequestVersion.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/controller/api/proof/PresentationRequestVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2021 - for information on the respective copyright owner + * Copyright (c) 2020-2022 - for information on the respective copyright owner * see the NOTICE file and/or the repository at * https://github.com/hyperledger-labs/business-partner-agent * @@ -17,14 +17,12 @@ */ package org.hyperledger.bpa.controller.api.proof; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hyperledger.aries.api.ExchangeVersion; @Data @NoArgsConstructor -@AllArgsConstructor public class PresentationRequestVersion { /** presentation exchange api version */ private ExchangeVersion exchangeVersion; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DidResolver.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DidResolver.java index a47424bca..51730da88 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DidResolver.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DidResolver.java @@ -75,7 +75,7 @@ public class DidResolver { public void resolveDid(PartnerProof pp, @NonNull List identifiers) { Optional cr = identifiers.stream() .filter(i -> StringUtils.isNotEmpty(i.getSchemaId())) - .filter(i -> AriesStringUtil.schemaGetName(i.getSchemaId()).equals("commercialregister")) + .filter(i -> "commercialregister".equals(AriesStringUtil.schemaGetName(i.getSchemaId()))) .findAny(); try { if (cr.isPresent()) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DocumentValidator.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DocumentValidator.java index c420e5cce..e353cbf19 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DocumentValidator.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/DocumentValidator.java @@ -28,13 +28,16 @@ import org.hyperledger.bpa.api.exception.WrongApiUsageException; import org.hyperledger.bpa.config.BPAMessageSource; import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.persistence.model.BPASchema; import org.hyperledger.bpa.persistence.model.MyDocument; import org.hyperledger.bpa.persistence.repository.MyDocumentRepository; +import org.jetbrains.annotations.NotNull; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.SortedSet; /** * Does some validation on incoming {@link MyDocumentAPI} objects. Like this we @@ -51,6 +54,9 @@ public class DocumentValidator { @Setter SchemaService schemaService; + @Inject + Converter conv; + @Inject BPAMessageSource.DefaultMessageSource ms; @@ -71,11 +77,9 @@ public void validateExisting(@NonNull Optional existing, @NonNull My validateInternal(newDocument); } - public void validateAttributesAgainstSchema(@NonNull JsonNode attributes, @NonNull String schemaId) { + public void validateAttributesAgainstIndySchema(@NonNull JsonNode attributes, @NonNull String schemaId) { // validate document data against schema - BPASchema schema = schemaService.getSchemaFor(schemaId) - .orElseThrow(() -> new WrongApiUsageException( - ms.getMessage("api.schema.not.found", Map.of("id", schemaId)))); + BPASchema schema = findSchema(schemaId); Set attributeNames = schema.getSchemaAttributeNames(); // assuming flat structure attributes.fieldNames().forEachRemaining(fn -> { @@ -87,12 +91,25 @@ public void validateAttributesAgainstSchema(@NonNull JsonNode attributes, @NonNu }); } + public void validateAttributesAgainstLDSchema(@NonNull BPASchema bpaSchema, @NonNull Map document) { + SortedSet attributeNames = bpaSchema.getSchemaAttributeNames(); + document.keySet().forEach(k -> { + if (!"id".equals(k) && !attributeNames.contains(k)) { + throw new WrongApiUsageException( + ms.getMessage("api.document.validation.attribute.not.in.schema", + Map.of("attr", k))); + } + }); + } + private void validateInternal(@NonNull MyDocumentAPI document) { if (CredentialType.INDY.equals(document.getType())) { - if (StringUtils.isEmpty(document.getSchemaId())) { - throw new WrongApiUsageException(ms.getMessage("api.document.validation.schema.id.missing")); - } - validateAttributesAgainstSchema(document.getDocumentData(), document.getSchemaId()); + mustHaveSchemaId(document); + validateAttributesAgainstIndySchema(document.getDocumentData(), document.getSchemaId()); + } else if (CredentialType.JSON_LD.equals(document.getType())) { + mustHaveSchemaId(document); + validateAttributesAgainstLDSchema(findSchema(document.getSchemaId()), + conv.toStringMap(document.getDocumentData())); } } @@ -105,4 +122,16 @@ private void verifyOnlyOneOrgProfile(@NonNull MyDocumentAPI doc) { }); } } + + private @io.micronaut.core.annotation.NonNull BPASchema findSchema(@NotNull String schemaId) { + return schemaService.getSchemaFor(schemaId) + .orElseThrow(() -> new WrongApiUsageException( + ms.getMessage("api.schema.not.found", Map.of("id", schemaId)))); + } + + private void mustHaveSchemaId(@NotNull MyDocumentAPI document) { + if (StringUtils.isEmpty(document.getSchemaId())) { + throw new WrongApiUsageException(ms.getMessage("api.document.validation.schema.id.missing")); + } + } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/LabelStrategy.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/LabelStrategy.java index 61ee14ec3..3e4e1f1a1 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/LabelStrategy.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/activity/LabelStrategy.java @@ -18,6 +18,8 @@ package org.hyperledger.bpa.impl.activity; import com.fasterxml.jackson.databind.JsonNode; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.micronaut.core.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -25,10 +27,13 @@ import lombok.Setter; import org.apache.commons.lang3.StringUtils; import org.hyperledger.aries.api.credentials.Credential; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; import org.hyperledger.bpa.api.MyDocumentAPI; import org.hyperledger.bpa.api.aries.AriesCredential; import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.aries.jsonld.LDContextHelper; import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import org.hyperledger.bpa.persistence.model.BPASchema; import java.util.Map; @@ -50,48 +55,63 @@ public class LabelStrategy { @Inject SchemaService schemaService; - public @Nullable String apply(@NonNull MyDocumentAPI document) { + public void apply(@NonNull MyDocumentAPI document) { if (StringUtils.isBlank(document.getLabel())) { - Optional attr = findDefaultAttribute(document.getSchemaId()); - if (attr.isPresent()) { + findDefaultAttribute(document.getSchemaId()).ifPresent(attr -> { JsonNode documentData = document.getDocumentData(); - JsonNode value = documentData.findValue(attr.get()); + JsonNode value = documentData.findValue(attr); if (value != null) { String label = value.asText(); document.setLabel(label); - return label; } - } - document.setLabel(null); + }); } - return null; } - public @Nullable String apply(@Nullable Credential credential) { - if (credential != null) { - Optional attr = findDefaultAttribute(credential.getSchemaId()); - if (attr.isPresent() && credential.getAttrs() != null) { - Map attrs = credential.getAttrs(); + public @Nullable String apply(@Nullable Credential ariesCredential) { + if (ariesCredential != null) { + Optional attr = findDefaultAttribute(ariesCredential.getSchemaId()); + if (attr.isPresent() && ariesCredential.getAttrs() != null) { + Map attrs = ariesCredential.getAttrs(); return attrs.get(attr.get()); } } return null; } - public @Nullable String apply(@Nullable String newLabel, @NonNull AriesCredential credential) { + public @Nullable String apply(@Nullable String newLabel, @NonNull AriesCredential ariesCredential) { String mergedLabel = null; if (StringUtils.isNotBlank(newLabel)) { mergedLabel = newLabel; } else { - Optional attr = findDefaultAttribute(credential.getSchemaId()); - if (attr.isPresent() && credential.getCredentialData() != null) { - Map attrs = credential.getCredentialData(); + Optional attr = findDefaultAttribute(ariesCredential.getSchemaId()); + if (attr.isPresent() && ariesCredential.getCredentialData() != null) { + Map attrs = ariesCredential.getCredentialData(); mergedLabel = attrs.get(attr.get()); } } return mergedLabel; } + public String apply(@Nullable BPACredentialExchange.ExchangePayload ldCredential) { + String result = null; + if (ldCredential != null && ldCredential.typeIsJsonLd()) { + V20CredExRecordByFormat.LdProof ldProof = ldCredential.getLdProof(); + String schemaId = LDContextHelper.findSchemaId(ldProof); + if (StringUtils.isNotEmpty(schemaId)) { + Optional defaultAttribute = findDefaultAttribute(schemaId); + if (defaultAttribute.isPresent()) { + JsonObject credentialSubject = ldProof.getCredential().getCredentialSubject(); + JsonElement je = credentialSubject.get(defaultAttribute.get()); + if (je != null) { + result = je.getAsString(); + } + } + } + } + return result; + } + private Optional findDefaultAttribute(@Nullable String schemaId) { if (StringUtils.isNotEmpty(schemaId)) { Optional schema = schemaService.getSchemaFor(schemaId); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java index 2f0d28b21..336b541de 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/AriesEventHandler.java @@ -25,7 +25,7 @@ import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; import org.hyperledger.aries.api.issue_credential_v2.V2IssueIndyCredentialEvent; -import org.hyperledger.aries.api.issue_credential_v2.V2ToV1IndyCredentialConverter; +import org.hyperledger.aries.api.issue_credential_v2.V2IssueLDCredentialEvent; import org.hyperledger.aries.api.message.BasicMessage; import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord; import org.hyperledger.aries.api.present_proof_v2.V20PresExRecord; @@ -36,9 +36,11 @@ import org.hyperledger.bpa.impl.aries.chat.ChatMessageManager; import org.hyperledger.bpa.impl.aries.connection.ConnectionManager; import org.hyperledger.bpa.impl.aries.connection.PingManager; -import org.hyperledger.bpa.impl.aries.credential.HolderCredentialManager; -import org.hyperledger.bpa.impl.aries.credential.IssuerCredentialManager; +import org.hyperledger.bpa.impl.aries.credential.HolderManager; +import org.hyperledger.bpa.impl.aries.credential.IssuerManager; +import org.hyperledger.bpa.impl.aries.jsonld.LDEventHandler; import org.hyperledger.bpa.impl.aries.proof.ProofEventHandler; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import java.util.Optional; @@ -46,62 +48,66 @@ @Singleton public class AriesEventHandler extends EventHandler { - private final ConnectionManager conMgmt; + private final ConnectionManager connection; - private final Optional pingMgmt; + private final Optional ping; - private final HolderCredentialManager holderMgr; + private final HolderManager credHolder; - private final IssuerCredentialManager issuerMgr; + private final IssuerManager credIssuer; - private final ProofEventHandler proofMgmt; + private final ProofEventHandler proof; - private final ChatMessageManager chatMessageManager; + private final LDEventHandler jsonLD; + + private final ChatMessageManager chatMessage; @Inject public AriesEventHandler( - ConnectionManager conMgmt, - Optional pingMgmt, - HolderCredentialManager holderMgr, - ProofEventHandler proofMgmt, - IssuerCredentialManager issuerMgr, + ConnectionManager connectionManager, + Optional pingManager, + HolderManager holderCredentialManager, + ProofEventHandler proofEventHandler, + LDEventHandler jsonLD, + IssuerManager issuerCredentialManager, ChatMessageManager chatMessageManager) { - this.conMgmt = conMgmt; - this.pingMgmt = pingMgmt; - this.holderMgr = holderMgr; - this.issuerMgr = issuerMgr; - this.proofMgmt = proofMgmt; - this.chatMessageManager = chatMessageManager; + this.connection = connectionManager; + this.ping = pingManager; + this.credHolder = holderCredentialManager; + this.credIssuer = issuerCredentialManager; + this.proof = proofEventHandler; + this.jsonLD = jsonLD; + this.chatMessage = chatMessageManager; } @Override - public void handleConnection(ConnectionRecord connection) { - log.debug("Connection Event: {}", connection); + public void handleConnection(ConnectionRecord connectionRecord) { + log.debug("Connection Event: {}", connectionRecord); // all events in state invitation are handled in the managers - if (connection.stateIsInvitation()) { + if (connectionRecord.stateIsInvitation()) { return; } - synchronized (conMgmt) { - if (connection.isConnectionInvitation()) { - conMgmt.handleInvitationEvent(connection); - } else if (connection.isOutgoingConnection()) { - conMgmt.handleOutgoingConnectionEvent(connection); + synchronized (connection) { + if (connectionRecord.isConnectionInvitation()) { + connection.handleInvitationEvent(connectionRecord); + } else if (connectionRecord.isOutgoingConnection()) { + connection.handleOutgoingConnectionEvent(connectionRecord); } else { - conMgmt.handleIncomingConnectionEvent(connection); + connection.handleIncomingConnectionEvent(connectionRecord); } } } @Override public void handlePing(PingEvent ping) { - pingMgmt.ifPresent(mgmt -> mgmt.handlePingEvent(ping)); + this.ping.ifPresent(mgmt -> mgmt.handlePingEvent(ping)); } @Override - public void handleProof(PresentationExchangeRecord proof) { - log.debug("Present Proof Event: {}", proof); - synchronized (proofMgmt) { - proofMgmt.dispatch(proof); + public void handleProof(PresentationExchangeRecord presExRecord) { + log.debug("Present Proof Event: {}", presExRecord); + synchronized (proof) { + proof.dispatch(presExRecord); } } @@ -116,26 +122,27 @@ public void handleCredential(V1CredentialExchange v1CredEx) { log.debug("Credential Event: {}", v1CredEx); // holder events if (v1CredEx.roleIsHolder()) { - synchronized (holderMgr) { + synchronized (credHolder) { if (v1CredEx.stateIsCredentialAcked()) { - holderMgr.handleV1CredentialExchangeAcked(v1CredEx); + credHolder.handleV1CredentialExchangeAcked(v1CredEx); } else if (v1CredEx.stateIsOfferReceived()) { - holderMgr.handleOfferReceived(v1CredEx, ExchangeVersion.V1); + credHolder.handleOfferReceived(v1CredEx, BPACredentialExchange.ExchangePayload + .indy(v1CredEx.getCredentialProposalDict().getCredentialProposal()), ExchangeVersion.V1); } else { - holderMgr.handleStateChangesOnly( + credHolder.handleStateChangesOnly( v1CredEx.getCredentialExchangeId(), v1CredEx.getState(), v1CredEx.getUpdatedAt(), v1CredEx.getErrorMsg()); } } // issuer events } else if (v1CredEx.roleIsIssuer()) { - synchronized (issuerMgr) { + synchronized (credIssuer) { if (v1CredEx.stateIsProposalReceived()) { - issuerMgr.handleCredentialProposal(v1CredEx, ExchangeVersion.V1); + credIssuer.handleV1CredentialProposal(v1CredEx); } else if (v1CredEx.stateIsRequestReceived()) { - issuerMgr.handleV1CredentialRequest(v1CredEx); + credIssuer.handleV1CredentialRequest(v1CredEx); } else { - issuerMgr.handleV1CredentialExchange(v1CredEx); + credIssuer.handleV1CredentialExchange(v1CredEx); } } } @@ -145,25 +152,23 @@ public void handleCredential(V1CredentialExchange v1CredEx) { public void handleCredentialV2(V20CredExRecord v2CredEx) { log.debug("Credential V2 Event: {}", v2CredEx); if (v2CredEx.roleIsIssuer()) { - synchronized (issuerMgr) { + synchronized (credIssuer) { if (v2CredEx.stateIsProposalReceived()) { - issuerMgr.handleCredentialProposal(V2ToV1IndyCredentialConverter.INSTANCE().toV1Proposal(v2CredEx), - ExchangeVersion.V2); + credIssuer.handleV2CredentialProposal(v2CredEx); } else if (v2CredEx.stateIsRequestReceived()) { - issuerMgr.handleV2CredentialRequest(v2CredEx); + credIssuer.handleV2CredentialRequest(v2CredEx); } else { - issuerMgr.handleV2CredentialExchange(v2CredEx); + credIssuer.handleV2CredentialExchange(v2CredEx); } } } else if (v2CredEx.roleIsHolder()) { - synchronized (holderMgr) { + synchronized (credHolder) { if (v2CredEx.stateIsOfferReceived()) { - holderMgr.handleOfferReceived( - V2ToV1IndyCredentialConverter.INSTANCE().toV1Offer(v2CredEx), ExchangeVersion.V2); + credHolder.handleV2OfferReceived(v2CredEx); } else if (v2CredEx.stateIsCredentialReceived()) { - holderMgr.handleV2CredentialReceived(v2CredEx); + credHolder.handleV2CredentialReceived(v2CredEx); } else { - holderMgr.handleStateChangesOnly( + credHolder.handleStateChangesOnly( v2CredEx.getCredentialExchangeId(), v2CredEx.getState(), v2CredEx.getUpdatedAt(), v2CredEx.getErrorMsg()); } @@ -174,8 +179,16 @@ public void handleCredentialV2(V20CredExRecord v2CredEx) { @Override public void handleIssueCredentialV2Indy(V2IssueIndyCredentialEvent revocationInfo) { log.debug("Issue Credential V2 Indy Event: {}", revocationInfo); - synchronized (issuerMgr) { - issuerMgr.handleIssueCredentialV2Indy(revocationInfo); + synchronized (credIssuer) { + credIssuer.handleIssueCredentialV2Indy(revocationInfo); + } + } + + @Override + public void handleIssueCredentialV2LD(V2IssueLDCredentialEvent credentialInfo) { + log.debug("Issue LD Credential V2 Event: {}", credentialInfo); + synchronized (jsonLD) { + jsonLD.handleIssueCredentialV2LD(credentialInfo); } } @@ -183,12 +196,12 @@ public void handleIssueCredentialV2Indy(V2IssueIndyCredentialEvent revocationInf public void handleBasicMessage(BasicMessage message) { // since basic message handling is so simple (only one way to handle it), let // the manager handle it. - chatMessageManager.handleIncomingMessage(message); + chatMessage.handleIncomingMessage(message); } @Override public void handleRevocationNotification(RevocationNotificationEvent revocationNotification) { - holderMgr.handleRevocationNotification(revocationNotification); + credHolder.handleRevocationNotification(revocationNotification); } @Override diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/connection/ConnectionManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/connection/ConnectionManager.java index 986ec005e..e1246e693 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/connection/ConnectionManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/connection/ConnectionManager.java @@ -44,9 +44,9 @@ import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.api.notification.*; import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.controller.api.invitation.APICreateInvitationResponse; import org.hyperledger.bpa.controller.api.invitation.CheckInvitationResponse; import org.hyperledger.bpa.controller.api.partner.CreatePartnerInvitationRequest; -import org.hyperledger.bpa.controller.api.invitation.APICreateInvitationResponse; import org.hyperledger.bpa.impl.activity.DidResolver; import org.hyperledger.bpa.impl.activity.PartnerCredDefLookup; import org.hyperledger.bpa.impl.util.TimeUtil; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/creddef/CredDefManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/creddef/CredDefManager.java new file mode 100644 index 000000000..ec69c957a --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/creddef/CredDefManager.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.creddef; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.credential_definition.CredentialDefinition; +import org.hyperledger.aries.api.schema.SchemaSendResponse; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.api.exception.IssuerException; +import org.hyperledger.bpa.api.exception.NetworkException; +import org.hyperledger.bpa.api.exception.WrongApiUsageException; +import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.config.RuntimeConfig; +import org.hyperledger.bpa.controller.api.issuer.CredDef; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.persistence.model.BPACredentialDefinition; +import org.hyperledger.bpa.persistence.model.BPASchema; +import org.hyperledger.bpa.persistence.repository.BPACredentialDefinitionRepository; +import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; + +import java.io.IOException; +import java.util.*; + +@Slf4j +@Singleton +public class CredDefManager { + + @Inject + AriesClient ac; + + @Inject + SchemaService schemaService; + + @Inject + BPACredentialDefinitionRepository credDefRepo; + + @Inject + BPAMessageSource.DefaultMessageSource msg; + + @Inject + RuntimeConfig config; + + @Inject + IssuerCredExRepository issuerCredExRepo; + + public List listCredDefs() { + List result = new ArrayList<>(); + credDefRepo.findAll().forEach(db -> result.add(CredDef.from(db))); + return result; + } + + public CredDef createCredDef(@NonNull String schemaId, @NonNull String tag, boolean supportRevocation) { + CredDef result; + try { + String sId = StringUtils.strip(schemaId); + String t = StringUtils.trim(tag); + Optional ariesSchema = ac.schemasGetById(sId); + if (ariesSchema.isEmpty()) { + throw new WrongApiUsageException(msg.getMessage("api.schema.restriction.schema.not.found.on.ledger", + Map.of("id", sId))); + } + + Optional bpaSchema = schemaService.getSchemaFor(sId); + if (bpaSchema.isEmpty()) { + // schema exists on ledger, but no in db, let's add it. + SchemaAPI schema = schemaService.addIndySchema(ariesSchema.get().getId(), null, null, null); + if (schema == null) { + throw new IssuerException(msg.getMessage("api.issuer.schema.failure", Map.of("id", sId))); + } + bpaSchema = schemaService.getSchemaFor(schema.getSchemaId()); + } + // send credDef to ledger... + // will create if needed, otherwise return existing... + CredentialDefinition.CredentialDefinitionRequest request = CredentialDefinition.CredentialDefinitionRequest + .builder() + .schemaId(schemaId) + .tag(t) + .supportRevocation(supportRevocation) + .revocationRegistrySize(config.getRevocationRegistrySize()) + .build(); + Optional response = ac + .credentialDefinitionsCreate(request); + if (response.isPresent()) { + // check to see if we have already saved this cred def. + if (credDefRepo.findByCredentialDefinitionId(response.get().getCredentialDefinitionId()).isEmpty()) { + // doesn't exist, save it to the db... + BPACredentialDefinition credDef = BPACredentialDefinition.builder() + .schema(bpaSchema.orElseThrow()) + .credentialDefinitionId(response.get().getCredentialDefinitionId()) + .isSupportRevocation(supportRevocation) + .revocationRegistrySize(config.getRevocationRegistrySize()) + .tag(t) + .build(); + BPACredentialDefinition saved = credDefRepo.save(credDef); + result = CredDef.from(saved); + } else { + throw new WrongApiUsageException(msg.getMessage("api.issuer.creddef.already.exists", + Map.of("id", sId, "tag", t))); + } + } else { + log.error("Credential Definition not created."); + throw new IssuerException(msg.getMessage("api.issuer.creddef.ledger.failure")); + } + } catch (IOException e) { + log.error("aca-py not reachable", e); + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + return result; + } + + public void deleteCredDef(@NonNull UUID id) { + int recs = issuerCredExRepo.countIdByCredDefId(id); + if (recs == 0) { + credDefRepo.deleteById(id); + } else { + throw new IssuerException(msg.getMessage("api.issuer.creddef.in.use")); + } + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/BaseCredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerBase.java similarity index 68% rename from backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/BaseCredentialManager.java rename to backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerBase.java index 05731a549..b3f780ddb 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/BaseCredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerBase.java @@ -19,89 +19,130 @@ import io.micronaut.core.annotation.Nullable; import jakarta.inject.Inject; +import jakarta.inject.Singleton; import lombok.NonNull; import org.hyperledger.acy_py.generated.model.V10CredentialProblemReportRequest; import org.hyperledger.acy_py.generated.model.V20CredIssueProblemReportRequest; import org.hyperledger.aries.AriesClient; import org.hyperledger.aries.api.ExchangeVersion; import org.hyperledger.aries.api.exception.AriesException; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; import org.hyperledger.bpa.api.exception.EntityNotFoundException; import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.controller.api.issuer.CredEx; +import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; import java.io.IOException; import java.time.Instant; +import java.util.List; +import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; -public abstract class BaseCredentialManager { +/** + * Wraps all credential exchange logic that is common for indy and json-ld + * credentials, and both holder and issuer flows. + */ +@Singleton +public abstract class CredentialManagerBase { @Inject AriesClient ac; @Inject - IssuerCredExRepository credExRepo; + BPAMessageSource.DefaultMessageSource msg; @Inject - BPAMessageSource.DefaultMessageSource msg; + IssuerCredExRepository issuerCredExRepo; + + @Inject + Converter conv; /** - * The only way to stop or decline a credential exchange is for any side (issuer - * or holder) to send a problem report. If a problem report is sent aca-py will - * set the state of the exchange to null and hence it becomes unusable and con - * not be restarted again. - * - * @param credEx {@link BPACredentialExchange} - * @param message to sent to the other party + * If there is a problem during the credential exchange and aca-py is started + * without the option to preserve exchange records, the record is deleted + * immediately. Hence, we need to check if the record exists in both systems. + * + * @param id credential exchange id + * @return {@link BPACredentialExchange} */ - public void declineCredentialExchange(@NonNull BPACredentialExchange credEx, @Nullable String message) { + public BPACredentialExchange getCredentialExchange(@NonNull UUID id) { + BPACredentialExchange credEx = issuerCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); try { if (ExchangeVersion.V1.equals(credEx.getExchangeVersion())) { - ac.issueCredentialRecordsProblemReport(credEx.getCredentialExchangeId(), - V10CredentialProblemReportRequest.builder().description(message).build()); + ac.issueCredentialRecordsGetById(credEx.getCredentialExchangeId()); } else { - ac.issueCredentialV2RecordsProblemReport(credEx.getCredentialExchangeId(), - V20CredIssueProblemReportRequest.builder().description(message).build()); + ac.issueCredentialV2RecordsGetById(credEx.getCredentialExchangeId()); } } catch (IOException e) { throw new NetworkException(msg.getMessage("acapy.unavailable"), e); } catch (AriesException e) { if (e.getCode() == 404) { + credEx.pushStates(CredentialExchangeState.PROBLEM, Instant.now()); + issuerCredExRepo.updateAfterEventNoRevocationInfo(credEx.getId(), credEx.getState(), + credEx.getStateToTimestamp(), msg.getMessage("api.credential.no.match")); throw new EntityNotFoundException(); } throw e; } + return credEx; + } + + public List listCredentialExchanges(@Nullable CredentialExchangeRole role, @Nullable UUID partnerId) { + List exchanges = issuerCredExRepo.listOrderByUpdatedAtDesc(); + // now, lets get credentials... + return exchanges.stream() + .filter(x -> { + if (role != null) { + return role.equals(x.getRole()); + } + return true; + }) + .filter(x -> x.getPartner() != null) + .filter(x -> { + if (partnerId != null) { + return x.getPartner().getId().equals(partnerId); + } + return true; + }) + .map(ex -> CredEx.from(ex, conv.toAPIObject(ex.getPartner()))) + .collect(Collectors.toList()); + } + + public CredEx findCredentialExchangeById(@NonNull UUID id) { + BPACredentialExchange credEx = issuerCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); + return CredEx.from(credEx, conv.toAPIObject(Objects.requireNonNull(credEx.getPartner()))); } /** - * If there is a problem during the credential exchange and aca-py is started - * without the option to preserve exchange records, the record is deleted - * immediately. Hence, we need to check if the record exists in both systems. - * - * @param id credential exchange id - * @return {@link BPACredentialExchange} + * The only way to stop or decline a credential exchange is for any side (issuer + * or holder) to send a problem report. If a problem report is sent aca-py will + * set the state of the exchange to null and hence it becomes unusable and con + * not be restarted again. + * + * @param credEx {@link BPACredentialExchange} + * @param message to sent to the other party */ - public BPACredentialExchange getCredentialExchange(@NonNull UUID id) { - BPACredentialExchange credEx = credExRepo.findById(id).orElseThrow(EntityNotFoundException::new); + public void declineCredentialExchange(@NonNull BPACredentialExchange credEx, @Nullable String message) { try { if (ExchangeVersion.V1.equals(credEx.getExchangeVersion())) { - ac.issueCredentialRecordsGetById(credEx.getCredentialExchangeId()); + ac.issueCredentialRecordsProblemReport(credEx.getCredentialExchangeId(), + V10CredentialProblemReportRequest.builder().description(message).build()); } else { - ac.issueCredentialV2RecordsGetById(credEx.getCredentialExchangeId()); + ac.issueCredentialV2RecordsProblemReport(credEx.getCredentialExchangeId(), + V20CredIssueProblemReportRequest.builder().description(message).build()); } } catch (IOException e) { throw new NetworkException(msg.getMessage("acapy.unavailable"), e); } catch (AriesException e) { if (e.getCode() == 404) { - credEx.pushStates(CredentialExchangeState.PROBLEM, Instant.now()); - credExRepo.updateAfterEventNoRevocationInfo(credEx.getId(), credEx.getState(), - credEx.getStateToTimestamp(), msg.getMessage("api.credential.no.match")); throw new EntityNotFoundException(); } throw e; } - return credEx; } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderIndyManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderIndyManager.java new file mode 100644 index 000000000..df030c3d9 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderIndyManager.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.credential; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.scheduling.annotation.Scheduled; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.credentials.CredentialAttributes; +import org.hyperledger.aries.api.credentials.CredentialPreview; +import org.hyperledger.aries.api.exception.AriesException; +import org.hyperledger.aries.api.issue_credential_v1.BaseCredExRecord; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialProposalRequest; +import org.hyperledger.aries.api.issue_credential_v2.V1ToV2IssueCredentialConverter; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.aries.api.issue_credential_v2.V2ToV1IndyCredentialConverter; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.impl.activity.LabelStrategy; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.BPASchema; +import org.hyperledger.bpa.persistence.repository.HolderCredExRepository; + +import java.io.IOException; +import java.time.Instant; +import java.util.Map; +import java.util.Objects; + +/** + * Handles all credential holder logic that is specific to indy + */ +@Slf4j +@Singleton +public class HolderIndyManager { + + @Inject + @Setter(AccessLevel.PROTECTED) + AriesClient ac; + + @Inject + HolderCredExRepository holderCredExRepo; + + @Inject + LabelStrategy labelStrategy; + + @Inject + @Setter(AccessLevel.PACKAGE) + SchemaService schemaService; + + public void sendCredentialProposal( + @NonNull String connectionId, + @NonNull String schemaId, + @NonNull Map document, + @NonNull BPACredentialExchange.BPACredentialExchangeBuilder dbCredEx, + @Nullable ExchangeVersion version) + throws IOException { + V1CredentialProposalRequest v1CredentialProposalRequest = V1CredentialProposalRequest + .builder() + .connectionId(Objects.requireNonNull(connectionId)) + .schemaId(schemaId) + .credentialProposal( + new CredentialPreview( + CredentialAttributes.from( + Objects.requireNonNull(document)))) + .build(); + if (version == null || ExchangeVersion.V1.equals(version)) { + ac.issueCredentialSendProposal(v1CredentialProposalRequest).ifPresent(v1 -> dbCredEx + .threadId(v1.getThreadId()) + .credentialExchangeId(v1.getCredentialExchangeId()) + .credentialProposal(BPACredentialExchange.ExchangePayload + .indy(v1.getCredentialProposalDict().getCredentialProposal()))); + } else { + V2CredentialExchangeFree v2Request = V1ToV2IssueCredentialConverter + .toV20CredExFree(v1CredentialProposalRequest); + ac.issueCredentialV2SendProposal(v2Request).ifPresent(v2 -> dbCredEx + .threadId(v2.getThreadId()) + .credentialExchangeId(v2.getCredentialExchangeId()) + .exchangeVersion(ExchangeVersion.V2) + .credentialProposal(BPACredentialExchange.ExchangePayload + .indy(V2ToV1IndyCredentialConverter.INSTANCE().toV1Proposal(v2) + .getCredentialProposalDict() + .getCredentialProposal()))); + } + } + + /** + * Scheduled task that checks the revocation status of all credentials issued to + * this BPA. + */ + @Scheduled(fixedDelay = "5m", initialDelay = "1m") + void checkRevocationStatus() { + log.trace("Running revocation checks"); + holderCredExRepo.findNotRevoked().parallelStream().forEach(cred -> { + try { + log.trace("Running revocation check for credential exchange: {}", cred.getReferent()); + ac.credentialRevoked(Objects.requireNonNull(cred.getReferent())).ifPresent(isRevoked -> { + if (isRevoked.getRevoked() != null && isRevoked.getRevoked()) { + cred.pushStates(CredentialExchangeState.CREDENTIAL_REVOKED, Instant.now()); + holderCredExRepo.updateRevoked(cred.getId(), Boolean.TRUE, cred.getState(), + cred.getStateToTimestamp()); + log.debug("Credential with referent id: {} has been revoked", cred.getReferent()); + } + }); + } catch (AriesException e) { + if (e.getCode() == 404) { + log.error("aca-py has no credential with referent id: {}", cred.getReferent()); + holderCredExRepo.updateReferent(cred.getId(), null); + } + } catch (Exception e) { + log.error("Revocation check failed", e); + } + }); + } + + // credential event handling + + // v2 credential, signed and stored in wallet + public void handleV2CredentialReceived(@NonNull V20CredExRecord credEx, @NonNull BPACredentialExchange dbCred, + String issuer) { + V2ToV1IndyCredentialConverter.INSTANCE().toV1Credential(credEx) + .ifPresent(c -> { + String label = labelStrategy.apply(c); + dbCred + .pushStates(credEx.getState(), credEx.getUpdatedAt()) + .setIndyCredential(c) + .setLabel(label) + .setIssuer(issuer); + holderCredExRepo.update(dbCred); + + }); + } + + public BPASchema checkSchema(BaseCredExRecord credEx) { + String schemaId = null; + BPASchema bpaSchema = null; + if (credEx instanceof V1CredentialExchange) { + schemaId = ((V1CredentialExchange) credEx).getSchemaId(); + } else if (credEx instanceof V20CredExRecord) { + schemaId = V2ToV1IndyCredentialConverter.INSTANCE() + .toV1Offer((V20CredExRecord) credEx).getCredentialProposalDict().getSchemaId(); + } + if (schemaId != null) { + bpaSchema = schemaService.getSchemaFor(schemaId).orElse(null); + if (bpaSchema == null) { + SchemaAPI schemaAPI = schemaService.addIndySchema(schemaId, null, null); + if (schemaAPI != null) { + bpaSchema = BPASchema.builder().id(schemaAPI.getId()).build(); + } + } + } + return bpaSchema; + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderCredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderManager.java similarity index 55% rename from backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderCredentialManager.java rename to backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderManager.java index e60758b14..0f426e5e4 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderCredentialManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/HolderManager.java @@ -17,48 +17,39 @@ */ package org.hyperledger.bpa.impl.aries.credential; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.micronaut.context.annotation.Value; import io.micronaut.context.event.ApplicationEventPublisher; import io.micronaut.core.annotation.Nullable; -import io.micronaut.scheduling.annotation.Scheduled; +import io.micronaut.core.util.CollectionUtils; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import lombok.AccessLevel; import lombok.NonNull; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hyperledger.acy_py.generated.model.V20CredRequestRequest; -import org.hyperledger.aries.AriesClient; import org.hyperledger.aries.api.ExchangeVersion; -import org.hyperledger.aries.api.credentials.Credential; -import org.hyperledger.aries.api.credentials.CredentialAttributes; -import org.hyperledger.aries.api.credentials.CredentialPreview; import org.hyperledger.aries.api.exception.AriesException; +import org.hyperledger.aries.api.issue_credential_v1.BaseCredExRecord; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; -import org.hyperledger.aries.api.issue_credential_v1.V1CredentialProposalRequest; import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; import org.hyperledger.aries.api.issue_credential_v2.V2ToV1IndyCredentialConverter; -import org.hyperledger.aries.api.jsonld.VerifiableCredential.VerifiableIndyCredential; +import org.hyperledger.aries.api.jsonld.VerifiableCredential; import org.hyperledger.aries.api.jsonld.VerifiablePresentation; import org.hyperledger.aries.api.revocation.RevocationNotificationEvent; import org.hyperledger.aries.config.GsonConfig; import org.hyperledger.bpa.api.CredentialType; import org.hyperledger.bpa.api.aries.AriesCredential; import org.hyperledger.bpa.api.aries.ProfileVC; -import org.hyperledger.bpa.api.aries.SchemaAPI; import org.hyperledger.bpa.api.exception.EntityNotFoundException; import org.hyperledger.bpa.api.exception.NetworkException; import org.hyperledger.bpa.api.exception.PartnerException; import org.hyperledger.bpa.api.notification.CredentialAddedEvent; import org.hyperledger.bpa.api.notification.CredentialOfferedEvent; -import org.hyperledger.bpa.config.BPAMessageSource; import org.hyperledger.bpa.impl.activity.LabelStrategy; +import org.hyperledger.bpa.impl.aries.jsonld.HolderLDManager; import org.hyperledger.bpa.impl.aries.jsonld.VPManager; -import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.aries.wallet.Identity; import org.hyperledger.bpa.impl.util.AriesStringUtil; import org.hyperledger.bpa.impl.util.Converter; import org.hyperledger.bpa.impl.util.CryptoUtil; @@ -76,50 +67,54 @@ import java.util.*; import java.util.stream.Collectors; +/** + * Wraps all credential holder specific logic that is common for both indy and + * json-ld credentials. + */ @Slf4j @Singleton -public class HolderCredentialManager extends BaseCredentialManager { - - @Value("${bpa.did.prefix}") - String didPrefix; - - @Inject - @Setter(AccessLevel.PACKAGE) - AriesClient ac; +public class HolderManager extends CredentialManagerBase { @Inject - PartnerRepository partnerRepo; + HolderIndyManager indy; @Inject - MyDocumentRepository docRepo; + HolderLDManager ld; @Inject HolderCredExRepository holderCredExRepo; @Inject - VPManager vpMgmt; + PartnerRepository partnerRepo; @Inject - @Setter(AccessLevel.PACKAGE) - SchemaService schemaService; + ApplicationEventPublisher eventPublisher; @Inject Converter conv; @Inject - ObjectMapper mapper; + MyDocumentRepository docRepo; @Inject - LabelStrategy labelStrategy; + VPManager vpMgmt; @Inject - ApplicationEventPublisher eventPublisher; + LabelStrategy labelStrategy; @Inject - BPAMessageSource.DefaultMessageSource msg; + Identity identity; - // request credential from issuer (partner) - public void sendCredentialRequest(@NonNull UUID partnerId, @NonNull UUID myDocId, + // Credential Management - Called By User + + /** + * Request credential from issuer (partner) + * + * @param partnerId {@link UUID} + * @param myDocId {@link UUID} + * @param version {@link ExchangeVersion} + */ + public void sendCredentialProposal(@NonNull UUID partnerId, @NonNull UUID myDocId, @Nullable ExchangeVersion version) { Partner dbPartner = partnerRepo.findById(partnerId) .orElseThrow( @@ -127,23 +122,12 @@ public void sendCredentialRequest(@NonNull UUID partnerId, @NonNull UUID myDocId MyDocument dbDoc = docRepo.findById(myDocId) .orElseThrow( () -> new PartnerException(msg.getMessage("api.document.not.found", Map.of("id", myDocId)))); - if (!CredentialType.INDY.equals(dbDoc.getType())) { - throw new PartnerException(msg.getMessage("api.schema.credential.document.conversion.failure")); + if (dbDoc.getSchema() == null) { + throw new PartnerException(msg.getMessage("api.schema.restriction.schema.not.found", + Map.of("id", dbDoc.getSchemaId() != null ? dbDoc.getSchemaId() : ""))); } + BPASchema s = dbDoc.getSchema(); try { - BPASchema s = schemaService.getSchemaFor(dbDoc.getSchemaId()) - .orElseThrow( - () -> new PartnerException(msg.getMessage("api.schema.restriction.schema.not.found", - Map.of("id", dbDoc.getSchemaId())))); - V1CredentialProposalRequest v1CredentialProposalRequest = V1CredentialProposalRequest - .builder() - .connectionId(Objects.requireNonNull(dbPartner.getConnectionId())) - .schemaId(s.getSchemaId()) - .credentialProposal( - new CredentialPreview( - CredentialAttributes.from( - Objects.requireNonNull(dbDoc.getDocument())))) - .build(); BPACredentialExchange.BPACredentialExchangeBuilder dbCredEx = BPACredentialExchange .builder() .partner(dbPartner) @@ -151,19 +135,12 @@ public void sendCredentialRequest(@NonNull UUID partnerId, @NonNull UUID myDocId .state(CredentialExchangeState.PROPOSAL_SENT) .pushStateChange(CredentialExchangeState.PROPOSAL_SENT, Instant.now()) .role(CredentialExchangeRole.HOLDER); - if (version == null || ExchangeVersion.V1.equals(version)) { - ac.issueCredentialSendProposal(v1CredentialProposalRequest).ifPresent(v1 -> dbCredEx - .threadId(v1.getThreadId()) - .credentialExchangeId(v1.getCredentialExchangeId()) - .credentialProposal(v1.getCredentialProposalDict().getCredentialProposal())); + String connectionId = Objects.requireNonNull(dbPartner.getConnectionId()); + Map document = Objects.requireNonNull(dbDoc.getDocument()); + if (dbDoc.typeIsIndy()) { + indy.sendCredentialProposal(connectionId, s.getSchemaId(), document, dbCredEx, version); } else { - ac.issueCredentialV2SendProposal(v1CredentialProposalRequest).ifPresent(v2 -> dbCredEx - .threadId(v2.getThreadId()) - .credentialExchangeId(v2.getCredentialExchangeId()) - .exchangeVersion(ExchangeVersion.V2) - .credentialProposal(V2ToV1IndyCredentialConverter.INSTANCE().toV1Proposal(v2) - .getCredentialProposalDict() - .getCredentialProposal())); + ld.sendCredentialProposal(connectionId, s, document, dbCredEx); } holderCredExRepo.save(dbCredEx.build()); } catch (IOException e) { @@ -171,36 +148,87 @@ public void sendCredentialRequest(@NonNull UUID partnerId, @NonNull UUID myDocId } } - // credential visible in public profile + /** + * Holder accepts credential offer received from issuer + * + * @param id {@link UUID} bpa credential exchange id + */ + public void sendCredentialRequest(@NonNull UUID id) { + BPACredentialExchange dbEx = holderCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); + try { + if (ExchangeVersion.V1.equals(dbEx.getExchangeVersion())) { + ac.issueCredentialRecordsSendRequest(dbEx.getCredentialExchangeId()); + } else { + V20CredRequestRequest.V20CredRequestRequestBuilder credentialRequest = V20CredRequestRequest + .builder(); + if (dbEx.typeIsJsonLd()) { + credentialRequest.holderDid(identity.getMyDid()); + } + ac.issueCredentialV2RecordsSendRequest(dbEx.getCredentialExchangeId(), credentialRequest.build()); + } + } catch (IOException e) { + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + } + + /** + * Holder declines credential offer received from issuer + * + * @param id {@link UUID} bpa credential exchange id + * @param message optional reason + */ + public void declineCredentialOffer(@NonNull UUID id, @Nullable String message) { + if (StringUtils.isEmpty(message)) { + message = msg.getMessage("api.holder.credential.exchange.declined"); + } + BPACredentialExchange dbEx = getCredentialExchange(id); + dbEx.pushStates(CredentialExchangeState.DECLINED, Instant.now()); + holderCredExRepo.updateStates(dbEx.getId(), dbEx.getState(), dbEx.getStateToTimestamp(), message); + declineCredentialExchange(dbEx, message); + } + + /** + * Sets the credential's visibility in the public profile + * + * @param id {@link UUID} bpa credential exchange id + */ public void toggleVisibility(UUID id) { BPACredentialExchange cred = holderCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); holderCredExRepo.updateIsPublic(id, !cred.checkIfPublic()); vpMgmt.recreateVerifiablePresentation(); } - // credential CRUD operations - - public List listCredentials() { + /** + * List credential that the user holds in the wallet + * + * @param typesToFilter filter by provided credential types + * @return list of {@link AriesCredential} + */ + public List listHeldCredentials(@Nullable List typesToFilter) { return holderCredExRepo.findByRoleEqualsAndStateIn( CredentialExchangeRole.HOLDER, List.of(CredentialExchangeState.CREDENTIAL_ACKED, CredentialExchangeState.DONE)) .stream() + .filter(c -> { + if (CollectionUtils.isEmpty(typesToFilter) || typesToFilter.contains(c.getType())) { + return true; + } + return false; + }) .map(this::buildCredential) .collect(Collectors.toList()); } - public AriesCredential getCredentialById(@NonNull UUID id) { + /** + * Find wallet credential by id + * + * @param id {@link UUID} bpa credential exchange id + * @return {@link AriesCredential} + */ + public AriesCredential findHeldCredentialById(@NonNull UUID id) { return holderCredExRepo.findById(id).map(this::buildCredential).orElseThrow(EntityNotFoundException::new); } - private AriesCredential buildCredential(@NonNull BPACredentialExchange dbCred) { - String typeLabel = null; - if (dbCred.getCredential() != null) { - typeLabel = schemaService.getSchemaLabel(dbCred.getCredential().getSchemaId()); - } - return AriesCredential.fromBPACredentialExchange(dbCred, typeLabel); - } - /** * Updates the credentials label * @@ -209,7 +237,7 @@ private AriesCredential buildCredential(@NonNull BPACredentialExchange dbCred) { * @return the updated credential if found */ public AriesCredential updateCredentialById(@NonNull UUID id, @Nullable String label) { - final AriesCredential cred = getCredentialById(id); + final AriesCredential cred = findHeldCredentialById(id); String mergedLabel = labelStrategy.apply(label, cred); holderCredExRepo.updateLabel(id, mergedLabel); cred.setLabel(label); @@ -220,8 +248,12 @@ public void deleteCredentialById(@NonNull UUID id) { holderCredExRepo.findById(id).ifPresent(c -> { boolean isPublic = c.checkIfPublic(); try { - if (c.getReferent() != null) { - ac.credentialRemove(c.getReferent()); + if (StringUtils.isNotEmpty(c.getReferent())) { + if (c.typeIsIndy()) { + ac.credentialRemove(c.getReferent()); + } else { + ac.credentialW3CRemove(c.getReferent()); + } } } catch (AriesException | IOException e) { // if we fail here it's not good, but also no deal-breaker, so log and continue @@ -234,133 +266,66 @@ public void deleteCredentialById(@NonNull UUID id) { }); } - public void sendCredentialRequest(@NonNull UUID id) { - BPACredentialExchange dbEx = holderCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); - try { - if (ExchangeVersion.V1.equals(dbEx.getExchangeVersion())) { - ac.issueCredentialRecordsSendRequest(dbEx.getCredentialExchangeId()); - } else { - ac.issueCredentialV2RecordsSendRequest(dbEx.getCredentialExchangeId(), V20CredRequestRequest - .builder().build()); - } - } catch (IOException e) { - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } - } + // Credential Management - Called By Event Handler - public void declineCredentialOffer(@NonNull UUID id, @Nullable String message) { - if (StringUtils.isEmpty(message)) { - message = msg.getMessage("api.holder.credential.exchange.declined"); - } - BPACredentialExchange dbEx = getCredentialExchange(id); - dbEx.pushStates(CredentialExchangeState.DECLINED, Instant.now()); - holderCredExRepo.updateStates(dbEx.getId(), dbEx.getState(), dbEx.getStateToTimestamp(), message); - declineCredentialExchange(dbEx, message); + // v1 credential, signed and stored in wallet + public void handleV1CredentialExchangeAcked(@NonNull V1CredentialExchange credEx) { + String label = labelStrategy.apply(credEx.getCredential()); + holderCredExRepo.findByCredentialExchangeId(credEx.getCredentialExchangeId()).ifPresent(db -> { + db + .setReferent(credEx.getCredential() != null ? credEx.getCredential().getReferent() : null) + .setIndyCredential(credEx.getCredential()) + .setCredRevId(credEx.getCredential() != null ? credEx.getCredential().getCredRevId() : null) + .setRevRegId(credEx.getCredential() != null ? credEx.getCredential().getRevRegId() : null) + .setLabel(label) + .setIssuer(resolveIssuer(db.getPartner())) + .pushStates(credEx.getState(), TimeUtil.fromISOInstant(credEx.getUpdatedAt())); + holderCredExRepo.update(db); + fireCredentialAddedEvent(db); + }); } - /** - * Tries to resolve the issuers DID into a human-readable name. Resolution order - * is: 1. Partner alias the user gave 2. Legal name from the partners public - * profile 3. ACA-PY Label 4. DID - * - * @param ariesCred {@link Credential} - * @return the issuer or null when the credential or the credential definition - * id is null - */ - @Nullable - String resolveIssuer(@Nullable Credential ariesCred) { - String issuer = null; - if (ariesCred != null && StringUtils.isNotEmpty(ariesCred.getCredentialDefinitionId())) { - String did = didPrefix + AriesStringUtil.credDefIdGetDid(ariesCred.getCredentialDefinitionId()); - Optional p = partnerRepo.findByDid(did); - if (p.isPresent()) { - if (StringUtils.isNotEmpty(p.get().getAlias())) { - issuer = p.get().getAlias(); - } else if (p.get().getVerifiablePresentation() != null) { - VerifiablePresentation vp = conv - .fromMap(Objects.requireNonNull(p.get().getVerifiablePresentation()), Converter.VP_TYPEREF); - Optional profile = vp.getVerifiableCredential() - .stream().filter(ic -> ic.getType().contains("OrganizationalProfileCredential")).findAny(); - if (profile.isPresent() && profile.get().getCredentialSubject() != null) { - ProfileVC pVC = GsonConfig.jacksonBehaviour().fromJson(profile.get().getCredentialSubject(), - ProfileVC.class); - issuer = pVC.getLegalName(); - } - } - if (issuer == null && p.get().getIncoming() != null && Boolean.TRUE.equals(p.get().getIncoming())) { - issuer = p.get().getLabel(); - } - } - if (issuer == null) { - issuer = did; - } + public void handleV2OfferReceived(@NonNull V20CredExRecord v2CredEx) { + if (v2CredEx.payloadIsLdProof()) { + handleOfferReceived(v2CredEx, + BPACredentialExchange.ExchangePayload.jsonLD(v2CredEx.resolveLDCredOffer()), + ExchangeVersion.V2); + } else { + handleOfferReceived(v2CredEx, + BPACredentialExchange.ExchangePayload.indy(V2ToV1IndyCredentialConverter.INSTANCE() + .toV1Offer(v2CredEx).getCredentialProposalDict().getCredentialProposal()), + ExchangeVersion.V2); } - return issuer; - } - - /** - * Scheduled task that checks the revocation status of all credentials issued to - * this BPA. - */ - @Scheduled(fixedDelay = "5m", initialDelay = "1m") - void checkRevocationStatus() { - log.trace("Running revocation checks"); - holderCredExRepo.findNotRevoked().parallelStream().forEach(cred -> { - try { - log.trace("Running revocation check for credential exchange: {}", cred.getReferent()); - ac.credentialRevoked(Objects.requireNonNull(cred.getReferent())).ifPresent(isRevoked -> { - if (isRevoked.getRevoked() != null && isRevoked.getRevoked()) { - cred.pushStates(CredentialExchangeState.CREDENTIAL_REVOKED, Instant.now()); - holderCredExRepo.updateRevoked(cred.getId(), Boolean.TRUE, cred.getState(), - cred.getStateToTimestamp()); - log.debug("Credential with referent id: {} has been revoked", cred.getReferent()); - } - }); - } catch (AriesException e) { - if (e.getCode() == 404) { - log.error("aca-py has no credential with referent id: {}", cred.getReferent()); - holderCredExRepo.updateReferent(cred.getId(), null); - } - } catch (Exception e) { - log.error("Revocation check failed", e); - } - }); } - // credential event handling - // credential offer event - public void handleOfferReceived(@NonNull V1CredentialExchange credEx, @NonNull ExchangeVersion version) { - holderCredExRepo.findByCredentialExchangeId(credEx.getCredentialExchangeId()).ifPresentOrElse(db -> { - // counter offer or accepted proposal from issuer - db.pushStates(credEx.getState(), credEx.getUpdatedAt()); - V1CredentialExchange.CredentialProposalDict.CredentialProposal credentialOffer = credEx - .getCredentialProposalDict().getCredentialProposal(); - holderCredExRepo.updateOnCredentialOfferEvent(db.getId(), db.getState(), db.getStateToTimestamp(), - credentialOffer); + public void handleOfferReceived(@NonNull BaseCredExRecord credExBase, + @NonNull BPACredentialExchange.ExchangePayload payload, @NonNull ExchangeVersion version) { + holderCredExRepo.findByCredentialExchangeId(credExBase.getCredentialExchangeId()).ifPresentOrElse(db -> { + db.pushStates(credExBase.getState()); + holderCredExRepo.updateOnCredentialOfferEvent(db.getId(), db.getState(), db.getStateToTimestamp(), payload); // if offer equals proposal send request immediately - if (CryptoUtil.hashCompare(db.getCredentialProposal(), credentialOffer)) { - this.sendCredentialRequest(db.getId()); + if (CryptoUtil.hashCompare(db.getCredentialProposal(), payload)) { + sendCredentialRequest(db.getId()); } - }, () -> partnerRepo.findByConnectionId(credEx.getConnectionId()).ifPresent(p -> { - // issuer started with offer, no preexisting proposal - BPASchema bpaSchema = schemaService.getSchemaFor(credEx.getSchemaId()).orElse(null); - if (bpaSchema == null) { - SchemaAPI schemaAPI = schemaService.addIndySchema(credEx.getSchemaId(), null, null); - if (schemaAPI != null) { - bpaSchema = BPASchema.builder().id(schemaAPI.getId()).build(); - } + }, () -> partnerRepo.findByConnectionId(credExBase.getConnectionId()).ifPresent(p -> { + BPASchema bpaSchema; + if (payload.typeIsIndy()) { + bpaSchema = indy.checkSchema(credExBase); + } else { + bpaSchema = ld.checkSchema(credExBase); } BPACredentialExchange ex = BPACredentialExchange .builder() - .partner(p) .schema(bpaSchema) - .threadId(credEx.getThreadId()) - .credentialExchangeId(credEx.getCredentialExchangeId()) - .state(credEx.getState()) - .credentialOffer(credEx.getCredentialProposalDict().getCredentialProposal()) - .pushStateChange(credEx.getState(), TimeUtil.fromISOInstant(credEx.getUpdatedAt())) + .partner(p) + .type(payload.getType()) .role(CredentialExchangeRole.HOLDER) + .state(credExBase.getState()) + .pushStateChange(credExBase.getState(), TimeUtil.fromISOInstant(credExBase.getUpdatedAt())) + .credentialOffer(payload) + .credentialExchangeId(credExBase.getCredentialExchangeId()) + .threadId(credExBase.getThreadId()) .exchangeVersion(version) .build(); holderCredExRepo.save(ex); @@ -368,52 +333,33 @@ public void handleOfferReceived(@NonNull V1CredentialExchange credEx, @NonNull E })); } + public void handleV2CredentialReceived(@NonNull V20CredExRecord credEx) { + holderCredExRepo.findByCredentialExchangeId(credEx.getCredentialExchangeId()).ifPresent(dbCred -> { + String issuer = resolveIssuer(dbCred.getPartner()); + if (dbCred.typeIsIndy()) { + indy.handleV2CredentialReceived(credEx, dbCred, issuer); + } else { + ld.handleV2CredentialReceived(credEx, dbCred, issuer); + } + fireCredentialAddedEvent(dbCred); + }); + } + // credential request, receive and problem events public void handleStateChangesOnly( @NonNull String credExId, @Nullable CredentialExchangeState state, @NonNull String updatedAt, @Nullable String errorMsg) { holderCredExRepo.findByCredentialExchangeId(credExId).ifPresent(db -> { if (db.stateIsNotDeclined()) { // already handled - CredentialExchangeState s = state != null ? state : CredentialExchangeState.PROBLEM; + CredentialExchangeState s = state == null || CredentialExchangeState.ABANDONED.equals(state) + ? CredentialExchangeState.PROBLEM + : state; db.pushStates(s, updatedAt); holderCredExRepo.updateStates(db.getId(), db.getState(), db.getStateToTimestamp(), errorMsg); } }); } - // v1 credential, signed and stored in wallet - public void handleV1CredentialExchangeAcked(@NonNull V1CredentialExchange credEx) { - String label = labelStrategy.apply(credEx.getCredential()); - holderCredExRepo.findByCredentialExchangeId(credEx.getCredentialExchangeId()).ifPresent(db -> { - db - .setReferent(credEx.getCredential() != null ? credEx.getCredential().getReferent() : null) - .setCredential(credEx.getCredential()) - .setCredRevId(credEx.getCredential() != null ? credEx.getCredential().getCredRevId() : null) - .setRevRegId(credEx.getCredential() != null ? credEx.getCredential().getRevRegId() : null) - .setLabel(label) - .setIssuer(resolveIssuer(credEx.getCredential())) - .pushStates(credEx.getState(), TimeUtil.fromISOInstant(credEx.getUpdatedAt())); - holderCredExRepo.update(db); - fireCredentialAddedEvent(db); - }); - } - - // v2 credential, signed and stored in wallet - public void handleV2CredentialReceived(@NonNull V20CredExRecord credEx) { - holderCredExRepo.findByCredentialExchangeId(credEx.getCredentialExchangeId()).ifPresent( - dbCred -> V2ToV1IndyCredentialConverter.INSTANCE().toV1Credential(credEx) - .ifPresent(c -> { - String label = labelStrategy.apply(c); - dbCred - .pushStates(credEx.getState(), credEx.getUpdatedAt()) - .setCredential(c) - .setLabel(label) - .setIssuer(resolveIssuer(c)); - BPACredentialExchange dbCredential = holderCredExRepo.update(dbCred); - fireCredentialAddedEvent(dbCredential); - })); - } - public void handleRevocationNotification(RevocationNotificationEvent revocationNotification) { AriesStringUtil.RevocationInfo revocationInfo = AriesStringUtil .revocationEventToRevocationInfo(revocationNotification.getThreadId()); @@ -425,17 +371,62 @@ public void handleRevocationNotification(RevocationNotificationEvent revocationN }); } - private void fireCredentialAddedEvent(@NonNull BPACredentialExchange updated) { + // Internal Events + + public void fireCredentialOfferedEvent(@NonNull BPACredentialExchange updated) { AriesCredential ariesCredential = buildCredential(updated); - eventPublisher.publishEventAsync(CredentialAddedEvent.builder() + eventPublisher.publishEventAsync(CredentialOfferedEvent.builder() .credential(ariesCredential) .build()); } - private void fireCredentialOfferedEvent(@NonNull BPACredentialExchange updated) { + public void fireCredentialAddedEvent(@NonNull BPACredentialExchange updated) { AriesCredential ariesCredential = buildCredential(updated); - eventPublisher.publishEventAsync(CredentialOfferedEvent.builder() + eventPublisher.publishEventAsync(CredentialAddedEvent.builder() .credential(ariesCredential) .build()); } + + // Helper Methods + + /** + * Tries to resolve the issuers DID into a human-readable name. Resolution order + * is: 1. Partner alias the user gave 2. Legal name from the partners public + * profile 3. ACA-PY Label 4. DID + * + * @param p {@link Partner} + * @return the issuer or null when the credential or the credential definition + * id is null + */ + @Nullable + public String resolveIssuer(@Nullable Partner p) { + String issuer = null; + if (p != null) { + if (StringUtils.isNotEmpty(p.getAlias())) { + issuer = p.getAlias(); + } else if (p.getVerifiablePresentation() != null) { + VerifiablePresentation vp = conv + .fromMap(Objects.requireNonNull(p.getVerifiablePresentation()), Converter.VP_TYPEREF); + Optional profile = vp.getVerifiableCredential() + .stream().filter(ic -> ic.getType().contains("OrganizationalProfileCredential")).findAny(); + if (profile.isPresent() && profile.get().getCredentialSubject() != null) { + ProfileVC pVC = GsonConfig.jacksonBehaviour().fromJson(profile.get().getCredentialSubject(), + ProfileVC.class); + issuer = pVC.getLegalName(); + } + } + if (issuer == null && Boolean.TRUE.equals(p.getIncoming())) { + issuer = p.getLabel(); + } + if (issuer == null) { + issuer = p.getDid(); + } + } + return issuer; + } + + private AriesCredential buildCredential(@NonNull BPACredentialExchange dbCred) { + return AriesCredential.fromBPACredentialExchange(dbCred, + dbCred.getSchema() != null ? dbCred.getSchema().resolveSchemaLabel() : null); + } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerCredentialManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerCredentialManager.java deleted file mode 100644 index 88f6e3391..000000000 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerCredentialManager.java +++ /dev/null @@ -1,685 +0,0 @@ -/* - * Copyright (c) 2020-2022 - for information on the respective copyright owner - * see the NOTICE file and/or the repository at - * https://github.com/hyperledger-labs/business-partner-agent - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.hyperledger.bpa.impl.aries.credential; - -import com.fasterxml.jackson.databind.JsonNode; -import io.micronaut.context.event.ApplicationEventPublisher; -import io.micronaut.core.annotation.Nullable; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import lombok.Builder; -import lombok.Data; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.hyperledger.acy_py.generated.model.*; -import org.hyperledger.aries.AriesClient; -import org.hyperledger.aries.api.ExchangeVersion; -import org.hyperledger.aries.api.credential_definition.CredentialDefinition; -import org.hyperledger.aries.api.credential_definition.CredentialDefinition.CredentialDefinitionRequest; -import org.hyperledger.aries.api.credentials.Credential; -import org.hyperledger.aries.api.credentials.CredentialAttributes; -import org.hyperledger.aries.api.credentials.CredentialPreview; -import org.hyperledger.aries.api.exception.AriesException; -import org.hyperledger.aries.api.issue_credential_v1.*; -import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; -import org.hyperledger.aries.api.issue_credential_v2.V2IssueIndyCredentialEvent; -import org.hyperledger.aries.api.revocation.RevokeRequest; -import org.hyperledger.aries.api.schema.SchemaSendResponse; -import org.hyperledger.bpa.api.aries.AriesCredential; -import org.hyperledger.bpa.api.aries.SchemaAPI; -import org.hyperledger.bpa.api.exception.EntityNotFoundException; -import org.hyperledger.bpa.api.exception.IssuerException; -import org.hyperledger.bpa.api.exception.NetworkException; -import org.hyperledger.bpa.api.exception.WrongApiUsageException; -import org.hyperledger.bpa.api.notification.CredentialAcceptedEvent; -import org.hyperledger.bpa.api.notification.CredentialIssuedEvent; -import org.hyperledger.bpa.api.notification.CredentialProblemEvent; -import org.hyperledger.bpa.api.notification.CredentialProposalEvent; -import org.hyperledger.bpa.config.AcaPyConfig; -import org.hyperledger.bpa.config.BPAMessageSource; -import org.hyperledger.bpa.config.RuntimeConfig; -import org.hyperledger.bpa.controller.api.issuer.CredDef; -import org.hyperledger.bpa.controller.api.issuer.CredEx; -import org.hyperledger.bpa.controller.api.issuer.CredentialOfferRequest; -import org.hyperledger.bpa.controller.api.issuer.IssueCredentialSendRequest; -import org.hyperledger.bpa.impl.aries.schema.SchemaService; -import org.hyperledger.bpa.impl.util.Converter; -import org.hyperledger.bpa.persistence.model.BPACredentialDefinition; -import org.hyperledger.bpa.persistence.model.BPACredentialExchange; -import org.hyperledger.bpa.persistence.model.BPASchema; -import org.hyperledger.bpa.persistence.model.Partner; -import org.hyperledger.bpa.persistence.repository.BPACredentialDefinitionRepository; -import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; -import org.hyperledger.bpa.persistence.repository.PartnerRepository; - -import java.io.IOException; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; - -@Slf4j -@Singleton -public class IssuerCredentialManager extends BaseCredentialManager { - - @Inject - AriesClient ac; - - @Inject - AcaPyConfig acaPyConfig; - - @Inject - SchemaService schemaService; - - @Inject - BPACredentialDefinitionRepository credDefRepo; - - @Inject - PartnerRepository partnerRepo; - - @Inject - IssuerCredExRepository credExRepo; - - @Inject - Converter conv; - - @Inject - RuntimeConfig config; - - @Inject - BPAMessageSource.DefaultMessageSource msg; - - @Inject - ApplicationEventPublisher eventPublisher; - - // Credential Definition Management - - public List listCredDefs() { - List result = new ArrayList<>(); - credDefRepo.findAll().forEach(db -> result.add(CredDef.from(db))); - return result; - } - - public CredDef createCredDef(@NonNull String schemaId, @NonNull String tag, boolean supportRevocation) { - CredDef result; - try { - String sId = StringUtils.strip(schemaId); - String t = StringUtils.trim(tag); - Optional ariesSchema = ac.schemasGetById(sId); - if (ariesSchema.isEmpty()) { - throw new WrongApiUsageException(msg.getMessage("api.schema.restriction.schema.not.found.on.ledger", - Map.of("id", sId))); - } - - Optional bpaSchema = schemaService.getSchemaFor(sId); - if (bpaSchema.isEmpty()) { - // schema exists on ledger, but no in db, let's add it. - SchemaAPI schema = schemaService.addIndySchema(ariesSchema.get().getId(), null, null, null); - if (schema == null) { - throw new IssuerException(msg.getMessage("api.issuer.schema.failure", Map.of("id", sId))); - } - bpaSchema = schemaService.getSchemaFor(schema.getSchemaId()); - } - // send credDef to ledger... - // will create if needed, otherwise return existing... - CredentialDefinitionRequest request = CredentialDefinitionRequest.builder() - .schemaId(schemaId) - .tag(t) - .supportRevocation(supportRevocation) - .revocationRegistrySize(config.getRevocationRegistrySize()) - .build(); - Optional response = ac - .credentialDefinitionsCreate(request); - if (response.isPresent()) { - // check to see if we have already saved this cred def. - if (credDefRepo.findByCredentialDefinitionId(response.get().getCredentialDefinitionId()).isEmpty()) { - // doesn't exist, save it to the db... - BPACredentialDefinition credDef = BPACredentialDefinition.builder() - .schema(bpaSchema.orElseThrow()) - .credentialDefinitionId(response.get().getCredentialDefinitionId()) - .isSupportRevocation(supportRevocation) - .revocationRegistrySize(config.getRevocationRegistrySize()) - .tag(t) - .build(); - BPACredentialDefinition saved = credDefRepo.save(credDef); - result = CredDef.from(saved); - } else { - throw new WrongApiUsageException(msg.getMessage("api.issuer.creddef.already.exists", - Map.of("id", sId, "tag", t))); - } - } else { - log.error("Credential Definition not created."); - throw new IssuerException(msg.getMessage("api.issuer.creddef.ledger.failure")); - } - } catch (IOException e) { - log.error("aca-py not reachable", e); - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } - return result; - } - - public void deleteCredDef(@NonNull UUID id) { - int recs = credExRepo.countIdByCredDefId(id); - if (recs == 0) { - credDefRepo.deleteById(id); - } else { - throw new IssuerException(msg.getMessage("api.issuer.creddef.in.use")); - } - } - - // Credential Management - Called By User - - /** - * Issuer initialises the credential exchange with an offer. There is no - * preexisting proposal from the holder. - * - * @param request {@link IssueCredentialRequest} - * @return credential exchange id - */ - public String issueCredential(@NonNull IssueCredentialRequest request) { - Partner dbPartner = partnerRepo.findById(request.getPartnerId()) - .orElseThrow(() -> new IssuerException(msg.getMessage("api.partner.not.found", - Map.of("id", request.getPartnerId())))); - - BPACredentialDefinition dbCredDef = credDefRepo.findById(request.getCredDefId()) - .orElseThrow(() -> new IssuerException( - msg.getMessage("api.issuer.creddef.not.found", Map.of("id", request.getCredDefId())))); - - Map document = conv.toStringMap(request.getDocument()); - - checkAttributes(document, dbCredDef); - - String connectionId = dbPartner.getConnectionId(); - String schemaId = dbCredDef.getSchema().getSchemaId(); - String credentialDefinitionId = dbCredDef.getCredentialDefinitionId(); - - ExchangeResult exResult; - ExchangeVersion exVersion; - - V1CredentialProposalRequest proposal = V1CredentialProposalRequest - .builder() - .connectionId(Objects.requireNonNull(connectionId)) - .schemaId(schemaId) - .credentialProposal(new CredentialPreview(CredentialAttributes.fromMap(document))) - .credentialDefinitionId(credentialDefinitionId) - .build(); - - if (request.isV1()) { - exVersion = ExchangeVersion.V1; - exResult = sendV1Credential(proposal); - } else { - exVersion = ExchangeVersion.V2; - exResult = sendV2Credential(proposal); - } - - BPACredentialExchange cex = BPACredentialExchange.builder() - .schema(dbCredDef.getSchema()) - .partner(dbPartner) - .credDef(dbCredDef) - .role(CredentialExchangeRole.ISSUER) - .state(CredentialExchangeState.OFFER_SENT) - .pushStateChange(CredentialExchangeState.OFFER_SENT, Instant.now()) - // as I'm the issuer I know what I have issued, no need to get this info from - // the exchange record again - .credential(Credential.builder() - .schemaId(schemaId) - .attrs(document) - .build()) - .credentialExchangeId(exResult.getCredentialExchangeId()) - .threadId(exResult.getThreadId()) - .exchangeVersion(exVersion) - .build(); - credExRepo.save(cex); - - fireCredentialIssuedEvent(cex); - return exResult.getCredentialExchangeId(); - } - - public void reIssueCredential(@NonNull UUID exchangeId) { - BPACredentialExchange credEx = credExRepo.findById(exchangeId).orElseThrow(EntityNotFoundException::new); - if (credEx.roleIsIssuer() && credEx.stateIsRevoked()) { - issueCredential(IssueCredentialRequest.builder() - .partnerId(credEx.getPartner() != null ? credEx.getPartner().getId() : null) - .credDefId(credEx.getCredDef() != null ? credEx.getCredDef().getId() : null) - .document(conv.mapToNode(credEx.getCredential().getAttrs())) - .exchangeVersion(credEx.getExchangeVersion()) - .build()); - } else { - throw new IssuerException( - msg.getMessage("api.issuer.reissue.wrong.state", Map.of("state", credEx.getState()))); - } - } - - /** - * Check if the supplied attributes match the schema - * - * @param document the credential - * @param dbCredDef {@link BPACredentialDefinition} - */ - private void checkAttributes(Map document, BPACredentialDefinition dbCredDef) { - Set documentAttributeNames = document.keySet(); - Set schemaAttributeNames = dbCredDef.getSchema().getSchemaAttributeNames(); - if (!documentAttributeNames.equals(schemaAttributeNames)) { - throw new IssuerException(msg.getMessage("api.issuer.credential.document.mismatch", - Map.of("doc", documentAttributeNames, "schema", schemaAttributeNames))); - } - } - - private ExchangeResult sendV1Credential(@NonNull V1CredentialProposalRequest proposal) { - try { - return ac.issueCredentialSend(proposal) - .map(ExchangeResult::fromV1) - .orElseThrow(); - } catch (IOException e) { - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } - } - - private ExchangeResult sendV2Credential(@NonNull V1CredentialProposalRequest proposal) { - try { - return ac.issueCredentialV2Send(proposal) - .map(ExchangeResult::fromV2) - .orElseThrow(); - } catch (IOException e) { - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } - } - - public List listCredentialExchanges(@Nullable CredentialExchangeRole role, @Nullable UUID partnerId) { - List exchanges = credExRepo.listOrderByUpdatedAtDesc(); - // now, lets get credentials... - return exchanges.stream() - .filter(x -> { - if (role != null) { - return role.equals(x.getRole()); - } - return true; - }) - .filter(x -> x.getPartner() != null) - .filter(x -> { - if (partnerId != null) { - return x.getPartner().getId().equals(partnerId); - } - return true; - }) - .map(ex -> CredEx.from(ex, conv.toAPIObject(ex.getPartner()))) - .collect(Collectors.toList()); - } - - public CredEx getCredEx(@NonNull UUID id) { - BPACredentialExchange credEx = credExRepo.findById(id).orElseThrow(EntityNotFoundException::new); - return CredEx.from(credEx, conv.toAPIObject(credEx.getPartner())); - } - - public CredEx revokeCredentialExchange(@NonNull UUID id) { - if (!config.getTailsServerConfigured()) { - throw new IssuerException(msg.getMessage("api.issuer.no.tails.server")); - } - BPACredentialExchange credEx = credExRepo.findById(id).orElseThrow(EntityNotFoundException::new); - if (StringUtils.isEmpty(credEx.getRevRegId())) { - throw new IssuerException(msg.getMessage("api.issuer.credential.missing.revocation.info")); - } - try { - ac.revocationRevoke(RevokeRequest - .builder() - .credRevId(credEx.getCredRevId()) - .revRegId(credEx.getRevRegId()) - .publish(Boolean.TRUE) - .connectionId(credEx.getPartner() != null ? credEx.getPartner().getConnectionId() : null) - .notify(Boolean.TRUE) - .build()); - credEx.setRevoked(Boolean.TRUE); - credEx.pushStates(CredentialExchangeState.CREDENTIAL_REVOKED); - credExRepo.update(credEx); - return CredEx.from(credEx); - } catch (IOException e) { - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } - } - - /** - * Send partner a credential (counter) offer in reference to a proposal (Not to - * be confused with the automated send-offer flow). - * - * @param id credential exchange id - * @param counterOffer {@link CredentialOfferRequest} - * @return {@link CredEx} updated credential exchange, if found - */ - public CredEx sendCredentialOffer(@NonNull UUID id, @NonNull CredentialOfferRequest counterOffer) { - BPACredentialExchange credEx = credExRepo.findById(id).orElseThrow(EntityNotFoundException::new); - if (!credEx.stateIsProposalReceived()) { - throw new WrongApiUsageException(msg.getMessage("api.issuer.credential.send.offer.wrong.state", - Map.of("state", credEx.getState()))); - } - List attributes; - if (counterOffer.acceptAll()) { - attributes = credEx.getCredentialProposal() != null - ? credEx.getCredentialProposal().getAttributes() - : List.of(); - } else { - attributes = counterOffer.toCredentialAttributes(); - } - String credDefId = credEx.getCredDef() != null ? credEx.getCredDef().getCredentialDefinitionId() : null; - if (StringUtils.isNotEmpty(counterOffer.getCredDefId()) && !counterOffer.getCredDefId().equals(credDefId)) { - BPACredentialDefinition counterCredDef = credDefRepo - .findByCredentialDefinitionId(counterOffer.getCredDefId()) - .orElseThrow(() -> new WrongApiUsageException( - msg.getMessage("api.issuer.credential.send.offer.wrong.creddef"))); - credDefId = counterCredDef.getCredentialDefinitionId(); - credEx.setCredDef(counterCredDef); - credExRepo.update(credEx); - } - V10CredentialBoundOfferRequest v1Offer = V10CredentialBoundOfferRequest - .builder() - .counterProposal(CredentialProposal - .builder() - .schemaId(credEx.getSchema() != null ? credEx.getSchema().getSchemaId() : null) - .credDefId(credDefId) - .credentialProposal(org.hyperledger.acy_py.generated.model.CredentialPreview - .builder() - .attributes(attributes.stream().map(a -> CredAttrSpec - .builder() - .name(a.getName()).value(a.getValue()).mimeType(a.getMimeType()) - .build()).collect(Collectors.toList())) - .build()) - .build()) - .build(); - try { - if (ExchangeVersion.V1.equals(credEx.getExchangeVersion())) { - ac.issueCredentialRecordsSendOffer(credEx.getCredentialExchangeId(), v1Offer); - } else { - ac.issueCredentialV2RecordsSendOffer(credEx.getCredentialExchangeId(), v1Offer); - } - } catch (IOException e) { - throw new NetworkException(msg.getMessage("acapy.unavailable"), e); - } catch (AriesException e) { - if (e.getCode() == 400) { - String message = msg.getMessage("api.issuer.credential.exchange.problem"); - credEx.pushStates(CredentialExchangeState.PROBLEM); - credExRepo.updateAfterEventNoRevocationInfo( - credEx.getId(), credEx.getState(), credEx.getStateToTimestamp(), message); - throw new WrongApiUsageException(message); - } - throw e; - } - Credential credential = Credential.builder() - .attrs(attributes.stream() - .collect(Collectors.toMap(CredentialAttributes::getName, CredentialAttributes::getValue))) - .build(); - credEx.setCredential(credential); - credExRepo.updateCredential(credEx.getId(), credential); - return CredEx.from(credEx); - } - - public void declineCredentialProposal(@NonNull UUID id, @Nullable String message) { - if (StringUtils.isEmpty(message)) { - message = msg.getMessage("api.issuer.credential.exchange.declined"); - } - BPACredentialExchange credEx = getCredentialExchange(id); - credEx.pushStates(CredentialExchangeState.DECLINED, Instant.now()); - credExRepo.updateAfterEventNoRevocationInfo(credEx.getId(), credEx.getState(), credEx.getStateToTimestamp(), - message); - declineCredentialExchange(credEx, message); - } - - // Credential Management - Called By Event Handler - - public void handleCredentialProposal(@NonNull V1CredentialExchange ex, ExchangeVersion exchangeVersion) { - partnerRepo.findByConnectionId(ex.getConnectionId()).ifPresent(partner -> { - BPACredentialExchange.BPACredentialExchangeBuilder b = BPACredentialExchange - .builder() - .partner(partner) - .role(CredentialExchangeRole.ISSUER) - .state(ex.getState()) - .pushStateChange(ex.getState(), Instant.now()) - .exchangeVersion(exchangeVersion) - .credentialExchangeId(ex.getCredentialExchangeId()) - .threadId(ex.getThreadId()) - .credentialProposal(ex.getCredentialProposalDict() != null - ? ex.getCredentialProposalDict().getCredentialProposal() - : null); - // preselecting first match - credDefRepo.findBySchemaId(ex.getCredentialProposalDict().getSchemaId()).stream().findFirst() - .ifPresentOrElse(dbCredDef -> { - b.schema(dbCredDef.getSchema()).credDef(dbCredDef); - credExRepo.save(b.build()); - }, () -> { - b.errorMsg(msg.getMessage("api.holder.issuer.has.no.creddef", - Map.of("id", ex.getCredentialProposalDict().getSchemaId()))); - credExRepo.save(b.build()); - }); - fireCredentialProposalEvent(); - }); - } - - /** - * In v1 (indy) this message can only be received after a preceding Credential - * Offer, meaning the holder can never start with a Credential Request, so it is - * ok to directly auto accept the request - * - * @param ex {@link V1CredentialExchange} - */ - public void handleV1CredentialRequest(@NonNull V1CredentialExchange ex) { - try { - if (Boolean.FALSE.equals(acaPyConfig.getAutoRespondCredentialRequest())) { - ac.issueCredentialRecordsIssue(ex.getCredentialExchangeId(), - V1CredentialIssueRequest.builder().build()); - } - handleV1CredentialExchange(ex); // save state changes - } catch (IOException e) { - log.error(msg.getMessage("acapy.unavailable")); - } - } - - /** - * Handle issue credential v1 state changes and revocation info - * - * @param ex {@link V1CredentialExchange} - */ - public void handleV1CredentialExchange(@NonNull V1CredentialExchange ex) { - credExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()).ifPresent(bpaEx -> { - boolean notDeclined = bpaEx.stateIsNotDeclined(); - CredentialExchangeState state = ex.getState() != null ? ex.getState() : CredentialExchangeState.PROBLEM; - bpaEx.pushStates(state, ex.getUpdatedAt()); - if (StringUtils.isNotEmpty(ex.getErrorMsg())) { - if (notDeclined) { - credExRepo.updateAfterEventNoRevocationInfo(bpaEx.getId(), - bpaEx.getState(), bpaEx.getStateToTimestamp(), ex.getErrorMsg()); - fireCredentialProblemEvent(bpaEx); - } - } else { - credExRepo.updateAfterEventWithRevocationInfo(bpaEx.getId(), - bpaEx.getState(), bpaEx.getStateToTimestamp(), - ex.getRevocRegId(), ex.getRevocationId(), ex.getErrorMsg()); - } - if (ex.stateIsCredentialAcked() && ex.autoIssueEnabled()) { - ex.findAttributesInCredentialOfferDict().ifPresent( - attr -> { - credExRepo.updateCredential(bpaEx.getId(), Credential.builder().attrs(attr).build()); - fireCredentialAcceptedEvent(bpaEx); - }); - } - }); - } - - /** - * Handle issue credential v2 state changes - * - * @param ex {@link V20CredExRecord} - */ - public void handleV2CredentialExchange(@NonNull V20CredExRecord ex) { - credExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()) - .ifPresent(bpaEx -> { - if (bpaEx.stateIsNotDeclined()) { - CredentialExchangeState state = ex.getState(); - if (StringUtils.isNotEmpty(ex.getErrorMsg())) { - state = CredentialExchangeState.PROBLEM; - } - bpaEx.pushStates(state, ex.getUpdatedAt()); - credExRepo.updateAfterEventNoRevocationInfo(bpaEx.getId(), - bpaEx.getState(), bpaEx.getStateToTimestamp(), ex.getErrorMsg()); - if (ex.stateIsCredentialIssued() && ex.autoIssueEnabled()) { - ex.getByFormat().findValuesInIndyCredIssue().ifPresent( - attr -> credExRepo.updateCredential(bpaEx.getId(), - Credential.builder().attrs(attr).build())); - } - } - }); - } - - /** - * Handle issue credential v2 revocation info - * - * @param revocationInfo {@link V2IssueIndyCredentialEvent} - */ - public void handleIssueCredentialV2Indy(V2IssueIndyCredentialEvent revocationInfo) { - // Note: This event contains no role info, so we have to check this here - // explicitly - credExRepo.findByCredentialExchangeId(revocationInfo.getCredExId()).ifPresent(bpaEx -> { - if (bpaEx.roleIsIssuer() && StringUtils.isNotEmpty(revocationInfo.getRevRegId())) { - credExRepo.updateRevocationInfo(bpaEx.getId(), revocationInfo.getRevRegId(), - revocationInfo.getCredRevId()); - } else if (bpaEx.roleIsHolder() && StringUtils.isNotEmpty(revocationInfo.getCredIdStored())) { - credExRepo.updateReferent(bpaEx.getId(), revocationInfo.getCredIdStored()); - // holder event is missing the credRevId, so get it from aca-py in case the - // credential is revocable - if (StringUtils.isNotEmpty(revocationInfo.getRevRegId())) { - try { - ac.credential(revocationInfo.getCredIdStored()).ifPresent( - c -> credExRepo.updateRevocationInfo(bpaEx.getId(), c.getRevRegId(), c.getCredRevId())); - } catch (IOException e) { - log.error(msg.getMessage("acapy.unavailable")); - } - } - } - }); - } - - /** - * In v2 (indy and w3c) a holder can decide to skip negotiation and directly - * start the whole flow with a request. So we check if there is a preceding - * record if not decline with problem report TODO support v2 credential request - * without prior negotiation - * - * @param ex {@link V20CredExRecord v2CredEx} - */ - public void handleV2CredentialRequest(@NonNull V20CredExRecord ex) { - credExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()).ifPresentOrElse(db -> { - try { - if (Boolean.FALSE.equals(acaPyConfig.getAutoRespondCredentialRequest())) { - ac.issueCredentialV2RecordsIssue(ex.getCredentialExchangeId(), - V20CredIssueRequest.builder().build()); - } - db.pushStates(ex.getState(), ex.getUpdatedAt()); - credExRepo.updateAfterEventNoRevocationInfo(db.getId(), - db.getState(), db.getStateToTimestamp(), ex.getErrorMsg()); - } catch (IOException e) { - log.error(msg.getMessage("acapy.unavailable")); - } - }, () -> { - try { - ac.issueCredentialV2RecordsProblemReport(ex.getCredentialExchangeId(), V20CredIssueProblemReportRequest - .builder() - .description( - "starting a credential exchange without prior negotiation is not supported by this agent") - .build()); - log.warn("Received credential request without existing offer, dropping request"); - } catch (IOException e) { - log.error(msg.getMessage("acapy.unavailable")); - } - }); - } - - private void fireCredentialIssuedEvent(@NonNull BPACredentialExchange db) { - eventPublisher.publishEventAsync(CredentialIssuedEvent.builder() - .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) - .build()); - } - - private void fireCredentialAcceptedEvent(@NonNull BPACredentialExchange db) { - eventPublisher.publishEventAsync(CredentialAcceptedEvent.builder() - .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) - .build()); - } - - private void fireCredentialProblemEvent(@NonNull BPACredentialExchange db) { - eventPublisher.publishEventAsync(CredentialProblemEvent.builder() - .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) - .build()); - } - - private void fireCredentialProposalEvent() { - eventPublisher.publishEventAsync(new CredentialProposalEvent()); - } - - private String schemaLabel(@NonNull BPACredentialExchange db) { - if (db.getCredential() != null && db.getCredential().getSchemaId() != null) { - return schemaService.getSchemaLabel(db.getCredential().getSchemaId()); - } - return ""; - } - - /** - * Internal transfer POJO - */ - @Data - @Builder - public static final class IssueCredentialRequest { - private UUID credDefId; - private UUID partnerId; - private ExchangeVersion exchangeVersion; - private JsonNode document; - - public boolean isV1() { - return exchangeVersion == null || ExchangeVersion.V1.equals(exchangeVersion); - } - - public static IssueCredentialRequest from(IssueCredentialSendRequest r) { - return IssueCredentialRequest - .builder() - .credDefId(UUID.fromString(r.getCredDefId())) - .partnerId(UUID.fromString(r.getPartnerId())) - .exchangeVersion(r.getExchangeVersion()) - .document(r.getDocument()) - .build(); - } - } - - @Data - @Builder - private static final class ExchangeResult { - private String credentialExchangeId; - private String threadId; - - public static ExchangeResult fromV1(@NonNull V1CredentialExchange ex) { - return ExchangeResult - .builder() - .credentialExchangeId(ex.getCredentialExchangeId()) - .threadId(ex.getThreadId()) - .build(); - } - - public static ExchangeResult fromV2(@NonNull V20CredExRecord ex) { - return ExchangeResult - .builder() - .credentialExchangeId(ex.getCredentialExchangeId()) - .threadId(ex.getThreadId()) - .build(); - } - } -} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerIndyManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerIndyManager.java new file mode 100644 index 000000000..e57f108d6 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerIndyManager.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.credential; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.acy_py.generated.model.CredAttrSpec; +import org.hyperledger.acy_py.generated.model.CredentialProposal; +import org.hyperledger.acy_py.generated.model.V10CredentialBoundOfferRequest; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.credentials.Credential; +import org.hyperledger.aries.api.credentials.CredentialAttributes; +import org.hyperledger.aries.api.credentials.CredentialPreview; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialProposalRequest; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.revocation.RevokeRequest; +import org.hyperledger.bpa.api.exception.IssuerException; +import org.hyperledger.bpa.api.exception.NetworkException; +import org.hyperledger.bpa.api.exception.WrongApiUsageException; +import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.controller.api.issuer.CredEx; +import org.hyperledger.bpa.controller.api.issuer.IssueCredentialRequest; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.persistence.model.BPACredentialDefinition; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.Partner; +import org.hyperledger.bpa.persistence.repository.BPACredentialDefinitionRepository; +import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; +import org.hyperledger.bpa.persistence.repository.PartnerRepository; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.time.Instant; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Handles all credential issuer logic that is specific to indy + */ +@Slf4j +@Singleton +public class IssuerIndyManager { + + @Inject + AriesClient ac; + + @Inject + BPACredentialDefinitionRepository credDefRepo; + + @Inject + PartnerRepository partnerRepo; + + @Inject + IssuerCredExRepository issuerCredExRepo; + + @Inject + Converter conv; + + @Inject + BPAMessageSource.DefaultMessageSource msg; + + // Indy Credential Management - Called By User + + /** + * Issuer initialises the indy credential exchange with an offer. There is no + * preexisting proposal from the holder. + * + * @param request {@link IssueCredentialRequest} + * @return {@link BPACredentialExchange} + */ + public BPACredentialExchange issueIndyCredential(@NonNull IssueCredentialRequest request) { + Partner dbPartner = partnerRepo.findById(request.getPartnerId()) + .orElseThrow(() -> new IssuerException(msg.getMessage("api.partner.not.found", + Map.of("id", request.getPartnerId())))); + + BPACredentialDefinition dbCredDef = credDefRepo.findById(request.getCredDefId()) + .orElseThrow(() -> new IssuerException( + msg.getMessage("api.issuer.creddef.not.found", Map.of("id", request.getCredDefId())))); + + Map document = conv.toStringMap(request.getDocument()); + + checkCredentialAttributes(document, dbCredDef); + + String connectionId = dbPartner.getConnectionId(); + String schemaId = dbCredDef.getSchema().getSchemaId(); + String credentialDefinitionId = dbCredDef.getCredentialDefinitionId(); + + ExchangeResult exResult; + ExchangeVersion exVersion; + + V1CredentialProposalRequest proposal = V1CredentialProposalRequest + .builder() + .connectionId(Objects.requireNonNull(connectionId)) + .schemaId(schemaId) + .credentialProposal(new CredentialPreview(CredentialAttributes.fromMap(document))) + .credentialDefinitionId(credentialDefinitionId) + .build(); + + if (request.exchangeIsV1()) { + exVersion = ExchangeVersion.V1; + exResult = sendV1IndyCredential(proposal); + } else { + exVersion = ExchangeVersion.V2; + exResult = sendV2IndyCredential(proposal); + } + + BPACredentialExchange cex = BPACredentialExchange.builder() + .schema(dbCredDef.getSchema()) + .partner(dbPartner) + .credDef(dbCredDef) + .role(CredentialExchangeRole.ISSUER) + .state(CredentialExchangeState.OFFER_SENT) + .pushStateChange(CredentialExchangeState.OFFER_SENT, Instant.now()) + // as I'm the issuer I know what I have issued, no need to get this info from + // the exchange record again + .indyCredential(Credential.builder() + .schemaId(schemaId) + .attrs(document) + .build()) + .credentialExchangeId(exResult.getCredentialExchangeId()) + .threadId(exResult.getThreadId()) + .exchangeVersion(exVersion) + .build(); + return issuerCredExRepo.save(cex); + } + + public void reIssueIndyCredential(@NonNull BPACredentialExchange credEx) { + if (credEx.roleIsIssuer() && credEx.stateIsRevoked()) { + issueIndyCredential(IssueCredentialRequest.builder() + .partnerId(credEx.getPartner() != null ? credEx.getPartner().getId() : null) + .credDefId(credEx.getCredDef() != null ? credEx.getCredDef().getId() : null) + .document(conv.mapToNode(Objects.requireNonNull(credEx.getIndyCredential()).getAttrs())) + .exchangeVersion(credEx.getExchangeVersion()) + .build()); + } else { + throw new IssuerException( + msg.getMessage("api.issuer.reissue.wrong.state", Map.of("state", credEx.getState()))); + } + } + + public CredEx revokeIndyCredential(BPACredentialExchange credEx) { + if (StringUtils.isEmpty(credEx.getRevRegId())) { + throw new IssuerException(msg.getMessage("api.issuer.credential.missing.revocation.info")); + } + try { + ac.revocationRevoke(RevokeRequest + .builder() + .credRevId(credEx.getCredRevId()) + .revRegId(credEx.getRevRegId()) + .publish(Boolean.TRUE) + .connectionId(credEx.getPartner() != null ? credEx.getPartner().getConnectionId() : null) + .notify(Boolean.TRUE) + .build()); + credEx.setRevoked(Boolean.TRUE); + credEx.pushStates(CredentialExchangeState.CREDENTIAL_REVOKED); + issuerCredExRepo.update(credEx); + return CredEx.from(credEx); + } catch (IOException e) { + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + } + + public CredEx sendOffer(@NonNull BPACredentialExchange credEx, @NotNull Map attributes, + @NonNull IssuerManager.IdWrapper ids) throws IOException { + String credDefId = credEx.getCredDef() != null ? credEx.getCredDef().getCredentialDefinitionId() : null; + if (StringUtils.isNotEmpty(ids.credDefId()) && !StringUtils.equals(credDefId, ids.credDefId())) { + BPACredentialDefinition counterCredDef = credDefRepo + .findByCredentialDefinitionId(ids.credDefId()) + .orElseThrow(() -> new WrongApiUsageException( + msg.getMessage("api.issuer.credential.send.offer.wrong.creddef"))); + credDefId = counterCredDef.getCredentialDefinitionId(); + credEx.setCredDef(counterCredDef); + issuerCredExRepo.update(credEx); + } + V10CredentialBoundOfferRequest v1Offer = V10CredentialBoundOfferRequest + .builder() + .counterProposal(CredentialProposal + .builder() + .schemaId(credEx.getSchema() != null ? credEx.getSchema().getSchemaId() : null) + .credDefId(credDefId) + .credentialProposal(org.hyperledger.acy_py.generated.model.CredentialPreview + .builder() + .attributes(attributes.entrySet().stream().map(a -> CredAttrSpec + .builder() + .name(a.getKey()).value(a.getValue()) + .build()).collect(Collectors.toList())) + .build()) + .build()) + .build(); + + if (ExchangeVersion.V1.equals(credEx.getExchangeVersion())) { + ac.issueCredentialRecordsSendOffer(credEx.getCredentialExchangeId(), v1Offer); + } else { + ac.issueCredentialV2RecordsSendOffer(credEx.getCredentialExchangeId(), v1Offer); + } + + Credential credential = Credential.builder() + .attrs(attributes) + .build(); + credEx.setIndyCredential(credential); + issuerCredExRepo.updateCredential(credEx.getId(), credential); + return CredEx.from(credEx); + } + + /** + * Check if the supplied attributes match the schema + * + * @param document the credential + * @param dbCredDef {@link BPACredentialDefinition} + */ + private void checkCredentialAttributes(Map document, BPACredentialDefinition dbCredDef) { + Set documentAttributeNames = document.keySet(); + Set schemaAttributeNames = dbCredDef.getSchema().getSchemaAttributeNames(); + if (!documentAttributeNames.equals(schemaAttributeNames)) { + throw new IssuerException(msg.getMessage("api.issuer.credential.document.mismatch", + Map.of("doc", documentAttributeNames, "schema", schemaAttributeNames))); + } + } + + private ExchangeResult sendV1IndyCredential(@NonNull V1CredentialProposalRequest proposal) { + try { + return ac.issueCredentialSend(proposal) + .map(ExchangeResult::fromV1) + .orElseThrow(); + } catch (IOException e) { + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + } + + private ExchangeResult sendV2IndyCredential(@NonNull V1CredentialProposalRequest proposal) { + try { + return ac.issueCredentialV2Send(proposal) + .map(ExchangeResult::fromV2) + .orElseThrow(); + } catch (IOException e) { + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + } + + @Data + @Builder + private static final class ExchangeResult { + private String credentialExchangeId; + private String threadId; + + public static ExchangeResult fromV1(@NonNull V1CredentialExchange ex) { + return ExchangeResult + .builder() + .credentialExchangeId(ex.getCredentialExchangeId()) + .threadId(ex.getThreadId()) + .build(); + } + + public static ExchangeResult fromV2(@NonNull V20CredExRecord ex) { + return ExchangeResult + .builder() + .credentialExchangeId(ex.getCredentialExchangeId()) + .threadId(ex.getThreadId()) + .build(); + } + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerManager.java new file mode 100644 index 000000000..1b0d36dbf --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/IssuerManager.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.credential; + +import io.micronaut.context.event.ApplicationEventPublisher; +import io.micronaut.core.annotation.Nullable; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.acy_py.generated.model.V20CredIssueProblemReportRequest; +import org.hyperledger.acy_py.generated.model.V20CredIssueRequest; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.credentials.Credential; +import org.hyperledger.aries.api.exception.AriesException; +import org.hyperledger.aries.api.issue_credential_v1.*; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V2IssueIndyCredentialEvent; +import org.hyperledger.aries.api.issue_credential_v2.V2ToV1IndyCredentialConverter; +import org.hyperledger.bpa.api.aries.AriesCredential; +import org.hyperledger.bpa.api.exception.EntityNotFoundException; +import org.hyperledger.bpa.api.exception.IssuerException; +import org.hyperledger.bpa.api.exception.NetworkException; +import org.hyperledger.bpa.api.exception.WrongApiUsageException; +import org.hyperledger.bpa.api.notification.CredentialAcceptedEvent; +import org.hyperledger.bpa.api.notification.CredentialIssuedEvent; +import org.hyperledger.bpa.api.notification.CredentialProblemEvent; +import org.hyperledger.bpa.api.notification.CredentialProposalEvent; +import org.hyperledger.bpa.config.AcaPyConfig; +import org.hyperledger.bpa.config.RuntimeConfig; +import org.hyperledger.bpa.controller.api.issuer.CredEx; +import org.hyperledger.bpa.controller.api.issuer.CredentialOfferRequest; +import org.hyperledger.bpa.controller.api.issuer.IssueCredentialRequest; +import org.hyperledger.bpa.impl.aries.jsonld.IssuerLDManager; +import org.hyperledger.bpa.impl.aries.jsonld.LDContextHelper; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.util.TimeUtil; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.repository.BPACredentialDefinitionRepository; +import org.hyperledger.bpa.persistence.repository.PartnerRepository; + +import java.io.IOException; +import java.time.Instant; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * Wraps all credential issuer specific logic that is common for both indy and + * json-ld credentials. + */ +@Slf4j +@Singleton +public class IssuerManager extends CredentialManagerBase { + + @Inject + AcaPyConfig acaPyConfig; + + @Inject + BPACredentialDefinitionRepository credDefRepo; + + @Inject + PartnerRepository partnerRepo; + + @Inject + IssuerIndyManager indy; + + @Inject + IssuerLDManager ld; + + @Inject + ApplicationEventPublisher eventPublisher; + + @Inject + RuntimeConfig config; + + @Inject + SchemaService schemaService; + + // Credential Management - Called By User + + public record IdWrapper(String credDefId, String schemaId) { + } + + /** + * Issuer initialises the credential exchange with an offer. There is no + * preexisting proposal from the holder. + * + * @param request {@link IssueCredentialRequest} + * @return credential exchange id + */ + public String issueCredential(@NonNull IssueCredentialRequest request) { + BPACredentialExchange credEx; + if (request.typeIsIndy()) { + credEx = indy.issueIndyCredential(request); + } else { + credEx = ld.issueLDCredential(request.getPartnerId(), request.getSchemaId(), request.getDocument()); + } + fireCredentialIssuedEvent(credEx); + return credEx.getCredentialExchangeId(); + } + + /** + * Re-issue a revoked credential, only works for indy credentials + * + * @param exchangeId bpa credential exchange id + */ + public void reIssueCredential(@NonNull UUID exchangeId) { + BPACredentialExchange credEx = issuerCredExRepo.findById(exchangeId).orElseThrow(EntityNotFoundException::new); + if (credEx.typeIsIndy()) { + indy.reIssueIndyCredential(credEx); + } else { + ld.reIssueLDCredential(); + } + } + + /** + * Revocation only works for indy credentials. Json-ld credentials are currently + * (February '22) not revocable, there is an ongoing discussion to use: + * https://w3c-ccg.github.io/vc-status-rl-2020/ for this. + * + * @param id bpa credential exchange id. + * @return {@link CredEx} + */ + public CredEx revokeCredential(@NonNull UUID id) { + if (!config.getTailsServerConfigured()) { + throw new IssuerException(msg.getMessage("api.issuer.no.tails.server")); + } + BPACredentialExchange credEx = issuerCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); + if (credEx.typeIsIndy()) { + return indy.revokeIndyCredential(credEx); + } + return ld.revokeLDCredential(); + } + + /** + * Send partner a credential (counter) offer in reference to a proposal (Not to + * be confused with the automated send-offer flow). + * + * @param id credential exchange id + * @param counterOffer {@link CredentialOfferRequest} + * @return {@link CredEx} updated credential exchange, if found + */ + public CredEx sendCredentialOffer(@NonNull UUID id, @NonNull CredentialOfferRequest counterOffer) { + BPACredentialExchange credEx = issuerCredExRepo.findById(id).orElseThrow(EntityNotFoundException::new); + if (!credEx.stateIsProposalReceived()) { + throw new WrongApiUsageException(msg.getMessage("api.issuer.credential.send.offer.wrong.state", + Map.of("state", credEx.getState()))); + } + Map attributes; + if (counterOffer.acceptAll()) { + attributes = credEx.proposalAttributesToMap(); + } else { + attributes = counterOffer.getAttributes(); + } + try { + IdWrapper ids = new IdWrapper(counterOffer.getCredDefId(), counterOffer.getSchemaId()); + if (credEx.typeIsIndy()) { + return indy.sendOffer(credEx, attributes, ids); + } + return ld.sendOffer(credEx, attributes, ids); + } catch (IOException e) { + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } catch (AriesException e) { + if (e.getCode() == 400) { + String message = msg.getMessage("api.issuer.credential.exchange.problem"); + credEx.pushStates(CredentialExchangeState.PROBLEM); + issuerCredExRepo.updateAfterEventNoRevocationInfo( + credEx.getId(), credEx.getState(), credEx.getStateToTimestamp(), message); + throw new WrongApiUsageException(message); + } + throw e; + } + } + + /** + * Issuer declines credential proposal received from holder + * + * @param id {@link UUID} bpa credential exchange id + * @param message optional reason + */ + public void declineCredentialProposal(@NonNull UUID id, @Nullable String message) { + if (StringUtils.isEmpty(message)) { + message = msg.getMessage("api.issuer.credential.exchange.declined"); + } + BPACredentialExchange credEx = getCredentialExchange(id); + credEx.pushStates(CredentialExchangeState.DECLINED, Instant.now()); + issuerCredExRepo.updateAfterEventNoRevocationInfo(credEx.getId(), credEx.getState(), + credEx.getStateToTimestamp(), + message); + declineCredentialExchange(credEx, message); + } + + // Credential Management - Called By Event Handler + + public void handleV1CredentialProposal(@NonNull V1CredentialExchange v1CredEx) { + handleCredentialProposalInternal(v1CredEx, ExchangeVersion.V1, true); + } + + public void handleV2CredentialProposal(@NonNull V20CredExRecord v2CredEx) { + if (v2CredEx.payloadIsLdProof()) { + handleCredentialProposalInternal(v2CredEx, ExchangeVersion.V2, false); + } else { + handleCredentialProposalInternal(V2ToV1IndyCredentialConverter.INSTANCE().toV1Proposal(v2CredEx), + ExchangeVersion.V2, true); + } + } + + private void handleCredentialProposalInternal(@NonNull BaseCredExRecord ex, ExchangeVersion exchangeVersion, + boolean isIndy) { + partnerRepo.findByConnectionId(ex.getConnectionId()).ifPresent(partner -> { + BPACredentialExchange.BPACredentialExchangeBuilder b = BPACredentialExchange + .builder() + .partner(partner) + .role(CredentialExchangeRole.ISSUER) + .state(ex.getState()) + .pushStateChange(ex.getState(), TimeUtil.fromISOInstant(ex.getUpdatedAt())) + .exchangeVersion(exchangeVersion) + .credentialExchangeId(ex.getCredentialExchangeId()) + .threadId(ex.getThreadId()) + .credentialProposal(resolveProposal(ex)); + if (isIndy) { + // preselecting first match + credDefRepo.findBySchemaId(resolveSchemaIdFromProposal(ex)).stream().findFirst() + .ifPresentOrElse(dbCredDef -> { + b.schema(dbCredDef.getSchema()).credDef(dbCredDef); + issuerCredExRepo.save(b.build()); + }, () -> { + b.errorMsg(msg.getMessage("api.holder.issuer.has.no.creddef", + Map.of("id", Objects.requireNonNullElse(resolveSchemaIdFromProposal(ex), "")))); + issuerCredExRepo.save(b.build()); + }); + } else { + ld.handleCredentialProposal(resolveSchemaIdFromProposal(ex), b); + } + fireCredentialProposalEvent(); + }); + } + + /** + * In v1 (indy) this message can only be received after a preceding Credential + * Offer, meaning the holder can never start with a Credential Request, so it is + * ok to directly auto accept the request + * + * @param ex {@link V1CredentialExchange} + */ + public void handleV1CredentialRequest(@NonNull V1CredentialExchange ex) { + try { + if (Boolean.FALSE.equals(acaPyConfig.getAutoRespondCredentialRequest())) { + ac.issueCredentialRecordsIssue(ex.getCredentialExchangeId(), + V1CredentialIssueRequest.builder().build()); + } + handleV1CredentialExchange(ex); // save state changes + } catch (IOException e) { + log.error(msg.getMessage("acapy.unavailable")); + } + } + + /** + * Handle issue credential v1 state changes and revocation info + * + * @param ex {@link V1CredentialExchange} + */ + public void handleV1CredentialExchange(@NonNull V1CredentialExchange ex) { + issuerCredExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()).ifPresent(bpaEx -> { + boolean notDeclined = bpaEx.stateIsNotDeclined(); + CredentialExchangeState state = ex.getState() == null + || CredentialExchangeState.ABANDONED.equals(ex.getState()) + ? CredentialExchangeState.PROBLEM + : ex.getState(); + bpaEx.pushStates(state, ex.getUpdatedAt()); + if (StringUtils.isNotEmpty(ex.getErrorMsg())) { + if (notDeclined) { + issuerCredExRepo.updateAfterEventNoRevocationInfo(bpaEx.getId(), + bpaEx.getState(), bpaEx.getStateToTimestamp(), ex.getErrorMsg()); + fireCredentialProblemEvent(bpaEx); + } + } else { + issuerCredExRepo.updateAfterEventWithRevocationInfo(bpaEx.getId(), + bpaEx.getState(), bpaEx.getStateToTimestamp(), + ex.getRevocRegId(), ex.getRevocationId(), ex.getErrorMsg()); + } + if (ex.stateIsCredentialAcked() && ex.autoIssueEnabled()) { + ex.findAttributesInCredentialOfferDict().ifPresent( + attr -> { + issuerCredExRepo.updateCredential(bpaEx.getId(), Credential.builder().attrs(attr).build()); + fireCredentialAcceptedEvent(bpaEx); + }); + } + }); + } + + /** + * In v2 (indy and w3c) a holder can decide to skip negotiation and directly + * start the whole flow with a request. So we check if there is a preceding + * record if not decline with problem report TODO support v2 credential request + * without prior negotiation + * + * @param ex {@link V20CredExRecord v2CredEx} + */ + public void handleV2CredentialRequest(@NonNull V20CredExRecord ex) { + issuerCredExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()).ifPresentOrElse(db -> { + try { + if (Boolean.FALSE.equals(acaPyConfig.getAutoRespondCredentialRequest())) { + ac.issueCredentialV2RecordsIssue(ex.getCredentialExchangeId(), + V20CredIssueRequest.builder().build()); + } + db.pushStates(ex.getState(), ex.getUpdatedAt()); + issuerCredExRepo.updateAfterEventNoRevocationInfo(db.getId(), + db.getState(), db.getStateToTimestamp(), ex.getErrorMsg()); + } catch (IOException e) { + log.error(msg.getMessage("acapy.unavailable")); + } + }, () -> { + try { + ac.issueCredentialV2RecordsProblemReport(ex.getCredentialExchangeId(), V20CredIssueProblemReportRequest + .builder() + .description( + "starting a credential exchange without prior negotiation is not supported by this agent") + .build()); + log.warn("Received credential request without existing offer, dropping request"); + } catch (IOException e) { + log.error(msg.getMessage("acapy.unavailable")); + } + }); + } + + /** + * Handle issue credential v2 state changes + * + * @param ex {@link V20CredExRecord} + */ + public void handleV2CredentialExchange(@NonNull V20CredExRecord ex) { + issuerCredExRepo.findByCredentialExchangeId(ex.getCredentialExchangeId()) + .ifPresent(bpaEx -> { + if (bpaEx.stateIsNotDeclined()) { + CredentialExchangeState state = ex.getState(); + if (StringUtils.isNotEmpty(ex.getErrorMsg())) { + state = CredentialExchangeState.PROBLEM; + } + bpaEx.pushStates(state, ex.getUpdatedAt()); + issuerCredExRepo.updateAfterEventNoRevocationInfo(bpaEx.getId(), + bpaEx.getState(), bpaEx.getStateToTimestamp(), ex.getErrorMsg()); + if (ex.stateIsCredentialIssued() && ex.autoIssueEnabled()) { + if (ex.payloadIsIndy()) { + ex.getByFormat().findValuesInIndyCredIssue().ifPresent( + attr -> issuerCredExRepo.updateCredential(bpaEx.getId(), + Credential.builder().attrs(attr).build())); + } else { + issuerCredExRepo.updateCredential(bpaEx.getId(), + BPACredentialExchange.ExchangePayload.jsonLD(ex.resolveLDCredential())); + } + // TODO events + } + } + }); + } + + /** + * Handle issue credential v2 revocation info + * + * @param revocationInfo {@link V2IssueIndyCredentialEvent} + */ + public void handleIssueCredentialV2Indy(V2IssueIndyCredentialEvent revocationInfo) { + // Note: This event contains no role info, so we have to check this here + // explicitly + issuerCredExRepo.findByCredentialExchangeId(revocationInfo.getCredExId()).ifPresent(bpaEx -> { + if (bpaEx.roleIsIssuer() && StringUtils.isNotEmpty(revocationInfo.getRevRegId())) { + issuerCredExRepo.updateRevocationInfo(bpaEx.getId(), revocationInfo.getRevRegId(), + revocationInfo.getCredRevId()); + } else if (bpaEx.roleIsHolder() && StringUtils.isNotEmpty(revocationInfo.getCredIdStored())) { + issuerCredExRepo.updateReferent(bpaEx.getId(), revocationInfo.getCredIdStored()); + // holder event is missing the credRevId, so get it from aca-py in case the + // credential is revocable + if (StringUtils.isNotEmpty(revocationInfo.getRevRegId())) { + try { + ac.credential(revocationInfo.getCredIdStored()).ifPresent( + c -> issuerCredExRepo.updateRevocationInfo(bpaEx.getId(), c.getRevRegId(), + c.getCredRevId())); + } catch (IOException e) { + log.error(msg.getMessage("acapy.unavailable")); + } + } + } + }); + } + + // Helpers + + private BPACredentialExchange.ExchangePayload resolveProposal(@NonNull BaseCredExRecord ex) { + if (ex instanceof V1CredentialExchange v1Indy) { + return v1Indy.getCredentialProposalDict() != null + ? BPACredentialExchange.ExchangePayload + .indy(v1Indy.getCredentialProposalDict().getCredentialProposal()) + : null; + } else if (ex instanceof V20CredExRecord v2) { + return BPACredentialExchange.ExchangePayload.jsonLD(Objects.requireNonNull(v2.resolveLDCredProposal())); + } + throw new IllegalStateException(); + } + + private String resolveSchemaIdFromProposal(@NonNull BaseCredExRecord ex) { + if (ex instanceof V1CredentialExchange v1Indy) { + return Objects.requireNonNull(v1Indy.getCredentialProposalDict()).getSchemaId(); + } else if (ex instanceof V20CredExRecord v2) { + return LDContextHelper.findSchemaId(v2.resolveLDCredProposal()); + } + return null; + } + + private String schemaLabel(@NonNull BPACredentialExchange db) { + if (db.getIndyCredential() != null && db.getIndyCredential().getSchemaId() != null) { + return schemaService.getSchemaLabel(db.getIndyCredential().getSchemaId()); + } + return ""; + } + + // Events + + private void fireCredentialIssuedEvent(@NonNull BPACredentialExchange db) { + eventPublisher.publishEventAsync(CredentialIssuedEvent.builder() + .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) + .build()); + } + + private void fireCredentialProposalEvent() { + eventPublisher.publishEventAsync(new CredentialProposalEvent()); + } + + private void fireCredentialAcceptedEvent(@NonNull BPACredentialExchange db) { + eventPublisher.publishEventAsync(CredentialAcceptedEvent.builder() + .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) + .build()); + } + + private void fireCredentialProblemEvent(@NonNull BPACredentialExchange db) { + eventPublisher.publishEventAsync(CredentialProblemEvent.builder() + .credential(AriesCredential.fromBPACredentialExchange(db, schemaLabel(db))) + .build()); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/OOBCredentialOffer.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/OOBCredentialOffer.java index 5ea40b0b0..3dfeb302d 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/OOBCredentialOffer.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/credential/OOBCredentialOffer.java @@ -101,11 +101,12 @@ public OOBCredentialOffer(AriesClient ac) { * @param req {@link IssueOOBCredentialRequest} * @return location of the offer */ + // TODO LD-Credential Support public APICreateInvitationResponse issueConnectionLess(@NonNull IssueOOBCredentialRequest req) { BPACredentialDefinition dbCredDef = credDefRepo.findById(req.getCredDefId()) .orElseThrow(() -> new WrongApiUsageException( ms.getMessage("api.issuer.creddef.not.found", Map.of("id", req.getCredDefId())))); - validator.validateAttributesAgainstSchema(req.getDocument(), dbCredDef.getSchema().getSchemaId()); + validator.validateAttributesAgainstIndySchema(req.getDocument(), dbCredDef.getSchema().getSchemaId()); Map document = conv.toStringMap(req.getDocument()); @@ -161,7 +162,8 @@ private void persistCredentialExchange( .credentialExchangeId(r.getCredentialExchangeId()) .threadId(r.getThreadId()) .credentialOffer(r.getCredentialProposalDict() != null - ? r.getCredentialProposalDict().getCredentialProposal() + ? BPACredentialExchange.ExchangePayload + .indy(r.getCredentialProposalDict().getCredentialProposal()) : null); credExRepo.save(b.build()); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDManager.java new file mode 100644 index 000000000..bb6801c30 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDManager.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.Setter; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.issue_credential_v1.BaseCredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.impl.activity.LabelStrategy; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.BPASchema; +import org.hyperledger.bpa.persistence.repository.BPASchemaRepository; +import org.hyperledger.bpa.persistence.repository.HolderCredExRepository; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +/** + * Handles all credential holder logic that is specific to json-ld + */ +@Singleton +public class HolderLDManager { + + @Inject + @Setter(AccessLevel.PROTECTED) + AriesClient ac; + + @Inject + BPASchemaRepository schemaRepo; + + @Inject + HolderCredExRepository holderCredExRepo; + + @Inject + SchemaService schemaService; + + @Inject + LabelStrategy labelStrategy; + + @Inject + LDContextHelper ldHelper; + + @Inject + ObjectMapper mapper; + + public void sendCredentialProposal( + @NonNull String connectionId, + @NonNull BPASchema s, + @NonNull Map document, + @NonNull BPACredentialExchange.BPACredentialExchangeBuilder dbCredEx) + throws IOException { + JsonNode jsonNode = mapper.valueToTree(document); + V2CredentialExchangeFree v2Request = V2CredentialExchangeFree.builder() + .connectionId(UUID.fromString(connectionId)) + .filter(ldHelper.buildVC(s, jsonNode, Boolean.FALSE)) + .build(); + ac.issueCredentialV2SendProposal(v2Request).ifPresent(v2 -> dbCredEx + .threadId(v2.getThreadId()) + .credentialExchangeId(v2.getCredentialExchangeId()) + .exchangeVersion(ExchangeVersion.V2) + .type(CredentialType.JSON_LD) + .credentialProposal(BPACredentialExchange.ExchangePayload.jsonLD(v2.resolveLDCredProposal()))); + } + + public BPASchema checkSchema(BaseCredExRecord credExBase) { + BPASchema schema = null; + if (credExBase instanceof V20CredExRecord) { + V20CredExRecordByFormat.LdProof offer = ((V20CredExRecord) credExBase).resolveLDCredOffer(); + List type = offer.getCredential().getType(); + type.removeAll(CredentialType.JSON_LD.getType()); + + String schemaId = LDContextHelper.findSchemaId(offer); + Optional bpaSchema = schemaRepo.findBySchemaId(schemaId); + if (bpaSchema.isPresent()) { + schema = bpaSchema.get(); + } else { + SchemaAPI schemaApi = schemaService.addJsonLDSchema(schemaId, null, null, type.get(0), + offer.getCredential().getCredentialSubject().keySet()); + schema = schemaRepo.findById(schemaApi.getId()).orElseThrow(); + } + } + return schema; + } + + public void handleV2CredentialReceived(@NonNull V20CredExRecord v2, @NonNull BPACredentialExchange dbCred, + String issuer) { + String label = labelStrategy.apply(dbCred.getLdCredential()); + dbCred + .pushStates(v2.getState(), v2.getUpdatedAt()) + .setLdCredential(BPACredentialExchange.ExchangePayload.jsonLD(v2.resolveLDCredOffer())) + .setIssuer(issuer) + .setLabel(label); + holderCredExRepo.update(dbCred); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDManager.java new file mode 100644 index 000000000..ae0ad0397 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDManager.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.credentials.CredentialAttributes; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; +import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; +import org.hyperledger.aries.api.issue_credential_v2.V20CredBoundOfferRequest; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.api.exception.EntityNotFoundException; +import org.hyperledger.bpa.api.exception.NetworkException; +import org.hyperledger.bpa.api.exception.WrongApiUsageException; +import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.controller.api.issuer.CredEx; +import org.hyperledger.bpa.impl.aries.credential.IssuerManager; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.BPASchema; +import org.hyperledger.bpa.persistence.model.Partner; +import org.hyperledger.bpa.persistence.repository.BPASchemaRepository; +import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; +import org.hyperledger.bpa.persistence.repository.PartnerRepository; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.time.Instant; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * Handles all credential holder logic that is specific to json-ld + */ +@Slf4j +@Singleton +public class IssuerLDManager { + + @Inject + AriesClient ac; + + @Inject + PartnerRepository partnerRepo; + + @Inject + BPASchemaRepository schemaRepo; + + @Inject + IssuerCredExRepository credExRepo; + + @Inject + LDContextHelper vcHelper; + + @Inject + BPAMessageSource.DefaultMessageSource msg; + + public BPACredentialExchange issueLDCredential(UUID partnerId, UUID bpaSchemaId, JsonNode document) { + Partner partner = partnerRepo.findById(partnerId).orElseThrow(EntityNotFoundException::new); + BPASchema bpaSchema = schemaRepo.findById(bpaSchemaId).orElseThrow(EntityNotFoundException::new); + try { + V20CredExRecord exRecord = ac.issueCredentialV2Send(V2CredentialExchangeFree.builder() + .connectionId(UUID.fromString(Objects.requireNonNull(partner.getConnectionId()))) + .filter(vcHelper.buildVC(bpaSchema, document, Boolean.TRUE)) + .build()) + .orElseThrow(); + + BPACredentialExchange cex = BPACredentialExchange.builder() + .schema(BPASchema.builder().id(bpaSchema.getId()).build()) + .partner(partner) + .type(CredentialType.JSON_LD) + .role(CredentialExchangeRole.ISSUER) + .state(CredentialExchangeState.OFFER_SENT) + // same behaviour as indy, in this case proposal == offer == credential + .pushStateChange(CredentialExchangeState.OFFER_SENT, Instant.now()) + .ldCredential(BPACredentialExchange.ExchangePayload.jsonLD(exRecord.resolveLDCredOffer())) + .credentialExchangeId(exRecord.getCredentialExchangeId()) + .threadId(exRecord.getThreadId()) + .exchangeVersion(ExchangeVersion.V2) + .build(); + return credExRepo.save(cex); + } catch (IOException e) { + log.error("aca-py is offline"); + throw new NetworkException(msg.getMessage("acapy.unavailable"), e); + } + } + + public void reIssueLDCredential() { + throw new WrongApiUsageException(msg.getMessage("api.issuer.credential.send.not.supported")); + } + + public CredEx revokeLDCredential() { + throw new WrongApiUsageException(msg.getMessage("api.issuer.credential.send.not.supported")); + } + + public CredEx sendOffer(@NonNull BPACredentialExchange credEx, @NotNull Map attributes, + @NonNull IssuerManager.IdWrapper ids) throws IOException { + String schemaId = credEx.getSchema() != null ? credEx.getSchema().getSchemaId() : null; + if (StringUtils.isNotEmpty(schemaId) && !StringUtils.equals(schemaId, ids.schemaId())) { + BPASchema counterSchema = schemaRepo.findBySchemaId(ids.schemaId()).orElseThrow( + () -> new WrongApiUsageException(msg.getMessage("api.issuer.credential.send.offer.wrong.schema"))); + credEx.setSchema(counterSchema); + credExRepo.update(credEx); + } + V2CredentialExchangeFree.V20CredFilter v20CredFilter = vcHelper.buildVC(credEx.getSchema(), attributes, + Boolean.TRUE); + V20CredExRecord v20CredExRecord = ac.issueCredentialV2RecordsSendOffer(credEx.getCredentialExchangeId(), + V20CredBoundOfferRequest.builder() + .filter(v20CredFilter) + .counterPreview(V2CredentialExchangeFree.V2CredentialPreview.builder() + .attributes(CredentialAttributes.fromMap(attributes)) + .build()) + .build()) + .orElseThrow(); + credExRepo.updateCredential(credEx.getId(), + BPACredentialExchange.ExchangePayload.jsonLD(v20CredExRecord.resolveLDCredOffer())); + return CredEx.from(credEx); + } + + public void handleCredentialProposal(@NonNull String schemaId, + @NonNull BPACredentialExchange.BPACredentialExchangeBuilder b) { + schemaRepo.findBySchemaId(schemaId).ifPresentOrElse(s -> { + b.schema(s); + b.type(CredentialType.JSON_LD); + credExRepo.save(b.build()); + }, () -> { + b.errorMsg(msg.getMessage("api.holder.issuer.has.no.creddef", + Map.of("id", schemaId))); + credExRepo.save(b.build()); + }); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelper.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelper.java new file mode 100644 index 000000000..171625e40 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelper.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import com.fasterxml.jackson.databind.JsonNode; +import io.micronaut.core.annotation.Nullable; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.NonNull; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.aries.api.jsonld.VerifiableCredential; +import org.hyperledger.aries.config.GsonConfig; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.config.BPAMessageSource; +import org.hyperledger.bpa.impl.activity.DocumentValidator; +import org.hyperledger.bpa.impl.aries.wallet.Identity; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.impl.util.TimeUtil; +import org.hyperledger.bpa.persistence.model.BPARestrictions; +import org.hyperledger.bpa.persistence.model.BPASchema; +import org.hyperledger.bpa.persistence.repository.BPARestrictionsRepository; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Singleton +public class LDContextHelper { + + @Inject + Identity identity; + + @Inject + Converter conv; + + @Inject + DocumentValidator documentValidator; + + @Inject + BPARestrictionsRepository trustedIssuer; + + @Inject + BPAMessageSource.DefaultMessageSource ms; + + public static String findSchemaId(@Nullable V20CredExRecordByFormat.LdProof ldProof) { + if (ldProof == null) { + return null; + } + // TODO this does not consider all use cases like complex schemas + List context = ldProof.getCredential().getContext(); + List contextCopy = new ArrayList<>(context); + contextCopy.removeAll(CredentialType.JSON_LD.getContext()); + return (String) contextCopy.get(0); + } + + public V2CredentialExchangeFree.V20CredFilter buildVC( + @NonNull BPASchema bpaSchema, @NonNull JsonNode document, @NonNull Boolean issuer) { + return buildVC(bpaSchema, conv.toStringMap(document), issuer); + } + + public V2CredentialExchangeFree.V20CredFilter buildVC( + @NonNull BPASchema bpaSchema, @NonNull Map document, @NonNull Boolean issuer) { + documentValidator.validateAttributesAgainstLDSchema(bpaSchema, document); + if (!issuer) { + document.put("id", identity.getMyDid()); + } + return V2CredentialExchangeFree.V20CredFilter.builder() + .ldProof(V2CredentialExchangeFree.LDProofVCDetail.builder() + .credential(VerifiableCredential.builder() + .context(List.of(CredentialType.JSON_LD.getContext().get(0), bpaSchema.getSchemaId())) + .credentialSubject(GsonConfig.defaultConfig().toJsonTree(document).getAsJsonObject()) + .issuanceDate(TimeUtil.toISOInstantTruncated(Instant.now())) + .issuer(issuer ? identity.getMyDid() : findIssuerDidOrFallback(bpaSchema)) + .type(List.of(CredentialType.JSON_LD.getType().get(0), + Objects.requireNonNull(bpaSchema.getLdType()))) + .build()) + .options(V2CredentialExchangeFree.LDProofVCDetailOptions.builder() + // TODO expose key type to user + .proofType(V2CredentialExchangeFree.ProofType.Ed25519Signature2018) + .build()) + .build()) + .build(); + } + + /** + * Returns the first configured did:key from the trusted issuer list, or falls + * back to the holders local did:key which will then be replaced by the issuer + * with the issuers key. Note: This will work, but in this case the issuer is + * not verified. + * + * @param bpaSchema {@link BPASchema} + * @return did:key + */ + private String findIssuerDidOrFallback(@NonNull BPASchema bpaSchema) { + return trustedIssuer.findBySchema(bpaSchema) + .stream() + .map(BPARestrictions::getIssuerDid) + // TODO fallback to partner did, needs check if did is public as it could also + // be a peer did + .findFirst() + .orElse(identity.getMyDid()); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDEventHandler.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDEventHandler.java new file mode 100644 index 000000000..c5deab841 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/LDEventHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import jakarta.inject.Singleton; +import org.hyperledger.aries.api.issue_credential_v2.V2IssueLDCredentialEvent; +import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; + +@Singleton +public record LDEventHandler(IssuerCredExRepository issuerCredExRepo) { + public void handleIssueCredentialV2LD(V2IssueLDCredentialEvent credentialInfo) { + issuerCredExRepo.updateByCredentialExchangeId(credentialInfo.getCredExId(), credentialInfo.getCredIdStored()); + } +} diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/VPManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/VPManager.java index c9df4fb8e..d12ca9def 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/VPManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/jsonld/VPManager.java @@ -29,6 +29,7 @@ import lombok.Setter; import org.hyperledger.aries.api.credentials.Credential; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; +import org.hyperledger.aries.api.jsonld.VerifiableCredential; import org.hyperledger.aries.api.jsonld.VerifiableCredential.VerifiableIndyCredential; import org.hyperledger.aries.api.jsonld.VerifiableCredential.VerifiableIndyCredential.VerifiableIndyCredentialBuilder; import org.hyperledger.aries.api.jsonld.VerifiablePresentation; @@ -84,7 +85,10 @@ public void recreateVerifiablePresentation() { docRepo.findByIsPublicTrue().forEach(doc -> vcs.add(buildFromDocument(doc, myDid))); holderCredExRepo.findByRoleAndIsPublicTrue(CredentialExchangeRole.HOLDER) - .forEach(cred -> vcs.add(buildFromCredential(cred))); + .stream() + .filter(credEx -> credEx.stateIsCredentialAcked() || credEx.stateIsDone() + || credEx.stateIsCredentialReceived()) + .forEach(credEx -> vcs.add(buildFromCredential(credEx))); // only split up into own method, because of a weird issue that the second // thread does @@ -136,10 +140,17 @@ protected VerifiableIndyCredential buildFromDocument(@NonNull MyDocument doc, @N } protected VerifiableIndyCredential buildFromCredential(@NonNull BPACredentialExchange cred) { + if (cred.typeIsJsonLd()) { + return buildFromLDCredential(cred); + } + return buildFromIndyCredential(cred); + } + + private VerifiableIndyCredential buildFromIndyCredential(@NonNull BPACredentialExchange cred) { final ArrayList type = new ArrayList<>(cred.getType().getType()); type.add("IndyCredential"); - Credential ariesCred = cred.getCredential(); + Credential ariesCred = cred.getIndyCredential(); final ArrayList context = new ArrayList<>( resolveContext(cred.getType(), Objects.requireNonNull(ariesCred).getSchemaId())); context.add(ApiConstants.INDY_CREDENTIAL_SCHEMA); @@ -158,6 +169,32 @@ protected VerifiableIndyCredential buildFromCredential(@NonNull BPACredentialExc return builder.build(); } + private VerifiableIndyCredential buildFromLDCredential(@NonNull BPACredentialExchange cred) { + VerifiableCredential vc = Objects.requireNonNull(cred.getLdCredential()).getLdProof().getCredential(); + + List ctx = new ArrayList<>(vc.getContext()); + ctx.add(ApiConstants.LABELED_CREDENTIAL_SCHEMA); + + List type = new ArrayList<>(vc.getType()); + type.add(ApiConstants.LABELED_CREDENTIAL_NAME); + + @SuppressWarnings("rawtypes") + VerifiableIndyCredentialBuilder builder = VerifiableIndyCredential.builder() + .context(ctx) + .credentialSubject(vc.getCredentialSubject()) + .expirationDate(vc.getExpirationDate()) + .id(vc.getId()) + .issuanceDate(vc.getIssuanceDate()) + .issuer(vc.getIssuer()) + .proof(vc.getProof()) + .type(type) + .label(cred.getLabel()) + .schemaId(null) + .credDefId(null) + .indyIssuer(null); + return builder.build(); + } + public Optional> getVerifiablePresentation() { final Optional dbVP = didRepo.findDidDocSingle(); if (dbVP.isPresent() && dbVP.get().getProfileJson() != null) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/proof/ProofManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/proof/ProofManager.java index cde21c13a..5f741f456 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/proof/ProofManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/proof/ProofManager.java @@ -168,7 +168,7 @@ public void sendProofProposal(@NonNull UUID partnerId, @NonNull UUID myCredentia @Nullable ExchangeVersion version) { partnerRepo.findById(partnerId).ifPresent(p -> holderCredExRepo.findById(myCredentialId).ifPresent(c -> { ExchangeVersion v = version != null ? version : ExchangeVersion.V1; - Credential cred = Objects.requireNonNull(c.getCredential()); + Credential cred = Objects.requireNonNull(c.getIndyCredential()); try { if (v.isV1()) { ac.presentProofSendProposal(PresentProofProposalBuilder.fromCredential(p.getConnectionId(), cred)) diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/RestrictionsManager.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/RestrictionsManager.java index 32d3d9de5..a28ddf8ed 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/RestrictionsManager.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/RestrictionsManager.java @@ -91,23 +91,26 @@ Optional addRestriction( String issuerDid = c.get("issuerDid"); if (StringUtils.isNotEmpty(issuerDid)) { try { - // simple check to test if issuer exists on the ledger - ac.ledgerDidVerkey(issuerDid).ifPresent(verkey -> { - BPARestrictions def = BPARestrictions - .builder() - .issuerDid(prefixIssuerDid(issuerDid)) - .label(c.get("label")) - .schema(BPASchema.builder().id(schemaId).build()) - .build(); - BPARestrictions db = repo.save(def); - result.setConfig(TrustedIssuer - .builder() - .id(db.getId()) - .label(db.getLabel()) - .issuerDid(db.getIssuerDid()) - .build()); - - }); + if (!AriesStringUtil.isDidKey(issuerDid)) { + // simple check to test if issuer exists on the ledger + ac.ledgerDidVerkey(issuerDid).orElseThrow(() -> new AriesException(404, "")); + } else if (schemaRepo.findById(schemaId).orElseThrow().typeIsIndy()) { + throw new WrongApiUsageException( + msg.getMessage("api.schema.restriction.schema.wrong.type")); + } + BPARestrictions def = BPARestrictions + .builder() + .issuerDid(prefixIssuerDid(issuerDid)) + .label(c.get("label")) + .schema(BPASchema.builder().id(schemaId).build()) + .build(); + BPARestrictions db = repo.save(def); + result.setConfig(TrustedIssuer + .builder() + .id(db.getId()) + .label(db.getLabel()) + .issuerDid(db.getIssuerDid()) + .build()); } catch (IOException e) { log.error("aca-py not available", e); } catch (AriesException e) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/SchemaService.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/SchemaService.java index 88d4de1b8..fb420ea6f 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/SchemaService.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/aries/schema/SchemaService.java @@ -48,6 +48,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; @Slf4j @Singleton @@ -131,10 +133,12 @@ public SchemaAPI addIndySchema(@NonNull String schemaId, @Nullable String label, try { Optional ariesSchema = ac.schemasGetById(sId); if (ariesSchema.isPresent()) { + LinkedHashSet schemaAttributeNames = new LinkedHashSet<>(ariesSchema.get().getAttrNames()); + validateDefaultAttribute(defaultAttributeName, schemaAttributeNames); BPASchema dbS = BPASchema.builder() .label(label != null ? label : AriesStringUtil.schemaGetName(schemaId)) .schemaId(ariesSchema.get().getId()) - .schemaAttributeNames(new LinkedHashSet<>(ariesSchema.get().getAttrNames())) + .schemaAttributeNames(schemaAttributeNames) .defaultAttributeName(defaultAttributeName) .seqNo(ariesSchema.get().getSeqNo()) .type(CredentialType.INDY) @@ -155,7 +159,6 @@ public SchemaAPI addIndySchema(@NonNull String schemaId, @Nullable String label, return result; } - @Nullable public SchemaAPI addJsonLDSchema(@NonNull String schemaId, @Nullable String label, @Nullable String defaultAttributeName, @NonNull String ldType, @NonNull Set attributes) { @@ -164,6 +167,7 @@ public SchemaAPI addJsonLDSchema(@NonNull String schemaId, @Nullable String labe } catch (URISyntaxException e) { throw new WrongApiUsageException(ms.getMessage("api.schema.ld.id.parse.error")); } + validateDefaultAttribute(defaultAttributeName, attributes); BPASchema dbS = BPASchema.builder() .label(label) @@ -179,20 +183,28 @@ public SchemaAPI addJsonLDSchema(@NonNull String schemaId, @Nullable String labe public SchemaAPI updateSchema(@NonNull UUID id, @Nullable String defaultAttribute) { BPASchema schema = schemaRepo.findById(id).orElseThrow(EntityNotFoundException::new); + validateDefaultAttribute(defaultAttribute, schema.getSchemaAttributeNames()); schemaRepo.updateDefaultAttributeName(id, defaultAttribute); schema.setDefaultAttributeName(defaultAttribute); return SchemaAPI.from(schema); } public List listSchemas() { - List result = new ArrayList<>(); - schemaRepo.findAll().forEach(dbS -> result.add(SchemaAPI.from(dbS, id))); - return result; + return StreamSupport.stream(schemaRepo.findAll().spliterator(), false) + .map(dbS -> SchemaAPI.from(dbS, id)) + .collect(Collectors.toList()); + } + + public List listLdSchemas() { + return schemaRepo + .findByType(CredentialType.JSON_LD) + .stream() + .map(s -> SchemaAPI.from(s, false, false)) + .collect(Collectors.toList()); } public Optional getSchema(@NonNull UUID id) { - Optional schema = schemaRepo.findById(id); - return schema.map(SchemaAPI::from); + return schemaRepo.findById(id).map(SchemaAPI::from); } public void deleteSchema(@NonNull UUID id) { @@ -233,10 +245,7 @@ public Set getSchemaAttributeNames(@NonNull String schemaId) { String result = null; Optional schema = schemaRepo.findBySchemaId(schemaId); if (schema.isPresent()) { - result = schema.get().getLabel(); - if (StringUtils.isEmpty(result) && schema.get().typeIsJsonLd()) { - result = schema.get().getLdType(); - } + result = schema.get().resolveSchemaLabel(); } if (StringUtils.isEmpty(result) && AriesStringUtil.isIndySchemaId(schemaId)) { result = AriesStringUtil.schemaGetName(schemaId); @@ -262,4 +271,11 @@ public void resetWriteOnlySchemas() { }); } } + + void validateDefaultAttribute(@Nullable String defaultAttributeName, @NonNull Set attributes) { + if (StringUtils.isNotEmpty(defaultAttributeName) + && !StringUtils.containsAnyIgnoreCase(defaultAttributeName, attributes.toArray(String[]::new))) { + throw new WrongApiUsageException(ms.getMessage("api.schema.default.attribute.mismatch")); + } + } } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/AriesStringUtil.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/AriesStringUtil.java index 155184244..27ed643d9 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/AriesStringUtil.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/AriesStringUtil.java @@ -24,9 +24,29 @@ import org.apache.commons.lang3.StringUtils; import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class AriesStringUtil { + private static final Pattern DID_KEY_PATTERN = Pattern.compile("z[a-km-zA-HJ-NP-Z1-9]+"); + private static final String DID_KEY = "did:key:"; + + /** + * tests if the provided did is a did:key + * + * @param did + * @return true if did is a did:key, false otherwise + */ + public static boolean isDidKey(@Nullable String did) { + if (StringUtils.isNotEmpty(did) && did.startsWith(DID_KEY)) { + String toMatch = did.replace(DID_KEY, ""); + Matcher m = DID_KEY_PATTERN.matcher(toMatch); + return m.matches(); + } + return false; + } + /** * Gets the last segment of a did * @@ -45,14 +65,11 @@ public static String getLastSegment(@NonNull String did) { * @return the last part of the input when separated by : null otherwise */ public static String getLastSegmentOrNull(@Nullable String did) { - if (StringUtils.trimToNull(did) != null) { - return getLastSegment(did); - } - return null; + return StringUtils.trimToNull(did) != null ? getLastSegment(did) : null; } - public static String schemaGetName(@NonNull String schemaId) { - return splitSchemaId(schemaId)[2]; + public static String schemaGetName(@Nullable String schemaId) { + return StringUtils.trimToNull(schemaId) != null ? splitSchemaId(schemaId)[2] : null; } public static String schemaGetCreator(@NonNull String schemaId) { @@ -97,10 +114,7 @@ public static String credDefIdGetTag(@NonNull String credDefId) { } public static boolean isCredDef(@Nullable String expression) { - if (StringUtils.isBlank(expression)) { - return false; - } - return expression.split(":").length == 5; + return StringUtils.isNotBlank(expression) && expression.split(":").length == 5; } private static String[] credDefIdSplit(@NonNull String credDefId) { diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java index 110b12fe6..e6627ea64 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/impl/util/Converter.java @@ -44,6 +44,7 @@ import org.hyperledger.bpa.api.PartnerAPI; import org.hyperledger.bpa.api.PartnerAPI.PartnerCredential; import org.hyperledger.bpa.api.aries.AriesProofExchange; +import org.hyperledger.bpa.api.exception.EntityNotFoundException; import org.hyperledger.bpa.config.BPAMessageSource; import org.hyperledger.bpa.impl.aries.credential.CredentialInfoResolver; import org.hyperledger.bpa.impl.aries.prooftemplates.ProofTemplateConversion; @@ -183,7 +184,8 @@ public MyDocument toModelObject(@NonNull MyDocumentAPI document) { */ public MyDocument updateMyCredential(@NonNull MyDocumentAPI apiDoc, @NonNull MyDocument myDoc) { Map data = toMap(apiDoc.getDocumentData()); - schemaService.getSchemaFor(apiDoc.getSchemaId()).ifPresent(myDoc::setSchema); + schemaService.getSchemaFor(apiDoc.getSchemaId()).ifPresentOrElse(myDoc::setSchema, + EntityNotFoundException::new); myDoc .setDocument(data) .setIsPublic(apiDoc.getIsPublic()) @@ -203,7 +205,7 @@ public MyDocumentAPI toApiObject(@NonNull MyDocument myDoc) { .type(myDoc.getType()) .typeLabel(resolveTypeLabel(myDoc.getType(), myDoc.getSchemaId(), null)) .schemaId(myDoc.getSchemaId()) - .label(myDoc.getLabel()) + .label(myDoc.getLabel() != null ? myDoc.getLabel() : "") .build(); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/converter/ExchangePayloadConverter.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/converter/ExchangePayloadConverter.java new file mode 100644 index 000000000..c76d68be1 --- /dev/null +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/model/converter/ExchangePayloadConverter.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.model.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.data.model.runtime.convert.AttributeConverter; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; + +@Slf4j +@Singleton +@NoArgsConstructor +public class ExchangePayloadConverter implements AttributeConverter { + + @Inject + ObjectMapper mapper; + + @Override + public String convertToPersistedValue(BPACredentialExchange.ExchangePayload entityValue, + @NonNull ConversionContext context) { + if (entityValue == null) { + return null; + } + try { + if (entityValue.typeIsIndy()) { + return mapper.writeValueAsString(entityValue.getIndy()); + } else if (entityValue.typeIsJsonLd()) { + return mapper.writeValueAsString(entityValue.getLdProof()); + } + return mapper.writeValueAsString(entityValue); + } catch (JsonProcessingException e) { + throw new RuntimeException("Could not serialise credential exchange record"); + } + } + + @Override + public BPACredentialExchange.ExchangePayload convertToEntityValue(String persistedValue, + @NonNull ConversionContext context) { + BPACredentialExchange.ExchangePayload.ExchangePayloadBuilder b = BPACredentialExchange.ExchangePayload + .builder(); + if (persistedValue == null) { + return null; + } + try { + JsonNode node = mapper.readValue(persistedValue, JsonNode.class); + if (node.has("attributes")) { + V1CredentialExchange.CredentialProposalDict.CredentialProposal credentialProposal = mapper + .convertValue(node, V1CredentialExchange.CredentialProposalDict.CredentialProposal.class); + b.indy(credentialProposal); + b.type(CredentialType.INDY); + } else if (node.has("credential")) { + V20CredExRecordByFormat.LdProof ldProof = mapper.convertValue(node, + V20CredExRecordByFormat.LdProof.class); + b.ldProof(ldProof); + b.type(CredentialType.JSON_LD); + } + } catch (JsonProcessingException e) { + throw new RuntimeException("Could not deserialize credential exchange record"); + } + return b.build(); + } +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPACredentialExchange.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPACredentialExchange.java index 422aef533..b8679c15d 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPACredentialExchange.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPACredentialExchange.java @@ -19,10 +19,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.util.CollectionUtils; -import io.micronaut.data.annotation.AutoPopulated; -import io.micronaut.data.annotation.DateCreated; -import io.micronaut.data.annotation.DateUpdated; -import io.micronaut.data.annotation.TypeDef; +import io.micronaut.data.annotation.*; import io.micronaut.data.model.DataType; import lombok.*; import lombok.experimental.Accessors; @@ -33,9 +30,12 @@ import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.model.converter.ExchangePayloadConverter; import org.hyperledger.bpa.persistence.model.type.CredentialTypeTranslator; +import javax.persistence.Id; import javax.persistence.*; import java.time.Instant; import java.util.Map; @@ -112,16 +112,21 @@ public class BPACredentialExchange private StateToTimestamp stateToTimestamp; @Nullable - @TypeDef(type = DataType.JSON) - private V1CredentialExchange.CredentialProposalDict.CredentialProposal credentialProposal; + @TypeDef(type = DataType.JSON, converter = ExchangePayloadConverter.class) + private ExchangePayload credentialProposal; @Nullable - @TypeDef(type = DataType.JSON) - private V1CredentialExchange.CredentialProposalDict.CredentialProposal credentialOffer; + @TypeDef(type = DataType.JSON, converter = ExchangePayloadConverter.class) + private ExchangePayload credentialOffer; + + @Nullable + @TypeDef(type = DataType.JSON, converter = ExchangePayloadConverter.class) + private ExchangePayload ldCredential; @Nullable @TypeDef(type = DataType.JSON) - private Credential credential; + @MappedProperty("credential") + private Credential indyCredential; // TODO deprecation and use ldCredential? @Nullable private String errorMsg; @@ -142,10 +147,28 @@ public class BPACredentialExchange private Boolean isPublic; @Nullable private String issuer; - /** aca-py credential identifier */ + /** aca-py credential identifier, referent when indy, record_id when json-ld */ @Nullable private String referent; + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static final class ExchangePayload implements CredentialTypeTranslator { + private CredentialType type; + private V1CredentialExchange.CredentialProposalDict.CredentialProposal indy; + private V20CredExRecordByFormat.LdProof ldProof; + + public static ExchangePayload indy(V1CredentialExchange.CredentialProposalDict.CredentialProposal indy) { + return ExchangePayload.builder().indy(indy).type(CredentialType.INDY).build(); + } + + public static ExchangePayload jsonLD(V20CredExRecordByFormat.LdProof ldProof) { + return ExchangePayload.builder().ldProof(ldProof).type(CredentialType.JSON_LD).build(); + } + } + public boolean checkIfPublic() { return isPublic != null && isPublic; } @@ -163,14 +186,39 @@ public Instant calculateIssuedAt() { } public @io.micronaut.core.annotation.NonNull Map proposalAttributesToMap() { - return attributesToMap(credentialProposal); + if (typeIsJsonLd()) { + return ldAttributesToMap(credentialProposal != null ? credentialProposal.getLdProof() : null); + } + return indyAttributesToMap(credentialProposal != null ? credentialProposal.getIndy() : null); } public @io.micronaut.core.annotation.NonNull Map offerAttributesToMap() { - return attributesToMap(credentialOffer); + if (typeIsJsonLd()) { + return ldAttributesToMap(credentialOffer != null ? credentialOffer.ldProof : null); + } + return indyAttributesToMap(credentialOffer != null ? credentialOffer.getIndy() : null); } - private Map attributesToMap(V1CredentialExchange.CredentialProposalDict.CredentialProposal p) { + public @io.micronaut.core.annotation.NonNull Map credentialAttributesToMap() { + if (typeIsJsonLd()) { + return ldAttributesToMap(ldCredential != null ? ldCredential.ldProof : null); + } + // TODO fallback to credential + if (indyCredential == null || CollectionUtils.isEmpty(indyCredential.getAttrs())) { + return Map.of(); + } + return indyCredential.getAttrs(); + } + + private Map ldAttributesToMap(V20CredExRecordByFormat.LdProof ldProof) { + if (ldProof == null) { + return Map.of(); + } + return ldProof.getCredential().getCredentialSubject().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getAsString())); + } + + private Map indyAttributesToMap(V1CredentialExchange.CredentialProposalDict.CredentialProposal p) { if (p == null || CollectionUtils.isEmpty(p.getAttributes())) { return Map.of(); } @@ -179,11 +227,26 @@ private Map attributesToMap(V1CredentialExchange.CredentialPropo .collect(Collectors.toMap(CredentialAttributes::getName, CredentialAttributes::getValue)); } - public @io.micronaut.core.annotation.NonNull Map credentialAttributesToMap() { - if (credential == null || CollectionUtils.isEmpty(credential.getAttrs())) { - return Map.of(); + public Map attributesByState() { + if (stateIsProposalReceived()) { + return proposalAttributesToMap(); + } else if (stateIsOfferReceived()) { + return offerAttributesToMap(); + } else if (stateIsDone() || stateIsCredentialIssued()) { + return credentialAttributesToMap(); + } + return Map.of(); + } + + public ExchangePayload exchangePayloadByState() { + if (stateIsProposalReceived()) { + return credentialProposal; + } else if (stateIsOfferReceived()) { + return credentialOffer; + } else if (stateIsDone() || stateIsCredentialIssued()) { + return ldCredential; } - return credential.getAttrs(); + return null; } // extends lombok builder diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPASchema.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPASchema.java index ea18fa0dc..a1062949e 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPASchema.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/BPASchema.java @@ -23,7 +23,9 @@ import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.model.DataType; import lombok.*; +import org.apache.commons.lang3.StringUtils; import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.impl.util.AriesStringUtil; import org.hyperledger.bpa.persistence.model.type.CredentialTypeTranslator; import javax.persistence.*; @@ -80,4 +82,14 @@ public class BPASchema implements CredentialTypeTranslator { @OneToMany(fetch = FetchType.LAZY, mappedBy = "schema", cascade = { CascadeType.PERSIST, CascadeType.REFRESH }) private List credentialDefinitions; + public @Nullable String resolveSchemaLabel() { + String result = label; + if (StringUtils.isEmpty(result) && typeIsJsonLd()) { + result = ldType; + } else if (StringUtils.isEmpty(result) && typeIsIndy()) { + result = AriesStringUtil.schemaGetName(schemaId); + } + return result; + } + } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/MyDocument.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/MyDocument.java index af1028216..9fdcfa3af 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/MyDocument.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/MyDocument.java @@ -59,7 +59,11 @@ public class MyDocument implements CredentialTypeTranslator { @Enumerated(EnumType.STRING) private CredentialType type; - /** There for backwards compatibility, use reference to schema instead */ + /** + * @deprecated There for backwards compatibility, use reference to schema + * instead + */ + @Deprecated @Nullable private String schemaId; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/messaging/MessageTemplate.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/messaging/MessageTemplate.java index 686b02742..c3d9d7c40 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/messaging/MessageTemplate.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/model/messaging/MessageTemplate.java @@ -21,7 +21,10 @@ import io.micronaut.data.annotation.AutoPopulated; import io.micronaut.data.annotation.DateCreated; import io.micronaut.data.annotation.DateUpdated; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/BPASchemaRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/BPASchemaRepository.java index 67d7e62e0..d524708d5 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/BPASchemaRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/BPASchemaRepository.java @@ -23,8 +23,10 @@ import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.CrudRepository; +import org.hyperledger.bpa.api.CredentialType; import org.hyperledger.bpa.persistence.model.BPASchema; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -39,6 +41,8 @@ public interface BPASchemaRepository extends CrudRepository { @Join(value = "credentialDefinitions", type = Join.Type.LEFT_FETCH) Iterable findAll(); + List findByType(CredentialType type); + @Override @NonNull @Join(value = "restrictions", type = Join.Type.LEFT_FETCH) diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepository.java index 795990cf3..72bfe4cd4 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepository.java @@ -17,6 +17,7 @@ */ package org.hyperledger.bpa.persistence.repository; +import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.Join; @@ -26,7 +27,6 @@ import io.micronaut.data.repository.CrudRepository; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; -import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import org.hyperledger.bpa.persistence.model.StateChangeDecorator; @@ -40,6 +40,12 @@ public interface HolderCredExRepository extends CrudRepository findById(@NonNull UUID id); + + @Join(value = "schema", type = Join.Type.LEFT_FETCH) List findByRoleEqualsAndStateIn(CredentialExchangeRole role, List state); @@ -47,6 +53,7 @@ List findByRoleEqualsAndStateIn(CredentialExchangeRole ro List findByPartnerId(UUID partnerId); + @Join(value = "schema", type = Join.Type.LEFT_FETCH) @Join(value = "partner", type = Join.Type.LEFT_FETCH) Optional findByCredentialExchangeId(String credentialExchangeId); @@ -75,7 +82,7 @@ void updateStates(@Id UUID id, CredentialExchangeState state, void updateOnCredentialOfferEvent(@Id UUID id, CredentialExchangeState state, StateChangeDecorator.StateToTimestamp stateToTimestamp, - V1CredentialExchange.CredentialProposalDict.CredentialProposal credentialOffer); + BPACredentialExchange.ExchangePayload credentialOffer); void updateLabel(@Id UUID id, String label); diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepository.java index 4f8431566..63e334893 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepository.java @@ -60,7 +60,9 @@ public interface IssuerCredExRepository extends CrudRepository listOrderByUpdatedAtDesc(); - Number updateCredential(@Id UUID id, Credential credential); + Number updateCredential(@Id UUID id, Credential indyCredential); + + Number updateCredential(@Id UUID id, BPACredentialExchange.ExchangePayload ldCredential); Number updateAfterEventWithRevocationInfo(@Id UUID id, CredentialExchangeState state, @@ -77,4 +79,6 @@ Number updateAfterEventNoRevocationInfo(@Id UUID id, Number updateRevocationInfo(@Id UUID id, String revRegId, @Nullable String credRevId); Number updateReferent(@Id UUID id, String referent); + + Number updateByCredentialExchangeId(String credentialExchangeId, String referent); } diff --git a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/MyDocumentRepository.java b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/MyDocumentRepository.java index ca0f6fc8b..bbf9fd1d7 100644 --- a/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/MyDocumentRepository.java +++ b/backend/business-partner-agent/src/main/java/org/hyperledger/bpa/persistence/repository/MyDocumentRepository.java @@ -17,6 +17,7 @@ */ package org.hyperledger.bpa.persistence.repository; +import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.data.annotation.Id; import io.micronaut.data.annotation.Join; @@ -27,11 +28,17 @@ import org.hyperledger.bpa.persistence.model.MyDocument; import java.util.List; +import java.util.Optional; import java.util.UUID; @JdbcRepository(dialect = Dialect.POSTGRES) public interface MyDocumentRepository extends CrudRepository { + @Override + @NonNull + @Join(value = "schema", type = Join.Type.LEFT_FETCH) + Optional findById(@NonNull UUID id); + /** * Find all my public credentials * diff --git a/backend/business-partner-agent/src/main/resources/databasemigrations/V1.31__json-ld-credential.sql b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.31__json-ld-credential.sql new file mode 100644 index 000000000..7650365eb --- /dev/null +++ b/backend/business-partner-agent/src/main/resources/databasemigrations/V1.31__json-ld-credential.sql @@ -0,0 +1 @@ +ALTER TABLE bpa_credential_exchange ADD COLUMN ld_credential jsonb; \ No newline at end of file diff --git a/backend/business-partner-agent/src/main/resources/org/hyperledger/bpa/i18n/messages.properties b/backend/business-partner-agent/src/main/resources/org/hyperledger/bpa/i18n/messages.properties index 63d26e326..4e1d75320 100644 --- a/backend/business-partner-agent/src/main/resources/org/hyperledger/bpa/i18n/messages.properties +++ b/backend/business-partner-agent/src/main/resources/org/hyperledger/bpa/i18n/messages.properties @@ -33,6 +33,8 @@ api.issuer.credential.exchange.declined=Issuer declined credential proposal: no api.issuer.credential.missing.revocation.info=Credential can not be revoked (missing revocation flag) api.issuer.credential.send.offer.wrong.state=Wrong exchange state expected: proposal received but was: {state} api.issuer.credential.send.offer.wrong.creddef=Provided credential definition id is not configured +api.issuer.credential.send.offer.wrong.schema=Provided schema id is not configured +api.issuer.credential.send.not.supported=This operation is not supported for json-ld credentials api.schema.credential.document.conversion.failure=Only documents that are based on a schema can be converted into a credential api.issuer.creddef.already.exists=Credential definition for schema: {id} already exists with tag: {tag} api.issuer.creddef.ledger.failure=Credential Definition not created; could not complete request with ledger @@ -71,10 +73,12 @@ api.schema.ledger.error=Error sending schema to ledger. {message} api.schema.already.exists=Schema with id: {id} already exists api.schema.already.exists.ledger=Schema with id: {id} does not exist on the ledger. api.schema.constrain.violation=Schema can not be deleted, because it is still used by a trusted issuer or credential definition +api.schema.default.attribute.mismatch=The provided default attribute name does not match any attribute name api.schema.restriction.already.configured=Trusted issuer: {did} already configured for this schema api.schema.restriction.issuer.not.found=Did {did} does not exist on the ledger api.schema.restriction.schema.not.found=Schema with id: {id} is not configured api.schema.restriction.schema.not.found.on.ledger=No schema with id - {id} found on ledger +api.schema.restriction.schema.wrong.type=A did:key can not be added as a trusted issuer to an indy schema api.schema.ld.id.parse.error=Expecting a valid URI as schema id api.tag.already.exists=Tag with name: {name} already exists. diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/AdminControllerTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/AdminControllerTest.java index 92398f7e2..5cb36ee35 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/AdminControllerTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/controller/AdminControllerTest.java @@ -67,7 +67,7 @@ void testAddSchemaWithRestriction() throws Exception { mockGetSchemaAndVerkey(); // add schema - HttpResponse addedSchema = addSchemaWithRestriction(schemaId1); + HttpResponse addedSchema = addSchemaWithRestriction(schemaId1, "name"); Assertions.assertEquals(HttpStatus.OK, addedSchema.getStatus()); Assertions.assertTrue(addedSchema.getBody().isPresent()); @@ -135,7 +135,7 @@ void testAddSchemaWithRestriction() throws Exception { void testAddRestrictionTwice() throws Exception { mockGetSchemaAndVerkey(); - SchemaAPI schema1 = addSchemaWithRestriction(schemaId2).getBody().orElseThrow(); + SchemaAPI schema1 = addSchemaWithRestriction(schemaId2, "other1").getBody().orElseThrow(); SchemaAPI schema2 = addSchemaNoRestriction().getBody().orElseThrow(); URI uri1 = UriBuilder.of("/{id}/trustedIssuer").expand(Map.of("id", schema1.getId().toString())); @@ -176,10 +176,10 @@ void testAddLDSchema() { Assertions.assertEquals("Person", api.getLdType()); } - private HttpResponse addSchemaWithRestriction(String schemaId) { + private HttpResponse addSchemaWithRestriction(String schemaId, String defaultAttribute) { return post(AddSchemaRequest.AddIndySchema.builder() .schemaId(schemaId) - .defaultAttributeName("name") + .defaultAttributeName(defaultAttribute) .label("Demo Bank") .trustedIssuer(List.of(AddTrustedIssuerRequest .builder() @@ -193,7 +193,7 @@ private HttpResponse addSchemaWithRestriction(String schemaId) { private HttpResponse addSchemaNoRestriction() { return post(AddSchemaRequest.AddIndySchema.builder() .schemaId(schemaId3) - .defaultAttributeName("other") + .defaultAttributeName("other2") .label("Demo Corp") .build(), SchemaAPI.class); diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/DocumentValidatorTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/DocumentValidatorTest.java index 55b58d66a..b1701922e 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/DocumentValidatorTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/DocumentValidatorTest.java @@ -28,6 +28,7 @@ import org.hyperledger.bpa.persistence.model.BPASchema; import org.hyperledger.bpa.persistence.model.MyDocument; import org.hyperledger.bpa.persistence.repository.MyDocumentRepository; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,6 +37,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -100,6 +102,21 @@ void testIndyCredentialWithNoMatchingSchema() throws JsonProcessingException { assertThrows(WrongApiUsageException.class, () -> validator.validateNew(buildMyDocument(jsonNode))); } + @Test + void testSchemaValidationSuccess() { + validator.validateAttributesAgainstLDSchema(BPASchema.builder() + .schemaAttributeNames(Set.of("name", "some")) + .build(), Map.of("name", "me", "id", "did:sov:123")); + } + + @Test + void testSchemaValidationFailure() { + Assertions.assertThrows(WrongApiUsageException.class, + () -> validator.validateAttributesAgainstLDSchema(BPASchema.builder() + .schemaAttributeNames(Set.of("name", "some")) + .build(), Map.of("other", "123"))); + } + private Optional buildSchema(Set attr) { return Optional.of(BPASchema .builder() diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/LabelStrategyTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/LabelStrategyTest.java index 426702044..61d90c2df 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/LabelStrategyTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/activity/LabelStrategyTest.java @@ -19,10 +19,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonObject; import org.hyperledger.aries.api.credentials.Credential; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; +import org.hyperledger.aries.api.jsonld.VerifiableCredential; import org.hyperledger.bpa.api.MyDocumentAPI; import org.hyperledger.bpa.api.aries.AriesCredential; import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import org.hyperledger.bpa.persistence.model.BPASchema; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,6 +34,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -124,4 +129,28 @@ void testResetLabelOnCredentialUpdate() { String label = labelStrategy.apply("", credential); assertEquals("test123", label); } + + @Test + void testFindLabelForLDProof() { + String schemaId = "https://foo.com/person"; + when(schemaService.getSchemaFor(anyString())).thenReturn(Optional.of(BPASchema + .builder() + .defaultAttributeName("name") + .schemaId(schemaId) + .build())); + JsonObject jo = new JsonObject(); + jo.addProperty("name", "myTest"); + jo.addProperty("email", "test@bar.com"); + VerifiableCredential vc = VerifiableCredential + .builder() + .context(List.of(schemaId)) + .type(List.of("person")) + .credentialSubject(jo) + .build(); + String label = labelStrategy.apply(BPACredentialExchange.ExchangePayload + .jsonLD(V20CredExRecordByFormat.LdProof.builder() + .credential(vc) + .build())); + assertEquals("myTest", label); + } } diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerIntegrationTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerIntegrationTest.java index 47bdcd435..ed7fbf870 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerIntegrationTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerIntegrationTest.java @@ -61,7 +61,10 @@ public class CredentialManagerIntegrationTest extends RunWithAries { AriesEventHandler eventHandler; @Inject - HolderCredentialManager holderMgmt; + HolderManager holderMgmt; + + @Inject + HolderIndyManager indy; @Inject HolderCredExRepository holderCredExRepo; @@ -89,7 +92,7 @@ public class CredentialManagerIntegrationTest extends RunWithAries { @BeforeEach public void injectAries() { - holderMgmt.setSchemaService(schemaService); + indy.setSchemaService(schemaService); crypto.setAcaPy(ac); acaCache.setAc(ac); id.setAcaPy(ac); diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerTest.java index c44f7d5a4..d23df78f0 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/credential/CredentialManagerTest.java @@ -19,7 +19,6 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; -import org.hyperledger.aries.api.credentials.Credential; import org.hyperledger.aries.api.jsonld.VerifiableCredential.VerifiableIndyCredential; import org.hyperledger.aries.api.jsonld.VerifiablePresentation; import org.hyperledger.bpa.BaseTest; @@ -34,11 +33,10 @@ @MicronautTest class CredentialManagerTest extends BaseTest { - private static final String CRED_DEF_ID = "M6Mbe3qx7vB4wpZF4sBRjt:3:CL:571:ba"; private static final String DID = "did:sov:M6Mbe3qx7vB4wpZF4sBRjt"; @Inject - HolderCredentialManager mgmt; + HolderManager mgmt; @Inject PartnerRepository partnerRepo; @@ -48,53 +46,48 @@ class CredentialManagerTest extends BaseTest { @Test void testResolveIssuerDidOnly() { - Credential c = new Credential(); - c.setCredentialDefinitionId(CRED_DEF_ID); - String iss = mgmt.resolveIssuer(c); + Partner p = partnerRepo.save(Partner + .builder() + .did(DID) + .ariesSupport(Boolean.TRUE) + .build()); + + String iss = mgmt.resolveIssuer(p); assertEquals(DID, iss); } @Test void testResolveIssuerByAlias() { - Credential c = new Credential(); - c.setCredentialDefinitionId(CRED_DEF_ID); - - partnerRepo.save(Partner + Partner p = partnerRepo.save(Partner .builder() .alias("My Bank") .did(DID) .ariesSupport(Boolean.TRUE) .build()); - String iss = mgmt.resolveIssuer(c); + String iss = mgmt.resolveIssuer(p); assertEquals("My Bank", iss); } @Test void testResolveIssuerByVP() throws Exception { - Credential c = new Credential(); - c.setCredentialDefinitionId(CRED_DEF_ID); - final String json = loader.load("files/verifiablePresentation.json"); final VerifiablePresentation vp = mapper.readValue(json, Converter.VP_TYPEREF); - partnerRepo.save(Partner + Partner p = partnerRepo.save(Partner .builder() .did(DID) .ariesSupport(Boolean.TRUE) .verifiablePresentation(conv.toMap(vp)) .build()); - String iss = mgmt.resolveIssuer(c); + String iss = mgmt.resolveIssuer(p); assertEquals("Test Corp", iss); } @Test void testResolveIssuerByLabel() { - Credential c = new Credential(); - c.setCredentialDefinitionId(CRED_DEF_ID); - - partnerRepo.save(Partner + Partner p = partnerRepo.save(Partner .builder() .did(DID) .ariesSupport(Boolean.TRUE) @@ -102,7 +95,7 @@ void testResolveIssuerByLabel() { .label("Their Label") .build()); - String iss = mgmt.resolveIssuer(c); + String iss = mgmt.resolveIssuer(p); assertEquals("Their Label", iss); } diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDCredentialTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDCredentialTest.java new file mode 100644 index 000000000..e84a3cf99 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/HolderLDCredentialTest.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import lombok.NonNull; +import org.hyperledger.acy_py.generated.model.DID; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.aries.api.issue_credential_v2.V2IssueLDCredentialEvent; +import org.hyperledger.aries.webhook.EventParser; +import org.hyperledger.bpa.BaseTest; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.api.MyDocumentAPI; +import org.hyperledger.bpa.impl.MyDocumentManager; +import org.hyperledger.bpa.impl.aries.AriesEventHandler; +import org.hyperledger.bpa.impl.aries.credential.HolderManager; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.Partner; +import org.hyperledger.bpa.persistence.repository.HolderCredExRepository; +import org.hyperledger.bpa.persistence.repository.PartnerRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@MicronautTest +@ExtendWith(MockitoExtension.class) +public class HolderLDCredentialTest extends BaseTest { + + private final String schemaId = "https://w3id.org/citizenship/v1"; + + @Inject + PartnerRepository partnerRepo; + + @Inject + HolderCredExRepository credExRepo; + + @Inject + MyDocumentManager doc; + + @Inject + SchemaService schemaService; + + @Inject + HolderManager holder; + + @Inject + Converter conv; + + @Inject + AriesEventHandler aeh; + + @Inject + AriesClient ac; + + private final EventParser ep = new EventParser(); + + @Test + void testHolderReceivesCredentialFromIssuerAndAccepts() throws IOException { + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + + String offerReceived = loader.load("files/v2-ld-credex-holder/01-offer-received.json"); + String requestSent = loader.load("files/v2-ld-credex-holder/02-request-sent.json"); + String credentialReceived = loader.load("files/v2-ld-credex-holder/03-credential-received.json"); + String ldProofIds = loader.load("files/v2-ld-credex-holder/04-issue-credential-ld-proof.json"); + String credDone = loader.load("files/v2-ld-credex-holder/05-done.json"); + + V20CredExRecord offer = ep.parseValueSave(offerReceived, V20CredExRecord.class).orElseThrow(); + V20CredExRecord request = ep.parseValueSave(requestSent, V20CredExRecord.class).orElseThrow(); + V20CredExRecord received = ep.parseValueSave(credentialReceived, V20CredExRecord.class).orElseThrow(); + V2IssueLDCredentialEvent ldIds = ep.parseValueSave(ldProofIds, V2IssueLDCredentialEvent.class).orElseThrow(); + V20CredExRecord done = ep.parseValueSave(credDone, V20CredExRecord.class).orElseThrow(); + + String id = offer.getCredentialExchangeId(); + + createDefaultPartner(offer.getConnectionId()); + + aeh.handleCredentialV2(offer); + BPACredentialExchange ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsOfferReceived()); + Assertions.assertTrue(ex.typeIsJsonLd()); + Assertions.assertEquals(ExchangeVersion.V2, ex.getExchangeVersion()); + Assertions.assertEquals(2, ex.offerAttributesToMap().size()); + Assertions.assertEquals("karl", ex.offerAttributesToMap().get("name")); + + holder.sendCredentialRequest(ex.getId()); + + aeh.handleCredentialV2(request); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsRequestSent()); + + aeh.handleCredentialV2(received); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsCredentialReceived()); + + aeh.handleIssueCredentialV2LD(ldIds); + ex = loadCredEx(id); + Assertions.assertEquals("2d9afcfd4a2145bcb5253da9890200e0", ex.getReferent()); + + aeh.handleCredentialV2(done); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsDone()); + Assertions.assertEquals(2, ex.credentialAttributesToMap().size()); + Assertions.assertEquals("karl", ex.credentialAttributesToMap().get("name")); + } + + @Test + void testHolderReceivesCredentialFromIssuerAndDeclines() { + String offerReceived = loader.load("files/v2-ld-credex-holder/01-offer-received.json"); + + V20CredExRecord offer = ep.parseValueSave(offerReceived, V20CredExRecord.class).orElseThrow(); + + String id = offer.getCredentialExchangeId(); + + createDefaultPartner(offer.getConnectionId()); + + aeh.handleCredentialV2(offer); + BPACredentialExchange ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsOfferReceived()); + + holder.declineCredentialOffer(ex.getId(), "my reason"); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsDeclined()); + Assertions.assertEquals("my reason", ex.getErrorMsg()); + } + + @Test + void testHolderRequestsCredentialFromIssuerAndIssuerAccepts() throws IOException { + String proposalSent = loader.load("files/v2-ld-credex-holder-proposal/01-proposal-sent.json"); + String offerReceived = loader.load("files/v2-ld-credex-holder-proposal/02-offer-received.json"); + + V20CredExRecord proposal = ep.parseValueSave(proposalSent, V20CredExRecord.class).orElseThrow(); + V20CredExRecord offer = ep.parseValueSave(offerReceived, V20CredExRecord.class).orElseThrow(); + + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + Mockito.when( + ac.issueCredentialV2SendProposal(Mockito.any(V2CredentialExchangeFree.class))) + .thenReturn(Optional.of(proposal)); + + String id = offer.getCredentialExchangeId(); + + Partner p = createDefaultPartner(offer.getConnectionId()); + createDefaultSchema(); + + MyDocumentAPI document = doc.saveNewDocument(MyDocumentAPI.builder() + .schemaId(schemaId) + .type(CredentialType.JSON_LD) + .isPublic(Boolean.FALSE) + .documentData(conv.mapToNode(Map.of("name", "My Name", "identifier", "something"))) + .build()); + + holder.sendCredentialProposal(p.getId(), document.getId(), null); + + aeh.handleCredentialV2(proposal); + BPACredentialExchange ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsProposalSent()); + Assertions.assertTrue(ex.typeIsJsonLd()); + Assertions.assertEquals(ExchangeVersion.V2, ex.getExchangeVersion()); + Assertions.assertEquals(2, ex.proposalAttributesToMap().size()); + Assertions.assertEquals("My Name", ex.proposalAttributesToMap().get("name")); + + aeh.handleCredentialV2(offer); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsOfferReceived()); + + // from here on same as testHolderReceivesCredentialFromIssuerAndAccepts() + } + + @Test + void testHolderRequestsCredentialFromIssuerAndIssuerDeclines() throws IOException { + String proposalSent = loader.load("files/v2-ld-credex-holder-proposal/01-proposal-sent.json"); + String abandonedEvent = loader.load("files/v2-ld-credex-holder-proposal/03-abandoned.json"); + + V20CredExRecord proposal = ep.parseValueSave(proposalSent, V20CredExRecord.class).orElseThrow(); + V20CredExRecord abandoned = ep.parseValueSave(abandonedEvent, V20CredExRecord.class).orElseThrow(); + + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + Mockito.when( + ac.issueCredentialV2SendProposal(Mockito.any(V2CredentialExchangeFree.class))) + .thenReturn(Optional.of(proposal)); + + String id = proposal.getCredentialExchangeId(); + + Partner p = createDefaultPartner(proposal.getConnectionId()); + createDefaultSchema(); + + MyDocumentAPI document = doc.saveNewDocument(MyDocumentAPI.builder() + .schemaId(schemaId) + .type(CredentialType.JSON_LD) + .isPublic(Boolean.FALSE) + .documentData(conv.mapToNode(Map.of("name", "My Name", "identifier", "something"))) + .build()); + + holder.sendCredentialProposal(p.getId(), document.getId(), null); + + aeh.handleCredentialV2(proposal); + BPACredentialExchange ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsProposalSent()); + + aeh.handleCredentialV2(abandoned); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsProblem()); + Assertions.assertEquals("issuance-abandoned: my reason", ex.getErrorMsg()); + } + + private BPACredentialExchange loadCredEx(String id) { + return credExRepo.findByCredentialExchangeId(id) + .orElseThrow(); + } + + private Partner createDefaultPartner(@NonNull String connectionId) { + Partner p = Partner.builder() + .connectionId(connectionId) + .did("did:sov:dummy") + .ariesSupport(Boolean.TRUE) + .build(); + return partnerRepo.save(p); + } + + private void createDefaultSchema() { + schemaService.addJsonLDSchema(schemaId, "Citizen", + null, "PermanentResident", Set.of("name", "identifier")); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDCredentialTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDCredentialTest.java new file mode 100644 index 000000000..fc7289667 --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/IssuerLDCredentialTest.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import lombok.NonNull; +import org.hyperledger.acy_py.generated.model.DID; +import org.hyperledger.aries.AriesClient; +import org.hyperledger.aries.api.ExchangeVersion; +import org.hyperledger.aries.api.issue_credential_v2.V20CredBoundOfferRequest; +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord; +import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree; +import org.hyperledger.aries.webhook.EventParser; +import org.hyperledger.bpa.BaseTest; +import org.hyperledger.bpa.api.CredentialType; +import org.hyperledger.bpa.api.aries.SchemaAPI; +import org.hyperledger.bpa.controller.api.issuer.CredentialOfferRequest; +import org.hyperledger.bpa.controller.api.issuer.IssueCredentialRequest; +import org.hyperledger.bpa.impl.aries.AriesEventHandler; +import org.hyperledger.bpa.impl.aries.credential.IssuerManager; +import org.hyperledger.bpa.impl.aries.schema.SchemaService; +import org.hyperledger.bpa.impl.util.Converter; +import org.hyperledger.bpa.persistence.model.BPACredentialExchange; +import org.hyperledger.bpa.persistence.model.Partner; +import org.hyperledger.bpa.persistence.repository.IssuerCredExRepository; +import org.hyperledger.bpa.persistence.repository.PartnerRepository; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@MicronautTest +@ExtendWith(MockitoExtension.class) +public class IssuerLDCredentialTest extends BaseTest { + + private final String schemaId = "https://w3id.org/citizenship/v1"; + + @Inject + IssuerManager issuer; + + @Inject + SchemaService schemaService; + + @Inject + PartnerRepository partnerRepo; + + @Inject + IssuerCredExRepository credExRepo; + + @Inject + AriesEventHandler aeh; + + @Inject + Converter conv; + + @Inject + AriesClient ac; + + private final EventParser ep = new EventParser(); + + @Test + void testHandleIssuerSendCredential() throws IOException { + String offerSent = loader.load("files/v2-ld-credex-issuer/01-offer-sent.json"); + String reqReceived = loader.load("files/v2-ld-credex-issuer/02-request-received.json"); + String credIssued = loader.load("files/v2-ld-credex-issuer/03-credential-issued.json"); + String exDone = loader.load("files/v2-ld-credex-issuer/04-done.json"); + + V20CredExRecord offer = ep.parseValueSave(offerSent, V20CredExRecord.class).orElseThrow(); + V20CredExRecord received = ep.parseValueSave(reqReceived, V20CredExRecord.class).orElseThrow(); + V20CredExRecord issued = ep.parseValueSave(credIssued, V20CredExRecord.class).orElseThrow(); + V20CredExRecord done = ep.parseValueSave(exDone, V20CredExRecord.class).orElseThrow(); + + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + Mockito.when(ac.issueCredentialV2Send(Mockito.any(V2CredentialExchangeFree.class))) + .thenReturn(Optional.of(offer)); + + SchemaAPI schemaAPI = createDefaultSchema(); + Partner p = createDefaultPartner(offer.getConnectionId()); + + issuer.issueCredential(IssueCredentialRequest.builder() + .schemaId(schemaAPI.getId()) + .partnerId(p.getId()) + .document(conv.mapToNode(Map.of("name", "555", "identifier", "1234"))) + .type(CredentialType.JSON_LD) + .build()); + + aeh.handleCredentialV2(offer); + BPACredentialExchange ex = loadCredEx(offer.getCredentialExchangeId()); + Assertions.assertTrue(ex.stateIsOfferSent()); + Assertions.assertTrue(ex.typeIsJsonLd()); + Assertions.assertEquals(ExchangeVersion.V2, ex.getExchangeVersion()); + Assertions.assertEquals(2, ex.credentialAttributesToMap().size()); + Assertions.assertEquals("555", ex.credentialAttributesToMap().get("name")); + + aeh.handleCredentialV2(received); + ex = credExRepo.findById(ex.getId()).orElseThrow(); + Assertions.assertTrue(ex.stateIsRequestReceived()); + + aeh.handleCredentialV2(issued); + ex = credExRepo.findById(ex.getId()).orElseThrow(); + Assertions.assertTrue(ex.stateIsCredentialIssued()); + + aeh.handleCredentialV2(done); + ex = credExRepo.findById(ex.getId()).orElseThrow(); + Assertions.assertTrue(ex.stateIsDone()); + } + + @Test + void testHandleIssuerSendCredentialHolderDeclines() throws IOException { + String offerSent = loader.load("files/v2-ld-credex-issuer/01-offer-sent.json"); + String abandoned = loader.load("files/v2-ld-credex-issuer/05-abandoned.json"); + + V20CredExRecord offer = ep.parseValueSave(offerSent, V20CredExRecord.class).orElseThrow(); + V20CredExRecord problem = ep.parseValueSave(abandoned, V20CredExRecord.class).orElseThrow(); + + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + Mockito.when(ac.issueCredentialV2Send(Mockito.any(V2CredentialExchangeFree.class))) + .thenReturn(Optional.of(offer)); + + SchemaAPI schemaAPI = createDefaultSchema(); + Partner p = createDefaultPartner(offer.getConnectionId()); + + issuer.issueCredential(IssueCredentialRequest.builder() + .schemaId(schemaAPI.getId()) + .partnerId(p.getId()) + .document(conv.mapToNode(Map.of("name", "555", "identifier", "1234"))) + .type(CredentialType.JSON_LD) + .build()); + + aeh.handleCredentialV2(offer); + BPACredentialExchange ex = loadCredEx(offer.getCredentialExchangeId()); + Assertions.assertTrue(ex.stateIsOfferSent()); + + aeh.handleCredentialV2(problem); + ex = loadCredEx(offer.getCredentialExchangeId()); + Assertions.assertTrue(ex.stateIsProblem()); + Assertions.assertEquals("issuance-abandoned: my reason2", ex.getErrorMsg()); + } + + @Test + void testHandleIssuerProposalReceived() throws IOException { + String proposalReceived = loader.load("files/v2-ld-credex-issuer-proposal/01-proposal-received.json"); + String counterOffer = loader.load("files/v2-ld-credex-issuer-proposal/02-counter-offer-sent.json"); + String requestReceived = loader.load("files/v2-ld-credex-issuer-proposal/03-request-received.json"); + String credentialIssued = loader.load("files/v2-ld-credex-issuer-proposal/04-credential-issued.json"); + + V20CredExRecord proposal = ep.parseValueSave(proposalReceived, V20CredExRecord.class).orElseThrow(); + V20CredExRecord offer = ep.parseValueSave(counterOffer, V20CredExRecord.class).orElseThrow(); + V20CredExRecord request = ep.parseValueSave(requestReceived, V20CredExRecord.class).orElseThrow(); + V20CredExRecord issued = ep.parseValueSave(credentialIssued, V20CredExRecord.class).orElseThrow(); + + Mockito.when(ac.walletDidPublic()).thenReturn(Optional.of(DID.builder() + .did("did:indy:1234").method(DID.MethodEnum.SOV).build())); + Mockito.when( + ac.issueCredentialV2RecordsSendOffer(Mockito.anyString(), Mockito.any(V20CredBoundOfferRequest.class))) + .thenReturn(Optional.of(offer)); + String id = proposal.getCredentialExchangeId(); + + createDefaultPartner(proposal.getConnectionId()); + createDefaultSchema(); + + aeh.handleCredentialV2(proposal); + BPACredentialExchange ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsProposalReceived()); + Assertions.assertTrue(ex.typeIsJsonLd()); + Assertions.assertEquals(ExchangeVersion.V2, ex.getExchangeVersion()); + Assertions.assertEquals(2, ex.proposalAttributesToMap().size()); + Assertions.assertEquals("Name", ex.proposalAttributesToMap().get("name")); + Assertions.assertEquals(0, ex.offerAttributesToMap().size()); + + CredentialOfferRequest req = new CredentialOfferRequest(); + req.setAcceptProposal(Boolean.TRUE); + req.setSchemaId(schemaId); + issuer.sendCredentialOffer(ex.getId(), req); + + aeh.handleCredentialV2(offer); + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsOfferSent()); + + aeh.handleCredentialV2(request); + aeh.handleCredentialV2(issued); + + ex = loadCredEx(id); + Assertions.assertTrue(ex.stateIsCredentialIssued()); + Assertions.assertEquals(2, ex.credentialAttributesToMap().size()); + Assertions.assertEquals("Other Name", ex.credentialAttributesToMap().get("name")); + } + + @Test + void testHandleIssuerProposalReceivedIssuerDeclines() { + String proposalReceived = loader.load("files/v2-ld-credex-issuer-proposal/01-proposal-received.json"); + V20CredExRecord proposal = ep.parseValueSave(proposalReceived, V20CredExRecord.class).orElseThrow(); + + createDefaultPartner(proposal.getConnectionId()); + createDefaultSchema(); + + aeh.handleCredentialV2(proposal); + + BPACredentialExchange ex = loadCredEx(proposal.getCredentialExchangeId()); + issuer.declineCredentialProposal(ex.getId(), "my reason"); + + ex = loadCredEx(proposal.getCredentialExchangeId()); + Assertions.assertTrue(ex.stateIsDeclined()); + Assertions.assertEquals("my reason", ex.getErrorMsg()); + } + + private BPACredentialExchange loadCredEx(String id) { + return credExRepo.findByCredentialExchangeId(id) + .orElseThrow(); + } + + private SchemaAPI createDefaultSchema() { + return schemaService.addJsonLDSchema(schemaId, "Citizen", + null, "PermanentResident", Set.of("name", "identifier")); + } + + private Partner createDefaultPartner(@NonNull String connectionId) { + Partner p = Partner.builder() + .connectionId(connectionId) + .did("did:sov:dummy") + .ariesSupport(Boolean.TRUE) + .build(); + return partnerRepo.save(p); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelperTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelperTest.java new file mode 100644 index 000000000..81959ad5c --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/LDContextHelperTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.jsonld; + +import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecordByFormat; +import org.hyperledger.aries.api.jsonld.VerifiableCredential; +import org.hyperledger.bpa.api.CredentialType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.List; + +@ExtendWith(MockitoExtension.class) +public class LDContextHelperTest { + + @Test + void testFindLDSchemaId() { + String sId = "https://w3id.org/citizenship/v1"; + List ctx = new ArrayList<>(CredentialType.JSON_LD.getContext()); + ctx.add(sId); + String resolvedId = LDContextHelper.findSchemaId(V20CredExRecordByFormat.LdProof.builder() + .credential(VerifiableCredential.builder() + .context(ctx) + .build()) + .build()); + Assertions.assertEquals(sId, resolvedId); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/VPManagerTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/VPManagerTest.java index 51c130d78..5d582e122 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/VPManagerTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/jsonld/VPManagerTest.java @@ -150,7 +150,7 @@ void buildFromCredentialCommReg() { BPACredentialExchange myCredential = BPACredentialExchange .builder() .id(UUID.randomUUID()) - .credential(credential) + .indyCredential(credential) .type(CredentialType.INDY) .build(); VerifiableCredential.VerifiableIndyCredential indyCred = vpm.buildFromCredential(myCredential); diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/schema/SchemaServiceTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/schema/SchemaServiceTest.java new file mode 100644 index 000000000..0e4fa69ee --- /dev/null +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/aries/schema/SchemaServiceTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020-2022 - for information on the respective copyright owner + * see the NOTICE file and/or the repository at + * https://github.com/hyperledger-labs/business-partner-agent + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.hyperledger.bpa.impl.aries.schema; + +import org.hyperledger.bpa.api.exception.WrongApiUsageException; +import org.hyperledger.bpa.config.BPAMessageSource; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Set; + +@ExtendWith(MockitoExtension.class) +public class SchemaServiceTest { + + @Mock + private BPAMessageSource.DefaultMessageSource ms; + + @InjectMocks + private SchemaService schemaService; + + @Test + void testValidateDefaultAttribute() { + schemaService.validateDefaultAttribute(null, Set.of("test")); + schemaService.validateDefaultAttribute("Test", Set.of("test", "other")); + Assertions.assertThrows(WrongApiUsageException.class, + () -> schemaService.validateDefaultAttribute("something", Set.of("test"))); + } +} diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/util/AriesStringUtilTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/util/AriesStringUtilTest.java index 81ed46a75..32bb98372 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/util/AriesStringUtilTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/impl/util/AriesStringUtilTest.java @@ -121,4 +121,14 @@ void testParseRevocationNotification() { () -> AriesStringUtil.revocationEventToRevocationInfo("12:foo:1::12")); } + @Test + void testIsDidKey() { + assertFalse(AriesStringUtil.isDidKey("did:key:äää")); + assertFalse(AriesStringUtil.isDidKey("zUC7FswgVt61TDM5y88uM")); + assertFalse(AriesStringUtil.isDidKey("")); + assertFalse(AriesStringUtil.isDidKey(null)); + assertTrue(AriesStringUtil.isDidKey( + "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap")); + } + } diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepositoryTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepositoryTest.java index 01a0ee2aa..290afc3a4 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepositoryTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/HolderCredExRepositoryTest.java @@ -20,6 +20,7 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.hyperledger.aries.api.connection.ConnectionState; +import org.hyperledger.aries.api.credentials.CredentialAttributes; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeRole; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; @@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @@ -60,7 +62,7 @@ void testSaveCredential() { .partner(createRandomPartner()) .state(CredentialExchangeState.CREDENTIAL_ACKED) .threadId("1") - .credential(ex.getCredential()) + .indyCredential(ex.getCredential()) .role(CredentialExchangeRole.HOLDER) .credentialExchangeId(UUID.randomUUID().toString()) .build(); @@ -143,6 +145,22 @@ void testFindByTypeAndState() { List.of(CredentialExchangeState.CREDENTIAL_ISSUED)).size()); } + @Test + void testUpdateCredentialOffer() { + Partner p = createRandomPartner(); + BPACredentialExchange saved = holderCredExRepo.save(createDummyCredEx(p)); + saved.pushStates(CredentialExchangeState.OFFER_RECEIVED); + holderCredExRepo.updateOnCredentialOfferEvent(saved.getId(), saved.getState(), saved.getStateToTimestamp(), + BPACredentialExchange.ExchangePayload + .indy(V1CredentialExchange.CredentialProposalDict.CredentialProposal.builder() + .attributes(CredentialAttributes.from(Map.of("attr1", "value1"))) + .build())); + BPACredentialExchange exchange = holderCredExRepo.findById(saved.getId()).orElseThrow(); + assertNotNull(exchange.getCredentialOffer()); + assertTrue(exchange.getCredentialOffer().typeIsIndy()); + assertEquals("value1", exchange.getCredentialOffer().getIndy().getAttributes().get(0).getValue()); + } + private static BPACredentialExchange createDummyCredEx(Partner partner) { return BPACredentialExchange .builder() diff --git a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepositoryTest.java b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepositoryTest.java index 1dfbe88ad..ed52d7bab 100644 --- a/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepositoryTest.java +++ b/backend/business-partner-agent/src/test/java/org/hyperledger/bpa/persistence/repository/IssuerCredExRepositoryTest.java @@ -20,7 +20,9 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.hyperledger.aries.api.credentials.Credential; +import org.hyperledger.aries.api.credentials.CredentialAttributes; import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState; +import org.hyperledger.aries.api.issue_credential_v1.V1CredentialExchange; import org.hyperledger.bpa.persistence.model.BPACredentialExchange; import org.hyperledger.bpa.persistence.model.Partner; import org.junit.jupiter.api.Assertions; @@ -59,7 +61,32 @@ void testUpdateCredential() { .build()); exchange = issuerCredExRepo.findById(exchange.getId()).orElseThrow(); - Assertions.assertNotNull(exchange.getCredential()); - Assertions.assertEquals("val1", exchange.getCredential().getAttrs().get("attr1")); + Assertions.assertNotNull(exchange.getIndyCredential()); + Assertions.assertEquals("val1", exchange.getIndyCredential().getAttrs().get("attr1")); + } + + @Test + void testSaveWithCredentialProposal() { + Partner p = partnerRepo.save(Partner.builder() + .did("did-1") + .ariesSupport(Boolean.TRUE) + .build()); + + BPACredentialExchange exchange = issuerCredExRepo.save(BPACredentialExchange + .builder() + .threadId(UUID.randomUUID().toString()) + .credentialExchangeId(UUID.randomUUID().toString()) + .credentialProposal(BPACredentialExchange.ExchangePayload + .indy(V1CredentialExchange.CredentialProposalDict.CredentialProposal.builder() + .attributes(CredentialAttributes.from(Map.of("attr1", "value1"))) + .build())) + .state(CredentialExchangeState.PROPOSAL_SENT) + .partner(p) + .build()); + + BPACredentialExchange saved = issuerCredExRepo.save(exchange); + saved = issuerCredExRepo.findById(saved.getId()).orElseThrow(); + Assertions.assertNotNull(saved.getCredentialProposal()); + Assertions.assertEquals("value1", saved.getCredentialProposal().getIndy().getAttributes().get(0).getValue()); } } diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/01-proposal-sent.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/01-proposal-sent.json new file mode 100644 index 000000000..238d07d68 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/01-proposal-sent.json @@ -0,0 +1,58 @@ +{ + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "4cbf3e01-4a7c-42b1-80c2-0a6986a7ad9a", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNzoxODo1OFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAidGVzdCB1c2VyIiwgImlkZW50aWZpZXIiOiAid2FsdGVyIn19LCAib3B0aW9ucyI6IHsicHJvb2ZUeXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAifX0=" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "role": "holder", + "by_format": { + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-16T17:18:58Z", + "credentialSubject": { + "name": "My Name", + "identifier": "something" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "proposal-sent", + "cred_ex_id": "e64a02ac-fd5c-4adc-b5a2-a8186820690b", + "auto_remove": true, + "created_at": "2022-02-16T17:18:58.197536Z", + "initiator": "self", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "updated_at": "2022-02-16T17:18:58.197536Z", + "auto_issue": false, + "thread_id": "4cbf3e01-4a7c-42b1-80c2-0a6986a7ad9a" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/02-offer-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/02-offer-received.json new file mode 100644 index 000000000..1c4f61c25 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/02-offer-received.json @@ -0,0 +1,117 @@ +{ + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "4cbf3e01-4a7c-42b1-80c2-0a6986a7ad9a", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNzoxODo1OFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAidGVzdCB1c2VyIiwgImlkZW50aWZpZXIiOiAid2FsdGVyIn19LCAib3B0aW9ucyI6IHsicHJvb2ZUeXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAifX0=" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "role": "holder", + "by_format": { + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-16T17:18:58Z", + "credentialSubject": { + "name": "test user", + "identifier": "walter" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T17:19:39Z", + "credentialSubject": { + "identifier": "walter", + "name": "other user" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "offer-received", + "cred_ex_id": "e64a02ac-fd5c-4adc-b5a2-a8186820690b", + "auto_remove": true, + "created_at": "2022-02-16T17:18:58.197536Z", + "initiator": "self", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "updated_at": "2022-02-16T17:19:41.495254Z", + "auto_issue": false, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "69710c8f-d7c4-4d56-b0cc-7bdbdc2d77eb", + "~thread": { + "thid": "4cbf3e01-4a7c-42b1-80c2-0a6986a7ad9a" + }, + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "12345" + }, + { + "name": "name", + "value": "other user" + } + ] + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNzoxOTozOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FsdGVyIiwgIm5hbWUiOiAib3RoZXIgdXNlciJ9fSwgIm9wdGlvbnMiOiB7InByb29mVHlwZSI6ICJCYnNCbHNTaWduYXR1cmUyMDIwIn19" + } + } + ] + }, + "thread_id": "4cbf3e01-4a7c-42b1-80c2-0a6986a7ad9a" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/03-abandoned.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/03-abandoned.json new file mode 100644 index 000000000..9114faa95 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder-proposal/03-abandoned.json @@ -0,0 +1,59 @@ +{ + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "66884145-7500-4067-9778-b83d48681997", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNzozOTozM1oiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAidGVzdCB1c2VyIiwgImlkZW50aWZpZXIiOiAid2FsdGVyIn19LCAib3B0aW9ucyI6IHsicHJvb2ZUeXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAifX0=" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "role": "holder", + "by_format": { + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-16T17:39:33Z", + "credentialSubject": { + "name": "test user", + "identifier": "walter" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "abandoned", + "cred_ex_id": "e64a02ac-fd5c-4adc-b5a2-a8186820690b", + "auto_remove": true, + "created_at": "2022-02-16T17:39:33.792791Z", + "initiator": "self", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "updated_at": "2022-02-16T17:39:51.396007Z", + "auto_issue": false, + "error_msg": "issuance-abandoned: my reason", + "thread_id": "66884145-7500-4067-9778-b83d48681997" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/01-offer-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/01-offer-received.json new file mode 100644 index 000000000..dcd9c42fa --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/01-offer-received.json @@ -0,0 +1,61 @@ +{ + "role": "holder", + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "offer-received", + "trace": false, + "cred_ex_id": "9a2b489a-5c47-49ce-9b87-093fc79817c7", + "auto_remove": true, + "created_at": "2022-02-16T16:53:21.634264Z", + "initiator": "external", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "updated_at": "2022-02-16T16:53:21.634264Z", + "auto_issue": false, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "b8942466-637f-4db3-a139-126be08ba306", + "~thread": {}, + "comment": "create automated v2.0 credential exchange record", + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "thread_id": "b8942466-637f-4db3-a139-126be08ba306" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/02-request-sent.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/02-request-sent.json new file mode 100644 index 000000000..43116c453 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/02-request-sent.json @@ -0,0 +1,108 @@ +{ + "role": "holder", + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234", + "id": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "request-sent", + "trace": false, + "cred_ex_id": "9a2b489a-5c47-49ce-9b87-093fc79817c7", + "auto_remove": true, + "created_at": "2022-02-16T16:53:21.634264Z", + "initiator": "external", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "1dc2f76a-5db7-4ff9-9291-685af66681fe", + "~thread": { + "thid": "b8942466-637f-4db3-a139-126be08ba306" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQiLCAiaWQiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "updated_at": "2022-02-16T16:54:13.188641Z", + "auto_issue": false, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "b8942466-637f-4db3-a139-126be08ba306", + "~thread": {}, + "comment": "create automated v2.0 credential exchange record", + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "thread_id": "b8942466-637f-4db3-a139-126be08ba306" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/03-credential-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/03-credential-received.json new file mode 100644 index 000000000..b4c8b5f0f --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/03-credential-received.json @@ -0,0 +1,157 @@ +{ + "role": "holder", + "by_format": { + "cred_issue": { + "ld_proof": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234", + "id": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap" + }, + "proof": { + "type": "BbsBlsSignature2020", + "verificationMethod": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz#zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "created": "2022-02-16T16:54:13.555228+00:00", + "proofPurpose": "assertionMethod", + "proofValue": "ryxHXJ3qXcj037k2Tg/u/+hckGVVMMfqOUGqz2MY8Sxj9Jwa7maTeGr433c3ICgIFxTVIitQWzAw44IRBvJqVf3iW4FPzt05Cq7CkBOyQqA7CPVSgIKIM1vYKhIlj7cplUAd4QAJKDOwhxGS6a9YQg==" + } + } + }, + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234", + "id": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "credential-received", + "trace": false, + "cred_ex_id": "9a2b489a-5c47-49ce-9b87-093fc79817c7", + "auto_remove": true, + "created_at": "2022-02-16T16:53:21.634264Z", + "initiator": "external", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "1dc2f76a-5db7-4ff9-9291-685af66681fe", + "~thread": { + "thid": "b8942466-637f-4db3-a139-126be08ba306" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQiLCAiaWQiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "updated_at": "2022-02-16T16:54:13.782041Z", + "cred_issue": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/issue-credential", + "@id": "cb01186c-99ed-41e4-99d6-cb4ebc7968c2", + "~thread": { + "thid": "b8942466-637f-4db3-a139-126be08ba306" + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc@v1.0" + } + ], + "credentials~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQiLCAiaWQiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAifSwgInByb29mIjogeyJ0eXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAiLCAidmVyaWZpY2F0aW9uTWV0aG9kIjogImRpZDprZXk6elVDN0dKY3FCZU5IQ0VEOUZQSnJuR3ByNnFORUVmcm9kY1dxZ2JYOVE2V043VENhaE14Y1JkMlVKR1ZuTVF0TXJUeVhSTWJ2dXN3NDVNVnBKWUQxVFlSNDR2d2tWckV2RlJlMVlXa2tiZVFKajd4YWdoV01wb1VDaFV4WGJmZHVSWXJXVHB6I3pVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJjcmVhdGVkIjogIjIwMjItMDItMTZUMTY6NTQ6MTMuNTU1MjI4KzAwOjAwIiwgInByb29mUHVycG9zZSI6ICJhc3NlcnRpb25NZXRob2QiLCAicHJvb2ZWYWx1ZSI6ICJyeXhIWEozcVhjajAzN2syVGcvdS8raGNrR1ZWTU1mcU9VR3F6Mk1ZOFN4ajlKd2E3bWFUZUdyNDMzYzNJQ2dJRnhUVklpdFFXekF3NDRJUkJ2SnFWZjNpVzRGUHp0MDVDcTdDa0JPeVFxQTdDUFZTZ0lLSU0xdllLaElsajdjcGxVQWQ0UUFKS0RPd2h4R1M2YTlZUWc9PSJ9fQ==" + } + } + ] + }, + "auto_issue": false, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "b8942466-637f-4db3-a139-126be08ba306", + "~thread": {}, + "comment": "create automated v2.0 credential exchange record", + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "thread_id": "b8942466-637f-4db3-a139-126be08ba306" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/04-issue-credential-ld-proof.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/04-issue-credential-ld-proof.json new file mode 100644 index 000000000..e9bb23b8d --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/04-issue-credential-ld-proof.json @@ -0,0 +1,7 @@ +{ + "cred_ex_id": "9a2b489a-5c47-49ce-9b87-093fc79817c7", + "created_at": "2022-02-16T16:54:20.945305Z", + "cred_ex_ld_proof_id": "39f9a017-d221-45d0-b6d1-63d2997fd41f", + "cred_id_stored": "2d9afcfd4a2145bcb5253da9890200e0", + "updated_at": "2022-02-16T16:54:20.945305Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/05-done.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/05-done.json new file mode 100644 index 000000000..bfd6368c4 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-holder/05-done.json @@ -0,0 +1,157 @@ +{ + "role": "holder", + "by_format": { + "cred_issue": { + "ld_proof": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234", + "id": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap" + }, + "proof": { + "type": "BbsBlsSignature2020", + "verificationMethod": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz#zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "created": "2022-02-16T16:54:13.555228+00:00", + "proofPurpose": "assertionMethod", + "proofValue": "ryxHXJ3qXcj037k2Tg/u/+hckGVVMMfqOUGqz2MY8Sxj9Jwa7maTeGr433c3ICgIFxTVIitQWzAw44IRBvJqVf3iW4FPzt05Cq7CkBOyQqA7CPVSgIKIM1vYKhIlj7cplUAd4QAJKDOwhxGS6a9YQg==" + } + } + }, + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-16T16:53:19Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234", + "id": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "done", + "trace": false, + "cred_ex_id": "9a2b489a-5c47-49ce-9b87-093fc79817c7", + "auto_remove": true, + "created_at": "2022-02-16T16:53:21.634264Z", + "initiator": "external", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "1dc2f76a-5db7-4ff9-9291-685af66681fe", + "~thread": { + "thid": "b8942466-637f-4db3-a139-126be08ba306" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQiLCAiaWQiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "updated_at": "2022-02-16T16:54:20.965010Z", + "cred_issue": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/issue-credential", + "@id": "cb01186c-99ed-41e4-99d6-cb4ebc7968c2", + "~thread": { + "thid": "b8942466-637f-4db3-a139-126be08ba306" + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc@v1.0" + } + ], + "credentials~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQiLCAiaWQiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAifSwgInByb29mIjogeyJ0eXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAiLCAidmVyaWZpY2F0aW9uTWV0aG9kIjogImRpZDprZXk6elVDN0dKY3FCZU5IQ0VEOUZQSnJuR3ByNnFORUVmcm9kY1dxZ2JYOVE2V043VENhaE14Y1JkMlVKR1ZuTVF0TXJUeVhSTWJ2dXN3NDVNVnBKWUQxVFlSNDR2d2tWckV2RlJlMVlXa2tiZVFKajd4YWdoV01wb1VDaFV4WGJmZHVSWXJXVHB6I3pVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJjcmVhdGVkIjogIjIwMjItMDItMTZUMTY6NTQ6MTMuNTU1MjI4KzAwOjAwIiwgInByb29mUHVycG9zZSI6ICJhc3NlcnRpb25NZXRob2QiLCAicHJvb2ZWYWx1ZSI6ICJyeXhIWEozcVhjajAzN2syVGcvdS8raGNrR1ZWTU1mcU9VR3F6Mk1ZOFN4ajlKd2E3bWFUZUdyNDMzYzNJQ2dJRnhUVklpdFFXekF3NDRJUkJ2SnFWZjNpVzRGUHp0MDVDcTdDa0JPeVFxQTdDUFZTZ0lLSU0xdllLaElsajdjcGxVQWQ0UUFKS0RPd2h4R1M2YTlZUWc9PSJ9fQ==" + } + } + ] + }, + "auto_issue": false, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "b8942466-637f-4db3-a139-126be08ba306", + "~thread": {}, + "comment": "create automated v2.0 credential exchange record", + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjo1MzoxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "thread_id": "b8942466-637f-4db3-a139-126be08ba306" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/01-proposal-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/01-proposal-received.json new file mode 100644 index 000000000..e7429df7f --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/01-proposal-received.json @@ -0,0 +1,57 @@ +{ + "state": "proposal-received", + "updated_at": "2022-02-15T18:25:04.522565Z", + "thread_id": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152", + "trace": false, + "auto_remove": true, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdHSmNxQmVOSENFRDlGUEpybkdwcjZxTkVFZnJvZGNXcWdiWDlRNldON1RDYWhNeGNSZDJVSkdWbk1RdE1yVHlYUk1idnVzdzQ1TVZwSllEMVRZUjQ0dndrVnJFdkZSZTFZV2trYmVRSmo3eGFnaFdNcG9VQ2hVeFhiZmR1UllyV1RweiIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNTowNFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAiSGFucyBNb3NlciIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "by_format": { + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7GJcqBeNHCED9FPJrnGpr6qNEEfrodcWqgbX9Q6WN7TCahMxcRd2UJGVnMQtMrTyXRMbvusw45MVpJYD1TYR44vwkVrEvFRe1YWkkbeQJj7xaghWMpoUChUxXbfduRYrWTpz", + "issuanceDate": "2022-02-15T18:25:04Z", + "credentialSubject": { + "name": "Name", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "external", + "cred_ex_id": "5c72dfb5-9965-4210-bf45-f89911b71c84", + "role": "issuer", + "created_at": "2022-02-15T18:25:04.522565Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/02-counter-offer-sent.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/02-counter-offer-sent.json new file mode 100644 index 000000000..8b268fc96 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/02-counter-offer-sent.json @@ -0,0 +1,141 @@ +{ + "state": "offer-sent", + "updated_at": "2022-02-15T18:26:46.874890Z", + "cred_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "thread_id": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152", + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "4860c2b8-d242-48fb-98a2-200774f7edbf", + "~thread": { + "thid": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152" + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "03006871-56ce-4029-8b42-857ac3701f70", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE4OjI2OjQ0WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + } + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + }, + "issuanceDate": "2022-02-15T18:26:44Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "external", + "cred_ex_id": "5c72dfb5-9965-4210-bf45-f89911b71c84", + "role": "issuer", + "created_at": "2022-02-15T18:25:04.522565Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/03-request-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/03-request-received.json new file mode 100644 index 000000000..6a3a19781 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/03-request-received.json @@ -0,0 +1,187 @@ +{ + "state": "request-received", + "updated_at": "2022-02-15T18:27:42.031853Z", + "cred_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "thread_id": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152", + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "4860c2b8-d242-48fb-98a2-200774f7edbf", + "~thread": { + "thid": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152" + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "03006871-56ce-4029-8b42-857ac3701f70", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE4OjI2OjQ0WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + } + }, + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "e5322135-613b-47cc-8f54-b0bcf92c3004", + "~thread": { + "thid": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + }, + "issuanceDate": "2022-02-15T18:26:44Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "external", + "cred_ex_id": "5c72dfb5-9965-4210-bf45-f89911b71c84", + "role": "issuer", + "created_at": "2022-02-15T18:25:04.522565Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/04-credential-issued.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/04-credential-issued.json new file mode 100644 index 000000000..3843d6d4f --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer-proposal/04-credential-issued.json @@ -0,0 +1,232 @@ +{ + "state": "credential-issued", + "updated_at": "2022-02-15T18:27:42.280765Z", + "cred_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "thread_id": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152", + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "4860c2b8-d242-48fb-98a2-200774f7edbf", + "~thread": { + "thid": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152" + }, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + }, + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "03006871-56ce-4029-8b42-857ac3701f70", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE4OjI2OjQ0WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "credential_preview": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/credential-preview", + "attributes": [ + { + "name": "identifier", + "value": "1234" + }, + { + "name": "name", + "value": "Other Name" + } + ] + } + }, + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "e5322135-613b-47cc-8f54-b0bcf92c3004", + "~thread": { + "thid": "bd86bace-be99-4dc1-8ad5-ec7d3cbbd152" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_issue": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/issue-credential", + "@id": "8760f7d4-5728-4c51-8f7d-5bc830c04b81", + "credentials~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxODoyNjo0NFoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAiMTIzNCIsICJuYW1lIjogIk90aGVyIE5hbWUifSwgInByb29mIjogeyJ0eXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAiLCAidmVyaWZpY2F0aW9uTWV0aG9kIjogImRpZDprZXk6elVDN0Zzd2dWdDYxVERNNXk4OHVNODNncExXWjd1cFZwN0tFaVR0Z2dKY3NwU2ZieEtkM3lIRlJYejduanVjWExFSFUxUXZVVmE0WnhIV2E3ZkJBNjhMUTlGTDJheTlxY0NGalllSHNCQWhXVDFIUkRCbmtuUTlDQ0JYb25BaDZ4bXZvRmFwI3pVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJjcmVhdGVkIjogIjIwMjItMDItMTVUMTg6Mjc6NDIuMTQ3NjIzKzAwOjAwIiwgInByb29mUHVycG9zZSI6ICJhc3NlcnRpb25NZXRob2QiLCAicHJvb2ZWYWx1ZSI6ICJsZDZTSWZIekFXQXJmTDUzRlljVUZ2aXpoUzJlaSt0ZTQ4UEY3SmJmQ1BOaklsMVVmVXcxcFpVTStlODJBcWNwWEVpQklBSXlXTFdqSlV3TzBMMm5NWWMvYkxLVmVFVGplREU5eCtoV2NaTTFXMlBXYmcweVBvUVlKT1U4c2E0ejhLVDRybEs0QXJROVRteHVucit0aGc9PSJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_issue": { + "ld_proof": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T18:26:44Z", + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + }, + "proof": { + "type": "BbsBlsSignature2020", + "verificationMethod": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap#zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "created": "2022-02-15T18:27:42.147623+00:00", + "proofPurpose": "assertionMethod", + "proofValue": "ld6SIfHzAWArfL53FYcUFvizhS2ei+te48PF7JbfCPNjIl1UfUw1pZUM+e82AqcpXEiBIAIyWLWjJUwO0L2nMYc/bLKVeETjeDE9x+hWcZM1W2PWbg0yPoQYJOU8sa4z8KT4rlK4ArQ9Tmxunr+thg==" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "1234", + "name": "Other Name" + }, + "issuanceDate": "2022-02-15T18:26:44Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "external", + "cred_ex_id": "5c72dfb5-9965-4210-bf45-f89911b71c84", + "role": "issuer", + "created_at": "2022-02-15T18:25:04.522565Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/01-offer-sent.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/01-offer-sent.json new file mode 100644 index 000000000..f14c6fe6a --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/01-offer-sent.json @@ -0,0 +1,103 @@ +{ + "state": "offer-sent", + "updated_at": "2022-02-15T17:45:21.543829Z", + "thread_id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "auto_issue": true, + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "~thread": {}, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "comment": "create automated v2.0 credential exchange record" + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "508c1d0e-ccd6-4a7a-b55d-91d205273f8d", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE3OjQ1OjE5WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "issuanceDate": "2022-02-15T17:45:19Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "self", + "cred_ex_id": "2b66c488-4af9-4e8d-a8f8-1dcfbbe2fc70", + "role": "issuer", + "auto_offer": false, + "created_at": "2022-02-15T17:45:21.543829Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/02-request-received.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/02-request-received.json new file mode 100644 index 000000000..4993efac4 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/02-request-received.json @@ -0,0 +1,149 @@ +{ + "state": "request-received", + "updated_at": "2022-02-15T17:46:26.212504Z", + "thread_id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "auto_issue": true, + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "~thread": {}, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "comment": "create automated v2.0 credential exchange record" + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "508c1d0e-ccd6-4a7a-b55d-91d205273f8d", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE3OjQ1OjE5WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "ba5ab5db-bd1d-4775-9670-18a18b96ea2e", + "~thread": { + "thid": "fcd27ccb-5a73-4a78-b214-ab5dad402d22" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "issuanceDate": "2022-02-15T17:45:19Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "self", + "cred_ex_id": "2b66c488-4af9-4e8d-a8f8-1dcfbbe2fc70", + "role": "issuer", + "auto_offer": false, + "created_at": "2022-02-15T17:45:21.543829Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/03-credential-issued.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/03-credential-issued.json new file mode 100644 index 000000000..40739ffdf --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/03-credential-issued.json @@ -0,0 +1,194 @@ +{ + "state": "credential-issued", + "updated_at": "2022-02-15T17:46:26.630447Z", + "thread_id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "auto_issue": true, + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "~thread": {}, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "comment": "create automated v2.0 credential exchange record" + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "508c1d0e-ccd6-4a7a-b55d-91d205273f8d", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE3OjQ1OjE5WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "ba5ab5db-bd1d-4775-9670-18a18b96ea2e", + "~thread": { + "thid": "fcd27ccb-5a73-4a78-b214-ab5dad402d22" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_issue": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/issue-credential", + "@id": "974b3535-ac1f-4cad-a1c2-6af8d3b6b411", + "credentials~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgInByb29mIjogeyJ0eXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAiLCAidmVyaWZpY2F0aW9uTWV0aG9kIjogImRpZDprZXk6elVDN0Zzd2dWdDYxVERNNXk4OHVNODNncExXWjd1cFZwN0tFaVR0Z2dKY3NwU2ZieEtkM3lIRlJYejduanVjWExFSFUxUXZVVmE0WnhIV2E3ZkJBNjhMUTlGTDJheTlxY0NGalllSHNCQWhXVDFIUkRCbmtuUTlDQ0JYb25BaDZ4bXZvRmFwI3pVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJjcmVhdGVkIjogIjIwMjItMDItMTVUMTc6NDY6MjYuNDgyNzQ2KzAwOjAwIiwgInByb29mUHVycG9zZSI6ICJhc3NlcnRpb25NZXRob2QiLCAicHJvb2ZWYWx1ZSI6ICJobmRnUnpadWRLeDNzVnlHSkFDc204a1p4L3l4Q2c1V2FsUkI1ODRITStFMFJSOTh2dElhUVliUlFFWVlpRG9hYlBCc3dsUHJIMTBrc013WHdqL3JzbnByTjhwcjlzelloRmZpSm5aQUR3UVBiMnRhT1N6MVA4ZFlCQUNTdGF1aThSK1F4THFDRWhKU2tqdGFoVmZINnc9PSJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_issue": { + "ld_proof": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "proof": { + "type": "BbsBlsSignature2020", + "verificationMethod": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap#zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "created": "2022-02-15T17:46:26.482746+00:00", + "proofPurpose": "assertionMethod", + "proofValue": "hndgRzZudKx3sVyGJACsm8kZx/yxCg5WalRB584HM+E0RR98vtIaQYbRQEYYiDoabPBswlPrH10ksMwXwj/rsnprN8pr9szYhFfiJnZADwQPb2taOSz1P8dYBACStaui8R+QxLqCEhJSkjtahVfH6w==" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "issuanceDate": "2022-02-15T17:45:19Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "self", + "cred_ex_id": "2b66c488-4af9-4e8d-a8f8-1dcfbbe2fc70", + "role": "issuer", + "auto_offer": false, + "created_at": "2022-02-15T17:45:21.543829Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/04-done.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/04-done.json new file mode 100644 index 000000000..2ec4230a5 --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/04-done.json @@ -0,0 +1,194 @@ +{ + "state": "done", + "updated_at": "2022-02-15T17:46:30.998173Z", + "thread_id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "auto_issue": true, + "trace": false, + "auto_remove": true, + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "fcd27ccb-5a73-4a78-b214-ab5dad402d22", + "~thread": {}, + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "comment": "create automated v2.0 credential exchange record" + }, + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "508c1d0e-ccd6-4a7a-b55d-91d205273f8d", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE1VDE3OjQ1OjE5WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_request": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/request-credential", + "@id": "ba5ab5db-bd1d-4775-9670-18a18b96ea2e", + "~thread": { + "thid": "fcd27ccb-5a73-4a78-b214-ab5dad402d22" + }, + "requests~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "cred_issue": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/issue-credential", + "@id": "974b3535-ac1f-4cad-a1c2-6af8d3b6b411", + "credentials~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNVQxNzo0NToxOVoiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7ImlkZW50aWZpZXIiOiAid2FzIGdlaHQiLCAibmFtZSI6ICI1NTUifSwgInByb29mIjogeyJ0eXBlIjogIkJic0Jsc1NpZ25hdHVyZTIwMjAiLCAidmVyaWZpY2F0aW9uTWV0aG9kIjogImRpZDprZXk6elVDN0Zzd2dWdDYxVERNNXk4OHVNODNncExXWjd1cFZwN0tFaVR0Z2dKY3NwU2ZieEtkM3lIRlJYejduanVjWExFSFUxUXZVVmE0WnhIV2E3ZkJBNjhMUTlGTDJheTlxY0NGalllSHNCQWhXVDFIUkRCbmtuUTlDQ0JYb25BaDZ4bXZvRmFwI3pVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJjcmVhdGVkIjogIjIwMjItMDItMTVUMTc6NDY6MjYuNDgyNzQ2KzAwOjAwIiwgInByb29mUHVycG9zZSI6ICJhc3NlcnRpb25NZXRob2QiLCAicHJvb2ZWYWx1ZSI6ICJobmRnUnpadWRLeDNzVnlHSkFDc204a1p4L3l4Q2c1V2FsUkI1ODRITStFMFJSOTh2dElhUVliUlFFWVlpRG9hYlBCc3dsUHJIMTBrc013WHdqL3JzbnByTjhwcjlzelloRmZpSm5aQUR3UVBiMnRhT1N6MVA4ZFlCQUNTdGF1aThSK1F4THFDRWhKU2tqdGFoVmZINnc9PSJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc@v1.0" + } + ] + }, + "by_format": { + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_request": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_issue": { + "ld_proof": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-15T17:45:19Z", + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "proof": { + "type": "BbsBlsSignature2020", + "verificationMethod": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap#zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "created": "2022-02-15T17:46:26.482746+00:00", + "proofPurpose": "assertionMethod", + "proofValue": "hndgRzZudKx3sVyGJACsm8kZx/yxCg5WalRB584HM+E0RR98vtIaQYbRQEYYiDoabPBswlPrH10ksMwXwj/rsnprN8pr9szYhFfiJnZADwQPb2taOSz1P8dYBACStaui8R+QxLqCEhJSkjtahVfH6w==" + } + } + }, + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "identifier": "was geht", + "name": "555" + }, + "issuanceDate": "2022-02-15T17:45:19Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "initiator": "self", + "cred_ex_id": "2b66c488-4af9-4e8d-a8f8-1dcfbbe2fc70", + "role": "issuer", + "auto_offer": false, + "created_at": "2022-02-15T17:45:21.543829Z" +} \ No newline at end of file diff --git a/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/05-abandoned.json b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/05-abandoned.json new file mode 100644 index 000000000..c197a0ccb --- /dev/null +++ b/backend/business-partner-agent/src/test/resources/files/v2-ld-credex-issuer/05-abandoned.json @@ -0,0 +1,104 @@ +{ + "cred_proposal": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/propose-credential", + "@id": "9bfa7f41-5319-47de-a501-a074d885ccc8", + "filters~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSJdLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifSwgImlzc3VhbmNlRGF0ZSI6ICIyMDIyLTAyLTE2VDE2OjA4OjU2WiIsICJpc3N1ZXIiOiAiZGlkOmtleTp6VUM3RnN3Z1Z0NjFURE01eTg4dU04M2dwTFdaN3VwVnA3S0VpVHRnZ0pjc3BTZmJ4S2QzeUhGUlh6N25qdWNYTEVIVTFRdlVWYTRaeEhXYTdmQkE2OExROUZMMmF5OXFjQ0ZqWWVIc0JBaFdUMUhSREJua25ROUNDQlhvbkFoNnhtdm9GYXAiLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ], + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ] + }, + "role": "issuer", + "by_format": { + "cred_proposal": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1" + ], + "credentialSubject": { + "name": "karl", + "identifier": "1234" + }, + "issuanceDate": "2022-02-16T16:08:56Z", + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "type": [ + "VerifiableCredential", + "PermanentResident" + ] + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + }, + "cred_offer": { + "ld_proof": { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/citizenship/v1", + "https://w3id.org/security/bbs/v1" + ], + "type": [ + "VerifiableCredential", + "PermanentResident" + ], + "issuer": "did:key:zUC7FswgVt61TDM5y88uM83gpLWZ7upVp7KEiTtggJcspSfbxKd3yHFRXz7njucXLEHU1QvUVa4ZxHWa7fBA68LQ9FL2ay9qcCFjYeHsBAhWT1HRDBnknQ9CCBXonAh6xmvoFap", + "issuanceDate": "2022-02-16T16:08:56Z", + "credentialSubject": { + "name": "karl", + "identifier": "1234" + } + }, + "options": { + "proofType": "BbsBlsSignature2020" + } + } + } + }, + "auto_offer": false, + "state": "abandoned", + "trace": false, + "cred_ex_id": "2b66c488-4af9-4e8d-a8f8-1dcfbbe2fc70", + "auto_remove": true, + "created_at": "2022-02-16T16:08:58.864678Z", + "initiator": "self", + "connection_id": "1be44de6-7538-4d49-a7f5-86b9dab151ea", + "updated_at": "2022-02-16T16:09:14.709277Z", + "auto_issue": true, + "error_msg": "issuance-abandoned: my reason2", + "cred_offer": { + "@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/issue-credential/2.0/offer-credential", + "@id": "8f9f01e1-ef76-4008-b639-3a6a42a9ff5b", + "~thread": {}, + "comment": "create automated v2.0 credential exchange record", + "formats": [ + { + "attach_id": "ld_proof", + "format": "aries/ld-proof-vc-detail@v1.0" + } + ], + "offers~attach": [ + { + "@id": "ld_proof", + "mime-type": "application/json", + "data": { + "base64": "eyJjcmVkZW50aWFsIjogeyJAY29udGV4dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0cHM6Ly93M2lkLm9yZy9jaXRpemVuc2hpcC92MSIsICJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L2Jicy92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCAiUGVybWFuZW50UmVzaWRlbnQiXSwgImlzc3VlciI6ICJkaWQ6a2V5OnpVQzdGc3dnVnQ2MVRETTV5ODh1TTgzZ3BMV1o3dXBWcDdLRWlUdGdnSmNzcFNmYnhLZDN5SEZSWHo3bmp1Y1hMRUhVMVF2VVZhNFp4SFdhN2ZCQTY4TFE5RkwyYXk5cWNDRmpZZUhzQkFoV1QxSFJEQm5rblE5Q0NCWG9uQWg2eG12b0ZhcCIsICJpc3N1YW5jZURhdGUiOiAiMjAyMi0wMi0xNlQxNjowODo1NloiLCAiY3JlZGVudGlhbFN1YmplY3QiOiB7Im5hbWUiOiAia2FybCIsICJpZGVudGlmaWVyIjogIjEyMzQifX0sICJvcHRpb25zIjogeyJwcm9vZlR5cGUiOiAiQmJzQmxzU2lnbmF0dXJlMjAyMCJ9fQ==" + } + } + ] + }, + "thread_id": "8f9f01e1-ef76-4008-b639-3a6a42a9ff5b" +} \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml index b13eb5a0b..f4486ce1d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -56,21 +56,21 @@ 2.17.1 1.18.22 1.18.20.0 - 3.3.0 + 3.3.3 3.2.2 4.0.0 3.3.0 4.3.1 4.9.3 1.4.2.Final - 6.41.0 + 6.42.0 4.5.3 1.16.3 4.1 - 3.9.0 + 3.10.0 3.0.0-M5 - 3.15.0 + 3.16.0 4.5.3.0 3.0.0-M5 @@ -135,7 +135,7 @@ org.flywaydb flyway-core - 8.4.3 + 8.5.0 @@ -318,7 +318,7 @@ io.micronaut.build micronaut-maven-plugin - 3.1.0 + 3.1.1 org.apache.maven.plugins @@ -462,7 +462,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.3.1 + 3.3.2 ${java.home}/bin/javadoc ${project.build.directory}/generated-sources/delombok @@ -528,7 +528,7 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.1.2 + 3.2.1 diff --git a/frontend/src/components/CredExList.vue b/frontend/src/components/CredExList.vue index 31722493b..76712d5a9 100644 --- a/frontend/src/components/CredExList.vue +++ b/frontend/src/components/CredExList.vue @@ -117,7 +117,7 @@ dense > { - EventBus.$emit("success", this.$axiosErrorMessage(response)); + EventBus.$emit("success"); this.closeDialog(); }) .catch((error) => { diff --git a/frontend/src/components/ManageSchema.vue b/frontend/src/components/ManageSchema.vue index 742f7e868..0ac563647 100644 --- a/frontend/src/components/ManageSchema.vue +++ b/frontend/src/components/ManageSchema.vue @@ -62,6 +62,11 @@ >

{{ attribute }} + $vuetify.icons.asterisk

@@ -106,6 +111,7 @@ import { adminService } from "@/services"; import TrustedIssuers from "@/components/TrustedIssuers.vue"; import CredentialDefinitions from "@/components/CredentialDefinitions.vue"; import VBpaButton from "@/components/BpaButton"; +import { CredentialTypes } from "@/constants"; export default { name: "ManageSchema", props: { @@ -143,6 +149,9 @@ export default { }; }, computed: { + typeIsJsonLD() { + return this.schema.type === CredentialTypes.JSON_LD.type; + }, schema: { get() { return this.value; @@ -158,12 +167,12 @@ export default { key: "schema-attributes", }, ]; - if (this.credentialDefinitions) + if (this.credentialDefinitions && !this.typeIsJsonLD) tabs.push({ title: this.$t("component.manageSchema.tabs.credentialDefinitions"), key: "credential-definitions", }); - if (!this.schema.isMine && this.trustedIssuers) + if ((!this.schema.isMine || this.typeIsJsonLD) && this.trustedIssuers) tabs.push({ title: this.$t("component.manageSchema.tabs.trustedIssuers"), key: "trusted-issuers", diff --git a/frontend/src/components/MyCredentialList.vue b/frontend/src/components/MyCredentialList.vue index b47427db1..6a681bcf0 100644 --- a/frontend/src/components/MyCredentialList.vue +++ b/frontend/src/components/MyCredentialList.vue @@ -120,6 +120,10 @@ export default { type: Boolean, default: false, }, + useJsonLd: { + type: Boolean, + default: false, + }, }, components: { NewMessageIcon, @@ -136,6 +140,17 @@ export default { }; }, computed: { + queryFilter() { + let q = ""; + if (this.useIndy && this.useJsonLd) { + q = "?types=INDY&types=JSON_LD"; + } else if (this.useIndy) { + q = "?types=INDY"; + } else if (this.useJsonLd) { + q = "?types=JSON_LD"; + } + return q; + }, credentialNotifications() { return this.$store.getters.credentialNotifications; }, @@ -159,11 +174,7 @@ export default { methods: { fetch(type) { this.$axios - .get( - `${this.$apiBaseUrl}/wallet/${type}${ - this.useIndy ? "?types=INDY" : "" - }` - ) + .get(`${this.$apiBaseUrl}/wallet/${type}${this.queryFilter}`) .then((result) => { if (Object.prototype.hasOwnProperty.call(result, "data")) { this.isBusy = false; diff --git a/frontend/src/components/Profile.vue b/frontend/src/components/Profile.vue index 6e09d78b7..4fd8e5fc2 100644 --- a/frontend/src/components/Profile.vue +++ b/frontend/src/components/Profile.vue @@ -41,7 +41,7 @@ class="text-caption mt-1 ml-1" > {{ $t("component.profile.credential.verifiedByLabel") }} - {{ item.issuer }} + {{ item.issuer | truncate }} 40) { + return t.slice(0, 40) + "..."; + } + return t; + }, + }, computed: { profile: function () { return getPartnerProfile(this.partner); diff --git a/frontend/src/plugins/vuetify.ts b/frontend/src/plugins/vuetify.ts index 39b49bb6c..2ba6470d9 100644 --- a/frontend/src/plugins/vuetify.ts +++ b/frontend/src/plugins/vuetify.ts @@ -11,6 +11,7 @@ import Vuetify from "vuetify/lib/framework"; import { en, de, pl } from "vuetify/lib/locale/index"; import { + mdiAsterisk, mdiViewDashboard, mdiForumOutline, mdiAccountCircle, @@ -104,6 +105,7 @@ export default new Vuetify({ invitation: mdiTicketConfirmationOutline, validationError: mdiAlert, attachment: mdiAttachment, + asterisk: mdiAsterisk, }, }, lang: { diff --git a/frontend/src/views/Document.vue b/frontend/src/views/Document.vue index 065f5243a..3ad9a9506 100644 --- a/frontend/src/views/Document.vue +++ b/frontend/src/views/Document.vue @@ -80,7 +80,11 @@ icon :to="{ name: 'RequestVerification', - params: { documentId: id, schemaId: intDoc.schemaId }, + params: { + documentId: id, + schemaId: intDoc.schemaId, + type: intDoc.type, + }, }" :disabled="docModified()" > diff --git a/frontend/src/views/RequestCredential.vue b/frontend/src/views/RequestCredential.vue index 556a63d20..ab2dbe03e 100644 --- a/frontend/src/views/RequestCredential.vue +++ b/frontend/src/views/RequestCredential.vue @@ -18,13 +18,14 @@ v-model="selectedDocument" disable-verification-request use-indy + use-json-ld selectable type="document" > @@ -50,7 +51,7 @@