diff --git a/Makefile b/Makefile
index bbbca63..11a4e6d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
.PHONY: generate
generate:
- mvn clean generate-sources
+ mvn clean process-sources
install:
mvn clean install
diff --git a/README.md b/README.md
index 82f7779..eb166d8 100644
--- a/README.md
+++ b/README.md
@@ -20,24 +20,11 @@ It is generated from the latest version of the OpenAPI specifications of the Nut
Find all versions on [Maven central](https://search.maven.org/artifact/nl.reinkrul.nuts/java-client).
-# Usage
-The example below instantiates the API client for VDR and calls `getDID` for `subjectDID`:
-```java
-var apiClient = new nl.reinkrul.nuts.ApiClient();
-
-var didApi = new nl.reinkrul.nuts.vdr.DidApi(apiClient);
-var didDocument = didApi.getDID(subjectDID, null, null);
-
-// do something with the resolved DID Document
-```
-
+# Usage and examples
Since each module in the Nuts Node has its own OpenAPI specification, there is a client API generated for each of them.
You can find in their own subpackage in `nl.reinkrul.nuts` (e.g. `nl.reinkrul.nuts.vdr`).
-# Examples
-
-See [src/test/java/CredentialExamples.java](src/test/java/CredentialExamples.java)
-for how to issue `NutsOrganizationCredential`, `NutsAuthenticationCredential` and `NutsEmployeeCredential`.
+See [src/test/java/nl/reinkrul/nuts/IntegrationTest.java](src/test/java/nl/reinkrul/nuts/IntegrationTest.java) for an example of how to use the client.
# Versioning
diff --git a/nutsnode/discovery/test.json b/nutsnode/discovery/test.json
new file mode 100644
index 0000000..01f4ed3
--- /dev/null
+++ b/nutsnode/discovery/test.json
@@ -0,0 +1,57 @@
+{
+ "id": "test",
+ "endpoint": "http://localhost:8080/discovery/test",
+ "presentation_max_validity": 2764800,
+ "presentation_definition": {
+ "id": "dev:eOverdracht2023",
+ "format": {
+ "ldp_vc": {
+ "proof_type": [
+ "JsonWebSignature2020"
+ ]
+ },
+ "jwt_vp": {
+ "alg": ["ES256"]
+ },
+ "jwt_vc": {
+ "alg": ["ES256"]
+ }
+ },
+ "input_descriptors": [
+ {
+ "id": "SelfIssued_NutsUraCredential",
+ "constraints": {
+ "fields": [
+ {
+ "path": [
+ "$.type"
+ ],
+ "filter": {
+ "type": "string",
+ "const": "NutsUraCredential"
+ }
+ },
+ {
+ "id": "organization.name",
+ "path": [
+ "$.credentialSubject.organization.name"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "organization.ura",
+ "path": [
+ "$.credentialSubject.organization.ura"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/nutsnode/docker-compose.yaml b/nutsnode/docker-compose.yaml
new file mode 100644
index 0000000..b192bbd
--- /dev/null
+++ b/nutsnode/docker-compose.yaml
@@ -0,0 +1,19 @@
+services:
+ nuts-node:
+ image: nutsfoundation/nuts-node:master
+ ports:
+ - "8081:8081"
+ - "8080:8080"
+ volumes:
+ - ./discovery:/opt/nuts/discovery:ro
+ - ./policy:/opt/nuts/policy:ro
+ environment:
+ NUTS_CRYPTO_STORAGE: fs
+ NUTS_HTTP_INTERNAL_ADDRESS: :8081
+ NUTS_URL: http://localhost:8080
+ NUTS_STRICTMODE: false
+ NUTS_DISCOVERY_DEFINITIONS_DIRECTORY: /opt/nuts/discovery
+ NUTS_DISCOVERY_SERVER_IDS: test
+ NUTS_POLICY_DIRECTORY: /opt/nuts/policy
+ NUTS_AUTH_CONTRACTVALIDATORS: dummy
+ NUTS_VDR_DIDMETHODS: web
diff --git a/nutsnode/policy/test.json b/nutsnode/policy/test.json
new file mode 100644
index 0000000..b0edd30
--- /dev/null
+++ b/nutsnode/policy/test.json
@@ -0,0 +1,137 @@
+{
+ "test": {
+ "organization": {
+ "format": {
+ "ldp_vc": {
+ "proof_type": [
+ "JsonWebSignature2020"
+ ]
+ },
+ "jwt_vc": {
+ "alg": [
+ "ES256"
+ ]
+ },
+ "ldp_vp": {
+ "proof_type": [
+ "JsonWebSignature2020"
+ ]
+ },
+ "jwt_vp": {
+ "alg": [
+ "ES256"
+ ]
+ }
+ },
+ "id": "pd_any_care_organization_with_employee",
+ "name": "Care organization with employee",
+ "purpose": "Finding a care organization with logged in user for authorizing access to medical metadata",
+ "input_descriptors": [
+ {
+ "id": "id_nuts_ura_credential",
+ "name": "Care organization",
+ "purpose": "Finding a care organization for authorizing access to medical metadata.",
+ "constraints": {
+ "fields": [
+ {
+ "path": [
+ "$.type"
+ ],
+ "filter": {
+ "type": "string",
+ "const": "NutsUraCredential"
+ }
+ },
+ {
+ "id": "organization_name",
+ "path": [
+ "$.credentialSubject.organization.name",
+ "$.credentialSubject[0].organization.name"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "organization_ura",
+ "path": [
+ "$.credentialSubject.organization.ura",
+ "$.credentialSubject[0].organization.ura"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "organization_city",
+ "path": [
+ "$.credentialSubject.organization.city",
+ "$.credentialSubject[0].organization.city"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "id": "id_employee_credential_cred",
+ "constraints": {
+ "fields": [
+ {
+ "path": [
+ "$.type"
+ ],
+ "filter": {
+ "type": "string",
+ "const": "NutsEmployeeCredential"
+ }
+ },
+ {
+ "id": "employee_identifier",
+ "path": [
+ "$.credentialSubject.member.identifier",
+ "$.credentialSubject[0].member.identifier"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "employee_name",
+ "path": [
+ "$.credentialSubject.member.member.familyName",
+ "$.credentialSubject[0].member.member.familyName"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "employee_initials",
+ "path": [
+ "$.credentialSubject.member.member.initials",
+ "$.credentialSubject[0].member.member.initials"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ },
+ {
+ "id": "employee_role",
+ "path": [
+ "$.credentialSubject.member.roleName",
+ "$.credentialSubject[0].member.roleName"
+ ],
+ "filter": {
+ "type": "string"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 33ad66a..125f96d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
nl.reinkrul.nuts
java-client
jar
- 5.3.0
+ 6.0.0-beta.1
Java Nuts Client Library
https://github.com/reinkrul/java-nuts-client
@@ -34,9 +34,9 @@
- 11
- 11
- v5.3.0
+ 18
+ 18
+ v6.0.0-rc.4
https://raw.githubusercontent.com/nuts-foundation/nuts-node/${nuts.version}/docs/_static
@@ -44,6 +44,12 @@
${project.basedir}/target/generated
1.0-SNAPSHOT
+
+ 3.1.1
+ 2.15.2
+ 2.15.2
+ 0.2.6
+ 2.1.1
@@ -54,28 +60,81 @@
+
- org.junit.jupiter
- junit-jupiter-engine
- 5.9.1
- test
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+
+
+ org.glassfish.jersey.core
+ jersey-client
+ ${jersey-version}
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ ${jersey-version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-multipart
+ ${jersey-version}
+
+
+ org.glassfish.jersey.media
+ jersey-media-json-jackson
+ ${jersey-version}
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${jackson-version}
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${jackson-version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson-databind-version}
+
org.openapitools
jackson-databind-nullable
- 0.2.6
+ ${jackson-databind-nullable-version}
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
- 2.14.2
+ ${jackson-version}
- com.google.code.findbugs
- jsr305
- 3.0.2
+ jakarta.annotation
+ jakarta.annotation-api
+ ${jakarta-annotation-version}
+ provided
+
+
+ org.glassfish.jersey.connectors
+ jersey-apache-connector
+ ${jersey-version}
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.9.1
+ test
+
+
io.swagger
swagger-annotations
@@ -86,6 +145,11 @@
did-common-java
1.4.0
+
+ com.danubetech
+ verifiable-credentials-java
+ 1.7.0
+
javax.annotation
javax.annotation-api
@@ -126,7 +190,7 @@
run
-
+
-
+
+
+
+ dest="${openapi.spec.dir}/vcr/v2.yaml"/>
+
+
+
-
+
+
+
+
+
+
+
+ replace-discovery-tag
+ generate-sources
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+ remove-generated-configuration-class
+ process-sources
+
+ run
+
+
+
+
+
@@ -169,7 +274,7 @@
org.openapitools
openapi-generator-maven-plugin
- 6.6.0
+ 7.5.0
java
false
@@ -195,38 +300,26 @@
https://www.reinkrul.nl
Java client library for using the Nuts Node's REST API.
- native
+ jersey3
- DIDDocument=nl.reinkrul.nuts.common.DIDDocument
+
+ DIDDocument=nl.reinkrul.nuts.common.DIDDocument,
+ VerifiableCredential=nl.reinkrul.nuts.common.VerifiableCredential,
+ VerifiablePresentation=nl.reinkrul.nuts.common.VerifiablePresentation,
+
DIDDocumentMetadata=nl.reinkrul.nuts.common.DIDDocumentMetadata,
EmbeddedProof=nl.reinkrul.nuts.common.EmbeddedProof,
Revocation=nl.reinkrul.nuts.common.Revocation,
Service=nl.reinkrul.nuts.common.Service,
- VerifiableCredential=nl.reinkrul.nuts.common.VerifiableCredential,
- VerifiablePresentation=nl.reinkrul.nuts.common.VerifiablePresentation,
VerificationMethod=nl.reinkrul.nuts.common.VerificationMethod
-
- generate-additions
-
- generate
-
-
- specs/credentials.yaml
- false
-
- nl.reinkrul.nuts.extra
- nl.reinkrul.nuts.extra
-
-
-
generate-common-ssi-types
@@ -236,7 +329,11 @@
${openapi.spec.dir}/common/ssi_types.yaml
true
- DIDDocument=nl.reinkrul.nuts.common.DIDDocument
+
+ DIDDocument=nl.reinkrul.nuts.common.DIDDocument,
+ VerifiableCredential=nl.reinkrul.nuts.common.VerifiableCredential,
+ VerifiablePresentation=nl.reinkrul.nuts.common.VerifiablePresentation
+
nl.reinkrul.nuts.common
nl.reinkrul.nuts.common
@@ -246,15 +343,16 @@
- generate-vdr-client
+ generate-vdr-v2-client
generate
- ${openapi.spec.dir}/vdr/v1.yaml
+ ${openapi.spec.dir}/vdr/v2.yaml
nl.reinkrul.nuts.vdr
nl.reinkrul.nuts.vdr
+ nl.reinkrul.nuts
@@ -265,7 +363,7 @@
generate
- ${openapi.spec.dir}/vcr/vcr_v2.yaml
+ ${openapi.spec.dir}/vcr/v2.yaml
nl.reinkrul.nuts.vcr
nl.reinkrul.nuts.vcr
@@ -286,6 +384,20 @@
+
+
+ generate-discovery-client
+
+ generate
+
+
+ ${openapi.spec.dir}/discovery/v1.yaml
+
+ nl.reinkrul.nuts.discovery
+ nl.reinkrul.nuts.discovery
+
+
+
generate-network-client
@@ -314,17 +426,32 @@
-
+
- generate-auth-client
+ generate-auth-v1-client
generate
${openapi.spec.dir}/auth/v1.yaml
- nl.reinkrul.nuts.auth
- nl.reinkrul.nuts.auth
+ nl.reinkrul.nuts.auth.v1
+ nl.reinkrul.nuts.auth.v1
+ nl.reinkrul.nuts
+
+
+
+
+ generate-auth-v2-client
+
+ generate
+
+
+ ${openapi.spec.dir}/auth/v2.yaml
+
+ nl.reinkrul.nuts.auth.v2
+ nl.reinkrul.nuts.auth.v2
+ nl.reinkrul.nuts
diff --git a/specs/credentials.yaml b/specs/credentials.yaml
deleted file mode 100644
index dfd8794..0000000
--- a/specs/credentials.yaml
+++ /dev/null
@@ -1,143 +0,0 @@
-openapi: 3.0.0
-info:
- title: Additional OpenAPI spec for interacting with the Nuts APIs.
- version: 1.0.0
-
-components:
- schemas:
- ID:
- type: string
- description: Identifies the subject of the credential. In other words, the ID of the entity to which the credential was issued. Generally a Nuts DID.
- Organization:
- type: object
- properties:
- "name":
- description: The name of the organization.
- type: string
- "city":
- description: The city of the organization.
- type: string
- NutsOrganizationCredential:
- type: object
- description: The subject of a NutsOrganizationCredential according to the Nuts specs.
- required:
- - "id"
- - "organization"
- properties:
- "id":
- $ref: '#/components/schemas/ID'
- "organization":
- $ref: '#/components/schemas/Organization'
- NutsAuthorizationCredential:
- type: object
- description: The subject of a NutsAuthorizationCredential according to the Nuts specs.
- required:
- - id
- - purposeOfUse
- - subject
- properties:
- "id":
- $ref: '#/components/schemas/ID'
- "purposeOfUse":
- description: Generally an access policy as defined by the Bolt.
- type: string
- "subject":
- type: string
- description: Identifier of the patient (Dutch Social Security Number).
- "resources":
- description: The FHIR resources that can be accessed using the credential.
- type: array
- items:
- $ref: '#/components/schemas/FHIRResource'
- FHIRResource:
- type: object
- required:
- - path
- - operations
- properties:
- "path":
- description: The path of the resource.
- type: string
- example: /Task/1
- "operations":
- description: The FHIR operations that are allowed on the resource.
- type: array
- items:
- type: string
- "userContext":
- description: Indicates whether access to the resource requires an authenticated user.
- type: boolean
- "assuranceLevel":
- description: The assurance level of the credential.
- type: string
- enum:
- - "low"
- - "substantial"
- - "high"
- NutsEmployeeCredential:
- type: object
- description: The subject of a NutsEmployeeCredential according to the Nuts specs.
- required:
- - "id"
- - "type"
- - "member"
- properties:
- "id":
- $ref: '#/components/schemas/ID'
- "type":
- description: The type of the employee credential subject, must be "Organization".
- example: Organization
- enum: [Organization]
- type: string
- "member":
- $ref: '#/components/schemas/OrganizationMember'
- OrganizationMember:
- type: object
- description: Part of the subject of a NutsEmployeeCredential according to the Nuts specs.
- required:
- - "identifier"
- - "type"
- - "member"
- properties:
- identifier:
- description: Organizational-wide unique identifier of the employee, e.g. a number, user name or e-mail address.
- example: 12345678
- type: string
- roleName:
- description: The role of the employee within the organization.
- type: string
- example: "Verpleegkundige niveau 2"
- type:
- description: The type of the employee credential subject, must be "EmployeeRole".
- example: EmployeeRole
- enum: [EmployeeRole]
- type: string
- member:
- $ref: '#/components/schemas/OrganizationMemberMember'
- OrganizationMemberMember:
- type: object
- description: Part of the subject of a NutsEmployeeCredential according to the Nuts specs.
- properties:
- type:
- description: The type of the employee credential subject, must be "Person".
- example: Person
- enum: [Person]
- type: string
- familyName:
- description: The family name of the employee.
- example: "Jansen"
- type: string
- initials:
- description: The initials of the employee.
- example: "A.B."
- type: string
-
-
-# Required to make it a valid OpenAPI spec (can be removed when migrating to OAS 3.1.0):
-paths:
- /bogus:
- get:
- operationId: bogus
- responses:
- "200":
- description: Bogus
\ No newline at end of file
diff --git a/src/main/java/nl/reinkrul/nuts/Configuration.java b/src/main/java/nl/reinkrul/nuts/Configuration.java
new file mode 100644
index 0000000..073c012
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/Configuration.java
@@ -0,0 +1,41 @@
+package nl.reinkrul.nuts;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import nl.reinkrul.nuts.common.*;
+
+public class Configuration {
+
+ private static ApiClient defaultApiClient = create();
+
+ /**
+ * Get the default API client, which would be used when creating API
+ * instances without providing an API client.
+ *
+ * @return Default API client
+ */
+ public static ApiClient getDefaultApiClient() {
+ return defaultApiClient;
+ }
+
+ /**
+ * Set the default API client, which would be used when creating API
+ * instances without providing an API client.
+ *
+ * @param apiClient API client
+ */
+ public static void setDefaultApiClient(ApiClient apiClient) {
+ defaultApiClient = apiClient;
+ }
+
+ public static ApiClient create() {
+ var result = new ApiClient();
+ result.setUserAgent("nuts-java-client");
+ var module = new SimpleModule();
+ module.addDeserializer(VerifiableCredential.class, new VerifiableCredentialDeserializer());
+ module.addSerializer(VerifiableCredential.class, new VerifiableCredentialSerializer());
+ module.addDeserializer(VerifiablePresentation.class, new VerifiablePresentationDeserializer());
+ module.addSerializer(VerifiablePresentation.class, new VerifiablePresentationSerializer());
+ result.getJSON().getMapper().registerModule(module);
+ return result;
+ }
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/DIDDocument.java b/src/main/java/nl/reinkrul/nuts/common/DIDDocument.java
index 757fc5f..73e7f85 100644
--- a/src/main/java/nl/reinkrul/nuts/common/DIDDocument.java
+++ b/src/main/java/nl/reinkrul/nuts/common/DIDDocument.java
@@ -1,5 +1,4 @@
package nl.reinkrul.nuts.common;
public class DIDDocument extends foundation.identity.did.DIDDocument {
-
}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiableCredential.java b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredential.java
new file mode 100644
index 0000000..fb800ff
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredential.java
@@ -0,0 +1,18 @@
+package nl.reinkrul.nuts.common;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.Map;
+
+public class VerifiableCredential extends com.danubetech.verifiablecredentials.VerifiableCredential {
+ public VerifiableCredential(Map jsonObject, String source) {
+ super(jsonObject);
+ this.source = source;
+ }
+
+ /**
+ * The raw source of the credential, as it was deserialized.
+ */
+ public final String source;
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializer.java b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializer.java
new file mode 100644
index 0000000..5470370
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializer.java
@@ -0,0 +1,42 @@
+package nl.reinkrul.nuts.common;
+
+import com.danubetech.verifiablecredentials.jwt.JwtVerifiableCredential;
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+
+public class VerifiableCredentialDeserializer extends StdDeserializer {
+
+ public VerifiableCredentialDeserializer() {
+ super(VerifiableCredential.class);
+ }
+
+ @Override
+ public VerifiableCredential deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
+ if (jsonParser.getCurrentToken().isScalarValue()) {
+ try {
+ final String valueAsString = jsonParser.getValueAsString();
+ JwtVerifiableCredential jwtVC = JwtVerifiableCredential.fromCompactSerialization(valueAsString);
+ Map claims = jwtVC.getPayload().getClaims();
+ VerifiableCredential vc = new VerifiableCredential(jwtVC.getPayloadObject().getJsonObject(), "\"" + valueAsString + "\"");
+ if (vc.getId() == null) {
+ // Take from jti claim
+ vc.setJsonObjectKeyValue("id", (String) claims.get("jti"));
+ }
+ return vc;
+ } catch (ParseException e) {
+ throw new IOException(e);
+ }
+ }
+ if (jsonParser.getCurrentToken().isStructStart()) {
+ var asString = jsonParser.readValueAsTree().toString();
+ return new VerifiableCredential(VerifiableCredential.fromJson(asString).getJsonObject(), asString);
+ }
+ throw new IOException("Unexpected token: " + jsonParser.getCurrentToken().toString());
+ }
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialSerializer.java b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialSerializer.java
new file mode 100644
index 0000000..5b2c675
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiableCredentialSerializer.java
@@ -0,0 +1,28 @@
+package nl.reinkrul.nuts.common;
+
+import com.danubetech.verifiablecredentials.jwt.JwtVerifiableCredential;
+import com.fasterxml.jackson.core.JacksonException;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+public class VerifiableCredentialSerializer extends StdSerializer {
+
+ public VerifiableCredentialSerializer() {
+ super(VerifiableCredential.class);
+ }
+
+ @Override
+ public void serialize(VerifiableCredential value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ if ("".equals(value.source)) {
+ throw new IOException("Can only unmarshal VerifiableCredential which has been deserialized earlier.");
+ }
+ gen.writeRawValue(value.source);
+ }
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentation.java b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentation.java
new file mode 100644
index 0000000..c7a280f
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentation.java
@@ -0,0 +1,15 @@
+package nl.reinkrul.nuts.common;
+
+import java.util.Map;
+
+public class VerifiablePresentation extends com.danubetech.verifiablecredentials.VerifiablePresentation {
+ public VerifiablePresentation(Map jsonObject, String source) {
+ super(jsonObject);
+ this.source = source;
+ }
+
+ /**
+ * The raw source of the presentation, as it was deserialized.
+ */
+ public final String source;
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializer.java b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializer.java
new file mode 100644
index 0000000..1f9b821
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializer.java
@@ -0,0 +1,36 @@
+package nl.reinkrul.nuts.common;
+
+import com.danubetech.verifiablecredentials.jwt.JwtVerifiablePresentation;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import java.io.IOException;
+import java.text.ParseException;
+
+public class VerifiablePresentationDeserializer extends StdDeserializer {
+
+ public VerifiablePresentationDeserializer() {
+ super(VerifiablePresentation.class);
+ }
+
+ @Override
+ public VerifiablePresentation deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+ if (jsonParser.getCurrentToken().isScalarValue()) {
+ var valueAsString = jsonParser.getValueAsString();
+ try {
+ return new VerifiablePresentation(JwtVerifiablePresentation.fromCompactSerialization(valueAsString).getPayloadObject().getJsonObject(), valueAsString);
+ } catch (ParseException e) {
+ throw new IOException(e);
+ }
+ }
+ if (jsonParser.getCurrentToken().isStructStart()) {
+ // Parse as JSON-LD
+ var valueAsString = jsonParser.readValueAsTree().toString();
+ return new VerifiablePresentation(com.danubetech.verifiablecredentials.VerifiablePresentation.fromJson(valueAsString).getJsonObject(), valueAsString);
+
+ }
+ throw new IOException("Unexpected token: " + jsonParser.getCurrentToken().id());
+ }
+}
diff --git a/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationSerializer.java b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationSerializer.java
new file mode 100644
index 0000000..927a5cf
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/common/VerifiablePresentationSerializer.java
@@ -0,0 +1,22 @@
+package nl.reinkrul.nuts.common;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+import java.io.IOException;
+
+public class VerifiablePresentationSerializer extends StdSerializer {
+
+ public VerifiablePresentationSerializer() {
+ super(VerifiablePresentation.class);
+ }
+
+ @Override
+ public void serialize(VerifiablePresentation value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ if ("".equals(value.source)) {
+ throw new IOException("Can only unmarshal VerifiablePresentation which has been deserialized earlier.");
+ }
+ gen.writeRawValue(value.source);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/nl/reinkrul/nuts/credentials/Context.java b/src/main/java/nl/reinkrul/nuts/credentials/Context.java
new file mode 100644
index 0000000..7d4b04a
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/credentials/Context.java
@@ -0,0 +1,9 @@
+package nl.reinkrul.nuts.credentials;
+
+import java.net.URI;
+
+public final class Context {
+ public static URI VerifiableCredentialV1 = URI.create("https://www.w3.org/2018/credentials/v1");
+ public static URI NutsV1 = URI.create("https://nuts.nl/credentials/v1");
+ public static URI Nuts2024 = URI.create("https://nuts.nl/credentials/2024");
+}
diff --git a/src/main/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredential.java b/src/main/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredential.java
new file mode 100644
index 0000000..553d627
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredential.java
@@ -0,0 +1,51 @@
+package nl.reinkrul.nuts.credentials;
+
+import com.danubetech.verifiablecredentials.CredentialSubject;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import nl.reinkrul.nuts.common.VerifiableCredential;
+
+import java.net.URI;
+import java.util.Map;
+
+public class NutsEmployeeCredential {
+ public final String identifier;
+ public final String initials;
+ public final String name;
+ public final String role;
+
+ public NutsEmployeeCredential(String identifier, String initials, String name, String role) {
+ this.identifier = identifier;
+ this.initials = initials;
+ this.name = name;
+ this.role = role;
+ }
+
+ public VerifiableCredential getCredential() {
+ var inner = com.danubetech.verifiablecredentials.VerifiableCredential.builder()
+ .context(Context.NutsV1)
+ .type("NutsEmployeeCredential")
+ .credentialSubject(CredentialSubject
+ .builder()
+ .type("Organization")
+ .claims(Map.of("member", Map.of(
+ "identifier", identifier,
+ "type", "EmployeeRole",
+ "roleName", role,
+ "member", Map.of(
+ "type", "Person",
+ "initials", initials,
+ "familyName", name
+ )
+ )))
+ .build())
+ .build();
+ final String source;
+ try {
+ source = new ObjectMapper().writeValueAsString(inner);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ return new VerifiableCredential(inner.getJsonObject(), source);
+ }
+}
diff --git a/src/main/java/nl/reinkrul/nuts/discovery/DiscoveryApi.java b/src/main/java/nl/reinkrul/nuts/discovery/DiscoveryApi.java
new file mode 100644
index 0000000..2533432
--- /dev/null
+++ b/src/main/java/nl/reinkrul/nuts/discovery/DiscoveryApi.java
@@ -0,0 +1,47 @@
+package nl.reinkrul.nuts.discovery;
+
+import jakarta.ws.rs.core.GenericType;
+import nl.reinkrul.nuts.ApiClient;
+import nl.reinkrul.nuts.ApiException;
+import nl.reinkrul.nuts.ApiResponse;
+import nl.reinkrul.nuts.Pair;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class DiscoveryApi extends BaseDiscoveryApi {
+
+ private final ApiClient apiClient;
+
+ public DiscoveryApi(ApiClient apiClient) {
+ super(apiClient);
+ this.apiClient = apiClient;
+ }
+
+ public ApiResponse> searchPresentationsWithHttpInfo(String serviceID, Map query) throws ApiException {
+ // Check required parameters
+ if (serviceID == null) {
+ throw new ApiException(400, "Missing the required parameter 'serviceID' when calling searchPresentations");
+ }
+
+ // Path parameters
+ String localVarPath = "/internal/discovery/v1/{serviceID}"
+ .replaceAll("\\{serviceID}", apiClient.escapeString(serviceID));
+
+ // Query parameters
+ // FIX: free form query parameters (style: form, explode: true) generates invalid query parameters
+ // when using Jersey (ends up being {key=value}). It's fixed for Okhttp (https://github.com/OpenAPITools/openapi-generator/issues/19225),
+ // but not yet for Jersey.
+ List localVarQueryParams = query.entrySet().stream().map(entry -> new Pair(entry.getKey(), entry.getValue())).collect(Collectors.toList());
+
+ String localVarAccept = apiClient.selectHeaderAccept("application/json", "application/problem+json");
+ String localVarContentType = apiClient.selectHeaderContentType();
+ String[] localVarAuthNames = new String[] {"jwtBearerAuth"};
+ GenericType> localVarReturnType = new GenericType>() {};
+ return apiClient.invokeAPI("DiscoveryApi.searchPresentations", localVarPath, "GET", localVarQueryParams, null,
+ new LinkedHashMap<>(), new LinkedHashMap<>(), new LinkedHashMap<>(), localVarAccept, localVarContentType,
+ localVarAuthNames, localVarReturnType, false);
+ }
+}
diff --git a/src/test/java/CredentialExamples.java b/src/test/java/CredentialExamples.java
deleted file mode 100644
index b857d8b..0000000
--- a/src/test/java/CredentialExamples.java
+++ /dev/null
@@ -1,109 +0,0 @@
-import nl.reinkrul.nuts.ApiException;
-import nl.reinkrul.nuts.common.VerifiableCredential;
-import nl.reinkrul.nuts.extra.*;
-import nl.reinkrul.nuts.vcr.*;
-
-import java.util.List;
-
-public class CredentialExamples {
-
- public void searchNutsOrganizationCredential() throws ApiException {
- var client = new CredentialApi();
- var issuedVC = client.searchVCs(new SearchVCRequest()
- // Search options
- .searchOptions(new SearchOptions().allowUntrustedIssuer(false))
- // VC to match
- .query(new VerifiableCredential()
- .type(List.of("VerifiableCredential", "NutsOrganizationCredential"))
- .atContext(List.of("https://nuts.nl/credentials/v1", "https://www.w3.org/2018/credentials/v1"))
- .issuer("did:nuts:some-did") // the DID of the issuer of the credential (DID of software vendor)
- .credentialSubject(
- new NutsOrganizationCredential()
- .organization(new nl.reinkrul.nuts.extra.Organization()
- .name("Extra Careful B.V.")
- .city("Zorgdorp")
- ))
- ));
-
- for (SearchVCResult result : issuedVC.getVerifiableCredentials()) {
- System.out.println(result.getVerifiableCredential().getId());
- }
- }
-
- public void issueNutsOrganizationCredential() throws ApiException {
- var client = new CredentialApi();
- var issuedVC = client.issueVC(new IssueVCRequest()
- // General VC properties
- .type("NutsOrganizationCredential")
- .issuer("did:nuts:some-did") // the DID of the issuer of the credential (DID of software vendor)
- .visibility(IssueVCRequest.VisibilityEnum.PUBLIC) // publish on network, anyone can read it
- // Subject of the credential
- .credentialSubject(
- new NutsOrganizationCredential()
- .id("did:nuts:some-other-did") // the DID of the receiver of the credential
- .organization(new nl.reinkrul.nuts.extra.Organization()
- .name("Extra Careful B.V.")
- .city("Zorgdorp")
- )
- )
- );
-
- System.out.println("VC has been issued, ID: " + issuedVC.getId());
- }
-
- public void issueNutsAuthorizationCredential() throws ApiException {
- var client = new CredentialApi();
- var issuedVC = client.issueVC(new IssueVCRequest()
- // General VC properties
- .type("NutsAuthenticationCredential")
- .issuer("did:nuts:some-did") // the DID of the issuer of the credential
- .visibility(IssueVCRequest.VisibilityEnum.PRIVATE) // publish on network, but keep it private
- // Subject of the credential
- .credentialSubject(
- new NutsAuthorizationCredential()
- .id("did:nuts:some-other-did") // the DID of the receiver of the credential
- .subject("1234567890") // social security number of the patient
- .addResourcesItem(
- // The FHIR resources that can be accessed using the credential
- new FHIRResource()
- .path("/Task/1")
- .userContext(true)
- .assuranceLevel(FHIRResource.AssuranceLevelEnum.LOW)
- .addOperationsItem("read")
- )
- )
- );
-
- System.out.println("VC has been issued, ID: " + issuedVC.getId());
- }
-
- public void issueNutsEmployeeCredential() throws ApiException {
- var client = new CredentialApi();
- var issuedVC = client.issueVC(new IssueVCRequest()
- // General VC properties
- .type("NutsEmployeeCredential")
- .issuer("did:nuts:some-did") // the DID of the issuer of the credential
- // Subject of the credential
- .credentialSubject(
- new NutsEmployeeCredential()
- .id("did:nuts:some-did") // the DID of the receiver of the credential, equal to the issuer for NutsEmployeeCredential
- .type(NutsEmployeeCredential.TypeEnum.ORGANIZATION) // hardcoded
- .member(
- new OrganizationMember()
- .identifier("12345678")
- .type(OrganizationMember.TypeEnum.EMPLOYEEROLE) // hardcoded
- .roleName("Verpleegkundige niveau 2") // optional
- .member(
- new OrganizationMemberMember()
- .type(OrganizationMemberMember.TypeEnum.PERSON) // hardcoded
- .initials("A.B.")
- .familyName("van der Zorg")
- )
-
- )
- )
- );
-
- System.out.println("VC has been issued, ID: " + issuedVC.getId());
- }
-}
diff --git a/src/test/java/nl/reinkrul/nuts/AuthApiTest.java b/src/test/java/nl/reinkrul/nuts/AuthApiTest.java
deleted file mode 100644
index e7dfdf8..0000000
--- a/src/test/java/nl/reinkrul/nuts/AuthApiTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package nl.reinkrul.nuts;
-
-import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpServer;
-import nl.reinkrul.nuts.auth.AuthApi;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-public class AuthApiTest {
-
- private static HttpServer server;
-
- private static Headers actualRequestHeaders;
- private static byte[] actualRequestBody;
-
- @BeforeAll
- public static void setup() throws IOException {
- int port;
- try (ServerSocket serverSocket = new ServerSocket(0)) {
- port = serverSocket.getLocalPort();
- }
-
- server = HttpServer.create(new InetSocketAddress("localhost", port), 10);
- server.createContext("/internal/auth/v1/accesstoken/introspect", exchange -> {
- actualRequestHeaders = exchange.getRequestHeaders();
- actualRequestBody = exchange.getRequestBody().readAllBytes();
- exchange.sendResponseHeaders(200, 0);
- exchange.getResponseBody().write("{\"sub\": \"admin\"}".getBytes());
- exchange.close();
- }
- );
- server.setExecutor(null);
- server.start();
- }
-
- @AfterAll
- public static void tearDown() {
- server.stop(0);
- }
-
- @Test
- public void introspectAccessToken() throws ApiException {
- var client = new ApiClient();
- client.updateBaseUri("http://" + server.getAddress().getHostName() + ":" + server.getAddress().getPort());
- var authApi = new AuthApi(client);
- var response = authApi.introspectAccessToken("some-token");
-
- assertEquals("application/x-www-form-urlencoded; charset=UTF-8", actualRequestHeaders.getFirst("Content-Type"));
- assertEquals("token=some-token", new String(actualRequestBody));
- assertEquals("admin", response.getSub());
- }
-}
diff --git a/src/test/java/nl/reinkrul/nuts/IntegrationTest.java b/src/test/java/nl/reinkrul/nuts/IntegrationTest.java
new file mode 100644
index 0000000..30d8135
--- /dev/null
+++ b/src/test/java/nl/reinkrul/nuts/IntegrationTest.java
@@ -0,0 +1,102 @@
+package nl.reinkrul.nuts;
+
+import nl.reinkrul.nuts.auth.v2.AuthApi;
+import nl.reinkrul.nuts.auth.v2.ServiceAccessTokenRequest;
+import nl.reinkrul.nuts.auth.v2.TokenIntrospectionResponse;
+import nl.reinkrul.nuts.auth.v2.TokenResponse;
+import nl.reinkrul.nuts.credentials.NutsEmployeeCredential;
+import nl.reinkrul.nuts.discovery.DiscoveryApi;
+import nl.reinkrul.nuts.discovery.ServiceActivationRequest;
+import nl.reinkrul.nuts.vcr.CredentialApi;
+import nl.reinkrul.nuts.vcr.IssueVCRequest;
+import nl.reinkrul.nuts.vcr.IssueVCRequestContext;
+import nl.reinkrul.nuts.vcr.IssueVCRequestType;
+import nl.reinkrul.nuts.vdr.CreateSubjectOptions;
+import nl.reinkrul.nuts.vdr.SubjectApi;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+// To run this integration test, start the Docker Compose file in the nutsnode directory.
+public class IntegrationTest {
+
+ //@Test
+ public void createSubjectIssueVC() throws ApiException {
+ var apiClient = Configuration.getDefaultApiClient();
+ apiClient.setBasePath("http://localhost:8081");
+
+ var subjectApi = new SubjectApi(apiClient);
+ var authApi = new AuthApi(apiClient);
+ var discoveryApi = new DiscoveryApi(apiClient);
+
+ // Admin: Create subject
+ var subjectCreationResult = subjectApi.createSubject(new CreateSubjectOptions());
+ System.out.println(subjectCreationResult.getSubject());
+ var subjectDID = subjectCreationResult.getDocuments().get(0).getId();
+ var credentialApi = new CredentialApi(apiClient);
+
+ // Admin: Issue VC
+ var nutsUraCredential = credentialApi.issueVC(new IssueVCRequest()
+ .atContext(new IssueVCRequestContext("https://nuts.nl/credentials/2024"))
+ .type(new IssueVCRequestType("NutsUraCredential"))
+ .issuer(subjectDID.toString())
+ .withStatusList2021Revocation(false)
+ .publishToNetwork(null)
+ .visibility(null)
+ .format(IssueVCRequest.FormatEnum.JWT_VC)
+ .credentialSubject(
+ Map.of(
+ "id", subjectDID.toString(),
+ "organization", Map.of(
+ "ura", "12345",
+ "name", "Extra Careful B.V.",
+ "city", "Zorgdorp"
+ )
+ )
+ )
+ );
+ Assertions.assertNotNull(nutsUraCredential.getId());
+
+ // Load it into wallet
+ var subjectID = subjectCreationResult.getSubject();
+ credentialApi.loadVC(subjectID, nutsUraCredential);
+
+ // List it
+ var vcs = credentialApi.getCredentialsInWallet(subjectID);
+ Assertions.assertEquals(1, vcs.size());
+ Assertions.assertEquals(nutsUraCredential.source, vcs.get(0).source);
+
+ // Application: Request Access Token
+ var employeeCredential = new NutsEmployeeCredential(
+ "12345",
+ "E",
+ "Careful",
+ "Caregiver"
+ );
+ TokenResponse accessTokenResponse = authApi.requestServiceAccessToken(subjectID, new ServiceAccessTokenRequest()
+ .tokenType(ServiceAccessTokenRequest.TokenTypeEnum.DPOP)
+ .addCredentialsItem(employeeCredential.getCredential())
+ .scope("test")
+ .authorizationServer("http://localhost:8080/oauth2/" + subjectID)
+ );
+
+ // PEP: Check access token
+ TokenIntrospectionResponse tokenIntrospectionResponse = authApi.introspectAccessToken(accessTokenResponse.getAccessToken());
+ Assertions.assertTrue(tokenIntrospectionResponse.getActive());
+ Assertions.assertEquals("12345", tokenIntrospectionResponse.getAdditionalProperty("organization_ura"));
+ Assertions.assertEquals(employeeCredential.identifier, tokenIntrospectionResponse.getAdditionalProperty("employee_identifier"));
+ Assertions.assertEquals(employeeCredential.role, tokenIntrospectionResponse.getAdditionalProperty("employee_role"));
+ Assertions.assertEquals(employeeCredential.initials, tokenIntrospectionResponse.getAdditionalProperty("employee_initials"));
+ Assertions.assertEquals(employeeCredential.name, tokenIntrospectionResponse.getAdditionalProperty("employee_name"));
+
+ // Application: Register on Discovery Service
+ discoveryApi.activateServiceForSubject("test", subjectID, new ServiceActivationRequest()
+ .registrationParameters(Map.of("fhir-url", "https://example.com/fhir"))
+ );
+
+ // Application: Search on Discovery Service
+ var services = discoveryApi.searchPresentations("test", Map.of("credentialSubject.organization.ura", "12345"));
+ Assertions.assertNotEquals(0, services.size());
+ }
+}
diff --git a/src/test/java/nl/reinkrul/nuts/MarshalTest.java b/src/test/java/nl/reinkrul/nuts/MarshalTest.java
deleted file mode 100644
index ebdabe3..0000000
--- a/src/test/java/nl/reinkrul/nuts/MarshalTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package nl.reinkrul.nuts;
-
-import com.sun.net.httpserver.HttpServer;
-import nl.reinkrul.nuts.vdr.DidApi;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class MarshalTest {
-
- private static HttpServer server;
-
- @BeforeAll
- public static void setup() throws IOException {
- int port;
- try (ServerSocket serverSocket = new ServerSocket(0)) {
- port = serverSocket.getLocalPort();
- }
-
- server = HttpServer.create(new InetSocketAddress("localhost", port), 10);
- server.createContext("/internal/vdr/v1/did/did:nuts:abcdefghijklmnop", exchange -> {
- exchange.sendResponseHeaders(200, 0);
- try (InputStream inputStream = MarshalTest.class.getResourceAsStream("/did-resolution-result.json")) {
- exchange.getResponseBody().write(inputStream.readAllBytes());
- }
- exchange.close();
- }
- );
- server.setExecutor(null);
- server.start();
- }
-
- @AfterAll
- public static void tearDown() {
- server.stop(0);
- }
-
- @Test
- public void didDocument() throws ApiException {
- var apiClient = new ApiClient();
- apiClient.updateBaseUri("http://localhost:" + server.getAddress().getPort());
- var didApi = new DidApi(apiClient);
-
- var result = didApi.getDID("did:nuts:abcdefghijklmnop", null, null);
-
- assertEquals("did:nuts:abcdefghijklmnop", result.getDocument().getId().toString());
- }
-}
diff --git a/src/test/java/nl/reinkrul/nuts/NutsEmployeeCredentialTest.java b/src/test/java/nl/reinkrul/nuts/NutsEmployeeCredentialTest.java
deleted file mode 100644
index 53cb5e0..0000000
--- a/src/test/java/nl/reinkrul/nuts/NutsEmployeeCredentialTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package nl.reinkrul.nuts;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import nl.reinkrul.nuts.extra.NutsEmployeeCredential;
-import nl.reinkrul.nuts.extra.OrganizationMember;
-import nl.reinkrul.nuts.extra.OrganizationMemberMember;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.Test;
-
-public class NutsEmployeeCredentialTest {
-
- @Test
- public void testMarshalling() throws JsonProcessingException {
- var credential = new NutsEmployeeCredential()
- .id("did:nuts:some-did") // the DID of the receiver of the credential, equal to the issuer for NutsEmployeeCredential
- .type(NutsEmployeeCredential.TypeEnum.ORGANIZATION) // hardcoded
- .member(
- new OrganizationMember()
- .identifier("12345678")
- .type(OrganizationMember.TypeEnum.EMPLOYEEROLE) // hardcoded
- .roleName("Verpleegkundige niveau 2") // optional
- .member(
- new OrganizationMemberMember()
- .type(OrganizationMemberMember.TypeEnum.PERSON) // hardcoded
- .initials("A.B.")
- .familyName("van der Zorg")
- )
-
- );
-
- var expected = "{\"id\":\"did:nuts:some-did\",\"type\":\"Organization\",\"member\":{\"identifier\":\"12345678\",\"roleName\":\"Verpleegkundige niveau 2\",\"type\":\"EmployeeRole\",\"member\":{\"type\":\"Person\",\"familyName\":\"van der Zorg\",\"initials\":\"A.B.\"}}}";
- var actual = new ObjectMapper().writeValueAsString(credential);
-
- Assertions.assertEquals(expected, actual);
- }
-}
diff --git a/src/test/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializerTest.java b/src/test/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializerTest.java
new file mode 100644
index 0000000..4aebf96
--- /dev/null
+++ b/src/test/java/nl/reinkrul/nuts/common/VerifiableCredentialDeserializerTest.java
@@ -0,0 +1,75 @@
+package nl.reinkrul.nuts.common;
+
+import com.danubetech.verifiablecredentials.VerifiableCredential;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class VerifiableCredentialDeserializerTest {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public VerifiableCredentialDeserializerTest() {
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(nl.reinkrul.nuts.common.VerifiableCredential.class, new VerifiableCredentialDeserializer());
+ mapper.registerModule(module);
+ }
+
+ @Test
+ void deserializeJWT() throws IOException {
+ var jwt = "\"eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6bnV0cy5ubDppYW06YmRhMDI0ZTMtYjk0My00ZDRkLTk0MjQtYTEwNjM3NmE1YmM3IzY4ODA2ZjU1LWVkMzEtNGQ3Yy1hNmMxLWJkYjc1YzIxMTc0NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOm51dHMubmw6aWFtOmJkYTAyNGUzLWI5NDMtNGQ0ZC05NDI0LWExMDYzNzZhNWJjNyIsImp0aSI6ImRpZDp3ZWI6bnV0cy5ubDppYW06YmRhMDI0ZTMtYjk0My00ZDRkLTk0MjQtYTEwNjM3NmE1YmM3IzlhMDRkMWM0LWE4YTItNDg1Zi1hMWJmLWVkNGVhMTQyNTdmOSIsIm5iZiI6MTcyNzQ0Mjg0Mywic3ViIjoiZGlkOndlYjpudXRzLm5sOmlhbTpiZGEwMjRlMy1iOTQzLTRkNGQtOTQyNC1hMTA2Mzc2YTViYzciLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vbnV0cy5ubC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSJdLCJjcmVkZW50aWFsU3RhdHVzIjpbeyJpZCI6Imh0dHBzOi8vbnV0cy5ubC9zdGF0dXNsaXN0L2RpZDp3ZWI6bnV0cy5ubDppYW06YmRhMDI0ZTMtYjk0My00ZDRkLTk0MjQtYTEwNjM3NmE1YmM3LzEjMCIsInR5cGUiOiJTdGF0dXNMaXN0MjAyMUVudHJ5Iiwic3RhdHVzUHVycG9zZSI6InJldm9jYXRpb24iLCJzdGF0dXNMaXN0SW5kZXgiOiIwIiwic3RhdHVzTGlzdENyZWRlbnRpYWwiOiJodHRwczovL251dHMubmwvc3RhdHVzbGlzdC9kaWQ6d2ViOm51dHMubmw6aWFtOmJkYTAyNGUzLWI5NDMtNGQ0ZC05NDI0LWExMDYzNzZhNWJjNy8xIn1dLCJjcmVkZW50aWFsU3ViamVjdCI6W3siaWQiOiJkaWQ6d2ViOm51dHMubmw6aWFtOmJkYTAyNGUzLWI5NDMtNGQ0ZC05NDI0LWExMDYzNzZhNWJjNyIsImlkZW50aWZpZXIiOiIxMjM0IiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZU5hbWUiOiJTb2Z0d2FyZSBFbmdpbmVlciJ9XSwidHlwZSI6WyJFbXBsb3llZUNyZWRlbnRpYWwiLCJWZXJpZmlhYmxlQ3JlZGVudGlhbCJdfX0.SglTXKcIhYFXlXyMEn8hBidoG7Ho21_t6pvl4CC-UYWuQyZZs6kjgBgtgV6dt_iwHqx3CqRQKuNJGwK48L6k8g\"";
+ var result = mapper.readValue(jwt, nl.reinkrul.nuts.common.VerifiableCredential.class);
+ assertEquals(jwt, result.source);
+ assertEquals("did:web:nuts.nl:iam:bda024e3-b943-4d4d-9424-a106376a5bc7#9a04d1c4-a8a2-485f-a1bf-ed4ea14257f9", result.getId().toString());
+ }
+
+ @Test
+ void deserializeJSONLD() {
+ var raw = """
+{
+ "@context": [
+ "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
+ "https://www.w3.org/2018/credentials/v1",
+ "https://nuts.nl/credentials/2024",
+ "https://w3id.org/vc/status-list/2021/v1"
+ ],
+ "credentialStatus": {
+ "id": "https://zorgbijjou.test.integration.zorgbijjou.com/nuts/statuslist/did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:4f6e2a8b-fb82-4b2e-aae7-283060d05167/1#1",
+ "statusListCredential": "https://zorgbijjou.test.integration.zorgbijjou.com/nuts/statuslist/did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:4f6e2a8b-fb82-4b2e-aae7-283060d05167/1",
+ "statusListIndex": "1",
+ "statusPurpose": "revocation",
+ "type": "StatusList2021Entry"
+ },
+ "credentialSubject": {
+ "id": "did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:914ab62d-9ae4-4dd0-bf76-022c7bec0f6a",
+ "organization": {
+ "city": "111",
+ "name": "11111",
+ "ura": "111"
+ }
+ },
+ "expirationDate": "2025-09-27T14:27:27.023Z",
+ "id": "did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:4f6e2a8b-fb82-4b2e-aae7-283060d05167#9475dfac-1581-41e4-97e4-bdd81b65945e",
+ "issuanceDate": "2024-09-27T14:27:27.113601538Z",
+ "issuer": "did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:4f6e2a8b-fb82-4b2e-aae7-283060d05167",
+ "proof": {
+ "created": "2024-09-27T14:27:27.113601538Z",
+ "jws": "eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6ImRpZDp3ZWI6em9yZ2JpampvdS50ZXN0LmludGVncmF0aW9uLnpvcmdiaWpqb3UuY29tOm51dHM6aWFtOjRmNmUyYThiLWZiODItNGIyZS1hYWU3LTI4MzA2MGQwNTE2NyMxNjczMjYwNC1lYzI2LTQ4YTctODI4OC0xYTJmMTUyOGY1MjIifQ..ovZs_rJrW6ScqNlekJpelOmpf2nD9Ak2q_unTdNZVj5602VZkVik6KOrGf7JBNkRQlHGnetd2auCUQtwhJ7yEg",
+ "proofPurpose": "assertionMethod",
+ "type": "JsonWebSignature2020",
+ "verificationMethod": "did:web:zorgbijjou.test.integration.zorgbijjou.com:nuts:iam:4f6e2a8b-fb82-4b2e-aae7-283060d05167#16732604-ec26-48a7-8288-1a2f1528f522"
+ },
+ "type": [
+ "NutsUraCredential",
+ "VerifiableCredential"
+ ]
+}
+""";
+ assertDoesNotThrow(() -> mapper.readValue(raw, nl.reinkrul.nuts.common.VerifiableCredential.class));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializerTest.java b/src/test/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializerTest.java
new file mode 100644
index 0000000..532940a
--- /dev/null
+++ b/src/test/java/nl/reinkrul/nuts/common/VerifiablePresentationDeserializerTest.java
@@ -0,0 +1,93 @@
+package nl.reinkrul.nuts.common;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class VerifiablePresentationDeserializerTest {
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public VerifiablePresentationDeserializerTest() {
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(nl.reinkrul.nuts.common.VerifiablePresentation.class, new VerifiablePresentationDeserializer());
+ mapper.registerModule(module);
+ }
+
+ @Test
+ void deserializeJWT() throws JsonProcessingException {
+ var jwt = "\"eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDp3ZWI6bG9jYWxob3N0JTNBODA4MDppYW06NDI1MDA4ZTQtZWE2Zi00ZWQyLTljZDItNTUzMDlhM2E1MjA1I2U5YTIyMTQzLTQ3NjktNGI5Mi04MzQ3LWEwMGEwMzUwMzdlMSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdCJdLCJleHAiOjE3MzAyNjQxNjgsImp0aSI6ImRpZDp3ZWI6bG9jYWxob3N0JTNBODA4MDppYW06NDI1MDA4ZTQtZWE2Zi00ZWQyLTljZDItNTUzMDlhM2E1MjA1Izc1Yjg4YmYxLWZlM2ItNDI4MC1hYjJiLWY5NDE1MjY0MWY5OCIsIm5iZiI6MTcyNzQ5OTM2OSwibm9uY2UiOiJ0eHhkSXVnQkpRa3ByQTNoNGtEWHdLbmllNzZFMENwb3gzOFdxekRrd3pzIiwic3ViIjoiZGlkOndlYjpsb2NhbGhvc3QlM0E4MDgwOmlhbTo0MjUwMDhlNC1lYTZmLTRlZDItOWNkMi01NTMwOWEzYTUyMDUiLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJob2xkZXIiOiJkaWQ6d2ViOmxvY2FsaG9zdCUzQTgwODA6aWFtOjQyNTAwOGU0LWVhNmYtNGVkMi05Y2QyLTU1MzA5YTNhNTIwNSIsInR5cGUiOiJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIiwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlt7IkBjb250ZXh0IjpbImh0dHBzOi8vdzNjLWNjZy5naXRodWIuaW8vbGRzLWp3czIwMjAvY29udGV4dHMvbGRzLWp3czIwMjAtdjEuanNvbiIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9udXRzLm5sL2NyZWRlbnRpYWxzLzIwMjQiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6d2ViOmxvY2FsaG9zdCUzQTgwODA6aWFtOjQyNTAwOGU0LWVhNmYtNGVkMi05Y2QyLTU1MzA5YTNhNTIwNSIsIm9yZ2FuaXphdGlvbiI6eyJjaXR5IjoiWm9yZ2RvcnAiLCJuYW1lIjoiRXh0cmEgQ2FyZWZ1bCBCLlYuIiwidXJhIjoiMTIzNDUifX0sImlkIjoiZGlkOndlYjpsb2NhbGhvc3QlM0E4MDgwOmlhbTo0MjUwMDhlNC1lYTZmLTRlZDItOWNkMi01NTMwOWEzYTUyMDUjNzQ3MzVlMWItYzIyYi00ODVlLWIxNTYtZTk0MmQ4YzhkYmZmIiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wOS0yOFQwNDo1NjowOS4xOTczNzAzOFoiLCJpc3N1ZXIiOiJkaWQ6d2ViOmxvY2FsaG9zdCUzQTgwODA6aWFtOjQyNTAwOGU0LWVhNmYtNGVkMi05Y2QyLTU1MzA5YTNhNTIwNSIsInByb29mIjp7ImNyZWF0ZWQiOiIyMDI0LTA5LTI4VDA0OjU2OjA5LjE5NzM3MDM4WiIsImp3cyI6ImV5SmhiR2NpT2lKRlV6STFOaUlzSW1JMk5DSTZabUZzYzJVc0ltTnlhWFFpT2xzaVlqWTBJbDBzSW10cFpDSTZJbVJwWkRwM1pXSTZiRzlqWVd4b2IzTjBKVE5CT0RBNE1EcHBZVzA2TkRJMU1EQTRaVFF0WldFMlppMDBaV1F5TFRsalpESXROVFV6TURsaE0yRTFNakExSTJVNVlUSXlNVFF6TFRRM05qa3ROR0k1TWkwNE16UTNMV0V3TUdFd016VXdNemRsTVNKOS4uNVljZ3ZpUjNsU3RrUG9QdlplcjAtR0ZoYTc2UjVmUHlqZ0xlSzBSYUw0cWFOZElZVFhfN3ZNbkNjUEdTbG9YZ0xSSnpobTB6SFBPR215cllhOGFLbFEiLCJwcm9vZlB1cnBvc2UiOiJhc3NlcnRpb25NZXRob2QiLCJ0eXBlIjoiSnNvbldlYlNpZ25hdHVyZTIwMjAiLCJ2ZXJpZmljYXRpb25NZXRob2QiOiJkaWQ6d2ViOmxvY2FsaG9zdCUzQTgwODA6aWFtOjQyNTAwOGU0LWVhNmYtNGVkMi05Y2QyLTU1MzA5YTNhNTIwNSNlOWEyMjE0My00NzY5LTRiOTItODM0Ny1hMDBhMDM1MDM3ZTEifSwidHlwZSI6WyJOdXRzVXJhQ3JlZGVudGlhbCIsIlZlcmlmaWFibGVDcmVkZW50aWFsIl19LHsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJodHRwczovL251dHMubmwvY3JlZGVudGlhbHMvdjEiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiYXV0aFNlcnZlclVSTCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9vYXV0aDIvMTdhNjE2ODgtNGJiOS00ZjA4LTg1MWQtNTIxMmJiMTliN2JiIiwiZmhpci11cmwiOiJodHRwczovL2V4YW1wbGUuY29tL2ZoaXIiLCJpZCI6ImRpZDp3ZWI6bG9jYWxob3N0JTNBODA4MDppYW06NDI1MDA4ZTQtZWE2Zi00ZWQyLTljZDItNTUzMDlhM2E1MjA1In0sImlkIjoiZGMyNzk5MzktZjViMi00YWU4LWE3MmUtYjRhNzU4ODYzYmYxIiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wOS0yOFQwNDo1NjowOVoiLCJpc3N1ZXIiOiJkaWQ6d2ViOmxvY2FsaG9zdCUzQTgwODA6aWFtOjQyNTAwOGU0LWVhNmYtNGVkMi05Y2QyLTU1MzA5YTNhNTIwNSIsInByb29mIjpudWxsLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRGlzY292ZXJ5UmVnaXN0cmF0aW9uQ3JlZGVudGlhbCJdfV19fQ.m6H4eDhqaL2WQmrkdpsoEE2g7xeV5ZghR2hrd22GADfLfYyK2oky-SB7pdErMRiSkDKmPQXDHUWsCeM-hWPOyA\"";
+ var result = mapper.readValue(jwt, nl.reinkrul.nuts.common.VerifiablePresentation.class);
+ assertEquals(jwt, "\"" + result.source + "\"");
+ }
+
+ @Test
+ void deserializeJSONLD() {
+ var raw = """
+{
+ "@context": [
+ "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
+ "https://nuts.nl/credentials/v1",
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "proof": {
+ "challenge": "EN:PractitionerLogin:v3 I hereby declare to act on behalf of CareBears located in Caretown. This declaration is valid from Wednesday, 19 April 2023 12:20:00 until Thursday, 20 April 2023 13:20:00.",
+ "created": "2023-04-20T09:53:03Z",
+ "expires": "2023-04-24T09:53:03Z",
+ "jws": "eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6ImRpZDpudXRzOjhOWXpmc25kWkpIaDZHcXpLaVNCcHlFUnJGeHVYNjR6NnRFNXJhYTduRWptI2JZY3VldDZFSG9qTWxhTXF3Tm9DM2M2ZXRLbFVIb0o5clJ2VXUzWktFRXcifQ..IqGTyxmKgQ2HQ6RuYSn2B0sFh-okj8aEYC1VGTtlm1eiLBVr2wnnp1fX9oifhWHocuEKURkuSubENeW-Z3nMHQ",
+ "proofPurpose": "assertionMethod",
+ "type": "JsonWebSignature2020",
+ "verificationMethod": "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm#bYcuet6EHojMlaMqwNoC3c6etKlUHoJ9rRvUu3ZKEEw"
+ },
+ "type": [
+ "VerifiablePresentation",
+ "NutsSelfSignedPresentation"
+ ],
+ "verifiableCredential": [
+ {
+ "@context": [
+ "https://nuts.nl/credentials/v1",
+ "https://www.w3.org/2018/credentials/v1",
+ "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json"
+ ],
+ "credentialSubject": [
+ {
+ "id": "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm",
+ "member": {
+ "identifier": "user@example.com",
+ "member": {
+ "familyName": "Tester",
+ "initials": "T",
+ "type": "Person"
+ },
+ "roleName": "Verpleegkundige niveau 2",
+ "type": "EmployeeRole"
+ },
+ "type": "Organization"
+ }
+ ],
+ "id": "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm#dde77e76-7e3c-483f-a813-2b851a6a969c",
+ "issuanceDate": "2023-04-20T08:52:45.941461+02:00",
+ "issuer": "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm",
+ "proof": {
+ "created": "2023-04-20T09:53:03Z",
+ "expires": "2023-04-24T09:53:03Z",
+ "jws": "eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6ImRpZDpudXRzOjhOWXpmc25kWkpIaDZHcXpLaVNCcHlFUnJGeHVYNjR6NnRFNXJhYTduRWptI2JZY3VldDZFSG9qTWxhTXF3Tm9DM2M2ZXRLbFVIb0o5clJ2VXUzWktFRXcifQ..VhEbDoth8GrAni_LhZm-12VnlJToAbX0FDg1Rf7u7qIy3W54IcxAxkZP28YxGG681WpufwPeqHrtnYLsW8Fh7w",
+ "proofPurpose": "assertionMethod",
+ "type": "JsonWebSignature2020",
+ "verificationMethod": "did:nuts:8NYzfsndZJHh6GqzKiSBpyERrFxuX64z6tE5raa7nEjm#bYcuet6EHojMlaMqwNoC3c6etKlUHoJ9rRvUu3ZKEEw"
+ },
+ "type": [
+ "NutsEmployeeCredential",
+ "VerifiableCredential"
+ ]
+ }
+ ]
+}
+""";
+ assertDoesNotThrow(() -> mapper.readValue(raw, nl.reinkrul.nuts.common.VerifiablePresentation.class));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredentialTest.java b/src/test/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredentialTest.java
new file mode 100644
index 0000000..9beeade
--- /dev/null
+++ b/src/test/java/nl/reinkrul/nuts/credentials/NutsEmployeeCredentialTest.java
@@ -0,0 +1,27 @@
+package nl.reinkrul.nuts.credentials;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import nl.reinkrul.nuts.Configuration;
+import nl.reinkrul.nuts.common.VerifiableCredential;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class NutsEmployeeCredentialTest {
+
+ @Test
+ void getCredential() throws JsonProcessingException {
+ var employeeIdentifier = "12345";
+ var employeeInitials = "A.B.";
+ var employeeName = "John Doe";
+ var employeeRole = "Manager";
+
+ var credential = new NutsEmployeeCredential(employeeIdentifier, employeeInitials, employeeName, employeeRole).getCredential();
+
+ var expected = "{\"@context\":[\"https://www.w3.org/2018/credentials/v1\",\"https://nuts.nl/credentials/v1\"],\"type\":[\"VerifiableCredential\",\"NutsEmployeeCredential\"],\"credentialSubject\":{\"type\":\"Organization\",\"member\":{\"type\":\"EmployeeRole\",\"identifier\":\"12345\",\"member\":{\"type\":\"Person\",\"familyName\":\"John Doe\",\"initials\":\"A.B.\"},\"roleName\":\"Manager\"}}}";
+ var expectedVC = Configuration.create().getJSON().getMapper().readValue(expected, VerifiableCredential.class);
+ var actualVC = Configuration.create().getJSON().getMapper().readValue(credential.source, VerifiableCredential.class);
+ assertEquals(expectedVC, actualVC);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/v6/CredentialExamples.java b/src/test/java/v6/CredentialExamples.java
new file mode 100644
index 0000000..1f9f9bd
--- /dev/null
+++ b/src/test/java/v6/CredentialExamples.java
@@ -0,0 +1,33 @@
+package v6;
+
+import nl.reinkrul.nuts.ApiException;
+import nl.reinkrul.nuts.vcr.CredentialApi;
+import nl.reinkrul.nuts.vcr.IssueVCRequest;
+import nl.reinkrul.nuts.vcr.IssueVCRequestType;
+
+import java.util.Map;
+
+public class CredentialExamples {
+
+ public void issueNutsOrganizationCredential() throws ApiException {
+ var client = new CredentialApi();
+ var issuedVC = client.issueVC(new IssueVCRequest()
+ // General VC properties
+ .type(new IssueVCRequestType("NutsOrganizationCredential"))
+ .issuer("did:web:example.com:issuer") // the DID of the issuer of the credential (DID of software vendor)
+ .withStatusList2021Revocation(true) // enable revocation
+ // Subject of the credential
+ .credentialSubject(
+ Map.of(
+ "id", "did:web:example.com:holder",
+ "organization", Map.of(
+ "name", "Extra Careful B.V.",
+ "city", "Zorgdorp"
+ )
+ )
+ )
+ );
+
+ System.out.println("VC has been issued, ID: " + issuedVC.getId());
+ }
+}