Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Obtain token endpoint URI from issuer metadata #118

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mii-process-feasibility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@
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;
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
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;
Expand All @@ -32,27 +34,23 @@ final class OAuthInterceptor implements IClientInterceptor {
private HTTPRequest tokenRequest;
private AccessToken token;
private DateTime tokenExpiry;
private Optional<Proxy> proxy;
private Optional<String> proxyAuthHeader;
private Issuer issuer;
private ClientSecretBasic clientAuth;

public OAuthInterceptor(String oauthClientId, String oauthClientSecret, String oauthTokenUrl,
public OAuthInterceptor(String oauthClientId, String oauthClientSecret, String oauthIssuerUrl,
Optional<String> proxyHost, Optional<Integer> proxyPort, Optional<String> proxyUsername,
Optional<String> 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) {
Expand All @@ -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() + " - "
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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();
Expand All @@ -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());
Expand All @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<compileTarget>17</compileTarget>
<dsf.version>1.3.1</dsf.version>
<hapi.fhir.version>5.1.0</hapi.fhir.version>
<testcontainers.version>1.19.3</testcontainers.version>
<testcontainers.version>1.19.8</testcontainers.version>
</properties>

<name>MII Processes Feasibility</name>
Expand Down
Loading