585 authorizationinterceptor wildcards (#586)

* Add wildcards for authorizationinterceptor

* Add changelog
This commit is contained in:
James Agnew 2017-03-14 22:26:45 -04:00 committed by Diederik Muylwyk
parent 95ef644612
commit a867890554
6 changed files with 242 additions and 53 deletions

View File

@ -301,18 +301,6 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
return true; 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 @CoverageIgnore
@Override @Override
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) { public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) {
@ -350,6 +338,18 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
myDefaultPolicy = theDefaultPolicy; myDefaultPolicy = theDefaultPolicy;
} }
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;
}
// private List<IBaseResource> toListOfResources(FhirContext fhirContext, IBaseBundle responseBundle) { // private List<IBaseResource> toListOfResources(FhirContext fhirContext, IBaseBundle responseBundle) {
// List<IBaseResource> retVal = BundleUtil.toListOfResources(fhirContext, responseBundle); // List<IBaseResource> retVal = BundleUtil.toListOfResources(fhirContext, responseBundle);
// for (int i = 0; i < retVal.size(); i++) { // for (int i = 0; i < retVal.size(); i++) {
@ -368,10 +368,10 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
} }
private enum OperationExamineDirection { private enum OperationExamineDirection {
BOTH,
IN, IN,
NONE, NONE,
OUT, OUT,
BOTH,
} }
public static class Verdict { public static class Verdict {

View File

@ -35,6 +35,11 @@ public interface IAuthRuleBuilderOperationNamed {
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType); IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType);
/**
* Rule applies to invocations of this operation at the <code>type</code> level on any type
*/
IAuthRuleFinished onAnyType();
/** /**
* Rule applies to invocations of this operation at the <code>instance</code> level * Rule applies to invocations of this operation at the <code>instance</code> level
*/ */
@ -45,4 +50,9 @@ public interface IAuthRuleBuilderOperationNamed {
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onInstancesOfType(Class<? extends IBaseResource> theType); IAuthRuleBuilderRuleOpClassifierFinished onInstancesOfType(Class<? extends IBaseResource> theType);
/**
* Rule applies to invocations of this operation at the <code>instance</code> level on any instance
*/
IAuthRuleFinished onAnyInstance();
} }

View File

