1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-04-19 17:26:51 +00:00

refactor: move custom rest template init to config class

This commit is contained in:
Paul-Christian Volkmer 2024-04-24 08:22:13 +02:00
parent effffcfc1a
commit b809a2da02
2 changed files with 88 additions and 103 deletions

View File

@ -23,40 +23,17 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import dev.dnpm.etl.processor.config.GPasConfigProperties; import dev.dnpm.etl.processor.config.GPasConfigProperties;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
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.BasicHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class GpasPseudonymGenerator implements Generator { public class GpasPseudonymGenerator implements Generator {
private final static FhirContext r4Context = FhirContext.forR4(); private final static FhirContext r4Context = FhirContext.forR4();
@ -68,33 +45,13 @@ public class GpasPseudonymGenerator implements Generator {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private SSLContext customSslContext;
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate, RestTemplate restTemplate) { public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate, RestTemplate restTemplate) {
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate;
this.gPasUrl = gpasCfg.getUri(); this.gPasUrl = gpasCfg.getUri();
this.psnTargetDomain = gpasCfg.getTarget(); this.psnTargetDomain = gpasCfg.getTarget();
httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword()); httpHeader = getHttpHeaders(gpasCfg.getUsername(), gpasCfg.getPassword());
try {
if (StringUtils.isNotBlank(gpasCfg.getSslCaLocation())) {
customSslContext = getSslContext(gpasCfg.getSslCaLocation());
log.warn(String.format("%s has been initialized with SSL certificate %s. This is deprecated in favor of including Root CA.",
this.getClass().getName(), gpasCfg.getSslCaLocation()));
if (customSslContext == null) {
this.restTemplate = restTemplate;
} else {
this.restTemplate = getCustomRestTemplate();
}
} else {
this.restTemplate = restTemplate;
}
} catch (IOException | KeyManagementException | KeyStoreException | CertificateException |
NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
log.debug(String.format("%s has been initialized", this.getClass().getName())); log.debug(String.format("%s has been initialized", this.getClass().getName()));
} }
@ -104,7 +61,7 @@ public class GpasPseudonymGenerator implements Generator {
var gPasRequestBody = getGpasRequestBody(id); var gPasRequestBody = getGpasRequestBody(id);
var responseEntity = getGpasPseudonym(gPasRequestBody); var responseEntity = getGpasPseudonym(gPasRequestBody);
var gPasPseudonymResult = (Parameters) r4Context.newJsonParser() var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
.parseResource(responseEntity.getBody()); .parseResource(responseEntity.getBody());
return unwrapPseudonym(gPasPseudonymResult); return unwrapPseudonym(gPasPseudonymResult);
} }
@ -118,9 +75,9 @@ public class GpasPseudonymGenerator implements Generator {
} }
final var identifier = (Identifier) parameters.get().getPart().stream() final var identifier = (Identifier) parameters.get().getPart().stream()
.filter(a -> a.getName().equals("pseudonym")) .filter(a -> a.getName().equals("pseudonym"))
.findFirst() .findFirst()
.orElseGet(ParametersParameterComponent::new).getValue(); .orElseGet(ParametersParameterComponent::new).getValue();
// pseudonym // pseudonym
return sanitizeValue(identifier.getValue()); return sanitizeValue(identifier.getValue());
@ -149,8 +106,8 @@ public class GpasPseudonymGenerator implements Generator {
try { try {
responseEntity = retryTemplate.execute( responseEntity = retryTemplate.execute(
ctx -> restTemplate.exchange(gPasUrl, HttpMethod.POST, requestEntity, ctx -> restTemplate.exchange(gPasUrl, HttpMethod.POST, requestEntity,
String.class)); String.class));
if (responseEntity.getStatusCode().is2xxSuccessful()) { if (responseEntity.getStatusCode().is2xxSuccessful()) {
log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode()); log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
@ -162,16 +119,16 @@ public class GpasPseudonymGenerator implements Generator {
return responseEntity; return responseEntity;
} catch (Exception unexpected) { } catch (Exception unexpected) {
throw new PseudonymRequestFailed( throw new PseudonymRequestFailed(
"API request due unexpected error unsuccessful gPas unsuccessful.", unexpected); "API request due unexpected error unsuccessful gPas unsuccessful.", unexpected);
} }
} }
protected String getGpasRequestBody(String id) { protected String getGpasRequestBody(String id) {
var requestParameters = new Parameters(); var requestParameters = new Parameters();
requestParameters.addParameter().setName("target") requestParameters.addParameter().setName("target")
.setValue(new StringType().setValue(psnTargetDomain)); .setValue(new StringType().setValue(psnTargetDomain));
requestParameters.addParameter().setName("original") requestParameters.addParameter().setName("original")
.setValue(new StringType().setValue(id)); .setValue(new StringType().setValue(id));
final IParser iParser = r4Context.newJsonParser(); final IParser iParser = r4Context.newJsonParser();
return iParser.encodeResourceToString(requestParameters); return iParser.encodeResourceToString(requestParameters);
} }
@ -188,53 +145,4 @@ public class GpasPseudonymGenerator implements Generator {
headers.setBasicAuth(gPasUserName, gPasPassword); headers.setBasicAuth(gPasUserName, gPasPassword);
return headers; return headers;
} }
/**
* Read SSL root certificate and return SSLContext
*
* @param certificateLocation file location to root certificate (PEM)
* @return initialized SSLContext
* @throws IOException file cannot be read
* @throws CertificateException in case we have an invalid certificate of type X.509
* @throws KeyStoreException keystore cannot be initialized
* @throws NoSuchAlgorithmException missing trust manager algorithmus
* @throws KeyManagementException key management failed at init SSLContext
*/
@Nullable
protected SSLContext getSslContext(String certificateLocation)
throws IOException, CertificateException, KeyStoreException, KeyManagementException, NoSuchAlgorithmException {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(certificateLocation);
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new BufferedInputStream(fis));
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), ca);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext;
}
protected RestTemplate getCustomRestTemplate() {
final var sslsf = new SSLConnectionSocketFactory(customSslContext);
final Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", sslsf).register("http", new PlainConnectionSocketFactory()).build();
final BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(
socketFactoryRegistry);
final CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager).build();
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
return new RestTemplate(requestFactory);
}
} }

