AuthorizationInterceptor enhancements
This commit is contained in:
parent
14f47b2048
commit
af8ae69ee4
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue