forked from google/fhir-gateway
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix preprocessing to add correct _tag filter
- Add random filter to users without location, team or organisation assignments - Disable modification of the complete URL and request path in ServletRequestDetails - Add tests for OpenSRPSyncAccessDecision - Fix tags
- Loading branch information
Showing
5 changed files
with
380 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,3 +21,5 @@ __pycache__/ | |
|
||
# MacOS | ||
.DS_Store | ||
|
||
out/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
plugins/src/main/java/com/google/fhir/proxy/plugin/OpenSRPAccessTestChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Copyright 2021-2022 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.proxy.plugin; | ||
|
||
import ca.uhn.fhir.context.FhirContext; | ||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.fhir.proxy.HttpFhirClient; | ||
import com.google.fhir.proxy.interfaces.AccessChecker; | ||
import com.google.fhir.proxy.interfaces.AccessCheckerFactory; | ||
import com.google.fhir.proxy.interfaces.AccessDecision; | ||
import com.google.fhir.proxy.interfaces.PatientFinder; | ||
import com.google.fhir.proxy.interfaces.RequestDetailsReader; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.inject.Named; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* For testing with OpenSRP Sync Access Decision. | ||
*/ | ||
public class OpenSRPAccessTestChecker implements AccessChecker { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(OpenSRPAccessTestChecker.class); | ||
|
||
|
||
private OpenSRPAccessTestChecker() { | ||
} | ||
|
||
/** | ||
* Inspects the given request to make sure that it is for a FHIR resource of a patient that the | ||
* current user has access too; i.e., the patient is in the patient-list associated to the user. | ||
* | ||
* @param requestDetails the original request sent to the proxy. | ||
* @return true iff patient is in the patient-list associated to the current user. | ||
*/ | ||
@Override | ||
public AccessDecision checkAccess(RequestDetailsReader requestDetails) { | ||
|
||
|
||
List<String> locationIds = new ArrayList<>(); | ||
locationIds.add("msf"); | ||
List<String> organisationIds = new ArrayList<>(); | ||
organisationIds.add("P0001"); | ||
List<String> careTeamIds = new ArrayList<>(); | ||
return new OpenSRPSyncAccessDecision(true, locationIds, careTeamIds, organisationIds); | ||
} | ||
|
||
@Named(value = "OPENSRP_TEST_ACCESS_CHECKER") | ||
public static class Factory implements AccessCheckerFactory { | ||
|
||
@VisibleForTesting static final String PATIENT_LIST_CLAIM = "patient_list"; | ||
|
||
@Override | ||
public AccessChecker create( | ||
DecodedJWT jwt, | ||
HttpFhirClient httpFhirClient, | ||
FhirContext fhirContext, | ||
PatientFinder patientFinder) { | ||
return new OpenSRPAccessTestChecker(); | ||
} | ||
} | ||
} |
137 changes: 120 additions & 17 deletions
137
plugins/src/main/java/com/google/fhir/proxy/plugin/OpenSRPSyncAccessDecision.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,162 @@ | ||
/* | ||
* Copyright 2021-2022 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.proxy.plugin; | ||
|
||
import ca.uhn.fhir.rest.api.RequestTypeEnum; | ||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; | ||
import com.google.fhir.proxy.interfaces.AccessDecision; | ||
import org.apache.commons.lang3.tuple.ImmutablePair; | ||
import org.apache.commons.lang3.tuple.Pair; | ||
import org.apache.http.HttpResponse; | ||
import org.apache.http.util.TextUtils; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class OpenSRPSyncAccessDecision implements AccessDecision { | ||
|
||
private AccessDecision accessDecision; | ||
public static final String CARE_TEAM_TAG_URL = "http://smartregister.org/fhir/care-team-tag"; | ||
|
||
public OpenSRPSyncAccessDecision(AccessDecision accessDecision) { | ||
this.accessDecision = accessDecision; | ||
public static final String LOCATION_TAG_URL = "http://smartregister.org/fhir/location-id"; | ||
|
||
public static final String ORGANISATION_TAG_URL = "http://smartregister.org/organisation-tag"; | ||
|
||
public static final String SEARCH_PARAM_TAG = "_tag"; | ||
|
||
private boolean accessGranted; | ||
|
||
private List<String> careTeamIds; | ||
|
||
private List<String> locationIds; | ||
|
||
private List<String> organizationIds; | ||
|
||
public OpenSRPSyncAccessDecision(boolean accessGranted, List<String> locationIds, List<String> careTeamIds, | ||
List<String> organizationIds) { | ||
this.accessGranted = accessGranted; | ||
this.careTeamIds = careTeamIds; | ||
this.locationIds = locationIds; | ||
this.organizationIds = organizationIds; | ||
} | ||
|
||
@Override | ||
public boolean canAccess() { | ||
return accessDecision.canAccess(); | ||
return accessGranted; | ||
} | ||
|
||
@Override | ||
public void preProcess(ServletRequestDetails servletRequestDetails) { | ||
// TODO: Disable access for a user who adds tags to organisations, locations or care teams that they do not have access to | ||
// This does not bar access to anyone who uses their own sync tags to circumvent | ||
// the filter. The aim of this feature based on scoping was to pre-filter the data for the user | ||
if (isSyncUrl(servletRequestDetails)) { | ||
addSyncTags(servletRequestDetails, getSyncTags()); | ||
// This prevents access to a user who has no location/organisation/team assigned to them | ||
if (locationIds.size() == 0 && careTeamIds.size() == 0 && organizationIds.size() == 0) { | ||
locationIds.add( | ||
"CR1bAeGgaYqIpsNkG0iidfE5WVb5BJV1yltmL4YFp3o6mxj3iJPhKh4k9ROhlyZveFC8298lYzft8SIy8yMNLl5GVWQXNRr1sSeBkP2McfFZjbMYyrxlNFOJgqvtccDKKYSwBiLHq2By5tRupHcmpIIghV7Hp39KgF4iBDNqIGMKhgOIieQwt5BRih5FgnwdHrdlK9ix"); | ||
} | ||
addSyncTags(servletRequestDetails, getSyncTags(locationIds, careTeamIds, organizationIds)); | ||
} | ||
} | ||
|
||
private void addSyncTags(ServletRequestDetails servletRequestDetails, Pair<String, Map<String, String[]>> syncTags) { | ||
String syncTagsString = getSyncTags().getKey(); | ||
if (servletRequestDetails.getParameters().size() == 0) { | ||
syncTagsString = "?" + syncTagsString; | ||
} | ||
|
||
servletRequestDetails.setCompleteUrl(servletRequestDetails.getCompleteUrl() + syncTagsString); | ||
servletRequestDetails.setRequestPath(servletRequestDetails.getRequestPath() + syncTagsString); | ||
List<String> params = new ArrayList<>(); | ||
|
||
for (Map.Entry<String, String[]> entry : syncTags.getValue().entrySet()) { | ||
String tagName = entry.getKey(); | ||
for (String tagValue : entry.getValue()) { | ||
StringBuilder sb = new StringBuilder(tagName.length() + tagValue.length() + 2); | ||
sb.append(tagName); | ||
sb.append("|"); | ||
sb.append(tagValue); | ||
params.add(sb.toString()); | ||
} | ||
} | ||
|
||
for (Map.Entry<String, String[]> entry: syncTags.getValue().entrySet()) { | ||
servletRequestDetails.addParameter(entry.getKey(), entry.getValue()); | ||
String[] prevTagFilters = servletRequestDetails.getParameters().get(SEARCH_PARAM_TAG); | ||
if (prevTagFilters != null && prevTagFilters.length > 1) { | ||
Collections.addAll(params, prevTagFilters); | ||
} | ||
|
||
servletRequestDetails.addParameter(SEARCH_PARAM_TAG, params.toArray(new String[0])); | ||
} | ||
|
||
@Override | ||
public String postProcess(HttpResponse response) throws IOException { | ||
return accessDecision.postProcess(response); | ||
return null; | ||
} | ||
|
||
private Pair<String, Map<String, String[]>> getSyncTags() { | ||
private Pair<String, Map<String, String[]>> getSyncTags(List<String> locationIds, List<String> careTeamIds, | ||
List<String> organizationIds) { | ||
StringBuilder sb = new StringBuilder(); | ||
Map<String, String[]> map = new HashMap<>(); | ||
|
||
sb.append(SEARCH_PARAM_TAG); | ||
sb.append("="); | ||
addTags(LOCATION_TAG_URL, locationIds, map, sb); | ||
addTags(ORGANISATION_TAG_URL, organizationIds, map, sb); | ||
addTags(CARE_TEAM_TAG_URL, careTeamIds, map, sb); | ||
|
||
return new ImmutablePair<>(sb.toString(), map); | ||
} | ||
|
||
private void addTags(String tagUrl, List<String> values, Map<String, String[]> map, StringBuilder sb) { | ||
int len = values.size(); | ||
if (len > 0) { | ||
if (sb.length() != (SEARCH_PARAM_TAG + "=").length()) { | ||
sb.append(","); | ||
} | ||
|
||
map.put(tagUrl, values.toArray(new String[0])); | ||
|
||
int i = 0; | ||
for (String tagValue : values) { | ||
sb.append(tagUrl); | ||
sb.append(":"); | ||
sb.append(tagValue); | ||
|
||
if (i != len - 1) { | ||
sb.append(","); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private boolean isSyncUrl(ServletRequestDetails servletRequestDetails) { | ||
return (servletRequestDetails.getRequestType() == RequestTypeEnum.GET && servletRequestDetails.getRestOperationType() | ||
.isTypeLevel()); | ||
if (servletRequestDetails.getRequestType() == RequestTypeEnum.GET && !TextUtils.isEmpty( | ||
servletRequestDetails.getResourceName())) { | ||
String requestPath = servletRequestDetails.getRequestPath(); | ||
return isResourceTypeRequest(requestPath.replace(servletRequestDetails.getFhirServerBase(), "")); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private boolean isResourceTypeRequest(String requestPath) { | ||
if (!TextUtils.isEmpty(requestPath)) { | ||
String[] sections = requestPath.split("/"); | ||
|
||
return sections.length == 1 || (sections.length == 2 && TextUtils.isEmpty(sections[1])); | ||
} | ||
|
||
return false; | ||
} | ||
} |
Oops, something went wrong.