Skip to content

Commit

Permalink
Fix preprocessing to add correct _tag filter
Browse files Browse the repository at this point in the history
- 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
ekigamba committed Nov 7, 2022
1 parent b6745a7 commit f6596f3
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 20 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ __pycache__/

# MacOS
.DS_Store

out/
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private DataAccessChecker(String applicationId, List<String> careTeamIds, List<S

@Override
public AccessDecision checkAccess(RequestDetailsReader requestDetails) {

OpenSRPSyncAccessDecision accessDecision = new OpenSRPSyncAccessDecision(false, locationIds, careTeamIds, organizationIds);
switch (requestDetails.getRequestType()) {
case GET:
// if ((requestDetails.getResourceName()).equals(PATIENT) || (requestDetails.getResourceName()).equals(ORGANIZATION) || (requestDetails.getResourceName()).equals(ORGANIZATION)) {
Expand All @@ -79,7 +79,7 @@ public AccessDecision checkAccess(RequestDetailsReader requestDetails) {

default:
// TODO handle other cases like DELETE
return NoOpAccessDecision.accessDenied();
return accessDecision;
}
}

Expand Down Expand Up @@ -169,7 +169,8 @@ private PractitionerDetails readPractitionerDetails(String keycloakUUID) {
keycloakUUID = "40353ad0-6fa0-4da3-9dd6-b2d9d5a09b6a";
Bundle practitionerDetailsBundle = client.search()
.forResource(PractitionerDetails.class)
.where(PractitionerDetails.KEYCLOAK_UUID.exactly().identifier(keycloakUUID))
// PractitionerDetails.KEYCLOAK_UUID is missing
//.where(PractitionerDetails.KEYCLOAK_UUID.exactly().identifier(keycloakUUID))
.returnBundle(Bundle.class)
.execute();

Expand Down
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();
}
}
}
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;
}
}
Loading

0 comments on commit f6596f3

Please sign in to comment.