Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FHIR Gateway Extensions enhancements #108

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions exec/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.smartregister</groupId>
<artifactId>opensrp-gateway-plugin</artifactId>
<version>2.2.3</version>
<version>2.2.4</version>
</parent>

<artifactId>exec</artifactId>
Expand Down Expand Up @@ -70,7 +70,7 @@
<dependency>
<groupId>org.smartregister</groupId>
<artifactId>plugins</artifactId>
<version>2.2.3</version>
<version>2.2.4</version>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.smartregister</groupId>
<artifactId>opensrp-gateway-plugin</artifactId>
<version>2.2.3</version>
<version>2.2.4</version>
</parent>

<artifactId>plugins</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ public List<Location> getDescendants(
allLocations.add(parentLocation);
}
if (childLocationBundle != null) {
Utils.fetchAllBundlePagesAndInject(r4FHIRClient, childLocationBundle);
childLocationBundle.getEntry().parallelStream()
.forEach(
childLocation -> {
Expand All @@ -193,24 +194,6 @@ public List<Location> getDescendants(
null,
adminLevels));
});

while (childLocationBundle.getLink(Bundle.LINK_NEXT) != null) {
childLocationBundle =
getFhirClientForR4().loadPage().next(childLocationBundle).execute();

childLocationBundle.getEntry().parallelStream()
.forEach(
childLocation -> {
Location childLocationEntity =
(Location) childLocation.getResource();
allLocations.add(childLocationEntity);
allLocations.addAll(
getDescendants(
childLocationEntity.getIdElement().getIdPart(),
null,
adminLevels));
});
}
}

return allLocations;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.smartregister.fhir.gateway.plugins;

import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -11,7 +12,6 @@
import javax.inject.Named;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CareTeam;
Expand Down Expand Up @@ -103,20 +103,22 @@
private void initSyncAccessDecision(RequestDetailsReader requestDetailsReader) {
Map<String, List<String>> syncStrategyIds;

Composition composition = fetchComposition();
String syncStrategy = readSyncStrategyFromComposition(composition);

Check warning on line 107 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L106-L107

Added lines #L106 - L107 were not covered by tests

if (CacheHelper.INSTANCE.skipCache()) {
syncStrategyIds =
getSyncStrategyIds(
jwt.getSubject(), applicationId, fhirContext, requestDetailsReader);
getSyncStrategyIds(jwt.getSubject(), syncStrategy, requestDetailsReader);

Check warning on line 111 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L111

Added line #L111 was not covered by tests
} else {
syncStrategyIds =
CacheHelper.INSTANCE.cache.get(
jwt.getSubject(),
userId ->
generateSyncStrategyIdsCacheKey(
jwt.getSubject(),

Check warning on line 116 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L115-L116

Added lines #L115 - L116 were not covered by tests
syncStrategy,
requestDetailsReader.getParameters()),

Check warning on line 118 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L118

Added line #L118 was not covered by tests
key ->
getSyncStrategyIds(
userId,
applicationId,
fhirContext,
requestDetailsReader));
jwt.getSubject(), syncStrategy, requestDetailsReader));

Check warning on line 121 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L121

Added line #L121 was not covered by tests
}

this.syncAccessDecision =
Expand All @@ -129,6 +131,38 @@
userRoles);
}

@VisibleForTesting
protected static String generateSyncStrategyIdsCacheKey(
String userId, String syncStrategy, Map<String, String[]> parameters) {

String key = null;
switch (syncStrategy) {
case Constants.SyncStrategy.RELATED_ENTITY_LOCATION:
try {

String[] syncLocations =
parameters.getOrDefault(
Constants.SYNC_LOCATIONS_SEARCH_PARAM, new String[] {});

if (syncLocations.length == 0) {
key = userId;

Check warning on line 148 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L148

Added line #L148 was not covered by tests
} else {
key = Utils.generateHash(Utils.getSortedInput(syncLocations[0], ","));
}

} catch (NoSuchAlgorithmException exception) {
logger.error(exception.getMessage());

Check warning on line 154 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L153-L154

Added lines #L153 - L154 were not covered by tests
}

break;

Check warning on line 157 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L157

Added line #L157 was not covered by tests

default:
key = userId;
}

return key;
}

