Add auth tester methods

This commit is contained in:
James Agnew 2018-04-22 19:30:09 -04:00
parent 591539cf13
commit a21d0a7752
9 changed files with 425 additions and 174 deletions

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,31 +20,73 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* #L%
*/
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
abstract class BaseRule implements IAuthRule {
private String myName;
private PolicyEnum myMode;
private List<IAuthRuleTester> myTesters;
BaseRule(String theRuleName) {
myName = theRuleName;
}
@Override
public String getName() {
return myName;
}
void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
public void addTester(IAuthRuleTester theTester) {
Validate.notNull(theTester, "theTester must not be null");
if (myTesters == null) {
myTesters = new ArrayList<>();
}
myTesters.add(theTester);
}
Verdict newVerdict() {
return new Verdict(myMode, this);
public void addTesters(List<IAuthRuleTester> theTesters) {
theTesters.forEach(this::addTester);
}
boolean applyTesters(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource, IBaseResource theOutputResource) {
boolean retVal = true;
if (theOutputResource == null) {
for (IAuthRuleTester next : getTesters()) {
if (!next.matches(theOperation, theRequestDetails, theInputResourceId, theInputResource)) {
retVal = false;
break;
}
}
}
return retVal;
}
PolicyEnum getMode() {
return myMode;
}
void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
}
@Override
public String getName() {
return myName;
}
public List<IAuthRuleTester> getTesters() {
if (myTesters == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(myTesters);
}
Verdict newVerdict() {
return new Verdict(myMode, this);
}
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -34,4 +34,18 @@ public interface IAuthRuleFinished {
*/
List<IAuthRule> build();
/**
* Add an additional tester that will be queried if all other conditions
* of this rule already match. For example, given the following rule
* <pre>
* return new RuleBuilder()
* .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).withTester(myTester)
* .build();
* </pre>
* ..the tester will be invoked on any $everything operations on Patient
* resources as a final check as to whether the rule applies or not. In this
* example, the tester is not invoked for other operations.
*/
IAuthRuleFinished withTester(IAuthRuleTester theTester);
}

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
/**
* Allows user-supplied logic for authorization rules.
* <p>
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
* may change.
*
* @since 3.4.0
*/
public interface IAuthRuleTester {
/**
* Allows user-supplied logic for authorization rules.
* <p>
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
* may change.
*
* @since 3.4.0
*/
boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource);
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,6 +40,7 @@ class OperationRule extends BaseRule implements IAuthRule {
private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType;
private boolean myAppliesToAnyType;
private boolean myAppliesToAnyInstance;
public OperationRule(String theRuleName) {
super(theRuleName);
}
@ -136,6 +137,10 @@ class OperationRule extends BaseRule implements IAuthRule {
return null;
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -89,11 +89,16 @@ public class RuleBuilder implements IAuthRuleBuilder {
return new RuleBuilderFinished(rule);
}
public interface ITenantApplicabilityChecker {
boolean applies(RequestDetails theRequest);
}
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished, IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId {
private final RuleImplOp myOpRule;
private final OperationRule myOperationRule;
protected ITenantApplicabilityChecker myTenantApplicabilityChecker;
private List<IAuthRuleTester> myTesters;
RuleBuilderFinished(RuleImplOp theRule) {
myOpRule = theRule;
@ -140,14 +145,11 @@ public class RuleBuilder implements IAuthRuleBuilder {
return this;
}
private void setTenantApplicabilityChecker(ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
if (myOpRule != null) {
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
}
if (myOperationRule != null) {
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker);
public List<IAuthRuleTester> getTesters() {
if (myTesters == null) {
return Collections.emptyList();
}
return myTesters;
}
@Override
@ -165,11 +167,32 @@ public class RuleBuilder implements IAuthRuleBuilder {
});
return this;
}
}
public interface ITenantApplicabilityChecker
{
boolean applies(RequestDetails theRequest);
private void setTenantApplicabilityChecker(ITenantApplicabilityChecker theTenantApplicabilityChecker) {
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
if (myOpRule != null) {
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
}
if (myOperationRule != null) {
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker);
}
}
@Override
public IAuthRuleFinished withTester(IAuthRuleTester theTester) {
if (myTesters == null) {
myTesters = new ArrayList<>();
}
myTesters.add(theTester);
if (myOperationRule != null) {
myOperationRule.addTester(theTester);
}
if (myOpRule != null) {
myOpRule.addTester(theTester);
}
return this;
}
}
private class RuleBuilderRule implements IAuthRuleBuilderRule {
@ -275,6 +298,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
rule.setAppliesTo(myAppliesTo);
rule.setAppliesToTypes(myAppliesToTypes);
rule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
rule.addTesters(getTesters());
myRules.add(rule);
}

View File

@ -69,6 +69,10 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
}
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}

View File

@ -87,6 +87,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
case HISTORY_INSTANCE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
default:
return null;
@ -195,11 +198,20 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return null;
}
case ALLOW_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
case DENY_ALL:
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.DENY, this);
case METADATA:
if (theOperation == RestOperationTypeEnum.METADATA) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}
return null;
@ -220,12 +232,18 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
continue;
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}
}
return null;
case ALL_RESOURCES:
if (appliesToResourceType != null) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
}
break;
@ -244,6 +262,9 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
if (appliesToResourceType != null) {
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceType).getImplementingClass();
if (myAppliesToTypes.contains(type)) {
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return new Verdict(PolicyEnum.ALLOW, this);
}
}
@ -280,6 +301,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
}
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
return null;
}
return newVerdict();
}

