Skip to content

Commit

Permalink
Affected Issue(s): Extend OAuth 2.0 support
Browse files Browse the repository at this point in the history
Resolves sid-indonesia/it-team#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.
  • Loading branch information
muhammad-levi committed Sep 13, 2024
1 parent 5150ca1 commit bd0ffb7
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
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;
import com.google.api.client.http.javanet.NetHttpTransport;
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;
Expand All @@ -61,6 +63,8 @@ public class FetchUtil {

private final String oAuthTokenEndpoint;

private final ClientCredentialsAuthMechanism oAuthMechanism;

private final String oAuthClientId;

private final String oAuthClientSecret;
Expand All @@ -74,13 +78,15 @@ public class FetchUtil {
String sourceUser,
String sourcePw,
String oAuthTokenEndpoint,
ClientCredentialsAuthMechanism oAuthMechanism,
String oAuthClientId,
String oAuthClientSecret,
FhirContext fhirContext) {
this.fhirUrl = sourceFhirUrl;
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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ private synchronized void flushViewWriter(String viewName) throws IOException, P
ParquetWriter<GenericRecord> writer = viewWriterMap.get(viewName);
if (writer != null && writer.getDataSize() > 0) {
writer.close();
//TODO: We need to investigate why we need to create the writer here. If we change this logic
// TODO: We need to investigate why we need to create the writer here. If we change this logic
// to remove the writer at this line, E2E Streaming Tests fail in CloudBuild.
createWriter(viewName, viewMap.get(viewName));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <code>
* {URL-encoded-client-ID}:{URL-encoded-client-secret}</code>
*/
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
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ private FhirSearchUtil getFhirSearchUtil(FhirEtlOptions options) {
options.getFhirServerUserName(),
options.getFhirServerPassword(),
options.getFhirServerOAuthTokenEndpoint(),
options.getFhirServerOAuthMechanism(),
options.getFhirServerOAuthClientId(),
options.getFhirServerOAuthClientSecret(),
avroConversionUtil.getFhirContext()));
Expand Down

0 comments on commit bd0ffb7

Please sign in to comment.