Skip to content

Commit 02723ff

Browse files
authored
feat: introduce messages and JSON-LD transformers for DCP issuer (#551)
1 parent 0102013 commit 02723ff

File tree

44 files changed

+1620
-131
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1620
-131
lines changed

dist/bom/issuerservice-base-bom/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ dependencies {
2626
runtimeOnly(project(":core:issuerservice:issuerservice-participants"))
2727
runtimeOnly(project(":extensions:did:local-did-publisher"))
2828
// API modules
29-
runtimeOnly(project(":extensions:protocols:dcp:issuer-api"))
29+
runtimeOnly(project(":extensions:protocols:dcp:dcp-issuer:dcp-issuer-api"))
3030

3131
runtimeOnly(project(":extensions:sts:sts-account-provisioner"))
3232
runtimeOnly(libs.edc.identity.did.core)

extensions/protocols/dcp/issuer-api/build.gradle.kts renamed to extensions/protocols/dcp/dcp-issuer/dcp-issuer-api/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ plugins {
2222
dependencies {
2323
api(project(":spi:identity-hub-spi"))
2424
api(project(":spi:verifiable-credential-spi"))
25+
api(project(":extensions:protocols:dcp:dcp-spi"))
2526
api(libs.edc.spi.jsonld)
2627
api(libs.edc.spi.jwt)
2728
api(libs.edc.spi.core)
@@ -30,6 +31,7 @@ dependencies {
3031
implementation(libs.edc.lib.jerseyproviders)
3132
implementation(libs.edc.lib.transform)
3233
implementation(libs.edc.dcp.transform)
34+
implementation(project(":extensions:protocols:dcp:dcp-issuer:dcp-issuer-transform-lib"))
3335
implementation(libs.jakarta.rsApi)
3436
testImplementation(libs.edc.junit)
3537
testImplementation(libs.edc.jsonld)
Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.credentialrequest.CredentialRequestApiController;
1919
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.credentialrequeststatus.CredentialRequestStatusApiController;
2020
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.issuermetadata.IssuerMetadataApiController;
21+
import org.eclipse.edc.identityhub.protocols.dcp.issuer.transform.from.JsonObjectFromCredentialObjectTransformer;
22+
import org.eclipse.edc.identityhub.protocols.dcp.issuer.transform.from.JsonObjectFromCredentialRequestStatusTransformer;
23+
import org.eclipse.edc.identityhub.protocols.dcp.issuer.transform.from.JsonObjectFromIssuerMetadataTransformer;
24+
import org.eclipse.edc.identityhub.protocols.dcp.issuer.transform.to.JsonObjectToCredentialRequestMessageTransformer;
25+
import org.eclipse.edc.jsonld.spi.JsonLd;
26+
import org.eclipse.edc.jsonld.spi.JsonLdNamespace;
2127
import org.eclipse.edc.runtime.metamodel.annotation.Configuration;
2228
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
2329
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
@@ -29,15 +35,22 @@
2935
import org.eclipse.edc.spi.system.apiversion.ApiVersionService;
3036
import org.eclipse.edc.spi.system.apiversion.VersionRecord;
3137
import org.eclipse.edc.spi.types.TypeManager;
38+
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
39+
import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor;
40+
import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider;
3241
import org.eclipse.edc.web.spi.WebService;
3342
import org.eclipse.edc.web.spi.configuration.PortMapping;
3443
import org.eclipse.edc.web.spi.configuration.PortMappingRegistry;
3544

3645
import java.io.IOException;
3746
import java.util.stream.Stream;
3847

48+
import static org.eclipse.edc.iam.identitytrust.spi.DcpConstants.DSPACE_DCP_NAMESPACE_V_1_0;
49+
import static org.eclipse.edc.iam.identitytrust.spi.DcpConstants.DSPACE_DCP_V_1_0_CONTEXT;
3950
import static org.eclipse.edc.identityhub.protocols.dcp.issuer.IssuerApiExtension.NAME;
51+
import static org.eclipse.edc.identityhub.protocols.dcp.spi.DcpConstants.DCP_SCOPE_V_1_0;
4052
import static org.eclipse.edc.identityhub.spi.webcontext.IdentityHubApiContext.ISSUER_API;
53+
import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD;
4154

4255
@Extension(value = NAME)
4356
public class IssuerApiExtension implements ServiceExtension {
@@ -57,18 +70,44 @@ public class IssuerApiExtension implements ServiceExtension {
5770
@Configuration
5871
private CredentialRequestApiConfiguration apiConfiguration;
5972

73+
@Inject
74+
private TypeTransformerRegistry transformerRegistry;
75+
76+
@Inject
77+
private JsonLd jsonLd;
78+
6079
@Override
6180
public void initialize(ServiceExtensionContext context) {
6281

6382
portMappingRegistry.register(new PortMapping(ISSUER_API, apiConfiguration.port(), apiConfiguration.path()));
6483

65-
webService.registerResource(ISSUER_API, new CredentialRequestApiController());
66-
webService.registerResource(ISSUER_API, new CredentialRequestStatusApiController());
67-
webService.registerResource(ISSUER_API, new IssuerMetadataApiController());
84+
var dcpRegistry = transformerRegistry.forContext(DCP_SCOPE_V_1_0);
85+
registerTransformers(dcpRegistry, DSPACE_DCP_NAMESPACE_V_1_0);
86+
87+
88+
webService.registerResource(ISSUER_API, new CredentialRequestApiController(dcpRegistry));
89+
webService.registerResource(ISSUER_API, new CredentialRequestStatusApiController(dcpRegistry));
90+
webService.registerResource(ISSUER_API, new IssuerMetadataApiController(dcpRegistry));
91+
92+
webService.registerResource(ISSUER_API, new ObjectMapperProvider(typeManager, JSON_LD));
93+
webService.registerResource(ISSUER_API, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, DCP_SCOPE_V_1_0));
94+
95+
jsonLd.registerContext(DSPACE_DCP_V_1_0_CONTEXT, DCP_SCOPE_V_1_0);
6896

6997
registerVersionInfo(getClass().getClassLoader());
7098
}
7199

100+
private void registerTransformers(TypeTransformerRegistry dcpRegistry, JsonLdNamespace namespace) {
101+
102+
// from
103+
dcpRegistry.register(new JsonObjectFromCredentialRequestStatusTransformer(namespace));
104+
dcpRegistry.register(new JsonObjectFromIssuerMetadataTransformer(namespace));
105+
dcpRegistry.register(new JsonObjectFromCredentialObjectTransformer(typeManager, JSON_LD, namespace));
106+
107+
// to
108+
dcpRegistry.register(new JsonObjectToCredentialRequestMessageTransformer(typeManager, JSON_LD, namespace));
109+
}
110+
72111
private void registerVersionInfo(ClassLoader resourceClassLoader) {
73112
try (var versionContent = resourceClassLoader.getResourceAsStream(API_VERSION_JSON_FILE)) {
74113
if (versionContent == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright (c) 2025 Cofinity-X
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Cofinity-X - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha;
16+
17+
import io.swagger.v3.oas.annotations.media.Schema;
18+
19+
import java.util.List;
20+
21+
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
22+
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT;
23+
24+
public interface ApiSchema {
25+
@Schema(name = "ApiErrorDetail", example = ApiErrorDetailSchema.API_ERROR_EXAMPLE)
26+
record ApiErrorDetailSchema(
27+
String message,
28+
String type,
29+
String path,
30+
String invalidValue
31+
) {
32+
public static final String API_ERROR_EXAMPLE = """
33+
{
34+
"message": "error message",
35+
"type": "ErrorType",
36+
"path": "object.error.path",
37+
"invalidValue": "this value is not valid"
38+
}
39+
""";
40+
}
41+
42+
@Schema(name = "CredentialRequestMessage", example = CredentialRequestMessageSchema.RESPONSE_EXAMPLE)
43+
record CredentialRequestMessageSchema(
44+
@Schema(name = CONTEXT, requiredMode = REQUIRED)
45+
Object context,
46+
@Schema(requiredMode = REQUIRED)
47+
List<CredentialRequestSchema> credentials
48+
) {
49+
50+
public static final String RESPONSE_EXAMPLE = """
51+
{
52+
"@context": [
53+
"https://w3id.org/dspace-dcp/v1.0/dcp.jsonld"
54+
],
55+
"type": "CredentialRequestMessage",
56+
"credentials": [
57+
{
58+
"credentialType": "MembershipCredential",
59+
"format": "vcdm11_jwt"
60+
},
61+
{
62+
"credentialType": "OrganizationCredential",
63+
"format": "vcdm11_ld"
64+
},
65+
{
66+
"credentialType": "Iso9001Credential",
67+
"format": "vcdm20_jose"
68+
}
69+
]
70+
}
71+
""";
72+
}
73+
74+
@Schema(name = "CredentialRequest", example = CredentialRequestSchema.EXAMPLE)
75+
record CredentialRequestSchema(
76+
@Schema(name = "credentialType", requiredMode = REQUIRED)
77+
String credentialType,
78+
@Schema(name = "format", requiredMode = REQUIRED)
79+
String format
80+
) {
81+
public static final String EXAMPLE = """
82+
{
83+
"credentialType": "MembershipCredential",
84+
"format": "vcdm11_jwt"
85+
}
86+
""";
87+
}
88+
89+
@Schema(name = "CredentialStatus", example = CredentialStatusSchema.RESPONSE_EXAMPLE)
90+
record CredentialStatusSchema(
91+
@Schema(name = CONTEXT, requiredMode = REQUIRED)
92+
Object context,
93+
@Schema(name = "type", requiredMode = REQUIRED)
94+
String type,
95+
@Schema(name = "status", requiredMode = REQUIRED)
96+
String status
97+
) {
98+
99+
public static final String RESPONSE_EXAMPLE = """
100+
{
101+
"@context": [
102+
"https://w3id.org/dspace-dcp/v1.0/dcp.jsonld"
103+
],
104+
"type": "CredentialStatus",
105+
"requestId": "requestId",
106+
"status": "RECEIVED"
107+
}
108+
""";
109+
}
110+
111+
@Schema(name = "IssuerMetadata", example = IssuerMetadataSchema.RESPONSE_EXAMPLE)
112+
record IssuerMetadataSchema(
113+
@Schema(name = CONTEXT, requiredMode = REQUIRED)
114+
Object context,
115+
@Schema(name = "type", requiredMode = REQUIRED)
116+
String type,
117+
@Schema(name = "credentialIssuer", requiredMode = REQUIRED)
118+
String credentialIssuer,
119+
@Schema(name = "status", requiredMode = REQUIRED)
120+
String status
121+
) {
122+
123+
public static final String RESPONSE_EXAMPLE = """
124+
{
125+
"@context": [
126+
"https://w3id.org/dspace-dcp/v1.0/dcp.jsonld"
127+
],
128+
"type": "IssuerMetadata",
129+
"credentialIssuer": "did:web:issuer-url",
130+
"credentialsSupported": [
131+
{
132+
"type": "CredentialObject",
133+
"credentialType": "MembershipCredential",
134+
"offerReason": "reissue",
135+
"bindingMethods": [
136+
"did:web"
137+
],
138+
"profiles": [
139+
"vc20-bssl/jwt", "vc10-sl2021/jwt", "..."
140+
],
141+
"issuancePolicy": {
142+
"id": "Scalable trust example",
143+
"input_descriptors": [
144+
{
145+
"id": "pd-id",
146+
"constraints": {
147+
"fields": [
148+
{
149+
"path": [
150+
"$.vc.type"
151+
],
152+
"filter": {
153+
"type": "string",
154+
"pattern": "^AttestationCredential$"
155+
}
156+
}
157+
]
158+
}
159+
}
160+
]
161+
}
162+
}
163+
]
164+
}
165+
""";
166+
}
167+
168+
@Schema(name = "CredentialObject", example = CredentialObjectSchema.EXAMPLE)
169+
record CredentialObjectSchema(
170+
@Schema(name = "credentialType", requiredMode = REQUIRED)
171+
String credentialType,
172+
@Schema(name = "format", requiredMode = REQUIRED)
173+
String format
174+
) {
175+
public static final String EXAMPLE = """
176+
{
177+
"credentialType": "MembershipCredential",
178+
"format": "vcdm11_jwt"
179+
}
180+
""";
181+
}
182+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.swagger.v3.oas.annotations.responses.ApiResponse;
2727
import io.swagger.v3.oas.annotations.security.SecurityScheme;
2828
import io.swagger.v3.oas.annotations.tags.Tag;
29+
import jakarta.json.JsonObject;
2930
import jakarta.ws.rs.core.Response;
3031
import org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.ApiSchema;
3132

@@ -42,10 +43,10 @@ public interface CredentialRequestApi {
4243
@Tag(name = "Credential Request API")
4344
@Operation(description = "Requests the issuance of one or several verifiable credentials from an issuer",
4445
operationId = "requestCredentials",
45-
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = CredentialRequestMessage.class))),
46+
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = ApiSchema.CredentialRequestMessageSchema.class))),
4647
responses = {
47-
@ApiResponse(responseCode = "201", description = "The request was successfully received and is being processed.", headers = {@Header(name = "Location",
48-
description = "contains the relative URL where the status of the request can be queried (Credential Request Status API)")}),
48+
@ApiResponse(responseCode = "201", description = "The request was successfully received and is being processed.", headers = { @Header(name = "Location",
49+
description = "contains the relative URL where the status of the request can be queried (Credential Request Status API)") }),
4950
@ApiResponse(responseCode = "400", description = "Request body was malformed, e.g. required parameter or properties were missing",
5051
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)))),
5152
@ApiResponse(responseCode = "401", description = "No Authorization header was provided.",
@@ -55,5 +56,5 @@ public interface CredentialRequestApi {
5556

5657
}
5758
)
58-
Response requestCredential(CredentialRequestMessage message);
59+
Response requestCredential(JsonObject message);
5960
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
package org.eclipse.edc.identityhub.protocols.dcp.issuer.api.v1alpha.credentialrequest;
1616

17+
import jakarta.json.JsonObject;
1718
import jakarta.ws.rs.Consumes;
1819
import jakarta.ws.rs.POST;
1920
import jakarta.ws.rs.Path;
2021
import jakarta.ws.rs.Produces;
2122
import jakarta.ws.rs.core.Response;
23+
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
2224

2325
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
2426

@@ -27,10 +29,14 @@
2729
@Path("/v1alpha/credentials")
2830
public class CredentialRequestApiController implements CredentialRequestApi {
2931

32+
public CredentialRequestApiController(TypeTransformerRegistry dcpRegistry) {
33+
34+
}
35+
3036
@POST
3137
@Path("/")
3238
@Override
33-
public Response requestCredential(CredentialRequestMessage message) {
39+
public Response requestCredential(JsonObject message) {
3440
return Response.noContent().build();
3541
}
3642
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ public interface CredentialRequestStatusApi {
4444
@Tag(name = "Credential Request Status API")
4545
@Operation(description = "Requests status information about an issuance request from an issuer",
4646
operationId = "getCredentialRequestStatus",
47-
parameters = {@Parameter(name = "credentialRequestId", description = "ID of the Credential Request that was sent previously", required = true, in = ParameterIn.PATH)},
47+
parameters = { @Parameter(name = "credentialRequestId", description = "ID of the Credential Request that was sent previously", required = true, in = ParameterIn.PATH) },
4848
responses = {
4949
@ApiResponse(responseCode = "200", description = "Gets the status of a credentials request.",
50-
content = @Content(schema = @Schema(implementation = CredentialRequestStatus.class))),
50+
content = @Content(schema = @Schema(implementation = ApiSchema.CredentialStatusSchema.class))),
5151
@ApiResponse(responseCode = "400", description = "Request was malformed, e.g. required parameter or properties were missing",
5252
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)))),
5353
@ApiResponse(responseCode = "401", description = "No Authorization header was provided.",
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
import jakarta.ws.rs.PathParam;
2121
import jakarta.ws.rs.Produces;
2222
import jakarta.ws.rs.core.Response;
23-
24-
import java.time.Instant;
23+
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
2524

2625
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
2726

@@ -30,18 +29,18 @@
3029
@Path("/v1alpha/requests")
3130
public class CredentialRequestStatusApiController implements CredentialRequestStatusApi {
3231

32+
public CredentialRequestStatusApiController(TypeTransformerRegistry dcpRegistry) {
33+
34+
}
35+
3336
@GET
3437
@Path("/{credentialRequestId}")
3538
@Override
3639
public Response requestCredential(@PathParam("credentialRequestId") String credentialRequestId) {
3740
if (credentialRequestId == null || credentialRequestId.isEmpty()) {
3841
return Response.status(400).build();
3942
}
40-
return Response.ok(CredentialRequestStatus.Builder.newInstance()
41-
.message("dummy-message")
42-
.requestId("dummy-request-id")
43-
.requestStatus("RECEIVED")
44-
.timestamp(Instant.now()).build())
43+
return Response.ok()
4544
.build();
4645
}
4746
}

0 commit comments

Comments
 (0)