Require explicit declaration of authorizationinterceptor operation rules

on whether the response is authorized or not
This commit is contained in:
James Agnew 2018-11-23 14:25:46 -05:00
parent 364b6cc5fd
commit b41c222880
10 changed files with 244 additions and 107 deletions

View File

@ -34,11 +34,11 @@ public class PublicSecurityInterceptor extends AuthorizationInterceptor {
if (isBlank(authHeader)) { if (isBlank(authHeader)) {
return new RuleBuilder() return new RuleBuilder()
.deny().operation().named(BaseJpaSystemProvider.MARK_ALL_RESOURCES_FOR_REINDEXING).onServer().andThen() .deny().operation().named(BaseJpaSystemProvider.MARK_ALL_RESOURCES_FOR_REINDEXING).onServer().andAllowAllResponses().andThen()
.deny().operation().named(BaseTerminologyUploaderProvider.UPLOAD_EXTERNAL_CODE_SYSTEM).onServer().andThen() .deny().operation().named(BaseTerminologyUploaderProvider.UPLOAD_EXTERNAL_CODE_SYSTEM).onServer().andAllowAllResponses().andThen()
.deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onServer().andThen() .deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onServer().andAllowAllResponses().andThen()
.deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onAnyType().andThen() .deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onAnyType().andAllowAllResponses().andThen()
.deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onAnyInstance().andThen() .deny().operation().named(JpaConstants.OPERATION_EXPUNGE).onAnyInstance().andAllowAllResponses().andThen()
.allowAll() .allowAll()
.build(); .build();
} }

View File

@ -108,5 +108,4 @@ abstract class BaseRule implements IAuthRule {
Verdict newVerdict() { Verdict newVerdict() {
return new Verdict(myMode, this); return new Verdict(myMode, this);
} }
} }

View File

@ -28,36 +28,36 @@ public interface IAuthRuleBuilderOperationNamed {
/** /**
* Rule applies to invocations of this operation at the <code>server</code> level * Rule applies to invocations of this operation at the <code>server</code> level
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onServer(); IAuthRuleBuilderOperationNamedAndScoped onServer();
/** /**
* Rule applies to invocations of this operation at the <code>type</code> level * Rule applies to invocations of this operation at the <code>type</code> level
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType); IAuthRuleBuilderOperationNamedAndScoped onType(Class<? extends IBaseResource> theType);
/** /**
* Rule applies to invocations of this operation at the <code>type</code> level on any type * Rule applies to invocations of this operation at the <code>type</code> level on any type
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onAnyType(); IAuthRuleBuilderOperationNamedAndScoped 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
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId); IAuthRuleBuilderOperationNamedAndScoped onInstance(IIdType theInstanceId);
/** /**
* Rule applies to invocations of this operation at the <code>instance</code> level on any instance of the given type * Rule applies to invocations of this operation at the <code>instance</code> level on any instance of the given type
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onInstancesOfType(Class<? extends IBaseResource> theType); IAuthRuleBuilderOperationNamedAndScoped onInstancesOfType(Class<? extends IBaseResource> theType);
/** /**
* Rule applies to invocations of this operation at the <code>instance</code> level on any instance * Rule applies to invocations of this operation at the <code>instance</code> level on any instance
*/ */
IAuthRuleBuilderRuleOpClassifierFinished onAnyInstance(); IAuthRuleBuilderOperationNamedAndScoped onAnyInstance();
/** /**
* Rule applies to invocations of this operation at any level (server, type or instance) * Rule applies to invocations of this operation at any level (server, type or instance)
*/ */
IAuthRuleBuilderRuleOpClassifierFinished atAnyLevel(); IAuthRuleBuilderOperationNamedAndScoped atAnyLevel();
} }

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
public interface IAuthRuleBuilderOperationNamedAndScoped {
/**
* Responses for this operation will not be checked
*/
IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses();
/**
* Responses for this operation must be authorized by other rules. For example, if this
* rule is authorizing the Patient $everything operation, there must be a separate
* rule (or rules) that actually authorize the user to read the
* resources being returned
*/
IAuthRuleBuilderRuleOpClassifierFinished andRequireExplicitResponseAuthorization();
}

View File

