Skip to content

Commit

Permalink
[ServerSide] Sync the OpenSRP FHIR Gateway repo with the upstream Goo…
Browse files Browse the repository at this point in the history
…gle FHIR Gateway repo (#29)

* Allow request mutation by Access checkers (google#140)

* Define access decision preprocess stage to mutate the request params

* Documentation for request mutation change. Added test cases.

* Removed static identifier from request mutation method

* Updated the scan paths for demo app to load the core beans (google#132)

Add default scan paths in the exec application

* Support gzip encoding in data transfer (google#147)

* supporting gzip for client and server data transfer

* Version change for the next dev cycle (google#159)

* Fix in unit test

* Fixed unimplemented method in the OpenSRPSyncAccessDecision

---------

Co-authored-by: vivekmittal07 <[email protected]>
Co-authored-by: anchita-g <[email protected]>
Co-authored-by: Bashir Sadjad <[email protected]>
  • Loading branch information
4 people authored May 15, 2023
1 parent dd8b50e commit 208f0fc
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 8 deletions.
Empty file modified Dockerfile
100644 → 100755
Empty file.
2 changes: 1 addition & 1 deletion exec/pom.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.google.fhir.gateway</groupId>
<artifactId>fhir-gateway</artifactId>
<version>0.1.18-beta</version>
<version>0.1.19-beta</version>
</parent>

<artifactId>exec</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions exec/src/main/java/com/google/fhir/gateway/MainApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
@SpringBootApplication(scanBasePackages = {"com.google.fhir.gateway.plugin"})
@ServletComponentScan(basePackages = "com.google.fhir.gateway")
public class MainApp {

public static void main(String[] args) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/pom.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
implementations do not have to do this; they can redeclare those deps. -->
<groupId>com.google.fhir.gateway</groupId>
<artifactId>fhir-gateway</artifactId>
<version>0.1.18-beta</version>
<version>0.1.19-beta</version>
</parent>

<artifactId>plugins</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import com.google.fhir.gateway.HttpFhirClient;
import com.google.fhir.gateway.HttpUtil;
import com.google.fhir.gateway.interfaces.AccessDecision;
import com.google.fhir.gateway.interfaces.RequestDetailsReader;
import com.google.fhir.gateway.interfaces.RequestMutation;
import java.io.IOException;
import java.util.Set;
import org.apache.http.HttpResponse;
Expand Down Expand Up @@ -63,6 +65,11 @@ private AccessGrantedAndUpdateList(
this.resourceTypeExpected = resourceTypeExpected;
}

@Override
public RequestMutation getRequestMutation(RequestDetailsReader requestDetailsReader) {
return null;
}

@Override
public boolean canAccess() {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.fhir.gateway.ProxyConstants;
import com.google.fhir.gateway.interfaces.AccessDecision;
import com.google.fhir.gateway.interfaces.RequestDetailsReader;
import com.google.fhir.gateway.interfaces.RequestMutation;
import com.google.gson.Gson;
import java.io.FileReader;
import java.io.IOException;
Expand Down Expand Up @@ -98,6 +100,11 @@ public void preProcess(ServletRequestDetails servletRequestDetails) {
}
}

@Override
public RequestMutation getRequestMutation(RequestDetailsReader requestDetailsReader) {
return null;
}

/**
* Adds filters to the {@link ServletRequestDetails} for the _tag property to allow filtering by
* specific code-url-values that match specific locations, teams or organisations
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<groupId>com.google.fhir.gateway</groupId>
<artifactId>fhir-gateway</artifactId>
<version>0.1.18-beta</version>
<version>0.1.19-beta</version>
<packaging>pom</packaging>

<name>FHIR Information Gateway</name>
Expand Down
9 changes: 8 additions & 1 deletion server/pom.xml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>com.google.fhir.gateway</groupId>
<artifactId>fhir-gateway</artifactId>
<version>0.1.18-beta</version>
<version>0.1.19-beta</version>
</parent>

<artifactId>server</artifactId>
Expand Down Expand Up @@ -71,6 +71,13 @@
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>

<!-- For GCP APIs -->
<dependency>
<groupId>com.google.http-client</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.google.fhir.gateway.interfaces.AccessCheckerFactory;
import com.google.fhir.gateway.interfaces.AccessDecision;
import com.google.fhir.gateway.interfaces.RequestDetailsReader;
import com.google.fhir.gateway.interfaces.RequestMutation;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
Expand All @@ -62,6 +63,7 @@
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

@Interceptor
public class BearerAuthorizationInterceptor {
Expand All @@ -72,6 +74,10 @@ public class BearerAuthorizationInterceptor {
private static final String DEFAULT_CONTENT_TYPE = "application/json; charset=UTF-8";
private static final String BEARER_PREFIX = "Bearer ";

private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding";

private static final String GZIP_ENCODING_VALUE = "gzip";

// See https://hl7.org/fhir/smart-app-launch/conformance.html#using-well-known
@VisibleForTesting static final String WELL_KNOWN_CONF_PATH = ".well-known/smart-configuration";

Expand Down Expand Up @@ -271,6 +277,7 @@ public boolean authorizeRequest(RequestDetails requestDetails) {
return false;
}
AccessDecision outcome = checkAuthorization(requestDetails);
mutateRequest(requestDetails, outcome);
outcome.preProcess(servletDetails);
logger.debug("Authorized request path " + requestPath);
try {
Expand Down Expand Up @@ -302,15 +309,14 @@ public boolean authorizeRequest(RequestDetails requestDetails) {
proxyResponse.addHeader(header.getName(), header.getValue());
}
// This should be called after adding headers.
// TODO handle non-text responses, e.g., gzip.
// TODO verify DEFAULT_CONTENT_TYPE/CHARSET are compatible with `entity.getContentType()`.
Writer writer =
proxyResponse.getResponseWriter(
response.getStatusLine().getStatusCode(),
response.getStatusLine().toString(),
DEFAULT_CONTENT_TYPE,
Constants.CHARSET_NAME_UTF8,
false);
sendGzippedResponse(servletDetails));
Reader reader;
if (content != null) {
// We can read the entity body stream only once; in this case we have already done that.
Expand All @@ -331,6 +337,15 @@ public boolean authorizeRequest(RequestDetails requestDetails) {
return false;
}

private boolean sendGzippedResponse(ServletRequestDetails requestDetails) {
// we send gzipped encoded response to client only if they requested so
String acceptEncodingValue = requestDetails.getHeader(ACCEPT_ENCODING_HEADER.toLowerCase());
if (acceptEncodingValue == null) {
return false;
}
return GZIP_ENCODING_VALUE.equalsIgnoreCase(acceptEncodingValue);
}

/**
* Reads the content from the FHIR store response `entity`, replaces any FHIR store URLs by the
* corresponding proxy URLs, and write the modified response to the proxy response `writer`.
Expand Down Expand Up @@ -367,6 +382,7 @@ private void replaceAndCopyResponse(Reader entityContentReader, Writer writer, S
// Handle any remaining characters that partially matched.
writer.write(fhirStoreUrl.substring(0, numMatched));
}
writer.close();
}

private void serveWellKnown(ServletRequestDetails request) {
Expand All @@ -386,10 +402,24 @@ private void serveWellKnown(ServletRequestDetails request) {
Constants.CHARSET_NAME_UTF8,
false);
writer.write(configJson);
writer.close();
} catch (IOException e) {
logger.error(
String.format("Exception serving %s with error %s", request.getRequestPath(), e));
ExceptionUtil.throwRuntimeExceptionAndLog(logger, e.getMessage(), e);
}
}

@VisibleForTesting
void mutateRequest(RequestDetails requestDetails, AccessDecision accessDecision) {
RequestMutation mutation =
accessDecision.getRequestMutation(new RequestDetailsToReader(requestDetails));
if (mutation == null || CollectionUtils.isEmpty(mutation.getQueryParams())) {
return;
}

mutation
.getQueryParams()
.forEach((key, value) -> requestDetails.addParameter(key, value.toArray(new String[0])));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.google.common.base.Preconditions;
import com.google.common.io.CharStreams;
import com.google.fhir.gateway.interfaces.AccessDecision;
import com.google.fhir.gateway.interfaces.RequestDetailsReader;
import com.google.fhir.gateway.interfaces.RequestMutation;
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.hl7.fhir.instance.model.api.IBaseResource;
Expand Down Expand Up @@ -53,6 +55,11 @@ static synchronized CapabilityPostProcessor getInstance(FhirContext fhirContext)
return instance;
}

@Override
public RequestMutation getRequestMutation(RequestDetailsReader requestDetailsReader) {
return null;
}

@Override
public boolean canAccess() {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public abstract class HttpFhirClient {
"date",
"expires",
"content-location",
"content-encoding",
"etag",
"location",
"x-progress",
Expand All @@ -67,6 +68,7 @@ public abstract class HttpFhirClient {
static final Set<String> REQUEST_HEADERS_TO_KEEP =
Sets.newHashSet(
"content-type",
"accept-encoding",
"last-modified",
"etag",
"prefer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import java.io.IOException;
import javax.annotation.Nullable;
import org.apache.http.HttpResponse;

public interface AccessDecision {
Expand All @@ -28,6 +29,20 @@ public interface AccessDecision {

void preProcess(ServletRequestDetails servletRequestDetails);

/**
* Allows the incoming request mutation based on the access decision.
*
* <p>Response is used to mutate the incoming request before executing the FHIR operation. We
* currently only support query parameters update for GET Http method. This is expected to be
* called after checking the access using @canAccess method. Mutating the request before checking
* access can have side effect of wrong access check.
*
* @param requestDetailsReader details about the resource and operation requested
* @return mutation to be applied on the incoming request or null if no mutation required
*/
@Nullable
RequestMutation getRequestMutation(RequestDetailsReader requestDetailsReader);

/**
* Depending on the outcome of the FHIR operations, this does any post-processing operations that
* are related to access policies. This is expected to be called only if the actual FHIR operation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public NoOpAccessDecision(boolean accessGranted) {
this.accessGranted = accessGranted;
}

@Override
public RequestMutation getRequestMutation(RequestDetailsReader requestDetailsReader) {
return null;
}

@Override
public boolean canAccess() {
return accessGranted;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2021-2023 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.gateway.interfaces;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Builder;
import lombok.Getter;

/** Defines mutations that can be applied to the incoming request by an {@link AccessChecker}. */
@Builder
@Getter
public class RequestMutation {

// Additional query parameters and list of values for a parameter that should be added to the
// outgoing FHIR request.
// New values overwrites the old one if there is a conflict for a request param (i.e. a returned
// parameter in RequestMutation is already present in the original request).
// Old parameter values should be explicitly retained while mutating values for that parameter.
@Builder.Default Map<String, List<String>> queryParams = new HashMap<>();
}
Loading

0 comments on commit 208f0fc

Please sign in to comment.