From 9c93174f91ce9912f99bc3c37b9bb43ad6a2e323 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 27 May 2024 09:54:34 +0200 Subject: [PATCH 01/20] start new development cylve and fix mvn action for publish --- .github/workflows/maven-publish.yml | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 639a40c..c46dcca 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -4,6 +4,8 @@ on: pull_request: types: [closed] branches: [develop] + release: + types: [ published ] jobs: publish: diff --git a/pom.xml b/pom.xml index 9311020..70342b2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.medizininformatik-initiative mii-processes-common - 1.0.1.0 + 1.0.2.0-SNAPSHOT UTF-8 From 82544f24799b391debade6969ddc235c23ef0174 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Mon, 27 May 2024 09:57:42 +0200 Subject: [PATCH 02/20] remove not needed whitespace --- .github/workflows/maven-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index c46dcca..a699954 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -5,7 +5,7 @@ on: types: [closed] branches: [develop] release: - types: [ published ] + types: [published] jobs: publish: From b1d9fdbcc59b4c28e60bf26ff2f2592dc6d5f910 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 18 Jun 2024 09:56:50 +0200 Subject: [PATCH 03/20] publish artefact on release --- .github/workflows/maven-publish.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index a699954..0b3e962 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -10,8 +10,9 @@ on: jobs: publish: - # Only run if pull requests are merged, omit running if pull requests are closed without merging - if: github.event.pull_request.merged + # Only run if releases are published or pull requests are merged, + # omit running if pull requests are closed without merging + if: github.event.pull_request.merged || github.event.action == 'published' runs-on: ubuntu-latest From 2f0e9a623f1c03a08a2b2a7dbe86345789c7293d Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 18 Jul 2024 08:25:45 +0200 Subject: [PATCH 04/20] client credentials workflow implementation --- .../common/fhir/client/FhirClientFactory.java | 14 +- .../common/fhir/client/FhirClientImpl.java | 22 +- .../client/interceptor/OAuth2Interceptor.java | 40 ++++ .../common/fhir/client/token/AccessToken.java | 34 +++ .../fhir/client/token/OAuth2TokenClient.java | 200 ++++++++++++++++++ .../client/token/OAuth2TokenProvider.java | 40 ++++ .../common/fhir/client/token/TokenClient.java | 8 + .../fhir/client/token/TokenProvider.java | 8 + 8 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/interceptor/OAuth2Interceptor.java create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java create mode 100644 src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java index ef3ec80..a8b390e 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java @@ -17,6 +17,7 @@ import ca.uhn.fhir.context.FhirContext; import de.medizininformatik_initiative.processes.common.fhir.client.logging.DataLogger; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenProvider; import de.rwh.utils.crypto.CertificateHelper; import de.rwh.utils.crypto.io.CertificateReader; import de.rwh.utils.crypto.io.PemIo; @@ -38,6 +39,7 @@ public class FhirClientFactory private final String fhirServerBasicAuthUsername; private final String fhirServerBasicAuthPassword; private final String fhirServerBearerToken; + private final TokenProvider fhirServerOAuth2TokenProvider; private final String proxyUrl; private final String proxyUsername; @@ -54,8 +56,8 @@ public class FhirClientFactory public FhirClientFactory(Path trustStorePath, Path certificatePath, Path privateKeyPath, char[] privateKeyPassword, int connectTimeout, int socketTimeout, int connectionRequestTimeout, String fhirServerBase, String fhirServerBasicAuthUsername, String fhirServerBasicAuthPassword, String fhirServerBearerToken, - String proxyUrl, String proxyUsername, String proxyPassword, boolean hapiClientVerbose, - FhirContext fhirContext, String localIdentifierValue, DataLogger dataLogger) + TokenProvider fhirServerOAuth2TokenProvider, String proxyUrl, String proxyUsername, String proxyPassword, + boolean hapiClientVerbose, FhirContext fhirContext, String localIdentifierValue, DataLogger dataLogger) { this.trustStorePath = trustStorePath; this.certificatePath = certificatePath; @@ -70,6 +72,7 @@ public FhirClientFactory(Path trustStorePath, Path certificatePath, Path private this.fhirServerBasicAuthUsername = fhirServerBasicAuthUsername; this.fhirServerBasicAuthPassword = fhirServerBasicAuthPassword; this.fhirServerBearerToken = fhirServerBearerToken; + this.fhirServerOAuth2TokenProvider = fhirServerOAuth2TokenProvider; this.proxyUrl = proxyUrl; this.proxyUsername = proxyUsername; @@ -85,11 +88,12 @@ public FhirClientFactory(Path trustStorePath, Path certificatePath, Path private public void testConnection() { + // TODO: log token provider configuration try { logger.info( "Testing connection to FHIR server with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {}," - + " basicAuthUsername {}, basicAuthPassword {}, bearerToken {}, serverBase: {}, proxyUrl {}, proxyUsername {}, proxyPassword {}}", + + " basicAuthUsername: {}, basicAuthPassword: {}, bearerToken: {}, serverBase: {}, proxyUrl: {}, proxyUsername: {}, proxyPassword: {}}", trustStorePath, certificatePath, privateKeyPath, privateKeyPassword != null ? "***" : "null", fhirServerBasicAuthUsername, fhirServerBasicAuthPassword != null ? "***" : "null", fhirServerBearerToken != null ? "***" : "null", fhirServerBase, proxyUrl, proxyUsername, @@ -137,8 +141,8 @@ protected FhirClient createFhirClientImpl() return new FhirClientImpl(trustStore, keyStore, keyStorePassword, connectTimeout, socketTimeout, connectionRequestTimeout, fhirServerBasicAuthUsername, fhirServerBasicAuthPassword, - fhirServerBearerToken, fhirServerBase, proxyUrl, proxyUsername, proxyPassword, hapiClientVerbose, - fhirContext, localIdentifierValue, dataLogger); + fhirServerBearerToken, fhirServerOAuth2TokenProvider, fhirServerBase, proxyUrl, proxyUsername, + proxyPassword, hapiClientVerbose, fhirContext, localIdentifierValue, dataLogger); } private KeyStore readTrustStore(Path trustPath) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java index 90bb0ab..ddabaaa 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java @@ -25,8 +25,10 @@ import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.gclient.IReadTyped; +import de.medizininformatik_initiative.processes.common.fhir.client.interceptor.OAuth2Interceptor; import de.medizininformatik_initiative.processes.common.fhir.client.logging.DataLogger; import de.medizininformatik_initiative.processes.common.fhir.client.logging.HapiClientLogger; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenProvider; public class FhirClientImpl implements FhirClient { @@ -39,6 +41,7 @@ public class FhirClientImpl implements FhirClient private final String fhirServerBasicAuthUsername; private final String fhirServerBasicAuthPassword; private final String fhirServerBearerToken; + private final TokenProvider fhirServerOAuth2TokenProvider; private final boolean hapiClientVerbose; @@ -50,9 +53,10 @@ public class FhirClientImpl implements FhirClient public FhirClientImpl(KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, int connectTimeout, int socketTimeout, int connectionRequestTimeout, String fhirServerBasicAuthUsername, - String fhirServerBasicAuthPassword, String fhirServerBearerToken, String fhirServerBase, String proxyUrl, - String proxyUsername, String proxyPassword, boolean hapiClientVerbose, FhirContext fhirContext, - String localIdentifierValue, DataLogger dataLogger) + String fhirServerBasicAuthPassword, String fhirServerBearerToken, + TokenProvider fhirServerOAuth2TokenProvider, String fhirServerBase, String proxyUrl, String proxyUsername, + String proxyPassword, boolean hapiClientVerbose, FhirContext fhirContext, String localIdentifierValue, + DataLogger dataLogger) { clientFactory = createClientFactory(trustStore, keyStore, keyStorePassword, connectTimeout, socketTimeout, connectionRequestTimeout); @@ -62,6 +66,7 @@ public FhirClientImpl(KeyStore trustStore, KeyStore keyStore, char[] keyStorePas this.fhirServerBasicAuthUsername = fhirServerBasicAuthUsername; this.fhirServerBasicAuthPassword = fhirServerBasicAuthPassword; this.fhirServerBearerToken = fhirServerBearerToken; + this.fhirServerOAuth2TokenProvider = fhirServerOAuth2TokenProvider; configureProxy(clientFactory, proxyUrl, proxyUsername, proxyPassword); @@ -118,12 +123,18 @@ private void configuredWithBasicAuth(IGenericClient client) new BasicAuthInterceptor(fhirServerBasicAuthUsername, fhirServerBasicAuthPassword)); } - private void configureBearerTokenAuthInterceptor(IGenericClient client) + private void configuredWithBearerTokenAuth(IGenericClient client) { if (fhirServerBearerToken != null) client.registerInterceptor(new BearerTokenAuthInterceptor(fhirServerBearerToken)); } + private void configuredWithOAuth(IGenericClient client) + { + if (fhirServerOAuth2TokenProvider != null && fhirServerOAuth2TokenProvider.isConfigured()) + client.registerInterceptor(new OAuth2Interceptor(fhirServerOAuth2TokenProvider)); + } + private void configureLoggingInterceptor(IGenericClient client) { if (hapiClientVerbose) @@ -158,7 +169,8 @@ public IGenericClient getGenericFhirClient() IGenericClient client = clientFactory.newGenericClient(fhirServerBase); configuredWithBasicAuth(client); - configureBearerTokenAuthInterceptor(client); + configuredWithBearerTokenAuth(client); + configuredWithOAuth(client); configureLoggingInterceptor(client); return client; diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/interceptor/OAuth2Interceptor.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/interceptor/OAuth2Interceptor.java new file mode 100644 index 0000000..c198829 --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/interceptor/OAuth2Interceptor.java @@ -0,0 +1,40 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.interceptor; + +import java.util.Objects; + +import org.springframework.beans.factory.InitializingBean; + +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenProvider; + +public class OAuth2Interceptor implements IClientInterceptor, InitializingBean +{ + private final TokenProvider tokenProvider; + + public OAuth2Interceptor(TokenProvider tokenProvider) + { + this.tokenProvider = tokenProvider; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(tokenProvider, "tokenProvider"); + } + + @Override + public void interceptRequest(IHttpRequest theRequest) + { + theRequest.addHeader(Constants.HEADER_AUTHORIZATION, + Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + tokenProvider.getToken()); + } + + @Override + public void interceptResponse(IHttpResponse theResponse) + { + // do not intercept response + } +} diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java new file mode 100644 index 0000000..0523ece --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java @@ -0,0 +1,34 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.token; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AccessToken +{ + private static final int BUFFER = 10; + + private final String token; + + private final LocalDateTime expiresAt; + + @JsonCreator + public AccessToken(@JsonProperty("access_token") String token, @JsonProperty("expires_in") int expiresIn) + { + this.token = token; + this.expiresAt = LocalDateTime.now().plusSeconds(expiresIn); + } + + public String get() + { + return token; + } + + public boolean isExpired() + { + return LocalDateTime.now().plusSeconds(BUFFER).isAfter(expiresAt); + } +} diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java new file mode 100644 index 0000000..ccd0a8d --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -0,0 +1,200 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.token; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Path; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.time.Duration; +import java.util.Base64; +import java.util.Objects; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.rwh.utils.crypto.io.CertificateReader; + +public class OAuth2TokenClient implements TokenClient, InitializingBean +{ + private static final Logger logger = LoggerFactory.getLogger(OAuth2TokenClient.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final String issuerUrl; + private final String clientId; + private final String clientSecret; + + private final int connectTimeout; + private final int socketTimeout; + + private final Path trustStorePath; + + private final String proxyUrl; + private final String proxyUsername; + private final String proxyPassword; + + public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, int connectTimeout, + int socketTimeout, Path trustStorePath, String proxyUrl, String proxyUsername, String proxyPassword) + { + this.issuerUrl = issuerUrl; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.connectTimeout = connectTimeout; + this.socketTimeout = socketTimeout; + this.trustStorePath = trustStorePath; + this.proxyUrl = proxyUrl; + this.proxyUsername = proxyUsername; + this.proxyPassword = proxyPassword; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(issuerUrl, "issuerUrl"); + Objects.requireNonNull(clientId, "clientId"); + Objects.requireNonNull(clientSecret, "clientSecret"); + + if (connectTimeout < 0) + throw new IllegalArgumentException("connectTimeout < 0"); + + if (socketTimeout < 0) + throw new IllegalArgumentException("socketTimeout < 0"); + } + + @Override + public boolean isConfigured() + { + return issuerUrl != null && clientId != null && clientSecret != null; + } + + @Override + public AccessToken requestToken() + { + try + { + HttpClient client = createClient(); + HttpRequest request = createAccessTokenRequest(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + return OBJECT_MAPPER.readValue(response.body(), AccessToken.class); + } + catch (IOException | InterruptedException exception) + { + throw new RuntimeException(exception); + } + } + + private HttpClient createClient() + { + HttpClient.Builder builder = HttpClient.newBuilder(); + builder.connectTimeout(Duration.ofMillis(connectTimeout)); + + configureProxy(builder); + configureTruststore(builder); + + return builder.build(); + } + + private void configureTruststore(HttpClient.Builder builder) + { + if (trustStorePath != null) + { + logger.debug("Reading trust-store from {}", trustStorePath.toString()); + KeyStore trustStore = readTrustStore(trustStorePath); + SSLContext sslContext = createSslContext(trustStore); + builder.sslContext(sslContext); + } + } + + private SSLContext createSslContext(KeyStore trustStore) + { + try + { + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + SSLContext sslContext = SSLContext.getDefault(); + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + + return sslContext; + } + catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException exception) + { + throw new RuntimeException(exception); + } + } + + private KeyStore readTrustStore(Path trustPath) + { + try + { + return CertificateReader.allFromCer(trustPath); + } + catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException exception) + { + throw new RuntimeException(exception); + } + } + + private void configureProxy(HttpClient.Builder builder) + { + if (proxyUrl != null) + { + URI uri = URI.create(proxyUrl); + builder.proxy(ProxySelector.of(new InetSocketAddress(uri.getHost(), uri.getPort()))); + } + } + + private HttpRequest createAccessTokenRequest() + { + HttpRequest.Builder builder = HttpRequest.newBuilder(); + builder.uri(URI.create(issuerUrl)); + builder.timeout(Duration.ofMillis(socketTimeout)); + + configureAuthentication(builder); + configureProxyAuthentication(builder); + + builder.header("Content-Type", "application/x-www-form-urlencoded"); + builder.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials")); + + return builder.build(); + } + + private void configureAuthentication(HttpRequest.Builder builder) + { + // Keycloak not sending WWW-Authenticate header for response code 401 + String credentials = getCredentials(clientId, clientSecret); + builder.header("Authorization", "Basic " + credentials); + } + + private void configureProxyAuthentication(HttpRequest.Builder builder) + { + // Proxy authentication using similar workaround as basic authentication + if (proxyUrl != null && proxyUsername != null && proxyPassword != null) + { + String proxyCredentials = getCredentials(proxyUsername, proxyPassword); + builder.setHeader("Proxy-Authorization", "Basic " + proxyCredentials); + } + } + + private String getCredentials(String username, String password) + { + String credentials = username + ":" + password; + return Base64.getEncoder().encodeToString(credentials.getBytes()); + } +} diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java new file mode 100644 index 0000000..4f6a767 --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java @@ -0,0 +1,40 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.token; + +import java.util.Objects; + +import org.springframework.beans.factory.InitializingBean; + +public class OAuth2TokenProvider implements TokenProvider, InitializingBean +{ + private final TokenClient tokenClient; + + private AccessToken token; + + public OAuth2TokenProvider(TokenClient tokenClient) + { + this.tokenClient = tokenClient; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(tokenClient, "tokenClient"); + } + + @Override + public boolean isConfigured() + { + return tokenClient.isConfigured(); + } + + @Override + public String getToken() + { + if (token == null || token.isExpired()) + { + token = tokenClient.requestToken(); + } + + return token.get(); + } +} diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java new file mode 100644 index 0000000..7b7ab0e --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java @@ -0,0 +1,8 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.token; + +public interface TokenClient +{ + boolean isConfigured(); + + AccessToken requestToken(); +} diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java new file mode 100644 index 0000000..c936920 --- /dev/null +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java @@ -0,0 +1,8 @@ +package de.medizininformatik_initiative.processes.common.fhir.client.token; + +public interface TokenProvider +{ + boolean isConfigured(); + + String getToken(); +} From 0d01765923ddd3f73a27577b8da25a28a9c9ed2a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 18 Jul 2024 15:22:22 +0200 Subject: [PATCH 05/20] log configuration of oauth provider, throw exception if token could not be retrieved --- .../common/fhir/client/FhirClientFactory.java | 8 ++++---- .../fhir/client/token/OAuth2TokenClient.java | 19 +++++++++++++------ .../client/token/OAuth2TokenProvider.java | 6 ++++++ .../common/fhir/client/token/TokenClient.java | 2 ++ .../fhir/client/token/TokenProvider.java | 2 ++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java index a8b390e..30c475b 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientFactory.java @@ -88,16 +88,16 @@ public FhirClientFactory(Path trustStorePath, Path certificatePath, Path private public void testConnection() { - // TODO: log token provider configuration try { logger.info( "Testing connection to FHIR server with {trustStorePath: {}, certificatePath: {}, privateKeyPath: {}, privateKeyPassword: {}," - + " basicAuthUsername: {}, basicAuthPassword: {}, bearerToken: {}, serverBase: {}, proxyUrl: {}, proxyUsername: {}, proxyPassword: {}}", + + " basicAuthUsername: {}, basicAuthPassword: {}, bearerToken: {}, oauth2Provider: {}, serverBase: {}, proxyUrl: {}, proxyUsername: {}, proxyPassword: {}}", trustStorePath, certificatePath, privateKeyPath, privateKeyPassword != null ? "***" : "null", fhirServerBasicAuthUsername, fhirServerBasicAuthPassword != null ? "***" : "null", - fhirServerBearerToken != null ? "***" : "null", fhirServerBase, proxyUrl, proxyUsername, - proxyPassword != null ? "***" : "null"); + fhirServerBearerToken != null ? "***" : "null", + fhirServerOAuth2TokenProvider != null ? fhirServerOAuth2TokenProvider.getInfo() : "null", + fhirServerBase, proxyUrl, proxyUsername, proxyPassword != null ? "***" : "null"); getFhirClient().testConnection(); } diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index ccd0a8d..26b1bfe 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -15,7 +15,6 @@ import java.security.cert.CertificateException; import java.time.Duration; import java.util.Base64; -import java.util.Objects; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; @@ -64,10 +63,6 @@ public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, @Override public void afterPropertiesSet() { - Objects.requireNonNull(issuerUrl, "issuerUrl"); - Objects.requireNonNull(clientId, "clientId"); - Objects.requireNonNull(clientSecret, "clientSecret"); - if (connectTimeout < 0) throw new IllegalArgumentException("connectTimeout < 0"); @@ -81,6 +76,15 @@ public boolean isConfigured() return issuerUrl != null && clientId != null && clientSecret != null; } + @Override + public String getInfo() + { + return "[issuerUrl: " + issuerUrl + ", clientId: " + clientId + ", clientSecret: " + + (clientSecret != null ? "***" : "null") + ", trustStorePath: " + connectTimeout + ", proxyUrl: " + + proxyUrl + ", proxyUsername: " + proxyUsername + ", proxyPassword: " + + (proxyPassword != null ? "***" : "null") + "]"; + } + @Override public AccessToken requestToken() { @@ -90,7 +94,10 @@ public AccessToken requestToken() HttpRequest request = createAccessTokenRequest(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - return OBJECT_MAPPER.readValue(response.body(), AccessToken.class); + if (response.statusCode() < 400) + return OBJECT_MAPPER.readValue(response.body(), AccessToken.class); + else + throw new RuntimeException("Could not retrieve access token: " + response.statusCode()); } catch (IOException | InterruptedException exception) { diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java index 4f6a767..99a0100 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenProvider.java @@ -27,6 +27,12 @@ public boolean isConfigured() return tokenClient.isConfigured(); } + @Override + public String getInfo() + { + return tokenClient.getInfo(); + } + @Override public String getToken() { diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java index 7b7ab0e..08db59b 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenClient.java @@ -4,5 +4,7 @@ public interface TokenClient { boolean isConfigured(); + String getInfo(); + AccessToken requestToken(); } diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java index c936920..c1486b5 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/TokenProvider.java @@ -4,5 +4,7 @@ public interface TokenProvider { boolean isConfigured(); + String getInfo(); + String getToken(); } From 700ff630d88f03adb2d6a9b5e44557846a740b9b Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 18 Jul 2024 17:33:17 +0200 Subject: [PATCH 06/20] use TLS context, fox typo --- .../common/fhir/client/token/OAuth2TokenClient.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 26b1bfe..4a93aa7 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -80,8 +80,8 @@ public boolean isConfigured() public String getInfo() { return "[issuerUrl: " + issuerUrl + ", clientId: " + clientId + ", clientSecret: " - + (clientSecret != null ? "***" : "null") + ", trustStorePath: " + connectTimeout + ", proxyUrl: " - + proxyUrl + ", proxyUsername: " + proxyUsername + ", proxyPassword: " + + (clientSecret != null ? "***" : "null") + ", trustStorePath: " + trustStorePath.toString() + + ", proxyUrl: " + proxyUrl + ", proxyUsername: " + proxyUsername + ", proxyPassword: " + (proxyPassword != null ? "***" : "null") + "]"; } @@ -111,12 +111,12 @@ private HttpClient createClient() builder.connectTimeout(Duration.ofMillis(connectTimeout)); configureProxy(builder); - configureTruststore(builder); + configureTrustStore(builder); return builder.build(); } - private void configureTruststore(HttpClient.Builder builder) + private void configureTrustStore(HttpClient.Builder builder) { if (trustStorePath != null) { @@ -135,7 +135,7 @@ private SSLContext createSslContext(KeyStore trustStore) .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); - SSLContext sslContext = SSLContext.getDefault(); + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext; From 1f8e0e0105fbd5566af5c8f7595bc9522c425e03 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Fri, 19 Jul 2024 09:19:35 +0200 Subject: [PATCH 07/20] replace strings with constants --- .../fhir/client/token/OAuth2TokenClient.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 4a93aa7..cad86b4 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -1,6 +1,7 @@ package de.medizininformatik_initiative.processes.common.fhir.client.token; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.URI; @@ -31,7 +32,14 @@ public class OAuth2TokenClient implements TokenClient, InitializingBean { private static final Logger logger = LoggerFactory.getLogger(OAuth2TokenClient.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String TLS = "TLS"; + + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + private static final String MIME_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + private static final String HEADER_AUTHORIZATION_PROXY = "Proxy-Authorization"; + private static final String PREFIX_BASIC_AUTH = "Basic "; + private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "grant_type=client_credentials"; private final String issuerUrl; private final String clientId; @@ -46,8 +54,18 @@ public class OAuth2TokenClient implements TokenClient, InitializingBean private final String proxyUsername; private final String proxyPassword; + private final ObjectMapper objectMapper; + public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, int connectTimeout, int socketTimeout, Path trustStorePath, String proxyUrl, String proxyUsername, String proxyPassword) + { + this(issuerUrl, clientId, clientSecret, connectTimeout, socketTimeout, trustStorePath, proxyUrl, proxyUsername, + proxyPassword, new ObjectMapper()); + } + + public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, int connectTimeout, + int socketTimeout, Path trustStorePath, String proxyUrl, String proxyUsername, String proxyPassword, + ObjectMapper objectMapper) { this.issuerUrl = issuerUrl; this.clientId = clientId; @@ -58,6 +76,7 @@ public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, this.proxyUrl = proxyUrl; this.proxyUsername = proxyUsername; this.proxyPassword = proxyPassword; + this.objectMapper = objectMapper; } @Override @@ -94,10 +113,10 @@ public AccessToken requestToken() HttpRequest request = createAccessTokenRequest(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() < 400) - return OBJECT_MAPPER.readValue(response.body(), AccessToken.class); + if (response.statusCode() == HttpURLConnection.HTTP_OK) + return objectMapper.readValue(response.body(), AccessToken.class); else - throw new RuntimeException("Could not retrieve access token: " + response.statusCode()); + throw new RuntimeException("Could not retrieve access token, status code: " + response.statusCode()); } catch (IOException | InterruptedException exception) { @@ -135,7 +154,7 @@ private SSLContext createSslContext(KeyStore trustStore) .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); - SSLContext sslContext = SSLContext.getInstance("TLS"); + SSLContext sslContext = SSLContext.getInstance(TLS); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext; @@ -176,8 +195,8 @@ private HttpRequest createAccessTokenRequest() configureAuthentication(builder); configureProxyAuthentication(builder); - builder.header("Content-Type", "application/x-www-form-urlencoded"); - builder.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials")); + builder.header(HEADER_CONTENT_TYPE, MIME_TYPE_FORM_URLENCODED); + builder.POST(HttpRequest.BodyPublishers.ofString(GRANT_TYPE_CLIENT_CREDENTIALS)); return builder.build(); } @@ -186,7 +205,7 @@ private void configureAuthentication(HttpRequest.Builder builder) { // Keycloak not sending WWW-Authenticate header for response code 401 String credentials = getCredentials(clientId, clientSecret); - builder.header("Authorization", "Basic " + credentials); + builder.header(HEADER_AUTHORIZATION, PREFIX_BASIC_AUTH + credentials); } private void configureProxyAuthentication(HttpRequest.Builder builder) @@ -195,7 +214,7 @@ private void configureProxyAuthentication(HttpRequest.Builder builder) if (proxyUrl != null && proxyUsername != null && proxyPassword != null) { String proxyCredentials = getCredentials(proxyUsername, proxyPassword); - builder.setHeader("Proxy-Authorization", "Basic " + proxyCredentials); + builder.setHeader(HEADER_AUTHORIZATION_PROXY, PREFIX_BASIC_AUTH + proxyCredentials); } } From ac47c31a5df030466e63f52ffd7315740f8662f8 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Fri, 19 Jul 2024 16:11:24 +0200 Subject: [PATCH 08/20] enable proxy auth for java.net.HttpClient --- .../common/fhir/client/token/OAuth2TokenClient.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index cad86b4..d39c9d3 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -56,6 +56,11 @@ public class OAuth2TokenClient implements TokenClient, InitializingBean private final ObjectMapper objectMapper; + static + { + System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); + } + public OAuth2TokenClient(String issuerUrl, String clientId, String clientSecret, int connectTimeout, int socketTimeout, Path trustStorePath, String proxyUrl, String proxyUsername, String proxyPassword) { From 32c69d77383c39f0cb994ce26b32bba87b02b669 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 23 Jul 2024 15:23:00 +0200 Subject: [PATCH 09/20] remove indirection via constants, more precise comments for preemptive authoization headers --- .../fhir/client/token/OAuth2TokenClient.java | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index d39c9d3..0324e9b 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -32,15 +32,6 @@ public class OAuth2TokenClient implements TokenClient, InitializingBean { private static final Logger logger = LoggerFactory.getLogger(OAuth2TokenClient.class); - private static final String TLS = "TLS"; - - private static final String HEADER_CONTENT_TYPE = "Content-Type"; - private static final String MIME_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; - private static final String HEADER_AUTHORIZATION = "Authorization"; - private static final String HEADER_AUTHORIZATION_PROXY = "Proxy-Authorization"; - private static final String PREFIX_BASIC_AUTH = "Basic "; - private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "grant_type=client_credentials"; - private final String issuerUrl; private final String clientId; private final String clientSecret; @@ -159,7 +150,7 @@ private SSLContext createSslContext(KeyStore trustStore) .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); - SSLContext sslContext = SSLContext.getInstance(TLS); + SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustManagerFactory.getTrustManagers(), null); return sslContext; @@ -200,27 +191,27 @@ private HttpRequest createAccessTokenRequest() configureAuthentication(builder); configureProxyAuthentication(builder); - builder.header(HEADER_CONTENT_TYPE, MIME_TYPE_FORM_URLENCODED); - builder.POST(HttpRequest.BodyPublishers.ofString(GRANT_TYPE_CLIENT_CREDENTIALS)); + builder.header("Content-Type", "application/x-www-form-urlencoded"); + builder.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials")); return builder.build(); } private void configureAuthentication(HttpRequest.Builder builder) { - // Keycloak not sending WWW-Authenticate header for response code 401 + // Preemptive basic authentication part of the OAuth 2.0 Authorization Framework + // RFC 6749 section 4.4.2 Access Token Request specification: + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2 String credentials = getCredentials(clientId, clientSecret); - builder.header(HEADER_AUTHORIZATION, PREFIX_BASIC_AUTH + credentials); + builder.header("Authorization", "Basic " + credentials); } private void configureProxyAuthentication(HttpRequest.Builder builder) { - // Proxy authentication using similar workaround as basic authentication - if (proxyUrl != null && proxyUsername != null && proxyPassword != null) - { - String proxyCredentials = getCredentials(proxyUsername, proxyPassword); - builder.setHeader(HEADER_AUTHORIZATION_PROXY, PREFIX_BASIC_AUTH + proxyCredentials); - } + // Preemptive proxy basic authentication because non preemptive prxy authentication overrides + // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) + String credentials = getCredentials(proxyUsername, proxyPassword); + builder.header("Proxy-Authorization", "Basic " + credentials); } private String getCredentials(String username, String password) From de25b4dbba46207a2988f826940eadd04b3c8aa8 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 23 Jul 2024 15:27:50 +0200 Subject: [PATCH 10/20] check for proxy variables before setting authentication --- .../common/fhir/client/token/OAuth2TokenClient.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 0324e9b..efd8173 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -208,10 +208,13 @@ private void configureAuthentication(HttpRequest.Builder builder) private void configureProxyAuthentication(HttpRequest.Builder builder) { - // Preemptive proxy basic authentication because non preemptive prxy authentication overrides - // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) - String credentials = getCredentials(proxyUsername, proxyPassword); - builder.header("Proxy-Authorization", "Basic " + credentials); + if (proxyUrl != null && proxyUsername != null & proxyPassword != null) + { + // Preemptive proxy basic authentication because non preemptive prxy authentication overrides + // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) + String credentials = getCredentials(proxyUsername, proxyPassword); + builder.header("Proxy-Authorization", "Basic " + credentials); + } } private String getCredentials(String username, String password) From d54182491d55fb0abf7c0a3b9c4a81f4f00134b5 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 23 Jul 2024 15:30:50 +0200 Subject: [PATCH 11/20] fix typo --- .../processes/common/fhir/client/token/OAuth2TokenClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index efd8173..9ab1722 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -210,7 +210,7 @@ private void configureProxyAuthentication(HttpRequest.Builder builder) { if (proxyUrl != null && proxyUsername != null & proxyPassword != null) { - // Preemptive proxy basic authentication because non preemptive prxy authentication overrides + // Preemptive proxy basic authentication because non preemptive proxy authentication overrides // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) String credentials = getCredentials(proxyUsername, proxyPassword); builder.header("Proxy-Authorization", "Basic " + credentials); From cc6a351824f7159137b6b8eddd458be862463c16 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 23 Jul 2024 16:35:26 +0200 Subject: [PATCH 12/20] rename buffer to include length --- .../processes/common/fhir/client/token/AccessToken.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java index 0523ece..a3cdf94 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/AccessToken.java @@ -9,7 +9,7 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class AccessToken { - private static final int BUFFER = 10; + private static final int BUFFER_10 = 10; private final String token; @@ -29,6 +29,6 @@ public String get() public boolean isExpired() { - return LocalDateTime.now().plusSeconds(BUFFER).isAfter(expiresAt); + return LocalDateTime.now().plusSeconds(BUFFER_10).isAfter(expiresAt); } } From ae3923e1cb3ad140d1168f9975bfc10924648a7b Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Fri, 26 Jul 2024 09:59:06 +0200 Subject: [PATCH 13/20] add logging for proxy --- .../processes/common/fhir/client/token/OAuth2TokenClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 9ab1722..81c8aad 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -179,6 +179,9 @@ private void configureProxy(HttpClient.Builder builder) { URI uri = URI.create(proxyUrl); builder.proxy(ProxySelector.of(new InetSocketAddress(uri.getHost(), uri.getPort()))); + + logger.info("Using proxy for oauth2 provider connection with {host: {}, port: {}, username: {}}", + uri.getHost(), uri.getPort(), proxyUsername); } } From 21d95edcd59859795d4d1a64eb7ca3789bd6c592 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Fri, 26 Jul 2024 10:10:05 +0200 Subject: [PATCH 14/20] change log level for coinfuguration when using proxy to debug --- .../processes/common/fhir/client/FhirClientImpl.java | 2 +- .../processes/common/fhir/client/token/OAuth2TokenClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java index ddabaaa..81a4870 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java @@ -90,7 +90,7 @@ private void configureProxy(IRestfulClientFactory clientFactory, String proxyUrl clientFactory.setProxy(url.getHost(), url.getPort()); clientFactory.setProxyCredentials(proxyUsername, proxyPassword); - logger.info("Using proxy for FHIR server connection with {host: {}, port: {}, username: {}}", + logger.debug("Using proxy for FHIR server connection with {host: {}, port: {}, username: {}}", url.getHost(), url.getPort(), proxyUsername); } catch (MalformedURLException e) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 81c8aad..1ed3634 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -180,7 +180,7 @@ private void configureProxy(HttpClient.Builder builder) URI uri = URI.create(proxyUrl); builder.proxy(ProxySelector.of(new InetSocketAddress(uri.getHost(), uri.getPort()))); - logger.info("Using proxy for oauth2 provider connection with {host: {}, port: {}, username: {}}", + logger.debug("Using proxy for oauth2 provider connection with {host: {}, port: {}, username: {}}", uri.getHost(), uri.getPort(), proxyUsername); } } From b928422d370026128b65e4520df0ad3032d8b3e8 Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Fri, 26 Jul 2024 11:26:10 +0200 Subject: [PATCH 15/20] use oauth2 instead of only oauth --- .../processes/common/fhir/client/FhirClientImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java index 81a4870..6fcda53 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java @@ -129,7 +129,7 @@ private void configuredWithBearerTokenAuth(IGenericClient client) client.registerInterceptor(new BearerTokenAuthInterceptor(fhirServerBearerToken)); } - private void configuredWithOAuth(IGenericClient client) + private void configuredWithOAuth2(IGenericClient client) { if (fhirServerOAuth2TokenProvider != null && fhirServerOAuth2TokenProvider.isConfigured()) client.registerInterceptor(new OAuth2Interceptor(fhirServerOAuth2TokenProvider)); @@ -170,7 +170,7 @@ public IGenericClient getGenericFhirClient() configuredWithBasicAuth(client); configuredWithBearerTokenAuth(client); - configuredWithOAuth(client); + configuredWithOAuth2(client); configureLoggingInterceptor(client); return client; From 97306f4118322c75fdeb69ff7c929f5b296f766e Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 30 Jul 2024 08:56:49 +0200 Subject: [PATCH 16/20] rename method to encodeCredentials --- .../common/fhir/client/token/OAuth2TokenClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 1ed3634..4d5ca19 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -205,7 +205,7 @@ private void configureAuthentication(HttpRequest.Builder builder) // Preemptive basic authentication part of the OAuth 2.0 Authorization Framework // RFC 6749 section 4.4.2 Access Token Request specification: // https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2 - String credentials = getCredentials(clientId, clientSecret); + String credentials = encodeCredentials(clientId, clientSecret); builder.header("Authorization", "Basic " + credentials); } @@ -215,12 +215,12 @@ private void configureProxyAuthentication(HttpRequest.Builder builder) { // Preemptive proxy basic authentication because non preemptive proxy authentication overrides // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) - String credentials = getCredentials(proxyUsername, proxyPassword); + String credentials = encodeCredentials(proxyUsername, proxyPassword); builder.header("Proxy-Authorization", "Basic " + credentials); } } - private String getCredentials(String username, String password) + private String encodeCredentials(String username, String password) { String credentials = username + ":" + password; return Base64.getEncoder().encodeToString(credentials.getBytes()); From e672fc3c339f742791e4360ebe2855175db9eb7d Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Tue, 30 Jul 2024 14:21:20 +0200 Subject: [PATCH 17/20] adds comment why preemptive authorization header is needed --- .../processes/common/fhir/client/token/OAuth2TokenClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index 4d5ca19..fa9aa5c 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -214,7 +214,8 @@ private void configureProxyAuthentication(HttpRequest.Builder builder) if (proxyUrl != null && proxyUsername != null & proxyPassword != null) { // Preemptive proxy basic authentication because non preemptive proxy authentication overrides - // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder) + // preemptive authentication for oauth2 provider, see configureAuthentication(HttpRequest.Builder builder): + // probably caused by https://bugs.openjdk.org/browse/JDK-8326949 String credentials = encodeCredentials(proxyUsername, proxyPassword); builder.header("Proxy-Authorization", "Basic " + credentials); } From f80679e9b9955a70f8006aac590f9b8faa5a269f Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Wed, 31 Jul 2024 08:56:15 +0200 Subject: [PATCH 18/20] add proxy password with *** to debug log if set --- .../processes/common/fhir/client/FhirClientImpl.java | 5 +++-- .../common/fhir/client/token/OAuth2TokenClient.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java index 6fcda53..c87132d 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/FhirClientImpl.java @@ -90,8 +90,9 @@ private void configureProxy(IRestfulClientFactory clientFactory, String proxyUrl clientFactory.setProxy(url.getHost(), url.getPort()); clientFactory.setProxyCredentials(proxyUsername, proxyPassword); - logger.debug("Using proxy for FHIR server connection with {host: {}, port: {}, username: {}}", - url.getHost(), url.getPort(), proxyUsername); + logger.debug( + "Using proxy for FHIR server connection with {host: {}, port: {}, username: {}, password: {}}", + url.getHost(), url.getPort(), proxyUsername, proxyPassword != null ? "***" : "null"); } catch (MalformedURLException e) { diff --git a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java index fa9aa5c..69c8b4e 100644 --- a/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java +++ b/src/main/java/de/medizininformatik_initiative/processes/common/fhir/client/token/OAuth2TokenClient.java @@ -180,8 +180,9 @@ private void configureProxy(HttpClient.Builder builder) URI uri = URI.create(proxyUrl); builder.proxy(ProxySelector.of(new InetSocketAddress(uri.getHost(), uri.getPort()))); - logger.debug("Using proxy for oauth2 provider connection with {host: {}, port: {}, username: {}}", - uri.getHost(), uri.getPort(), proxyUsername); + logger.debug( + "Using proxy for oauth2 provider connection with {host: {}, port: {}, username: {}, password: {}}", + uri.getHost(), uri.getPort(), proxyUsername, proxyPassword != null ? "***" : "null"); } } From 51820c82142b73a07e5bb6902ceb7c067745051e Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Wed, 31 Jul 2024 13:55:07 +0200 Subject: [PATCH 19/20] update dependencies --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 70342b2..46485ae 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ dev.dsf dsf-bpe-process-api-v1 - 1.5.1 + 1.5.2 provided @@ -87,12 +87,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.3.1 org.apache.maven.plugins maven-jar-plugin - 3.4.1 + 3.4.2 @@ -109,7 +109,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.3 + 3.6.0 package @@ -144,12 +144,12 @@ org.apache.maven.plugins maven-dependency-plugin - 3.6.1 + 3.7.1 org.apache.maven.plugins maven-clean-plugin - 3.3.2 + 3.4.0 org.codehaus.mojo @@ -172,7 +172,7 @@ net.revelc.code.formatter formatter-maven-plugin - 2.23.0 + 2.24.1 eclipse-formatter-config.xml @@ -180,7 +180,7 @@ net.revelc.code impsort-maven-plugin - 1.9.0 + 1.11.0 17 java.,javax.,org.,com. @@ -195,17 +195,17 @@ org.apache.maven.plugins maven-project-info-reports-plugin - 3.5.0 + 3.6.2 com.github.spotbugs spotbugs-maven-plugin - 4.8.5.0 + 4.8.6.2 org.apache.maven.plugins maven-pmd-plugin - 3.22.0 + 3.24.0 From 670853e638a0f71439fdba2995f0f2d6d9cfc11a Mon Sep 17 00:00:00 2001 From: Reto Wettstein Date: Thu, 1 Aug 2024 08:16:15 +0200 Subject: [PATCH 20/20] release 1.0.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 46485ae..26a9921 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.medizininformatik-initiative mii-processes-common - 1.0.2.0-SNAPSHOT + 1.0.2.0 UTF-8