@ -41,6 +41,7 @@ class OperationRule extends BaseRule implements IAuthRule {
private boolean myAppliesToAnyType; private boolean myAppliesToAnyType;
private boolean myAppliesToAnyInstance; private boolean myAppliesToAnyInstance;
private boolean myAppliesAtAnyLevel; private boolean myAppliesAtAnyLevel;
private boolean myAllowAllResponses;
OperationRule(String theRuleName) { OperationRule(String theRuleName) {
super(theRuleName); super(theRuleName);
@ -50,6 +51,10 @@ class OperationRule extends BaseRule implements IAuthRule {
myAppliesAtAnyLevel = theAppliesAtAnyLevel; myAppliesAtAnyLevel = theAppliesAtAnyLevel;
} }
public void allowAllResponses() {
myAllowAllResponses = true;
}
void appliesToAnyInstance() { void appliesToAnyInstance() {
myAppliesToAnyInstance = true; myAppliesToAnyInstance = true;
} }
@ -114,9 +119,17 @@ class OperationRule extends BaseRule implements IAuthRule {
case EXTENDED_OPERATION_INSTANCE: case EXTENDED_OPERATION_INSTANCE:
if (myAppliesToAnyInstance || myAppliesAtAnyLevel) { if (myAppliesToAnyInstance || myAppliesAtAnyLevel) {
applies = true; applies = true;
} else if (theInputResourceId != null) { } else {
IIdType requestResourceId = null;
if (theInputResourceId != null) {
requestResourceId = theInputResourceId;
}
if (requestResourceId == null && myAllowAllResponses) {
requestResourceId = theRequestDetails.getId();
}
if (requestResourceId != null) {
if (myAppliesToIds != null) { if (myAppliesToIds != null) {
String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue(); String instanceId = requestResourceId .toUnqualifiedVersionless().getValue();
for (IIdType next : myAppliesToIds) { for (IIdType next : myAppliesToIds) {
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) { if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
applies = true; applies = true;
@ -128,13 +141,14 @@ class OperationRule extends BaseRule implements IAuthRule {
// 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 : myAppliesToInstancesOfType) { for (Class<? extends IBaseResource> next : myAppliesToInstancesOfType) {
String resName = ctx.getResourceDefinition(next).getName(); String resName = ctx.getResourceDefinition(next).getName();
if (resName.equals(theInputResourceId.getResourceType())) { if (resName.equals(requestResourceId .getResourceType())) {
applies = true; applies = true;
break; break;
} }
} }
} }
} }
}
break; break;
case CREATE: case CREATE:
break; break;

View File

@ -416,6 +416,28 @@ public class RuleBuilder implements IAuthRuleBuilder {
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed { private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
private class RuleBuilderOperationNamedAndScoped implements IAuthRuleBuilderOperationNamedAndScoped {
private final OperationRule myRule;
public RuleBuilderOperationNamedAndScoped(OperationRule theRule) {
myRule = theRule;
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished andAllowAllResponses() {
myRule.allowAllResponses();
myRules.add(myRule);
return new RuleBuilderFinished(myRule);
}
@Override
public IAuthRuleBuilderRuleOpClassifierFinished andRequireExplicitResponseAuthorization() {
myRules.add(myRule);
return new RuleBuilderFinished(myRule);
}
}
private String myOperationName; private String myOperationName;
RuleBuilderRuleOperationNamed(String theOperationName) { RuleBuilderRuleOperationNamed(String theOperationName) {
@ -434,31 +456,28 @@ public class RuleBuilder implements IAuthRuleBuilder {
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onAnyInstance() { public IAuthRuleBuilderOperationNamedAndScoped onAnyInstance() {
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesToAnyInstance(); rule.appliesToAnyInstance();
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished atAnyLevel() { public IAuthRuleBuilderOperationNamedAndScoped atAnyLevel() {
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesAtAnyLevel(true); rule.appliesAtAnyLevel(true);
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onAnyType() { public IAuthRuleBuilderOperationNamedAndScoped onAnyType() {
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesToAnyType(); rule.appliesToAnyType();
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) { public IAuthRuleBuilderOperationNamedAndScoped onInstance(IIdType theInstanceId) {
Validate.notNull(theInstanceId, "theInstanceId must not be null"); Validate.notNull(theInstanceId, "theInstanceId must not be null");
Validate.notBlank(theInstanceId.getResourceType(), "theInstanceId does not have a resource type"); Validate.notBlank(theInstanceId.getResourceType(), "theInstanceId does not have a resource type");
Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part"); Validate.notBlank(theInstanceId.getIdPart(), "theInstanceId does not have an ID part");
@ -467,36 +486,32 @@ public class RuleBuilder implements IAuthRuleBuilder {
ArrayList<IIdType> ids = new ArrayList<>(); ArrayList<IIdType> ids = new ArrayList<>();
ids.add(theInstanceId); ids.add(theInstanceId);
rule.appliesToInstances(ids); rule.appliesToInstances(ids);
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onInstancesOfType(Class<? extends IBaseResource> theType) { public IAuthRuleBuilderOperationNamedAndScoped onInstancesOfType(Class<? extends IBaseResource> theType) {
validateType(theType); validateType(theType);
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesToInstancesOfType(toTypeSet(theType)); rule.appliesToInstancesOfType(toTypeSet(theType));
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onServer() { public IAuthRuleBuilderOperationNamedAndScoped onServer() {
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesToServer(); rule.appliesToServer();
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
@Override @Override
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) { public IAuthRuleBuilderOperationNamedAndScoped onType(Class<? extends IBaseResource> theType) {
validateType(theType); validateType(theType);
OperationRule rule = createRule(); OperationRule rule = createRule();
rule.appliesToTypes(toTypeSet(theType)); rule.appliesToTypes(toTypeSet(theType));
myRules.add(rule); return new RuleBuilderOperationNamedAndScoped(rule);
return new RuleBuilderFinished(rule);
} }
private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) { private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) {

View File

@ -572,7 +572,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().withAnyName().onServer().andThen() .allow("RULE 1").operation().withAnyName().onServer().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -598,7 +598,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -633,7 +633,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andThen() .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen() .allow("Rule 2").read().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.build(); .build();
} }
@ -671,7 +671,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -705,7 +705,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onInstance(new IdDt("http://example.com/Patient/1/_history/2")).andThen() .allow("RULE 1").operation().named("opName").onInstance(new IdDt("http://example.com/Patient/1/_history/2")).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -764,7 +764,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen() .allow("RULE 1").operation().named("opName").onAnyInstance().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -890,7 +890,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onServer().andThen() .allow("RULE 1").operation().named("opName").onServer().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -937,7 +937,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).andThen() .allow("RULE 1").operation().named("opName").onType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1006,7 +1006,7 @@ public class AuthorizationInterceptorDstu2Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyType().andThen() .allow("RULE 1").operation().named("opName").onAnyType().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });

View File

@ -882,7 +882,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().withAnyName().onServer().andThen() .allow("RULE 1").operation().withAnyName().onServer().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -908,7 +908,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").atAnyLevel().andThen() .allow("RULE 1").operation().named("opName").atAnyLevel().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -964,7 +964,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opNameBadOp").atAnyLevel().andThen() .allow("RULE 1").operation().named("opNameBadOp").atAnyLevel().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1020,7 +1020,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -1055,7 +1055,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andThen() .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build(); .build();
} }
@ -1093,7 +1093,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -1127,7 +1127,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onInstance(new IdType("http://example.com/Patient/1/_history/2")).andThen() .allow("RULE 1").operation().named("opName").onInstance(new IdType("http://example.com/Patient/1/_history/2")).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1186,7 +1186,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen() .allow("RULE 1").operation().named("opName").onAnyInstance().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1311,7 +1311,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onServer().andThen() .allow("RULE 1").operation().named("opName").onServer().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1358,7 +1358,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).andThen() .allow("RULE 1").operation().named("opName").onType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1427,7 +1427,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyType().andThen() .allow("RULE 1").operation().named("opName").onAnyType().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1495,7 +1495,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Organization.class).andThen() .allow("RULE 1").operation().named("opName").onType(Organization.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1554,7 +1554,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).forTenantIds("TENANTA").andThen() .allow("RULE 1").operation().named("opName").onType(Patient.class).andRequireExplicitResponseAuthorization().forTenantIds("TENANTA").andThen()
.build(); .build();
} }
}); });
@ -1591,7 +1591,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("process-message").onType(MessageHeader.class).andThen() .allow("RULE 1").operation().named("process-message").onType(MessageHeader.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1630,7 +1630,7 @@ public class AuthorizationInterceptorDstu3Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).withTester(new IAuthRuleTester() { .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization().withTester(new IAuthRuleTester() {
@Override @Override
public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) { public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
return theInputResourceId.getIdPart().equals("1"); return theInputResourceId.getIdPart().equals("1");

View File

@ -36,11 +36,10 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass; import org.junit.*;
import org.junit.Before; import org.springframework.util.Base64Utils;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -49,6 +48,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static javax.print.DocFlavor.READER.TEXT_HTML;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -65,6 +65,7 @@ public class AuthorizationInterceptorR4Test {
private static List<Resource> ourReturn; private static List<Resource> ourReturn;
private static Server ourServer; private static Server ourServer;
private static RestfulServer ourServlet; private static RestfulServer ourServlet;
private static String ourLastAcceptHeader;
@Before @Before
public void before() { public void before() {
@ -76,6 +77,7 @@ public class AuthorizationInterceptorR4Test {
ourReturn = null; ourReturn = null;
ourHitMethod = false; ourHitMethod = false;
ourConditionalCreateId = "1123"; ourConditionalCreateId = "1123";
ourLastAcceptHeader = null;
} }
private Resource createCarePlan(Integer theId, String theSubjectId) { private Resource createCarePlan(Integer theId, String theSubjectId) {
@ -922,7 +924,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().withAnyName().onServer().andThen() .allow("RULE 1").operation().withAnyName().onServer().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -948,7 +950,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").atAnyLevel().andThen() .allow("RULE 1").operation().named("opName").atAnyLevel().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1004,7 +1006,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opNameBadOp").atAnyLevel().andThen() .allow("RULE 1").operation().named("opNameBadOp").atAnyLevel().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1060,7 +1062,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -1090,12 +1092,13 @@ public class AuthorizationInterceptorR4Test {
} }
@Test @Test
@Ignore
public void testOperationByInstanceOfTypeWithInvalidReturnValue() throws Exception { public void testOperationByInstanceOfTypeWithInvalidReturnValue() 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) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andThen() .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen() .allow("Rule 2").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
.build(); .build();
} }
@ -1133,7 +1136,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class) .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization()
.build(); .build();
} }
}); });
@ -1167,7 +1170,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onInstance(new IdType("http://example.com/Patient/1/_history/2")).andThen() .allow("RULE 1").operation().named("opName").onInstance(new IdType("http://example.com/Patient/1/_history/2")).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1226,7 +1229,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyInstance().andThen() .allow("RULE 1").operation().named("opName").onAnyInstance().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1289,6 +1292,31 @@ public class AuthorizationInterceptorR4Test {
} }
@Test
public void testOperationInstanceLevelWithHtmlResponse() throws IOException {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("binaryop").onInstancesOfType(Patient.class).andAllowAllResponses().andThen()
.build();
}
});
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/2/$binaryop");
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("text/html", status.getEntity().getContentType().getValue());
assertEquals("<html>TAGS</html>", IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8));
assertEquals("text/html", ourLastAcceptHeader);
}
}
@Test @Test
public void testOperationNotAllowedWithWritePermissiom() throws Exception { public void testOperationNotAllowedWithWritePermissiom() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) { ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@ -1346,12 +1374,12 @@ public class AuthorizationInterceptorR4Test {
} }
@Test @Test
public void testOperationServerLevel() throws Exception { public void testOperationServerLevelAllowAllResponses() 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) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onServer().andThen() .allow("RULE 1").operation().named("opName").onServer().andAllowAllResponses().andThen()
.build(); .build();
} }
}); });
@ -1392,13 +1420,48 @@ public class AuthorizationInterceptorR4Test {
assertFalse(ourHitMethod); assertFalse(ourHitMethod);
} }
@Test
public void testOperationServerLevelRequireResponseAuthorization() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onServer().andRequireExplicitResponseAuthorization().andThen()
.allow().read().instance("Observation/10").andThen()
.build();
}
});
HttpGet httpGet;
HttpResponse status;
String response;
// Server
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
// Server
ourHitMethod = false;
ourReturn = Collections.singletonList(createObservation(99, "Patient/2"));
httpGet = new HttpGet("http://localhost:" + ourPort + "/$opName");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(200, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@Test @Test
public void testOperationTypeLevel() throws Exception { public void testOperationTypeLevel() 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) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).andThen() .allow("RULE 1").operation().named("opName").onType(Patient.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1467,7 +1530,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onAnyType().andThen() .allow("RULE 1").operation().named("opName").onAnyType().andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1535,7 +1598,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Organization.class).andThen() .allow("RULE 1").operation().named("opName").onType(Organization.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1594,7 +1657,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("opName").onType(Patient.class).forTenantIds("TENANTA").andThen() .allow("RULE 1").operation().named("opName").onType(Patient.class).andRequireExplicitResponseAuthorization().forTenantIds("TENANTA").andThen()
.build(); .build();
} }
}); });
@ -1631,7 +1694,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("RULE 1").operation().named("process-message").onType(MessageHeader.class).andThen() .allow("RULE 1").operation().named("process-message").onType(MessageHeader.class).andRequireExplicitResponseAuthorization().andThen()
.build(); .build();
} }
}); });
@ -1670,7 +1733,7 @@ public class AuthorizationInterceptorR4Test {
@Override @Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) { public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
return new RuleBuilder() return new RuleBuilder()
.allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).withTester(new IAuthRuleTester() { .allow("Rule 1").operation().named("everything").onInstancesOfType(Patient.class).andRequireExplicitResponseAuthorization().withTester(new IAuthRuleTester() {
@Override @Override
public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) { public boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
return theInputResourceId.getIdPart().equals("1"); return theInputResourceId.getIdPart().equals("1");
@ -3264,6 +3327,7 @@ public class AuthorizationInterceptorR4Test {
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static class DummyPatientResourceProvider implements IResourceProvider { public static class DummyPatientResourceProvider implements IResourceProvider {
@Create() @Create()
public MethodOutcome create(@ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) { public MethodOutcome create(@ResourceParam Patient theResource, @ConditionalUrlParam String theConditionalUrl, RequestDetails theRequestDetails) {
@ -3303,6 +3367,26 @@ public class AuthorizationInterceptorR4Test {
return retVal; return retVal;
} }
@Operation(name = "$binaryop", idempotent = true)
public Binary binaryOp(
@IdParam IIdType theId,
@OperationParam(name = "PARAM3", min = 0, max = 1) List<org.hl7.fhir.r4.model.StringType> theParam3,
HttpServletRequest theServletRequest
) {
ourLastAcceptHeader = theServletRequest.getHeader(ca.uhn.fhir.rest.api.Constants.HEADER_ACCEPT);
Binary retVal = new Binary();
if (ourLastAcceptHeader.contains("html")) {
retVal.setContentType("text/html");
retVal.setContent("<html>TAGS</html>".getBytes(Charsets.UTF_8));
} else {
retVal.setContentType("application/weird");
retVal.setContent(new byte[]{0,0,1,1,2,2,3,3,0,0});
}
return retVal;
}
@Override @Override
public Class<? extends IBaseResource> getResourceType() { public Class<? extends IBaseResource> getResourceType() {
return Patient.class; return Patient.class;

View File

@ -76,7 +76,8 @@
ResourceIndexedSearchParams, IdHelperService, SearcchParamExtractorService, and MatchUrlService. ResourceIndexedSearchParams, IdHelperService, SearcchParamExtractorService, and MatchUrlService.
</action> </action>
<action type="add"> <action type="add">
Replaced explicit @Bean construction in BaseConfig.java with @ComponentScan. Beans with state are annotated with Replaced explicit @Bean construction in BaseConfig.java with @ComponentScan. Beans with state are annotated
with
@Component and stateless beans are annotated as @Service. Also changed SearchBuilder.java and the @Component and stateless beans are annotated as @Service. Also changed SearchBuilder.java and the
three Subscriber classes into @Scope("protoype") so their dependencies can be @Autowired injected three Subscriber classes into @Scope("protoype") so their dependencies can be @Autowired injected
as opposed to constructor parameters. as opposed to constructor parameters.
@ -91,6 +92,11 @@
larger batches (20000 instead of 500) in order to reduce the amount of noise larger batches (20000 instead of 500) in order to reduce the amount of noise
in the logs. in the logs.
</action> </action>
<action type="add">
AuthorizationInterceptor now allows arbitrary FHIR $operations to be authorized,
including support for either allowing the operation response to proceed unchallenged,
or authorizing the contents of the response.
</action>
</release> </release>
<release version="3.6.0" date="2018-11-12" description="Food"> <release version="3.6.0" date="2018-11-12" description="Food">
<action type="add"> <action type="add">