diff --git a/core/data-plane/data-plane-core/src/main/java/org/eclipse/edc/connector/dataplane/framework/manager/DataPlaneManagerImpl.java b/core/data-plane/data-plane-core/src/main/java/org/eclipse/edc/connector/dataplane/framework/manager/DataPlaneManagerImpl.java index f84a534bf86..df74944bba0 100644 --- a/core/data-plane/data-plane-core/src/main/java/org/eclipse/edc/connector/dataplane/framework/manager/DataPlaneManagerImpl.java +++ b/core/data-plane/data-plane-core/src/main/java/org/eclipse/edc/connector/dataplane/framework/manager/DataPlaneManagerImpl.java @@ -139,7 +139,7 @@ private StatusResult stop(String dataFlowId) { private StatusResult stop(String dataFlowId, String reason) { var result = store.findByIdAndLease(dataFlowId); if (result.failed()) { - return StatusResult.from(result).map(it -> null); + return StatusResult.from(result).mapFailure(); } var dataFlow = result.getContent(); diff --git a/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/ApiCoreDefaultServicesExtension.java b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/ApiCoreDefaultServicesExtension.java index 0aeb7cf74f5..68fe1539610 100644 --- a/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/ApiCoreDefaultServicesExtension.java +++ b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/ApiCoreDefaultServicesExtension.java @@ -14,12 +14,11 @@ package org.eclipse.edc.api; -import org.eclipse.edc.api.auth.spi.AllPassAuthenticationService; -import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.ApiAuthenticationRegistryImpl; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; /** * Provides default service implementations for fallback @@ -34,10 +33,9 @@ public String name() { return NAME; } - @Provider(isDefault = true) - public AuthenticationService authenticationService(ServiceExtensionContext context) { - context.getMonitor().warning("No AuthenticationService registered, an all-pass implementation will be used, not suitable for production environments"); - return new AllPassAuthenticationService(); + @Provider + public ApiAuthenticationRegistry apiAuthenticationRegistry() { + return new ApiAuthenticationRegistryImpl(); } } diff --git a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AllPassAuthenticationService.java b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/AllPassAuthenticationService.java similarity index 78% rename from spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AllPassAuthenticationService.java rename to extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/AllPassAuthenticationService.java index d1b72f5580b..da159e556df 100644 --- a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AllPassAuthenticationService.java +++ b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/AllPassAuthenticationService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2024 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 @@ -8,11 +8,13 @@ * SPDX-License-Identifier: Apache-2.0 * * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - Initial implementation + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation * */ -package org.eclipse.edc.api.auth.spi; +package org.eclipse.edc.api.auth; + +import org.eclipse.edc.api.auth.spi.AuthenticationService; import java.util.List; import java.util.Map; diff --git a/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImpl.java b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImpl.java new file mode 100644 index 00000000000..7305f5cc98c --- /dev/null +++ b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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.api.auth; + +import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public class ApiAuthenticationRegistryImpl implements ApiAuthenticationRegistry { + + private static final AuthenticationService ALL_PASS = new AllPassAuthenticationService(); + private final Map services = new HashMap<>(); + + public ApiAuthenticationRegistryImpl() { + } + + @Override + public void register(String context, AuthenticationService service) { + services.put(context, service); + } + + @Override + public @NotNull AuthenticationService resolve(String context) { + return services.getOrDefault(context, ALL_PASS); + } +} diff --git a/extensions/common/api/api-core/src/test/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImplTest.java b/extensions/common/api/api-core/src/test/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImplTest.java new file mode 100644 index 00000000000..24cd9d6199f --- /dev/null +++ b/extensions/common/api/api-core/src/test/java/org/eclipse/edc/api/auth/ApiAuthenticationRegistryImplTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 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.api.auth; + +import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class ApiAuthenticationRegistryImplTest { + + private final ApiAuthenticationRegistryImpl registry = new ApiAuthenticationRegistryImpl(); + + @Test + void shouldResolveRegisteredService() { + AuthenticationService service = mock(); + registry.register("context", service); + + var actual = registry.resolve("context"); + + assertThat(actual).isSameAs(service); + } + + @Test + void shouldReturnAllPass_whenNoServiceRegistered() { + var service = registry.resolve("context"); + + assertThat(service).isInstanceOf(AllPassAuthenticationService.class); + } +} diff --git a/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java b/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java index 13e507b7ced..d52873f4f7b 100644 --- a/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java +++ b/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java @@ -14,10 +14,14 @@ package org.eclipse.edc.connector.api.control.configuration; +import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.connector.controlplane.transfer.spi.callback.ControlApiUrl; import org.eclipse.edc.jsonld.spi.JsonLd; 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.Provides; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.EdcException; @@ -34,6 +38,7 @@ import org.eclipse.edc.web.spi.configuration.WebServiceSettings; import java.net.URI; +import java.util.Collections; import static java.lang.String.format; import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_PREFIX; @@ -54,6 +59,7 @@ public class ControlApiConfigurationExtension implements ServiceExtension { @Setting(value = "Configures endpoint for reaching the Control API. If it's missing it defaults to the hostname configuration.") public static final String CONTROL_API_ENDPOINT = "edc.control.endpoint"; + public static final String CONTROL_CONTEXT_ALIAS = "control"; private static final String WEB_SERVICE_NAME = "Control API"; private static final int DEFAULT_CONTROL_API_PORT = 9191; @@ -72,18 +78,16 @@ public class ControlApiConfigurationExtension implements ServiceExtension { private WebServer webServer; @Inject private WebServiceConfigurer configurator; - @Inject private WebService webService; - @Inject private Hostname hostname; - @Inject private JsonLd jsonLd; - @Inject private TypeManager typeManager; + @Inject + private ApiAuthenticationRegistry authenticationRegistry; @Override public String name() { @@ -93,18 +97,25 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { var config = configurator.configure(context, webServer, SETTINGS); - var callbackAddress = controlApiUrl(context, config); var jsonLdMapper = typeManager.getMapper(JSON_LD); context.registerService(ControlApiConfiguration.class, new ControlApiConfiguration(config)); - context.registerService(ControlApiUrl.class, callbackAddress); + context.registerService(ControlApiUrl.class, controlApiUrl(context, config)); jsonLd.registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, CONTROL_SCOPE); jsonLd.registerNamespace(DSPACE_PREFIX, DSPACE_SCHEMA, CONTROL_SCOPE); + var authenticationRequestFilter = new AuthenticationRequestFilter(authenticationRegistry, "control-api"); + webService.registerResource(SETTINGS.getContextAlias(), authenticationRequestFilter); + webService.registerResource(SETTINGS.getContextAlias(), new ObjectMapperProvider(jsonLdMapper)); webService.registerResource(SETTINGS.getContextAlias(), new JerseyJsonLdInterceptor(jsonLd, jsonLdMapper, CONTROL_SCOPE)); } + @Provider(isDefault = true) + public ControlClientAuthenticationProvider controlClientAuthenticationProvider() { + return Collections::emptyMap; + } + private ControlApiUrl controlApiUrl(ServiceExtensionContext context, WebServiceConfiguration config) { var callbackAddress = context.getSetting(CONTROL_API_ENDPOINT, format("http://%s:%s%s", hostname.get(), config.getPort(), config.getPath())); try { diff --git a/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java b/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java index 0656b45ed66..c0d66274806 100644 --- a/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java +++ b/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java @@ -14,12 +14,14 @@ package org.eclipse.edc.connector.api.control.configuration; +import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; import org.eclipse.edc.connector.controlplane.transfer.spi.callback.ControlApiUrl; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; import org.junit.jupiter.api.BeforeEach; @@ -33,13 +35,16 @@ import static org.eclipse.edc.connector.api.control.configuration.ControlApiConfigurationExtension.CONTROL_CONTEXT_ALIAS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class ControlApiConfigurationExtensionTest { private final WebServiceConfigurer configurator = mock(); + private final WebService webService = mock(); private final WebServiceConfiguration webServiceConfiguration = WebServiceConfiguration.Builder.newInstance() .contextAlias(CONTROL_CONTEXT_ALIAS) @@ -51,6 +56,7 @@ public class ControlApiConfigurationExtensionTest { void setUp(ServiceExtensionContext context) { context.registerService(WebServiceConfigurer.class, configurator); context.registerService(Hostname.class, () -> "localhost"); + context.registerService(WebService.class, webService); when(configurator.configure(any(), any(), any())).thenReturn(webServiceConfiguration); } @@ -94,6 +100,12 @@ void initialize_withInvalidEndpoint(ControlApiConfigurationExtension extension, when(context.getSetting(eq(CONTROL_API_ENDPOINT), any())).thenReturn(endpoint); assertThatThrownBy(() -> extension.initialize(context)).isInstanceOf(EdcException.class); + } + + @Test + void shouldRegisterAuthenticationFilter(ControlApiConfigurationExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + verify(webService).registerResource(any(), isA(AuthenticationRequestFilter.class)); } } diff --git a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java index 9a0d6d4922f..e35157e3e87 100644 --- a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java +++ b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java @@ -16,7 +16,7 @@ import jakarta.json.Json; import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; -import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.connector.api.management.configuration.transform.JsonObjectFromContractAgreementTransformer; import org.eclipse.edc.connector.controlplane.transform.edc.from.JsonObjectFromAssetTransformer; import org.eclipse.edc.connector.controlplane.transform.edc.to.JsonObjectToAssetTransformer; @@ -77,7 +77,7 @@ public class ManagementApiConfigurationExtension implements ServiceExtension { @Inject private WebServer webServer; @Inject - private AuthenticationService authenticationService; + private ApiAuthenticationRegistry authenticationRegistry; @Inject private WebServiceConfigurer configurator; @Inject @@ -99,7 +99,9 @@ public void initialize(ServiceExtensionContext context) { var webServiceConfiguration = configurator.configure(context, webServer, SETTINGS); context.registerService(ManagementApiConfiguration.class, new ManagementApiConfiguration(webServiceConfiguration)); - webService.registerResource(webServiceConfiguration.getContextAlias(), new AuthenticationRequestFilter(authenticationService)); + + var authenticationFilter = new AuthenticationRequestFilter(authenticationRegistry, "management-api"); + webService.registerResource(webServiceConfiguration.getContextAlias(), authenticationFilter); jsonLd.registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, MANAGEMENT_SCOPE); var jsonLdMapper = typeManager.getMapper(JSON_LD); diff --git a/extensions/common/auth/auth-basic/build.gradle.kts b/extensions/common/auth/auth-basic/build.gradle.kts index 07a59ee9569..e2013366b73 100644 --- a/extensions/common/auth/auth-basic/build.gradle.kts +++ b/extensions/common/auth/auth-basic/build.gradle.kts @@ -19,8 +19,8 @@ plugins { dependencies { api(project(":spi:common:auth-spi")) - api(project(":spi:common:core-spi")) - implementation(libs.jakarta.rsApi) + + testImplementation(project(":core:common:junit")) } diff --git a/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtension.java b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtension.java index 4e156062774..d8fbf927e47 100644 --- a/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtension.java +++ b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtension.java @@ -15,6 +15,7 @@ package org.eclipse.edc.api.auth.basic; import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; @@ -23,56 +24,43 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import java.util.stream.Collectors; - import static java.lang.String.format; /** * Extension that registers an AuthenticationService that uses API Keys + * + * @deprecated this module is not supported anymore and it will be removed in the next iterations. */ @Provides(AuthenticationService.class) @Extension(value = "Basic authentication") +@Deprecated(since = "0.6.5") public class BasicAuthenticationExtension implements ServiceExtension { - @Setting - public static final String BASIC_AUTH = "edc.api.auth.basic.vault-keys"; + @Setting(value = "Key-value object defining authentication credentials stored in the vault", type = "map", required = true) + static final String BASIC_AUTH = "edc.api.auth.basic.vault-keys"; @Inject private Vault vault; + @Inject + private ApiAuthenticationRegistry authenticationRegistry; @Override public void initialize(ServiceExtensionContext context) { var monitor = context.getMonitor(); + monitor.warning("The 'auth-basic' module has been deprecated and it will removed in the next iterations."); + var credentials = context.getConfig(BASIC_AUTH) .getRelativeEntries().entrySet().stream() .map(entry -> new ConfigCredentials(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); + .toList(); - // Register basic authentication filter if (!credentials.isEmpty()) { var authService = new BasicAuthenticationService(vault, credentials, monitor); - context.registerService(AuthenticationService.class, authService); + authenticationRegistry.register("management-api", authService); monitor.info(format("API Authentication: basic auth configured with %s credential(s)", credentials.size())); } else { monitor.warning("API Authentication: no basic auth credentials provided"); } } - static class ConfigCredentials { - private final String username; - private final String vaultKey; - - ConfigCredentials(String username, String vaultKey) { - this.username = username; - this.vaultKey = vaultKey; - } - - public String getUsername() { - return username; - } - - public String getVaultKey() { - return vaultKey; - } - } } diff --git a/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationService.java b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationService.java index 1f42d52585f..a7046156413 100644 --- a/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationService.java +++ b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationService.java @@ -29,12 +29,12 @@ public class BasicAuthenticationService implements AuthenticationService { private static final String BASIC_AUTH_HEADER_NAME = "Authorization"; private final Base64.Decoder b64Decoder; private final Vault vault; - private final List basicAuthUsersWithVaultKeyConfigs; + private final List basicAuthUsersWithVaultKeyConfigs; private final Monitor monitor; - public BasicAuthenticationService( + BasicAuthenticationService( Vault vault, - List basicAuthUsersWithVaultKeyConfigs, + List basicAuthUsersWithVaultKeyConfigs, Monitor monitor) { this.vault = vault; this.basicAuthUsersWithVaultKeyConfigs = basicAuthUsersWithVaultKeyConfigs; @@ -105,7 +105,7 @@ private boolean checkBasicAuthValid(Result authCredentials var creds = authCredentials.getContent(); return basicAuthUsersWithVaultKeyConfigs.stream() - .anyMatch(it -> it.getUsername().equals(creds.username) && Objects.equals(vault.resolveSecret(it.getVaultKey()), creds.password)); + .anyMatch(it -> it.username().equals(creds.username) && Objects.equals(vault.resolveSecret(it.vaultKey()), creds.password)); } static class BasicAuthCredentials { diff --git a/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/ConfigCredentials.java b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/ConfigCredentials.java new file mode 100644 index 00000000000..dde29b429ae --- /dev/null +++ b/extensions/common/auth/auth-basic/src/main/java/org/eclipse/edc/api/auth/basic/ConfigCredentials.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024 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.api.auth.basic; + +record ConfigCredentials(String username, String vaultKey) { +} diff --git a/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtensionTest.java b/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtensionTest.java new file mode 100644 index 00000000000..e9556274458 --- /dev/null +++ b/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationExtensionTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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.api.auth.basic; + +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Map; + +import static org.eclipse.edc.api.auth.basic.BasicAuthenticationExtension.BASIC_AUTH; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class BasicAuthenticationExtensionTest { + + private final ApiAuthenticationRegistry apiAuthenticationRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(ApiAuthenticationRegistry.class, apiAuthenticationRegistry); + } + + @Test + void shouldRegisterApiAuthorization(BasicAuthenticationExtension extension, ServiceExtensionContext context) { + when(context.getConfig(BASIC_AUTH)).thenReturn(ConfigFactory.fromMap(Map.of("edc.api.auth.basic.vault-keys.key", "value"))); + + extension.initialize(context); + + verify(apiAuthenticationRegistry).register(eq("management-api"), isA(BasicAuthenticationService.class)); + } + + @Test + void shouldNoRegisterApiAuthentication_whenNoConfigProvided(BasicAuthenticationExtension extension, ServiceExtensionContext context) { + when(context.getConfig(BASIC_AUTH)).thenReturn(ConfigFactory.empty()); + + extension.initialize(context); + + verifyNoInteractions(apiAuthenticationRegistry); + } +} diff --git a/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationServiceTest.java b/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationServiceTest.java index 07426e6496c..f73354aba4f 100644 --- a/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationServiceTest.java +++ b/extensions/common/auth/auth-basic/src/test/java/org/eclipse/edc/api/auth/basic/BasicAuthenticationServiceTest.java @@ -34,9 +34,9 @@ class BasicAuthenticationServiceTest { - private static final List TEST_CREDENTIALS = List.of( - new BasicAuthenticationExtension.ConfigCredentials("usera", "api-basic-auth-usera"), - new BasicAuthenticationExtension.ConfigCredentials("userb", "api-basic-auth-userb") + private static final List TEST_CREDENTIALS = List.of( + new ConfigCredentials("usera", "api-basic-auth-usera"), + new ConfigCredentials("userb", "api-basic-auth-userb") ); private static final Base64.Encoder BASE_64_ENCODER = Base64.getEncoder(); @@ -143,7 +143,7 @@ void isAuthorized_multipleValues_notAuthorized() { @Test void isAuthorized_wrongVaultKey() { var wrongVaultKeyConfig = List.of( - new BasicAuthenticationExtension.ConfigCredentials("usera", "wrong.key") + new ConfigCredentials("usera", "wrong.key") ); var service = new BasicAuthenticationService(vault, wrongVaultKeyConfig, mock(Monitor.class)); var map = Map.of("authorization", List.of(testCredentialsEncoded.get(0))); diff --git a/extensions/common/auth/auth-tokenbased/src/main/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtension.java b/extensions/common/auth/auth-tokenbased/src/main/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtension.java index 05567999e0f..38473c84e14 100644 --- a/extensions/common/auth/auth-tokenbased/src/main/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtension.java +++ b/extensions/common/auth/auth-tokenbased/src/main/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtension.java @@ -17,6 +17,7 @@ package org.eclipse.edc.api.auth.token; import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; @@ -25,6 +26,7 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import java.util.Optional; import java.util.UUID; /** @@ -39,8 +41,11 @@ public class TokenBasedAuthenticationExtension implements ServiceExtension { private static final String AUTH_SETTING_APIKEY = "edc.api.auth.key"; @Setting private static final String AUTH_SETTING_APIKEY_ALIAS = "edc.api.auth.key.alias"; + @Inject private Vault vault; + @Inject + private ApiAuthenticationRegistry authenticationRegistry; @Override public String name() { @@ -49,17 +54,10 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - String apiKey = null; - - var apiKeyAlias = context.getSetting(AUTH_SETTING_APIKEY_ALIAS, null); - if (apiKeyAlias != null) { - apiKey = vault.resolveSecret(apiKeyAlias); - } - - if (apiKey == null) { - apiKey = context.getSetting(AUTH_SETTING_APIKEY, UUID.randomUUID().toString()); - } + var apiKey = Optional.ofNullable(context.getSetting(AUTH_SETTING_APIKEY_ALIAS, null)) + .map(alias -> vault.resolveSecret(alias)) + .orElseGet(() -> context.getSetting(AUTH_SETTING_APIKEY, UUID.randomUUID().toString())); - context.registerService(AuthenticationService.class, new TokenBasedAuthenticationService(apiKey)); + authenticationRegistry.register("management-api", new TokenBasedAuthenticationService(apiKey)); } } diff --git a/extensions/common/auth/auth-tokenbased/src/test/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtensionTest.java b/extensions/common/auth/auth-tokenbased/src/test/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtensionTest.java index e4f7a822bab..02e9f8b80ef 100644 --- a/extensions/common/auth/auth-tokenbased/src/test/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtensionTest.java +++ b/extensions/common/auth/auth-tokenbased/src/test/java/org/eclipse/edc/api/auth/token/TokenBasedAuthenticationExtensionTest.java @@ -14,6 +14,7 @@ package org.eclipse.edc.api.auth.token; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -21,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.isNull; @@ -37,10 +39,12 @@ public class TokenBasedAuthenticationExtensionTest { private static final String VAULT_KEY = "foo"; private final Vault vault = mock(); + private final ApiAuthenticationRegistry apiAuthenticationRegistry = mock(); @BeforeEach void setup(ServiceExtensionContext context) { context.registerService(Vault.class, vault); + context.registerService(ApiAuthenticationRegistry.class, apiAuthenticationRegistry); when(vault.resolveSecret(VAULT_KEY)).thenReturn("foo"); } @@ -59,6 +63,7 @@ public void testPrimaryMethod_loadKeyFromVault(ServiceExtensionContext context, .getSetting(AUTH_SETTING_APIKEY_ALIAS, null); verify(vault).resolveSecret(VAULT_KEY); + verify(apiAuthenticationRegistry).register(eq("management-api"), isA(TokenBasedAuthenticationService.class)); } @Test @@ -75,6 +80,7 @@ public void testSecondaryMethod_loadKeyFromConfig(ServiceExtensionContext contex .getSetting(AUTH_SETTING_APIKEY_ALIAS, null); verify(vault, never()).resolveSecret(anyString()); + verify(apiAuthenticationRegistry).register(eq("management-api"), isA(TokenBasedAuthenticationService.class)); } } diff --git a/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/IdentityProviderKeyResolver.java b/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/IdentityProviderKeyResolver.java index 679739f2f36..9c1107a48f5 100644 --- a/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/IdentityProviderKeyResolver.java +++ b/extensions/common/iam/oauth2/oauth2-core/src/main/java/org/eclipse/edc/iam/oauth2/identity/IdentityProviderKeyResolver.java @@ -137,7 +137,7 @@ private Result refreshKeys() { if (result.succeeded()) { cache.set(result.getContent()); } - return result.map(it -> null); + return result.mapEmpty(); } private Map deserializeKeys(List jwkKeys) { diff --git a/extensions/control-plane/api/control-plane-api-client/build.gradle.kts b/extensions/control-plane/api/control-plane-api-client/build.gradle.kts index c2d9f7e504c..e8bb68d4031 100644 --- a/extensions/control-plane/api/control-plane-api-client/build.gradle.kts +++ b/extensions/control-plane/api/control-plane-api-client/build.gradle.kts @@ -17,6 +17,7 @@ plugins { } dependencies { + api(project(":spi:common:auth-spi")) api(project(":spi:common:http-spi")) api(project(":spi:control-plane:control-plane-api-client-spi")) diff --git a/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/ControlPlaneApiClientExtension.java b/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/ControlPlaneApiClientExtension.java index cb7d2a2a090..81b0a4ffc56 100644 --- a/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/ControlPlaneApiClientExtension.java +++ b/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/ControlPlaneApiClientExtension.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.controlplane.api.client; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.controlplane.api.client.spi.transferprocess.TransferProcessApiClient; import org.eclipse.edc.connector.controlplane.api.client.transferprocess.TransferProcessHttpClient; import org.eclipse.edc.connector.controlplane.api.client.transferprocess.model.TransferProcessFailRequest; @@ -36,15 +37,21 @@ public class ControlPlaneApiClientExtension implements ServiceExtension { @Inject private EdcHttpClient httpClient; - @Inject private TypeManager typeManager; + @Inject + private ControlClientAuthenticationProvider authenticationProvider; + + @Override + public String name() { + return NAME; + } @Provider public TransferProcessApiClient transferProcessApiClient(ServiceExtensionContext context) { typeManager.registerTypes(TransferProcessFailRequest.class); - return new TransferProcessHttpClient(httpClient, typeManager.getMapper(), context.getMonitor()); + return new TransferProcessHttpClient(httpClient, typeManager.getMapper(), context.getMonitor(), authenticationProvider); } } diff --git a/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClient.java b/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClient.java index d8b2dd394ee..8df34f24ced 100644 --- a/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClient.java +++ b/extensions/control-plane/api/control-plane-api-client/src/main/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClient.java @@ -19,6 +19,7 @@ import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.controlplane.api.client.spi.transferprocess.TransferProcessApiClient; import org.eclipse.edc.connector.controlplane.api.client.transferprocess.model.TransferProcessFailRequest; import org.eclipse.edc.http.spi.EdcHttpClient; @@ -42,11 +43,14 @@ public class TransferProcessHttpClient implements TransferProcessApiClient { private final EdcHttpClient httpClient; private final ObjectMapper mapper; private final Monitor monitor; + private final ControlClientAuthenticationProvider authenticationProvider; - public TransferProcessHttpClient(EdcHttpClient httpClient, ObjectMapper mapper, Monitor monitor) { + public TransferProcessHttpClient(EdcHttpClient httpClient, ObjectMapper mapper, Monitor monitor, + ControlClientAuthenticationProvider authenticationProvider) { this.httpClient = httpClient; this.mapper = mapper; this.monitor = monitor; + this.authenticationProvider = authenticationProvider; } @Override @@ -63,8 +67,12 @@ private Result sendRequest(DataFlowStartMessage dataFlowStartMessage, Stri if (dataFlowStartMessage.getCallbackAddress() != null) { try { - var request = createRequest(buildUrl(dataFlowStartMessage, action), body); - try (var response = httpClient.execute(request, List.of(retryWhenStatusIsNotIn(200, 204)))) { + var builder = new Request.Builder() + .url(buildUrl(dataFlowStartMessage, action)) + .post(createRequestBody(body)); + authenticationProvider.authenticationHeaders().forEach(builder::header); + + try (var response = httpClient.execute(builder.build(), List.of(retryWhenStatusIsNotIn(200, 204)))) { if (!response.isSuccessful()) { var message = "Failed to send callback request: received %s from the TransferProcess API" .formatted(response.code()); @@ -91,17 +99,11 @@ private String buildUrl(DataFlowStartMessage dataFlowStartMessage, String action return url.toString(); } - private Request createRequest(String url, Object body) throws JsonProcessingException { - RequestBody requestBody; + private @NotNull RequestBody createRequestBody(Object body) throws JsonProcessingException { if (body != null) { - requestBody = - RequestBody.create(mapper.writeValueAsString(body), TYPE_JSON); + return RequestBody.create(mapper.writeValueAsString(body), TYPE_JSON); } else { - requestBody = RequestBody.create("", null); + return RequestBody.create("", null); } - return new Request.Builder() - .url(url) - .post(requestBody) - .build(); } } diff --git a/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClientTest.java b/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClientTest.java index b6a4a44f01f..cdb3d859ca5 100644 --- a/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClientTest.java +++ b/extensions/control-plane/api/control-plane-api-client/src/test/java/org/eclipse/edc/connector/controlplane/api/client/transferprocess/TransferProcessHttpClientTest.java @@ -19,10 +19,10 @@ import okhttp3.MediaType; import okhttp3.Response; import okhttp3.ResponseBody; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.transfer.DataFlowStartMessage; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; @@ -41,23 +41,12 @@ public class TransferProcessHttpClientTest { - private final Interceptor interceptor = mock(Interceptor.class); + private final Interceptor interceptor = mock(); private final Monitor monitor = mock(); - private TransferProcessHttpClient transferProcessHttpClient; + private final ControlClientAuthenticationProvider authenticationProvider = mock(); - private static Response createResponse(int code, InvocationOnMock invocation) { - Interceptor.Chain chain = invocation.getArgument(0); - return new Response.Builder() - .request(chain.request()) - .protocol(HTTP_1_1).code(code) - .body(ResponseBody.create("", MediaType.get("application/json"))).message("test") - .build(); - } - - @BeforeEach - void setup() { - transferProcessHttpClient = new TransferProcessHttpClient(testHttpClient(interceptor), new ObjectMapper(), monitor); - } + private final TransferProcessHttpClient transferProcessHttpClient = new TransferProcessHttpClient( + testHttpClient(interceptor), new ObjectMapper(), monitor, authenticationProvider); @Test void complete() throws IOException { @@ -68,12 +57,12 @@ void complete() throws IOException { var result = transferProcessHttpClient.completed(req); assertThat(result).isSucceeded(); - verifyNoInteractions(monitor); + verify(authenticationProvider).authenticationHeaders(); } @Test - void complete_shouldSucceed_withNoCallbacks() throws IOException { + void complete_shouldSucceed_withNoCallbacks() { var req = createRequest().build(); var result = transferProcessHttpClient.completed(req); @@ -121,8 +110,8 @@ void fail() throws IOException { var result = transferProcessHttpClient.failed(req, "failure"); assertThat(result).isSucceeded(); - verifyNoInteractions(monitor); + verify(authenticationProvider).authenticationHeaders(); } private DataFlowStartMessage.Builder createRequest() { @@ -132,4 +121,13 @@ private DataFlowStartMessage.Builder createRequest() { .sourceDataAddress(DataAddress.Builder.newInstance().type("type").build()) .destinationDataAddress(DataAddress.Builder.newInstance().type("type").build()); } + + private Response createResponse(int code, InvocationOnMock invocation) { + Interceptor.Chain chain = invocation.getArgument(0); + return new Response.Builder() + .request(chain.request()) + .protocol(HTTP_1_1).code(code) + .body(ResponseBody.create("", MediaType.get("application/json"))).message("test") + .build(); + } } diff --git a/extensions/control-plane/provision/provision-http/src/main/java/org/eclipse/edc/connector/controlplane/provision/http/HttpWebhookExtension.java b/extensions/control-plane/provision/provision-http/src/main/java/org/eclipse/edc/connector/controlplane/provision/http/HttpWebhookExtension.java index 8c57d7771d9..1ba89dd32d9 100644 --- a/extensions/control-plane/provision/provision-http/src/main/java/org/eclipse/edc/connector/controlplane/provision/http/HttpWebhookExtension.java +++ b/extensions/control-plane/provision/provision-http/src/main/java/org/eclipse/edc/connector/controlplane/provision/http/HttpWebhookExtension.java @@ -14,8 +14,6 @@ package org.eclipse.edc.connector.controlplane.provision.http; -import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; -import org.eclipse.edc.api.auth.spi.AuthenticationService; import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; import org.eclipse.edc.connector.controlplane.provision.http.webhook.HttpProvisionerWebhookApiController; import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; @@ -36,9 +34,6 @@ public class HttpWebhookExtension implements ServiceExtension { @Inject private WebService webService; - @Inject - private AuthenticationService authService; - @Inject private TransferProcessService transferProcessService; @@ -53,7 +48,6 @@ public void initialize(ServiceExtensionContext context) { registerCallbackUrl(context, managementApiConfiguration.getPath(), managementApiConfiguration.getPort()); webService.registerResource(managementApiConfiguration.getContextAlias(), new HttpProvisionerWebhookApiController(transferProcessService)); - webService.registerResource(managementApiConfiguration.getContextAlias(), new AuthenticationRequestFilter(authService)); } private void registerCallbackUrl(ServiceExtensionContext context, String path, int port) { diff --git a/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/controlplane/provision/http/webhook/HttpWebhookExtensionTest.java b/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/controlplane/provision/http/webhook/HttpWebhookExtensionTest.java index dcfc568e08b..9e70749fc40 100644 --- a/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/controlplane/provision/http/webhook/HttpWebhookExtensionTest.java +++ b/extensions/control-plane/provision/provision-http/src/test/java/org/eclipse/edc/connector/controlplane/provision/http/webhook/HttpWebhookExtensionTest.java @@ -14,8 +14,6 @@ package org.eclipse.edc.connector.controlplane.provision.http.webhook; -import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; -import org.eclipse.edc.api.auth.spi.AuthenticationService; import org.eclipse.edc.boot.system.injection.ObjectFactory; import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; import org.eclipse.edc.connector.controlplane.provision.http.HttpProvisionerWebhookUrl; @@ -58,7 +56,6 @@ void setup(ServiceExtensionContext context, ObjectFactory factory) { context.registerService(WebService.class, webService); context.registerService(Hostname.class, () -> "localhost"); context.registerService(TransferProcessService.class, mock(TransferProcessService.class)); - context.registerService(AuthenticationService.class, mock(AuthenticationService.class)); context.registerService(ManagementApiConfiguration.class, new ManagementApiConfiguration(webServiceConfiguration)); when(context.getMonitor()).thenReturn(monitor); @@ -73,7 +70,6 @@ void initialize_shouldBeRegisteredAsManagementApiService(ServiceExtensionContext extension.initialize(context); verify(webService).registerResource(eq("management"), isA(HttpProvisionerWebhookApiController.class)); - verify(webService).registerResource(eq("management"), isA(AuthenticationRequestFilter.class)); assertThat(context.getService(HttpProvisionerWebhookUrl.class)) .extracting(HttpProvisionerWebhookUrl::get).extracting(URL::toString) .isEqualTo("http://localhost:8888/management/callback"); diff --git a/extensions/data-plane-selector/data-plane-selector-client/build.gradle.kts b/extensions/data-plane-selector/data-plane-selector-client/build.gradle.kts index 8113d5d2702..ce73feb14b3 100644 --- a/extensions/data-plane-selector/data-plane-selector-client/build.gradle.kts +++ b/extensions/data-plane-selector/data-plane-selector-client/build.gradle.kts @@ -17,6 +17,7 @@ plugins { dependencies { api(project(":spi:data-plane-selector:data-plane-selector-spi")) + api(project(":spi:common:auth-spi")) api(project(":spi:common:http-spi")) api(project(":spi:common:transform-spi")) implementation(project(":core:common:lib:transform-lib")) diff --git a/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/DataPlaneSelectorClientExtension.java b/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/DataPlaneSelectorClientExtension.java index 83661dc84d0..ca0a267d7e1 100644 --- a/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/DataPlaneSelectorClientExtension.java +++ b/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/DataPlaneSelectorClientExtension.java @@ -15,6 +15,7 @@ package org.eclipse.edc.connector.dataplane.selector; import jakarta.json.Json; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.DataPlaneSelectorService; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.runtime.metamodel.annotation.Extension; @@ -50,6 +51,9 @@ public class DataPlaneSelectorClientExtension implements ServiceExtension { @Inject private TypeTransformerRegistry typeTransformerRegistry; + @Inject + private ControlClientAuthenticationProvider authenticationProvider; + @Override public String name() { return NAME; @@ -67,6 +71,7 @@ public DataPlaneSelectorService dataPlaneSelectorService(ServiceExtensionContext var config = context.getConfig(); var url = config.getString(DPF_SELECTOR_URL_SETTING); var selectionStrategy = config.getString(DPF_SELECTOR_STRATEGY, DataPlaneSelectorService.DEFAULT_STRATEGY); - return new RemoteDataPlaneSelectorService(httpClient, url, typeManager.getMapper(), typeTransformerRegistry, selectionStrategy); + return new RemoteDataPlaneSelectorService(httpClient, url, typeManager.getMapper(), typeTransformerRegistry, + selectionStrategy, authenticationProvider); } } diff --git a/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorService.java b/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorService.java index 4d32a1f6ab7..b9fd6cd48ad 100644 --- a/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorService.java +++ b/extensions/data-plane-selector/data-plane-selector-client/src/main/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorService.java @@ -22,6 +22,7 @@ import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.DataPlaneSelectorService; import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstance; import org.eclipse.edc.http.spi.EdcHttpClient; @@ -53,20 +54,25 @@ public class RemoteDataPlaneSelectorService implements DataPlaneSelectorService private final ObjectMapper mapper; private final TypeTransformerRegistry typeTransformerRegistry; private final String selectionStrategy; + private final ControlClientAuthenticationProvider authenticationProvider; public RemoteDataPlaneSelectorService(EdcHttpClient httpClient, String url, ObjectMapper mapper, - TypeTransformerRegistry typeTransformerRegistry, String selectionStrategy) { + TypeTransformerRegistry typeTransformerRegistry, String selectionStrategy, + ControlClientAuthenticationProvider authenticationProvider) { this.httpClient = httpClient; this.url = url; this.mapper = mapper; this.typeTransformerRegistry = typeTransformerRegistry; this.selectionStrategy = selectionStrategy; + this.authenticationProvider = authenticationProvider; } @Override public ServiceResult> getAll() { - var request = new Request.Builder().get().url(url).build(); - return request(request) + var builder = new Request.Builder().get().url(url); + authenticationProvider.authenticationHeaders().forEach(builder::header); + + return request(builder.build()) .compose(this::toJsonArray) .map(it -> it.stream() .map(j -> typeTransformerRegistry.transform(j, DataPlaneInstance.class)) @@ -89,9 +95,10 @@ public ServiceResult select(DataAddress source, String transf var body = RequestBody.create(jsonObject.toString(), TYPE_JSON); - var request = new Request.Builder().post(body).url(url + SELECT_PATH).build(); + var builder = new Request.Builder().post(body).url(url + SELECT_PATH); + authenticationProvider.authenticationHeaders().forEach(builder::header); - return request(request).compose(this::toJsonObject) + return request(builder.build()).compose(this::toJsonObject) .map(it -> typeTransformerRegistry.transform(it, DataPlaneInstance.class)) .compose(ServiceResult::from); } @@ -108,9 +115,10 @@ public ServiceResult addInstance(DataPlaneInstance instance) { .build(); var body = RequestBody.create(requestBody.toString(), TYPE_JSON); - var request = new Request.Builder().post(body).url(url).build(); + var builder = new Request.Builder().post(body).url(url); + authenticationProvider.authenticationHeaders().forEach(builder::header); - return request(request).mapEmpty(); + return request(builder.build()).mapEmpty(); } @Override diff --git a/extensions/data-plane-selector/data-plane-selector-client/src/test/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorServiceTest.java b/extensions/data-plane-selector/data-plane-selector-client/src/test/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorServiceTest.java index d71fffb5bdd..363f72f3bf1 100644 --- a/extensions/data-plane-selector/data-plane-selector-client/src/test/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorServiceTest.java +++ b/extensions/data-plane-selector/data-plane-selector-client/src/test/java/org/eclipse/edc/connector/dataplane/selector/RemoteDataPlaneSelectorServiceTest.java @@ -15,6 +15,7 @@ package org.eclipse.edc.connector.dataplane.selector; import jakarta.json.Json; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.api.transformer.JsonObjectFromIdResponseTransformer; import org.eclipse.edc.connector.dataplane.selector.control.api.DataplaneSelectorControlApiController; import org.eclipse.edc.connector.dataplane.selector.spi.DataPlaneSelectorService; @@ -56,10 +57,12 @@ class RemoteDataPlaneSelectorServiceTest extends RestControllerTestBase { private final String url = "http://localhost:%d/v1/dataplanes".formatted(port); + private final ControlClientAuthenticationProvider authenticationProvider = mock(); private final DataPlaneSelectorService serverService = mock(); private final TypeTransformerRegistry typeTransformerRegistry = new TypeTransformerRegistryImpl(); private final JsonObjectValidatorRegistry validator = mock(); - private RemoteDataPlaneSelectorService service; + private final RemoteDataPlaneSelectorService service = new RemoteDataPlaneSelectorService(testHttpClient(), url, + JacksonJsonLd.createObjectMapper(), typeTransformerRegistry, "selectionStrategy", authenticationProvider); @BeforeEach void setUp() { @@ -73,7 +76,6 @@ void setUp() { typeTransformerRegistry.register(new JsonObjectFromIdResponseTransformer(factory)); typeTransformerRegistry.register(new org.eclipse.edc.connector.dataplane.selector.control.api.transformer.JsonObjectToSelectionRequestTransformer()); typeTransformerRegistry.register(new JsonValueToGenericTypeTransformer(objectMapper)); - service = new RemoteDataPlaneSelectorService(testHttpClient(), url, JacksonJsonLd.createObjectMapper(), typeTransformerRegistry, "selectionStrategy"); } @Test @@ -86,6 +88,7 @@ void addInstance() { assertThat(result).isSucceeded(); verify(serverService).addInstance(any()); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -96,6 +99,7 @@ void select() { var result = service.select(DataAddress.Builder.newInstance().type("test1").build(), "transferType", "random"); assertThat(result).isSucceeded().usingRecursiveComparison().isEqualTo(expected); + verify(authenticationProvider).authenticationHeaders(); } @Nested diff --git a/extensions/data-plane/data-plane-client/build.gradle.kts b/extensions/data-plane/data-plane-client/build.gradle.kts index 79560b81a22..ece68df85cc 100644 --- a/extensions/data-plane/data-plane-client/build.gradle.kts +++ b/extensions/data-plane/data-plane-client/build.gradle.kts @@ -19,6 +19,7 @@ plugins { } dependencies { + api(project(":spi:common:auth-spi")) api(project(":spi:common:http-spi")) api(project(":spi:data-plane:data-plane-spi")) api(project(":spi:data-plane-selector:data-plane-selector-spi")) diff --git a/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneClientExtension.java b/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneClientExtension.java index c68277dc7dd..b3e57f3d026 100644 --- a/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneClientExtension.java +++ b/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneClientExtension.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.dataplane.client; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClientFactory; import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; import org.eclipse.edc.http.spi.EdcHttpClient; @@ -37,12 +38,12 @@ public class DataPlaneClientExtension implements ServiceExtension { @Inject(required = false) private DataPlaneManager dataPlaneManager; - @Inject(required = false) private EdcHttpClient httpClient; - @Inject private TypeManager typeManager; + @Inject + private ControlClientAuthenticationProvider authenticationProvider; @Override public String name() { @@ -59,7 +60,7 @@ public DataPlaneClientFactory dataPlaneClientFactory(ServiceExtensionContext con context.getMonitor().debug(() -> "Using remote Data Plane client."); Objects.requireNonNull(httpClient, "To use remote Data Plane client, an EdcHttpClient instance must be registered"); - return instance -> new RemoteDataPlaneClient(httpClient, typeManager.getMapper(), instance); + return instance -> new RemoteDataPlaneClient(httpClient, typeManager.getMapper(), instance, authenticationProvider); } } diff --git a/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClient.java b/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClient.java index 8562fc6354c..6d16d67a7c6 100644 --- a/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClient.java +++ b/extensions/data-plane/data-plane-client/src/main/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClient.java @@ -22,6 +22,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClient; import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstance; import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; @@ -46,11 +47,14 @@ public class RemoteDataPlaneClient implements DataPlaneClient { private final EdcHttpClient httpClient; private final ObjectMapper mapper; private final DataPlaneInstance dataPlane; + private final ControlClientAuthenticationProvider authenticationProvider; - public RemoteDataPlaneClient(EdcHttpClient httpClient, ObjectMapper mapper, DataPlaneInstance dataPlane) { + public RemoteDataPlaneClient(EdcHttpClient httpClient, ObjectMapper mapper, DataPlaneInstance dataPlane, + ControlClientAuthenticationProvider authenticationProvider) { this.httpClient = httpClient; this.mapper = mapper; this.dataPlane = dataPlane; + this.authenticationProvider = authenticationProvider; } @WithSpan @@ -62,9 +66,10 @@ public StatusResult start(DataFlowStartMessage dataFlow } catch (JsonProcessingException e) { throw new EdcException(e); } - var request = new Request.Builder().post(body).url(dataPlane.getUrl()).build(); + var builder = new Request.Builder().post(body).url(dataPlane.getUrl()); + authenticationProvider.authenticationHeaders().forEach(builder::header); - try (var response = httpClient.execute(request)) { + try (var response = httpClient.execute(builder.build())) { var result = handleResponse(response, dataFlowStartMessage.getId()); if (result.failed()) { @@ -84,9 +89,10 @@ public StatusResult suspend(String transferProcessId) { @Override public StatusResult terminate(String transferProcessId) { - var request = new Request.Builder().delete().url(dataPlane.getUrl() + "/" + transferProcessId).build(); + var builder = new Request.Builder().delete().url(dataPlane.getUrl() + "/" + transferProcessId); + authenticationProvider.authenticationHeaders().forEach(builder::header); - try (var response = httpClient.execute(request)) { + try (var response = httpClient.execute(builder.build())) { return handleResponse(response, transferProcessId); } catch (IOException e) { return StatusResult.failure(FATAL_ERROR, e.getMessage()); diff --git a/extensions/data-plane/data-plane-client/src/test/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClientTest.java b/extensions/data-plane/data-plane-client/src/test/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClientTest.java index ddcddd05f23..2b36833f5a7 100644 --- a/extensions/data-plane/data-plane-client/src/test/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClientTest.java +++ b/extensions/data-plane/data-plane-client/src/test/java/org/eclipse/edc/connector/dataplane/client/RemoteDataPlaneClientTest.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClient; import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstance; import org.eclipse.edc.connector.dataplane.spi.response.TransferErrorResponse; @@ -42,6 +43,8 @@ import static org.eclipse.edc.http.client.testfixtures.HttpTestUtils.testHttpClient; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.util.io.Ports.getFreePort; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockserver.integration.ClientAndServer.startClientAndServer; import static org.mockserver.matchers.Times.once; import static org.mockserver.model.HttpResponse.response; @@ -57,8 +60,9 @@ class RemoteDataPlaneClientTest { private static final String DATA_PLANE_PATH = "/transfer"; private static final String DATA_PLANE_API_URI = "http://localhost:" + DATA_PLANE_API_PORT + DATA_PLANE_PATH; private static ClientAndServer dataPlane; + private final ControlClientAuthenticationProvider authenticationProvider = mock(); private final DataPlaneInstance instance = DataPlaneInstance.Builder.newInstance().url(DATA_PLANE_API_URI).build(); - private final DataPlaneClient dataPlaneClient = new RemoteDataPlaneClient(testHttpClient(), MAPPER, instance); + private final DataPlaneClient dataPlaneClient = new RemoteDataPlaneClient(testHttpClient(), MAPPER, instance, authenticationProvider); @BeforeAll public static void setUp() { @@ -70,20 +74,6 @@ public static void tearDown() { stopQuietly(dataPlane); } - private static HttpResponse withResponse(String errorMsg) throws JsonProcessingException { - return response().withStatusCode(HttpStatusCode.BAD_REQUEST_400.code()) - .withBody(MAPPER.writeValueAsString(new TransferErrorResponse(List.of(errorMsg))), MediaType.APPLICATION_JSON); - } - - private static DataFlowStartMessage createDataFlowRequest() { - return DataFlowStartMessage.Builder.newInstance() - .id("123") - .processId("456") - .sourceDataAddress(DataAddress.Builder.newInstance().type("test").build()) - .destinationDataAddress(DataAddress.Builder.newInstance().type("test").build()) - .build(); - } - @AfterEach public void resetMockServer() { dataPlane.reset(); @@ -140,6 +130,7 @@ void transfer_verifyTransferSuccess() throws JsonProcessingException { dataPlane.verify(httpRequest, VerificationTimes.once()); assertThat(result.succeeded()).isTrue(); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -151,6 +142,7 @@ void terminate_shouldCallTerminateOnAllTheAvailableDataPlanes() { assertThat(result).isSucceeded(); dataPlane.verify(httpRequest, VerificationTimes.once()); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -162,4 +154,19 @@ void terminate_shouldFail_whenConflictResponse() { assertThat(result).isFailed(); } + + private HttpResponse withResponse(String errorMsg) throws JsonProcessingException { + return response().withStatusCode(HttpStatusCode.BAD_REQUEST_400.code()) + .withBody(MAPPER.writeValueAsString(new TransferErrorResponse(List.of(errorMsg))), MediaType.APPLICATION_JSON); + } + + private DataFlowStartMessage createDataFlowRequest() { + return DataFlowStartMessage.Builder.newInstance() + .id("123") + .processId("456") + .sourceDataAddress(DataAddress.Builder.newInstance().type("test").build()) + .destinationDataAddress(DataAddress.Builder.newInstance().type("test").build()) + .build(); + } + } diff --git a/extensions/data-plane/data-plane-http/build.gradle.kts b/extensions/data-plane/data-plane-http/build.gradle.kts index 034d008c316..029bfb81841 100644 --- a/extensions/data-plane/data-plane-http/build.gradle.kts +++ b/extensions/data-plane/data-plane-http/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testImplementation(project(":core:common:junit")) testImplementation(project(":core:data-plane:data-plane-core")) - testImplementation(project(":extensions:control-plane:api:control-plane-api-client")) + testImplementation(project(":extensions:common:json-ld")) testImplementation(libs.restAssured) testImplementation(libs.mockserver.netty) diff --git a/extensions/data-plane/data-plane-http/src/test/java/org/eclipse/edc/connector/dataplane/http/DataPlaneHttpExtensionTest.java b/extensions/data-plane/data-plane-http/src/test/java/org/eclipse/edc/connector/dataplane/http/DataPlaneHttpExtensionTest.java index fcb36c6de40..d603bdcfed2 100644 --- a/extensions/data-plane/data-plane-http/src/test/java/org/eclipse/edc/connector/dataplane/http/DataPlaneHttpExtensionTest.java +++ b/extensions/data-plane/data-plane-http/src/test/java/org/eclipse/edc/connector/dataplane/http/DataPlaneHttpExtensionTest.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.dataplane.http; +import org.eclipse.edc.connector.controlplane.api.client.spi.transferprocess.TransferProcessApiClient; import org.eclipse.edc.connector.dataplane.http.spi.HttpDataAddress; import org.eclipse.edc.connector.dataplane.http.spi.HttpRequestParamsProvider; import org.eclipse.edc.connector.dataplane.spi.pipeline.PipelineService; @@ -33,6 +34,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.util.io.Ports.getFreePort; +import static org.mockito.Mockito.mock; import static org.mockserver.integration.ClientAndServer.startClientAndServer; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.stop.Stop.stopQuietly; @@ -46,7 +48,8 @@ public class DataPlaneHttpExtensionTest { private static final int DESTINATION_PORT = getFreePort(); @BeforeAll - public static void setUp() { + public static void setUp(EdcExtension extension) { + extension.registerServiceMock(TransferProcessApiClient.class, mock()); sourceServer = startClientAndServer(SOURCE_PORT); destinationServer = startClientAndServer(DESTINATION_PORT); } diff --git a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiExtension.java b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiExtension.java index e270998c2d9..e9dc0d86795 100644 --- a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiExtension.java +++ b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiExtension.java @@ -36,8 +36,11 @@ /** * This extension provides generic endpoints which are open to public participants of the Dataspace to execute * requests on the actual data source. + * + * @deprecated will be replaced by v2. */ @Extension(value = DataPlanePublicApiExtension.NAME) +@Deprecated(since = "0.6.0") public class DataPlanePublicApiExtension implements ServiceExtension { public static final String NAME = "Data Plane Public API"; private static final int DEFAULT_PUBLIC_PORT = 8185; diff --git a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApi.java b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApi.java index a90f74be0f2..36188bdf8a3 100644 --- a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApi.java +++ b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApi.java @@ -32,6 +32,7 @@ "is a Rest API itself." + "In the same manner, any set of arbitrary query parameters, path parameters and request body are supported " + "(in the limits fixed by the HTTP server) and can also conveyed to the actual data source.") +@Deprecated(since = "0.6.0") public interface DataPlanePublicApi { @Operation(description = "Send `GET` data query to the Data Plane.", diff --git a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApiController.java b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApiController.java index 64fe5effe91..de0e566e857 100644 --- a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApiController.java +++ b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/controller/DataPlanePublicApiController.java @@ -48,6 +48,7 @@ @Path("{any:.*}") @Produces(WILDCARD) +@Deprecated(since = "0.6.0") public class DataPlanePublicApiController implements DataPlanePublicApi { private final PipelineService pipelineService; diff --git a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/validation/ConsumerPullTransferDataAddressResolver.java b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/validation/ConsumerPullTransferDataAddressResolver.java index 2ae75e9767c..908822ac5c8 100644 --- a/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/validation/ConsumerPullTransferDataAddressResolver.java +++ b/extensions/data-plane/data-plane-public-api/src/main/java/org/eclipse/edc/connector/dataplane/api/validation/ConsumerPullTransferDataAddressResolver.java @@ -26,6 +26,7 @@ import static java.lang.String.format; +@Deprecated(since = "0.6.0") public class ConsumerPullTransferDataAddressResolver implements DataAddressResolver { private final EdcHttpClient httpClient; diff --git a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/build.gradle.kts b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/build.gradle.kts index 0ad242439b2..5547bfee5b9 100644 --- a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/build.gradle.kts +++ b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/build.gradle.kts @@ -18,6 +18,7 @@ plugins { } dependencies { + api(project(":spi:common:auth-spi")) api(project(":spi:common:http-spi")) api(project(":spi:common:core-spi")) api(project(":spi:data-plane-selector:data-plane-selector-spi")) diff --git a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClient.java b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClient.java index 5b8ce491558..abaf7fcbb4b 100644 --- a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClient.java +++ b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClient.java @@ -23,6 +23,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClient; import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstance; import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; @@ -51,17 +52,21 @@ public class DataPlaneSignalingClient implements DataPlaneClient { public static final MediaType TYPE_JSON = MediaType.parse("application/json"); private final EdcHttpClient httpClient; private final DataPlaneInstance dataPlane; + private final ControlClientAuthenticationProvider authenticationProvider; private final TypeTransformerRegistry transformerRegistry; private final JsonLd jsonLd; private final ObjectMapper mapper; - public DataPlaneSignalingClient(EdcHttpClient httpClient, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, ObjectMapper mapper, DataPlaneInstance dataPlane) { + public DataPlaneSignalingClient(EdcHttpClient httpClient, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, + ObjectMapper mapper, DataPlaneInstance dataPlane, + ControlClientAuthenticationProvider authenticationProvider) { this.httpClient = httpClient; this.transformerRegistry = transformerRegistry; this.jsonLd = jsonLd; this.mapper = mapper; this.dataPlane = dataPlane; + this.authenticationProvider = authenticationProvider; } @WithSpan @@ -98,11 +103,12 @@ private StatusResult send(Object message, String url, String processId, F .compose(jsonLd::compact) .compose(this::serializeMessage) .map(rawBody -> RequestBody.create(rawBody, TYPE_JSON)) - .map(body -> new Request.Builder().post(body).url(url).build()); + .map(body -> new Request.Builder().post(body).url(url)); if (requestBuilder.succeeded()) { - var request = requestBuilder.getContent(); - try (var response = httpClient.execute(request)) { + var builder = requestBuilder.getContent(); + authenticationProvider.authenticationHeaders().forEach(builder::header); + try (var response = httpClient.execute(builder.build())) { if (response.isSuccessful()) { return handleStartResponse.apply(response); } else { diff --git a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientExtension.java b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientExtension.java index c3d28940590..e8f40c13274 100644 --- a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientExtension.java +++ b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/main/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientExtension.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.dataplane.client; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClient; import org.eclipse.edc.connector.dataplane.selector.spi.client.DataPlaneClientFactory; import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; @@ -40,17 +41,16 @@ public class DataPlaneSignalingClientExtension implements ServiceExtension { @Inject(required = false) private EdcHttpClient httpClient; - @Inject private TypeManager typeManager; - @Inject private TypeTransformerRegistry transformerRegistry; - @Inject private JsonLd jsonLd; @Inject(required = false) private DataPlaneManager dataPlaneManager; + @Inject + private ControlClientAuthenticationProvider authenticationProvider; @Override public String name() { @@ -70,7 +70,8 @@ public DataPlaneClientFactory dataPlaneClientFactory(ServiceExtensionContext con context.getMonitor().debug(() -> "Using remote Data Plane client."); Objects.requireNonNull(httpClient, "To use remote Data Plane client, an EdcHttpClient instance must be registered"); var signalingApiTypeTransformerRegistry = transformerRegistry.forContext("signaling-api"); - return instance -> new DataPlaneSignalingClient(httpClient, signalingApiTypeTransformerRegistry, jsonLd, mapper, instance); + return instance -> new DataPlaneSignalingClient(httpClient, signalingApiTypeTransformerRegistry, jsonLd, mapper, + instance, authenticationProvider); } } diff --git a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/test/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientTest.java b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/test/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientTest.java index 9abe1cfa62b..aec57a9ac3b 100644 --- a/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/test/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientTest.java +++ b/extensions/data-plane/data-plane-signaling/data-plane-signaling-client/src/test/java/org/eclipse/edc/connector/dataplane/client/DataPlaneSignalingClientTest.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.Json; import jakarta.json.JsonObject; +import org.eclipse.edc.api.auth.spi.ControlClientAuthenticationProvider; import org.eclipse.edc.connector.api.signaling.transform.from.JsonObjectFromDataFlowResponseMessageTransformer; import org.eclipse.edc.connector.api.signaling.transform.from.JsonObjectFromDataFlowStartMessageTransformer; import org.eclipse.edc.connector.api.signaling.transform.from.JsonObjectFromDataFlowSuspendMessageTransformer; @@ -67,6 +68,7 @@ import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockserver.integration.ClientAndServer.startClientAndServer; import static org.mockserver.matchers.Times.once; @@ -87,8 +89,10 @@ class DataPlaneSignalingClientTest { private static final TypeTransformerRegistry TRANSFORMER_REGISTRY = new TypeTransformerRegistryImpl(); private static final TitaniumJsonLd JSON_LD = new TitaniumJsonLd(mock(Monitor.class)); private static ClientAndServer dataPlane; + private final ControlClientAuthenticationProvider authenticationProvider = mock(); private final DataPlaneInstance instance = DataPlaneInstance.Builder.newInstance().url(DATA_PLANE_API_URI).build(); - private final DataPlaneClient dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), TRANSFORMER_REGISTRY, JSON_LD, MAPPER, instance); + private final DataPlaneClient dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), TRANSFORMER_REGISTRY, + JSON_LD, MAPPER, instance, authenticationProvider); @BeforeAll public static void setUp() { @@ -115,26 +119,6 @@ public void resetMockServer() { dataPlane.reset(); } - - private HttpResponse withResponse(String errorMsg) throws JsonProcessingException { - return response().withStatusCode(HttpStatusCode.BAD_REQUEST_400.code()) - .withBody(MAPPER.writeValueAsString(new TransferErrorResponse(List.of(errorMsg))), MediaType.APPLICATION_JSON); - } - - private DataFlowStartMessage createDataFlowRequest() { - return DataFlowStartMessage.Builder.newInstance() - .id("123") - .processId("456") - .flowType(FlowType.PULL) - .assetId("assetId") - .agreementId("agreementId") - .participantId("participantId") - .callbackAddress(URI.create("http://void")) - .sourceDataAddress(DataAddress.Builder.newInstance().type("test").build()) - .destinationDataAddress(DataAddress.Builder.newInstance().type("test").build()) - .build(); - } - @Nested class Start { @@ -189,7 +173,7 @@ void verifyReturnFatalErrorIfReceiveErrorInResponse() throws JsonProcessingExcep void verifyReturnFatalErrorIfTransformFails() { var flowRequest = createDataFlowRequest(); TypeTransformerRegistry registry = mock(); - var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance); + var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance, authenticationProvider); when(registry.transform(any(), any())).thenReturn(Result.failure("Transform Failure")); @@ -203,7 +187,6 @@ void verifyReturnFatalErrorIfTransformFails() { ); } - @Test void verifyReturnFatalError_whenBadResponse() throws JsonProcessingException { var flowRequest = createDataFlowRequest(); @@ -231,7 +214,7 @@ void verifyReturnFatalError_whenBadResponse() throws JsonProcessingException { void verifyReturnFatalErrorWhenDataPlaneInstanceIsNull() { var flowRequest = createDataFlowRequest(); TypeTransformerRegistry registry = mock(); - var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, null); + var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, null, authenticationProvider); var result = dataPlaneClient.start(flowRequest); @@ -260,9 +243,8 @@ void verifyTransferSuccess() throws JsonProcessingException { dataPlane.verify(httpRequest, VerificationTimes.once()); - assertThat(result.succeeded()).isTrue(); - - assertThat(result.getContent().getDataAddress()).isNotNull(); + assertThat(result).isSucceeded().extracting(DataFlowResponseMessage::getDataAddress).isNotNull(); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -289,6 +271,26 @@ void verifyTransferSuccess_withoutDataAddress() throws JsonProcessingException { assertThat(result.getContent().getDataAddress()).isNull(); } + + private HttpResponse withResponse(String errorMsg) throws JsonProcessingException { + return response().withStatusCode(HttpStatusCode.BAD_REQUEST_400.code()) + .withBody(MAPPER.writeValueAsString(new TransferErrorResponse(List.of(errorMsg))), MediaType.APPLICATION_JSON); + } + + private DataFlowStartMessage createDataFlowRequest() { + return DataFlowStartMessage.Builder.newInstance() + .id("123") + .processId("456") + .flowType(FlowType.PULL) + .assetId("assetId") + .agreementId("agreementId") + .participantId("participantId") + .callbackAddress(URI.create("http://void")) + .sourceDataAddress(DataAddress.Builder.newInstance().type("test").build()) + .destinationDataAddress(DataAddress.Builder.newInstance().type("test").build()) + .build(); + } + } @Nested @@ -303,6 +305,7 @@ void shouldCallTerminateOnAllTheAvailableDataPlanes() { assertThat(result).isSucceeded(); dataPlane.verify(httpRequest, VerificationTimes.once()); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -318,7 +321,7 @@ void shouldFail_whenConflictResponse() { @Test void verifyReturnFatalErrorIfTransformFails() { TypeTransformerRegistry registry = mock(); - var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance); + var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance, authenticationProvider); when(registry.transform(any(), any())).thenReturn(Result.failure("Transform Failure")); @@ -346,6 +349,7 @@ void shouldCallSuspendOnAllTheAvailableDataPlanes() { assertThat(result).isSucceeded(); dataPlane.verify(httpRequest, VerificationTimes.once()); + verify(authenticationProvider).authenticationHeaders(); } @Test @@ -361,7 +365,7 @@ void shouldFail_whenConflictResponse() { @Test void verifyReturnFatalErrorIfTransformFails() { TypeTransformerRegistry registry = mock(); - var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance); + var dataPlaneClient = new DataPlaneSignalingClient(testHttpClient(), registry, JSON_LD, MAPPER, instance, authenticationProvider); when(registry.transform(any(), any())).thenReturn(Result.failure("Transform Failure")); diff --git a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilter.java b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilter.java index b389665a1c5..3b044fa2c3c 100644 --- a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilter.java +++ b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilter.java @@ -16,12 +16,13 @@ import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.container.ContainerRequestFilter; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.web.spi.exception.AuthenticationFailedException; import java.util.Map; -import java.util.stream.Collectors; import static jakarta.ws.rs.HttpMethod.OPTIONS; +import static java.util.stream.Collectors.toMap; /** * Intercepts all requests sent to this resource and authenticates them using an {@link AuthenticationService}. In order @@ -29,19 +30,23 @@ * contain credentials. */ public class AuthenticationRequestFilter implements ContainerRequestFilter { - private final AuthenticationService authenticationService; - public AuthenticationRequestFilter(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; + private final ApiAuthenticationRegistry authenticationRegistry; + private final String context; + + public AuthenticationRequestFilter(ApiAuthenticationRegistry authenticationRegistry, String context) { + this.authenticationRegistry = authenticationRegistry; + this.context = context; } @Override public void filter(ContainerRequestContext requestContext) { - var headers = requestContext.getHeaders(); // OPTIONS requests don't have credentials - do not authenticate if (!OPTIONS.equalsIgnoreCase(requestContext.getMethod())) { - var isAuthenticated = authenticationService.isAuthenticated(headers.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + var headers = requestContext.getHeaders().entrySet().stream() + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + var isAuthenticated = authenticationRegistry.resolve(context).isAuthenticated(headers); if (!isAuthenticated) { throw new AuthenticationFailedException(); } diff --git a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/ControlClientAuthenticationProvider.java b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/ControlClientAuthenticationProvider.java new file mode 100644 index 00000000000..026042bd3d1 --- /dev/null +++ b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/ControlClientAuthenticationProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 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.api.auth.spi; + +import java.util.Map; + +/** + * Provide headers to be used in intra-components (control-api) communication. + */ +@FunctionalInterface +public interface ControlClientAuthenticationProvider { + + /** + * Return headers needed to authenticate the client + * + * @return the headers stored in a {@link Map}. + */ + Map authenticationHeaders(); + +} diff --git a/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/registry/ApiAuthenticationRegistry.java b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/registry/ApiAuthenticationRegistry.java new file mode 100644 index 00000000000..b24f4b23297 --- /dev/null +++ b/spi/common/auth-spi/src/main/java/org/eclipse/edc/api/auth/spi/registry/ApiAuthenticationRegistry.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 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.api.auth.spi.registry; + +import org.eclipse.edc.api.auth.spi.AuthenticationService; +import org.jetbrains.annotations.NotNull; + +/** + * Registry for {@link AuthenticationService}. + */ +public interface ApiAuthenticationRegistry { + + /** + * Register {@link AuthenticationService} for the specified API context. + * + * @param context the context. + * @param service the service. + */ + void register(String context, AuthenticationService service); + + /** + * Resolve {@link AuthenticationService} for the specified context. If no service was registered, an "all-pass" will + * be returned. + * + * @param context the context. + * @return the {@link AuthenticationService} + */ + @NotNull AuthenticationService resolve(String context); + +} diff --git a/spi/common/auth-spi/src/test/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilterTest.java b/spi/common/auth-spi/src/test/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilterTest.java index aba14c7e834..58c2bda2289 100644 --- a/spi/common/auth-spi/src/test/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilterTest.java +++ b/spi/common/auth-spi/src/test/java/org/eclipse/edc/api/auth/spi/AuthenticationRequestFilterTest.java @@ -16,11 +16,11 @@ import jakarta.ws.rs.container.ContainerRequestContext; import jakarta.ws.rs.core.MultivaluedHashMap; +import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.web.spi.exception.AuthenticationFailedException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.Map; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -32,47 +32,48 @@ class AuthenticationRequestFilterTest { - private AuthenticationRequestFilter filter; - private AuthenticationService authSrvMock; + private final AuthenticationService authenticationService = mock(); + private final ApiAuthenticationRegistry authenticationRegistry = mock(); + + private final AuthenticationRequestFilter filter = new AuthenticationRequestFilter(authenticationRegistry, "context"); @BeforeEach void setUp() { - authSrvMock = mock(AuthenticationService.class); - filter = new AuthenticationRequestFilter(authSrvMock); + when(authenticationRegistry.resolve("context")).thenReturn(authenticationService); } @Test void filter() { - when(authSrvMock.isAuthenticated(anyMap())).thenReturn(true); + when(authenticationService.isAuthenticated(anyMap())).thenReturn(true); var contextMock = mock(ContainerRequestContext.class); when(contextMock.getHeaders()).thenReturn(new MultivaluedHashMap<>(Map.of("foo", "bar"))); filter.filter(contextMock); //should not throw an exception - verify(authSrvMock).isAuthenticated(anyMap()); + verify(authenticationService).isAuthenticated(anyMap()); } @Test - void filter_serviceThrowsException() throws IOException { + void filter_serviceThrowsException() { var exc = new AuthenticationFailedException("test"); - when(authSrvMock.isAuthenticated(anyMap())).thenThrow(exc); + when(authenticationService.isAuthenticated(anyMap())).thenThrow(exc); var contextMock = mock(ContainerRequestContext.class); when(contextMock.getHeaders()).thenReturn(new MultivaluedHashMap<>(Map.of("foo", "bar"))); assertThatThrownBy(() -> filter.filter(contextMock)).isInstanceOf(AuthenticationFailedException.class).hasMessage("test"); - verify(authSrvMock).isAuthenticated(anyMap()); + verify(authenticationService).isAuthenticated(anyMap()); } @Test void filter_notAuthorized() { - when(authSrvMock.isAuthenticated(anyMap())).thenReturn(false); + when(authenticationService.isAuthenticated(anyMap())).thenReturn(false); var contextMock = mock(ContainerRequestContext.class); when(contextMock.getHeaders()).thenReturn(new MultivaluedHashMap<>(Map.of("foo", "bar"))); assertThatThrownBy(() -> filter.filter(contextMock)).isInstanceOf(AuthenticationFailedException.class); - verify(authSrvMock).isAuthenticated(anyMap()); + verify(authenticationService).isAuthenticated(anyMap()); } @Test @@ -81,6 +82,6 @@ void filter_shouldSkipOnOptions() { when(contextMock.getMethod()).thenReturn("OPTIONS"); filter.filter(contextMock); - verify(authSrvMock, never()).isAuthenticated(anyMap()); + verify(authenticationService, never()).isAuthenticated(anyMap()); } -} \ No newline at end of file +} diff --git a/system-tests/management-api/management-api-test-runtime/build.gradle.kts b/system-tests/management-api/management-api-test-runtime/build.gradle.kts index ca27ce7be04..b4ecd9d264a 100644 --- a/system-tests/management-api/management-api-test-runtime/build.gradle.kts +++ b/system-tests/management-api/management-api-test-runtime/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(project(":extensions:common:http")) implementation(project(":extensions:common:iam:iam-mock")) implementation(project(":extensions:common:json-ld")) + implementation(project(":extensions:common:api:control-api-configuration")) implementation(project(":extensions:control-plane:api:management-api")) implementation(project(":extensions:control-plane:api:management-api:secrets-api")) implementation(project(":extensions:data-plane:data-plane-client")) diff --git a/system-tests/telemetry/telemetry-test-runtime/build.gradle.kts b/system-tests/telemetry/telemetry-test-runtime/build.gradle.kts index 96789fc6d24..2a55a2ec345 100644 --- a/system-tests/telemetry/telemetry-test-runtime/build.gradle.kts +++ b/system-tests/telemetry/telemetry-test-runtime/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { implementation(project(":extensions:common:http")) implementation(project(":extensions:common:iam:iam-mock")) implementation(project(":extensions:common:json-ld")) + implementation(project(":extensions:common:api:control-api-configuration")) implementation(project(":extensions:control-plane:api:management-api")) implementation(project(":extensions:data-plane:data-plane-client"))