Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • anbindung_ed
  • main
  • release
  • 1.0.0
  • 1.1.0
  • 1.2.0
  • 1.3.0
  • 1.4.0
8 results

Target

Select target project
  • ozg-cloud/app/document-manager
1 result
Select Git revision
  • anbindung_ed
  • main
  • release
  • 1.0.0
  • 1.1.0
  • 1.2.0
  • 1.3.0
  • 1.4.0
8 results
Show changes
Commits on Source (9)
......@@ -25,6 +25,7 @@ package de.ozgcloud.document.bescheid.smartdocuments;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Optional;
......@@ -34,14 +35,15 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.client.RestClient;
import org.w3c.dom.Document;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
......@@ -69,7 +71,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import reactor.core.publisher.Mono;
@Log4j2
@Service
......@@ -87,7 +88,7 @@ class SmartDocumentsBescheidRemoteService implements BescheidRemoteService {
private static final MediaType JSON_MEDIA_TYPE_FOR_SD = MediaType.APPLICATION_JSON_UTF8;
@Qualifier("smartDocuments")
private final WebClient smartDocumentsWebClient;
private final RestClient smartDocumentsRestClient;
private final SmartDocumentsProperties properties;
......@@ -96,19 +97,24 @@ class SmartDocumentsBescheidRemoteService implements BescheidRemoteService {
var sdRequest = createRequest(request, vorgang);
LOG.debug(() -> buildLogRequest(sdRequest));
return smartDocumentsWebClient.post().accept(MediaType.APPLICATION_JSON)
var response = smartDocumentsRestClient.post().accept(MediaType.APPLICATION_JSON)
.contentType(JSON_MEDIA_TYPE_FOR_SD)
.bodyValue(sdRequest)
.body(sdRequest)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, this::handleClientError)
.bodyToMono(SmartDocumentsResponse.class)
.map(response -> buildBescheid(request, response))
.block();
.toEntity(SmartDocumentsResponse.class)
.getBody();
return buildBescheid(request, response);
}
Mono<Throwable> handleClientError(ClientResponse response) {
return response.body(BodyExtractors.toMono(String.class))
.map(content -> new TechnicalException("Client-Error: " + content));
void handleClientError(HttpRequest request, ClientHttpResponse response) {
try {
var responseBody = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8);
throw new TechnicalException("Client-Error: " + response.getStatusCode() + " " + responseBody);
} catch (IOException e) {
throw new TechnicalException("Couldn't read response body", e);
}
}
private String buildLogRequest(SmartDocumentsRequest request) {
......
......@@ -23,54 +23,100 @@
*/
package de.ozgcloud.document.bescheid.smartdocuments;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Objects;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.CredentialsProvider;
import org.apache.hc.client5.http.impl.auth.CredentialsProviderBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.http.HttpHost;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.transport.ProxyProvider;
import lombok.AllArgsConstructor;
@AllArgsConstructor
@Configuration
@ConditionalOnProperty("ozgcloud.bescheid.smart-documents.url")
class SmartDocumentsConfiguration {
@Autowired
private SmartDocumentsProperties properties;
private final SmartDocumentsProperties properties;
private final SslBundles sslBundles;
@Bean("smartDocuments")
WebClient smartDocumentsWebClient() {
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(buildHttpClient());
return WebClient.builder()
RestClient smartDocumentsRestClient() {
return RestClient.builder()
.requestFactory(new HttpComponentsClientHttpRequestFactory(buildHttpClient()))
.baseUrl(properties.getUrl())
.filter(ExchangeFilterFunctions.basicAuthentication(properties.getBasicAuth().getUsername(), properties.getBasicAuth().getPassword()))
.clientConnector(connector)
.defaultHeaders(this::addBasicAuthenticationIfConfigured)
.build();
}
private HttpClient buildHttpClient() {
CloseableHttpClient buildHttpClient() {
if (properties.getProxy() != null) {
return createProxyHttpClient();
} else {
return createNoProxyHttpClient();
return createHttpClientUsingProxy();
}
return createNoProxyHttpClient();
}
private HttpClientBuilder createDefaultHttpClientBuilder() {
return HttpClients.custom()
.setConnectionManager(createConnectionManagerWithClientCertificateIfConfigured());
}
CloseableHttpClient createNoProxyHttpClient() {
return createDefaultHttpClientBuilder().build();
}
CloseableHttpClient createHttpClientUsingProxy() {
return createDefaultHttpClientBuilder()
.setRoutePlanner(createProxyRoutePlanner())
.setDefaultCredentialsProvider(createProxyCredentialsProvider())
.build();
}
DefaultProxyRoutePlanner createProxyRoutePlanner() {
return new DefaultProxyRoutePlanner(new HttpHost(properties.getProxy().getHost(), properties.getProxy().getPort()));
}
private CredentialsProvider createProxyCredentialsProvider() {
var host = properties.getProxy().getHost();
var port = properties.getProxy().getPort();
var username = properties.getProxy().getUsername();
var password = properties.getProxy().getPassword().toCharArray();
return CredentialsProviderBuilder.create().add(new AuthScope(host, port), username, password).build();
}
private HttpClient createNoProxyHttpClient() {
return HttpClient.create();
void addBasicAuthenticationIfConfigured(HttpHeaders headers) {
if (Objects.nonNull(properties.getBasicAuth())) {
headers.setBasicAuth(properties.getBasicAuth().getUsername(), properties.getBasicAuth().getPassword());
}
}
private HttpClient createProxyHttpClient() {
return HttpClient.create()
.proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP)
.host(properties.getProxy().getHost())
.port(properties.getProxy().getPort())
.username(properties.getProxy().getUsername())
.password(username -> properties.getProxy().getPassword()));
HttpClientConnectionManager createConnectionManagerWithClientCertificateIfConfigured() {
var builder = PoolingHttpClientConnectionManagerBuilder.create();
try {
var sslBundleName = properties.getSslBundleName();
if (Objects.nonNull(sslBundleName)) {
builder.setTlsSocketStrategy(new DefaultClientTlsStrategy(sslBundles.getBundle(sslBundleName).createSslContext()));
}
return builder.build();
} catch (NoSuchSslBundleException e) {
return builder.build();
}
}
}
......@@ -52,10 +52,14 @@ public class SmartDocumentsProperties {
/**
* Credential for basic auth to the Smart Documents Server
*/
@NotNull
@Valid
private UsernamePassword basicAuth;
/**
* Name of SSL bundle need for client certificate authentication.
*/
private String sslBundleName;
/**
* Smart Documents Template Group
*/
......
package de.ozgcloud.document.bescheid.smartdocuments;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHost;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Spy;
import org.springframework.boot.ssl.NoSuchSslBundleException;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.http.HttpHeaders;
import de.ozgcloud.document.bescheid.smartdocuments.SmartDocumentsProperties.ProxyConfiguration;
class SmartDocumentsConfigurationTest {
@Spy
@InjectMocks
private SmartDocumentsConfiguration smartDocumentsConfiguration;
@Mock
private SmartDocumentsProperties properties;
@Mock
private SslBundles sslBundles;
@Nested
class TestBuildHttpClient {
private final CloseableHttpClient proxyHttpClient = HttpClients.createDefault();
private final CloseableHttpClient nonProxyHttpClient = HttpClients.createDefault();
@Test
void shouldReturnProxyHttpClient() {
doReturn(proxyHttpClient).when(smartDocumentsConfiguration).createHttpClientUsingProxy();
when(properties.getProxy()).thenReturn(new ProxyConfiguration());
var httpClient = smartDocumentsConfiguration.buildHttpClient();
assertThat(httpClient).isSameAs(proxyHttpClient);
}
@Test
void shouldReturnNonProxyHttpClient() {
doReturn(nonProxyHttpClient).when(smartDocumentsConfiguration).createNoProxyHttpClient();
when(properties.getProxy()).thenReturn(null);
var httpClient = smartDocumentsConfiguration.buildHttpClient();
assertThat(httpClient).isSameAs(nonProxyHttpClient);
}
}
@Nested
class TestCreateProxyRoutePlanner {
private final String host = "test-proxy.local";
private final int port = 8080;
private final ProxyConfiguration proxyConfiguration = new ProxyConfiguration();
@BeforeEach
void setUp() {
proxyConfiguration.setHost(host);
proxyConfiguration.setPort(port);
when(properties.getProxy()).thenReturn(proxyConfiguration);
}
@Test
void shouldCreateWithProxySettings() throws HttpException {
var routePlanner = smartDocumentsConfiguration.createProxyRoutePlanner();
var proxyHost = routePlanner.determineRoute(new HttpHost("http://any"), new HttpClientContext()).getProxyHost();
assertThat(proxyHost).extracting(
HttpHost::getHostName,
HttpHost::getPort
).containsExactly(host, port);
}
}
@Nested
class TestCreateProxyCredentialsProvider {
private final String user = "max";
private final String password = "max2";
private final String host = "test-proxy.local";
private final int port = 8080;
private final ProxyConfiguration proxyConfiguration = new ProxyConfiguration();
@BeforeEach
void setUp() {
proxyConfiguration.setHost(host);
proxyConfiguration.setPort(port);
proxyConfiguration.setUsername(user);
proxyConfiguration.setPassword(password);
when(properties.getProxy()).thenReturn(proxyConfiguration);
}
@Test
void shouldCreateWithUserAndPassword() {
var credentialsProvider = smartDocumentsConfiguration.createProxyCredentialsProvider();
var credentialsForProxy = (UsernamePasswordCredentials) credentialsProvider.getCredentials(new AuthScope(host, port), null);
assertThat(credentialsForProxy).extracting(
UsernamePasswordCredentials::getUserName,
UsernamePasswordCredentials::getUserPassword)
.containsExactly(user, password.toCharArray());
}
}
@Nested
class TestAddBasicAuthenticationIfConfigured {
@Test
void shouldNotAddBasicAuthentication() {
when(properties.getBasicAuth()).thenReturn(null);
var headers = new HttpHeaders();
smartDocumentsConfiguration.addBasicAuthenticationIfConfigured(headers);
assertThat(headers.get(HttpHeaders.AUTHORIZATION)).isNull();
}
@Test
void shouldAddBasicAuthentication() {
when(properties.getBasicAuth()).thenReturn(UsernamePasswordTestFactory.create());
var headers = new HttpHeaders();
smartDocumentsConfiguration.addBasicAuthenticationIfConfigured(headers);
assertThat(headers.get(HttpHeaders.AUTHORIZATION))
.isNotNull()
.containsExactly(UsernamePasswordTestFactory.getAsBasicAuthenticationHeader());
}
}
@Nested
class TestCreateConnectionManagerWithClientCertificateIfConfigured {
private MockedStatic<PoolingHttpClientConnectionManagerBuilder> staticBuilder;
@Mock
private PoolingHttpClientConnectionManagerBuilder builder;
@Mock
private SslBundle sslBundle;
@Mock
private SSLContext sslContext;
private final String bundleName = "smartdocuments_bundle";
private final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
@BeforeEach
void setUp() {
staticBuilder = mockStatic(PoolingHttpClientConnectionManagerBuilder.class);
staticBuilder.when(PoolingHttpClientConnectionManagerBuilder::create).thenReturn(builder);
when(builder.build()).thenReturn(connectionManager);
}
@AfterEach
void tearDown() {
staticBuilder.close();
}
@Test
void shouldCreateWithBuilder() {
smartDocumentsConfiguration.createConnectionManagerWithClientCertificateIfConfigured();
staticBuilder.verify(() -> PoolingHttpClientConnectionManagerBuilder.create());
}
@Nested
class OnMissingClientCertificateConfiguration {
@Test
void shouldCreateWithoutTlsStrategyOnMissingBundleName() {
when(properties.getSslBundleName()).thenReturn(null);
var created = smartDocumentsConfiguration.createConnectionManagerWithClientCertificateIfConfigured();
verify(builder, never()).setTlsSocketStrategy(any());
assertThat(created).isSameAs(connectionManager);
}
@Test
void shouldCreateWithoutTlsStrategyOnMissingBundleConfiguration() {
when(properties.getSslBundleName()).thenReturn(bundleName);
when(sslBundles.getBundle(anyString())).thenThrow(new NoSuchSslBundleException("some_bundle", "error message"));
var created = smartDocumentsConfiguration.createConnectionManagerWithClientCertificateIfConfigured();
verify(builder, never()).setTlsSocketStrategy(any());
assertThat(created).isSameAs(connectionManager);
}
}
@Nested
class OnExistingClientCertificateConfiguration {
@BeforeEach
void setUp() {
when(properties.getSslBundleName()).thenReturn(bundleName);
when(sslBundles.getBundle(any())).thenReturn(sslBundle);
when(sslBundle.createSslContext()).thenReturn(sslContext);
}
@Test
void shouldGetSslBundle() {
smartDocumentsConfiguration.createConnectionManagerWithClientCertificateIfConfigured();
verify(sslBundles).getBundle(bundleName);
}
@Test
void shouldCreateWithTlsStrategy() {
var created = smartDocumentsConfiguration.createConnectionManagerWithClientCertificateIfConfigured();
verify(builder).setTlsSocketStrategy(any());
assertThat(created).isSameAs(connectionManager);
}
}
}
}
\ No newline at end of file
package de.ozgcloud.document.bescheid.smartdocuments;
import org.bouncycastle.util.encoders.Base64;
import de.ozgcloud.document.bescheid.smartdocuments.SmartDocumentsProperties.UsernamePassword;
public class UsernamePasswordTestFactory {
public static final String USERNAME = "max";
public static final String PASSWORD = "max123";
public static UsernamePassword create() {
var usernamePassword = new UsernamePassword();
usernamePassword.setUsername(USERNAME);
usernamePassword.setPassword(PASSWORD);
return usernamePassword;
}
public static String getAsBasicAuthenticationHeader() {
var base64EncodedAuth = new String(Base64.encode(
(UsernamePasswordTestFactory.USERNAME + ":" + UsernamePasswordTestFactory.PASSWORD).getBytes()));
return "Basic " + base64EncodedAuth;
}
}