diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java index 6559227d384fd2af43f561149553e38c91029b82..89761838c98336a31ca91d874373d5efa68792d1 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteService.java @@ -11,7 +11,7 @@ import de.ozgcloud.nachrichten.postfach.osiv2.transfer.PostfachApiFacadeService; import lombok.extern.log4j.Log4j2; @Service -@ConditionalOnProperty("ozgcloud.osiv2-postfach.enabled") +@ConditionalOnProperty("ozgcloud.osiv2.enabled") @Log4j2 public record OsiPostfachRemoteService( PostfachApiFacadeService postfachApiFacadeService diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java index 650960e79abaad1ee18de412e30d6ff0e4fa54e1..5b34ff84d276b7d339010ce28efa82ec1a5df252 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/ApiClientConfiguration.java @@ -1,6 +1,8 @@ package de.ozgcloud.nachrichten.postfach.osiv2.config; -import jakarta.annotation.PostConstruct; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.ApiClient; +import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi; +import lombok.RequiredArgsConstructor; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; @@ -10,33 +12,26 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.client.*; -import org.springframework.security.oauth2.client.endpoint.*; +import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.endpoint.RestClientClientCredentialsTokenResponseClient; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ClientRegistrations; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.client.OAuth2ClientHttpRequestInterceptor; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; -import de.ozgcloud.nachrichten.postfach.osiv2.gen.ApiClient; -import de.ozgcloud.nachrichten.postfach.osiv2.gen.api.MessageExchangeApi; -import lombok.RequiredArgsConstructor; - @Configuration @EnableWebSecurity @RequiredArgsConstructor -@ConditionalOnProperty("ozgcloud.osiv2-postfach.enabled") +@ConditionalOnProperty("ozgcloud.osiv2.enabled") public class ApiClientConfiguration { private final OsiPostfachProperties.ApiConfiguration apiConfiguration; @@ -49,139 +44,97 @@ public class ApiClientConfiguration { } @Bean - ApiClient apiClient(OAuth2AuthorizedClientManager authorizedClientManager){ - var apiClient = new ApiClient(restClient(authorizedClientManager)); + ApiClient apiClient(RestClient restClient) { + var apiClient = new ApiClient(restClient); apiClient.setBasePath(apiConfiguration.getUrl()); return apiClient; } - @Bean - public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) { + public RestClient restClient(ClientRegistrationRepository clientRegistrations) { OAuth2ClientHttpRequestInterceptor requestInterceptor = - new OAuth2ClientHttpRequestInterceptor(authorizedClientManager); + new OAuth2ClientHttpRequestInterceptor(authorizedClientManager(clientRegistrations)); + requestInterceptor.setClientRegistrationIdResolver(request -> "osi2"); - return RestClient.builder() + return defaultRestClientBuilder() .requestInterceptor(requestInterceptor) - . - .build(); } - - - private RestClient restClient; - - @PostConstruct - void initialize() { - - this.restClient = RestClient.builder() - .messageConverters((messageConverters) -> { - messageConverters.clear(); - messageConverters.add(new FormHttpMessageConverter()); -// messageConverters.add(new JsonbHttpMessageConverter()); - messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter()); - }) - .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler()) - // TODO: Customize the instance of RestClient as needed... - .build(); + private RestClient.Builder defaultRestClientBuilder() { + return RestClient.builder() + .requestFactory(createProxyRequestFactory()); } - @Bean - public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() { - RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient = - new RestClientAuthorizationCodeTokenResponseClient(); - accessTokenResponseClient.setRestClient(this.restClient); - - return accessTokenResponseClient; + private ClientHttpRequestFactory createProxyRequestFactory() { + var requestFactory = new HttpComponentsClientHttpRequestFactory(); + if (proxyConfiguration.isEnabled()) { + requestFactory.setHttpClient( + HttpClientBuilder.create() + .setProxy(new HttpHost(proxyConfiguration.getHost(), proxyConfiguration.getPort())) + .setDefaultCredentialsProvider(basicCredentialsProviderForProxy()) + .build() + ); + } + return requestFactory; } - @Bean - public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenAccessTokenResponseClient() { - RestClientRefreshTokenTokenResponseClient accessTokenResponseClient = - new RestClientRefreshTokenTokenResponseClient(); - accessTokenResponseClient.setRestClient(this.restClient); - - return accessTokenResponseClient; + private BasicCredentialsProvider basicCredentialsProviderForProxy() { + var credentialsProvider = new BasicCredentialsProvider(); + var username = proxyConfiguration.getUsername(); + var password = proxyConfiguration.getPassword(); + if (username != null && password != null) { + credentialsProvider.setCredentials(new AuthScope(proxyConfiguration.getHost(), proxyConfiguration.getPort()), + new UsernamePasswordCredentials(username, password.toCharArray())); + } + return credentialsProvider; } - @Bean - public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() { - RestClientClientCredentialsTokenResponseClient accessTokenResponseClient = - new RestClientClientCredentialsTokenResponseClient(); - accessTokenResponseClient.setRestClient(this.restClient); + private AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrations) { + var clientService = new InMemoryOAuth2AuthorizedClientService( + clientRegistrations); + var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( + clientRegistrations, clientService); - return accessTokenResponseClient; - } + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider()); - @Bean - public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() { - return (grantRequest) -> { - throw new UnsupportedOperationException("The `password` grant type is not supported."); - }; + return authorizedClientManager; } - @Bean - public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() { - RestClientJwtBearerTokenResponseClient accessTokenResponseClient = - new RestClientJwtBearerTokenResponseClient(); - accessTokenResponseClient.setRestClient(this.restClient); - - return accessTokenResponseClient; + private OAuth2AuthorizedClientProvider authorizedClientProvider() { + return OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials(builder -> + builder.accessTokenResponseClient(clientCredentialsTokenResponseClient()) + ) + .build(); } - @Bean - public OAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeAccessTokenResponseClient() { - RestClientTokenExchangeTokenResponseClient accessTokenResponseClient = - new RestClientTokenExchangeTokenResponseClient(); - accessTokenResponseClient.setRestClient(this.restClient); - - return accessTokenResponseClient; + private RestClientClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient() { + var client = new RestClientClientCredentialsTokenResponseClient(); + configureClientCredentialsRestClient(client); + configureParametersForTokenRequests(client); + return client; } -// @Bean -// RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager) { -// -// RestClient restClient = RestClient.builder() -// .requestFactory(createProxyRequestFactory()) -// .requestInterceptor(createOAuth2Interceptor(authorizedClientManager)) -// .baseUrl(apiConfiguration.getUrl()) -//// .defaultHeader(RESOURCE_HEADER, apiConfiguration.getResource()) -// .build(); -// return restClient; -// } - -// @Bean -// public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient(RestClient restClient) { -// RestClientClientCredentialsTokenResponseClient accessTokenResponseClient = -// new RestClientClientCredentialsTokenResponseClient(); -// accessTokenResponseClient.setRestClient(restClient); -// -// return accessTokenResponseClient; -// } - - - private ClientHttpRequestFactory createProxyRequestFactory(){ - var requestFactory = new HttpComponentsClientHttpRequestFactory(); - if(proxyConfiguration.isEnabled()){ - var credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope(proxyConfiguration.getHost(), proxyConfiguration.getPort()), -//TODO: hier brauchen wir noch eine Ordentliche Lösung -// new UsernamePasswordCredentials(proxyConfiguration.getUsername(), proxyConfiguration.getPassword().toCharArray()) - new UsernamePasswordCredentials("", "".toCharArray()) - ); - var httpClient = HttpClientBuilder.create() - .setProxy(new HttpHost(proxyConfiguration.getHost(), proxyConfiguration.getPort())) - .setDefaultCredentialsProvider(credsProvider) - .build(); - requestFactory.setHttpClient(httpClient); - } - return requestFactory; + private void configureClientCredentialsRestClient(RestClientClientCredentialsTokenResponseClient client) { + client.setRestClient(defaultRestClientBuilder() + .messageConverters(messageConverters -> { + messageConverters.clear(); + messageConverters.add(new FormHttpMessageConverter()); + messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter()); + }) + .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler()) + .build()); } - private ClientHttpRequestInterceptor createOAuth2Interceptor(OAuth2AuthorizedClientManager authorizedClientManager) { - var interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager); - return interceptor; + private void configureParametersForTokenRequests(RestClientClientCredentialsTokenResponseClient client) { + client.addParametersConverter(source -> { + MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); + // Pass a resource indicator parameter https://datatracker.ietf.org/doc/html/rfc8707 + parameters.add("resource", apiConfiguration.getResource()); + return parameters; + }); } + } diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java index 44fd8ef163bbfae627be0353f5346af4a4944dbc..1d41b28f159a49d063a09392d15241bd848547a4 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/config/OsiPostfachProperties.java @@ -14,7 +14,7 @@ import lombok.Setter; @ConfigurationProperties(prefix = OsiPostfachProperties.PREFIX) public class OsiPostfachProperties { - static final String PREFIX = "ozgcloud.osiv2-postfach"; + static final String PREFIX = "ozgcloud.osiv2"; private boolean enabled; @@ -40,11 +40,13 @@ public class OsiPostfachProperties { @Configuration @ConfigurationProperties(prefix = ProxyConfiguration.PREFIX) static class ProxyConfiguration { - static final String PREFIX = OsiPostfachProperties.PREFIX + ".http-proxy"; + static final String PREFIX = OsiPostfachProperties.PREFIX + ".proxy"; private boolean enabled; + @Nullable private String host; + @Nullable private Integer port; @Nullable diff --git a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java index 858f50cdc1c140138cf63e258dbaa318fbdcdf90..c815b32ed002daf1ddb9d0da2bb35a02300a5df7 100644 --- a/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java +++ b/src/main/java/de/ozgcloud/nachrichten/postfach/osiv2/transfer/PostfachApiFacadeService.java @@ -13,7 +13,7 @@ import lombok.extern.log4j.Log4j2; @Log4j2 @Service -@ConditionalOnProperty("ozgcloud.osiv2-postfach.enabled") +@ConditionalOnProperty("ozgcloud.osiv2.enabled") public record PostfachApiFacadeService(MessageExchangeApi messageExchangeApi, RequestMapper requestMapper, ResponseMapper responseMapper) { private static int MAX_NUMBER_RECEIVED_MESSAGES = 100; diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java index eaf8f546886bd13409d8f28596bc86b23c530d25..885c5a2ac1425dae711ae924550c2eb17272a04c 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceITCase.java @@ -47,7 +47,7 @@ import lombok.SneakyThrows; @SpringBootTest(classes = TestApplication.class) @ActiveProfiles("itcase") @TestPropertySource(properties = { - "ozgcloud.osiv2-postfach.http-proxy.enabled=false", + "ozgcloud.osiv2.proxy.enabled=false", }) public class OsiPostfachRemoteServiceITCase { @@ -64,8 +64,8 @@ public class OsiPostfachRemoteServiceITCase { registry.add("spring.security.oauth2.client.provider.osi2.token-uri", OSI_MOCK_SERVER_EXTENSION::getAccessTokenUrl); registry.add("spring.security.oauth2.client.registration.osi2.scope", () -> CLIENT_SCOPES); registry.add("spring.security.oauth2.client.registration.osi2.client-id", () -> CLIENT_ID); - registry.add("ozgcloud.osiv2-postfach.api.url", OSI_MOCK_SERVER_EXTENSION::getPostfachFacadeUrl); - registry.add("ozgcloud.osiv2-postfach.api.resource", () -> RESOURCE_URN); + registry.add("ozgcloud.osiv2.api.url", OSI_MOCK_SERVER_EXTENSION::getPostfachFacadeUrl); + registry.add("ozgcloud.osiv2.api.resource", () -> RESOURCE_URN); } private MockServerClient postfachFacadeMockClient; @@ -103,7 +103,7 @@ public class OsiPostfachRemoteServiceITCase { ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); - @Disabled +// @Disabled @DisplayName("should receive one messages") @Test @SneakyThrows @@ -120,7 +120,7 @@ public class OsiPostfachRemoteServiceITCase { assertThat(messageList).size().isEqualTo(1); } - @Disabled +// @Disabled @DisplayName("should receive two messages") @Test @SneakyThrows @@ -170,7 +170,7 @@ public class OsiPostfachRemoteServiceITCase { createMessagesCall("getMessage", messageJson); } - @Disabled +// @Disabled @DisplayName("should delete message") @Test @SneakyThrows diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java index 6012b3c7a331124e0c4403b82ccf38e6ae86c644..2ef54e19469d70d16511d1ebeb80c4cce1ee22fd 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/OsiPostfachRemoteServiceRemoteITCase.java @@ -39,18 +39,18 @@ public class OsiPostfachRemoteServiceRemoteITCase { .build()) .build(); - @DynamicPropertySource + //@DynamicPropertySource static void dynamicProperties(DynamicPropertyRegistry registry) { registry.add( "spring.security.oauth2.client.registration.osi2.client-secret", () -> System.getenv("SH_STAGE_CLIENT_SECRET") ); registry.add( - "ozgcloud.osiv2-postfach.http-proxy.host", + "ozgcloud.osiv2.proxy.host", () -> matchProxyRegex(System.getenv("HTTP_PROXY")).group(1) ); registry.add( - "ozgcloud.osiv2-postfach.http-proxy.port", + "ozgcloud.osiv2.proxy.port", () -> matchProxyRegex(System.getenv("HTTP_PROXY")).group(2) ); } @@ -63,7 +63,7 @@ public class OsiPostfachRemoteServiceRemoteITCase { throw new IllegalArgumentException("Proxy host and port not found in '%s'".formatted(text)); } - @Disabled + //@Disabled @DisplayName("send message") @Nested class TestSendMessage { diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java index 5ce2169b26b87ac81589556b0a6f5469d89f68fb..9d59d950c805922702d0a18bf38cbdb30e6c3a5c 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/extension/OsiMockServerExtension.java @@ -105,7 +105,7 @@ public class OsiMockServerExtension implements BeforeAllCallback, AfterAllCallba .withMethod("POST") .withPath("/access-token") .withHeaders( - header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") + header("Content-Type", "application/x-www-form-urlencoded") ) .withBody( params( diff --git a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java index 5353606ac693ef6d98d03dc58d77d900c4943279..fb9565d6714674ce91bdd0d275c67e46b91a0c76 100644 --- a/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java +++ b/src/test/java/de/ozgcloud/nachrichten/postfach/osiv2/factory/JsonUtil.java @@ -2,10 +2,11 @@ package de.ozgcloud.nachrichten.postfach.osiv2.factory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.SneakyThrows; public class JsonUtil { - private static final ObjectMapper jsonMapper = new ObjectMapper(); + private static final ObjectMapper jsonMapper = new ObjectMapper().registerModule(new JavaTimeModule()); @SneakyThrows public static String toJson(Object object) {