diff --git a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/PresentationApiExtension.java b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/PresentationApiExtension.java index 1be8f5d89..5daf6877c 100644 --- a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/PresentationApiExtension.java +++ b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/PresentationApiExtension.java @@ -21,7 +21,7 @@ import org.eclipse.edc.identityhub.spi.generator.VerifiablePresentationService; import org.eclipse.edc.identityhub.spi.resolution.CredentialQueryResolver; import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -68,7 +68,7 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { // setup validator - validatorRegistry.register(PresentationQuery.PRESENTATION_QUERY_TYPE_PROPERTY, new PresentationQueryValidator()); + validatorRegistry.register(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY, new PresentationQueryValidator()); var controller = new PresentationApiController(validatorRegistry, typeTransformer, credentialResolver, accessTokenVerifier, verifiablePresentationService, context.getMonitor()); diff --git a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApi.java b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApi.java index cf2738d52..1015bdea6 100644 --- a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApi.java +++ b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApi.java @@ -28,7 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.json.JsonObject; import jakarta.ws.rs.core.Response; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; @OpenAPIDefinition( info = @Info(description = "This represents the Presentation API as per IATP specification. It serves endpoints to query for specific VerifiablePresentations.", title = "Resolution API", @@ -45,13 +45,13 @@ public interface PresentationApi { requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = ApiSchema.PresentationQuerySchema.class), mediaType = "application/ld+json")), responses = { @ApiResponse(responseCode = "200", description = "The query was successfully processed, the response contains the VerifiablePresentation", - content = @Content(schema = @Schema(implementation = PresentationResponse.class), mediaType = "application/ld+json")), + content = @Content(schema = @Schema(implementation = PresentationResponseMessage.class), mediaType = "application/ld+json")), @ApiResponse(responseCode = "400", description = "Request body was malformed, for example when both scope and presentationDefinition are given", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")), @ApiResponse(responseCode = "401", description = "No Authorization header was given.", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")), @ApiResponse(responseCode = "403", description = "The given authentication token could not be validated. This can happen, when the request body " + - "calls for a broader query scope than the granted scope in the auth token", + "calls for a broader query scope than the granted scope in the auth token", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")), @ApiResponse(responseCode = "501", description = "When the request contained a presentationDefinition object, but the implementation does not support it.", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiSchema.ApiErrorDetailSchema.class)), mediaType = "application/json")) diff --git a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApiController.java b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApiController.java index 1a6ce92dd..acdd9c0cd 100644 --- a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApiController.java +++ b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/v1/PresentationApiController.java @@ -25,7 +25,7 @@ import org.eclipse.edc.identityhub.spi.generator.VerifiablePresentationService; import org.eclipse.edc.identityhub.spi.resolution.CredentialQueryResolver; import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; @@ -42,7 +42,7 @@ import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery.PRESENTATION_QUERY_TYPE_PROPERTY; +import static org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY; @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @@ -74,9 +74,9 @@ public Response queryPresentation(JsonObject query, @HeaderParam(AUTHORIZATION) if (token == null) { throw new AuthenticationFailedException("Authorization header missing"); } - validatorRegistry.validate(PRESENTATION_QUERY_TYPE_PROPERTY, query).orElseThrow(ValidationFailureException::new); + validatorRegistry.validate(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY, query).orElseThrow(ValidationFailureException::new); - var presentationQuery = transformerRegistry.transform(query, PresentationQuery.class).orElseThrow(InvalidRequestException::new); + var presentationQuery = transformerRegistry.transform(query, PresentationQueryMessage.class).orElseThrow(InvalidRequestException::new); if (presentationQuery.getPresentationDefinition() != null) { monitor.warning("DIF Presentation Queries are not supported yet. This will get implemented in future iterations."); diff --git a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/validation/PresentationQueryValidator.java b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/validation/PresentationQueryValidator.java index 504a0d114..eb9822e6a 100644 --- a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/validation/PresentationQueryValidator.java +++ b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityhub/api/validation/PresentationQueryValidator.java @@ -17,7 +17,7 @@ import jakarta.json.JsonArray; import jakarta.json.JsonObject; import jakarta.json.JsonValue; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; import org.eclipse.edc.validator.spi.ValidationResult; import org.eclipse.edc.validator.spi.Validator; @@ -27,7 +27,7 @@ import static org.eclipse.edc.validator.spi.Violation.violation; /** - * Validates, that a JsonObject representing a {@link PresentationQuery} contains either a {@code scope} property, + * Validates, that a JsonObject representing a {@link PresentationQueryMessage} contains either a {@code scope} property, * or a {@code presentationDefinition} query. */ public class PresentationQueryValidator implements Validator { @@ -36,9 +36,9 @@ public ValidationResult validate(JsonObject input) { if (input == null) { return failure(violation("Presentation query was null", ".")); } - var scope = input.get(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY); + var scope = input.get(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_SCOPE_PROPERTY); - var presentationDef = input.get(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY); + var presentationDef = input.get(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_DEFINITION_PROPERTY); if (isNullObject(scope) && isNullObject(presentationDef)) { return failure(violation("Must contain either a 'scope' or a 'presentationDefinition' property.", null)); diff --git a/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/v1/PresentationApiControllerTest.java b/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/v1/PresentationApiControllerTest.java index 24c8108f2..a7a272ad9 100644 --- a/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/v1/PresentationApiControllerTest.java +++ b/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/v1/PresentationApiControllerTest.java @@ -22,8 +22,8 @@ import org.eclipse.edc.identityhub.spi.resolution.QueryResult; import org.eclipse.edc.identityhub.spi.verification.AccessTokenVerifier; import org.eclipse.edc.identitytrust.model.credentialservice.InputDescriptorMapping; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; import org.eclipse.edc.identitytrust.model.credentialservice.PresentationSubmission; import org.eclipse.edc.identitytrust.model.presentationdefinition.PresentationDefinition; import org.eclipse.edc.junit.annotations.ApiTest; @@ -51,7 +51,7 @@ import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.buildSignedJwt; import static org.eclipse.edc.identityhub.junit.testfixtures.VerifiableCredentialTestUtil.generateEcKey; import static org.eclipse.edc.identityhub.spi.resolution.QueryResult.success; -import static org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery.PRESENTATION_QUERY_TYPE_PROPERTY; +import static org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY; import static org.eclipse.edc.validator.spi.ValidationResult.failure; import static org.eclipse.edc.validator.spi.ValidationResult.success; import static org.eclipse.edc.validator.spi.Violation.violation; @@ -84,7 +84,7 @@ void query_tokenNotPresent_shouldReturn401() { @Test void query_validationError_shouldReturn400() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(failure(violation("foo", "bar"))); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(failure(violation("foo", "bar"))); assertThatThrownBy(() -> controller().queryPresentation(createObjectBuilder().build(), generateJwt())) .isInstanceOf(ValidationFailureException.class) @@ -93,8 +93,8 @@ void query_validationError_shouldReturn400() { @Test void query_transformationError_shouldReturn400() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.failure("cannot transform")); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.failure("cannot transform")); assertThatThrownBy(() -> controller().queryPresentation(createObjectBuilder().build(), generateJwt())) .isInstanceOf(InvalidRequestException.class) @@ -104,10 +104,10 @@ void query_transformationError_shouldReturn400() { @Test void query_withPresentationDefinition_shouldReturn501() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); var presentationQueryBuilder = createPresentationQueryBuilder() .presentationDefinition(PresentationDefinition.Builder.newInstance().id(UUID.randomUUID().toString()).build()); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.success(presentationQueryBuilder.build())); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.success(presentationQueryBuilder.build())); var response = controller().queryPresentation(createObjectBuilder().build(), generateJwt()); assertThat(response.getStatus()).isEqualTo(503); @@ -121,9 +121,9 @@ void query_withPresentationDefinition_shouldReturn501() { @Test void query_tokenVerificationFails_shouldReturn401() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); var presentationQueryBuilder = createPresentationQueryBuilder().build(); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.success(presentationQueryBuilder)); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.success(presentationQueryBuilder)); when(accessTokenVerifier.verify(anyString())).thenReturn(Result.failure("test-failure")); assertThatThrownBy(() -> controller().queryPresentation(createObjectBuilder().build(), generateJwt())) @@ -134,9 +134,9 @@ void query_tokenVerificationFails_shouldReturn401() { @Test void query_queryResolutionFails_shouldReturn403() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); var presentationQueryBuilder = createPresentationQueryBuilder().build(); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.success(presentationQueryBuilder)); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.success(presentationQueryBuilder)); when(accessTokenVerifier.verify(anyString())).thenReturn(Result.success(List.of("test-scope1"))); when(queryResolver.query(any(), eq(List.of("test-scope1")))).thenReturn(QueryResult.unauthorized("test-failure")); @@ -148,9 +148,9 @@ void query_queryResolutionFails_shouldReturn403() { @Test void query_presentationGenerationFails_shouldReturn500() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); var presentationQueryBuilder = createPresentationQueryBuilder().build(); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.success(presentationQueryBuilder)); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.success(presentationQueryBuilder)); when(accessTokenVerifier.verify(anyString())).thenReturn(Result.success(List.of("test-scope1"))); when(queryResolver.query(any(), eq(List.of("test-scope1")))).thenReturn(success(Stream.empty())); @@ -163,13 +163,15 @@ void query_presentationGenerationFails_shouldReturn500() { @Test void query_success() { - when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_TYPE_PROPERTY), any())).thenReturn(success()); + when(validatorRegistryMock.validate(eq(PRESENTATION_QUERY_MESSAGE_TYPE_PROPERTY), any())).thenReturn(success()); var presentationQueryBuilder = createPresentationQueryBuilder().build(); - when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQuery.class))).thenReturn(Result.success(presentationQueryBuilder)); + when(typeTransformerRegistry.transform(isA(JsonObject.class), eq(PresentationQueryMessage.class))).thenReturn(Result.success(presentationQueryBuilder)); when(accessTokenVerifier.verify(anyString())).thenReturn(Result.success(List.of("test-scope1"))); when(queryResolver.query(any(), eq(List.of("test-scope1")))).thenReturn(success(Stream.empty())); - var pres = new PresentationResponse(new Object[] {generateJwt()}, new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("id", "ldp_vp", "$.verifiableCredentials[0]")))); + var pres = PresentationResponseMessage.Builder.newinstance().presentation(List.of(generateJwt())) + .presentationSubmission(new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("id", "ldp_vp", "$.verifiableCredentials[0]")))) + .build(); when(generator.createPresentation(anyList(), any(), any())).thenReturn(Result.success(pres)); var response = controller().queryPresentation(createObjectBuilder().build(), generateJwt()); @@ -195,8 +197,8 @@ private String generateJwt() { return jwt.serialize(); } - private PresentationQuery.Builder createPresentationQueryBuilder() { - return PresentationQuery.Builder.newinstance() + private PresentationQueryMessage.Builder createPresentationQueryBuilder() { + return PresentationQueryMessage.Builder.newinstance() .scopes(List.of("test-scope1", "test-scope2")); } } \ No newline at end of file diff --git a/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/validation/PresentationQueryValidatorTest.java b/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/validation/PresentationQueryValidatorTest.java index e5b965f86..77b0198b2 100644 --- a/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/validation/PresentationQueryValidatorTest.java +++ b/core/identity-hub-api/src/test/java/org/eclipse/edc/identityservice/api/validation/PresentationQueryValidatorTest.java @@ -20,7 +20,7 @@ import jakarta.json.JsonArray; import jakarta.json.JsonObject; import org.eclipse.edc.identityhub.api.validation.PresentationQueryValidator; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.identitytrust.model.presentationdefinition.Constraints; import org.eclipse.edc.identitytrust.model.presentationdefinition.Field; import org.eclipse.edc.identitytrust.model.presentationdefinition.InputDescriptor; @@ -48,7 +48,7 @@ class PresentationQueryValidatorTest { @Test void validate_withScope_success() { var jo = createObjectBuilder() - .add(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY, createScopeArray()) + .add(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_SCOPE_PROPERTY, createScopeArray()) .build(); assertThat(validator.validate(jo)).isSucceeded(); @@ -61,7 +61,7 @@ void validate_withPresentationDefinition_success() throws JsonProcessingExceptio .inputDescriptors(List.of(InputDescriptor.Builder.newInstance().id(UUID.randomUUID().toString()).constraints(new Constraints(List.of(Field.Builder.newInstance().build()))).build())) .build(); var jo = createObjectBuilder() - .add(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY, createPresentationDefArray(presDef)) + .add(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_DEFINITION_PROPERTY, createPresentationDefArray(presDef)) .build(); assertThat(validator.validate(jo)).isSucceeded(); @@ -81,8 +81,8 @@ void validate_withBoth_fails() throws JsonProcessingException { .inputDescriptors(List.of(InputDescriptor.Builder.newInstance().id(UUID.randomUUID().toString()).constraints(new Constraints(List.of(Field.Builder.newInstance().build()))).build())) .build(); var jo = createObjectBuilder() - .add(PresentationQuery.PRESENTATION_QUERY_SCOPE_PROPERTY, createScopeArray()) - .add(PresentationQuery.PRESENTATION_QUERY_DEFINITION_PROPERTY, createPresentationDefArray(presDef)) + .add(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_SCOPE_PROPERTY, createScopeArray()) + .add(PresentationQueryMessage.PRESENTATION_QUERY_MESSAGE_DEFINITION_PROPERTY, createPresentationDefArray(presDef)) .build(); assertThat(validator.validate(jo)).isFailed().detail().contains("Must contain either a 'scope' or a 'presentationDefinition', not both."); diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java index 877e45281..5b07fd8a7 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java @@ -15,11 +15,11 @@ package org.eclipse.edc.identityhub.core; import org.eclipse.edc.identityhub.spi.ScopeToCriterionTransformer; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.resolution.CredentialQueryResolver; import org.eclipse.edc.identityhub.spi.resolution.QueryResult; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.AbstractResult; @@ -43,7 +43,7 @@ public CredentialQueryResolverImpl(CredentialStore credentialStore, ScopeToCrite } @Override - public QueryResult query(PresentationQuery query, List issuerScopes) { + public QueryResult query(PresentationQueryMessage query, List issuerScopes) { if (query.getPresentationDefinition() != null) { throw new UnsupportedOperationException("Querying with a DIF Presentation Exchange definition is not yet supported."); } diff --git a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImpl.java b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImpl.java index f08a1e97d..70179acb0 100644 --- a/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImpl.java +++ b/core/identity-hub-credentials/src/main/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImpl.java @@ -19,7 +19,7 @@ import org.eclipse.edc.identityhub.spi.generator.VerifiablePresentationService; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; import org.eclipse.edc.identitytrust.model.presentationdefinition.PresentationDefinition; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -60,7 +60,7 @@ public VerifiablePresentationServiceImpl(CredentialFormat defaultFormatVp, Prese * @return A Result object wrapping the PresentationResponse. */ @Override - public Result createPresentation(List credentials, @Nullable PresentationDefinition presentationDefinition, @Nullable String audience) { + public Result createPresentation(List credentials, @Nullable PresentationDefinition presentationDefinition, @Nullable String audience) { if (presentationDefinition != null) { monitor.warning("A PresentationDefinition was submitted, but is currently ignored by the generator."); @@ -92,7 +92,7 @@ public Result createPresentation(List implements KeyPairResourceStore { diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java index ef6b7f0cc..ec2d4e7ab 100644 --- a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java @@ -16,15 +16,15 @@ import org.eclipse.edc.identityhub.defaults.EdcScopeToCriterionTransformer; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.resolution.QueryFailure; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.CredentialSubject; import org.eclipse.edc.identitytrust.model.Issuer; import org.eclipse.edc.identitytrust.model.VerifiableCredential; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; import org.eclipse.edc.identitytrust.model.presentationdefinition.PresentationDefinition; import org.eclipse.edc.spi.result.StoreResult; import org.jetbrains.annotations.Nullable; @@ -110,7 +110,7 @@ void query_multipleScopeStrings() { @Test void query_presentationDefinition_unsupported() { - var q = PresentationQuery.Builder.newinstance().presentationDefinition(PresentationDefinition.Builder.newInstance().id("test-pd").build()).build(); + var q = PresentationQueryMessage.Builder.newinstance().presentationDefinition(PresentationDefinition.Builder.newInstance().id("test-pd").build()).build(); assertThatThrownBy(() -> resolver.query(q, List.of("org.eclipse.edc.vc.type:SomeCredential:read"))) .isInstanceOf(UnsupportedOperationException.class) .hasMessage("Querying with a DIF Presentation Exchange definition is not yet supported."); @@ -201,9 +201,9 @@ void query_storeReturnsFailure() { assertThat(res.getFailureDetail()).isEqualTo("test-failure"); } - private PresentationQuery createPresentationQuery(@Nullable String... scope) { + private PresentationQueryMessage createPresentationQuery(@Nullable String... scope) { var scopes = new ArrayList<>(Arrays.asList(scope)); - return PresentationQuery.Builder.newinstance().scopes(scopes).build(); + return PresentationQueryMessage.Builder.newinstance().scopes(scopes).build(); } private VerifiableCredentialResource createCredentialResource(String... type) { diff --git a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImplTest.java b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImplTest.java index 1220bca05..0ea5c9bf1 100644 --- a/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImplTest.java +++ b/core/identity-hub-credentials/src/test/java/org/eclipse/edc/identityhub/core/VerifiablePresentationServiceImplTest.java @@ -63,7 +63,7 @@ void generate_noCredentials() { List ldpVcs = List.of(); var result = presentationGenerator.createPresentation(ldpVcs, null, null); - assertThat(result).isSucceeded().matches(pr -> pr.vpToken().length == 0, "VP Tokens should be empty"); + assertThat(result).isSucceeded().matches(pr -> pr.getPresentation().isEmpty(), "VP Tokens should be empty"); } @Test diff --git a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceExtension.java b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceExtension.java index ae833ff0d..bb4b738bf 100644 --- a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceExtension.java +++ b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceExtension.java @@ -21,6 +21,7 @@ import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; import static org.eclipse.edc.identityhub.keypairs.KeyPairServiceExtension.NAME; @@ -34,7 +35,7 @@ public class KeyPairServiceExtension implements ServiceExtension { private KeyPairResourceStore keyPairResourceStore; @Provider - public KeyPairService createParticipantService() { - return new KeyPairServiceImpl(keyPairResourceStore, vault); + public KeyPairService createParticipantService(ServiceExtensionContext context) { + return new KeyPairServiceImpl(keyPairResourceStore, vault, context.getMonitor()); } } diff --git a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java index 99a063d60..517fbac91 100644 --- a/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java +++ b/core/identity-hub-keypairs/src/main/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImpl.java @@ -16,11 +16,12 @@ import org.eclipse.edc.identityhub.security.KeyPairGenerator; import org.eclipse.edc.identityhub.spi.KeyPairService; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.KeyPairState; import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairState; import org.eclipse.edc.security.token.jwt.CryptoConverter; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.Result; @@ -29,16 +30,18 @@ import org.jetbrains.annotations.Nullable; import java.time.Instant; -import java.util.Objects; +import java.util.Collection; import java.util.Optional; public class KeyPairServiceImpl implements KeyPairService { private final KeyPairResourceStore keyPairResourceStore; private final Vault vault; + private final Monitor monitor; - public KeyPairServiceImpl(KeyPairResourceStore keyPairResourceStore, Vault vault) { + public KeyPairServiceImpl(KeyPairResourceStore keyPairResourceStore, Vault vault, Monitor monitor) { this.keyPairResourceStore = keyPairResourceStore; this.vault = vault; + this.monitor = monitor; } @Override @@ -50,6 +53,7 @@ public ServiceResult addKeyPair(String participantId, KeyDescriptor keyDes } var newResource = KeyPairResource.Builder.newInstance() + .id(keyDescriptor.getKeyId()) .keyId(keyDescriptor.getKeyId()) .state(KeyPairState.CREATED) .isDefaultPair(makeDefault) @@ -63,8 +67,8 @@ public ServiceResult addKeyPair(String participantId, KeyDescriptor keyDes } @Override - public ServiceResult rotateKeyPair(String oldId, KeyDescriptor newKeySpec, long duration) { - Objects.requireNonNull(newKeySpec); + public ServiceResult rotateKeyPair(String oldId, @Nullable KeyDescriptor newKeySpec, long duration) { + var oldKey = findById(oldId); if (oldKey == null) { return ServiceResult.notFound("A KeyPairResource with ID '%s' does not exist.".formatted(oldId)); @@ -79,7 +83,11 @@ public ServiceResult rotateKeyPair(String oldId, KeyDescriptor newKeySpec, oldKey.rotate(duration); keyPairResourceStore.update(oldKey); - return addKeyPair(participantId, newKeySpec, wasDefault); + if (newKeySpec != null) { + return addKeyPair(participantId, newKeySpec, wasDefault); + } + monitor.warning("Rotating keys without a successor key may leave the participant without an active keypair."); + return ServiceResult.success(); } @Override @@ -102,9 +110,15 @@ public ServiceResult revokeKey(String id, @Nullable KeyDescriptor newKeySp if (newKeySpec != null) { return addKeyPair(participantId, newKeySpec, wasDefault); } + monitor.warning("Revoking keys without a successor key may leave the participant without an active keypair."); return ServiceResult.success(); } + @Override + public ServiceResult> query(QuerySpec querySpec) { + return ServiceResult.from(keyPairResourceStore.query(querySpec)); + } + private KeyPairResource findById(String oldId) { var q = QuerySpec.Builder.newInstance() .filter(new Criterion("id", "=", oldId)).build(); diff --git a/core/identity-hub-keypairs/src/test/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImplTest.java b/core/identity-hub-keypairs/src/test/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImplTest.java index ab981c646..18250a2f1 100644 --- a/core/identity-hub-keypairs/src/test/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImplTest.java +++ b/core/identity-hub-keypairs/src/test/java/org/eclipse/edc/identityhub/keypairs/KeyPairServiceImplTest.java @@ -17,10 +17,10 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.KeyPairState; import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairState; import org.eclipse.edc.spi.security.Vault; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -47,7 +47,7 @@ class KeyPairServiceImplTest { private final KeyPairResourceStore keyPairResourceStore = mock(); private final Vault vault = mock(); - private final KeyPairServiceImpl keyPairService = new KeyPairServiceImpl(keyPairResourceStore, vault); + private final KeyPairServiceImpl keyPairService = new KeyPairServiceImpl(keyPairResourceStore, vault, mock()); @ParameterizedTest(name = "make default: {0}") @@ -104,6 +104,22 @@ void rotateKeyPair_withNewKey() { verifyNoMoreInteractions(vault, keyPairResourceStore); } + @Test + void rotateKeyPair_withoutNewKey() { + var oldId = "old-id"; + var oldKey = createKeyPairResource().id(oldId).build(); + + when(keyPairResourceStore.query(any())).thenReturn(success(List.of(oldKey))); + when(keyPairResourceStore.create(any())).thenReturn(success()); + + assertThat(keyPairService.rotateKeyPair(oldId, null, Duration.ofDays(100).toMillis())).isSucceeded(); + + verify(keyPairResourceStore).query(any()); + verify(keyPairResourceStore).update(argThat(kpr -> kpr.getId().equals(oldId))); + verify(vault).deleteSecret(eq(oldKey.getPrivateKeyAlias())); //deletes old private key + verifyNoMoreInteractions(vault, keyPairResourceStore); + } + @Test void rotateKeyPair_withNewKeyGenerate() { var oldId = "old-id"; diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/KeyPairResourceApiEndToEndTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/KeyPairResourceApiEndToEndTest.java new file mode 100644 index 000000000..0919c74b9 --- /dev/null +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/KeyPairResourceApiEndToEndTest.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.tests; + +import com.nimbusds.jose.jwk.Curve; +import io.restassured.http.Header; +import org.eclipse.edc.identityhub.spi.KeyPairService; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; +import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.EdcException; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.UUID; + +import static io.restassured.http.ContentType.JSON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +@EndToEndTest +public class KeyPairResourceApiEndToEndTest extends ManagementApiEndToEndTest { + + @Test + void findById_notAuthorized() { + var user1 = "user1"; + createParticipant(user1); + + + // create second user + var user2 = "user2"; + var user2Context = ParticipantContext.Builder.newInstance() + .participantId(user2) + .did("did:web:" + user2) + .apiTokenAlias(user2 + "-alias") + .build(); + var user2Token = storeParticipant(user2Context); + + var key = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", user2Token)) + .get("/v1/keypairs/" + key) + .then() + .log().ifValidationFails() + .statusCode(403) + .body(notNullValue()); + } + + @Test + void findById() { + var user1 = "user1"; + var token = createParticipant(user1); + + + var key = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token)) + .get("/v1/keypairs/" + key) + .then() + .log().ifValidationFails() + .statusCode(200) + .body(notNullValue()); + } + + @Test + void findForParticipant_notAuthorized() { + var user1 = "user1"; + createParticipant(user1); + + + // create second user + var user2 = "user2"; + var user2Context = ParticipantContext.Builder.newInstance() + .participantId(user2) + .did("did:web:" + user2) + .apiTokenAlias(user2 + "-alias") + .build(); + var user2Token = storeParticipant(user2Context); + + var key = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + var res = RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", user2Token)) + .get("/v1/keypairs?participantId=" + user1) + .then() + .log().ifValidationFails() + .statusCode(200) + .extract().body().as(KeyPairResource[].class); + + assertThat(res).isEmpty(); + + } + + @Test + void findForParticipant() { + var user1 = "user1"; + var token = createParticipant(user1); + + + var key = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token)) + .get("/v1/keypairs?participantId=" + user1) + .then() + .log().ifValidationFails() + .statusCode(200) + .body(notNullValue()); + } + + @Test + void addKeyPair() { + var user1 = "user1"; + var token = createParticipant(user1); + + + // attempt to publish user1's DID document, which should fail + var keyDesc = createKeyDescriptor(user1).build(); + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token)) + .body(keyDesc) + .put("/v1/keypairs?participantId=" + user1) + .then() + .log().ifValidationFails() + .statusCode(204) + .body(notNullValue()); + } + + @Test + void addKeyPair_notAuthorized() { + var user1 = "user1"; + var token = createParticipant(user1); + + var user2 = "user2"; + var token2 = createParticipant(user2); + + + // attempt to publish user1's DID document, which should fail + var keyDesc = createKeyDescriptor(user1).build(); + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token2)) + .body(keyDesc) + .put("/v1/keypairs?participantId=" + user1) + .then() + .log().ifValidationFails() + .statusCode(403) + .body(notNullValue()); + } + + @Test + void rotate() { + var user1 = "user1"; + var token = createParticipant(user1); + + var keyId = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + var keyDesc = createKeyDescriptor(user1).build(); + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token)) + .body(keyDesc) + .post("/v1/keypairs/%s/rotate".formatted(keyId)) + .then() + .log().ifValidationFails() + .statusCode(204) + .body(notNullValue()); + } + + @Test + void rotate_notAuthorized() { + var user1 = "user1"; + var token = createParticipant(user1); + + var user2 = "user2"; + var token2 = createParticipant(user2); + + var keyId = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + var keyDesc = createKeyDescriptor(user1).build(); + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token2)) + .body(keyDesc) + .post("/v1/keypairs/%s/rotate".formatted(keyId)) + .then() + .log().ifValidationFails() + .statusCode(403) + .body(notNullValue()); + } + + @Test + void revoke() { + var user1 = "user1"; + var token = createParticipant(user1); + + var keyId = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token)) + .post("/v1/keypairs/%s/revoke".formatted(keyId)) + .then() + .log().ifValidationFails() + .statusCode(204) + .body(notNullValue()); + } + + @Test + void revoke_notAuthorized() { + var user1 = "user1"; + var token = createParticipant(user1); + + var user2 = "user2"; + var token2 = createParticipant(user2); + + var keyId = createKeyPair(user1); + + // attempt to publish user1's DID document, which should fail + RUNTIME_CONFIGURATION.getManagementEndpoint().baseRequest() + .contentType(JSON) + .header(new Header("x-api-key", token2)) + .post("/v1/keypairs/%s/revoke".formatted(keyId)) + .then() + .log().ifValidationFails() + .statusCode(403) + .body(notNullValue()); + } + + private String createKeyPair(String participantId) { + + var descriptor = createKeyDescriptor(participantId).build(); + + var service = RUNTIME.getContext().getService(KeyPairService.class); + service.addKeyPair(participantId, descriptor, true) + .orElseThrow(f -> new EdcException(f.getFailureDetail())); + return descriptor.getKeyId(); + } + + private static KeyDescriptor.Builder createKeyDescriptor(String participantId) { + return KeyDescriptor.Builder.newInstance() + .keyId(UUID.randomUUID().toString()) + .keyGeneratorParams(Map.of("algorithm", "EC", "curve", Curve.P_384.getStdName())) + .privateKeyAlias(participantId + "-alias"); + } + +} diff --git a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ResolutionApiComponentTest.java b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ResolutionApiComponentTest.java index 4d062241f..abb11512e 100644 --- a/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ResolutionApiComponentTest.java +++ b/e2e-tests/api-tests/src/test/java/org/eclipse/edc/identityhub/tests/ResolutionApiComponentTest.java @@ -22,7 +22,7 @@ import org.eclipse.edc.identityhub.tests.fixtures.IdentityHubRuntimeConfiguration; import org.eclipse.edc.identityhub.tests.fixtures.TestData; import org.eclipse.edc.identitytrust.model.credentialservice.InputDescriptorMapping; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; import org.eclipse.edc.identitytrust.model.credentialservice.PresentationSubmission; import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; @@ -54,7 +54,7 @@ public class ResolutionApiComponentTest { "https://identity.foundation/presentation-exchange/submission/v1", "https://w3id.org/tractusx-trust/v0.8" ], - "@type": "Query", + "@type": "PresentationQueryMessage", "scope":[ "test-scope1" ] @@ -122,7 +122,7 @@ void query_withPresentationDefinition_shouldReturn503() { "https://identity.foundation/presentation-exchange/submission/v1", "https://w3id.org/tractusx-trust/v0.8" ], - "@type": "Query", + "@type": "PresentationQueryMessage", "presentationDefinition":{ } } @@ -204,13 +204,16 @@ void query_success() { .then() .statusCode(200) .log().ifValidationFails() - .extract().body().as(PresentationResponse.class); + .extract().body().as(PresentationResponseMessage.class); } - private PresentationResponse createPresentationResponse() { + private PresentationResponseMessage createPresentationResponse() { var submission = new PresentationSubmission("id", "def-id", List.of(new InputDescriptorMapping("input-id", "ldp-vp", "foo"))); - return new PresentationResponse(new Object[] {TestData.VP_EXAMPLE}, submission); + return PresentationResponseMessage.Builder.newinstance() + .presentation(List.of(TestData.VP_EXAMPLE)) + .presentationSubmission(submission) + .build(); } diff --git a/extensions/api/keypair-mgmt-api/build.gradle.kts b/extensions/api/keypair-mgmt-api/build.gradle.kts new file mode 100644 index 000000000..865ad385b --- /dev/null +++ b/extensions/api/keypair-mgmt-api/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + `java-library` + `maven-publish` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + api(libs.edc.spi.core) + api(project(":spi:identity-hub-spi")) + api(project(":spi:identity-hub-store-spi")) + implementation(project(":extensions:api:identityhub-management-api-configuration")) + implementation(libs.edc.spi.web) + implementation(libs.edc.util) + implementation(libs.jakarta.rsApi) + + testImplementation(libs.edc.junit) + testImplementation(libs.restAssured) + testImplementation(testFixtures(libs.edc.core.jersey)) +} diff --git a/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/KeyPairResourceManagementApiExtension.java b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/KeyPairResourceManagementApiExtension.java new file mode 100644 index 000000000..2d7481f4f --- /dev/null +++ b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/KeyPairResourceManagementApiExtension.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.api.verifiablecredentials; + +import org.eclipse.edc.identityhub.api.configuration.ManagementApiConfiguration; +import org.eclipse.edc.identityhub.api.verifiablecredentials.v1.KeyPairResourceApiController; +import org.eclipse.edc.identityhub.spi.AuthorizationService; +import org.eclipse.edc.identityhub.spi.KeyPairService; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.ParticipantResource; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.query.Criterion; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; + +import static org.eclipse.edc.identityhub.api.verifiablecredentials.KeyPairResourceManagementApiExtension.NAME; + +@Extension(NAME) +public class KeyPairResourceManagementApiExtension implements ServiceExtension { + public static final String NAME = "KeyPairResource Management API Extension"; + + @Inject + private ManagementApiConfiguration apiConfiguration; + @Inject + private WebService webService; + @Inject + private KeyPairService keyPairService; + @Inject + private AuthorizationService authorizationService; + + @Override + public void initialize(ServiceExtensionContext context) { + authorizationService.addLoookupFunction(KeyPairResource.class, this::findById); + var controller = new KeyPairResourceApiController(authorizationService, keyPairService); + webService.registerResource(apiConfiguration.getContextAlias(), controller); + } + + private ParticipantResource findById(String keyPairId) { + var q = QuerySpec.Builder.newInstance() + .filter(new Criterion("id", "=", keyPairId)) + .build(); + return keyPairService.query(q) + .orElseThrow(f -> new EdcException(f.getFailureDetail())) + .stream() + .findFirst() + .orElse(null); + } +} diff --git a/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApi.java b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApi.java new file mode 100644 index 000000000..89ec01e21 --- /dev/null +++ b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApi.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.api.verifiablecredentials.v1; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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.tags.Tag; +import jakarta.ws.rs.core.SecurityContext; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; +import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext; +import org.eclipse.edc.web.spi.ApiErrorDetail; + +import java.util.Collection; + +@OpenAPIDefinition(info = @Info(description = "This is the Management API for KeyPairResources", title = "KeyPairResources Management API", version = "1")) +public interface KeyPairResourceApi { + + @Tag(name = "KeyPairResources Management API") + @Operation(description = "Finds a KeyPairResource by ID.", + responses = { + @ApiResponse(responseCode = "200", description = "The KeyPairResource.", + content = @Content(schema = @Schema(implementation = ParticipantContext.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "404", description = "A KeyPairResource with the given ID does not exist.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")) + } + ) + KeyPairResource findById(String id, SecurityContext securityContext); + + @Tag(name = "KeyPairResources Management API") + @Operation(description = "Finds all KeyPairResources for a particular ParticipantContext.", + parameters = @Parameter(name = "participantId", description = "ID of the participant context for which to list the keys. May need elevated rights."), + responses = { + @ApiResponse(responseCode = "200", description = "The KeyPairResource.", + content = @Content(schema = @Schema(implementation = ParticipantContext.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "404", description = "A KeyPairResource with the given ID does not exist.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")) + } + ) + Collection findForParticipant(String participantId, SecurityContext securityContext); + + @Tag(name = "KeyPairResources Management API") + @Operation(description = "Adds a new key pair to a ParticipantContext. Note that the key pair is either generated, or the private key is expected to be found in the vault.", + requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = KeyDescriptor.class), mediaType = "application/json")), + parameters = @Parameter(name = "makeDefault", description = "Make the new key pair the default key pair"), + responses = { + @ApiResponse(responseCode = "200", description = "The KeyPairResource was successfully created and linked to the participant.", + content = @Content(schema = @Schema(implementation = ParticipantContext.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "404", description = "A KeyPairResource with the given ID does not exist.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")) + } + ) + void addKeyPair(String participantId, KeyDescriptor keyDescriptor, boolean makeDefault, SecurityContext securityContext); + + @Tag(name = "KeyPairResources Management API") + @Operation(description = "Rotates (=retires) a particular key pair, identified by their ID and optionally create a new successor key.", + requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = KeyDescriptor.class), mediaType = "application/json")), + parameters = @Parameter(name = "duration", description = "Indicates for how long the public key of the rotated/retired key pair should still be available "), + responses = { + @ApiResponse(responseCode = "200", description = "The KeyPairResource was successfully rotated and linked to the participant.", + content = @Content(schema = @Schema(implementation = ParticipantContext.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "404", description = "A KeyPairResource with the given ID does not exist.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")) + } + ) + void rotateKeyPair(String id, KeyDescriptor newKey, long duration, SecurityContext securityContext); + + @Tag(name = "KeyPairResources Management API") + @Operation(description = "Revokes (=removes) a particular key pair, identified by their ID and create a new successor key.", + requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = KeyDescriptor.class), mediaType = "application/json")), + responses = { + @ApiResponse(responseCode = "200", description = "The KeyPairResource was successfully rotated and linked to the participant.", + content = @Content(schema = @Schema(implementation = ParticipantContext.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")), + @ApiResponse(responseCode = "404", description = "A KeyPairResource with the given ID does not exist.", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)), mediaType = "application/json")) + } + ) + void revokeKey(String id, KeyDescriptor newKey, SecurityContext securityContext); +} diff --git a/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiController.java b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiController.java new file mode 100644 index 000000000..ce2de4a23 --- /dev/null +++ b/extensions/api/keypair-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.api.verifiablecredentials.v1; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.SecurityContext; +import org.eclipse.edc.identityhub.spi.AuthorizationService; +import org.eclipse.edc.identityhub.spi.KeyPairService; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; +import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.query.Criterion; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.identityhub.spi.AuthorizationResultHandler.exceptionMapper; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v1/keypairs") +public class KeyPairResourceApiController implements KeyPairResourceApi { + + private final AuthorizationService authorizationService; + private final KeyPairService keyPairService; + + public KeyPairResourceApiController(AuthorizationService authorizationService, KeyPairService keyPairService) { + this.authorizationService = authorizationService; + this.keyPairService = keyPairService; + } + + @GET + @Path("/{keyPairId}") + @Override + public KeyPairResource findById(@PathParam("keyPairId") String id, @Context SecurityContext securityContext) { + + authorizationService.isAuthorized(securityContext.getUserPrincipal(), id, KeyPairResource.class) + .orElseThrow(exceptionMapper(KeyPairResource.class, id)); + + var query = QuerySpec.Builder.newInstance().filter(new Criterion("id", "=", id)).build(); + var result = keyPairService.query(query).orElseThrow(exceptionMapper(KeyPairResource.class, id)); + if (result.isEmpty()) { + throw new ObjectNotFoundException(KeyPairResource.class, id); + } + if (result.size() > 1) { + throw new EdcException("Expected only 1 result, but got %s".formatted(result.size())); + } + return result.iterator().next(); + } + + @GET + @Override + public Collection findForParticipant(@QueryParam("participantId") String participantId, @Context SecurityContext securityContext) { + var query = QuerySpec.Builder.newInstance().filter(new Criterion("participantId", "=", participantId)).build(); + return keyPairService.query(query) + .orElseThrow(exceptionMapper(KeyPairResource.class, participantId)) + .stream().filter(kpr -> authorizationService.isAuthorized(securityContext.getUserPrincipal(), kpr.getId(), KeyPairResource.class).succeeded()) + .toList(); + } + + @PUT + @Override + public void addKeyPair(@QueryParam("participantId") String participantId, KeyDescriptor keyDescriptor, @QueryParam("makeDefault") boolean makeDefault, + @Context SecurityContext securityContext) { + authorizationService.isAuthorized(securityContext.getUserPrincipal(), participantId, ParticipantContext.class) + .compose(u -> keyPairService.addKeyPair(participantId, keyDescriptor, makeDefault)) + .orElseThrow(exceptionMapper(KeyPairResource.class)); + } + + @POST + @Path("/{keyPairId}/rotate") + @Override + public void rotateKeyPair(@PathParam("keyPairId") String id, @Nullable KeyDescriptor newKey, @QueryParam("duration") long duration, @Context SecurityContext securityContext) { + authorizationService.isAuthorized(securityContext.getUserPrincipal(), id, KeyPairResource.class) + .compose(u -> keyPairService.rotateKeyPair(id, newKey, duration)) + .orElseThrow(exceptionMapper(KeyPairResource.class, id)); + } + + @POST + @Path("/{keyPairId}/revoke") + @Override + public void revokeKey(@PathParam("keyPairId") String id, KeyDescriptor newKey, @Context SecurityContext securityContext) { + authorizationService.isAuthorized(securityContext.getUserPrincipal(), id, KeyPairResource.class) + .compose(u -> keyPairService.revokeKey(id, newKey)) + .orElseThrow(exceptionMapper(KeyPairResource.class, id)); + } +} diff --git a/extensions/api/keypair-mgmt-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/api/keypair-mgmt-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..aa9f9c822 --- /dev/null +++ b/extensions/api/keypair-mgmt-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Metaform Systems, Inc. +# +# 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: +# Metaform Systems, Inc. - initial API and implementation +# +# + +org.eclipse.edc.identityhub.api.verifiablecredentials.KeyPairResourceManagementApiExtension diff --git a/extensions/api/keypair-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiControllerTest.java b/extensions/api/keypair-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiControllerTest.java new file mode 100644 index 000000000..1a4621f60 --- /dev/null +++ b/extensions/api/keypair-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/KeyPairResourceApiControllerTest.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.identityhub.api.verifiablecredentials.v1; + +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import org.eclipse.edc.identityhub.spi.AuthorizationService; +import org.eclipse.edc.identityhub.spi.KeyPairService; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.time.Duration; +import java.util.List; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +class KeyPairResourceApiControllerTest extends RestControllerTestBase { + + private final KeyPairService keyPairService = mock(); + private final AuthorizationService authService = mock(); + + + @BeforeEach + void setUp() { + when(authService.isAuthorized(any(), anyString(), any())).thenReturn(ServiceResult.success()); + } + + @Test + void findById() { + var keyPair = createKeyPair().build(); + + when(keyPairService.query(any())).thenReturn(ServiceResult.success(List.of(keyPair))); + + var found = baseRequest() + .get("/test-keypairId") + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(KeyPairResource.class); + assertThat(found).usingRecursiveComparison().isEqualTo(keyPair); + } + + @Test + void findById_notExist() { + when(keyPairService.query(any())).thenReturn(ServiceResult.notFound("tst-msg")); + + var found = baseRequest() + .get("/test-keypairId") + .then() + .log().ifValidationFails() + .statusCode(404); + } + + @Test + void findForParticipant() { + var keyPair = createKeyPair().build(); + + when(keyPairService.query(any())).thenReturn(ServiceResult.success(List.of(keyPair))); + + var found = baseRequest() + .get("?participantId=test-participant") + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(KeyPairResource[].class); + assertThat(found).usingRecursiveFieldByFieldElementComparator().containsExactly(keyPair); + + verify(keyPairService).query(argThat(q -> { + var criterion = q.getFilterExpression().get(0); + return criterion.getOperandLeft().equals("participantId") && + criterion.getOperator().equals("=") && + criterion.getOperandRight().equals("test-participant"); + })); + } + + @Test + void findForParticipant_noResult() { + var keyPair = createKeyPair().build(); + + when(keyPairService.query(any())).thenReturn(ServiceResult.success(List.of())); + + var found = baseRequest() + .get("?participantId=test-participant") + .then() + .statusCode(200) + .log().ifError() + .extract().body().as(KeyPairResource[].class); + assertThat(found).isEmpty(); + + verify(keyPairService).query(argThat(q -> { + var criterion = q.getFilterExpression().get(0); + return criterion.getOperandLeft().equals("participantId") && + criterion.getOperator().equals("=") && + criterion.getOperandRight().equals("test-participant"); + })); + } + + @Test + void findForParticipant_notfound() { + when(keyPairService.query(any())).thenReturn(ServiceResult.notFound("test-message")); + + baseRequest() + .get("?participantId=test-participant") + .then() + .statusCode(404) + .log().ifError(); + + verify(keyPairService).query(argThat(q -> { + var criterion = q.getFilterExpression().get(0); + return criterion.getOperandLeft().equals("participantId") && + criterion.getOperator().equals("=") && + criterion.getOperandRight().equals("test-participant"); + })); + } + + @ParameterizedTest(name = "Make default: {0}") + @ValueSource(booleans = {true, false}) + void addKeyPair(boolean makeDefault) { + var descriptor = createKeyDescriptor() + .build(); + when(keyPairService.addKeyPair(eq("test-participant"), any(), eq(makeDefault))).thenReturn(ServiceResult.success()); + + baseRequest() + .contentType(ContentType.JSON) + .body(descriptor) + .put("?participantId=%s&makeDefault=%s".formatted("test-participant", makeDefault)) + .then() + .log().ifError() + .statusCode(204); + + verify(keyPairService).addKeyPair(eq("test-participant"), argThat(d -> d.getKeyId().equals(descriptor.getKeyId())), eq(makeDefault)); + verifyNoMoreInteractions(keyPairService); + } + + @Test + void rotate() { + var duration = Duration.ofDays(100).toMillis(); + when(keyPairService.rotateKeyPair(eq("old-id"), any(), eq(duration))).thenReturn(ServiceResult.success()); + + var descriptor = createKeyDescriptor().build(); + baseRequest() + .contentType(ContentType.JSON) + .body(descriptor) + .post("/old-id/rotate?duration=" + duration) + .then() + .log().ifError() + .statusCode(204); + + verify(keyPairService).rotateKeyPair(eq("old-id"), argThat(d -> d.getKeyId().equals(descriptor.getKeyId())), eq(duration)); + verifyNoMoreInteractions(keyPairService); + } + + @Test + void rotate_idNotFound() { + var duration = Duration.ofDays(100).toMillis(); + when(keyPairService.rotateKeyPair(eq("old-id"), any(), eq(duration))).thenReturn(ServiceResult.notFound("test-message")); + + var descriptor = createKeyDescriptor().build(); + baseRequest() + .contentType(ContentType.JSON) + .body(descriptor) + .post("/old-id/rotate?duration=" + duration) + .then() + .log().ifValidationFails() + .statusCode(404); + + verify(keyPairService).rotateKeyPair(eq("old-id"), argThat(d -> d.getKeyId().equals(descriptor.getKeyId())), eq(duration)); + verifyNoMoreInteractions(keyPairService); + } + + @Test + void rotate_withoutSuccessor() { + var duration = Duration.ofDays(100).toMillis(); + when(keyPairService.rotateKeyPair(eq("old-id"), any(), eq(duration))).thenReturn(ServiceResult.success()); + + baseRequest() + .contentType(ContentType.JSON) + .post("/old-id/rotate?duration=" + duration) + .then() + .log().ifError() + .statusCode(204); + + verify(keyPairService).rotateKeyPair(eq("old-id"), isNull(), eq(duration)); + verifyNoMoreInteractions(keyPairService); + } + + @Test + void revoke() { + when(keyPairService.revokeKey(eq("old-id"), any())).thenReturn(ServiceResult.success()); + + var descriptor = createKeyDescriptor().build(); + baseRequest() + .contentType(ContentType.JSON) + .body(descriptor) + .post("/old-id/revoke") + .then() + .log().ifError() + .statusCode(204); + + verify(keyPairService).revokeKey(eq("old-id"), argThat(d -> d.getKeyId().equals(descriptor.getKeyId()))); + verifyNoMoreInteractions(keyPairService); + } + + @Test + void revoke_notFound() { + when(keyPairService.revokeKey(eq("old-id"), any())).thenReturn(ServiceResult.notFound("test-message")); + + var descriptor = createKeyDescriptor().build(); + baseRequest() + .contentType(ContentType.JSON) + .body(descriptor) + .post("/old-id/revoke") + .then() + .log().ifError() + .statusCode(404); + + verify(keyPairService).revokeKey(eq("old-id"), argThat(d -> d.getKeyId().equals(descriptor.getKeyId()))); + verifyNoMoreInteractions(keyPairService); + } + + @Override + protected Object controller() { + return new KeyPairResourceApiController(authService, keyPairService); + } + + private KeyPairResource.Builder createKeyPair() { + return KeyPairResource.Builder.newInstance() + .id("test-keypair") + .participantId("test-participant") + .isDefaultPair(true) + .privateKeyAlias("test-alias") + .useDuration(Duration.ofDays(365).toMillis()); + } + + private RequestSpecification baseRequest() { + return given() + .contentType("application/json") + .baseUri("http://localhost:" + port + "/v1/keypairs") + .when(); + } + + @NotNull + private static KeyDescriptor.Builder createKeyDescriptor() { + return KeyDescriptor.Builder.newInstance() + .keyId("new-key-id") + .keyGeneratorParams(Map.of("algorithm", "EC", "curve", "secp256r1")); + } +} \ No newline at end of file diff --git a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java index a8d9a4965..cd63e88bd 100644 --- a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java +++ b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/VerifiableCredentialApiExtension.java @@ -18,8 +18,8 @@ import org.eclipse.edc.identityhub.api.verifiablecredentials.v1.VerifiableCredentialsApiController; import org.eclipse.edc.identityhub.spi.AuthorizationService; import org.eclipse.edc.identityhub.spi.model.ParticipantResource; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.EdcException; diff --git a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApi.java b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApi.java index 3044fb804..c817ea727 100644 --- a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApi.java +++ b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApi.java @@ -25,9 +25,9 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.core.SecurityContext; import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext; import org.eclipse.edc.identityhub.spi.model.participant.ParticipantManifest; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.web.spi.ApiErrorDetail; import java.util.Collection; diff --git a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiController.java b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiController.java index 1c00eb23f..b3d612765 100644 --- a/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiController.java +++ b/extensions/api/verifiable-credential-mgmt-api/src/main/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiController.java @@ -24,8 +24,8 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.SecurityContext; import org.eclipse.edc.identityhub.spi.AuthorizationService; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.ServiceResult; diff --git a/extensions/api/verifiable-credential-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiControllerTest.java b/extensions/api/verifiable-credential-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiControllerTest.java index 3808b1c46..a01777b88 100644 --- a/extensions/api/verifiable-credential-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiControllerTest.java +++ b/extensions/api/verifiable-credential-mgmt-api/src/test/java/org/eclipse/edc/identityhub/api/verifiablecredentials/v1/VerifiableCredentialsApiControllerTest.java @@ -16,8 +16,8 @@ import io.restassured.specification.RequestSpecification; import org.eclipse.edc.identityhub.spi.AuthorizationService; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.CredentialSubject; import org.eclipse.edc.identitytrust.model.Issuer; diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java index ebb38d706..90d8026c5 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/SqlCredentialStore.java @@ -15,9 +15,9 @@ package org.eclipse.edc.identityhub.store.sql.credentials; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.identityhub.spi.model.VcState; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VcState; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.VerifiableCredential; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; diff --git a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java index bbcc9e0ea..ea13297d3 100644 --- a/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java +++ b/extensions/store/sql/identity-hub-credentials-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/credentials/schema/postgres/VerifiableCredentialResourceMapping.java @@ -14,12 +14,13 @@ package org.eclipse.edc.identityhub.store.sql.credentials.schema.postgres; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.store.sql.credentials.CredentialStoreStatements; import org.eclipse.edc.sql.translation.TranslationMapping; /** - * Provides a mapping from the canonical format to SQL column names for a {@link org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource} + * Provides a mapping from the canonical format to SQL column names for a {@link VerifiableCredentialResource} */ public class VerifiableCredentialResourceMapping extends TranslationMapping { diff --git a/extensions/store/sql/identity-hub-keypair-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/keypair/SqlKeyPairResourceStore.java b/extensions/store/sql/identity-hub-keypair-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/keypair/SqlKeyPairResourceStore.java index 677e0ad89..f46bd8a28 100644 --- a/extensions/store/sql/identity-hub-keypair-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/keypair/SqlKeyPairResourceStore.java +++ b/extensions/store/sql/identity-hub-keypair-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/keypair/SqlKeyPairResourceStore.java @@ -15,8 +15,8 @@ package org.eclipse.edc.identityhub.store.sql.keypair; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource; import org.eclipse.edc.spi.persistence.EdcPersistenceException; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.StoreResult; diff --git a/extensions/store/sql/identity-hub-participantcontext-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/participantcontext/schema/postgres/ParticipantContextMapping.java b/extensions/store/sql/identity-hub-participantcontext-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/participantcontext/schema/postgres/ParticipantContextMapping.java index 2f38f0a6b..436c19dca 100644 --- a/extensions/store/sql/identity-hub-participantcontext-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/participantcontext/schema/postgres/ParticipantContextMapping.java +++ b/extensions/store/sql/identity-hub-participantcontext-store-sql/src/main/java/org/eclipse/edc/identityhub/store/sql/participantcontext/schema/postgres/ParticipantContextMapping.java @@ -14,12 +14,13 @@ package org.eclipse.edc.identityhub.store.sql.participantcontext.schema.postgres; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.store.sql.participantcontext.ParticipantContextStoreStatements; import org.eclipse.edc.sql.translation.TranslationMapping; /** - * Provides a mapping from the canonical format to SQL column names for a {@link org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource} + * Provides a mapping from the canonical format to SQL column names for a {@link VerifiableCredentialResource} */ public class ParticipantContextMapping extends TranslationMapping { diff --git a/launcher/build.gradle.kts b/launcher/build.gradle.kts index 4ccb766c0..7a2e6ffd1 100644 --- a/launcher/build.gradle.kts +++ b/launcher/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { runtimeOnly(project(":extensions:api:participant-context-mgmt-api")) runtimeOnly(project(":extensions:api:verifiable-credential-mgmt-api")) runtimeOnly(project(":extensions:api:identityhub-management-api-configuration")) + runtimeOnly(project(":extensions:api:keypair-mgmt-api")) runtimeOnly(libs.edc.identity.did.core) runtimeOnly(libs.edc.core.token) diff --git a/settings.gradle.kts b/settings.gradle.kts index 0b431c951..f001d5b53 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -47,6 +47,7 @@ include(":extensions:api:identityhub-management-api-configuration") include(":extensions:api:identityhub-api-auth") include(":extensions:api:participant-context-mgmt-api") include(":extensions:api:verifiable-credential-mgmt-api") +include(":extensions:api:keypair-mgmt-api") include(":extensions:did:did-management-api") // other modules diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/AuthorizationResultHandler.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/AuthorizationResultHandler.java index 5456ae998..75b5a958f 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/AuthorizationResultHandler.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/AuthorizationResultHandler.java @@ -35,4 +35,13 @@ public static Function exceptionMapper(@NotNull Cl return ServiceResultHandler.exceptionMapper(clazz, id).apply(failure); }; } + + public static Function exceptionMapper(@NotNull Class clazz) { + return failure -> { + if (failure.getReason() == ServiceFailure.Reason.UNAUTHORIZED) { + return new NotAuthorizedException(failure.getFailureDetail()); + } + return ServiceResultHandler.exceptionMapper(clazz).apply(failure); + }; + } } diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/KeyPairService.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/KeyPairService.java index 9e0422c97..6f85c3796 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/KeyPairService.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/KeyPairService.java @@ -14,11 +14,15 @@ package org.eclipse.edc.identityhub.spi; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; import org.eclipse.edc.identityhub.spi.model.participant.KeyDescriptor; +import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.security.Vault; import org.jetbrains.annotations.Nullable; +import java.util.Collection; + public interface KeyPairService { /** @@ -61,4 +65,6 @@ public interface KeyPairService { * @return success if rotated, a failure indicated the problem otherwise. */ ServiceResult revokeKey(String id, @Nullable KeyDescriptor newKeySpec); + + ServiceResult> query(QuerySpec querySpec); } diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/VerifiablePresentationService.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/VerifiablePresentationService.java index fa804ae08..cc13d601e 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/VerifiablePresentationService.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/VerifiablePresentationService.java @@ -15,7 +15,7 @@ package org.eclipse.edc.identityhub.spi.generator; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponse; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; import org.eclipse.edc.identitytrust.model.presentationdefinition.PresentationDefinition; import org.eclipse.edc.spi.result.Result; import org.jetbrains.annotations.Nullable; @@ -36,5 +36,5 @@ public interface VerifiablePresentationService { * @param audience The Participant ID of the party who the presentation is intended for. May not be relevant for all VP formats * @return A Result object containing a PresentationResponse if the presentation creation is successful, or a failure message if it fails. */ - Result createPresentation(List credentials, @Nullable PresentationDefinition presentationDefinition, @Nullable String audience); + Result createPresentation(List credentials, @Nullable PresentationDefinition presentationDefinition, @Nullable String audience); } diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/IdentityResource.java similarity index 89% rename from spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/IdentityResource.java index d2a70a944..186909725 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/IdentityResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 Metaform Systems, Inc. * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at @@ -8,14 +8,13 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * Metaform Systems, Inc. - initial API and implementation * */ -package org.eclipse.edc.identityhub.spi.store.model; +package org.eclipse.edc.identityhub.spi.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.eclipse.edc.identityhub.spi.model.ParticipantResource; import java.time.Clock; import java.util.Objects; diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairResource.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairResource.java similarity index 94% rename from spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairResource.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairResource.java index 9b34bd282..e119f7be3 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairResource.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairResource.java @@ -12,10 +12,9 @@ * */ -package org.eclipse.edc.identityhub.spi.store.model; +package org.eclipse.edc.identityhub.spi.model; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.eclipse.edc.identityhub.spi.model.ParticipantResource; import org.eclipse.edc.identityhub.spi.model.participant.ParticipantContext; import org.eclipse.edc.spi.security.KeyParserRegistry; import org.eclipse.edc.spi.security.Vault; @@ -31,7 +30,7 @@ public class KeyPairResource extends ParticipantResource { private long timestamp; private String keyId; private String groupName; - private boolean isDefaultPair; + private boolean defaultPair; private long useDuration; private long rotationDuration; private String serializedPublicKey; @@ -56,7 +55,7 @@ public String getId() { * Whether this KeyPair is the default for a {@link ParticipantContext}. */ public boolean isDefaultPair() { - return isDefaultPair; + return defaultPair; } /** @@ -111,12 +110,12 @@ public int getState() { public void rotate(long duration) { state = KeyPairState.ROTATED.code(); rotationDuration = duration; - isDefaultPair = false; + defaultPair = false; } public void revoke() { state = KeyPairState.REVOKED.code(); - isDefaultPair = false; + defaultPair = false; } public static final class Builder extends ParticipantResource.Builder { @@ -151,7 +150,7 @@ public Builder keyId(String keyId) { } public Builder isDefaultPair(boolean isDefaultPair) { - entity.isDefaultPair = isDefaultPair; + entity.defaultPair = isDefaultPair; return this; } diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairState.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairState.java similarity index 96% rename from spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairState.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairState.java index 48127398d..ec4b2952a 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/KeyPairState.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/KeyPairState.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.identityhub.spi.store.model; +package org.eclipse.edc.identityhub.spi.model; import java.util.Arrays; diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VcState.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VcState.java similarity index 77% rename from spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VcState.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VcState.java index 6304fb48a..9b99440d4 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VcState.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VcState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 Metaform Systems, Inc. * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at @@ -8,11 +8,11 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * Metaform Systems, Inc. - initial API and implementation * */ -package org.eclipse.edc.identityhub.spi.store.model; +package org.eclipse.edc.identityhub.spi.model; import java.util.Arrays; diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResource.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VerifiableCredentialResource.java similarity index 92% rename from spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResource.java rename to spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VerifiableCredentialResource.java index 6b0055a5a..afd930507 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResource.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/model/VerifiableCredentialResource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 Metaform Systems, Inc. * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at @@ -8,11 +8,11 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * Metaform Systems, Inc. - initial API and implementation * */ -package org.eclipse.edc.identityhub.spi.store.model; +package org.eclipse.edc.identityhub.spi.model; import com.fasterxml.jackson.annotation.JsonIgnore; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/resolution/CredentialQueryResolver.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/resolution/CredentialQueryResolver.java index 49e84d185..400bbcafe 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/resolution/CredentialQueryResolver.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/resolution/CredentialQueryResolver.java @@ -15,12 +15,13 @@ package org.eclipse.edc.identityhub.spi.resolution; import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; -import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQuery; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationQueryMessage; +import org.eclipse.edc.identitytrust.model.credentialservice.PresentationResponseMessage; import java.util.List; /** - * Resolves a list of {@link VerifiableCredentialContainer} objects based on an incoming {@link PresentationQuery} and a list of scope strings. + * Resolves a list of {@link VerifiableCredentialContainer} objects based on an incoming {@link PresentationResponseMessage} and a list of scope strings. */ public interface CredentialQueryResolver { @@ -32,5 +33,5 @@ public interface CredentialQueryResolver { * @param query The representation of the query to be executed. * @param issuerScopes The list of issuer scopes to be considered during the query processing. */ - QueryResult query(PresentationQuery query, List issuerScopes); + QueryResult query(PresentationQueryMessage query, List issuerScopes); } \ No newline at end of file diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/CredentialStore.java b/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/CredentialStore.java index 6f9eb7662..38a8209b9 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/CredentialStore.java +++ b/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/CredentialStore.java @@ -15,7 +15,7 @@ package org.eclipse.edc.identityhub.spi.store; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.StoreResult; diff --git a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/KeyPairResourceStore.java b/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/KeyPairResourceStore.java index 35c070e91..d88bcaff9 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/KeyPairResourceStore.java +++ b/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/KeyPairResourceStore.java @@ -14,7 +14,7 @@ package org.eclipse.edc.identityhub.spi.store; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.StoreResult; diff --git a/spi/identity-hub-store-spi/src/test/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResourceTest.java b/spi/identity-hub-store-spi/src/test/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResourceTest.java index 5adc3ae4c..4b17d4ebd 100644 --- a/spi/identity-hub-store-spi/src/test/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResourceTest.java +++ b/spi/identity-hub-store-spi/src/test/java/org/eclipse/edc/identityhub/spi/store/model/VerifiableCredentialResourceTest.java @@ -14,6 +14,8 @@ package org.eclipse.edc.identityhub.spi.store.model; +import org.eclipse.edc.identityhub.spi.model.VcState; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +47,7 @@ void verifyBuilder_assertDefaultValues() { .build(); assertThat(vc.getClock()).isNotNull(); - assertThat(vc.id).isNotNull(); + assertThat(vc.getId()).isNotNull(); assertThat(vc.getStateAsEnum()).isEqualTo(VcState.INITIAL); } } \ No newline at end of file diff --git a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java index 92c952073..9d6f5e4a9 100644 --- a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java +++ b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/CredentialStoreTestBase.java @@ -15,9 +15,9 @@ package org.eclipse.edc.identityhub.store.test; import org.assertj.core.api.Assertions; +import org.eclipse.edc.identityhub.spi.model.VcState; +import org.eclipse.edc.identityhub.spi.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.store.CredentialStore; -import org.eclipse.edc.identityhub.spi.store.model.VcState; -import org.eclipse.edc.identityhub.spi.store.model.VerifiableCredentialResource; import org.eclipse.edc.identitytrust.model.CredentialFormat; import org.eclipse.edc.identitytrust.model.CredentialSubject; import org.eclipse.edc.identitytrust.model.Issuer; diff --git a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/KeyPairResourceStoreTestBase.java b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/KeyPairResourceStoreTestBase.java index d2cfa59a3..b90b299b5 100644 --- a/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/KeyPairResourceStoreTestBase.java +++ b/spi/identity-hub-store-spi/src/testFixtures/java/org/eclipse/edc/identityhub/store/test/KeyPairResourceStoreTestBase.java @@ -15,9 +15,9 @@ package org.eclipse.edc.identityhub.store.test; import org.assertj.core.api.Assertions; +import org.eclipse.edc.identityhub.spi.model.KeyPairResource; +import org.eclipse.edc.identityhub.spi.model.KeyPairState; import org.eclipse.edc.identityhub.spi.store.KeyPairResourceStore; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairResource; -import org.eclipse.edc.identityhub.spi.store.model.KeyPairState; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; import org.junit.jupiter.api.Test;