Skip to content

Commit a125404

Browse files
feat: add scope discriminator mapping (#955)
* add discriminator mapping registry * add prepopulation via config * cosmetics
1 parent 1bf8530 commit a125404

10 files changed

Lines changed: 299 additions & 9 deletions

File tree

core/common-core/src/main/java/org/eclipse/edc/identityhub/DefaultServicesExtension.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.eclipse.edc.identityhub.defaults.store.InMemorySignatureSuiteRegistry;
2525
import org.eclipse.edc.identityhub.spi.credential.request.store.HolderCredentialRequestStore;
2626
import org.eclipse.edc.identityhub.spi.keypair.store.KeyPairResourceStore;
27+
import org.eclipse.edc.identityhub.spi.transformation.DiscriminatorMappingRegistry;
2728
import org.eclipse.edc.identityhub.spi.transformation.ScopeToCriterionTransformer;
2829
import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialOfferStore;
2930
import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialStore;
@@ -89,6 +90,8 @@ public class DefaultServicesExtension implements ServiceExtension {
8990
private JsonLd jsonLd;
9091
@Inject
9192
private JtiValidationStore jtiValidationStore;
93+
@Inject
94+
private DiscriminatorMappingRegistry discriminatorMappingRegistry;
9295

9396
@Override
9497
public String name() {
@@ -128,9 +131,7 @@ public KeyPairResourceStore createDefaultKeyPairResourceStore() {
128131

129132
@Provider(isDefault = true)
130133
public ScopeToCriterionTransformer createScopeTransformer(ServiceExtensionContext context) {
131-
context.getMonitor().warning("Using the default EdcScopeToCriterionTransformer. This is not intended for production use and should be replaced " +
132-
"with a specialized implementation for your dataspace");
133-
return new EdcScopeToCriterionTransformer();
134+
return new EdcScopeToCriterionTransformer(discriminatorMappingRegistry);
134135
}
135136

136137
@Provider(isDefault = true)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.identityhub;
16+
17+
import org.eclipse.edc.identityhub.defaults.DiscriminatorMappingRegistryImpl;
18+
import org.eclipse.edc.identityhub.spi.transformation.DiscriminatorMappingRegistry;
19+
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
20+
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
21+
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
22+
import org.eclipse.edc.spi.system.ServiceExtension;
23+
import org.eclipse.edc.spi.system.ServiceExtensionContext;
24+
25+
import static org.eclipse.edc.identityhub.DiscriminatorMappingExtension.NAME;
26+
27+
28+
@Extension(NAME)
29+
public class DiscriminatorMappingExtension implements ServiceExtension {
30+
31+
public static final String CONFIG_PREFIX = "edc.identityhub.discriminator";
32+
public static final String DISCRIMINATOR_ALIAS = CONFIG_PREFIX + ".<alias>.";
33+
34+
@Setting(context = DISCRIMINATOR_ALIAS, description = "the full value for the discriminator")
35+
public static final String DISCRIMINATOR_ALIAS_VALUE_SUFFIX = "value";
36+
37+
@Setting(context = DISCRIMINATOR_ALIAS, description = "the discriminator alias")
38+
public static final String DISCRIMINATOR_ALIAS_SUFFIX = "alias";
39+
40+
public static final String NAME = "Discriminator Mapping Extension";
41+
42+
@Override
43+
public String name() {
44+
return NAME;
45+
}
46+
47+
@Provider(isDefault = true)
48+
public DiscriminatorMappingRegistry createDiscriminatorMappingRegistry(ServiceExtensionContext context) {
49+
var config = context.getConfig(CONFIG_PREFIX);
50+
51+
var configs = config.partition().toList();
52+
53+
var discriminatorMappingRegistry = new DiscriminatorMappingRegistryImpl();
54+
configs.forEach(c -> {
55+
var value = c.getString(DISCRIMINATOR_ALIAS_VALUE_SUFFIX);
56+
var alias = c.getString(DISCRIMINATOR_ALIAS_SUFFIX);
57+
discriminatorMappingRegistry.addMapping(alias, value);
58+
});
59+
60+
return discriminatorMappingRegistry;
61+
}
62+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.identityhub.defaults;
16+
17+
import org.eclipse.edc.identityhub.spi.transformation.DiscriminatorMappingRegistry;
18+
import org.jetbrains.annotations.NotNull;
19+
20+
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
23+
24+
/**
25+
* An implementation of the {@link DiscriminatorMappingRegistry} interface that maintains a registry
26+
* of discriminator mappings in a thread-safe manner.
27+
* <p>
28+
* Thread-Safety: the class is designed to handle multiple threads concurrently accessing or modifying the mappings.
29+
*/
30+
public class DiscriminatorMappingRegistryImpl implements DiscriminatorMappingRegistry {
31+
// this might be accessed from multiple threads (API requests), so it needs to be thread-safe
32+
private final Map<String, String> mappings = new ConcurrentHashMap<>();
33+
34+
@Override
35+
public void addMapping(String alias, String discriminator) {
36+
mappings.put(alias, discriminator);
37+
}
38+
39+
@Override
40+
public @NotNull String getMapping(String alias) {
41+
return mappings.getOrDefault(alias, alias);
42+
}
43+
}

core/common-core/src/main/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformer.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package org.eclipse.edc.identityhub.defaults;
1616

17+
import org.eclipse.edc.identityhub.spi.transformation.DiscriminatorMappingRegistry;
1718
import org.eclipse.edc.identityhub.spi.transformation.ScopeToCriterionTransformer;
1819
import org.eclipse.edc.spi.query.Criterion;
1920
import org.eclipse.edc.spi.result.Result;
@@ -47,6 +48,12 @@ public class EdcScopeToCriterionTransformer implements ScopeToCriterionTransform
4748
private static final String SCOPE_SEPARATOR = ":";
4849
private final List<String> allowedOperations = List.of("read", "*", "all");
4950

51+
private final DiscriminatorMappingRegistry discriminatorMappingRegistry;
52+
53+
public EdcScopeToCriterionTransformer(DiscriminatorMappingRegistry discriminatorMappingRegistry) {
54+
this.discriminatorMappingRegistry = discriminatorMappingRegistry;
55+
}
56+
5057
@Override
5158
public Result<List<Criterion>> transformScope(String scope) {
5259
var tokens = tokenize(scope);
@@ -55,7 +62,10 @@ public Result<List<Criterion>> transformScope(String scope) {
5562
}
5663
var discriminator = tokens.getContent()[1];
5764

58-
return convertDiscriminator(discriminator);
65+
// see if there's a mapping for the discriminator
66+
var mappedDiscriminator = discriminatorMappingRegistry.getMapping(discriminator);
67+
68+
return convertDiscriminator(mappedDiscriminator);
5969
}
6070

6171
protected Result<String[]> tokenize(String scope) {

core/common-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
#
1313
#
1414

15-
org.eclipse.edc.identityhub.DefaultServicesExtension
15+
org.eclipse.edc.identityhub.DefaultServicesExtension
16+
org.eclipse.edc.identityhub.DiscriminatorMappingExtension
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.identityhub;
16+
17+
import org.eclipse.edc.identityhub.defaults.DiscriminatorMappingRegistryImpl;
18+
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
19+
import org.eclipse.edc.spi.EdcException;
20+
import org.eclipse.edc.spi.system.ServiceExtensionContext;
21+
import org.eclipse.edc.spi.system.configuration.ConfigFactory;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import java.util.Map;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
29+
import static org.eclipse.edc.identityhub.DiscriminatorMappingExtension.CONFIG_PREFIX;
30+
import static org.mockito.ArgumentMatchers.eq;
31+
import static org.mockito.Mockito.when;
32+
33+
@ExtendWith(DependencyInjectionExtension.class)
34+
class DiscriminatorMappingExtensionTest {
35+
36+
@Test
37+
void createDiscriminatorMappingRegistry(DiscriminatorMappingExtension extension, ServiceExtensionContext context) {
38+
assertThat(extension.createDiscriminatorMappingRegistry(context))
39+
.isInstanceOf(DiscriminatorMappingRegistryImpl.class);
40+
}
41+
42+
@Test
43+
void createDiscriminatorMappingRegistry_withSingleConfig(DiscriminatorMappingExtension extension, ServiceExtensionContext context) {
44+
when(context.getConfig(eq(CONFIG_PREFIX)))
45+
.thenReturn(ConfigFactory.fromMap(Map.of("foo.alias", "bar", "foo.value", "long-value")));
46+
var discriminatorMappingRegistry = extension.createDiscriminatorMappingRegistry(context);
47+
assertThat(discriminatorMappingRegistry)
48+
.isInstanceOf(DiscriminatorMappingRegistryImpl.class);
49+
50+
assertThat(discriminatorMappingRegistry.getMapping("bar")).isEqualTo("long-value");
51+
}
52+
53+
@Test
54+
void createDiscriminatorMappingRegistry_withIncompleteConfig(DiscriminatorMappingExtension extension, ServiceExtensionContext context) {
55+
when(context.getConfig(eq(CONFIG_PREFIX)))
56+
.thenReturn(ConfigFactory.fromMap(Map.of("foo.alias", "bar")));
57+
assertThatThrownBy(() -> extension.createDiscriminatorMappingRegistry(context))
58+
.isInstanceOf(EdcException.class)
59+
.hasMessage("No setting found for key foo.value");
60+
}
61+
62+
@Test
63+
void createDiscriminatorMappingRegistry_withInvalidSuffix(DiscriminatorMappingExtension extension, ServiceExtensionContext context) {
64+
when(context.getConfig(eq(CONFIG_PREFIX)))
65+
.thenReturn(ConfigFactory.fromMap(Map.of("foo.name", "bar", // should be .alias
66+
"foo.value", "long-value")));
67+
assertThatThrownBy(() -> extension.createDiscriminatorMappingRegistry(context))
68+
.isInstanceOf(EdcException.class)
69+
.hasMessage("No setting found for key foo.alias");
70+
71+
}
72+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (c) 2026 Metaform Systems, Inc.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Metaform Systems, Inc. - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.identityhub.defaults;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
class DiscriminatorMappingRegistryImplTest {
22+
23+
private final DiscriminatorMappingRegistryImpl registry = new DiscriminatorMappingRegistryImpl();
24+
25+
@Test
26+
void addMapping_shouldStoreMapping() {
27+
registry.addMapping("alias1", "discriminator1");
28+
29+
assertThat(registry.getMapping("alias1")).isEqualTo("discriminator1");
30+
}
31+
32+
@Test
33+
void addMapping_shouldOverwriteExistingMapping() {
34+
registry.addMapping("alias1", "discriminator1");
35+
registry.addMapping("alias1", "discriminator2");
36+
37+
assertThat(registry.getMapping("alias1")).isEqualTo("discriminator2");
38+
}
39+
40+
@Test
41+
void getMapping_shouldReturnDefaultWhenNotFound() {
42+
assertThat(registry.getMapping("nonexistent")).isEqualTo("nonexistent");
43+
}
44+
45+
@Test
46+
void getMapping_shouldReturnMappedDiscriminator() {
47+
registry.addMapping("short", "long.discriminator.value");
48+
49+
assertThat(registry.getMapping("short")).isEqualTo("long.discriminator.value");
50+
}
51+
52+
@Test
53+
void addMapping_withMultipleMappings_shouldStoreAll() {
54+
registry.addMapping("alias1", "discriminator1");
55+
registry.addMapping("alias2", "discriminator2");
56+
registry.addMapping("alias3", "discriminator3");
57+
58+
assertThat(registry.getMapping("alias1")).isEqualTo("discriminator1");
59+
assertThat(registry.getMapping("alias2")).isEqualTo("discriminator2");
60+
assertThat(registry.getMapping("alias3")).isEqualTo("discriminator3");
61+
}
62+
63+
}

core/common-core/src/test/java/org/eclipse/edc/identityhub/defaults/EdcScopeToCriterionTransformerTest.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414

1515
package org.eclipse.edc.identityhub.defaults;
1616

17+
import org.junit.jupiter.api.Test;
1718
import org.junit.jupiter.params.ParameterizedTest;
1819
import org.junit.jupiter.params.provider.ValueSource;
1920

21+
import static org.assertj.core.api.Assertions.assertThat;
2022
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
2123

2224
class EdcScopeToCriterionTransformerTest {
23-
private final EdcScopeToCriterionTransformer transformer = new EdcScopeToCriterionTransformer();
25+
private final DiscriminatorMappingRegistryImpl discriminatorMappingRegistry = new DiscriminatorMappingRegistryImpl();
26+
private final EdcScopeToCriterionTransformer transformer = new EdcScopeToCriterionTransformer(discriminatorMappingRegistry);
2427

2528
@ParameterizedTest
2629
@ValueSource(strings = {
@@ -35,6 +38,17 @@ void transform_validScope(String scope) {
3538
assertThat(transformer.transformScope(scope)).isSucceeded();
3639
}
3740

41+
@Test
42+
void transform_withAlias() {
43+
discriminatorMappingRegistry.addMapping("SomeFancyCredential", "https://example.com/contexts/v1#TestCredential");
44+
assertThat(transformer.transformScope("org.eclipse.dspace.dcp.vc.type:SomeFancyCredential:read")).isSucceeded()
45+
.satisfies(criteria -> {
46+
assertThat(criteria).hasSize(2);
47+
assertThat(criteria).anyMatch(c -> c.getOperandRight().equals("https://example.com/contexts/v1"));
48+
assertThat(criteria).anyMatch(c -> c.getOperandRight().equals("TestCredential"));
49+
});
50+
}
51+
3852
@ParameterizedTest
3953
@ValueSource(strings = {
4054
"invalidAlias:TestCredential:read",
@@ -47,4 +61,4 @@ void transform_validScope(String scope) {
4761
void transform_invalidScope(String scope) {
4862
assertThat(transformer.transformScope(scope)).isFailed();
4963
}
50-
}
64+
}

core/identity-hub-core/src/test/java/org/eclipse/edc/identityhub/core/services/query/CredentialQueryResolverImplTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
2424
import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer;
2525
import org.eclipse.edc.iam.verifiablecredentials.spi.model.presentationdefinition.PresentationDefinition;
26+
import org.eclipse.edc.identityhub.defaults.DiscriminatorMappingRegistryImpl;
2627
import org.eclipse.edc.identityhub.defaults.EdcScopeToCriterionTransformer;
2728
import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource;
2829
import org.eclipse.edc.identityhub.spi.verifiablecredentials.resolution.QueryFailure;
@@ -60,7 +61,7 @@ class CredentialQueryResolverImplTest {
6061
private final CredentialStore storeMock = mock();
6162
private final RevocationServiceRegistry revocationServiceRegistry = mock();
6263
private final Monitor monitor = mock();
63-
private final CredentialQueryResolverImpl resolver = new CredentialQueryResolverImpl(storeMock, new EdcScopeToCriterionTransformer(), revocationServiceRegistry, monitor);
64+
private final CredentialQueryResolverImpl resolver = new CredentialQueryResolverImpl(storeMock, new EdcScopeToCriterionTransformer(new DiscriminatorMappingRegistryImpl()), revocationServiceRegistry, monitor);
6465

6566
@BeforeEach
6667
void setUp() {
@@ -134,7 +135,7 @@ void query_scopeStringHasWrongOperator_shouldReturnFailure() {
134135
}
135136

136137
@Test
137-
void query_scopeStringWithFqct() {
138+
void query_scopeStringWithFullyQualifiedCredentialType() {
138139
var cred = createCredential("TestCredential").context("https://example.com/contexts/v1").build();
139140
var resource = createCredentialResource(cred).build();
140141
when(storeMock.query(any())).thenAnswer(i -> success(List.of(resource)));

0 commit comments

Comments
 (0)