View File

@ -20,7 +20,10 @@
package dev.dnpm.etl.processor.config package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.monitoring.* import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
import dev.dnpm.etl.processor.pseudonym.Generator import dev.dnpm.etl.processor.pseudonym.Generator
import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
@ -29,12 +32,19 @@ import dev.dnpm.etl.processor.services.TokenRepository
import dev.dnpm.etl.processor.services.TokenService import dev.dnpm.etl.processor.services.TokenService
import dev.dnpm.etl.processor.services.Transformation import dev.dnpm.etl.processor.services.Transformation
import dev.dnpm.etl.processor.services.TransformationService import dev.dnpm.etl.processor.services.TransformationService
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.apache.hc.client5.http.impl.io.BasicHttpClientConnectionManager
import org.apache.hc.client5.http.socket.ConnectionSocketFactory
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory
import org.apache.hc.core5.http.config.RegistryBuilder
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
import org.springframework.retry.RetryCallback import org.springframework.retry.RetryCallback
import org.springframework.retry.RetryContext import org.springframework.retry.RetryContext
import org.springframework.retry.RetryListener import org.springframework.retry.RetryListener
@ -46,6 +56,13 @@ import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration import kotlin.time.toJavaDuration
@ -84,6 +101,66 @@ class AppConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@Bean @Bean
fun gpasPseudonymGeneratorOnDeprecatedProperty(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate): Generator { fun gpasPseudonymGeneratorOnDeprecatedProperty(configProperties: GPasConfigProperties, retryTemplate: RetryTemplate, restTemplate: RestTemplate): Generator {
fun getSslContext(certificateLocation: String): SSLContext? {
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
val fis = FileInputStream(certificateLocation)
val ca = CertificateFactory.getInstance("X.509")
.generateCertificate(BufferedInputStream(fis)) as X509Certificate
ks.load(null, null)
ks.setCertificateEntry(1.toString(), ca)
val tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm()
)
tmf.init(ks)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null)
return sslContext
}
fun getCustomRestTemplate(customSslContext: SSLContext): RestTemplate {
val sslsf = SSLConnectionSocketFactory(customSslContext)
val socketFactoryRegistry = RegistryBuilder.create<ConnectionSocketFactory>()
.register("https", sslsf).register("http", PlainConnectionSocketFactory()).build()
val connectionManager = BasicHttpClientConnectionManager(
socketFactoryRegistry
)
val httpClient = HttpClients.custom()
.setConnectionManager(connectionManager).build()
val requestFactory = HttpComponentsClientHttpRequestFactory(
httpClient
)
return RestTemplate(requestFactory)
}
try {
if (!configProperties.sslCaLocation.isNullOrBlank()) {
val customSslContext = getSslContext(configProperties.sslCaLocation)
logger.warn(
String.format(
"%s has been initialized with SSL certificate %s. This is deprecated in favor of including Root CA.",
this.javaClass.name, configProperties.sslCaLocation
)
)
if (customSslContext != null) {
return GpasPseudonymGenerator(
configProperties,
retryTemplate,
getCustomRestTemplate(customSslContext)
)
}
}
} catch (e: Exception) {
throw RuntimeException(e)
}
return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate) return GpasPseudonymGenerator(configProperties, retryTemplate, restTemplate)
} }