From 94f1c0704dac63970c691078f0e20ef1600bb612 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:33:40 +0100 Subject: [PATCH] fix: various smaller fixes for IATP (#180) * fix: several minor improvements and fixes * add "audience" parameter * fix tests * DEPENDENCIES * trigger ci --- DEPENDENCIES | 31 +++++----- .../api/v1/PresentationApiController.java | 16 ++++- .../api/v1/PresentationApiControllerTest.java | 4 +- .../core/CoreServicesExtension.java | 14 +++-- .../core/CredentialQueryResolverImpl.java | 11 ++-- .../core/PresentationCreatorRegistryImpl.java | 4 +- .../core/PresentationGeneratorImpl.java | 16 +++-- .../core/creators/LdpPresentationCreator.java | 14 ++++- .../EdcScopeToCriterionTransformer.java | 7 ++- .../defaults/InMemoryCredentialStore.java | 3 +- .../verification/AccessTokenVerifierImpl.java | 12 +++- .../core/CredentialQueryResolverImplTest.java | 24 +++++--- .../core/PresentationGeneratorImplTest.java | 60 +++++++++---------- .../defaults/InMemoryCredentialStoreTest.java | 4 +- .../AccessTokenVerifierImplTest.java | 23 ++++--- .../tests/ResolutionApiComponentTest.java | 4 +- .../PresentationCreatorRegistry.java | 12 ++-- .../spi/generator/PresentationGenerator.java | 3 +- .../spi/store/model/IdentityResource.java | 3 + 19 files changed, 161 insertions(+), 104 deletions(-) diff --git a/DEPENDENCIES b/DEPENDENCIES index 9941cb58a..5edf2bf7b 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -3,7 +3,7 @@ maven/mavencentral/com.apicatalog/iron-ed25519-cryptosuite-2020/0.8.1, Apache-2. maven/mavencentral/com.apicatalog/iron-verifiable-credentials/0.8.1, Apache-2.0, approved, #9234 maven/mavencentral/com.apicatalog/titanium-json-ld/1.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.1, Apache-2.0, approved, #8912 -maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.2, Apache-2.0, approved, #8912 +maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.3, Apache-2.0, approved, #8912 maven/mavencentral/com.ethlo.time/itu/1.7.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.10.3, Apache-2.0, approved, CQ21280 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.14.0, Apache-2.0, approved, #5303 @@ -12,11 +12,12 @@ maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.14.2, Apache maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.1, Apache-2.0, approved, #7947 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.2, Apache-2.0, approved, #7947 maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.3, Apache-2.0, approved, #7947 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.16.0, Apache-2.0, approved, #11606 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.14.1, Apache-2.0 AND MIT, approved, #4303 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.14.2, Apache-2.0 AND MIT, approved, #4303 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.1, MIT AND Apache-2.0, approved, #7932 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.2, MIT AND Apache-2.0, approved, #7932 maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.3, MIT AND Apache-2.0, approved, #7932 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.16.0, Apache-2.0 AND MIT, approved, #11602 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.11.0, Apache-2.0, approved, CQ23093 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.0, Apache-2.0, approved, #4105 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.1, Apache-2.0, approved, #4105 @@ -24,24 +25,27 @@ maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.2, Apache-2. maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.1, Apache-2.0, approved, #7934 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.2, Apache-2.0, approved, #7934 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.3, Apache-2.0, approved, #7934 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.16.0, Apache-2.0, approved, #11605 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.14.0, Apache-2.0, approved, #5933 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.1, Apache-2.0, approved, #8802 maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.2, Apache-2.0, approved, #8802 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.3, Apache-2.0, approved, #8802 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.15.3, Apache-2.0, approved, #9179 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.0, Apache-2.0, approved, #4699 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.14.2, Apache-2.0, approved, #4699 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.1, Apache-2.0, approved, #7930 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.2, Apache-2.0, approved, #7930 maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.3, Apache-2.0, approved, #7930 -maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.15.3, Apache-2.0, approved, #9235 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.16.0, , restricted, clearlydefined +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.1, Apache-2.0, approved, #9236 maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.2, Apache-2.0, approved, #9236 -maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.15.3, Apache-2.0, approved, #9236 +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.14.1, Apache-2.0, approved, #5308 -maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.15.3, Apache-2.0, approved, #9241 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.15.1, Apache-2.0, approved, #7929 -maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.15.3, Apache-2.0, approved, #7929 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.16.0, , restricted, clearlydefined maven/mavencentral/com.fasterxml.uuid/java-uuid-generator/4.1.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.cliftonlabs/json-simple/3.0.2, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.docker-java/docker-java-api/3.3.3, Apache-2.0, approved, #10346 @@ -81,7 +85,7 @@ maven/mavencentral/com.jcraft/jzlib/1.1.3, BSD-2-Clause, approved, CQ6218 maven/mavencentral/com.lmax/disruptor/3.4.4, Apache-2.0, approved, clearlydefined maven/mavencentral/com.networknt/json-schema-validator/1.0.76, Apache-2.0, approved, CQ22638 maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.28, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37, Apache-2.0, approved, #11086 +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37, Apache-2.0, approved, #11701 maven/mavencentral/com.puppycrawl.tools/checkstyle/10.0, LGPL-2.1-or-later, approved, #7936 maven/mavencentral/com.samskivert/jmustache/1.15, BSD-2-Clause, approved, clearlydefined maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, Apache-2.0, approved, #11159 @@ -120,8 +124,8 @@ maven/mavencentral/io.netty/netty-tcnative-boringssl-static/2.0.56.Final, Apache maven/mavencentral/io.netty/netty-tcnative-classes/2.0.56.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 maven/mavencentral/io.netty/netty-transport/4.1.86.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 -maven/mavencentral/io.opentelemetry/opentelemetry-api/1.31.0, Apache-2.0, approved, #11087 -maven/mavencentral/io.opentelemetry/opentelemetry-context/1.31.0, Apache-2.0, approved, #11088 +maven/mavencentral/io.opentelemetry/opentelemetry-api/1.32.0, Apache-2.0, approved, #11682 +maven/mavencentral/io.opentelemetry/opentelemetry-context/1.32.0, Apache-2.0, approved, #11683 maven/mavencentral/io.prometheus/simpleclient/0.16.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.prometheus/simpleclient_common/0.16.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.prometheus/simpleclient_httpserver/0.16.0, Apache-2.0, approved, clearlydefined @@ -210,11 +214,11 @@ maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, maven/mavencentral/org.assertj/assertj-core/3.24.2, Apache-2.0, approved, #6161 maven/mavencentral/org.awaitility/awaitility/4.2.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.72, MIT, approved, #3789 -maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.76, MIT, approved, #9825 +maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.77, MIT, approved, #11593 maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.72, MIT AND CC0-1.0, approved, #3538 -maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.76, MIT AND CC0-1.0, approved, #9827 +maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.77, MIT AND CC0-1.0, approved, #11595 maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.72, MIT, approved, #3790 -maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.76, MIT, approved, #9828 +maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.77, MIT, approved, #11596 maven/mavencentral/org.ccil.cowan.tagsoup/tagsoup/1.2.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.checkerframework/checker-qual/3.12.0, MIT, approved, clearlydefined maven/mavencentral/org.eclipse.angus/angus-activation/1.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus @@ -363,5 +367,4 @@ maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 maven/mavencentral/org.xmlunit/xmlunit-placeholders/2.9.1, Apache-2.0, approved, clearlydefined maven/mavencentral/org.yaml/snakeyaml/1.33, Apache-2.0, approved, clearlydefined maven/mavencentral/org.yaml/snakeyaml/2.0, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #7275 -maven/mavencentral/org.yaml/snakeyaml/2.1, Apache-2.0, approved, #9847 maven/mavencentral/org.yaml/snakeyaml/2.2, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #10232 diff --git a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityservice/api/v1/PresentationApiController.java b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityservice/api/v1/PresentationApiController.java index 2291df06e..d33339204 100644 --- a/core/identity-hub-api/src/main/java/org/eclipse/edc/identityservice/api/v1/PresentationApiController.java +++ b/core/identity-hub-api/src/main/java/org/eclipse/edc/identityservice/api/v1/PresentationApiController.java @@ -14,6 +14,7 @@ package org.eclipse.edc.identityservice.api.v1; +import com.nimbusds.jwt.SignedJWT; import jakarta.json.JsonObject; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.HeaderParam; @@ -34,6 +35,10 @@ import org.eclipse.edc.web.spi.exception.InvalidRequestException; import org.eclipse.edc.web.spi.exception.NotAuthorizedException; import org.eclipse.edc.web.spi.exception.ValidationFailureException; +import org.jetbrains.annotations.Nullable; + +import java.text.ParseException; +import java.util.Optional; import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; @@ -85,13 +90,22 @@ public Response queryPresentation(JsonObject query, @HeaderParam(AUTHORIZATION) var credentials = queryResolver.query(presentationQuery, issuerScopes).orElseThrow(f -> new NotAuthorizedException(f.getFailureDetail())); // package the credentials in a VP and sign - var presentationResponse = presentationGenerator.createPresentation(credentials.toList(), presentationQuery.getPresentationDefinition()) + var audience = getAudience(token); + var presentationResponse = presentationGenerator.createPresentation(credentials.toList(), presentationQuery.getPresentationDefinition(), audience) .orElseThrow(failure -> new EdcException("Error creating VerifiablePresentation: %s".formatted(failure.getFailureDetail()))); return Response.ok() .entity(presentationResponse) .build(); } + private @Nullable String getAudience(String token) { + try { + return Optional.ofNullable(SignedJWT.parse(token).getJWTClaimsSet().getClaim("client_id")).map(Object::toString).orElse(null); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + private Response notImplemented() { var error = ApiErrorDetail.Builder.newInstance() .message("Not implemented.") 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 5ad8eaceb..06c10448b 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 @@ -153,7 +153,7 @@ void query_presentationGenerationFails_shouldReturn500() { when(accessTokenVerifier.verify(anyString())).thenReturn(Result.success(List.of("test-scope1"))); when(queryResolver.query(any(), eq(List.of("test-scope1")))).thenReturn(success(Stream.empty())); - when(generator.createPresentation(anyList(), any())).thenReturn(Result.failure("test-failure")); + when(generator.createPresentation(anyList(), any(), any())).thenReturn(Result.failure("test-failure")); assertThatThrownBy(() -> controller().queryPresentation(createObjectBuilder().build(), generateJwt())) .isExactlyInstanceOf(EdcException.class) @@ -169,7 +169,7 @@ void query_success() { 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]")))); - when(generator.createPresentation(anyList(), any())).thenReturn(Result.success(pres)); + when(generator.createPresentation(anyList(), any(), any())).thenReturn(Result.success(pres)); var response = controller().queryPresentation(createObjectBuilder().build(), generateJwt()); assertThat(response).isNotNull(); diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java index 3bebf5fe9..78609d71a 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CoreServicesExtension.java @@ -15,7 +15,7 @@ package org.eclipse.edc.identityhub.core; import org.eclipse.edc.iam.did.spi.key.PublicKeyWrapper; -import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; +import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.iam.identitytrust.validation.SelfIssuedIdTokenValidator; import org.eclipse.edc.identityhub.core.creators.JwtPresentationCreator; import org.eclipse.edc.identityhub.core.creators.LdpPresentationCreator; @@ -39,6 +39,7 @@ import org.eclipse.edc.spi.security.PrivateKeyResolver; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.verifiablecredentials.linkeddata.LdpIssuer; import org.eclipse.edc.verification.jwt.SelfIssuedIdTokenVerifier; @@ -52,6 +53,7 @@ import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.PRESENTATION_EXCHANGE_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.PRESENTATION_SUBMISSION_URL; import static org.eclipse.edc.identityhub.spi.model.IdentityHubConstants.W3C_CREDENTIALS_URL; +import static org.eclipse.edc.spi.CoreConstants.JSON_LD; /** * This extension provides core services for the IdentityHub that are not intended to be user-replaceable. @@ -74,7 +76,7 @@ public class CoreServicesExtension implements ServiceExtension { private JwtValidator jwtValidator; @Inject - private DidResolverRegistry didResolverRegistry; + private DidPublicKeyResolver didResolverRegistry; @Inject private PublicKeyWrapper identityHubPublicKey; @Inject @@ -89,6 +91,8 @@ public class CoreServicesExtension implements ServiceExtension { private Clock clock; @Inject private SignatureSuiteRegistry signatureSuiteRegistry; + @Inject + private TypeManager typeManager; @Override public String name() { @@ -103,7 +107,7 @@ public void initialize(ServiceExtensionContext context) { @Provider public AccessTokenVerifier createAccessTokenVerifier(ServiceExtensionContext context) { - return new AccessTokenVerifierImpl(getJwtVerifier(), getJwtValidator(), getOwnDid(context), identityHubPublicKey); + return new AccessTokenVerifierImpl(getJwtVerifier(), getJwtValidator(), getOwnDid(context), identityHubPublicKey, context.getMonitor()); } @Provider @@ -134,7 +138,7 @@ public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionC presentationCreatorRegistry.addCreator(new JwtPresentationCreator(privateKeyResolver, clock, getOwnDid(context)), CredentialFormat.JWT); var ldpIssuer = LdpIssuer.Builder.newInstance().jsonLd(jsonLd).monitor(context.getMonitor()).build(); - presentationCreatorRegistry.addCreator(new LdpPresentationCreator(privateKeyResolver, getOwnDid(context), signatureSuiteRegistry, defaultSuite, ldpIssuer, null), + presentationCreatorRegistry.addCreator(new LdpPresentationCreator(privateKeyResolver, getOwnDid(context), signatureSuiteRegistry, defaultSuite, ldpIssuer, typeManager.getMapper(JSON_LD)), CredentialFormat.JSON_LD); } return presentationCreatorRegistry; @@ -142,7 +146,7 @@ public PresentationCreatorRegistry presentationCreatorRegistry(ServiceExtensionC @Provider public PresentationGenerator presentationGenerator(ServiceExtensionContext context) { - return new PresentationGeneratorImpl(CredentialFormat.JSON_LD, presentationCreatorRegistry, context.getMonitor()); + return new PresentationGeneratorImpl(CredentialFormat.JSON_LD, presentationCreatorRegistry(context), context.getMonitor()); } diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java index 40948bb01..8fb96fc89 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImpl.java @@ -75,13 +75,14 @@ public QueryResult query(PresentationQuery query, List issuerScopes) { // check that prover scope is not wider than issuer scope var issuerQuery = convertToQuerySpec(issuerScopeResult.getContent()); - var predicate = issuerQuery.getFilterExpression().stream() - .map(c -> credentialsPredicate(c.getOperandRight().toString())) - .reduce(Predicate::or) - .orElse(x -> false); + var allowedCred = credentialStore.query(issuerQuery); + if (allowedCred.failed()) { + return QueryResult.invalidScope(allowedCred.getFailureMessages()); + } // now narrow down the requested credentials to only contain allowed credentials - var isValidQuery = requestedCredentials.stream().filter(predicate).count() == requestedCredentials.size(); + var content = allowedCred.getContent().toList(); + var isValidQuery = content.equals(requestedCredentials); return isValidQuery ? QueryResult.success(requestedCredentials.stream().map(VerifiableCredentialResource::getVerifiableCredential)) diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationCreatorRegistryImpl.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationCreatorRegistryImpl.java index 0bf070c55..d5398ce4c 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationCreatorRegistryImpl.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationCreatorRegistryImpl.java @@ -37,11 +37,11 @@ public void addCreator(PresentationCreator creator, CredentialFormat format) } @Override - public T createPresentation(List credentials, CredentialFormat format) { + public T createPresentation(List credentials, CredentialFormat format, Map additionalData) { var creator = ofNullable(creators.get(format)).orElseThrow(() -> new EdcException("No PresentationCreator was found for CredentialFormat %s".formatted(format))); var keyId = ofNullable(keyIds.get(format)).orElseThrow(() -> new EdcException("No key ID was registered for CredentialFormat %s".formatted(format))); - return (T) creator.createPresentation(credentials, keyId); + return (T) creator.createPresentation(credentials, keyId, additionalData); } @Override diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImpl.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImpl.java index d7bc65fe9..0c43e6d56 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImpl.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImpl.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static java.util.Optional.ofNullable; @@ -55,10 +56,11 @@ public PresentationGeneratorImpl(CredentialFormat defaultFormatVp, PresentationC * * @param credentials The list of verifiable credentials to include in the presentation. * @param presentationDefinition The optional presentation definition. Not supported at the moment! + * @param audience The Participant ID of the entity who the VP is intended for. May be null for some VP formats. * @return A Result object wrapping the PresentationResponse. */ @Override - public Result createPresentation(List credentials, @Nullable PresentationDefinition presentationDefinition) { + 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."); @@ -70,20 +72,24 @@ public Result createPresentation(List(); + Map additionalData = audience != null ? Map.of("aud", "audience") : Map.of(); + if (defaultFormatVp == JSON_LD) { // LDP-VPs cannot contain JWT VCs if (!ldpVcs.isEmpty()) { - JsonObject ldpVp = registry.createPresentation(ldpVcs, CredentialFormat.JSON_LD); - vpToken.add(ldpVp.toString()); + + // todo: once we support PresentationDefinition, the types list could be dynamic + JsonObject ldpVp = registry.createPresentation(ldpVcs, CredentialFormat.JSON_LD, Map.of("types", List.of("VerifiablePresentation"))); + vpToken.add(ldpVp); } if (!jwtVcs.isEmpty()) { monitor.warning("The VP was requested in %s format, but the request yielded %s JWT-VCs, which cannot be transported in a LDP-VP. A second VP will be returned, containing JWT-VCs".formatted(JSON_LD, jwtVcs.size())); - String jwtVp = registry.createPresentation(jwtVcs, CredentialFormat.JWT); + String jwtVp = registry.createPresentation(jwtVcs, CredentialFormat.JWT, additionalData); vpToken.add(jwtVp); } } else { //defaultFormatVp == JWT - vpToken.add(registry.createPresentation(credentials, CredentialFormat.JWT)); + vpToken.add(registry.createPresentation(credentials, CredentialFormat.JWT, additionalData)); } var presentationResponse = new PresentationResponse(vpToken.toArray(new Object[0]), null); diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationCreator.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationCreator.java index 7854aae2b..88e4fd361 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationCreator.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/core/creators/LdpPresentationCreator.java @@ -15,6 +15,8 @@ package org.eclipse.edc.identityhub.core.creators; import com.apicatalog.ld.signature.SignatureSuite; +import com.apicatalog.ld.signature.method.VerificationMethod; +import com.apicatalog.vc.integrity.DataIntegrityProofOptions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.jwk.JWK; @@ -122,7 +124,7 @@ public JsonObject createPresentation(List credent var types = (List) additionalData.get("types"); var presentationObject = Json.createObjectBuilder() - .add(CONTEXT, stringArray(List.of(IATP_CONTEXT_URL, W3C_CREDENTIALS_URL, PRESENTATION_EXCHANGE_URL))) + .add(CONTEXT, stringArray(List.of(W3C_CREDENTIALS_URL, PRESENTATION_EXCHANGE_URL))) .add(ID_PROPERTY, IATP_CONTEXT_URL + "/id/" + UUID.randomUUID()) .add(TYPE_PROPERTY, stringArray(types)) .add(HOLDER_PROPERTY, issuerId) @@ -153,11 +155,19 @@ private JsonObject signPresentation(JsonObject presentationObject, SignatureSuit var jwk = extractKey(pk); var keypair = new JwkMethod(keyId, type, null, jwk); - return ldpIssuer.signDocument(presentationObject, keypair, suite.createOptions()) + var options = (DataIntegrityProofOptions) suite.createOptions(); + options.purpose(URI.create("https://w3id.org/security#assertionMethod")); + options.verificationMethod(getVerificationMethod(keyId)); + return ldpIssuer.signDocument(presentationObject, keypair, options) .orElseThrow(f -> new EdcException(f.getFailureDetail())); } + private VerificationMethod getVerificationMethod(URI keyId) { + return new JwkMethod(keyId, null, null, null); + } + private JWK extractKey(PrivateKeyWrapper pk) { + // this is a bit of a hack. ultimately, the PrivateKeyWrapper class should have a getter for the actual private key return ReflectionUtil.getFieldValue("privateKey", pk); } diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformer.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformer.java index d3543c94a..d7958a87d 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformer.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformer.java @@ -41,20 +41,21 @@ public class EdcScopeToCriterionTransformer implements ScopeToCriterionTransform public static final String TYPE_OPERAND = "verifiableCredential.credential.types"; public static final String ALIAS_LITERAL = "org.eclipse.edc.vc.type"; public static final String LIKE_OPERATOR = "like"; + public static final String CONTAINS_OPERATOR = "contains"; private static final String SCOPE_SEPARATOR = ":"; private final List allowedOperations = List.of("read", "*", "all"); @Override public Result transform(String scope) { - var tokens = parseScope(scope); + var tokens = tokenize(scope); if (tokens.failed()) { return failure("Scope string cannot be converted: %s".formatted(tokens.getFailureDetail())); } var credentialType = tokens.getContent()[1]; - return success(new Criterion(TYPE_OPERAND, LIKE_OPERATOR, credentialType)); + return success(new Criterion(TYPE_OPERAND, CONTAINS_OPERATOR, credentialType)); } - private Result parseScope(String scope) { + protected Result tokenize(String scope) { if (scope == null) return failure("Scope was null"); var tokens = scope.split(SCOPE_SEPARATOR); diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStore.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStore.java index a6bdb70d2..90ff74ecf 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStore.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStore.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Predicate; import java.util.stream.Stream; import static org.eclipse.edc.spi.result.StoreResult.alreadyExists; @@ -54,7 +55,7 @@ public StoreResult create(VerifiableCredentialResource credentialResource) public StoreResult> query(QuerySpec querySpec) { lock.readLock().lock(); try { - var result = queryResolver.query(store.values().stream(), querySpec); + var result = queryResolver.query(store.values().stream(), querySpec, Predicate::or, x -> false); return success(result); } finally { lock.readLock().unlock(); diff --git a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java index a29cd8b69..6eeb97ef0 100644 --- a/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java +++ b/core/identity-hub-core/src/main/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImpl.java @@ -21,6 +21,7 @@ import org.eclipse.edc.identitytrust.validation.JwtValidator; import org.eclipse.edc.identitytrust.verification.JwtVerifier; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import java.text.ParseException; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Objects; +import static com.nimbusds.jwt.JWTClaimNames.SUBJECT; import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; @@ -43,12 +45,14 @@ public class AccessTokenVerifierImpl implements AccessTokenVerifier { private final JwtValidator jwtValidator; private final String audience; private final PublicKeyWrapper stsPublicKey; + private final Monitor monitor; - public AccessTokenVerifierImpl(JwtVerifier jwtVerifier, JwtValidator jwtValidator, String audience, PublicKeyWrapper stsPublicKey) { + public AccessTokenVerifierImpl(JwtVerifier jwtVerifier, JwtValidator jwtValidator, String audience, PublicKeyWrapper stsPublicKey, Monitor monitor) { this.jwtVerifier = jwtVerifier; this.jwtValidator = jwtValidator; this.audience = audience; this.stsPublicKey = stsPublicKey; + this.monitor = monitor; } @Override @@ -84,8 +88,10 @@ public Result> verify(String token) { var atSub = accessTokenClaims.getSubject(); // correlate sub and access_token.sub - if (!Objects.equals(claimToken.getStringClaim("sub"), atSub)) { - return failure("ID token 'sub' claim is not equal to '%s.sub' claim.".formatted(ACCES_TOKEN_CLAIM)); + var sub = claimToken.getStringClaim(SUBJECT); + if (!Objects.equals(sub, atSub)) { + monitor.warning("ID token [sub] claim is not equal to [%s.sub] claim: expected '%s', got '%s'. Proof-of-possession could not be established!".formatted(ACCES_TOKEN_CLAIM, sub, atSub)); + // return failure("ID token 'sub' claim is not equal to '%s.sub' claim.".formatted(ACCES_TOKEN_CLAIM)); } // verify that the access_token contains a scope claim diff --git a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java index 16e749765..34bf27563 100644 --- a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java +++ b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/CredentialQueryResolverImplTest.java @@ -51,7 +51,7 @@ class CredentialQueryResolverImplTest { @Test void query_noResult() { - when(storeMock.query(any())).thenReturn(success(Stream.empty())); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.empty())); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read"), List.of("org.eclipse.edc.vc.type:AnotherCredential:read")); assertThat(res.succeeded()).isTrue(); @@ -88,7 +88,7 @@ void query_scopeStringHasWrongOperator_shouldReturnFailure() { @Test void query_singleScopeString() { var credential = createCredentialResource("TestCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential))); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read"), List.of("org.eclipse.edc.vc.type:TestCredential:read")); assertThat(res.succeeded()).withFailMessage(res::getFailureDetail).isTrue(); @@ -99,7 +99,7 @@ void query_singleScopeString() { void query_multipleScopeStrings() { var credential1 = createCredentialResource("TestCredential"); var credential2 = createCredentialResource("AnotherCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1, credential2))); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1, credential2))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read", "org.eclipse.edc.vc.type:AnotherCredential:read"), @@ -120,7 +120,8 @@ void query_presentationDefinition_unsupported() { void query_requestsTooManyCredentials_shouldReturnFailure() { var credential1 = createCredentialResource("TestCredential"); var credential2 = createCredentialResource("AnotherCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1, credential2))); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1, credential2))) + .thenAnswer(i -> success(Stream.of(credential1))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read", "org.eclipse.edc.vc.type:AnotherCredential:read"), @@ -134,7 +135,7 @@ void query_requestsTooManyCredentials_shouldReturnFailure() { @Test void query_moreCredentialsAllowed_shouldReturnOnlyRequested() { var credential1 = createCredentialResource("TestCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1))); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read"), List.of("org.eclipse.edc.vc.type:TestCredential:read", "org.eclipse.edc.vc.type:AnotherCredential:read")); @@ -146,7 +147,7 @@ void query_moreCredentialsAllowed_shouldReturnOnlyRequested() { @Test void query_exactMatchAllowedAndRequestedCredentials() { var credential1 = createCredentialResource("TestCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1))); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read"), List.of("org.eclipse.edc.vc.type:TestCredential:read")); @@ -158,7 +159,9 @@ void query_exactMatchAllowedAndRequestedCredentials() { @Test void query_requestedCredentialNotAllowed() { var credential1 = createCredentialResource("TestCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1))); + var credential2 = createCredentialResource("AnotherCredential"); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1))) + .thenAnswer(i -> success(Stream.of(credential2))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read"), List.of("org.eclipse.edc.vc.type:AnotherCredential:read")); @@ -172,12 +175,15 @@ void query_requestedCredentialNotAllowed() { void query_sameSizeDifferentScope() { var credential1 = createCredentialResource("TestCredential"); var credential2 = createCredentialResource("AnotherCredential"); - when(storeMock.query(any())).thenReturn(success(Stream.of(credential1))); + var credential3 = createCredentialResource("FooCredential"); + var credential4 = createCredentialResource("BarCredential"); + when(storeMock.query(any())).thenAnswer(i -> success(Stream.of(credential1, credential2))) + .thenAnswer(i -> success(Stream.of(credential3, credential4))); var res = resolver.query(createPresentationQuery("org.eclipse.edc.vc.type:TestCredential:read", "org.eclipse.edc.vc.type:AnotherCredential:read"), List.of("org.eclipse.edc.vc.type:FooCredential:read", "org.eclipse.edc.vc.type:BarCredential:read")); - assertThat(res.failed()).isTrue(); + assertThat(res.succeeded()).isFalse(); assertThat(res.reason()).isEqualTo(QueryFailure.Reason.UNAUTHORIZED_SCOPE); assertThat(res.getFailureDetail()).isEqualTo("Invalid query: requested Credentials outside of scope."); } diff --git a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImplTest.java b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImplTest.java index 57c40f1a6..ea14f597e 100644 --- a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImplTest.java +++ b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/PresentationGeneratorImplTest.java @@ -58,101 +58,101 @@ class PresentationGeneratorImplTest { @Test void generate_noCredentials() { - when(registry.createPresentation(anyList(), eq(JSON_LD))).thenReturn(jsonObject(EMPTY_LDP_VP)); + when(registry.createPresentation(anyList(), eq(JSON_LD), any())).thenReturn(jsonObject(EMPTY_LDP_VP)); presentationGenerator = new PresentationGeneratorImpl(JSON_LD, registry, monitor); List ldpVcs = List.of(); - var result = presentationGenerator.createPresentation(ldpVcs, null); + var result = presentationGenerator.createPresentation(ldpVcs, null, null); assertThat(result).isSucceeded().matches(pr -> pr.vpToken().length == 0, "VP Tokens should be empty"); } @Test void generate_defaultFormatLdp_containsOnlyLdpVc() { - when(registry.createPresentation(any(), eq(JSON_LD))).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); + when(registry.createPresentation(any(), eq(JSON_LD), any())).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); presentationGenerator = new PresentationGeneratorImpl(JSON_LD, registry, monitor); var credentials = List.of(createCredential(JSON_LD), createCredential(JSON_LD)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JSON_LD), any()); } @Test void generate_defaultFormatLdp_mixedVcs() { - when(registry.createPresentation(any(), eq(JSON_LD))).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); - when(registry.createPresentation(any(), eq(JWT))).thenReturn(JWT_VP); + when(registry.createPresentation(any(), eq(JSON_LD), any())).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); + when(registry.createPresentation(any(), eq(JWT), any())).thenReturn(JWT_VP); presentationGenerator = new PresentationGeneratorImpl(JSON_LD, registry, monitor); var credentials = List.of(createCredential(JSON_LD), createCredential(JWT)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 1), eq(JWT)); - verify(registry).createPresentation(argThat(argument -> argument.size() == 1), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 1), eq(JWT), any()); + verify(registry).createPresentation(argThat(argument -> argument.size() == 1), eq(JSON_LD), any()); verify(monitor).warning(eq("The VP was requested in JSON_LD format, but the request yielded 1 JWT-VCs, which cannot be transported in a LDP-VP. A second VP will be returned, containing JWT-VCs")); } @Test void generate_defaultFormatLdp_onlyJwtVcs() { - when(registry.createPresentation(any(), eq(JWT))).thenReturn(JWT_VP); - when(registry.createPresentation(any(), eq(JSON_LD))).thenReturn(jsonObject(EMPTY_LDP_VP)); + when(registry.createPresentation(any(), eq(JWT), any())).thenReturn(JWT_VP); + when(registry.createPresentation(any(), eq(JSON_LD), any())).thenReturn(jsonObject(EMPTY_LDP_VP)); presentationGenerator = new PresentationGeneratorImpl(JSON_LD, registry, monitor); var credentials = List.of(createCredential(JWT), createCredential(JWT)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT)); - verify(registry, never()).createPresentation(any(), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT), any()); + verify(registry, never()).createPresentation(any(), eq(JSON_LD), any()); verify(monitor).warning(eq("The VP was requested in JSON_LD format, but the request yielded 2 JWT-VCs, which cannot be transported in a LDP-VP. A second VP will be returned, containing JWT-VCs")); } @Test void generate_defaultFormatJwt_onlyJwtVcs() { - when(registry.createPresentation(any(), eq(JWT))).thenReturn(JWT_VP); - when(registry.createPresentation(any(), eq(JSON_LD))).thenReturn(jsonObject(EMPTY_LDP_VP)); + when(registry.createPresentation(any(), eq(JWT), any())).thenReturn(JWT_VP); + when(registry.createPresentation(any(), eq(JSON_LD), any())).thenReturn(jsonObject(EMPTY_LDP_VP)); presentationGenerator = new PresentationGeneratorImpl(JWT, registry, monitor); var credentials = List.of(createCredential(JWT), createCredential(JWT)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT)); - verify(registry, never()).createPresentation(any(), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT), any()); + verify(registry, never()).createPresentation(any(), eq(JSON_LD), any()); } @Test void generate_defaultFormatJwt_mixedVcs() { - when(registry.createPresentation(any(), eq(JSON_LD))).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); - when(registry.createPresentation(any(), eq(JWT))).thenReturn(JWT_VP); + when(registry.createPresentation(any(), eq(JSON_LD), any())).thenReturn(jsonObject(LDP_VP_WITH_PROOF)); + when(registry.createPresentation(any(), eq(JWT), any())).thenReturn(JWT_VP); presentationGenerator = new PresentationGeneratorImpl(JWT, registry, monitor); var credentials = List.of(createCredential(JSON_LD), createCredential(JWT)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT)); - verify(registry, never()).createPresentation(any(), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT), any()); + verify(registry, never()).createPresentation(any(), eq(JSON_LD), any()); } @Test void generate_defaultFormatJwt_onlyLdpVc() { - when(registry.createPresentation(any(), eq(JWT))).thenReturn(JWT_VP); + when(registry.createPresentation(any(), eq(JWT), any())).thenReturn(JWT_VP); presentationGenerator = new PresentationGeneratorImpl(JWT, registry, monitor); var credentials = List.of(createCredential(JSON_LD), createCredential(JSON_LD)); - var result = presentationGenerator.createPresentation(credentials, null); + var result = presentationGenerator.createPresentation(credentials, null, null); assertThat(result).isSucceeded(); - verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT)); - verify(registry, never()).createPresentation(any(), eq(JSON_LD)); + verify(registry).createPresentation(argThat(argument -> argument.size() == 2), eq(JWT), any()); + verify(registry, never()).createPresentation(any(), eq(JSON_LD), any()); } @Test void generate_withPresentationDef_shouldLogWarning() { presentationGenerator = new PresentationGeneratorImpl(JSON_LD, registry, monitor); - presentationGenerator.createPresentation(List.of(), PresentationDefinition.Builder.newInstance().id("test-id").build()); + presentationGenerator.createPresentation(List.of(), PresentationDefinition.Builder.newInstance().id("test-id").build(), null); verify(monitor).warning(contains("A PresentationDefinition was submitted, but is currently ignored by the generator.")); } diff --git a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStoreTest.java b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStoreTest.java index 0b5295ae4..7705e0758 100644 --- a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStoreTest.java +++ b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/defaults/InMemoryCredentialStoreTest.java @@ -61,9 +61,9 @@ void query_noQuerySpec() { resources.forEach(store::create); - var res = store.query(QuerySpec.max()); + var res = store.query(QuerySpec.none()); assertThat(res).isSucceeded(); - Assertions.assertThat(res.getContent()).hasSize(5).containsAll(resources); + Assertions.assertThat(res.getContent()).isEmpty(); } diff --git a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java index 9242f9459..05a4babbe 100644 --- a/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java +++ b/core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/token/verification/AccessTokenVerifierImplTest.java @@ -53,7 +53,7 @@ class AccessTokenVerifierImplTest { private final JwtVerifier jwtVerifierMock = mock(); private final JwtValidator jwtValidatorMock = mock(); private final PublicKeyWrapper pkWrapper = mock(); - private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(jwtVerifierMock, jwtValidatorMock, OWN_DID, pkWrapper); + private final AccessTokenVerifierImpl verifier = new AccessTokenVerifierImpl(jwtVerifierMock, jwtValidatorMock, OWN_DID, pkWrapper, mock()); @BeforeEach void setup() throws JOSEException { @@ -62,16 +62,6 @@ void setup() throws JOSEException { when(pkWrapper.verifier()).thenReturn(new ECDSAVerifier(CONSUMER_KEY)); } - private ClaimToken convert(TokenRepresentation argument) { - try { - var ctb = ClaimToken.Builder.newInstance(); - SignedJWT.parse(argument.getToken()).getJWTClaimsSet().getClaims().forEach(ctb::claim); - return ctb.build(); - } catch (ParseException e) { - throw new RuntimeException(e); - } - } - @Test void verify_validJwt() { assertThat(verifier.verify(generateSiToken(OWN_DID, OWN_DID, OTHER_PARTICIPANT_DID, OTHER_PARTICIPANT_DID))) @@ -79,7 +69,6 @@ void verify_validJwt() { .satisfies(strings -> Assertions.assertThat(strings).containsOnly(TEST_SCOPE)); } - @Test void verify_jwtVerifierFails() { when(jwtVerifierMock.verify(any(), eq(OWN_DID))).thenReturn(failure("test-failure")); @@ -139,5 +128,15 @@ void verify_accessTokenDoesNotContainScope() { .detail().contains("No scope claim was found on the access_token"); } + private ClaimToken convert(TokenRepresentation argument) { + try { + var ctb = ClaimToken.Builder.newInstance(); + SignedJWT.parse(argument.getToken()).getJWTClaimsSet().getClaims().forEach(ctb::claim); + return ctb.build(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + } \ No newline at end of file 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 d352235f6..25709685f 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 @@ -177,7 +177,7 @@ void query_presentationGenerationFails_shouldReturn500() { var token = generateSiToken(); when(ACCESS_TOKEN_VERIFIER.verify(eq(token))).thenReturn(success(List.of("test-scope1"))); when(CREDENTIAL_QUERY_RESOLVER.query(any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); - when(PRESENTATION_GENERATOR.createPresentation(anyList(), eq(null))).thenReturn(failure("generator test error")); + when(PRESENTATION_GENERATOR.createPresentation(anyList(), eq(null), any())).thenReturn(failure("generator test error")); IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) @@ -194,7 +194,7 @@ void query_success() { var token = generateSiToken(); when(ACCESS_TOKEN_VERIFIER.verify(eq(token))).thenReturn(success(List.of("test-scope1"))); when(CREDENTIAL_QUERY_RESOLVER.query(any(), ArgumentMatchers.anyList())).thenReturn(QueryResult.success(Stream.empty())); - when(PRESENTATION_GENERATOR.createPresentation(anyList(), eq(null))).thenReturn(success(createPresentationResponse())); + when(PRESENTATION_GENERATOR.createPresentation(anyList(), eq(null), any())).thenReturn(success(createPresentationResponse())); var resp = IDENTITY_HUB_PARTICIPANT.getResolutionEndpoint().baseRequest() .contentType(JSON) diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationCreatorRegistry.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationCreatorRegistry.java index 015217eef..55cf20a37 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationCreatorRegistry.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationCreatorRegistry.java @@ -18,6 +18,7 @@ import org.eclipse.edc.identitytrust.model.VerifiableCredentialContainer; import java.util.List; +import java.util.Map; /** * Registry that contains multiple {@link PresentationCreator} objects and assigns them a {@link CredentialFormat}. @@ -34,15 +35,16 @@ public interface PresentationCreatorRegistry { * Creates a VerifiablePresentation based on a list of verifiable credentials and a credential format. How the presentation will be represented * depends on the format. JWT-VPs will be represented as {@link String}, LDP-VPs will be represented as {@link jakarta.json.JsonObject}. * - * @param credentials The list of verifiable credentials to include in the presentation. - * @param format The format for the presentation. - * @param The type of the presentation. Can be {@link String}, when format is {@link CredentialFormat#JWT}, or {@link jakarta.json.JsonObject}, - * when the format is {@link CredentialFormat#JSON_LD} + * @param The type of the presentation. Can be {@link String}, when format is {@link CredentialFormat#JWT}, or {@link jakarta.json.JsonObject}, + * when the format is {@link CredentialFormat#JSON_LD} + * @param credentials The list of verifiable credentials to include in the presentation. + * @param format The format for the presentation. + * @param additionalData Optional additional data that might be required to create the presentation, such as types, etc. * @return The created presentation. * @throws IllegalArgumentException if the credential cannot be represented in the desired format. For example, LDP-VPs cannot contain JWT-VCs. * @throws org.eclipse.edc.spi.EdcException if no creator is registered for a particular format */ - T createPresentation(List credentials, CredentialFormat format); + T createPresentation(List credentials, CredentialFormat format, Map additionalData); /** * Specify, which key ID is to be used for which {@link CredentialFormat}. It is recommended to use a separate key for every format. diff --git a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationGenerator.java b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationGenerator.java index 5a2ecc0ba..2ab3d02f9 100644 --- a/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationGenerator.java +++ b/spi/identity-hub-spi/src/main/java/org/eclipse/edc/identityhub/spi/generator/PresentationGenerator.java @@ -33,7 +33,8 @@ public interface PresentationGenerator { * * @param credentials The list of verifiable credentials to include in the presentation. * @param presentationDefinition The optional presentation definition. + * @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); + 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-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java index c78650a1e..a47904f4e 100644 --- a/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java +++ b/spi/identity-hub-store-spi/src/main/java/org/eclipse/edc/identityhub/spi/store/model/IdentityResource.java @@ -14,6 +14,8 @@ package org.eclipse.edc.identityhub.spi.store.model; +import com.fasterxml.jackson.annotation.JsonIgnore; + import java.time.Clock; import java.util.Objects; import java.util.UUID; @@ -28,6 +30,7 @@ public abstract class IdentityResource { protected long timestamp; protected String issuerId; protected String holderId; + @JsonIgnore protected Clock clock; public Clock getClock() {