View File

@ -12,10 +12,7 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
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.PolicyEnum;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
import ca.uhn.fhir.rest.server.interceptor.auth.*;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
@ -32,6 +29,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
@ -518,6 +516,43 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod);
}
/**
* #528
*/
@Test
public void testDenyActionsNotOnTenant() throws Exception {
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.ALLOW) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().denyAll().notForTenantIds("TENANTA", "TENANTB").build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/1");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by rule: (unnamed rule)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testDenyAll() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -604,43 +639,6 @@ public class AuthorizationInterceptorR4Test {
}
/**
* #528
*/
@Test
public void testDenyActionsNotOnTenant() throws Exception {
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.ALLOW) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder().denyAll().notForTenantIds("TENANTA", "TENANTB").build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/1");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by rule: (unnamed rule)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
/**
* #528
*/
@ -712,73 +710,6 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod);
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseUnauthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseAuthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
@Test
public void testInvalidInstanceIds() {
try {
@ -1296,43 +1227,6 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod);
}
@Test
public void testOperationTypeLevelWithTenant() throws Exception {
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).forTenantIds("TENANTA").andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Right Tenant
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong Tenant
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testOperationTypeLevelWildcard() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1401,6 +1295,82 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod);
}
@Test
public void testOperationTypeLevelWithTenant() throws Exception {
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).forTenantIds("TENANTA").andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Right Tenant
ourHitMethod = false;
ourReturn = Collections.singletonList(createPatient(2));
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Wrong Tenant
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/$opName");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("Access denied by default policy"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testOperationWithTester() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).withTester(new IAuthRuleTester() {
@Override
public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
return theInputResourceId.getIdPart().equals("1");
}
})
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = new ArrayList<>();
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 = new ArrayList<>();
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$everything");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
assertThat(response, containsString("OperationOutcome"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertEquals(false, ourHitMethod);
}
@Test
public void testReadByAnyId() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1537,6 +1507,64 @@ public class AuthorizationInterceptorR4Test {
}
@Test
public void testReadByAnyIdWithTester() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("Rule 1").read().resourcesOfType(Patient.class).withAnyId().withTester(new IAuthRuleTester() {
@Override
public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
return theInputResourceId != null && theInputResourceId.getIdPart().equals("1");
}
})
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createPatient(2));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/222");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/10");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2"));
ourHitMethod = false;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
status = ourClient.execute(httpGet);
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@Test
public void testReadByCompartmentRight() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1787,6 +1815,73 @@ public class AuthorizationInterceptorR4Test {
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseAuthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
/**
* See #762
*/
@Test
public void testTransactionWithPlaceholderIdsResponseUnauthorized() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("write patient").write().resourcesOfType(Patient.class).withAnyId().andThen()
.allow("write encounter").write().resourcesOfType(Encounter.class).withAnyId().andThen()
.allow("write condition").write().resourcesOfType(Condition.class).withAnyId().andThen()
.denyAll("deny all")
.build();
}
});
Bundle input = createTransactionWithPlaceholdersRequestBundle();
Bundle output = createTransactionWithPlaceholdersResponseBundle();
ourReturn = Collections.singletonList((Resource) output);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
CloseableHttpResponse status = ourClient.execute(httpPost);
String resp = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
ourLog.info(resp);
}
@Test
public void testTransactionWriteGood() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {

View File

@ -57,6 +57,20 @@
The REST Generic Client now supports invoking an operation
on a specific version of a resource instance.
</action>
<action type="add">
A new operation has been added to the JPA server called
"$expunge". This operation can be used to physically delete
old versions of resources, logically deleted resources, or
even all resources in the database.
</action>
<action type="add">
An experimental new feature has been added to AuthorizationInterceptor which
allows user-supplied checkers to add additional checking logic
to determine whether a particular rule applies. This could be
used for example to restrict an auth rule to particular
source IPs, or to only allow operations with specific
parameter values.
</action>
</release>
<release version="3.3.0" date="2018-03-29">
<action type="add">