Skip to content

Commit

Permalink
feat: inject the audience(DID) as additional property in the DataFlow…
Browse files Browse the repository at this point in the history
…StartMessage for later verification in refresh token (#1187)
  • Loading branch information
wolf4ood authored Apr 3, 2024
1 parent 4972a3e commit 8e1a320
Show file tree
Hide file tree
Showing 28 changed files with 620 additions and 54 deletions.
2 changes: 1 addition & 1 deletion edc-controlplane/edc-controlplane-base/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ dependencies {
runtimeOnly(libs.edc.core.identitytrust)
runtimeOnly(project(":edc-extensions:iatp:tx-iatp-sts-dim"))
runtimeOnly(project(":edc-extensions:bdrs-client"))

runtimeOnly(project(":edc-extensions:data-flow-properties-provider"))

runtimeOnly(libs.edc.core.connector)
runtimeOnly(libs.edc.core.controlplane)
Expand Down
1 change: 1 addition & 0 deletions edc-extensions/bdrs-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ plugins {

dependencies {
implementation(project(":core:core-utils"))
implementation(project(":spi:bdrs-client-spi"))
implementation(libs.edc.spi.core)
implementation(libs.edc.spi.http)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/********************************************************************************
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

package org.eclipse.tractusx.edc.identity.mapper;

import org.eclipse.edc.spi.iam.AudienceResolver;
import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;

/**
* An incoming {@link RemoteMessage} is mapped to a DID by calling {@link BdrsClient#resolve(String)} with the {@link RemoteMessage#getCounterPartyId()}
*/
class BdrsClientAudienceMapper implements AudienceResolver {

private final BdrsClient client;

BdrsClientAudienceMapper(BdrsClient client) {
this.client = client;
}

@Override
public String resolve(RemoteMessage remoteMessage) {
return client.resolve(remoteMessage.getCounterPartyId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
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.iam.AudienceResolver;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.tractusx.edc.core.utils.RequiredConfigWarnings;
import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;

import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.NAME;

Expand All @@ -54,13 +54,13 @@ public String name() {
}

@Provider
public AudienceResolver getBdrsAudienceResolver(ServiceExtensionContext context) {
public BdrsClient getBdrsClient(ServiceExtensionContext context) {
var baseUrl = context.getConfig().getString(BDRS_SERVER_URL_PROPERTY, null);
if (baseUrl == null) {
RequiredConfigWarnings.warningNotPresent(context.getMonitor(), BDRS_SERVER_URL_PROPERTY);
}
var cacheValidity = context.getConfig().getInteger(BDRS_SERVER_CACHE_VALIDITY_PERIOD, DEFAULT_BDRS_CACHE_VALIDITY);
return new BdrsClient(baseUrl, cacheValidity, httpClient, context.getMonitor(), typeManager.getMapper());
return new BdrsClientImpl(baseUrl, cacheValidity, httpClient, context.getMonitor(), typeManager.getMapper());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@
import okhttp3.Request;
import org.eclipse.edc.http.spi.EdcHttpClient;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.iam.AudienceResolver;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;

import java.io.IOException;
import java.time.Instant;
Expand All @@ -37,12 +36,11 @@
import java.util.zip.GZIPInputStream;

/**
* Holds a local cache of BPN-to-DID mapping entries. An incoming {@link RemoteMessage} is mapped by looking up the {@link RemoteMessage#getCounterPartyId()}
* property in that map.
* Holds a local cache of BPN-to-DID mapping entries.
* <p>
* The local cache expires after a configurable time, at which point {@link BdrsClient#resolve(RemoteMessage)} requests will hit the server again.
* The local cache expires after a configurable time, at which point {@link BdrsClientImpl#resolve(String)}} requests will hit the server again.
*/
class BdrsClient implements AudienceResolver {
class BdrsClientImpl implements BdrsClient {
private static final TypeReference<Map<String, String>> MAP_REF = new TypeReference<>() {
};
private final String serverUrl;
Expand All @@ -54,7 +52,7 @@ class BdrsClient implements AudienceResolver {
private Map<String, String> cache = new HashMap<>();
private Instant lastCacheUpdate;

BdrsClient(String baseUrl, int cacheValidity, EdcHttpClient httpClient, Monitor monitor, ObjectMapper mapper) {
BdrsClientImpl(String baseUrl, int cacheValidity, EdcHttpClient httpClient, Monitor monitor, ObjectMapper mapper) {
this.serverUrl = baseUrl;
this.cacheValidity = cacheValidity;
this.httpClient = httpClient;
Expand All @@ -63,8 +61,7 @@ class BdrsClient implements AudienceResolver {
}

@Override
public String resolve(RemoteMessage remoteMessage) {
var bpn = remoteMessage.getCounterPartyId();
public String resolve(String bpn) {
String value;
lock.readLock().lock();
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/********************************************************************************
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

package org.eclipse.tractusx.edc.identity.mapper;

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.spi.iam.AudienceResolver;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;

import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.NAME;

@Extension(value = NAME)
public class BdrsClientMapperExtension implements ServiceExtension {

@Inject
private BdrsClient bdrsClient;

@Override
public String name() {
return NAME;
}

@Provider
public AudienceResolver getBdrsAudienceResolver() {
return new BdrsClientAudienceMapper(bdrsClient);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
# SPDX-License-Identifier: Apache-2.0
#################################################################################

org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension
org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension
org.eclipse.tractusx.edc.identity.mapper.BdrsClientMapperExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/********************************************************************************
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

package org.eclipse.tractusx.edc.identity.mapper;

import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class BdrsClientAudienceMapperTest {

private final BdrsClient client = mock();

private final BdrsClientAudienceMapper clientAudienceMapper = new BdrsClientAudienceMapper(client);

@Test
void resolve() {

when(client.resolve("bpn1")).thenReturn("did:web:did1");

var did = clientAudienceMapper.resolve(new TestMessage("bpn1"));

assertThat(did).isEqualTo("did:web:did1");

}

@Test
void resolve_notFound() {

when(client.resolve("bpn1")).thenReturn(null);

var did = clientAudienceMapper.resolve(new TestMessage("bpn1"));

assertThat(did).isNull();

}

private record TestMessage(String bpn) implements RemoteMessage {
@Override
public String getProtocol() {
return "test-proto";
}

@Override
public String getCounterPartyAddress() {
return "http://bpn1";
}

@Override
public String getCounterPartyId() {
return bpn;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import static org.mockito.Mockito.when;

@ExtendWith(DependencyInjectionExtension.class)
class BdrsClientExtensionTest {
class BdrsClientImplExtensionTest {

private final Monitor monitor = mock();

Expand All @@ -50,7 +50,7 @@ void createClient_whenUrlMissing_expectLogError(ServiceExtensionContext context,
when(context.getConfig()).thenReturn(cfg);
when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY), isNull())).thenReturn(null);

extension.getBdrsAudienceResolver(context);
extension.getBdrsClient(context);
verify(monitor).severe(eq("Mandatory config value missing: 'tx.iam.iatp.bdrs.server.url'. This runtime will not be fully operational! Starting with v0.7.x this will be a runtime error."));

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.eclipse.edc.http.client.EdcHttpClientImpl;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -48,11 +47,11 @@
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.verify.VerificationTimes.exactly;

class BdrsClientTest {
class BdrsClientImplTest {

private final Monitor monitor = mock();
private final ObjectMapper mapper = new ObjectMapper();
private BdrsClient client;
private BdrsClientImpl client;
private ClientAndServer bdrsServer;

@BeforeEach
Expand All @@ -66,7 +65,7 @@ void setup() {
.withBody(createGzipStream())
.withStatusCode(200));

client = new BdrsClient("http://localhost:%d/api".formatted(bdrsServer.getPort()), 1, new EdcHttpClientImpl(new OkHttpClient(), RetryPolicy.ofDefaults(), monitor), monitor, mapper);
client = new BdrsClientImpl("http://localhost:%d/api".formatted(bdrsServer.getPort()), 1, new EdcHttpClientImpl(new OkHttpClient(), RetryPolicy.ofDefaults(), monitor), monitor, mapper);
}

@AfterEach
Expand All @@ -76,7 +75,7 @@ void teardown() {

@Test
void getData_whenCacheCold_shouldHitServer() {
var did = client.resolve(new TestMessage("bpn1"));
var did = client.resolve("bpn1");
assertThat(did).isEqualTo("did:web:did1");

bdrsServer.verify(request()
Expand All @@ -88,8 +87,8 @@ void getData_whenCacheCold_shouldHitServer() {

@Test
void getData_whenCacheHot_shouldNotHitServer() {
var did1 = client.resolve(new TestMessage("bpn1"));
var did2 = client.resolve(new TestMessage("bpn2"));
var did1 = client.resolve("bpn1");
var did2 = client.resolve("bpn2");
assertThat(did1).isEqualTo("did:web:did1");
assertThat(did2).isEqualTo("did:web:did2");

Expand All @@ -102,13 +101,13 @@ void getData_whenCacheHot_shouldNotHitServer() {

@Test
void getData_whenCacheExpired_shouldHitServer() {
var did1 = client.resolve(new TestMessage("bpn1")); // hits server
var did1 = client.resolve("bpn1"); // hits server
assertThat(did1).isEqualTo("did:web:did1");

await().pollDelay(ofSeconds(2))
.atMost(ofSeconds(3)) //cache expires
.untilAsserted(() -> {
var did2 = client.resolve(new TestMessage("bpn2")); // hits server as well, b/c cache is expired
var did2 = client.resolve("bpn2"); // hits server as well, b/c cache is expired
assertThat(did2).isEqualTo("did:web:did2");

bdrsServer.verify(request()
Expand All @@ -122,7 +121,7 @@ void getData_whenCacheExpired_shouldHitServer() {

@Test
void getData_whenNotFound() {
var did = client.resolve(new TestMessage("bpn-notexist"));
var did = client.resolve("bpn-notexist");
assertThat(did).isNull();
bdrsServer.verify(request()
.withMethod("GET")
Expand All @@ -137,7 +136,7 @@ void getData_bdrsReturnsError(int code) {
bdrsServer.reset();
bdrsServer.when(request().withPath("/api/bpn-directory").withMethod("GET"))
.respond(HttpResponse.response().withStatusCode(code));
assertThatThrownBy(() -> client.resolve(new TestMessage("bpn1"))).isInstanceOf(EdcException.class);
assertThatThrownBy(() -> client.resolve("bpn1")).isInstanceOf(EdcException.class);
}

private byte[] createGzipStream() {
Expand All @@ -154,20 +153,4 @@ private byte[] createGzipStream() {
return bas.toByteArray();
}

private record TestMessage(String bpn) implements RemoteMessage {
@Override
public String getProtocol() {
return "test-proto";
}

@Override
public String getCounterPartyAddress() {
return "http://bpn1";
}

@Override
public String getCounterPartyId() {
return bpn;
}
}
}
Loading

0 comments on commit 8e1a320

Please sign in to comment.