diff --git a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImpl.java b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImpl.java index 2d65fcf607d..18bc0b419cb 100644 --- a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImpl.java +++ b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImpl.java @@ -20,6 +20,7 @@ import org.eclipse.edc.util.reflection.ReflectionUtil; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -40,6 +41,7 @@ public Predicate convert(Criterion criterion) { case "=" -> equalPredicate(criterion); case "in" -> inPredicate(criterion); case "like" -> likePredicate(criterion); + case "contains" -> containsPredicate(criterion); default -> throw new IllegalArgumentException(format("Operator [%s] is not supported by this converter!", criterion.getOperator())); }; @@ -53,6 +55,23 @@ protected Object property(String key, Object object) { } } + private Predicate containsPredicate(Criterion criterion) { + return t -> { + var operandLeft = (String) criterion.getOperandLeft(); + var operandRight = criterion.getOperandRight(); + var property = property(operandLeft, t); + if (property == null) { + return false; + } + + if (property instanceof Collection collection) { + return collection.contains(operandRight); + } + + return false; + }; + } + @NotNull private Predicate equalPredicate(Criterion criterion) { return t -> { diff --git a/core/common/connector-core/src/test/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImplTest.java b/core/common/connector-core/src/test/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImplTest.java index 7f25dd72c7f..d5ba3dbd21d 100644 --- a/core/common/connector-core/src/test/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImplTest.java +++ b/core/common/connector-core/src/test/java/org/eclipse/edc/connector/core/store/CriterionToPredicateConverterImplTest.java @@ -129,6 +129,39 @@ void like_shouldThrowException_whenPropertyDoesNotExits() { assertThat(predicate).rejects(new TestObject(List.of(new NestedObject("any")))); } + @Test + void contains_success() { + var predicate = converter.convert(new Criterion("values", "contains", "bar")); + assertThat(predicate).accepts(new StringTestObject(List.of("foo", "bar"))); + } + + @Test + void contains_typesNotMatch() { + var predicate = converter.convert(new Criterion("values", "contains", 42)); + assertThat(predicate).rejects(new StringTestObject(List.of("foo", "bar"))); + + var predicate2 = converter.convert(new Criterion("values", "contains", "42")); + assertThat(predicate2).accepts(new StringTestObject(List.of("foo", "bar", "42"))); + } + + @Test + void contains_notCollection() { + var predicate = converter.convert(new Criterion("value", "contains", 42)); + assertThat(predicate).rejects(new TestObject("someval")); + } + + @Test + void contains_notContained() { + var predicate = converter.convert(new Criterion("values", "contains", "baz")); + assertThat(predicate).rejects(new StringTestObject(List.of("foo", "bar"))); + } + + @Test + void contains_propertyDoesNotExist() { + var predicate = converter.convert(new Criterion("notexist", "contains", "bar")); + assertThat(predicate).rejects(new StringTestObject(List.of("foo", "bar"))); + } + public enum TestEnum { ENTRY1, ENTRY2 } @@ -167,6 +200,10 @@ public String toString() { } } - private record NestedObject(String value) { } + private record NestedObject(String value) { + } + + private record StringTestObject(List values) { + } } diff --git a/extensions/common/crypto/jws2020/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java b/extensions/common/crypto/jws2020/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java index 1d11b6de8b6..c1caaa3bda0 100644 --- a/extensions/common/crypto/jws2020/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java +++ b/extensions/common/crypto/jws2020/src/main/java/org/eclipse/edc/security/signature/jws2020/Jws2020Schema.java @@ -75,7 +75,7 @@ public static LdSchema create(ObjectMapper mapper) { type(JSON_WEB_SIGNATURE_TYPE).required(), property(CREATED, xsdDateTime()) .test(created -> Instant.now().isAfter(created)) - .required(), + .optional(), property(CONTROLLER, link()), property(PURPOSE, link()).required().test(uri -> uri.toString().equals("https://w3id.org/security#assertionMethod")), verificationMethod(VERIFICATION_METHOD, getVerificationMethod(mapper).map(new JwkAdapter())).required(), diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java index 8db219cbd87..05ed801d96e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IatpDefaultServicesExtension.java @@ -14,39 +14,30 @@ package org.eclipse.edc.iam.identitytrust.core; -import jakarta.json.Json; -import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultCredentialServiceClient; import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultTrustedIssuerRegistry; import org.eclipse.edc.iam.identitytrust.core.defaults.InMemorySignatureSuiteRegistry; import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorRegistry; import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService; -import org.eclipse.edc.identitytrust.CredentialServiceClient; +import org.eclipse.edc.identitytrust.AudienceResolver; import org.eclipse.edc.identitytrust.SecureTokenService; import org.eclipse.edc.identitytrust.TrustedIssuerRegistry; import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry; import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry; -import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.jwt.TokenGenerationServiceImpl; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.http.EdcHttpClient; import org.eclipse.edc.spi.security.KeyPairFactory; 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.transform.spi.TypeTransformerRegistry; import java.security.KeyPair; import java.time.Clock; -import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; -import static org.eclipse.edc.spi.CoreConstants.JSON_LD; - @Extension("Identity And Trust Extension to register default services") public class IatpDefaultServicesExtension implements ServiceExtension { @@ -54,8 +45,6 @@ public class IatpDefaultServicesExtension implements ServiceExtension { public static final String STS_PRIVATE_KEY_ALIAS = "edc.iam.sts.privatekey.alias"; @Setting(value = "Alias of public key used for verifying the tokens, retrieved from the vault", defaultValue = "A random EC public key") public static final String STS_PUBLIC_KEY_ALIAS = "edc.iam.sts.publickey.alias"; - @Setting(value = "URL of the CredentialService used to present credentials", required = true) - public static final String CREDENTIALSERVICE_URL_PROPERTY = "edc.iam.credentialservice.url"; // not a setting, it's defined in Oauth2ServiceExtension private static final String OAUTH_TOKENURL_PROPERTY = "edc.oauth.token.url"; @Setting(value = "Self-issued ID Token expiration in minutes. By default is 5 minutes", defaultValue = "" + IatpDefaultServicesExtension.DEFAULT_STS_TOKEN_EXPIRATION_MIN) @@ -68,18 +57,6 @@ public class IatpDefaultServicesExtension implements ServiceExtension { @Inject private Clock clock; - @Inject - private TypeTransformerRegistry typeTransformerRegistry; - - @Inject - private EdcHttpClient httpClient; - - @Inject - private TypeManager typeManager; - - @Inject - private JsonLd jsonLd; - @Provider(isDefault = true) public SecureTokenService createDefaultTokenService(ServiceExtensionContext context) { context.getMonitor().info("Using the Embedded STS client, as no other implementation was provided."); @@ -111,10 +88,8 @@ public ScopeExtractorRegistry scopeExtractorRegistry() { } @Provider(isDefault = true) - public CredentialServiceClient createClient(ServiceExtensionContext context) { - return new DefaultCredentialServiceClient(httpClient, Json.createBuilderFactory(Map.of()), - typeManager.getMapper(JSON_LD), typeTransformerRegistry, jsonLd, context.getMonitor(), - context.getConfig().getString(CREDENTIALSERVICE_URL_PROPERTY)); + public AudienceResolver identityResolver() { + return identity -> identity; } private KeyPair keyPairFromConfig(ServiceExtensionContext context) { diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java index 3906ae96f8a..fe8d752c627 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtension.java @@ -14,8 +14,10 @@ package org.eclipse.edc.iam.identitytrust.core; +import jakarta.json.Json; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.iam.identitytrust.IdentityAndTrustService; +import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultCredentialServiceClient; import org.eclipse.edc.iam.identitytrust.validation.SelfIssuedIdTokenValidator; import org.eclipse.edc.iam.identitytrust.verification.MultiFormatPresentationVerifier; import org.eclipse.edc.identitytrust.CredentialServiceClient; @@ -30,15 +32,18 @@ import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.http.EdcHttpClient; import org.eclipse.edc.spi.iam.IdentityService; 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.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.verifiablecredentials.jwt.JwtPresentationVerifier; import org.eclipse.edc.verifiablecredentials.linkeddata.LdpVerifier; import org.eclipse.edc.verification.jwt.SelfIssuedIdTokenVerifier; import java.time.Clock; +import java.util.Map; import static org.eclipse.edc.spi.CoreConstants.JSON_LD; @@ -46,7 +51,7 @@ public class IdentityAndTrustExtension implements ServiceExtension { @Setting(value = "DID of this connector", required = true) - public static final String ISSUER_DID_PROPERTY = "edc.iam.issuer.id"; + public static final String CONNECTOR_DID_PROPERTY = "edc.iam.issuer.id"; @Inject @@ -73,6 +78,10 @@ public class IdentityAndTrustExtension implements ServiceExtension { @Inject private Clock clock; + @Inject + private EdcHttpClient httpClient; + @Inject + private TypeTransformerRegistry typeTransformerRegistry; private JwtValidator jwtValidator; @@ -81,8 +90,8 @@ public class IdentityAndTrustExtension implements ServiceExtension { @Provider public IdentityService createIdentityService(ServiceExtensionContext context) { - return new IdentityAndTrustService(secureTokenService, getIssuerDid(context), context.getParticipantId(), getPresentationVerifier(context), - credentialServiceClient, getJwtValidator(), getJwtVerifier(), registry, clock); + return new IdentityAndTrustService(secureTokenService, getOwnDid(context), context.getParticipantId(), getPresentationVerifier(context), + getCredentialServiceclient(context), getJwtValidator(), getJwtVerifier(), registry, clock); } @Provider @@ -93,6 +102,15 @@ public JwtValidator getJwtValidator() { return jwtValidator; } + @Provider + public CredentialServiceClient getCredentialServiceclient(ServiceExtensionContext context) { + if (credentialServiceClient == null) { + credentialServiceClient = new DefaultCredentialServiceClient(httpClient, Json.createBuilderFactory(Map.of()), + typeManager.getMapper(JSON_LD), typeTransformerRegistry, jsonLd, context.getMonitor()); + } + return credentialServiceClient; + } + @Provider public PresentationVerifier getPresentationVerifier(ServiceExtensionContext context) { if (presentationVerifier == null) { @@ -120,11 +138,6 @@ public JwtVerifier getJwtVerifier() { private String getOwnDid(ServiceExtensionContext context) { - // todo: this must be config value - return null; - } - - private String getIssuerDid(ServiceExtensionContext context) { - return context.getConfig().getString(ISSUER_DID_PROPERTY); + return context.getConfig().getString(CONNECTOR_DID_PROPERTY); } } diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClient.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClient.java index a5450b7740f..28327e4fb78 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClient.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClient.java @@ -52,20 +52,18 @@ public class DefaultCredentialServiceClient implements CredentialServiceClient { private final TypeTransformerRegistry transformerRegistry; private final JsonLd jsonLd; private final Monitor monitor; - private final String credentialServiceUrl; - public DefaultCredentialServiceClient(EdcHttpClient httpClient, JsonBuilderFactory jsonFactory, ObjectMapper jsonLdMapper, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, Monitor monitor, String credentialServiceUrl) { + public DefaultCredentialServiceClient(EdcHttpClient httpClient, JsonBuilderFactory jsonFactory, ObjectMapper jsonLdMapper, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, Monitor monitor) { this.httpClient = httpClient; this.jsonFactory = jsonFactory; this.objectMapper = jsonLdMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); this.transformerRegistry = transformerRegistry; this.jsonLd = jsonLd; this.monitor = monitor; - this.credentialServiceUrl = credentialServiceUrl; } @Override - public Result> requestPresentation(String selfIssuedTokenJwt, List scopes) { + public Result> requestPresentation(String credentialServiceUrl, String selfIssuedTokenJwt, List scopes) { var query = createPresentationQuery(scopes); try { diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java index bbbd0b12d7c..5fd72093b0e 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/IdentityAndTrustExtensionTest.java @@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.spi.CoreConstants.JSON_LD; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -49,12 +50,12 @@ void setUp(ServiceExtensionContext context) { @Test void verifyCorrectService(IdentityAndTrustExtension extension) { var configMock = mock(Config.class); - when(configMock.getString(eq(IdentityAndTrustExtension.ISSUER_DID_PROPERTY))).thenReturn("did:web:test"); + when(configMock.getString(eq(IdentityAndTrustExtension.CONNECTOR_DID_PROPERTY))).thenReturn("did:web:test"); when(spiedContext.getConfig()).thenReturn(configMock); var is = extension.createIdentityService(spiedContext); assertThat(is).isInstanceOf(IdentityAndTrustService.class); - verify(configMock).getString(eq(IdentityAndTrustExtension.ISSUER_DID_PROPERTY)); + verify(configMock, atLeastOnce()).getString(eq(IdentityAndTrustExtension.CONNECTOR_DID_PROPERTY)); } } \ No newline at end of file diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClientTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClientTest.java index 0d9af897c99..bd70b08ad4d 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClientTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/defaults/DefaultCredentialServiceClientTest.java @@ -61,7 +61,7 @@ void setup() { var jsonLdMock = mock(JsonLd.class); when(jsonLdMock.expand(any())).thenAnswer(a -> success(a.getArgument(0))); client = new DefaultCredentialServiceClient(httpClientMock, Json.createBuilderFactory(Map.of()), - createObjectMapper(), registry, jsonLdMock, mock(), CS_URL); + createObjectMapper(), registry, jsonLdMock, mock()); } @Test @@ -70,7 +70,7 @@ void requestPresentation_singleLdpVp() throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(200, getResourceFileContentAsString("single_ldp-vp.json"))); - var result = client.requestPresentation("foo", List.of()); + var result = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JSON_LD); } @@ -81,7 +81,7 @@ void requestPresentation_singleJwtVp() throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(200, getResourceFileContentAsString("single_jwt-vp.json"))); - var result = client.requestPresentation("foo", List.of()); + var result = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JWT); } @@ -92,7 +92,7 @@ void requestPresentationLdp_multipleVp_mixed() throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_mixed.json"))); - var result = client.requestPresentation("foo", List.of()); + var result = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent()).hasSize(2) .anySatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD)) @@ -105,7 +105,7 @@ void requestPresentation_mulipleVp_onlyLdp() throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_ldp.json"))); - var result = client.requestPresentation("foo", List.of()); + var result = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent()).hasSize(2) .allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD)); @@ -117,7 +117,7 @@ void requestPresentation_mulipleVp_onlyJwt() throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_jwt.json"))); - var result = client.requestPresentation("foo", List.of()); + var result = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(result.succeeded()).isTrue(); assertThat(result.getContent()).hasSize(2) .allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JWT)); @@ -129,11 +129,22 @@ void requestPresentation_csReturnsError(int httpCode) throws IOException { when(httpClientMock.execute(any())) .thenReturn(response(httpCode, "Test failure")); - var res = client.requestPresentation("foo", List.of()); + var res = client.requestPresentation(CS_URL, "foo", List.of()); assertThat(res.failed()).isTrue(); assertThat(res.getFailureDetail()).isEqualTo("Presentation Query failed: HTTP %s, message: Test failure".formatted(httpCode)); } + @DisplayName("CS returns an empty array, because no VC was found") + @Test + void requestPresentation_emptyArray() throws IOException { + when(httpClientMock.execute(any())) + .thenReturn(response(200, "{\"vp_token\":[],\"presentation_submission\":null}")); + + var res = client.requestPresentation(CS_URL, "foo", List.of()); + assertThat(res.succeeded()).isTrue(); + assertThat(res.getContent()).isNotNull().doesNotContainNull().isEmpty(); + } + private VerifiablePresentation createPresentation() { return VerifiablePresentation.Builder.newInstance() .type("VerifiablePresentation") diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java index 43a2e4dca52..ab2fde4337f 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/IdentityAndTrustService.java @@ -123,7 +123,7 @@ public Result verifyJwtToken(TokenRepresentation tokenRepresentation } // todo: create SI Token, extract scope strings - var vpResponse = credentialServiceClient.requestPresentation(null, null); + var vpResponse = credentialServiceClient.requestPresentation(null, null, null); if (vpResponse.failed()) { return vpResponse.mapTo(); diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/HasValidSubjectIds.java b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/HasValidSubjectIds.java index 70df6339a10..bae737c5306 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/HasValidSubjectIds.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/main/java/org/eclipse/edc/iam/identitytrust/validation/rules/HasValidSubjectIds.java @@ -37,7 +37,7 @@ public HasValidSubjectIds(String expectedSubjectId) { @Override public Result apply(VerifiableCredential credential) { - return credential.getCredentialSubject().stream().allMatch(sub -> sub.getId().equals(expectedSubjectId)) ? + return credential.getCredentialSubject().stream().allMatch(sub -> expectedSubjectId.equals(sub.getId())) ? success() : failure("Not all subject IDs match the expected subject ID %s".formatted(expectedSubjectId)); } } diff --git a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java index 51d7d2b6cb1..e92742a8dde 100644 --- a/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-service/src/test/java/org/eclipse/edc/iam/identitytrust/service/IdentityAndTrustServiceTest.java @@ -135,19 +135,19 @@ class VerifyJwtToken { @Test void presentationRequestFails() { - when(mockedClient.requestPresentation(any(), any())).thenReturn(failure("test-failure")); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(failure("test-failure")); var token = createJwt(); var result = service.verifyJwtToken(token, "test-audience"); assertThat(result).isFailed().detail().isEqualTo("test-failure"); verifyNoInteractions(mockedVerifier); - verify(mockedClient).requestPresentation(any(), any()); + verify(mockedClient).requestPresentation(any(), any(), any()); } @Test void cryptographicError() { when(mockedVerifier.verifyPresentation(any())).thenReturn(Result.failure("Cryptographic error")); - when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(createPresentationContainer()))); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(createPresentationContainer()))); var token = createJwt(); var result = service.verifyJwtToken(token, "test-audience"); assertThat(result).isFailed().detail().isEqualTo("Cryptographic error"); @@ -163,7 +163,7 @@ void notYetValid() { .build(); var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); - when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer))); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, "test-audience"); assertThat(result).isFailed().messages() @@ -184,7 +184,7 @@ void oneInvalidSubjectId() { .build(); var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); - when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer))); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, "test-audience"); assertThat(result).isFailed().messages() @@ -209,7 +209,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() { .build(); var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation); when(mockedVerifier.verifyPresentation(any())).thenReturn(success()); - when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer))); + when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer))); var token = createJwt(consumerDid, EXPECTED_OWN_DID); var result = service.verifyJwtToken(token, "test-audience"); assertThat(result).isFailed().messages() diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformer.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformer.java index 5d4724bdbac..acee238cb64 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformer.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformer.java @@ -30,6 +30,7 @@ public JsonObjectToCredentialSubjectTransformer() { public @Nullable CredentialSubject transform(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) { var builder = CredentialSubject.Builder.newInstance(); + builder.id(nodeId(jsonObject)); visitProperties(jsonObject, (s, jsonValue) -> { if (s.equals(CredentialSubject.CREDENTIAL_SUBJECT_ID_PROPERTY)) { builder.id(transformString(jsonValue, context)); diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiableCredentialTransformer.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiableCredentialTransformer.java index e2b8e226633..855fb2a8449 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiableCredentialTransformer.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiableCredentialTransformer.java @@ -35,6 +35,7 @@ import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_ISSUANCEDATE_PROPERTY; import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_ISSUER_PROPERTY; import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_NAME_PROPERTY; +import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_PROOF_PROPERTY; import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_STATUS_PROPERTY; import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_SUBJECT_PROPERTY; import static org.eclipse.edc.identitytrust.model.VerifiableCredential.VERIFIABLE_CREDENTIAL_VALIDFROM_PROPERTY; @@ -75,7 +76,9 @@ private void transformProperties(String key, JsonValue jsonValue, Builder vcBuil case VERIFIABLE_CREDENTIAL_SUBJECT_PROPERTY -> vcBuilder.credentialSubjects(transformArray(jsonValue, CredentialSubject.class, context)); case VERIFIABLE_CREDENTIAL_NAME_PROPERTY -> vcBuilder.name(transformString(jsonValue, context)); - + case VERIFIABLE_CREDENTIAL_PROOF_PROPERTY -> { + //noop + } default -> context.reportProblem("Unknown property: %s type: %s".formatted(key, jsonValue.getValueType().name())); } diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformer.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformer.java index d3fafd8735b..9521debcdfc 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformer.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/main/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformer.java @@ -46,6 +46,9 @@ private void transformProperties(String key, JsonValue jsonValue, VerifiablePres vpBuilder.holder(transformString(jsonValue, context)); case VerifiablePresentation.VERIFIABLE_PRESENTATION_VC_PROPERTY -> transformCredential(jsonValue, vpBuilder, context); + case VerifiablePresentation.VERIFIABLE_PRESENTATION_PROOF_PROPERTY -> { + //noop + } default -> context.reportProblem("Unknown property: %s type: %s".formatted(key, jsonValue.getValueType().name())); } diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/TestData.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/TestData.java index 78b8c7c1502..4c41798dab8 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/TestData.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/TestData.java @@ -73,6 +73,13 @@ public interface TestData { "statusPurpose": "revocation", "statusListIndex": "94567", "statusListCredential": "https://university.example/credentials/status/3" + }, + "proof": { + "type": "Ed25519Signature2020", + "created": "2022-02-25T14:58:42Z", + "verificationMethod": "https://example.edu/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "z3FXQjecWufY46yg5abdVZsXqLhxhueuSoZgNSARiKBk9czhSePTFehP8c3PGfb6a22gkfUKods5D2UAUL5n2Brbx" } } """; diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformerTest.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformerTest.java index e705f00d224..cbf755553e4 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformerTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToCredentialSubjectTransformerTest.java @@ -45,6 +45,22 @@ void setUp() { transformer = new JsonObjectToCredentialSubjectTransformer(); } + @Test + void transform_simpleClaims_withoutId() { + var json = Json.createObjectBuilder() + .add(TEST_NAMESPACE + "simpleclaim1", "value1") + .add(TEST_NAMESPACE + "simpleclaim2", "value2") + .build(); + when(context.transform(isA(JsonObject.class), eq(Object.class))).thenAnswer(a -> ((JsonObject) a.getArgument(0)).getString(VALUE)); + var subj = transformer.transform(jsonLdService.expand(json).getContent(), context); + + assertThat(subj).isNotNull(); + assertThat(subj.getId()).isNull(); + assertThat(subj.getClaims()) + .containsEntry(TEST_NAMESPACE + "simpleclaim1", "value1") + .containsEntry(TEST_NAMESPACE + "simpleclaim2", "value2"); + } + @Test void transform_simpleClaims() { var json = Json.createObjectBuilder() diff --git a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformerTest.java b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformerTest.java index 4482736e015..6f4f6931f85 100644 --- a/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformerTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-transform/src/test/java/org/eclipse/edc/iam/identitytrust/transform/to/JsonObjectToVerifiablePresentationTransformerTest.java @@ -32,8 +32,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.iam.identitytrust.transform.TestData.EXAMPLE_VP_JSONLD; import static org.eclipse.edc.identitytrust.VcConstants.VC_PREFIX; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; class JsonObjectToVerifiablePresentationTransformerTest { public static final ObjectMapper OBJECT_MAPPER = JacksonJsonLd.createObjectMapper(); @@ -67,5 +70,6 @@ void transform() throws JsonProcessingException { assertThat(vp.getHolder()).isEqualTo("did:web:test-holder"); assertThat(vp.getCredentials()).isNotNull().isNotEmpty().hasSize(1); assertThat(vp.getTypes()).isNotEmpty().containsExactly(VC_PREFIX + "VerifiablePresentation"); + verify(context, never()).reportProblem(any()); } } \ No newline at end of file diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/AudienceResolver.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/AudienceResolver.java new file mode 100644 index 00000000000..05525b396df --- /dev/null +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/AudienceResolver.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.identitytrust; + +/** + * An interface for identity resolution, which is useful when multiple identities need to be correlated. + * This is a temporary workaround, because currently, the {@code aud} claim of tokens is always set to the + * counter-party's DSP callback URL, which may not suit all needs. + *

+ * todo: remove once the decoupling of DSP URL and identity is done! + */ +@FunctionalInterface +public interface AudienceResolver { + String resolve(String identity); +} diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/CredentialServiceClient.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/CredentialServiceClient.java index a72a67a2645..1012f14c230 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/CredentialServiceClient.java +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/CredentialServiceClient.java @@ -31,11 +31,12 @@ public interface CredentialServiceClient { *

* Note that sending a DIF Presentation Definition is not supported yet and will result in a 5xx error. * - * @param siTokenJwt A Self-Issued ID token in JWT format, that contains the access_token - * @param scopes A list of strings, each containing a scope definition + * @param credentialServiceUrl The URL of the credentialService from which the VP is to be requested + * @param siTokenJwt A Self-Issued ID token in JWT format, that contains the access_token + * @param scopes A list of strings, each containing a scope definition * @return A list of {@link VerifiablePresentationContainer} objects, or a failure if the request was unsuccessful. */ - Result> requestPresentation(String siTokenJwt, List scopes); + Result> requestPresentation(String credentialServiceUrl, String siTokenJwt, List scopes); //todo: add write api? } diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java index eb7007e3af8..890d9ca480a 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiableCredential.java @@ -38,6 +38,7 @@ public class VerifiableCredential { public static final String VERIFIABLE_CREDENTIAL_SUBJECT_PROPERTY = VC_PREFIX + "credentialSubject"; public static final String VERIFIABLE_CREDENTIAL_NAME_PROPERTY = SCHEMA_ORG_NAMESPACE + "name"; public static final String VERIFIABLE_CREDENTIAL_DESCRIPTION_PROPERTY = SCHEMA_ORG_NAMESPACE + "description"; + public static final String VERIFIABLE_CREDENTIAL_PROOF_PROPERTY = "https://w3id.org/security#proof"; private List credentialSubject = new ArrayList<>(); private String id; // must be URI, but URI is less efficient at runtime private List types = new ArrayList<>(); diff --git a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiablePresentation.java b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiablePresentation.java index 04583f513cb..cf5d7f72825 100644 --- a/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiablePresentation.java +++ b/spi/common/identity-trust-spi/src/main/java/org/eclipse/edc/identitytrust/model/VerifiablePresentation.java @@ -26,6 +26,7 @@ public class VerifiablePresentation { public static final String VERIFIABLE_PRESENTATION_HOLDER_PROPERTY = VC_PREFIX + "holder"; public static final String VERIFIABLE_PRESENTATION_VC_PROPERTY = VC_PREFIX + "verifiableCredential"; + public static final String VERIFIABLE_PRESENTATION_PROOF_PROPERTY = "https://w3id.org/security#proof"; private List credentials = new ArrayList<>(); private String id; private List types = new ArrayList<>();