diff --git a/libraries/edc-extension/pom.xml b/libraries/edc-extension/pom.xml
new file mode 100644
index 00000000..f3ac2e7b
--- /dev/null
+++ b/libraries/edc-extension/pom.xml
@@ -0,0 +1,112 @@
+
+
+
+
+ 4.0.0
+
+ org.eclipse.tractusx
+ digital-twin-registry
+ DEV-SNAPSHOT
+ ../../pom.xml
+
+
+ org.eclipse.tractusx.digital_twin_registry
+ dtr-edc-access-control-extension
+ Tractus-X Semantic Layer Digital Twin Registry Access Control Extension for Eclipse Dataspace Connector Dataplane
+ Module contains the EDC extension triggering access control calls to the Semantic Layer Digital Twin Registry Service's relevant API endpoint.
+ jar
+
+
+ ${organization}
+ ${url}
+
+
+
+
+ ${licence_name}
+ ${licence_url}
+ ${licence_distribution}
+ ${licence_comments}
+
+
+
+
+
+ org.eclipse.edc
+ connector-core
+
+
+ org.eclipse.edc
+ data-plane-spi
+
+
+ org.eclipse.edc
+ data-plane-http-spi
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.assertj
+ assertj-core
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.github.git-commit-id
+ git-commit-id-maven-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+
+
+
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java
new file mode 100644
index 00000000..cf856e73
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+public class AccessControlServiceException extends RuntimeException {
+
+ public AccessControlServiceException( final String message ) {
+ super( message );
+ }
+
+ public AccessControlServiceException( final Throwable cause ) {
+ super( cause );
+ }
+}
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java
new file mode 100644
index 00000000..72e2837b
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessTokenService;
+import org.eclipse.edc.runtime.metamodel.annotation.Extension;
+import org.eclipse.edc.runtime.metamodel.annotation.Inject;
+import org.eclipse.edc.runtime.metamodel.annotation.Setting;
+import org.eclipse.edc.spi.http.EdcHttpClient;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.security.Vault;
+import org.eclipse.edc.spi.system.ServiceExtension;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+import org.eclipse.edc.spi.types.TypeManager;
+
+@Extension( value = "Data Plane HTTP Access Control" )
+public class DtrDataPlaneAccessControlServiceExtension implements ServiceExtension {
+
+ @Setting( value = "Contains the base URL of the EDC data plane endpoint where the data plane requests are sent by the end users." )
+ public static final String EDC_DATA_PLANE_BASE_URL = "edc.granular.access.verification.edc.data.plane.baseUrl";
+ @Setting( value = "Comma separated list of DTR configuration names used as keys for DTR clients." )
+ public static final String EDC_DTR_CONFIG_NAMES = "edc.granular.access.verification.dtr.names";
+ /**
+ * Prefix for individual DTR configurations.
+ */
+ public static final String EDC_DTR_CONFIG_PREFIX = "edc.granular.access.verification.dtr.config.";
+ /**
+ * Configuration property suffix for the configuration of DTR decision cache. The cache is turned off if set to 0.
+ */
+ public static final String DTR_DECISION_CACHE_MINUTES = "dtr.decision.cache.duration.minutes";
+ /**
+ * Configuration property suffix for the pattern to allow for the recognition of aspect model requests which need
+ * to be handled by DTR access control.
+ */
+ public static final String ASPECT_MODEL_URL_PATTERN = "aspect.model.url.pattern";
+ /**
+ * Configuration property suffix for the URL where DTR can be reached.
+ */
+ public static final String DTR_ACCESS_VERIFICATION_URL = "dtr.access.verification.endpoint.url";
+ /**
+ * Configuration property suffix for the URL where OAUTH2 tokens can be obtained for the DTR requests.
+ */
+ public static final String OAUTH2_TOKEN_ENDPOINT_URL = "oauth2.token.endpoint.url";
+ /**
+ * Configuration property suffix for the scope we need to use for OAUTH2 token requests when we need to access DTR.
+ */
+ public static final String OAUTH2_TOKEN_SCOPE = "oauth2.token.scope";
+ /**
+ * Configuration property suffix for the client id we need to use for OAUTH2 token requests when we need to access DTR.
+ */
+ public static final String OAUTH2_TOKEN_CLIENT_ID = "oauth2.token.clientId";
+
+ /**
+ * Configuration property suffix for the path where we can find the client secret in vault for the OAUTH2 token requests when we need to access DTR.
+ */
+ public static final String OAUTH2_TOKEN_CLIENT_SECRET_PATH = "oauth2.token.clientSecret.path";
+ @Inject
+ private Monitor monitor;
+ @Inject
+ private EdcHttpClient httpClient;
+ @Inject
+ private TypeManager typeManager;
+ @Inject
+ private Vault vault;
+ @Inject
+ private DataPlaneAccessTokenService dataPlaneAccessTokenService;
+ private HttpAccessControlCheckClientConfig config;
+
+ @Override
+ public String name() {
+ return "DTR Data Plane Access Control Service";
+ }
+
+ @Override
+ public void initialize( final ServiceExtensionContext context ) {
+ monitor.info( "Initializing " + name() );
+ config = new HttpAccessControlCheckClientConfig( context );
+ }
+}
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java
new file mode 100644
index 00000000..547821ee
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+
+public class HttpAccessControlCheckClientConfig {
+
+ private final Map dtrClientConfigMap;
+ private final String edcDataPlaneBaseUrl;
+
+ public HttpAccessControlCheckClientConfig( final ServiceExtensionContext context ) {
+ dtrClientConfigMap = Arrays.stream( context.getSetting( EDC_DTR_CONFIG_NAMES, "" ).split( "," ) )
+ .filter( StringUtils::isNotBlank )
+ .collect( Collectors.toUnmodifiableMap( Function.identity(),
+ name -> new HttpAccessControlCheckDtrClientConfig(
+ context.getConfig( EDC_DTR_CONFIG_PREFIX + name ) ) ) );
+ edcDataPlaneBaseUrl = context.getSetting( EDC_DATA_PLANE_BASE_URL, null );
+ }
+
+ public Map getDtrClientConfigMap() {
+ return dtrClientConfigMap;
+ }
+
+ public String getEdcDataPlaneBaseUrl() {
+ return edcDataPlaneBaseUrl;
+ }
+}
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java
new file mode 100644
index 00000000..d1cf2ca1
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*;
+
+import org.eclipse.edc.spi.system.configuration.Config;
+
+public class HttpAccessControlCheckDtrClientConfig {
+
+ private final String aspectModelUrlPattern;
+ private final String dtrAccessVerificationUrl;
+ private final String oauth2TokenEndpointUrl;
+ private final String oauth2TokenScope;
+ private final String oauth2ClientId;
+ private final String oauth2ClientSecretPath;
+ private final int decisionCacheDurationMinutes;
+
+ public HttpAccessControlCheckDtrClientConfig( final Config context ) {
+ aspectModelUrlPattern = context.getString( ASPECT_MODEL_URL_PATTERN, null );
+ dtrAccessVerificationUrl = context.getString( DTR_ACCESS_VERIFICATION_URL, null );
+ oauth2TokenEndpointUrl = context.getString( OAUTH2_TOKEN_ENDPOINT_URL, null );
+ oauth2TokenScope = context.getString( OAUTH2_TOKEN_SCOPE, null );
+ oauth2ClientId = context.getString( OAUTH2_TOKEN_CLIENT_ID, null );
+ oauth2ClientSecretPath = context.getString( OAUTH2_TOKEN_CLIENT_SECRET_PATH, null );
+ decisionCacheDurationMinutes = context.getInteger( DTR_DECISION_CACHE_MINUTES, 0 );
+ }
+
+ public String getAspectModelUrlPattern() {
+ return aspectModelUrlPattern;
+ }
+
+ public String getDtrAccessVerificationUrl() {
+ return dtrAccessVerificationUrl;
+ }
+
+ public String getOauth2TokenEndpointUrl() {
+ return oauth2TokenEndpointUrl;
+ }
+
+ public String getOauth2TokenScope() {
+ return oauth2TokenScope;
+ }
+
+ public String getOauth2ClientId() {
+ return oauth2ClientId;
+ }
+
+ public String getOauth2ClientSecretPath() {
+ return oauth2ClientSecretPath;
+ }
+
+ public int getDecisionCacheDurationMinutes() {
+ return decisionCacheDurationMinutes;
+ }
+}
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java
new file mode 100644
index 00000000..17e9980c
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol.client;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.edc.spi.http.EdcHttpClient;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.security.Vault;
+import org.eclipse.edc.spi.types.TypeManager;
+import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.AccessControlServiceException;
+import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.HttpAccessControlCheckDtrClientConfig;
+import org.jetbrains.annotations.NotNull;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+
+import okhttp3.FormBody;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+public class DtrOauth2TokenClient implements Oauth2TokenClient {
+
+ static final TypeReference> MAP_TYPE_REFERENCE = new TypeReference<>() {
+ };
+ static final String CONTENT_TYPE = "Content-Type";
+ static final String ACCESS_TOKEN = "access_token";
+ private final Monitor monitor;
+ private final EdcHttpClient httpClient;
+ private final HttpAccessControlCheckDtrClientConfig dtrConfig;
+ private final TypeManager typeManager;
+ private final LoadingCache secretCache;
+
+ public DtrOauth2TokenClient(
+ final Monitor monitor,
+ final EdcHttpClient httpClient,
+ final TypeManager typeManager,
+ final Vault vault,
+ final HttpAccessControlCheckDtrClientConfig dtrConfig ) {
+ this.monitor = monitor;
+ this.httpClient = httpClient;
+ this.typeManager = typeManager;
+ this.dtrConfig = dtrConfig;
+ this.secretCache = Caffeine.newBuilder()
+ .maximumSize( 1 )
+ .expireAfterWrite( Duration.ofMinutes( 30 ) )
+ .refreshAfterWrite( Duration.ofMinutes( 15 ) )
+ .build( vault::resolveSecret );
+ }
+
+ @Override
+ public String getBearerToken( final String scope ) {
+ final Request request = createTokenRequest( scope );
+ try ( Response response = httpClient.execute( request ) ) {
+ if ( !response.isSuccessful() ) {
+ throw new IllegalStateException( "OAuth2 authentication error. Response code=" + response.code() );
+ }
+ final ResponseBody body = response.body();
+ if ( body == null ) {
+ throw new IllegalStateException( "OAuth2 response body is empty." );
+ }
+ final var map = typeManager.readValue( body.string(), MAP_TYPE_REFERENCE );
+ if ( !map.containsKey( ACCESS_TOKEN ) ) {
+ throw new IllegalStateException( "OAuth2 response body had no token." );
+ }
+ return map.get( ACCESS_TOKEN );
+ } catch ( final Exception e ) {
+ monitor.severe( "Failed to obtain Bearer token: " + e.getMessage() );
+ return null;
+ }
+ }
+
+ @NotNull
+ private String fetchClientSecret() {
+ final String secret = secretCache.get( dtrConfig.getOauth2ClientSecretPath() );
+ if ( secret == null ) {
+ throw new AccessControlServiceException( "Cannot resolve authentication credentials." );
+ }
+ return secret;
+ }
+
+ @NotNull
+ private Request createTokenRequest( final String scope ) {
+ final String secret = fetchClientSecret();
+ final FormBody formBody = new FormBody(
+ List.of( "grant_type", "client_id", "client_secret", "scope" ),
+ List.of( "client_credentials",
+ URLEncoder.encode( dtrConfig.getOauth2ClientId(), StandardCharsets.UTF_8 ),
+ URLEncoder.encode( secret, StandardCharsets.UTF_8 ),
+ URLEncoder.encode( scope, StandardCharsets.UTF_8 ) ) );
+ return new Request.Builder()
+ .url( dtrConfig.getOauth2TokenEndpointUrl() )
+ .addHeader( "Accept", "application/json" )
+ .addHeader( "Content-Type", formBody.contentType().toString() )
+ .post( formBody )
+ .build();
+ }
+}
diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java
new file mode 100644
index 00000000..4e5440e4
--- /dev/null
+++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol.client;
+
+public interface Oauth2TokenClient {
+
+ String getBearerToken( String scope );
+}
diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java
new file mode 100644
index 00000000..57cda353
--- /dev/null
+++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+import java.util.Map;
+
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class HttpAccessControlCheckClientConfigTest {
+ private ServiceExtensionContext serviceExtensionContext;
+ private HttpAccessControlCheckClientConfig underTest;
+
+ @BeforeEach
+ void setUp() {
+ serviceExtensionContext = mock();
+ }
+
+ @Test
+ void test_GetDtrClientConfigMap_ShouldReturnEmptyMap_WhenConfigurationIsNotSet() {
+ //given
+ final int expected = 1;
+ when( serviceExtensionContext.getSetting( EDC_DTR_CONFIG_NAMES, "" ) ).thenReturn( "" );
+ underTest = new HttpAccessControlCheckClientConfig( serviceExtensionContext );
+ //when
+ final var actual = underTest.getDtrClientConfigMap();
+ //then
+ assertThat( actual ).isEqualTo( Map.of() );
+ verify( serviceExtensionContext, never() ).getConfig( anyString() );
+ }
+
+ @Test
+ void test_GetEdcDataPlaneBaseUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "http://edc-data-plane/proxy";
+ when( serviceExtensionContext.getSetting( EDC_DTR_CONFIG_NAMES, "" ) ).thenReturn( "name" );
+ when( serviceExtensionContext.getSetting( eq( EDC_DATA_PLANE_BASE_URL ), isNull() ) ).thenReturn( expected );
+ when( serviceExtensionContext.getConfig( EDC_DTR_CONFIG_PREFIX + "name" ) ).thenReturn( mock() );
+ underTest = new HttpAccessControlCheckClientConfig( serviceExtensionContext );
+ //when
+ final String actual = underTest.getEdcDataPlaneBaseUrl();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+}
diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java
new file mode 100644
index 00000000..3916a022
--- /dev/null
+++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*;
+import static org.mockito.Mockito.*;
+
+import org.eclipse.edc.spi.system.configuration.Config;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class HttpAccessControlCheckDtrClientConfigTest {
+ private Config config;
+ private HttpAccessControlCheckDtrClientConfig underTest;
+
+ @BeforeEach
+ void setUp() {
+ config = mock();
+ }
+
+ @Test
+ void test_GetAspectModelUrlPattern_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "http://aspec-model/api";
+ when( config.getString( eq( ASPECT_MODEL_URL_PATTERN ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getAspectModelUrlPattern();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ void test_GetDtrAccessVerificationUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "http://dtr/submodel-descriptor/authorized";
+ when( config.getString( eq( DTR_ACCESS_VERIFICATION_URL ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getDtrAccessVerificationUrl();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ void test_GetOauth2TokenEndpointUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "http://oauth2/token";
+ when( config.getString( eq( OAUTH2_TOKEN_ENDPOINT_URL ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getOauth2TokenEndpointUrl();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ void test_GetOauth2TokenScope_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "aud:dtr-id";
+ when( config.getString( eq( OAUTH2_TOKEN_SCOPE ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getOauth2TokenScope();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ void test_GetOauth2ClientId_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "edc-client-id";
+ when( config.getString( eq( OAUTH2_TOKEN_CLIENT_ID ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getOauth2ClientId();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+
+ @Test
+ void test_GetOauth2ClientSecretPath_ShouldReturnExpectedValue_WhenConfigurationWasSet() {
+ //given
+ final String expected = "edc-client-secret-path";
+ when( config.getString( eq( OAUTH2_TOKEN_CLIENT_SECRET_PATH ), isNull() ) ).thenReturn( expected );
+ underTest = new HttpAccessControlCheckDtrClientConfig( config );
+ //when
+ final String actual = underTest.getOauth2ClientSecretPath();
+ //then
+ assertThat( actual ).isEqualTo( expected );
+ }
+}
diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java
new file mode 100644
index 00000000..0f1ae482
--- /dev/null
+++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java
@@ -0,0 +1,251 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others
+ * Copyright (c) 2024 Contributors to the Eclipse Foundation
+ *
+ * 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.semantics.edc.dataplane.http.accesscontrol.client;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrOauth2TokenClient.*;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.*;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.edc.spi.http.EdcHttpClient;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.security.Vault;
+import org.eclipse.edc.spi.types.TypeManager;
+import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.AccessControlServiceException;
+import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.HttpAccessControlCheckDtrClientConfig;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import okhttp3.FormBody;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+
+class DtrOauth2TokenClientTest {
+ static final String POST = "POST";
+ static final String SCOPE = "aud:dtr";
+ static final String ENCODED_SCOPE = URLEncoder.encode( SCOPE, StandardCharsets.UTF_8 );
+ static final String CLIENT_ID = "client:id";
+ static final String ENCODED_CLIENT_ID = URLEncoder.encode( CLIENT_ID, StandardCharsets.UTF_8 );
+ static final String CLIENT_SECRET_VAULT_PATH = "client_secret";
+ static final String CLIENT_SECRET = "client:secret";
+ static final String ENCODED_CLIENT_SECRET = URLEncoder.encode( CLIENT_SECRET, StandardCharsets.UTF_8 );
+ static final String DUMMY_TOKEN = "dummy_token_value";
+ static final String RESPONSE_BODY = "{\"" + ACCESS_TOKEN + "\": \"" + DUMMY_TOKEN + "\"}";
+ static final String LOCALHOST_TOKEN_ENDPOINT = "https://localhost/token";
+ @Mock
+ private Response httpResponse;
+ @Mock
+ private ResponseBody responseBody;
+ @Mock
+ private Monitor monitor;
+ @Mock
+ private EdcHttpClient httpClient;
+ @Mock
+ private Vault vault;
+ @Mock
+ private TypeManager typeManager;
+ @Mock
+ private HttpAccessControlCheckDtrClientConfig dtrConfig;
+ @InjectMocks
+ private DtrOauth2TokenClient underTest;
+ private AutoCloseable mocks;
+
+ @BeforeEach
+ void setUp() {
+ mocks = MockitoAnnotations.openMocks( this );
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ mocks.close();
+ }
+
+ @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } )
+ @Test
+ void test_GetBearerToken_ShouldReturnToken_WhenOauth2ResponseContainsOne() throws IOException {
+ //given
+ when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID );
+ when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE );
+ when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH );
+ when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT );
+ when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET );
+ final var requestMatcher = new RequestMatcher(
+ LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE );
+ when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse );
+ when( httpResponse.isSuccessful() ).thenReturn( true );
+ when( httpResponse.body() ).thenReturn( responseBody );
+ when( responseBody.string() ).thenReturn( RESPONSE_BODY );
+ when( typeManager.readValue( RESPONSE_BODY, MAP_TYPE_REFERENCE ) )
+ .thenReturn( new HashMap<>( Map.of( ACCESS_TOKEN, DUMMY_TOKEN ) ) );
+ //when
+ final String actual = underTest.getBearerToken( SCOPE );
+ //then
+ assertThat( actual ).isEqualTo( DUMMY_TOKEN );
+ final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor );
+ inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH );
+ inOrder.verify( httpClient ).execute( argThat( requestMatcher ) );
+ inOrder.verify( httpResponse ).isSuccessful();
+ inOrder.verify( httpResponse, atLeastOnce() ).body();
+ inOrder.verify( responseBody ).string();
+ inOrder.verify( typeManager ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) );
+ }
+
+ @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } )
+ @Test
+ void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseIsNotSuccessful() throws IOException {
+ //given
+ when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID );
+ when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE );
+ when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH );
+ when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT );
+ when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET );
+ final var requestMatcher = new RequestMatcher(
+ LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE );
+ when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse );
+ when( httpResponse.isSuccessful() ).thenReturn( false );
+ //when
+ final String actual = underTest.getBearerToken( SCOPE );
+ //then
+ assertThat( actual ).isNull();
+ final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor );
+ inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH );
+ inOrder.verify( httpClient ).execute( argThat( requestMatcher ) );
+ inOrder.verify( httpResponse ).isSuccessful();
+ inOrder.verify( monitor ).severe( anyString() );
+ inOrder.verify( httpResponse, never() ).body();
+ inOrder.verify( responseBody, never() ).string();
+ inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) );
+ }
+
+ @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } )
+ @Test
+ void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseHasNoBody() throws IOException {
+ //given
+ when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID );
+ when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE );
+ when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH );
+ when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT );
+ when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET );
+ final var requestMatcher = new RequestMatcher(
+ LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE );
+ when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse );
+ when( httpResponse.isSuccessful() ).thenReturn( true );
+ when( httpResponse.body() ).thenReturn( null );
+ //when
+ final String actual = underTest.getBearerToken( SCOPE );
+ //then
+ assertThat( actual ).isNull();
+ final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor );
+ inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH );
+ inOrder.verify( httpClient ).execute( argThat( requestMatcher ) );
+ inOrder.verify( httpResponse ).isSuccessful();
+ inOrder.verify( httpResponse ).body();
+ inOrder.verify( monitor ).severe( anyString() );
+ inOrder.verify( responseBody, never() ).string();
+ inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) );
+ }
+
+ @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } )
+ @Test
+ void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseHasNoTokenBody() throws IOException {
+ //given
+ when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID );
+ when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE );
+ when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH );
+ when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT );
+ when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET );
+ final var requestMatcher = new RequestMatcher(
+ LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE );
+ when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse );
+ when( httpResponse.isSuccessful() ).thenReturn( true );
+ when( httpResponse.body() ).thenReturn( responseBody );
+ when( responseBody.string() ).thenReturn( "{}" );
+ when( typeManager.readValue( "{}", MAP_TYPE_REFERENCE ) )
+ .thenReturn( new HashMap<>() );
+ //when
+ final String actual = underTest.getBearerToken( SCOPE );
+ //then
+ assertThat( actual ).isNull();
+ final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor );
+ inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH );
+ inOrder.verify( httpClient ).execute( argThat( requestMatcher ) );
+ inOrder.verify( httpResponse ).isSuccessful();
+ inOrder.verify( httpResponse ).body();
+ inOrder.verify( responseBody ).string();
+ inOrder.verify( typeManager ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) );
+ inOrder.verify( monitor ).severe( anyString() );
+ }
+
+ @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } )
+ @Test
+ void test_GetBearerToken_ShouldThrowException_WhenSecretCannotBeFetchedFromVault() throws IOException {
+ //given
+ when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID );
+ when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE );
+ when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH );
+ when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT );
+ when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( null );
+ //when
+ assertThatExceptionOfType( AccessControlServiceException.class ).isThrownBy( () -> underTest.getBearerToken( SCOPE ) );
+ //then
+ final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor );
+ inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH );
+ inOrder.verify( httpClient, never() ).execute( any() );
+ inOrder.verify( httpResponse, never() ).isSuccessful();
+ inOrder.verify( httpResponse, never() ).body();
+ inOrder.verify( responseBody, never() ).string();
+ inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) );
+ }
+
+ private record RequestMatcher(String url, String clientId, String clientSecret, String scope) implements ArgumentMatcher {
+ @SuppressWarnings( "DataFlowIssue" )
+ @Override
+ public boolean matches( final Request request ) {
+ final boolean methodMatched = POST.equals( request.method() );
+ final boolean urlMatched = url.equals( request.url().url().toString() );
+ final FormBody body = (FormBody) request.body();
+ final boolean contentTypeHeaderFound = body.contentType().toString().equals( request.header( CONTENT_TYPE ) );
+ final Map expectedParameterMap = Map.of(
+ "client_id", clientId,
+ "client_secret", clientSecret,
+ "scope", scope,
+ "grant_type", "client_credentials" );
+ boolean bodyMatched = body.size() == expectedParameterMap.size();
+ for ( int i = 0; i < body.size(); i++ ) {
+ bodyMatched &= expectedParameterMap.get( body.name( i ) ).equals( body.encodedValue( i ) );
+ }
+ return methodMatched && urlMatched && contentTypeHeaderFound && bodyMatched;
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 2e9ac1dd..383985cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -95,6 +95,9 @@
2.2.220
4.19.1
+
+ 0.5.2-20240321-SNAPSHOT
+
3.24.2
5.9.3
@@ -107,6 +110,7 @@
backend
access-control-service-interface
access-control-service-sql-impl
+ libraries/edc-extension
@@ -280,6 +284,23 @@
${snakeyaml.version}
+
+
+ org.eclipse.edc
+ connector-core
+ ${edc.version}
+
+
+ org.eclipse.edc
+ data-plane-spi
+ ${edc.version}
+
+
+ org.eclipse.edc
+ data-plane-http-spi
+ ${edc.version}
+
+
org.postgresql
@@ -413,6 +434,11 @@
maven-central
https://repo1.maven.org/maven2/
+
+ Maven Snapshots
+ maven-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots/
+