private boolean checkUserHasRole(String resourceName, String requestType) {
return StringUtils.isNotBlank(resourceName)
&& (checkIfRoleExists(getAdminRoleName(resourceName), this.userRoles)
Expand Down Expand Up @@ -216,8 +250,7 @@
return compositionEntry != null ? (Composition) compositionEntry.getResource() : null;
}

Pair<Composition, PractitionerDetails> fetchCompositionAndPractitionerDetails(
String subject, String applicationId, FhirContext fhirContext) {
PractitionerDetails fetchPractitionerDetails(String subject) {
fhirContext.registerCustomType(PractitionerDetails.class);

IGenericClient client = Utils.createFhirClientForR4(fhirContext);
Expand All @@ -227,53 +260,43 @@
PractitionerDetails practitionerDetails =
practitionerDetailsEndpointHelper.getPractitionerDetailsByKeycloakId(subject);

Composition composition = readCompositionResource(applicationId, fhirContext);

if (composition == null)
throw new IllegalStateException(
"No Composition resource found for application id '" + applicationId + "'");

if (practitionerDetails == null)
throw new IllegalStateException(
"No PractitionerDetail resource found for user with id '" + subject + "'");

return Pair.of(composition, practitionerDetails);
return practitionerDetails;

Check warning on line 267 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L267

Added line #L267 was not covered by tests
}

Pair<String, PractitionerDetails> fetchSyncStrategyDetails(
String subject, String applicationId, FhirContext fhirContext) {
private Composition fetchComposition() {
Composition composition = readCompositionResource(applicationId, fhirContext);

Check warning on line 271 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L271

Added line #L271 was not covered by tests
if (composition == null)
throw new IllegalStateException(

Check warning on line 273 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L273

Added line #L273 was not covered by tests
"No Composition resource found for application id '" + applicationId + "'");

Pair<Composition, PractitionerDetails> compositionPractitionerDetailsPair =
fetchCompositionAndPractitionerDetails(subject, applicationId, fhirContext);
Composition composition = compositionPractitionerDetailsPair.getLeft();
PractitionerDetails practitionerDetails = compositionPractitionerDetailsPair.getRight();
return composition;

Check warning on line 276 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L276

Added line #L276 was not covered by tests
}

private String readSyncStrategyFromComposition(Composition composition) {
String binaryResourceReference = Utils.getBinaryResourceReference(composition);
Binary binary =
Utils.readApplicationConfigBinaryResource(binaryResourceReference, fhirContext);

return Pair.of(Utils.findSyncStrategy(binary), practitionerDetails);
return Utils.findSyncStrategy(binary);

Check warning on line 283 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L283

Added line #L283 was not covered by tests
}

private Map<String, List<String>> getSyncStrategyIds(
String subjectId,
String applicationId,
FhirContext fhirContext,
RequestDetailsReader requestDetailsReader) {
Pair<String, PractitionerDetails> syncStrategyDetails =
fetchSyncStrategyDetails(subjectId, applicationId, fhirContext);
String subjectId, String syncStrategy, RequestDetailsReader requestDetailsReader) {

String syncStrategy = syncStrategyDetails.getLeft();
PractitionerDetails practitionerDetails = syncStrategyDetails.getRight();
PractitionerDetails practitionerDetails = fetchPractitionerDetails(subjectId);

Check warning on line 289 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L289

Added line #L289 was not covered by tests

return collateSyncStrategyIds(syncStrategy, practitionerDetails, requestDetailsReader);
}

private List<String> getLocationUuids(String[] syncLocations) {
List<String> locationUuids = new ArrayList<>();
String syncLocationParam;
for (int i = 0; i < syncLocations.length; i++) {
syncLocationParam = syncLocations[i];

for (String syncLocation : syncLocations) {
syncLocationParam = syncLocation;

Check warning on line 299 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L299

Added line #L299 was not covered by tests
if (!syncLocationParam.isEmpty())
locationUuids.addAll(
Set.of(syncLocationParam.split(Constants.PARAM_VALUES_SEPARATOR)));
Expand Down Expand Up @@ -353,32 +376,17 @@
&& practitionerDetails.getFhirPractitionerDetails()
!= null
? PractitionerDetailsEndpointHelper.getAttributedLocations(
PractitionerDetailsEndpointHelper.getLocationsHierarchy(
practitionerDetails
.getFhirPractitionerDetails()
.getLocations()
.stream()
.map(
location ->
location.getIdElement()
.getIdPart())
.collect(Collectors.toList())))
practitionerDetails
.getFhirPractitionerDetails()
.getLocationHierarchyList())

Check warning on line 381 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L380-L381

Added lines #L380 - L381 were not covered by tests
: new HashSet<>();
}

} else
throw new IllegalStateException(
"'" + syncStrategy + "' sync strategy NOT supported!!");

resultMap =
!syncStrategyIds.isEmpty()
? Map.of(syncStrategy, new ArrayList<>(syncStrategyIds))
: null;

if (resultMap == null) {
throw new IllegalStateException(
"No Sync strategy ids found for selected sync strategy " + syncStrategy);
}
resultMap = Map.of(syncStrategy, new ArrayList<>(syncStrategyIds));

Check warning on line 389 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/PermissionAccessChecker.java#L389

Added line #L389 was not covered by tests

} else
throw new IllegalStateException(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
package org.smartregister.fhir.gateway.plugins;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.codec.binary.Hex;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.impl.GenericClient;

public class Utils {

Expand Down Expand Up @@ -193,4 +203,84 @@

return syncStrategy;
}

public static String generateHash(String input) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest(input.getBytes());
return Hex.encodeHexString(hashBytes);
}

public static String getSortedInput(String input, String separator) {
return getSortedInput(Arrays.stream(input.split(separator)), separator);

Check warning on line 214 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java#L214

Added line #L214 was not covered by tests
}

public static String getSortedInput(Stream<String> inputStream, String separator) {
return inputStream.sorted(Comparator.naturalOrder()).collect(Collectors.joining(separator));

Check warning on line 218 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java#L218

Added line #L218 was not covered by tests
}

/**
* This is a recursive function which updates the result bundle with results of all pages
* whenever there's an entry for Bundle.LINK_NEXT
*
* @param fhirClient the Generic FHIR Client instance
* @param resultBundle the result bundle from the first request
*/
public static void fetchAllBundlePagesAndInject(
IGenericClient fhirClient, Bundle resultBundle) {

if (resultBundle.getLink(Bundle.LINK_NEXT) != null) {

cleanUpBundlePaginationNextLinkServerBaseUrl((GenericClient) fhirClient, resultBundle);

Bundle pageResultBundle = fhirClient.loadPage().next(resultBundle).execute();

resultBundle.getEntry().addAll(pageResultBundle.getEntry());
resultBundle.setLink(pageResultBundle.getLink());

fetchAllBundlePagesAndInject(fhirClient, resultBundle);
}

resultBundle.setLink(
resultBundle.getLink().stream()
.filter(
bundleLinkComponent ->
!Bundle.LINK_NEXT.equals(bundleLinkComponent.getRelation()))
.collect(Collectors.toList()));
resultBundle.getMeta().setLastUpdated(resultBundle.getMeta().getLastUpdated());
}

public static void cleanUpBundlePaginationNextLinkServerBaseUrl(
GenericClient fhirClient, Bundle resultBundle) {
String cleanUrl =
cleanHapiPaginationLinkBaseUrl(
resultBundle.getLink(Bundle.LINK_NEXT).getUrl(), fhirClient.getUrlBase());
resultBundle
.getLink()
.replaceAll(
bundleLinkComponent ->
Bundle.LINK_NEXT.equals(bundleLinkComponent.getRelation())
? new Bundle.BundleLinkComponent(
new StringType(Bundle.LINK_NEXT),
new UriType(cleanUrl))
: bundleLinkComponent);

Check warning on line 265 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java#L265

Added line #L265 was not covered by tests
}

public static String cleanBaseUrl(String originalUrl, String fhirServerBaseUrl) {
int hostStartIndex = originalUrl.indexOf("://") + 3;
int pathStartIndex = originalUrl.indexOf("/", hostStartIndex);

// If the URL has no path, assume it ends right after the host
if (pathStartIndex == -1) {
pathStartIndex = originalUrl.length();

Check warning on line 274 in plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java

View check run for this annotation

Codecov / codecov/patch

plugins/src/main/java/org/smartregister/fhir/gateway/plugins/Utils.java#L274

Added line #L274 was not covered by tests
}

return fhirServerBaseUrl + originalUrl.substring(pathStartIndex);
}

public static String cleanHapiPaginationLinkBaseUrl(
String originalUrl, String fhirServerBaseUrl) {
return originalUrl.indexOf('?') > -1
? fhirServerBaseUrl + originalUrl.substring(originalUrl.indexOf('?'))
: fhirServerBaseUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -451,4 +451,16 @@ public void testAccessDeniedWhenSingleRoleMissingForTypeBundleResources() throws

assertThat(canAccess, equalTo(false));
}

@Test
public void testGenerateSyncStrategyIdsCacheKey() {
String testUserId = "my-test-user-id";
Map<String, String[]> strategyIdMap =
Map.of(Constants.SyncStrategy.CARE_TEAM, new String[] {"id-1, id-2,id-3"});
String cacheKey =
PermissionAccessChecker.generateSyncStrategyIdsCacheKey(
testUserId, Constants.SyncStrategy.CARE_TEAM, strategyIdMap);

Assert.assertEquals(testUserId, cacheKey);
}
}
Loading
Loading