From eb97b98940a03e997337634f34247530fdc7d746 Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 13 Sep 2024 16:48:53 +0700 Subject: [PATCH] Affected Issue(s): Extend OAuth 2.0 support Resolves https://github.com/sid-indonesia/it-team/issues/333 What this commit has achieved: 1. Added support for Body Authentication on Client Credentials flow 2. TODO implement OAuth 2.0 for sink FHIR Server. --- .../google/fhir/analytics/FhirEtlOptions.java | 7 ++++ .../com/google/fhir/analytics/FetchUtil.java | 39 +++++++++++++++---- .../ClientCredentialsAuthMechanism.java | 39 +++++++++++++++++++ .../analytics/ControlPanelApplication.java | 2 +- .../fhir/analytics/PipelineManager.java | 1 + 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 pipelines/common/src/main/java/com/google/fhir/analytics/enumeration/ClientCredentialsAuthMechanism.java diff --git a/pipelines/batch/src/main/java/com/google/fhir/analytics/FhirEtlOptions.java b/pipelines/batch/src/main/java/com/google/fhir/analytics/FhirEtlOptions.java index 070c53822..46e1fb219 100644 --- a/pipelines/batch/src/main/java/com/google/fhir/analytics/FhirEtlOptions.java +++ b/pipelines/batch/src/main/java/com/google/fhir/analytics/FhirEtlOptions.java @@ -15,6 +15,7 @@ */ package com.google.fhir.analytics; +import com.google.fhir.analytics.enumeration.ClientCredentialsAuthMechanism; import org.apache.beam.sdk.options.Default; import org.apache.beam.sdk.options.Description; import org.apache.beam.sdk.options.Validation.Required; @@ -84,6 +85,12 @@ public interface FhirEtlOptions extends BasePipelineOptions { void setFhirServerOAuthTokenEndpoint(String value); + @Description("The mechanism for authenticating a client in the Client Credentials flow.") + @Default.Enum("BASIC") + ClientCredentialsAuthMechanism getFhirServerOAuthMechanism(); + + void setFhirServerOAuthMechanism(ClientCredentialsAuthMechanism value); + @Description( "The `client_id` to be used in the OAuth Client Credential flow when interacting with the " + "FHIR server; see `fhirServerOAuthEndpoint`.") diff --git a/pipelines/common/src/main/java/com/google/fhir/analytics/FetchUtil.java b/pipelines/common/src/main/java/com/google/fhir/analytics/FetchUtil.java index a94ecf55d..fdbd9239d 100644 --- a/pipelines/common/src/main/java/com/google/fhir/analytics/FetchUtil.java +++ b/pipelines/common/src/main/java/com/google/fhir/analytics/FetchUtil.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.rest.gclient.IOperationUntyped; import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput; import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest; +import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.BasicAuthentication; import com.google.api.client.http.GenericUrl; @@ -38,6 +39,7 @@ import com.google.api.client.json.gson.GsonFactory; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.fhir.analytics.enumeration.ClientCredentialsAuthMechanism; import java.io.IOException; import java.time.Instant; import java.util.List; @@ -61,6 +63,8 @@ public class FetchUtil { private final String oAuthTokenEndpoint; + private final ClientCredentialsAuthMechanism oAuthMechanism; + private final String oAuthClientId; private final String oAuthClientSecret; @@ -74,6 +78,7 @@ public class FetchUtil { String sourceUser, String sourcePw, String oAuthTokenEndpoint, + ClientCredentialsAuthMechanism oAuthMechanism, String oAuthClientId, String oAuthClientSecret, FhirContext fhirContext) { @@ -81,6 +86,7 @@ public class FetchUtil { this.sourceUser = Strings.nullToEmpty(sourceUser); this.sourcePw = Strings.nullToEmpty(sourcePw); this.oAuthTokenEndpoint = Strings.nullToEmpty(oAuthTokenEndpoint); + this.oAuthMechanism = oAuthMechanism; this.oAuthClientId = Strings.nullToEmpty(oAuthClientId); this.oAuthClientSecret = Strings.nullToEmpty(oAuthClientSecret); this.fhirContext = fhirContext; @@ -93,7 +99,7 @@ public class FetchUtil { log.info("Fetching access tokens from {}", oAuthTokenEndpoint); authInterceptor = new ClientCredentialsAuthInterceptor( - oAuthTokenEndpoint, oAuthClientId, oAuthClientSecret); + oAuthTokenEndpoint, oAuthMechanism, oAuthClientId, oAuthClientSecret); } else if (!this.sourceUser.isEmpty()) { authInterceptor = new BasicAuthInterceptor(this.sourceUser, sourcePw); } else { @@ -252,16 +258,23 @@ private static class ClientCredentialsAuthInterceptor extends BearerTokenAuthInt private static final int TOKEN_REFRESH_LEEWAY_IN_SECONDS = 10; private final String tokenEndpoint; + private final ClientCredentialsAuthMechanism oAuthMechanism; private final String clientId; private final String clientSecret; private TokenResponse tokenResponse; private Instant nextRefresh; - ClientCredentialsAuthInterceptor(String tokenEndpoint, String clientId, String clientSecret) { + ClientCredentialsAuthInterceptor( + String tokenEndpoint, + ClientCredentialsAuthMechanism oAuthMechanism, + String clientId, + String clientSecret) { Preconditions.checkNotNull(tokenEndpoint); + Preconditions.checkNotNull(clientSecret); Preconditions.checkNotNull(clientId); Preconditions.checkNotNull(clientSecret); this.tokenEndpoint = tokenEndpoint; + this.oAuthMechanism = oAuthMechanism; this.clientId = clientId; this.clientSecret = clientSecret; } @@ -291,12 +304,24 @@ public void interceptRequest(IHttpRequest theRequest) { } TokenResponse requestAccessToken() throws IOException { - TokenResponse response = + ClientCredentialsTokenRequest clientCredentialsTokenRequest = new ClientCredentialsTokenRequest( - new NetHttpTransport(), new GsonFactory(), new GenericUrl(tokenEndpoint)) - .setClientAuthentication(new BasicAuthentication(clientId, clientSecret)) - .execute(); - return response; + new NetHttpTransport(), new GsonFactory(), new GenericUrl(tokenEndpoint)); + switch (oAuthMechanism) { + case BASIC: + clientCredentialsTokenRequest = + clientCredentialsTokenRequest.setClientAuthentication( + new BasicAuthentication(clientId, clientSecret)); + break; + case BODY: + clientCredentialsTokenRequest = + clientCredentialsTokenRequest.setClientAuthentication( + new ClientParametersAuthentication(clientId, clientSecret)); + break; + case JWT: + break; + } + return clientCredentialsTokenRequest.execute(); } } } diff --git a/pipelines/common/src/main/java/com/google/fhir/analytics/enumeration/ClientCredentialsAuthMechanism.java b/pipelines/common/src/main/java/com/google/fhir/analytics/enumeration/ClientCredentialsAuthMechanism.java new file mode 100644 index 000000000..8af2f7b22 --- /dev/null +++ b/pipelines/common/src/main/java/com/google/fhir/analytics/enumeration/ClientCredentialsAuthMechanism.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020-2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.fhir.analytics.enumeration; + +/** + * A set of enumerations to specify the mechanism for authenticating a client in the Client + * Credentials flow. + */ +public enum ClientCredentialsAuthMechanism { + /** + * the client sends its client ID and client secret as part of the Authorization header in an HTTP + * request. The Authorization header contains a Base64-encoded string of + * {URL-encoded-client-ID}:{URL-encoded-client-secret} + */ + BASIC, + + /** + * The client sends its client ID and client secret as parameters in the body of the HTTP request. + * This is similar to Basic Authentication, but instead of sending the credentials in the + * Authorization header, they are sent in the request body. + */ + BODY, + + /** TODO: Unimplemented yet, as the need for it has not arisen. */ + JWT +} diff --git a/pipelines/controller/src/main/java/com/google/fhir/analytics/ControlPanelApplication.java b/pipelines/controller/src/main/java/com/google/fhir/analytics/ControlPanelApplication.java index bf8bfbe68..29751ee2a 100644 --- a/pipelines/controller/src/main/java/com/google/fhir/analytics/ControlPanelApplication.java +++ b/pipelines/controller/src/main/java/com/google/fhir/analytics/ControlPanelApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 Google LLC + * Copyright 2020-2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/pipelines/controller/src/main/java/com/google/fhir/analytics/PipelineManager.java b/pipelines/controller/src/main/java/com/google/fhir/analytics/PipelineManager.java index f4ad85241..2ec11244c 100644 --- a/pipelines/controller/src/main/java/com/google/fhir/analytics/PipelineManager.java +++ b/pipelines/controller/src/main/java/com/google/fhir/analytics/PipelineManager.java @@ -331,6 +331,7 @@ private FhirSearchUtil getFhirSearchUtil(FhirEtlOptions options) { options.getFhirServerUserName(), options.getFhirServerPassword(), options.getFhirServerOAuthTokenEndpoint(), + options.getFhirServerOAuthMechanism(), options.getFhirServerOAuthClientId(), options.getFhirServerOAuthClientSecret(), avroConversionUtil.getFhirContext()));