diff --git a/pom.xml b/pom.xml index 6c3a783..d086735 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.medizininformatik-initiative mii-process-data-transfer - 1.0.1.1-SNAPSHOT + 1.0.2.0-SNAPSHOT UTF-8 @@ -45,7 +45,7 @@ de.medizininformatik-initiative mii-processes-common - 1.0.1.0 + 1.0.2.0-SNAPSHOT diff --git a/src/main/java/de/medizininformatik_initiative/process/data_transfer/DataTransferProcessPluginDefinition.java b/src/main/java/de/medizininformatik_initiative/process/data_transfer/DataTransferProcessPluginDefinition.java index 5d84198..f2e1af9 100644 --- a/src/main/java/de/medizininformatik_initiative/process/data_transfer/DataTransferProcessPluginDefinition.java +++ b/src/main/java/de/medizininformatik_initiative/process/data_transfer/DataTransferProcessPluginDefinition.java @@ -11,7 +11,7 @@ public class DataTransferProcessPluginDefinition implements ProcessPluginDefinition { - public static final String VERSION = "1.0.1.1"; + public static final String VERSION = "1.0.2.0"; public static final LocalDate RELEASE_DATE = LocalDate.of(2024, 6, 11); @Override diff --git a/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DicFhirClientConfig.java b/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DicFhirClientConfig.java index d254212..6dcdc3f 100644 --- a/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DicFhirClientConfig.java +++ b/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DicFhirClientConfig.java @@ -11,15 +11,22 @@ import ca.uhn.fhir.context.FhirContext; import de.medizininformatik_initiative.processes.common.fhir.client.FhirClientFactory; import de.medizininformatik_initiative.processes.common.fhir.client.logging.DataLogger; +import de.medizininformatik_initiative.processes.common.fhir.client.token.OAuth2TokenClient; +import de.medizininformatik_initiative.processes.common.fhir.client.token.OAuth2TokenProvider; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenClient; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenProvider; +import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.documentation.ProcessDocumentation; @Configuration public class DicFhirClientConfig { - // TODO: use default proxy config from DSF @Autowired private FhirContext fhirContext; + @Autowired + private ProcessPluginApi api; + @ProcessDocumentation(required = true, processNames = { "medizininformatik-initiativede_dataSend" }, description = "The base address of the DIC FHIR server to read/store FHIR resources", example = "http://foo.bar/fhir") @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.base.url:#{null}}") @@ -81,20 +88,65 @@ public class DicFhirClientConfig private boolean fhirStoreHapiClientVerbose; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataSend" }, description = "Proxy location, set if the server containing the FHIR data can only be reached through a proxy", example = "http://proxy.foo:8080") + "medizininformatik-initiativede_dataSend" }, description = "Proxy location, set if the server containing the FHIR data can only be reached through a proxy, uses value from DEV_DSF_PROXY_URL if not set", example = "http://proxy.foo:8080") @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.proxy.url:#{null}}") private String fhirStoreProxyUrl; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataSend" }, description = "Proxy username, set if the server containing the FHIR data can only be reached through a proxy which requests authentication") + "medizininformatik-initiativede_dataSend" }, description = "Proxy username, set if the server containing the FHIR data can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_USERNAME if not set") @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.proxy.username:#{null}}") private String fhirStoreProxyUsername; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataSend" }, description = "Proxy password, set if the server containing the FHIR data can only be reached through a proxy which requests authentication", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + "medizininformatik-initiativede_dataSend" }, description = "Proxy password, set if the server containing the FHIR data can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_PASSWORD if not set", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.proxy.password:#{null}}") private String fhirStoreProxyPassword; + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "The url of the oidc provider to request access tokens (token endpoint)", example = "http://foo.baz/realms/fhir-realm/protocol/openid-connect/token") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.issuer.url:#{null}}") + private String fhirStoreOAuth2IssuerUrl; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Identifier of the client (username) used for authentication when accessing the oidc provider token endpoint") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.client.id:#{null}}") + private String fhirStoreOAuth2ClientId; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Secret of the client (password) used for authentication when accessing the oidc provider token endpoint", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.client.password:#{null}}") + private String fhirStoreOAuth2ClientSecret; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "The timeout in milliseconds until a connection is established between the client and the oidc provider", recommendation = "Change default value only if timeout exceptions occur") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.timeout.connect:20000}") + private int fhirStoreOAuth2ConnectTimeout; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Maximum period of inactivity in milliseconds between two consecutive data packets of the client and the oidc provider", recommendation = "Change default value only if timeout exceptions occur") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.timeout.socket:60000}") + private int fhirStoreOAuth2SocketTimeout; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "PEM encoded file with one or more trusted root certificate to validate the oidc provider server certificate when connecting via https", recommendation = "Use docker secret file to configure", example = "/run/secrets/hospital_ca.pem") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.trust.certificates:#{null}}") + private String fhirStoreOAuth2TrustStore; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Proxy location, set if the oidc provider can only be reached through a proxy, uses value from DEV_DSF_PROXY_URL if not set", example = "http://proxy.foo:8080") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.proxy.url:#{null}}") + private String fhirStoreOAuth2ProxyUrl; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Proxy username, set if the oidc provider can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_USERNAME if not set") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.proxy.username:#{null}}") + private String fhirStoreOAuth2ProxyUsername; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataSend" }, description = "Proxy password, set if the oidc provider can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_PASSWORD if not set", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.server.oauth2.proxy.password:#{null}}") + private String fhirStoreOAuth2ProxyPassword; + @ProcessDocumentation(processNames = { "medizininformatik-initiativede_dataSend" }, description = "To enable debug logging of FHIR resources set to `true`") @Value("${de.medizininformatik.initiative.data.transfer.dic.fhir.dataLoggingEnabled:false}") @@ -109,10 +161,46 @@ public FhirClientFactory fhirClientFactory() Path certificatePath = checkExists(fhirStoreCertificate); Path privateKeyPath = checkExists(fhirStorePrivateKey); + String proxyUrl = fhirStoreProxyUrl, proxyUsername = fhirStoreProxyUsername, + proxyPassword = fhirStoreProxyPassword; + if (proxyUrl == null && api.getProxyConfig().isEnabled() + && !api.getProxyConfig().isNoProxyUrl(fhirStoreBaseUrl)) + { + proxyUrl = api.getProxyConfig().getUrl(); + proxyUsername = api.getProxyConfig().getUsername(); + proxyPassword = api.getProxyConfig().getPassword() == null ? null + : new String(api.getProxyConfig().getPassword()); + } + return new FhirClientFactory(trustStorePath, certificatePath, privateKeyPath, fhirStorePrivateKeyPassword, fhirStoreConnectTimeout, fhirStoreSocketTimeout, fhirStoreConnectionRequestTimeout, fhirStoreBaseUrl, - fhirStoreUsername, fhirStorePassword, fhirStoreBearerToken, fhirStoreProxyUrl, fhirStoreProxyUsername, - fhirStoreProxyPassword, fhirStoreHapiClientVerbose, fhirContext, localIdentifierValue, dataLogger()); + fhirStoreUsername, fhirStorePassword, fhirStoreBearerToken, tokenProvider(), proxyUrl, proxyUsername, + proxyPassword, fhirStoreHapiClientVerbose, fhirContext, localIdentifierValue, dataLogger()); + } + + public TokenProvider tokenProvider() + { + return new OAuth2TokenProvider(tokenClient()); + } + + public TokenClient tokenClient() + { + Path trustStoreOAuth2Path = checkExists(fhirStoreOAuth2TrustStore); + + String proxyUrl = fhirStoreOAuth2ProxyUrl, proxyUsername = fhirStoreOAuth2ProxyUsername, + proxyPassword = fhirStoreOAuth2ProxyPassword; + if (proxyUrl == null && api.getProxyConfig().isEnabled() + && !api.getProxyConfig().isNoProxyUrl(fhirStoreOAuth2IssuerUrl)) + { + proxyUrl = api.getProxyConfig().getUrl(); + proxyUsername = api.getProxyConfig().getUsername(); + proxyPassword = api.getProxyConfig().getPassword() == null ? null + : new String(api.getProxyConfig().getPassword()); + } + + return new OAuth2TokenClient(fhirStoreOAuth2IssuerUrl, fhirStoreOAuth2ClientId, fhirStoreOAuth2ClientSecret, + fhirStoreOAuth2ConnectTimeout, fhirStoreOAuth2SocketTimeout, trustStoreOAuth2Path, proxyUrl, + proxyUsername, proxyPassword); } public DataLogger dataLogger() diff --git a/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DmsFhirClientConfig.java b/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DmsFhirClientConfig.java index 6026945..a356a47 100644 --- a/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DmsFhirClientConfig.java +++ b/src/main/java/de/medizininformatik_initiative/process/data_transfer/spring/config/DmsFhirClientConfig.java @@ -11,15 +11,22 @@ import ca.uhn.fhir.context.FhirContext; import de.medizininformatik_initiative.processes.common.fhir.client.FhirClientFactory; import de.medizininformatik_initiative.processes.common.fhir.client.logging.DataLogger; +import de.medizininformatik_initiative.processes.common.fhir.client.token.OAuth2TokenClient; +import de.medizininformatik_initiative.processes.common.fhir.client.token.OAuth2TokenProvider; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenClient; +import de.medizininformatik_initiative.processes.common.fhir.client.token.TokenProvider; +import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.documentation.ProcessDocumentation; @Configuration public class DmsFhirClientConfig { - // TODO: use default proxy config from DSF @Autowired private FhirContext fhirContext; + @Autowired + private ProcessPluginApi api; + @ProcessDocumentation(required = true, processNames = { "medizininformatik-initiativede_dataReceive" }, description = "The base address of the DMS FHIR server to read/store FHIR resources", example = "http://foo.bar/fhir") @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.base.url:#{null}}") @@ -81,20 +88,65 @@ public class DmsFhirClientConfig private boolean fhirStoreHapiClientVerbose; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataReceive" }, description = "Proxy location, set if the server containing the FHIR data can only be reached through a proxy", example = "http://proxy.foo:8080") + "medizininformatik-initiativede_dataReceive" }, description = "Proxy location, set if the server containing the FHIR data can only be reached through a proxy, uses value from DEV_DSF_PROXY_URL if not set", example = "http://proxy.foo:8080") @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.proxy.url:#{null}}") private String fhirStoreProxyUrl; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataReceive" }, description = "Proxy username, set if the server containing the FHIR data can only be reached through a proxy which requests authentication") + "medizininformatik-initiativede_dataReceive" }, description = "Proxy username, set if the server containing the FHIR data can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_USERNAME if not set") @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.proxy.username:#{null}}") private String fhirStoreProxyUsername; @ProcessDocumentation(processNames = { - "medizininformatik-initiativede_dataReceive" }, description = "Proxy password, set if the server containing the FHIR data can only be reached through a proxy which requests authentication", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + "medizininformatik-initiativede_dataReceive" }, description = "Proxy password, set if the server containing the FHIR data can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_PASSWORD if not set", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.proxy.password:#{null}}") private String fhirStoreProxyPassword; + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "The url of the oidc provider to request access tokens (token endpoint)", example = "http://foo.baz/realms/fhir-realm/protocol/openid-connect/token") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.issuer.url:#{null}}") + private String fhirStoreOAuth2IssuerUrl; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Identifier of the client (username) used for authentication when accessing the oidc provider token endpoint") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.client.id:#{null}}") + private String fhirStoreOAuth2ClientId; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Secret of the client (password) used for authentication when accessing the oidc provider token endpoint", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.client.password:#{null}}") + private String fhirStoreOAuth2ClientSecret; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "The timeout in milliseconds until a connection is established between the client and the oidc provider", recommendation = "Change default value only if timeout exceptions occur") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.timeout.connect:20000}") + private int fhirStoreOAuth2ConnectTimeout; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Maximum period of inactivity in milliseconds between two consecutive data packets of the client and the oidc provider", recommendation = "Change default value only if timeout exceptions occur") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.timeout.socket:60000}") + private int fhirStoreOAuth2SocketTimeout; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "PEM encoded file with one or more trusted root certificate to validate the oidc provider server certificate when connecting via https", recommendation = "Use docker secret file to configure", example = "/run/secrets/hospital_ca.pem") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.trust.certificates:#{null}}") + private String fhirStoreOAuth2TrustStore; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Proxy location, set if the oidc provider can only be reached through a proxy, uses value from DEV_DSF_PROXY_URL if not set", example = "http://proxy.foo:8080") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.proxy.url:#{null}}") + private String fhirStoreOAuth2ProxyUrl; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Proxy username, set if the oidc provider can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_USERNAME if not set") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.proxy.username:#{null}}") + private String fhirStoreOAuth2ProxyUsername; + + @ProcessDocumentation(processNames = { + "medizininformatik-initiativede_dataReceive" }, description = "Proxy password, set if the oidc provider can only be reached through a proxy which requests authentication, uses value from DEV_DSF_PROXY_PASSWORD if not set", recommendation = "Use docker secret file to configure by using *${env_variable}_FILE*") + @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.server.oauth2.proxy.password:#{null}}") + private String fhirStoreOAuth2ProxyPassword; + @ProcessDocumentation(processNames = { "medizininformatik-initiativede_dataReceive" }, description = "To enable debug logging of FHIR resources set to `true`") @Value("${de.medizininformatik.initiative.data.transfer.dms.fhir.dataLoggingEnabled:false}") @@ -109,10 +161,46 @@ public FhirClientFactory fhirClientFactory() Path certificatePath = checkExists(fhirStoreCertificate); Path privateKeyPath = checkExists(fhirStorePrivateKey); + String proxyUrl = fhirStoreProxyUrl, proxyUsername = fhirStoreProxyUsername, + proxyPassword = fhirStoreProxyPassword; + if (proxyUrl == null && api.getProxyConfig().isEnabled() + && !api.getProxyConfig().isNoProxyUrl(fhirStoreBaseUrl)) + { + proxyUrl = api.getProxyConfig().getUrl(); + proxyUsername = api.getProxyConfig().getUsername(); + proxyPassword = api.getProxyConfig().getPassword() == null ? null + : new String(api.getProxyConfig().getPassword()); + } + return new FhirClientFactory(trustStorePath, certificatePath, privateKeyPath, fhirStorePrivateKeyPassword, fhirStoreConnectTimeout, fhirStoreSocketTimeout, fhirStoreConnectionRequestTimeout, fhirStoreBaseUrl, - fhirStoreUsername, fhirStorePassword, fhirStoreBearerToken, fhirStoreProxyUrl, fhirStoreProxyUsername, - fhirStoreProxyPassword, fhirStoreHapiClientVerbose, fhirContext, localIdentifierValue, dataLogger()); + fhirStoreUsername, fhirStorePassword, fhirStoreBearerToken, tokenProvider(), proxyUrl, proxyUsername, + proxyPassword, fhirStoreHapiClientVerbose, fhirContext, localIdentifierValue, dataLogger()); + } + + public TokenProvider tokenProvider() + { + return new OAuth2TokenProvider(tokenClient()); + } + + public TokenClient tokenClient() + { + Path trustStoreOAuth2Path = checkExists(fhirStoreOAuth2TrustStore); + + String proxyUrl = fhirStoreOAuth2ProxyUrl, proxyUsername = fhirStoreOAuth2ProxyUsername, + proxyPassword = fhirStoreOAuth2ProxyPassword; + if (proxyUrl == null && api.getProxyConfig().isEnabled() + && !api.getProxyConfig().isNoProxyUrl(fhirStoreOAuth2IssuerUrl)) + { + proxyUrl = api.getProxyConfig().getUrl(); + proxyUsername = api.getProxyConfig().getUsername(); + proxyPassword = api.getProxyConfig().getPassword() == null ? null + : new String(api.getProxyConfig().getPassword()); + } + + return new OAuth2TokenClient(fhirStoreOAuth2IssuerUrl, fhirStoreOAuth2ClientId, fhirStoreOAuth2ClientSecret, + fhirStoreOAuth2ConnectTimeout, fhirStoreOAuth2SocketTimeout, trustStoreOAuth2Path, proxyUrl, + proxyUsername, proxyPassword); } public DataLogger dataLogger()