Add auth tester methods
This commit is contained in:
parent
591539cf13
commit
a21d0a7752
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -69,6 +69,10 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
}
|
||||
}
|
||||
|
||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newVerdict();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue