This reverts commit 2b3858572d
.
This commit is contained in:
parent
2b3858572d
commit
422d4f0741
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
type: fix
|
|
||||||
issue: 6258
|
|
||||||
title: "The AuthorizationInterceptor handling for operations has been improved
|
|
||||||
so that operation rules now directly test the contents of response Bundle
|
|
||||||
or Parameters objects returned by the operation when configure to require
|
|
||||||
explicit response authorization. This fixes a regression in 7.4.0 where
|
|
||||||
operation responses could sometimes be denied even if appropriate
|
|
||||||
permissions were granted to view resources in a response bundle. Thanks to
|
|
||||||
Gijsbert van den Brink for reporting the issue with a sample test!"
|
|
|
@ -20,15 +20,11 @@ import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||||
import ca.uhn.fhir.rest.gclient.IOperation;
|
|
||||||
import ca.uhn.fhir.rest.gclient.IOperationUnnamed;
|
|
||||||
import ca.uhn.fhir.rest.gclient.IOperationUntypedWithInput;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleBuilder;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester;
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||||
|
@ -72,7 +68,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
import org.junit.jupiter.params.provider.CsvSource;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.junit.jupiter.params.provider.NullSource;
|
import org.junit.jupiter.params.provider.NullSource;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
@ -902,17 +897,8 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@CsvSource({
|
public void testDiffOperation_AllowedByType_Instance() {
|
||||||
// ResourceId , RequireExplicitResponseAuthorization , ShouldSucceed
|
|
||||||
"Patient/A , true , true",
|
|
||||||
"Patient/A/_history/2 , true , true",
|
|
||||||
"Observation/B , true , false",
|
|
||||||
"Patient/A , false , true",
|
|
||||||
"Patient/A/_history/2 , false , true",
|
|
||||||
"Observation/B , false , true"
|
|
||||||
})
|
|
||||||
public void testDiffOperation_AllowedByType_Instance(String theResourceId, boolean theRequireExplicitResponseAuthorization, boolean theShouldSucceed) {
|
|
||||||
createPatient(withId("A"), withActiveTrue());
|
createPatient(withId("A"), withActiveTrue());
|
||||||
createPatient(withId("A"), withActiveFalse());
|
createPatient(withId("A"), withActiveFalse());
|
||||||
createObservation(withId("B"), withStatus("final"));
|
createObservation(withId("B"), withStatus("final"));
|
||||||
|
@ -920,42 +906,30 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
@Override
|
@Override
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
RuleBuilder ruleBuilder = new RuleBuilder();
|
return new RuleBuilder()
|
||||||
if (theRequireExplicitResponseAuthorization) {
|
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onAnyInstance().andAllowAllResponses().andThen()
|
||||||
ruleBuilder.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onAnyInstance().andRequireExplicitResponseAuthorization().andThen();
|
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onServer().andAllowAllResponses().andThen()
|
||||||
ruleBuilder.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onServer().andRequireExplicitResponseAuthorization().andThen();
|
.allow().read().resourcesOfType(Patient.class).withAnyId().andThen()
|
||||||
} else {
|
.denyAll()
|
||||||
ruleBuilder.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onAnyInstance().andAllowAllResponses().andThen();
|
.build();
|
||||||
ruleBuilder.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onServer().andAllowAllResponses().andThen();
|
|
||||||
}
|
|
||||||
ruleBuilder.allow().read().resourcesOfType(Patient.class).withAnyId().andThen();
|
|
||||||
ruleBuilder.denyAll();
|
|
||||||
return ruleBuilder.build();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Parameters diff;
|
Parameters diff;
|
||||||
|
|
||||||
IOperation operation = myClient.operation();
|
diff = myClient.operation().onInstance("Patient/A").named(ProviderConstants.DIFF_OPERATION_NAME).withNoParameters(Parameters.class).execute();
|
||||||
IOperationUnnamed target;
|
|
||||||
if (theResourceId.contains("_history")) {
|
|
||||||
target = operation.onInstanceVersion(new IdType(theResourceId));
|
|
||||||
} else {
|
|
||||||
target = operation.onInstance(theResourceId);
|
|
||||||
}
|
|
||||||
IOperationUntypedWithInput<Parameters> executable = target.named(ProviderConstants.DIFF_OPERATION_NAME).withNoParameters(Parameters.class);
|
|
||||||
|
|
||||||
if (theShouldSucceed) {
|
|
||||||
diff = executable.execute();
|
|
||||||
assertThat(diff.getParameter()).hasSize(1);
|
assertThat(diff.getParameter()).hasSize(1);
|
||||||
} else {
|
|
||||||
|
diff = myClient.operation().onInstanceVersion(new IdType("Patient/A/_history/2")).named(ProviderConstants.DIFF_OPERATION_NAME).withNoParameters(Parameters.class).execute();
|
||||||
|
assertThat(diff.getParameter()).hasSize(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
executable.execute();
|
myClient.operation().onInstance("Observation/B").named(ProviderConstants.DIFF_OPERATION_NAME).withNoParameters(Parameters.class).execute();
|
||||||
fail();
|
fail();
|
||||||
} catch (ForbiddenOperationException e) {
|
} catch (ForbiddenOperationException e) {
|
||||||
// good
|
// good
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -969,8 +943,8 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
@Override
|
@Override
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
return new RuleBuilder()
|
return new RuleBuilder()
|
||||||
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onAnyInstance().andRequireExplicitResponseAuthorization().andThen()
|
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onAnyInstance().andAllowAllResponses().andThen()
|
||||||
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onServer().andRequireExplicitResponseAuthorization().andThen()
|
.allow().operation().named(ProviderConstants.DIFF_OPERATION_NAME).onServer().andAllowAllResponses().andThen()
|
||||||
.allow().read().resourcesOfType(Patient.class).withAnyId().andThen()
|
.allow().read().resourcesOfType(Patient.class).withAnyId().andThen()
|
||||||
.denyAll()
|
.denyAll()
|
||||||
.build();
|
.build();
|
||||||
|
@ -1003,72 +977,6 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDocumentOperation_withExplicitAuthorization() {
|
|
||||||
IIdType patientId = createPatient();
|
|
||||||
IIdType compositionId = createResource("Composition", withSubject(patientId));
|
|
||||||
|
|
||||||
AuthorizationInterceptor interceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
|
||||||
@Override
|
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
|
||||||
return new RuleBuilder()
|
|
||||||
.allow().read().instance(patientId).andThen()
|
|
||||||
.allow().operation().named("$document").onInstance(compositionId).andRequireExplicitResponseAuthorization().andThen()
|
|
||||||
.allow().read().instance(compositionId).andThen()
|
|
||||||
.denyAll()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
||||||
|
|
||||||
Bundle bundle = myClient.operation().onInstanceVersion(compositionId).named("$document").withNoParameters(Parameters.class).returnResourceType(Bundle.class).execute();
|
|
||||||
assertEquals(2, bundle.getEntry().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDocumentOperation_explicitAuthorizationNotNeeded() {
|
|
||||||
IIdType patientId = createPatient();
|
|
||||||
IIdType compositionId = createResource("Composition", withSubject(patientId));
|
|
||||||
|
|
||||||
AuthorizationInterceptor interceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
|
||||||
@Override
|
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
|
||||||
return new RuleBuilder()
|
|
||||||
.allow().operation().named("$document").onInstance(compositionId).andAllowAllResponses().andThen()
|
|
||||||
.denyAll()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
||||||
|
|
||||||
Bundle bundle = myClient.operation().onInstanceVersion(compositionId).named("$document").withNoParameters(Parameters.class).returnResourceType(Bundle.class).execute();
|
|
||||||
assertEquals(2, bundle.getEntry().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDocumentOperation_withoutExplicitAuthorization() {
|
|
||||||
IIdType patientId = createPatient();
|
|
||||||
IIdType compositionId = createResource("Composition", withSubject(patientId));
|
|
||||||
|
|
||||||
AuthorizationInterceptor interceptor = new AuthorizationInterceptor(PolicyEnum.DENY) {
|
|
||||||
@Override
|
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
|
||||||
return new RuleBuilder()
|
|
||||||
.allow().operation().named("$document").onInstance(compositionId).andRequireExplicitResponseAuthorization().andThen()
|
|
||||||
.allow().read().instance(compositionId).andThen()
|
|
||||||
.denyAll()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
myServer.getRestfulServer().registerInterceptor(interceptor);
|
|
||||||
|
|
||||||
try {
|
|
||||||
myClient.operation().onInstanceVersion(compositionId).named("$document").withNoParameters(Parameters.class).returnResourceType(Bundle.class).execute();
|
|
||||||
fail();
|
|
||||||
} catch (ForbiddenOperationException e) {
|
|
||||||
// good
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGraphQL_AllowedByType_Instance() throws IOException {
|
public void testGraphQL_AllowedByType_Instance() throws IOException {
|
||||||
|
@ -1297,17 +1205,8 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
|
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@CsvSource({
|
public void testOperationEverything_SomeIncludedResourcesNotAuthorized() {
|
||||||
// RequireExplicitResponseAuthorization , AddNotExplicitlyAuthorizedResources , ShouldSucceed
|
|
||||||
"true , false , true",
|
|
||||||
"true , true , false",
|
|
||||||
"false , false , true",
|
|
||||||
"false , true , true",
|
|
||||||
})
|
|
||||||
public void testOperationEverything_SomeIncludedResourcesNotAuthorized(boolean theRequireExplicitResponseAuthorization, boolean theAddNotExplicitlyAuthorizedResources, boolean theShouldSucceed) {
|
|
||||||
int expectedCount = 2;
|
|
||||||
|
|
||||||
Patient pt1 = new Patient();
|
Patient pt1 = new Patient();
|
||||||
pt1.setActive(true);
|
pt1.setActive(true);
|
||||||
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();
|
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();
|
||||||
|
@ -1317,59 +1216,47 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
obs1.setSubject(new Reference(pid1));
|
obs1.setSubject(new Reference(pid1));
|
||||||
myClient.create().resource(obs1).execute();
|
myClient.create().resource(obs1).execute();
|
||||||
|
|
||||||
if (theAddNotExplicitlyAuthorizedResources) {
|
|
||||||
// Add an Encounter, which will be returned by $everything but that hasn't been
|
|
||||||
// explicitly authorized
|
|
||||||
Encounter enc = new Encounter();
|
|
||||||
enc.setSubject(new Reference(pid1));
|
|
||||||
myClient.create().resource(enc).execute();
|
|
||||||
|
|
||||||
Organization org = new Organization();
|
|
||||||
org.setName("Hello");
|
|
||||||
IIdType orgId = myClient.create().resource(org).execute().getId().toUnqualifiedVersionless();
|
|
||||||
expectedCount++;
|
|
||||||
|
|
||||||
pt1.setId(pid1);
|
|
||||||
pt1.setManagingOrganization(new Reference(orgId));
|
|
||||||
myClient.update().resource(pt1).execute();
|
|
||||||
expectedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
myServer.getRestfulServer().registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
@Override
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
IAuthRuleBuilder ruleBuilder = new RuleBuilder();
|
return new RuleBuilder()
|
||||||
if (theRequireExplicitResponseAuthorization) {
|
.allow().operation().named(JpaConstants.OPERATION_EVERYTHING).onInstance(pid1).andRequireExplicitResponseAuthorization().andThen()
|
||||||
ruleBuilder.allow().operation().named(JpaConstants.OPERATION_EVERYTHING).onInstance(pid1).andRequireExplicitResponseAuthorization().andThen();
|
.allow().read().resourcesOfType(Patient.class).inCompartment("Patient", pid1).andThen()
|
||||||
} else {
|
.allow().read().resourcesOfType(Observation.class).inCompartment("Patient", pid1).andThen()
|
||||||
ruleBuilder.allow().operation().named(JpaConstants.OPERATION_EVERYTHING).onInstance(pid1).andAllowAllResponsesWithAllResourcesAccess().andThen();
|
.allow().create().resourcesOfType(Encounter.class).withAnyId().andThen()
|
||||||
}
|
.build();
|
||||||
ruleBuilder.allow().read().resourcesOfType(Patient.class).inCompartment("Patient", pid1).andThen();
|
|
||||||
ruleBuilder.allow().read().resourcesOfType(Observation.class).inCompartment("Patient", pid1).andThen();
|
|
||||||
return ruleBuilder.build();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
IOperationUntypedWithInput<Bundle> executable = myClient
|
Bundle outcome = myClient
|
||||||
.operation()
|
.operation()
|
||||||
.onInstance(pid1)
|
.onInstance(pid1)
|
||||||
.named(JpaConstants.OPERATION_EVERYTHING)
|
.named(JpaConstants.OPERATION_EVERYTHING)
|
||||||
.withNoParameters(Parameters.class)
|
.withNoParameters(Parameters.class)
|
||||||
.returnResourceType(Bundle.class);
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
assertThat(outcome.getEntry()).hasSize(2);
|
||||||
|
|
||||||
|
// Add an Encounter, which will be returned by $everything but that hasn't been
|
||||||
|
// explicitly authorized
|
||||||
|
|
||||||
|
Encounter enc = new Encounter();
|
||||||
|
enc.setSubject(new Reference(pid1));
|
||||||
|
myClient.create().resource(enc).execute();
|
||||||
|
|
||||||
if (theShouldSucceed) {
|
|
||||||
Bundle outcome = executable.execute();
|
|
||||||
assertThat(outcome.getEntry()).hasSize(expectedCount);
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
executable.execute();
|
myClient
|
||||||
|
.operation()
|
||||||
|
.onInstance(pid1)
|
||||||
|
.named(JpaConstants.OPERATION_EVERYTHING)
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
fail();
|
fail();
|
||||||
} catch (ForbiddenOperationException e) {
|
} catch (ForbiddenOperationException e) {
|
||||||
assertThat(e.getMessage()).contains("Access denied by default policy");
|
assertThat(e.getMessage()).contains("Access denied by default policy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -2142,7 +2029,6 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes
|
||||||
super(theResourceTypes);
|
super(theResourceTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
List<IAuthRule> rules = new ArrayList<>(super.buildRuleList(theRequestDetails));
|
List<IAuthRule> rules = new ArrayList<>(super.buildRuleList(theRequestDetails));
|
||||||
List<IAuthRule> rulesToAdd = new RuleBuilder().allow().transaction().withAnyOperation().andApplyNormalRules().build();
|
List<IAuthRule> rulesToAdd = new RuleBuilder().allow().transaction().withAnyOperation().andApplyNormalRules().build();
|
||||||
|
|
|
@ -164,8 +164,7 @@ public class AuthorizationInterceptor implements IRuleApplier {
|
||||||
rules.size(),
|
rules.size(),
|
||||||
getPointcutNameOrEmpty(thePointcut),
|
getPointcutNameOrEmpty(thePointcut),
|
||||||
getResourceTypeOrEmpty(theInputResource),
|
getResourceTypeOrEmpty(theInputResource),
|
||||||
getResourceTypeOrEmpty(theOutputResource),
|
getResourceTypeOrEmpty(theOutputResource));
|
||||||
thePointcut);
|
|
||||||
|
|
||||||
Verdict verdict = null;
|
Verdict verdict = null;
|
||||||
for (IAuthRule nextRule : rules) {
|
for (IAuthRule nextRule : rules) {
|
||||||
|
@ -529,7 +528,7 @@ public class AuthorizationInterceptor implements IRuleApplier {
|
||||||
case EXTENDED_OPERATION_TYPE:
|
case EXTENDED_OPERATION_TYPE:
|
||||||
case EXTENDED_OPERATION_INSTANCE: {
|
case EXTENDED_OPERATION_INSTANCE: {
|
||||||
if (theResponseObject != null) {
|
if (theResponseObject != null) {
|
||||||
resources = toListOfResourcesAndExcludeContainerUnlessStandalone(theResponseObject, fhirContext);
|
resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -576,7 +575,7 @@ public class AuthorizationInterceptor implements IRuleApplier {
|
||||||
OUT,
|
OUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static List<IBaseResource> toListOfResourcesAndExcludeContainerUnlessStandalone(
|
protected static List<IBaseResource> toListOfResourcesAndExcludeContainer(
|
||||||
IBaseResource theResponseObject, FhirContext fhirContext) {
|
IBaseResource theResponseObject, FhirContext fhirContext) {
|
||||||
if (theResponseObject == null) {
|
if (theResponseObject == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
@ -589,13 +588,6 @@ public class AuthorizationInterceptor implements IRuleApplier {
|
||||||
return Collections.singletonList(theResponseObject);
|
return Collections.singletonList(theResponseObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
return toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static List<IBaseResource> toListOfResourcesAndExcludeContainer(
|
|
||||||
IBaseResource theResponseObject, FhirContext fhirContext) {
|
|
||||||
List<IBaseResource> retVal;
|
|
||||||
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
|
retVal = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
|
||||||
|
|
||||||
// Exclude the container
|
// Exclude the container
|
||||||
|
|
|
@ -27,9 +27,11 @@ public interface IAuthRuleBuilderOperationNamedAndScoped {
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses();
|
IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated This is a synonym for {@link #andAllowAllResponses()}, use that method instead
|
* Responses for this operation will not be checked and access to all resources is allowed. This
|
||||||
|
* is intended for operations which are known to fetch a graph of resources that is known to be
|
||||||
|
* safe, such as `$everything` which may access and fetch resources outside the patient's compartment
|
||||||
|
* but enforces safety in what it fetches via strict SQL queries.
|
||||||
*/
|
*/
|
||||||
@Deprecated(since = "7.6.0")
|
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponsesWithAllResourcesAccess();
|
IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponsesWithAllResourcesAccess();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -85,7 +85,6 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
myAppliesToTypes = theAppliesToTypes;
|
myAppliesToTypes = theAppliesToTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
|
||||||
@Override
|
@Override
|
||||||
public Verdict applyRule(
|
public Verdict applyRule(
|
||||||
RestOperationTypeEnum theOperation,
|
RestOperationTypeEnum theOperation,
|
||||||
|
@ -98,8 +97,23 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
Pointcut thePointcut) {
|
Pointcut thePointcut) {
|
||||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||||
|
|
||||||
|
// Operation rules apply to the execution of the operation itself, not to side effects like
|
||||||
|
// loading resources (that will presumably be reflected in the response). Those loads need
|
||||||
|
// to be explicitly authorized
|
||||||
|
if (!myAllowAllResourcesAccess && isResourceAccess(thePointcut)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
boolean applies = false;
|
boolean applies = false;
|
||||||
switch (theOperation) {
|
switch (theOperation) {
|
||||||
|
case ADD_TAGS:
|
||||||
|
case DELETE_TAGS:
|
||||||
|
case GET_TAGS:
|
||||||
|
case GET_PAGE:
|
||||||
|
case GRAPHQL_REQUEST:
|
||||||
|
// These things can't be tracked by the AuthorizationInterceptor
|
||||||
|
// at this time
|
||||||
|
return null;
|
||||||
case EXTENDED_OPERATION_SERVER:
|
case EXTENDED_OPERATION_SERVER:
|
||||||
if (myAppliesToServer || myAppliesAtAnyLevel) {
|
if (myAppliesToServer || myAppliesAtAnyLevel) {
|
||||||
applies = true;
|
applies = true;
|
||||||
|
@ -124,7 +138,10 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
applies = true;
|
applies = true;
|
||||||
} else {
|
} else {
|
||||||
IIdType requestResourceId = null;
|
IIdType requestResourceId = null;
|
||||||
if (theRequestDetails.getId() != null) {
|
if (theInputResourceId != null) {
|
||||||
|
requestResourceId = theInputResourceId;
|
||||||
|
}
|
||||||
|
if (requestResourceId == null && myAllowAllResponses) {
|
||||||
requestResourceId = theRequestDetails.getId();
|
requestResourceId = theRequestDetails.getId();
|
||||||
}
|
}
|
||||||
if (requestResourceId != null) {
|
if (requestResourceId != null) {
|
||||||
|
@ -151,6 +168,40 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case CREATE:
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
break;
|
||||||
|
case HISTORY_INSTANCE:
|
||||||
|
break;
|
||||||
|
case HISTORY_SYSTEM:
|
||||||
|
break;
|
||||||
|
case HISTORY_TYPE:
|
||||||
|
break;
|
||||||
|
case READ:
|
||||||
|
break;
|
||||||
|
case SEARCH_SYSTEM:
|
||||||
|
break;
|
||||||
|
case SEARCH_TYPE:
|
||||||
|
break;
|
||||||
|
case TRANSACTION:
|
||||||
|
break;
|
||||||
|
case UPDATE:
|
||||||
|
break;
|
||||||
|
case VALIDATE:
|
||||||
|
break;
|
||||||
|
case VREAD:
|
||||||
|
break;
|
||||||
|
case METADATA:
|
||||||
|
break;
|
||||||
|
case META_ADD:
|
||||||
|
break;
|
||||||
|
case META:
|
||||||
|
break;
|
||||||
|
case META_DELETE:
|
||||||
|
break;
|
||||||
|
case PATCH:
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -163,8 +214,6 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theOutputResource == null) {
|
|
||||||
// This is the request part
|
|
||||||
return newVerdict(
|
return newVerdict(
|
||||||
theOperation,
|
theOperation,
|
||||||
theRequestDetails,
|
theRequestDetails,
|
||||||
|
@ -172,24 +221,6 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
theInputResourceId,
|
theInputResourceId,
|
||||||
theOutputResource,
|
theOutputResource,
|
||||||
theRuleApplier);
|
theRuleApplier);
|
||||||
} else {
|
|
||||||
// This is the response part, so we might want to check all of the
|
|
||||||
// resources in the response
|
|
||||||
if (myAllowAllResponses) {
|
|
||||||
return newVerdict(
|
|
||||||
theOperation,
|
|
||||||
theRequestDetails,
|
|
||||||
theInputResource,
|
|
||||||
theInputResourceId,
|
|
||||||
theOutputResource,
|
|
||||||
theRuleApplier);
|
|
||||||
} else {
|
|
||||||
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(
|
|
||||||
theOutputResource, theRequestDetails.getFhirContext());
|
|
||||||
return RuleImplOp.applyRulesToResponseResources(
|
|
||||||
theRequestDetails, theRuleApplier, thePointcut, outputResources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -828,30 +828,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
|
|
||||||
return verdict;
|
return verdict;
|
||||||
} else if (theOutputResource != null) {
|
} else if (theOutputResource != null) {
|
||||||
return applyRulesToResponseBundle(theRequestDetails, theOutputResource, theRuleApplier, thePointcut);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
List<IBaseResource> outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(
|
||||||
private static Verdict applyRulesToResponseBundle(
|
|
||||||
RequestDetails theRequestDetails,
|
|
||||||
IBaseResource theOutputResource,
|
|
||||||
IRuleApplier theRuleApplier,
|
|
||||||
Pointcut thePointcut) {
|
|
||||||
List<IBaseResource> outputResources =
|
|
||||||
AuthorizationInterceptor.toListOfResourcesAndExcludeContainerUnlessStandalone(
|
|
||||||
theOutputResource, theRequestDetails.getFhirContext());
|
theOutputResource, theRequestDetails.getFhirContext());
|
||||||
return applyRulesToResponseResources(theRequestDetails, theRuleApplier, thePointcut, outputResources);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static Verdict applyRulesToResponseResources(
|
|
||||||
RequestDetails theRequestDetails,
|
|
||||||
IRuleApplier theRuleApplier,
|
|
||||||
Pointcut thePointcut,
|
|
||||||
List<IBaseResource> outputResources) {
|
|
||||||
Verdict verdict = null;
|
Verdict verdict = null;
|
||||||
for (IBaseResource nextResource : outputResources) {
|
for (IBaseResource nextResource : outputResources) {
|
||||||
if (nextResource == null) {
|
if (nextResource == null) {
|
||||||
|
@ -868,6 +848,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return verdict;
|
return verdict;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInvalidNestedBundleRequest(BundleEntryParts theEntry) {
|
private boolean isInvalidNestedBundleRequest(BundleEntryParts theEntry) {
|
||||||
|
|
|
@ -4230,7 +4230,7 @@ public class AuthorizationInterceptorR4Test extends BaseValidationTestWithInline
|
||||||
RequestDetails requestDetails = new SystemRequestDetails();
|
RequestDetails requestDetails = new SystemRequestDetails();
|
||||||
requestDetails.setResourceName("Bundle");
|
requestDetails.setResourceName("Bundle");
|
||||||
|
|
||||||
List<IBaseResource> resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainerUnlessStandalone(searchSet, ourCtx);
|
List<IBaseResource> resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(searchSet, ourCtx);
|
||||||
assertEquals(1, resources.size());
|
assertEquals(1, resources.size());
|
||||||
assertTrue(resources.contains(bundle));
|
assertTrue(resources.contains(bundle));
|
||||||
}
|
}
|
||||||
|
@ -4247,7 +4247,7 @@ public class AuthorizationInterceptorR4Test extends BaseValidationTestWithInline
|
||||||
RequestDetails requestDetails = new SystemRequestDetails();
|
RequestDetails requestDetails = new SystemRequestDetails();
|
||||||
requestDetails.setResourceName("Patient");
|
requestDetails.setResourceName("Patient");
|
||||||
|
|
||||||
List<IBaseResource> resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainerUnlessStandalone(searchSet, ourCtx);
|
List<IBaseResource> resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(searchSet, ourCtx);
|
||||||
assertEquals(2, resources.size());
|
assertEquals(2, resources.size());
|
||||||
assertTrue(resources.contains(patient1));
|
assertTrue(resources.contains(patient1));
|
||||||
assertTrue(resources.contains(patient2));
|
assertTrue(resources.contains(patient2));
|
||||||
|
|
Loading…
Reference in New Issue