@ -42,6 +42,8 @@ class OperationRule extends BaseRule implements IAuthRule {
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes; private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
private List<IIdType> myAppliesToIds; private List<IIdType> myAppliesToIds;
private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType; private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType;
private boolean myAppliesToAnyType;
private boolean myAppliesToAnyInstance;
/** /**
* Must include the leading $ * Must include the leading $
@ -66,7 +68,9 @@ class OperationRule extends BaseRule implements IAuthRule {
} }
break; break;
case EXTENDED_OPERATION_TYPE: case EXTENDED_OPERATION_TYPE:
if (myAppliesToTypes != null) { if (myAppliesToAnyType) {
applies = true;
} else if (myAppliesToTypes != null) {
// TODO: Convert to a map of strings and keep the result // TODO: Convert to a map of strings and keep the result
for (Class<? extends IBaseResource> next : myAppliesToTypes) { for (Class<? extends IBaseResource> next : myAppliesToTypes) {
String resName = ctx.getResourceDefinition(next).getName(); String resName = ctx.getResourceDefinition(next).getName();
@ -78,7 +82,9 @@ class OperationRule extends BaseRule implements IAuthRule {
} }
break; break;
case EXTENDED_OPERATION_INSTANCE: case EXTENDED_OPERATION_INSTANCE:
if (theInputResourceId != null) { if (myAppliesToAnyInstance) {
applies = true;
} else if (theInputResourceId != null) {
if (myAppliesToIds != null) { if (myAppliesToIds != null) {
String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue(); String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue();
for (IIdType next : myAppliesToIds) { for (IIdType next : myAppliesToIds) {
@ -131,4 +137,12 @@ class OperationRule extends BaseRule implements IAuthRule {
myAppliesToInstancesOfType = theAppliesToTypes; myAppliesToInstancesOfType = theAppliesToTypes;
} }
public void appliesToAnyInstance() {
myAppliesToAnyInstance = true;
}
public void appliesToAnyType() {
myAppliesToAnyType = true;
}
} }

View File

@ -371,6 +371,22 @@ public class RuleBuilder implements IAuthRuleBuilder {
return appliesToTypes; return appliesToTypes;
} }
@Override
public IAuthRuleFinished onAnyType() {
OperationRule rule = createRule();
rule.appliesToAnyType();
myRules.add(rule);
return new RuleBuilderFinished();
}
@Override
public IAuthRuleFinished onAnyInstance() {
OperationRule rule = createRule();
rule.appliesToAnyInstance();
myRules.add(rule);
return new RuleBuilderFinished();
}
} }
} }

View File

@ -99,12 +99,6 @@ public class AuthorizationInterceptorDstu2Test {
return retVal; return retVal;
} }
private IResource createPatient(Integer theId, int theVersion) {
IResource retVal = createPatient(theId);
retVal.setId(retVal.getId().withVersion(Integer.toString(theVersion)));
return retVal;
}
private IResource createPatient(Integer theId) { private IResource createPatient(Integer theId) {
Patient retVal = new Patient(); Patient retVal = new Patient();
if (theId != null) { if (theId != null) {
@ -114,6 +108,12 @@ public class AuthorizationInterceptorDstu2Test {
return retVal; return retVal;
} }
private IResource createPatient(Integer theId, int theVersion) {
IResource retVal = createPatient(theId);
retVal.setId(retVal.getId().withVersion(Integer.toString(theVersion)));
return retVal;
}
private String extractResponseAndClose(HttpResponse status) throws IOException { private String extractResponseAndClose(HttpResponse status) throws IOException {
if (status.getEntity() == null) { if (status.getEntity() == null) {
return null; return null;
@ -497,6 +497,46 @@ public class AuthorizationInterceptorDstu2Test {
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
} }
@Test
public void testHistoryWithReadAll() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").read().allResources().withAnyId()
.build();
//@formatter:on
}
});
HttpGet httpGet;
HttpResponse status;
ourReturn = Arrays.asList(createPatient(2, 1));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test @Test
public void testMetadataAllow() throws Exception { public void testMetadataAllow() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -747,6 +787,75 @@ public class AuthorizationInterceptorDstu2Test {
} }
@Test
public void testOperationInstanceLevelAnyInstance() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Another Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/2/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong name
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$opName2");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test @Test
public void testOperationNotAllowedWithWritePermissiom() throws Exception { public void testOperationNotAllowedWithWritePermissiom() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -926,13 +1035,13 @@ public class AuthorizationInterceptorDstu2Test {
} }
@Test @Test
public void testHistoryWithReadAll() throws Exception { public void testOperationTypeLevelWildcard() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off //@formatter:off
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").read().allResources().withAnyId() .allow("RULE 1").operation().named("opName").onAnyType().andThen()
.build(); .build();
//@formatter:on //@formatter:on
} }
@ -940,29 +1049,59 @@ public class AuthorizationInterceptorDstu2Test {
HttpGet httpGet; HttpGet httpGet;
HttpResponse status; HttpResponse status;
String response;
ourReturn = Arrays.asList(createPatient(2, 1)); // Server
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/_history"); ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
extractResponseAndClose(status); response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Type
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
// Another type
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history"); ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/$opName");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
extractResponseAndClose(status); response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod); assertTrue(ourHitMethod);
// Wrong name
ourHitMethod = false; ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history"); ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$opName2");
status = ourClient.execute(httpGet); status = ourClient.execute(httpGet);
extractResponseAndClose(status); response = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode()); ourLog.info(response);
assertTrue(ourHitMethod); assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
// Instance
ourHitMethod = false;
ourReturn = Arrays.asList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
} }
@Test @Test
@ -1711,19 +1850,6 @@ public class AuthorizationInterceptorDstu2Test {
return retVal; return retVal;
} }
@History()
public List<IResource> history() {
ourHitMethod = true;
return (ourReturn);
}
@History()
public List<IResource> history(@IdParam IdDt theId) {
ourHitMethod = true;
return (ourReturn);
}
@Delete() @Delete()
public MethodOutcome delete(IRequestOperationCallback theRequestOperationCallback, @IdParam IdDt theId, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { public MethodOutcome delete(IRequestOperationCallback theRequestOperationCallback, @IdParam IdDt theId, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) {
ourHitMethod = true; ourHitMethod = true;
@ -1744,11 +1870,24 @@ public class AuthorizationInterceptorDstu2Test {
return retVal; return retVal;
} }
@Override @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Patient.class; return Patient.class;
} }
@History()
public List<IResource> history() {
ourHitMethod = true;
return (ourReturn);
}
@History()
public List<IResource> history(@IdParam IdDt theId) {
ourHitMethod = true;
return (ourReturn);
}
@Operation(name = "opName", idempotent = true) @Operation(name = "opName", idempotent = true)
public Parameters operation() { public Parameters operation() {
ourHitMethod = true; ourHitMethod = true;
@ -1767,6 +1906,12 @@ public class AuthorizationInterceptorDstu2Test {
return (Parameters) new Parameters().setId("1"); return (Parameters) new Parameters().setId("1");
} }
@Operation(name = "opName2", idempotent = true)
public Parameters operation2() {
ourHitMethod = true;
return (Parameters) new Parameters().setId("1");
}
@Read(version = true) @Read(version = true)
public Patient read(@IdParam IdDt theId) { public Patient read(@IdParam IdDt theId) {
ourHitMethod = true; ourHitMethod = true;
@ -1821,6 +1966,12 @@ public class AuthorizationInterceptorDstu2Test {
public static class PlainProvider { public static class PlainProvider {
@History()
public List<IResource> history() {
ourHitMethod = true;
return (ourReturn);
}
@Operation(name = "opName", idempotent = true) @Operation(name = "opName", idempotent = true)
public Parameters operation() { public Parameters operation() {
ourHitMethod = true; ourHitMethod = true;
@ -1833,12 +1984,6 @@ public class AuthorizationInterceptorDstu2Test {
return (Bundle) ourReturn.get(0); return (Bundle) ourReturn.get(0);
} }
@History()
public List<IResource> history() {
ourHitMethod = true;
return (ourReturn);
}
} }

View File

@ -166,6 +166,10 @@
JPA server interceptor methods for create/update/delete provided JPA server interceptor methods for create/update/delete provided
the wrong version ID to the interceptors the wrong version ID to the interceptors
</action> </action>
<action type="add" issue="585">
AuthorizationInterceptor can now authorize (allow/deny) extended operations
on instances and types by wildcard (on any type, or on any instance)
</action>
</release> </release>
<release version="2.2" date="2016-12-20"> <release version="2.2" date="2016-12-20">
<action type="add"> <action type="add">