From cd1a330bb6caf11837ff2ad5df83686673169400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20R=C3=BChle?= Date: Wed, 19 Jun 2024 15:07:51 +0200 Subject: [PATCH 1/2] Update Test Dependencies --- .../process/feasibility/client/store/OAuthInterceptorIT.java | 2 +- .../process/feasibility/client/store/StoreClientIT.java | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java index f9bd9bd..9dd98ca 100644 --- a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java +++ b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java @@ -20,7 +20,7 @@ public class OAuthInterceptorIT { protected static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork(); @Container - public static KeycloakContainer keycloak = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") + public static KeycloakContainer keycloak = new KeycloakContainer("quay.io/keycloak/keycloak:25.0") .withNetwork(DEFAULT_CONTAINER_NETWORK) .withNetworkAliases("keycloak") .withAdminUsername("admin") diff --git a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientIT.java b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientIT.java index 057aff3..fdb1435 100644 --- a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientIT.java +++ b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientIT.java @@ -53,7 +53,7 @@ public class StoreClientIT { private static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork(); @Container - public GenericContainer fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.24")) + public GenericContainer fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.27")) .withExposedPorts(8080) .withNetwork(DEFAULT_CONTAINER_NETWORK) .withNetworkAliases("fhir-server") diff --git a/pom.xml b/pom.xml index 59df77b..5903b43 100755 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ 17 1.3.1 5.1.0 - 1.19.3 + 1.19.8 MII Processes Feasibility From 9027a0c27490c6728a8e6724cf58aa0e2521ffbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20R=C3=BChle?= Date: Wed, 19 Jun 2024 15:12:17 +0200 Subject: [PATCH 2/2] Get Token Endpoint URI from Issuer OpenID Connect Metadata --- mii-process-feasibility/README.md | 1 + .../client/store/OAuthInterceptor.java | 61 +++++++++++++------ .../client/store/StoreClientSpringConfig.java | 2 +- .../client/store/OAuthInterceptorIT.java | 13 ++-- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/mii-process-feasibility/README.md b/mii-process-feasibility/README.md index 7f06e63..3f9ac7b 100644 --- a/mii-process-feasibility/README.md +++ b/mii-process-feasibility/README.md @@ -113,6 +113,7 @@ Besides the [common DSF settings controlled by different environment variables][ | CLIENT_STORE_AUTH_BEARER_TOKEN | Bearer token used for authentication against a client target. Do not prefix this with `Bearer `! | `null` | | CLIENT_STORE_AUTH_BASIC_USERNAME | Username for basic authentication against a FHIR server client target. | `null` | | CLIENT_STORE_AUTH_BASIC_PASSWORD | Password for basic authentication against a FHIR server client target. | `null` | +| CLIENT_STORE_AUTH_OAUTH_ISSUER_URL | Issuer URL of the OpenID Connect provider to gain access token for FHIR server client target. | `null` | | CLIENT_STORE_AUTH_OAUTH_CLIENT_ID | Client ID for authentication against a OpenID Connect provider to gain access token for FHIR server client target. | `null` | | CLIENT_STORE_AUTH_OAUTH_CLIENT_PASSWORD | Client Password for authentication against a OpenID Connect provider to gain access token for FHIR server client target. | `null` | | CLIENT_STORE_AUTH_OAUTH_PROXY_HOST | Forward proxy host for connecting to OpenID Connect provider. | `null` | diff --git a/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptor.java b/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptor.java index be70c60..5f354f8 100644 --- a/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptor.java +++ b/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptor.java @@ -6,7 +6,7 @@ import ca.uhn.fhir.rest.client.api.IHttpResponse; import com.nimbusds.oauth2.sdk.AccessTokenResponse; import com.nimbusds.oauth2.sdk.ClientCredentialsGrant; -import com.nimbusds.oauth2.sdk.ParseException; +import com.nimbusds.oauth2.sdk.GeneralException; import com.nimbusds.oauth2.sdk.TokenErrorResponse; import com.nimbusds.oauth2.sdk.TokenRequest; import com.nimbusds.oauth2.sdk.TokenResponse; @@ -14,7 +14,9 @@ import com.nimbusds.oauth2.sdk.auth.Secret; import com.nimbusds.oauth2.sdk.http.HTTPRequest; import com.nimbusds.oauth2.sdk.id.ClientID; +import com.nimbusds.oauth2.sdk.id.Issuer; import com.nimbusds.oauth2.sdk.token.AccessToken; +import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import org.joda.time.DateTime; import java.io.IOException; @@ -32,27 +34,23 @@ final class OAuthInterceptor implements IClientInterceptor { private HTTPRequest tokenRequest; private AccessToken token; private DateTime tokenExpiry; + private Optional proxy; + private Optional proxyAuthHeader; + private Issuer issuer; + private ClientSecretBasic clientAuth; - public OAuthInterceptor(String oauthClientId, String oauthClientSecret, String oauthTokenUrl, + public OAuthInterceptor(String oauthClientId, String oauthClientSecret, String oauthIssuerUrl, Optional proxyHost, Optional proxyPort, Optional proxyUsername, Optional proxyPassword) { super(); - ClientSecretBasic clientAuth = new ClientSecretBasic(new ClientID(oauthClientId), - new Secret(oauthClientSecret)); - HTTPRequest request = new TokenRequest(URI.create(oauthTokenUrl), clientAuth, new ClientCredentialsGrant()) - .toHTTPRequest(); - - if (proxyHost.isPresent() && proxyPort.isPresent()) { - Proxy proxy = new Proxy(Type.HTTP, - InetSocketAddress.createUnresolved(proxyHost.get(), proxyPort.get())); - request.setProxy(proxy); - - if (proxyUsername.isPresent() && proxyPassword.isPresent()) { - request.setHeader(HEADER_PROXY_AUTHORIZATION, - generateBasicAuthHeader(proxyUsername.get(), proxyPassword.get())); - } - } - tokenRequest = request; + clientAuth = new ClientSecretBasic(new ClientID(oauthClientId), new Secret(oauthClientSecret)); + issuer = new Issuer(oauthIssuerUrl); + proxy = proxyHost.map( + h -> proxyPort.map( + p -> new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(h, p))).orElse(null)); + proxyAuthHeader = proxyUsername.map( + n -> proxyPassword.map( + p -> generateBasicAuthHeader(n, p)).orElse(null)); } private String generateBasicAuthHeader(String username, String password) { @@ -63,7 +61,7 @@ private String generateBasicAuthHeader(String username, String password) { public String getToken() { if (token == null || tokenExpiry == null || tokenExpiry.isBefore(DateTime.now().plus(TOKEN_EXPIRY_THRESHOLD))) { try { - TokenResponse response = TokenResponse.parse(tokenRequest.send()); + TokenResponse response = TokenResponse.parse(getTokenRequest().send()); if (!response.indicatesSuccess()) { TokenErrorResponse errorResponse = response.toErrorResponse(); throw new OAuth2ClientException(errorResponse.getErrorObject().getCode() + " - " @@ -73,13 +71,36 @@ public String getToken() { token = successResponse.getTokens().getAccessToken(); tokenExpiry = DateTime.now().plus(token.getLifetime() * 1000); - } catch (ParseException | IOException e) { + } catch (GeneralException | IOException e) { throw new OAuth2ClientException("OAuth2 access token tokenRequest failed", e); } } return token.getValue(); } + private HTTPRequest getTokenRequest() throws GeneralException, IOException { + if (tokenRequest == null) { + HTTPRequest request = new TokenRequest(getTokenUri(), clientAuth, new ClientCredentialsGrant()) + .toHTTPRequest(); + tokenRequest = setProxy(request); + } + return tokenRequest; + } + + private URI getTokenUri() throws GeneralException, IOException { + return OIDCProviderMetadata.resolve(issuer, r -> { setProxy(r); }).getTokenEndpointURI(); + } + + private HTTPRequest setProxy(HTTPRequest request) { + if (proxy.isPresent()) { + request.setProxy(proxy.get()); + if (proxyAuthHeader.isPresent()) { + request.setHeader(HEADER_PROXY_AUTHORIZATION, proxyAuthHeader.get()); + } + } + return request; + } + @Override public void interceptRequest(IHttpRequest theRequest) { theRequest.addHeader(Constants.HEADER_AUTHORIZATION, diff --git a/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientSpringConfig.java b/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientSpringConfig.java index 97dce11..22a1fca 100644 --- a/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientSpringConfig.java +++ b/mii-process-feasibility/src/main/java/de/medizininformatik_initiative/process/feasibility/client/store/StoreClientSpringConfig.java @@ -63,7 +63,7 @@ public class StoreClientSpringConfig { @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.oauth.client.id:#{null}}") private String oauthClientId; - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.oauth.token.url:#{null}}") + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.oauth.issuer.url:#{null}}") private String oauthTokenUrl; @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.oauth.proxy.host:#{null}}") diff --git a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java index 9dd98ca..844f164 100644 --- a/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java +++ b/mii-process-feasibility/src/test/java/de/medizininformatik_initiative/process/feasibility/client/store/OAuthInterceptorIT.java @@ -48,9 +48,8 @@ public class OAuthInterceptorIT { @Test public void getToken() { - String tokenUrl = "http://" + keycloak.getHost() + ":" + keycloak.getFirstMappedPort() - + "/realms/test/protocol/openid-connect/token"; - OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", tokenUrl, Optional.empty(), + String issuerUrl = "http://" + keycloak.getHost() + ":" + keycloak.getFirstMappedPort() + "/realms/test"; + OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", issuerUrl, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); String token = interceptor.getToken(); @@ -60,8 +59,8 @@ public void getToken() { @Test public void getTokenViaProxyNoAuth() { - String tokenUrl = "http://keycloak:8080/realms/test/protocol/openid-connect/token"; - OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", tokenUrl, + String issuerUrl = "http://keycloak:8080/realms/test"; + OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", issuerUrl, Optional.of(forwardProxyNoAuth.getHost()), Optional.of(forwardProxyNoAuth.getFirstMappedPort()), Optional.empty(), Optional.empty()); @@ -73,8 +72,8 @@ public void getTokenViaProxyNoAuth() { @Test public void getTokenViaProxyBasicAuth() { - String tokenUrl = "http://keycloak:8080/realms/test/protocol/openid-connect/token"; - OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", tokenUrl, + String issuerUrl = "http://keycloak:8080/realms/test"; + OAuthInterceptor interceptor = new OAuthInterceptor("account", "test", issuerUrl, Optional.of(forwardProxyBasicAuth.getHost()), Optional.of(forwardProxyBasicAuth.getFirstMappedPort()), Optional.of("test"),