AuthorizationInterceptor enhancements

This commit is contained in:
James 2016-11-04 07:43:22 -04:00
parent 14f47b2048
commit af8ae69ee4
5 changed files with 198 additions and 29 deletions

View File

@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -138,6 +139,8 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
case EXTENDED_OPERATION_INSTANCE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
return OperationExamineDirection.BOTH;
case METADATA:
// Security does not apply to these operations
return OperationExamineDirection.IN;
@ -270,10 +273,18 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case TRANSACTION:{
case TRANSACTION:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
case EXTENDED_OPERATION_INSTANCE: {
if (theResponseObject != null) {
IBaseBundle responseBundle = (IBaseBundle) theResponseObject;
resources = toListOfResources(fhirContext, responseBundle);
if (theResponseObject instanceof IBaseBundle) {
// IBaseBundle responseBundle = (IBaseBundle) theResponseObject;
// resources = toListOfResources(fhirContext, responseBundle);
resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
} else if (theResponseObject instanceof IBaseParameters) {
resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext);
}
}
break;
}
@ -292,6 +303,18 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
return true;
}
private List<IBaseResource> toListOfResourcesAndExcludeContainer(IBaseResource theResponseObject, FhirContext fhirContext) {
List<IBaseResource> resources;
resources = fhirContext.newTerser().getAllPopulatedChildElementsOfType(theResponseObject, IBaseResource.class);
// Exclude the container
if (resources.size() > 0 && resources.get(0) == theResponseObject) {
resources = resources.subList(1, resources.size());
}
return resources;
}
@CoverageIgnore
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) {
@ -329,18 +352,18 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
myDefaultPolicy = theDefaultPolicy;
}
private List<IBaseResource> toListOfResources(FhirContext fhirContext, IBaseBundle responseBundle) {
List<IBaseResource> retVal = BundleUtil.toListOfResources(fhirContext, responseBundle);
for (int i = 0; i < retVal.size(); i++) {
IBaseResource nextResource = retVal.get(i);
if (nextResource instanceof IBaseBundle) {
retVal.addAll(BundleUtil.toListOfResources(fhirContext, (IBaseBundle) nextResource));
retVal.remove(i);
i--;
}
}
return retVal;
}
// private List<IBaseResource> toListOfResources(FhirContext fhirContext, IBaseBundle responseBundle) {
// List<IBaseResource> retVal = BundleUtil.toListOfResources(fhirContext, responseBundle);
// for (int i = 0; i < retVal.size(); i++) {
// IBaseResource nextResource = retVal.get(i);
// if (nextResource instanceof IBaseBundle) {
// retVal.addAll(BundleUtil.toListOfResources(fhirContext, (IBaseBundle) nextResource));
// retVal.remove(i);
// i--;
// }
// }
// return retVal;
// }
private static UnsupportedOperationException failForDstu1() {
return new UnsupportedOperationException("Use of this interceptor on DSTU1 servers is not supportd");

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* 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
* 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,
@ -41,6 +41,7 @@ class OperationRule extends BaseRule implements IAuthRule {
private boolean myAppliesToServer;
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
private List<IIdType> myAppliesToIds;
private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType;
/**
* Must include the leading $
@ -66,6 +67,7 @@ class OperationRule extends BaseRule implements IAuthRule {
break;
case EXTENDED_OPERATION_TYPE:
if (myAppliesToTypes != null) {
// TODO: Convert to a map of strings and keep the result
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
String resName = ctx.getResourceDefinition(next).getName();
if (resName.equals(theRequestDetails.getResourceName())) {
@ -76,12 +78,24 @@ class OperationRule extends BaseRule implements IAuthRule {
}
break;
case EXTENDED_OPERATION_INSTANCE:
if (myAppliesToIds != null) {
String instanceId = theRequestDetails.getId().toUnqualifiedVersionless().getValue();
for (IIdType next : myAppliesToIds) {
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
applies = true;
break;
if (theInputResourceId != null) {
if (myAppliesToIds != null) {
String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue();
for (IIdType next : myAppliesToIds) {
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
applies = true;
break;
}
}
}
if (myAppliesToInstancesOfType != null) {
// TODO: Convert to a map of strings and keep the result
for (Class<? extends IBaseResource> next : myAppliesToInstancesOfType) {
String resName = ctx.getResourceDefinition(next).getName();
if (resName.equals(theInputResourceId.getResourceType())) {
applies = true;
break;
}
}
}
}
@ -97,7 +111,7 @@ class OperationRule extends BaseRule implements IAuthRule {
if (myOperationName != null && !myOperationName.equals(theRequestDetails.getOperation())) {
return null;
}
return newVerdict();
}
@ -113,4 +127,8 @@ class OperationRule extends BaseRule implements IAuthRule {
myAppliesToIds = theAppliesToIds;
}
public void appliesToInstancesOfType(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
myAppliesToInstancesOfType = theAppliesToTypes;
}
}

View File

@ -343,16 +343,34 @@ public class RuleBuilder implements IAuthRuleBuilder {
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
Validate.notNull(theType, "theType must not be null");
validateType(theType);
OperationRule rule = createRule();
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
appliesToTypes.add(theType);
rule.appliesToTypes(appliesToTypes);
rule.appliesToTypes(toTypeSet(theType));
myRules.add(rule);
return new RuleBuilderFinished();
}
private void validateType(Class<? extends IBaseResource> theType) {
Validate.notNull(theType, "theType must not be null");
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished onInstancesOfType(Class<? extends IBaseResource> theType) {
validateType(theType);
OperationRule rule = createRule();
rule.appliesToInstancesOfType(toTypeSet(theType));
myRules.add(rule);
return new RuleBuilderFinished();
}
private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) {
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
appliesToTypes.add(theType);
return appliesToTypes;
}
}
}

View File

@ -40,6 +40,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
@ -234,7 +235,95 @@ public class AuthorizationInterceptorDstu2Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Bundle"));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(true, ourHitMethod);
ourReturn = Arrays.asList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Encounter/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("OperationOutcome"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(false, ourHitMethod);
}
@Test
public void testOperationByInstanceOfTypeWithInvalidReturnValue() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// With a return value we don't allow
ourReturn = Arrays.asList(createPatient(222));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("OperationOutcome"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(true, ourHitMethod);
// With a return value we do
ourReturn = Arrays.asList(createPatient(1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Bundle"));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(true, ourHitMethod);
}
@Test
public void testOperationByInstanceOfTypeWithReturnValue() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class)
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Arrays.asList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Bundle"));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals(true, ourHitMethod);
ourReturn = Arrays.asList();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Encounter/1/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("OperationOutcome"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(false, ourHitMethod);
}
@Test
@ -1297,12 +1386,13 @@ public class AuthorizationInterceptorDstu2Test {
DummyPatientResourceProvider patProvider = new DummyPatientResourceProvider();
DummyObservationResourceProvider obsProv = new DummyObservationResourceProvider();
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
PlainProvider plainProvider = new PlainProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
ourServlet.setResourceProviders(patProvider, obsProv);
ourServlet.setResourceProviders(patProvider, obsProv, encProv);
ourServlet.setPlainProviders(plainProvider);
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
@ -1387,6 +1477,23 @@ public class AuthorizationInterceptorDstu2Test {
}
public static class DummyEncounterResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Encounter.class;
}
@Operation(name = "everything", idempotent = true)
public Bundle everything(@IdParam IdDt theId) {
ourHitMethod = true;
Bundle retVal = new Bundle();
for (IResource next : ourReturn) {
retVal.addEntry().setResource(next);
}
return retVal;
}
}
public static class DummyPatientResourceProvider implements IResourceProvider {
@Create()

View File

@ -229,7 +229,10 @@
AuthorizationInterceptor is now a bit more aggressive
at blocking read operations, stopping them on the
way in if there is no way they will be accepted
to the resource check on the way out
to the resource check on the way out. In addition
it can now be configured to allow/deny operation
invocations at the instance level on any
instance of a given type
</action>
<action type="fix" issue="472">
STU3 servers were incorrectly returning the