Delete-expunge does not honor delete resourcesOfType permissions (#5857)
* Delete-expunge does not honor delete resourcesOfType permissions - fix
This commit is contained in:
parent
4125d8c9ad
commit
d05009e526
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 5856
|
||||||
|
title: "Previously, it was possible to execute the `$delete-expunge` operation on a resource even if the user did not
|
||||||
|
have delete permissions for the given resource type. This has been fixed."
|
|
@ -1425,12 +1425,8 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteExpungeBlocked() {
|
public void testDeleteExpunge_allResourcesPermission_forbidden() {
|
||||||
// Create Patient, and Observation that refers to it
|
createPatient();
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
|
||||||
patient.addName().setFamily("Tester").addGiven("Siobhan");
|
|
||||||
myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
// Allow any deletes, but don't allow expunge
|
// Allow any deletes, but don't allow expunge
|
||||||
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@ -1442,25 +1438,12 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
validateDeleteConditionalByUrlIsForbidden("Patient?name=Siobhan&_expunge=true");
|
||||||
myClient
|
|
||||||
.delete()
|
|
||||||
.resourceConditionalByUrl("Patient?name=Siobhan&_expunge=true")
|
|
||||||
.execute();
|
|
||||||
fail();
|
|
||||||
} catch (ForbiddenOperationException e) {
|
|
||||||
// good
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeleteExpungeAllowed() {
|
public void testDeleteExpunge_allResourcesPermission_allowed() {
|
||||||
|
createPatient();
|
||||||
// Create Patient, and Observation that refers to it
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
|
||||||
patient.addName().setFamily("Tester").addGiven("Raghad");
|
|
||||||
myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
// Allow deletes and allow expunge
|
// Allow deletes and allow expunge
|
||||||
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@ -1473,12 +1456,139 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
myClient
|
executeDeleteConditionalByUrl("Patient?name=Siobhan&_expunge=true");
|
||||||
.delete()
|
}
|
||||||
.resourceConditionalByUrl("Patient?name=Siobhan&_expunge=true")
|
|
||||||
|
private void createDeleteByTypeRule(String theType) {
|
||||||
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder().allow()
|
||||||
|
.delete()
|
||||||
|
.onExpunge()
|
||||||
|
.resourcesOfType(theType)
|
||||||
|
.withAnyId()
|
||||||
|
.andThen().build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_typePermission_allowed() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createDeleteByTypeRule("Patient");
|
||||||
|
executeDeleteConditionalByUrl("Patient?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_typePermission_forbidden() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createDeleteByTypeRule("Observation");
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Patient?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_noIdTypePermission_forbidden() {
|
||||||
|
createDeleteByTypeRule("Observation");
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Patient?_expunge=true");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createPatientCompartmentRule(IIdType theId) {
|
||||||
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder().allow()
|
||||||
|
.delete()
|
||||||
|
.onExpunge()
|
||||||
|
.allResources()
|
||||||
|
.inCompartment("Patient", theId)
|
||||||
|
.andThen().build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_compartmentPermission_allowed() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createPatientCompartmentRule(id);
|
||||||
|
executeDeleteConditionalByUrl("Patient?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_compartmentPermission_forbidden() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
IdDt compartmentId = new IdDt();
|
||||||
|
compartmentId.setParts(null, "Patient", "123", null);
|
||||||
|
createPatientCompartmentRule(compartmentId);
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Patient?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_urlWithSearchParameterCompartmentPermission_forbidden() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
IdDt compartmentId = new IdDt();
|
||||||
|
compartmentId.setParts(null, "Patient", "123", null);
|
||||||
|
createPatientCompartmentRule(compartmentId);
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Observation?_expunge=true&patient=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_multipleIdsCompartmentPermission_forbidden() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createPatientCompartmentRule(id);
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Patient?_expunge=true&_id=" + id.getIdPart() + "_id=123");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTypeInPatientCompartmentRule(IIdType theId) {
|
||||||
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder().allow()
|
||||||
|
.delete()
|
||||||
|
.onExpunge()
|
||||||
|
.resourcesOfType("Patient")
|
||||||
|
.inCompartment("Patient", theId)
|
||||||
|
.andThen().build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_typeInCompartmentPermission_allowed() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createTypeInPatientCompartmentRule(id);
|
||||||
|
executeDeleteConditionalByUrl("Patient?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeleteExpunge_typeInCompartmentPermission_forbidden() {
|
||||||
|
IIdType id = createPatient();
|
||||||
|
createTypeInPatientCompartmentRule(id);
|
||||||
|
validateDeleteConditionalByUrlIsForbidden("Observation?_expunge=true&_id=" + id.getIdPart());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateDeleteConditionalByUrlIsForbidden(String theUrl) {
|
||||||
|
try {
|
||||||
|
executeDeleteConditionalByUrl(theUrl);
|
||||||
|
fail();
|
||||||
|
} catch (ForbiddenOperationException e) {
|
||||||
|
// good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeDeleteConditionalByUrl(String theUrl) {
|
||||||
|
myClient.delete()
|
||||||
|
.resourceConditionalByUrl(theUrl)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IIdType createPatient() {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
||||||
|
patient.addName().setFamily("Tester").addGiven("Raghad");
|
||||||
|
return myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmartFilterSearchAllowed() {
|
public void testSmartFilterSearchAllowed() {
|
||||||
createObservation(withId("allowed"), withObservationCode(TermTestUtil.URL_MY_CODE_SYSTEM, "A"));
|
createObservation(withId("allowed"), withObservationCode(TermTestUtil.URL_MY_CODE_SYSTEM, "A"));
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -56,6 +57,8 @@ import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.server.interceptor.auth.AppliesTypeEnum.ALL_RESOURCES;
|
||||||
|
import static ca.uhn.fhir.rest.server.interceptor.auth.AppliesTypeEnum.TYPES;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
@ -267,13 +270,16 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
case DELETE:
|
case DELETE:
|
||||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||||
if (thePointcut == Pointcut.STORAGE_PRE_DELETE_EXPUNGE && myAppliesToDeleteExpunge) {
|
if (thePointcut == Pointcut.STORAGE_PRE_DELETE_EXPUNGE && myAppliesToDeleteExpunge) {
|
||||||
return newVerdict(
|
target.resourceType = theRequestDetails.getResourceName();
|
||||||
theOperation,
|
String[] resourceIds = theRequestDetails.getParameters().get("_id");
|
||||||
theRequestDetails,
|
if (resourceIds != null) {
|
||||||
theInputResource,
|
target.resourceIds =
|
||||||
theInputResourceId,
|
extractResourceIdsFromRequestParameters(theRequestDetails, resourceIds);
|
||||||
theOutputResource,
|
} else {
|
||||||
theRuleApplier);
|
target.setSearchParams(theRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (myAppliesToDeleteCascade != (thePointcut == Pointcut.STORAGE_CASCADE_DELETE)) {
|
if (myAppliesToDeleteCascade != (thePointcut == Pointcut.STORAGE_CASCADE_DELETE)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -431,6 +437,18 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
theRuleApplier);
|
theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<IIdType> extractResourceIdsFromRequestParameters(
|
||||||
|
RequestDetails theRequestDetails, String[] theResourceIds) {
|
||||||
|
return Arrays.stream(theResourceIds)
|
||||||
|
.map(id -> {
|
||||||
|
IIdType inputResourceId =
|
||||||
|
theRequestDetails.getFhirContext().getVersion().newIdType();
|
||||||
|
inputResourceId.setParts(null, theRequestDetails.getResourceName(), id, null);
|
||||||
|
return inputResourceId;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply any special processing logic specific to this rule.
|
* Apply any special processing logic specific to this rule.
|
||||||
* This is intended to be overridden.
|
* This is intended to be overridden.
|
||||||
|
|
Loading…
Reference in New Issue