-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement StorageApi + validator
- Loading branch information
1 parent
fb6fc8e
commit e2fbefc
Showing
29 changed files
with
908 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
47 changes: 47 additions & 0 deletions
47
extensions/protocols/dcp/dcp-identityhub/storage-api/build.gradle.kts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
* Copyright (c) 2025 Cofinity-X | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Cofinity-X - initial API and implementation | ||
* | ||
*/ | ||
|
||
plugins { | ||
`java-library` | ||
`maven-publish` | ||
id("io.swagger.core.v3.swagger-gradle-plugin") | ||
} | ||
|
||
dependencies { | ||
api(project(":spi:identity-hub-spi")) | ||
api(project(":spi:verifiable-credential-spi")) | ||
api(project(":extensions:protocols:dcp:dcp-spi")) | ||
api(libs.edc.spi.jsonld) | ||
api(libs.edc.spi.jwt) | ||
api(libs.edc.spi.core) | ||
implementation(libs.edc.spi.validator) | ||
implementation(libs.edc.spi.web) | ||
implementation(libs.edc.spi.dcp) | ||
implementation(libs.edc.lib.jerseyproviders) | ||
implementation(libs.edc.lib.transform) | ||
implementation(libs.edc.dcp.transform) | ||
implementation(libs.jakarta.rsApi) | ||
testImplementation(libs.edc.junit) | ||
testImplementation(libs.edc.jsonld) | ||
testImplementation(testFixtures(libs.edc.core.jersey)) | ||
testImplementation(testFixtures(project(":spi:verifiable-credential-spi"))) | ||
testImplementation(libs.nimbus.jwt) | ||
testImplementation(libs.restAssured) | ||
} | ||
|
||
edcBuild { | ||
swagger { | ||
apiGroup.set("storage-api") | ||
} | ||
} |
136 changes: 136 additions & 0 deletions
136
...ityhub/storage-api/src/main/java/org/eclipse/edc/identityhub/api/StorageApiExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright (c) 2025 Cofinity-X | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Cofinity-X - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.identityhub.api; | ||
|
||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import org.eclipse.edc.iam.identitytrust.transform.from.JsonObjectFromPresentationResponseMessageTransformer; | ||
import org.eclipse.edc.iam.identitytrust.transform.to.JsonObjectToPresentationQueryTransformer; | ||
import org.eclipse.edc.identityhub.api.storage.StorageApiController; | ||
import org.eclipse.edc.identityhub.api.validation.CredentialMessageValidator; | ||
import org.eclipse.edc.identityhub.spi.verification.SelfIssuedTokenVerifier; | ||
import org.eclipse.edc.jsonld.spi.JsonLd; | ||
import org.eclipse.edc.jsonld.spi.JsonLdNamespace; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Configuration; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Extension; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Inject; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Setting; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Settings; | ||
import org.eclipse.edc.spi.EdcException; | ||
import org.eclipse.edc.spi.system.ServiceExtension; | ||
import org.eclipse.edc.spi.system.ServiceExtensionContext; | ||
import org.eclipse.edc.spi.system.apiversion.ApiVersionService; | ||
import org.eclipse.edc.spi.system.apiversion.VersionRecord; | ||
import org.eclipse.edc.spi.types.TypeManager; | ||
import org.eclipse.edc.transform.spi.TypeTransformerRegistry; | ||
import org.eclipse.edc.transform.transformer.edc.to.JsonValueToGenericTypeTransformer; | ||
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; | ||
import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; | ||
import org.eclipse.edc.web.spi.WebService; | ||
import org.eclipse.edc.web.spi.configuration.PortMapping; | ||
import org.eclipse.edc.web.spi.configuration.PortMappingRegistry; | ||
|
||
import java.io.IOException; | ||
import java.util.stream.Stream; | ||
|
||
import static org.eclipse.edc.iam.identitytrust.spi.DcpConstants.DSPACE_DCP_NAMESPACE_V_1_0; | ||
import static org.eclipse.edc.iam.identitytrust.spi.DcpConstants.DSPACE_DCP_V_1_0_CONTEXT; | ||
import static org.eclipse.edc.identityhub.api.StorageApiExtension.NAME; | ||
import static org.eclipse.edc.identityhub.protocols.dcp.spi.DcpConstants.DCP_SCOPE_V_1_0; | ||
import static org.eclipse.edc.identityhub.protocols.dcp.spi.model.CredentialMessage.CREDENTIAL_MESSAGE_TERM; | ||
import static org.eclipse.edc.identityhub.spi.webcontext.IdentityHubApiContext.STORAGE; | ||
import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; | ||
|
||
@Extension(value = NAME) | ||
public class StorageApiExtension implements ServiceExtension { | ||
|
||
public static final String NAME = "Storage API Extension"; | ||
private static final String API_VERSION_JSON_FILE = "storage-api-version.json"; | ||
|
||
@Configuration | ||
private StorageApiConfiguration apiConfiguration; | ||
@Inject | ||
private TypeTransformerRegistry typeTransformer; | ||
@Inject | ||
private JsonObjectValidatorRegistry validatorRegistry; | ||
@Inject | ||
private WebService webService; | ||
@Inject | ||
private SelfIssuedTokenVerifier selfIssuedTokenVerifier; | ||
@Inject | ||
private JsonLd jsonLd; | ||
@Inject | ||
private TypeManager typeManager; | ||
@Inject | ||
private ApiVersionService apiVersionService; | ||
@Inject | ||
private PortMappingRegistry portMappingRegistry; | ||
|
||
@Override | ||
public String name() { | ||
return NAME; | ||
} | ||
|
||
@Override | ||
public void initialize(ServiceExtensionContext context) { | ||
var contextString = STORAGE; | ||
|
||
portMappingRegistry.register(new PortMapping(contextString, apiConfiguration.port(), apiConfiguration.path())); | ||
|
||
validatorRegistry.register(DSPACE_DCP_NAMESPACE_V_1_0.toIri(CREDENTIAL_MESSAGE_TERM), new CredentialMessageValidator()); | ||
|
||
|
||
var controller = new StorageApiController(validatorRegistry, typeTransformer, selfIssuedTokenVerifier, jsonLd); | ||
webService.registerResource(contextString, new ObjectMapperProvider(typeManager, JSON_LD)); | ||
webService.registerResource(contextString, controller); | ||
|
||
jsonLd.registerContext(DSPACE_DCP_V_1_0_CONTEXT, DCP_SCOPE_V_1_0); | ||
|
||
registerTransformers(DCP_SCOPE_V_1_0, DSPACE_DCP_NAMESPACE_V_1_0); | ||
|
||
registerVersionInfo(getClass().getClassLoader()); | ||
} | ||
|
||
void registerTransformers(String scope, JsonLdNamespace namespace) { | ||
var scopedTransformerRegistry = typeTransformer.forContext(scope); | ||
scopedTransformerRegistry.register(new JsonObjectToPresentationQueryTransformer(typeManager, JSON_LD, namespace)); | ||
scopedTransformerRegistry.register(new JsonValueToGenericTypeTransformer(typeManager, JSON_LD)); | ||
scopedTransformerRegistry.register(new JsonObjectFromPresentationResponseMessageTransformer(namespace)); | ||
} | ||
|
||
private void registerVersionInfo(ClassLoader resourceClassLoader) { | ||
try (var versionContent = resourceClassLoader.getResourceAsStream(API_VERSION_JSON_FILE)) { | ||
if (versionContent == null) { | ||
throw new EdcException("Version file not found or not readable."); | ||
} | ||
Stream.of(typeManager.getMapper() | ||
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) | ||
.readValue(versionContent, VersionRecord[].class)) | ||
.forEach(vr -> apiVersionService.addRecord("presentation", vr)); | ||
} catch (IOException e) { | ||
throw new EdcException(e); | ||
} | ||
} | ||
|
||
@Settings | ||
record StorageApiConfiguration( | ||
@Setting(key = "web.http." + STORAGE + ".port", description = "Port for " + STORAGE + " api context", defaultValue = 14141 + "") | ||
int port, | ||
@Setting(key = "web.http." + STORAGE + ".path", description = "Path for " + STORAGE + " api context", defaultValue = "/api/storage") | ||
String path | ||
) { | ||
|
||
} | ||
|
||
} |
90 changes: 90 additions & 0 deletions
90
...ntityhub/storage-api/src/main/java/org/eclipse/edc/identityhub/api/storage/ApiSchema.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright (c) 2025 Cofinity-X | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Cofinity-X - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.identityhub.api.storage; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
import java.util.List; | ||
|
||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; | ||
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; | ||
|
||
public interface ApiSchema { | ||
@Schema(name = "ApiErrorDetail", example = ApiErrorDetailSchema.API_ERROR_EXAMPLE) | ||
record ApiErrorDetailSchema( | ||
String message, | ||
String type, | ||
String path, | ||
String invalidValue | ||
) { | ||
public static final String API_ERROR_EXAMPLE = """ | ||
{ | ||
"message": "error message", | ||
"type": "ErrorType", | ||
"path": "object.error.path", | ||
"invalidValue": "this value is not valid" | ||
} | ||
"""; | ||
} | ||
|
||
@Schema(name = "CredentialMessage", example = CredentialMessageSchema.CREDENTIALMESSAGE_EXAMPLE) | ||
record CredentialMessageSchema( | ||
@Schema(name = CONTEXT, requiredMode = REQUIRED) | ||
Object context, | ||
@Schema(name = TYPE, requiredMode = REQUIRED) | ||
String type, | ||
@Schema(name = "credentials", requiredMode = REQUIRED) | ||
List<CredentialContainerSchema> credentials, | ||
@Schema(name = "requestId", requiredMode = REQUIRED) | ||
String requestId | ||
) { | ||
|
||
public static final String CREDENTIALMESSAGE_EXAMPLE = """ | ||
{ | ||
"@context": [ | ||
"https://w3id.org/dspace-dcp/v1.0/dcp.jsonld" | ||
], | ||
"type": "CredentialMessage", | ||
"credentials": [ | ||
{ | ||
"credentialType": "MembershipCredential", | ||
"payload": "", | ||
"format": "jwt" | ||
}, | ||
{ | ||
"credentialType": "OrganizationCredential", | ||
"payload": "", | ||
"format": "json-ld" | ||
} | ||
], | ||
"requestId": "requestId" | ||
} | ||
"""; | ||
} | ||
|
||
|
||
@Schema(name = "CredentialContainerSchema", example = CredentialContainerSchema.EXAMPLE) | ||
record CredentialContainerSchema() { | ||
|
||
private static final String EXAMPLE = """ | ||
{ | ||
"credentialType": "MembershipCredential", | ||
"payload": "", | ||
"format": "jwt" | ||
} | ||
"""; | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...tityhub/storage-api/src/main/java/org/eclipse/edc/identityhub/api/storage/StorageApi.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright (c) 2025 Cofinity-X | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Cofinity-X - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.identityhub.api.storage; | ||
|
||
|
||
import io.swagger.v3.oas.annotations.OpenAPIDefinition; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; | ||
import io.swagger.v3.oas.annotations.info.Info; | ||
import io.swagger.v3.oas.annotations.media.ArraySchema; | ||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.parameters.RequestBody; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.security.SecurityScheme; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.json.JsonObject; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
@OpenAPIDefinition( | ||
info = @Info(description = "This represents the Storage API as per DCP specification. It serves endpoints to write VerifiableCredentials into storage.", title = "Storage API", | ||
version = "1")) | ||
@SecurityScheme(name = "Authentication", | ||
description = "Self-Issued ID token containing an access_token", | ||
type = SecuritySchemeType.HTTP, | ||
scheme = "bearer", | ||
bearerFormat = "JWT") | ||
public interface StorageApi { | ||
|
||
@Tag(name = "Storage API") | ||
@Operation(description = "Writes a set of credentials into storage", | ||
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = ApiSchema.CredentialMessageSchema.class))), | ||
responses = { | ||
@ApiResponse(responseCode = "2xx", description = "The credentialMessage was successfully processed and stored"), | ||
@ApiResponse(responseCode = "400", description = "Request body was malformed", | ||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)))), | ||
@ApiResponse(responseCode = "401", description = "No Authorization header was given.", | ||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)))), | ||
@ApiResponse(responseCode = "403", description = "The given authentication token could not be validated. This can happen, when the request body " + | ||
"calls for a broader credentialMessage scope than the granted scope in the auth token", | ||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)))) | ||
|
||
} | ||
) | ||
Response storeCredential(String participantContextId, JsonObject credentialMessage, String authHeader); | ||
} |
Oops, something went wrong.