Change the IAuthRuleTester api to a parameter object. (#3756)
* Try using tester to implement fhir filtering.
This commit is contained in:
parent
b68ccb373a
commit
616a126fad
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
type: change
|
||||||
|
issue: 3756
|
||||||
|
title: "The IAuthRuleTester api has changed and now uses a parameter object for flexibility."
|
|
@ -23,8 +23,14 @@ package ca.uhn.fhir.jpa.searchparam.matcher;
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthorizationSearchParamMatcher;
|
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthorizationSearchParamMatcher;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter from {@link SearchParamMatcher} to our authorization version.
|
||||||
|
*/
|
||||||
public class AuthorizationSearchParamMatcher implements IAuthorizationSearchParamMatcher {
|
public class AuthorizationSearchParamMatcher implements IAuthorizationSearchParamMatcher {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationSearchParamMatcher.class);
|
||||||
private final SearchParamMatcher mySearchParamMatcher;
|
private final SearchParamMatcher mySearchParamMatcher;
|
||||||
|
|
||||||
public AuthorizationSearchParamMatcher(SearchParamMatcher mySearchParamMatcher) {
|
public AuthorizationSearchParamMatcher(SearchParamMatcher mySearchParamMatcher) {
|
||||||
|
@ -36,18 +42,20 @@ public class AuthorizationSearchParamMatcher implements IAuthorizationSearchPara
|
||||||
try {
|
try {
|
||||||
InMemoryMatchResult inMemoryMatchResult = mySearchParamMatcher.match(theCriteria, theResource, null);
|
InMemoryMatchResult inMemoryMatchResult = mySearchParamMatcher.match(theCriteria, theResource, null);
|
||||||
if (!inMemoryMatchResult.supported()) {
|
if (!inMemoryMatchResult.supported()) {
|
||||||
return new MatchResult(Match.UNSUPPORTED, inMemoryMatchResult.getUnsupportedReason());
|
return MatchResult.buildUnsupported(inMemoryMatchResult.getUnsupportedReason());
|
||||||
}
|
}
|
||||||
if (inMemoryMatchResult.matched()) {
|
if (inMemoryMatchResult.matched()) {
|
||||||
return new MatchResult(Match.MATCH, null);
|
return MatchResult.buildMatched();
|
||||||
} else {
|
} else {
|
||||||
return new MatchResult(Match.NO_MATCH, null);
|
return MatchResult.buildUnmatched();
|
||||||
}
|
}
|
||||||
} catch (MatchUrlService.UnrecognizedSearchParameterException e) {
|
} catch (MatchUrlService.UnrecognizedSearchParameterException e) {
|
||||||
// wipmb revisit this design
|
// The matcher treats a bad expression as InvalidRequestException because
|
||||||
// The matcher treats a bad expression as InvalidRequestException because it assumes it is during SearchParameter storage.
|
// it assumes it is during SearchParameter storage.
|
||||||
// Instead, we adapt this to UNSUPPORTED during authorization. We may be applying to all types, and this filter won't match.
|
// Instead, we adapt this to UNSUPPORTED during authorization.
|
||||||
return new MatchResult(Match.UNSUPPORTED, e.getMessage());
|
// We may be applying to all types, and this filter won't match.
|
||||||
|
ourLog.info("Unsupported filter {} applied to resource: {}", theCriteria, e.getMessage());
|
||||||
|
return MatchResult.buildUnsupported(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
.andThen().build().get(0);
|
.andThen().build().get(0);
|
||||||
|
|
||||||
when(myMatcher.match(ArgumentMatchers.eq("Patient?family=Smith"), ArgumentMatchers.same(myResource)))
|
when(myMatcher.match(ArgumentMatchers.eq("Patient?family=Smith"), ArgumentMatchers.same(myResource)))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeMatched());
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildMatched());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
||||||
|
@ -112,7 +112,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi")
|
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi")
|
||||||
.andThen().build().get(0);
|
.andThen().build().get(0);
|
||||||
when(myMatcher.match(ArgumentMatchers.eq("Patient?family=smi"), ArgumentMatchers.same(myResource)))
|
when(myMatcher.match(ArgumentMatchers.eq("Patient?family=smi"), ArgumentMatchers.same(myResource)))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeUnmatched());
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildUnmatched());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
||||||
|
@ -133,7 +133,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "code=28521000087105")
|
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "code=28521000087105")
|
||||||
.andThen().build().get(0);
|
.andThen().build().get(0);
|
||||||
when(myMatcher.match("Observation?code=28521000087105", myResource2))
|
when(myMatcher.match("Observation?code=28521000087105", myResource2))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeUnmatched());
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildUnmatched());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
||||||
|
@ -152,7 +152,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
.withFilter("code=12")
|
.withFilter("code=12")
|
||||||
.andThen().build().get(0);
|
.andThen().build().get(0);
|
||||||
when(myMatcher.match("Observation?code=12", myResource2))
|
when(myMatcher.match("Observation?code=12", myResource2))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeUnmatched());
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildUnmatched());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
||||||
|
@ -171,7 +171,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
.withFilter("code=28521000087105")
|
.withFilter("code=28521000087105")
|
||||||
.andThen().build().get(0);
|
.andThen().build().get(0);
|
||||||
when(myMatcher.match("Observation?code=28521000087105", myResource2))
|
when(myMatcher.match("Observation?code=28521000087105", myResource2))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeMatched());
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildMatched());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource2);
|
||||||
|
@ -200,7 +200,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
myRule = (FhirQueryRuleImpl) new RuleBuilder().allow().read().resourcesOfType("Patient")
|
myRule = (FhirQueryRuleImpl) new RuleBuilder().allow().read().resourcesOfType("Patient")
|
||||||
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi").andThen().build().get(0);
|
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi").andThen().build().get(0);
|
||||||
when(myMatcher.match("Patient?family=smi", myResource))
|
when(myMatcher.match("Patient?family=smi", myResource))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeUnsupported("I'm broken unsupported chain XXX"));
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildUnsupported("I'm broken unsupported chain XXX"));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
||||||
|
@ -217,7 +217,7 @@ class FhirQueryRuleImplTest implements ITestDataBuilder {
|
||||||
myRule = (FhirQueryRuleImpl) new RuleBuilder().deny().read().resourcesOfType("Patient")
|
myRule = (FhirQueryRuleImpl) new RuleBuilder().deny().read().resourcesOfType("Patient")
|
||||||
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi").andThen().build().get(0);
|
.inCompartmentWithFilter("patient", myResource.getIdElement().withResourceType("Patient"), "family=smi").andThen().build().get(0);
|
||||||
when(myMatcher.match("Patient?family=smi", myResource))
|
when(myMatcher.match("Patient?family=smi", myResource))
|
||||||
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.makeUnsupported("I'm broken unsupported chain XXX"));
|
.thenReturn(IAuthorizationSearchParamMatcher.MatchResult.buildUnsupported("I'm broken unsupported chain XXX"));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
AuthorizationInterceptor.Verdict verdict = applyRuleToResource(myResource);
|
||||||
|
|
|
@ -29,7 +29,6 @@ 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 java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -54,20 +53,24 @@ abstract class BaseRule implements IAuthRule {
|
||||||
theTesters.forEach(this::addTester);
|
theTesters.forEach(this::addTester);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean applyTesters(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource, IBaseResource theOutputResource) {
|
private boolean applyTesters(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||||
assert !(theInputResource != null && theOutputResource != null);
|
assert !(theInputResource != null && theOutputResource != null);
|
||||||
|
|
||||||
boolean retVal = true;
|
boolean retVal = true;
|
||||||
if (theOutputResource == null) {
|
if (theOutputResource == null) {
|
||||||
|
IAuthRuleTester.RuleTestRequest inputRequest = new IAuthRuleTester.RuleTestRequest(myMode, theOperation, theRequestDetails, theInputResourceId, theInputResource, theRuleApplier);
|
||||||
|
|
||||||
|
|
||||||
for (IAuthRuleTester next : getTesters()) {
|
for (IAuthRuleTester next : getTesters()) {
|
||||||
if (!next.matches(theOperation, theRequestDetails, theInputResourceId, theInputResource)) {
|
if (!next.matches(inputRequest)) {
|
||||||
retVal = false;
|
retVal = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
IAuthRuleTester.RuleTestRequest outputRequest = new IAuthRuleTester.RuleTestRequest(myMode, theOperation, theRequestDetails, theOutputResource.getIdElement(), theOutputResource, theRuleApplier);
|
||||||
for (IAuthRuleTester next : getTesters()) {
|
for (IAuthRuleTester next : getTesters()) {
|
||||||
if (!next.matchesOutput(theOperation, theRequestDetails, theOutputResource)) {
|
if (!next.matchesOutput(outputRequest)) {
|
||||||
retVal = false;
|
retVal = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -98,8 +101,8 @@ abstract class BaseRule implements IAuthRule {
|
||||||
return Collections.unmodifiableList(myTesters);
|
return Collections.unmodifiableList(myTesters);
|
||||||
}
|
}
|
||||||
|
|
||||||
Verdict newVerdict(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
Verdict newVerdict(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||||
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource)) {
|
if (!applyTesters(theOperation, theRequestDetails, theInputResourceId, theInputResource, theOutputResource, theRuleApplier)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Verdict(myMode, this);
|
return new Verdict(myMode, this);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
public class FhirQueryRuleTester implements IAuthRuleTester {
|
||||||
|
private final String myType;
|
||||||
|
private final String myFilter;
|
||||||
|
|
||||||
|
public FhirQueryRuleTester(String theType, String theFilter) {
|
||||||
|
myType = theType;
|
||||||
|
myFilter = theFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(RuleTestRequest theRuleTestRequest) {
|
||||||
|
// wipmb placeholder until we get to writes
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matchesOutput(RuleTestRequest theRuleTestRequest) {
|
||||||
|
|
||||||
|
// look for a matcher
|
||||||
|
IAuthorizationSearchParamMatcher matcher = theRuleTestRequest.ruleApplier.getSearchParamMatcher();
|
||||||
|
if (matcher == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wipmb myType, or target type? Is this just query filter, or do we bring * into it?
|
||||||
|
IAuthorizationSearchParamMatcher.MatchResult mr = matcher.match(myType + "?" + myFilter, theRuleTestRequest.resource);
|
||||||
|
|
||||||
|
switch (mr.getMatch()) {
|
||||||
|
case MATCH:
|
||||||
|
return true;
|
||||||
|
case UNSUPPORTED:
|
||||||
|
theRuleTestRequest.ruleApplier.getTroubleshootingLog().warn("Unsupported matcher expression {}: {}.", myFilter, mr.getUnsupportedReason());
|
||||||
|
// unsupported doesn't match unless this is a deny request, and we need to be safe!
|
||||||
|
return (theRuleTestRequest.mode == PolicyEnum.DENY);
|
||||||
|
case NO_MATCH:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,9 +22,13 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
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 javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows user-supplied logic for authorization rules.
|
* Allows user-supplied logic for authorization rules.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -34,6 +38,44 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
* @since 3.4.0
|
* @since 3.4.0
|
||||||
*/
|
*/
|
||||||
public interface IAuthRuleTester {
|
public interface IAuthRuleTester {
|
||||||
|
/**
|
||||||
|
* A request object to make this easier to extend.
|
||||||
|
*/
|
||||||
|
class RuleTestRequest {
|
||||||
|
// fake record pattern
|
||||||
|
/** the mode of the calling rule context */
|
||||||
|
@Nonnull public final PolicyEnum mode;
|
||||||
|
/**
|
||||||
|
* The FHIR operation being performed.
|
||||||
|
* Note that this is not necessarily the same as the value obtained from invoking
|
||||||
|
* {@link RequestDetails#getRestOperationType()} on {@literal requestDetails}
|
||||||
|
* because multiple operations can be nested within
|
||||||
|
* an HTTP request using FHIR transaction and batch operations
|
||||||
|
*/
|
||||||
|
@Nonnull public final RestOperationTypeEnum operation;
|
||||||
|
@Nonnull public final RequestDetails requestDetails;
|
||||||
|
@Nullable public final IIdType resourceId;
|
||||||
|
@Nullable public final IBaseResource resource;
|
||||||
|
/** supplier for support services */
|
||||||
|
@Nonnull public final IRuleApplier ruleApplier;
|
||||||
|
|
||||||
|
public RuleTestRequest(PolicyEnum theMode, @Nonnull RestOperationTypeEnum theOperation, @Nonnull RequestDetails theRequestDetails, @Nullable IIdType theResourceId, @Nullable IBaseResource theResource, @Nonnull IRuleApplier theRuleApplier) {
|
||||||
|
Validate.notNull(theMode);
|
||||||
|
Validate.notNull(theOperation);
|
||||||
|
Validate.notNull(theRequestDetails);
|
||||||
|
Validate.notNull(theRuleApplier);
|
||||||
|
mode = theMode;
|
||||||
|
operation = theOperation;
|
||||||
|
requestDetails = theRequestDetails;
|
||||||
|
resource = theResource;
|
||||||
|
if (theResourceId == null && resource != null) {
|
||||||
|
resourceId = resource.getIdElement();
|
||||||
|
} else {
|
||||||
|
resourceId = theResourceId;
|
||||||
|
}
|
||||||
|
ruleApplier = theRuleApplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows user-supplied logic for authorization rules.
|
* Allows user-supplied logic for authorization rules.
|
||||||
|
@ -41,11 +83,19 @@ public interface IAuthRuleTester {
|
||||||
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
|
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
|
||||||
* may change.
|
* may change.
|
||||||
*
|
*
|
||||||
* @param theOperation The FHIR operation being performed - Note that this is not necessarily the same as the value obtained from invoking
|
* @param theRequest The details to evaluate
|
||||||
* {@link RequestDetails#getRestOperationType()} on {@literal theRequestDetails} because multiple operations can be nested within
|
* @since 6.1.0
|
||||||
* an HTTP request using FHIR transaction and batch operations
|
|
||||||
* @since 3.4.0
|
|
||||||
*/
|
*/
|
||||||
|
default boolean matches(RuleTestRequest theRequest) {
|
||||||
|
return this.matches(theRequest.operation, theRequest.requestDetails, theRequest.resourceId, theRequest.resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DO NOT IMPLEMENT - Old api. {@link #matches(RuleTestRequest)} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "6.1.0")
|
||||||
default boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
|
default boolean matches(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -56,11 +106,18 @@ public interface IAuthRuleTester {
|
||||||
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
|
* THIS IS AN EXPERIMENTAL API! Feedback is welcome, and this API
|
||||||
* may change.
|
* may change.
|
||||||
*
|
*
|
||||||
* @param theOperation The FHIR operation being performed - Note that this is not necessarily the same as the value obtained from invoking
|
* @param theRequest The details to evaluate
|
||||||
* {@link RequestDetails#getRestOperationType()} on {@literal theRequestDetails} because multiple operations can be nested within
|
* @since 6.1.0
|
||||||
* an HTTP request using FHIR transaction and batch operations
|
|
||||||
* @since 5.0.0
|
|
||||||
*/
|
*/
|
||||||
|
default boolean matchesOutput(RuleTestRequest theRequest) {
|
||||||
|
return this.matchesOutput(theRequest.operation, theRequest.requestDetails, theRequest.resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DO NOT IMPLEMENT - Old api. {@link #matches(RuleTestRequest)} instead.
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "6.1.0")
|
||||||
default boolean matchesOutput(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theOutputResource) {
|
default boolean matchesOutput(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theOutputResource) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,36 +23,42 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wipjv Ken do we like this name, or this package?
|
* Adapt the InMemoryMatcher to support authorization filters in {@link FhirQueryRuleImpl}.
|
||||||
*/
|
*/
|
||||||
public interface IAuthorizationSearchParamMatcher {
|
public interface IAuthorizationSearchParamMatcher {
|
||||||
MatchResult match(String theCriteria, IBaseResource theResource);
|
MatchResult match(String theCriteria, IBaseResource theResource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match outcomes.
|
||||||
|
*/
|
||||||
enum Match {
|
enum Match {
|
||||||
MATCH,
|
MATCH,
|
||||||
NO_MATCH,
|
NO_MATCH,
|
||||||
|
/** Used for contexts without matcher infrastructure like hybrid providers */
|
||||||
UNSUPPORTED
|
UNSUPPORTED
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MatchResult {
|
class MatchResult {
|
||||||
private final Match myMatch;
|
// wipmb consider a record pattern - public and drop the accessors.
|
||||||
private final String myUnsupportedReason;
|
@Nonnull private final Match myMatch;
|
||||||
|
@Nullable private final String myUnsupportedReason;
|
||||||
|
|
||||||
public static MatchResult makeMatched() {
|
public static MatchResult buildMatched() {
|
||||||
return new MatchResult(Match.MATCH, null);
|
return new MatchResult(Match.MATCH, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MatchResult makeUnmatched() {
|
public static MatchResult buildUnmatched() {
|
||||||
return new MatchResult(Match.NO_MATCH, null);
|
return new MatchResult(Match.NO_MATCH, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MatchResult makeUnsupported(@Nonnull String theReason) {
|
public static MatchResult buildUnsupported(@Nonnull String theReason) {
|
||||||
return new MatchResult(Match.UNSUPPORTED, theReason);
|
return new MatchResult(Match.UNSUPPORTED, theReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MatchResult(Match myMatch, String myUnsupportedReason) {
|
private MatchResult(Match myMatch, String myUnsupportedReason) {
|
||||||
this.myMatch = myMatch;
|
this.myMatch = myMatch;
|
||||||
this.myUnsupportedReason = myUnsupportedReason;
|
this.myUnsupportedReason = myUnsupportedReason;
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -583,7 +583,6 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleFinished inCompartmentWithFilter(String theCompartmentName, IIdType theIdElement, String theFilter) {
|
public IAuthRuleFinished inCompartmentWithFilter(String theCompartmentName, IIdType theIdElement, String theFilter) {
|
||||||
// wipjv (resolved?) implemented
|
|
||||||
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
|
||||||
Validate.notNull(theIdElement, "theOwner must not be null");
|
Validate.notNull(theIdElement, "theOwner must not be null");
|
||||||
validateOwner(theIdElement);
|
validateOwner(theIdElement);
|
||||||
|
|
|
@ -72,14 +72,14 @@ public class RuleBulkExportImpl extends BaseRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myWantAnyStyle || myWantExportStyle == BulkDataExportOptions.ExportStyle.SYSTEM) {
|
if (myWantAnyStyle || myWantExportStyle == BulkDataExportOptions.ExportStyle.SYSTEM) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotBlank(myGroupId) && options.getGroupId() != null) {
|
if (isNotBlank(myGroupId) && options.getGroupId() != null) {
|
||||||
String expectedGroupId = new IdDt(myGroupId).toUnqualifiedVersionless().getValue();
|
String expectedGroupId = new IdDt(myGroupId).toUnqualifiedVersionless().getValue();
|
||||||
String actualGroupId = options.getGroupId().toUnqualifiedVersionless().getValue();
|
String actualGroupId = options.getGroupId().toUnqualifiedVersionless().getValue();
|
||||||
if (Objects.equals(expectedGroupId, actualGroupId)) {
|
if (Objects.equals(expectedGroupId, actualGroupId)) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -115,12 +115,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
case SEARCH_SYSTEM:
|
case SEARCH_SYSTEM:
|
||||||
case HISTORY_SYSTEM:
|
case HISTORY_SYSTEM:
|
||||||
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SEARCH_TYPE:
|
case SEARCH_TYPE:
|
||||||
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
target.resourceType = theRequestDetails.getResourceName();
|
target.resourceType = theRequestDetails.getResourceName();
|
||||||
target.setSearchParams(theRequestDetails);
|
target.setSearchParams(theRequestDetails);
|
||||||
|
@ -135,18 +135,18 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
break;
|
break;
|
||||||
case HISTORY_TYPE:
|
case HISTORY_TYPE:
|
||||||
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
target.resourceType = theRequestDetails.getResourceName();
|
target.resourceType = theRequestDetails.getResourceName();
|
||||||
break;
|
break;
|
||||||
case HISTORY_INSTANCE:
|
case HISTORY_INSTANCE:
|
||||||
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
if (theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
target.resourceIds = Collections.singleton(theInputResourceId);
|
target.resourceIds = Collections.singleton(theInputResourceId);
|
||||||
break;
|
break;
|
||||||
case GET_PAGE:
|
case GET_PAGE:
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
|
|
||||||
// None of the following are checked on the way in
|
// None of the following are checked on the way in
|
||||||
case ADD_TAGS:
|
case ADD_TAGS:
|
||||||
|
@ -222,7 +222,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
case DELETE:
|
case DELETE:
|
||||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||||
if (thePointcut == Pointcut.STORAGE_PRE_DELETE_EXPUNGE && myAppliesToDeleteExpunge) {
|
if (thePointcut == Pointcut.STORAGE_PRE_DELETE_EXPUNGE && myAppliesToDeleteExpunge) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
if (myAppliesToDeleteCascade != (thePointcut == Pointcut.STORAGE_CASCADE_DELETE)) {
|
if (myAppliesToDeleteCascade != (thePointcut == Pointcut.STORAGE_CASCADE_DELETE)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -233,10 +233,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
if (theInputResourceId.hasIdPart() == false) {
|
if (theInputResourceId.hasIdPart() == false) {
|
||||||
// This is a conditional DELETE, so we'll authorize it using STORAGE events instead
|
// This is a conditional DELETE, so we'll authorize it using STORAGE events instead
|
||||||
// so just let it through for now..
|
// so just let it through for now..
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
if (theInputResource == null && myClassifierCompartmentOwners != null && myClassifierCompartmentOwners.size() > 0) {
|
if (theInputResource == null && myClassifierCompartmentOwners != null && myClassifierCompartmentOwners.size() > 0) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.resource = theInputResource;
|
target.resource = theInputResource;
|
||||||
|
@ -246,14 +246,14 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case GRAPHQL:
|
case GRAPHQL:
|
||||||
return applyRuleToGraphQl(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, thePointcut);
|
return applyRuleToGraphQl(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, thePointcut, theRuleApplier);
|
||||||
case TRANSACTION:
|
case TRANSACTION:
|
||||||
return applyRuleToTransaction(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier, thePointcut, ctx);
|
return applyRuleToTransaction(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier, thePointcut, ctx);
|
||||||
case ALL:
|
case ALL:
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
case METADATA:
|
case METADATA:
|
||||||
if (theOperation == RestOperationTypeEnum.METADATA) {
|
if (theOperation == RestOperationTypeEnum.METADATA) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
default:
|
default:
|
||||||
|
@ -263,11 +263,11 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
|
|
||||||
switch (myAppliesTo) {
|
switch (myAppliesTo) {
|
||||||
case INSTANCES:
|
case INSTANCES:
|
||||||
return applyRuleToInstances(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, target);
|
return applyRuleToInstances(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, target, theRuleApplier);
|
||||||
case ALL_RESOURCES:
|
case ALL_RESOURCES:
|
||||||
if (target.resourceType != null) {
|
if (target.resourceType != null) {
|
||||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -295,7 +295,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
if (myClassifierType == ClassifierTypeEnum.ANY_ID) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
|
} else if (myClassifierType == ClassifierTypeEnum.IN_COMPARTMENT) {
|
||||||
// ok we'll check below
|
// ok we'll check below
|
||||||
}
|
}
|
||||||
|
@ -320,16 +320,16 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
case ANY_ID:
|
case ANY_ID:
|
||||||
break;
|
break;
|
||||||
case IN_COMPARTMENT:
|
case IN_COMPARTMENT:
|
||||||
return applyRuleToCompartment(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theFlags, theFhirContext, theRuleTarget);
|
return applyRuleToCompartment(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theFlags, theFhirContext, theRuleTarget, theRuleApplier);
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(Msg.code(337) + "Unable to apply security to event of applies to type " + myAppliesTo);
|
throw new IllegalStateException(Msg.code(337) + "Unable to apply security to event of applies to type " + myAppliesTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Verdict applyRuleToGraphQl(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Pointcut thePointcut) {
|
private Verdict applyRuleToGraphQl(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Pointcut thePointcut, IRuleApplier theRuleApplier) {
|
||||||
if (theOperation == RestOperationTypeEnum.GRAPHQL_REQUEST) {
|
if (theOperation == RestOperationTypeEnum.GRAPHQL_REQUEST) {
|
||||||
|
|
||||||
// Make sure that the requestor actually has sufficient access to see the given resource
|
// Make sure that the requestor actually has sufficient access to see the given resource
|
||||||
|
@ -337,14 +337,14 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Verdict applyRuleToCompartment(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Set<AuthorizationFlagsEnum> theFlags, FhirContext ctx, RuleTarget target) {
|
private Verdict applyRuleToCompartment(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, Set<AuthorizationFlagsEnum> theFlags, FhirContext ctx, RuleTarget target, IRuleApplier theRuleApplier) {
|
||||||
FhirTerser t = ctx.newTerser();
|
FhirTerser t = ctx.newTerser();
|
||||||
boolean foundMatch = false;
|
boolean foundMatch = false;
|
||||||
|
|
||||||
|
@ -377,7 +377,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
* it makes sense.
|
* it makes sense.
|
||||||
*/
|
*/
|
||||||
if (next.getResourceType().equals(target.resourceType)) {
|
if (next.getResourceType().equals(target.resourceType)) {
|
||||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, SP_RES_ID, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, SP_RES_ID, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
if (verdict != null) {
|
if (verdict != null) {
|
||||||
return verdict;
|
return verdict;
|
||||||
}
|
}
|
||||||
|
@ -423,13 +423,13 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
if (target.getSearchParams() != null && !theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
if (target.getSearchParams() != null && !theFlags.contains(AuthorizationFlagsEnum.DO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS)) {
|
||||||
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
|
for (RuntimeSearchParam nextRuntimeSearchParam : params) {
|
||||||
String name = nextRuntimeSearchParam.getName();
|
String name = nextRuntimeSearchParam.getName();
|
||||||
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, name, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
Verdict verdict = checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(target.getSearchParams(), next, name, theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
if (verdict != null) {
|
if (verdict != null) {
|
||||||
return verdict;
|
return verdict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (getMode() == PolicyEnum.ALLOW) {
|
} else if (getMode() == PolicyEnum.ALLOW) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,11 +438,11 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
if (!foundMatch) {
|
if (!foundMatch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Verdict applyRuleToInstances(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, RuleTarget target) {
|
private Verdict applyRuleToInstances(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, RuleTarget target, IRuleApplier theRuleApplier) {
|
||||||
if (target.resourceIds != null && target.resourceIds.size() > 0) {
|
if (target.resourceIds != null && target.resourceIds.size() > 0) {
|
||||||
int haveMatches = 0;
|
int haveMatches = 0;
|
||||||
for (IIdType requestAppliesToResource : target.resourceIds) {
|
for (IIdType requestAppliesToResource : target.resourceIds) {
|
||||||
|
@ -463,7 +463,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (haveMatches == target.resourceIds.size()) {
|
if (haveMatches == target.resourceIds.size()) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +477,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
|
||||||
if (getMode() == PolicyEnum.DENY) {
|
if (getMode() == PolicyEnum.DENY) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
|
||||||
Verdict verdict = null;
|
Verdict verdict = null;
|
||||||
|
@ -551,7 +551,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
* be applying security on the way out
|
* be applying security on the way out
|
||||||
*/
|
*/
|
||||||
if (allComponentsAreGets) {
|
if (allComponentsAreGets) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
return verdict;
|
return verdict;
|
||||||
|
@ -602,7 +602,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Verdict checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(Map<String, String[]> theSearchParams, IIdType theCompartmentOwner, String theSearchParamName, RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
private Verdict checkForSearchParameterMatchingCompartmentAndReturnSuccessfulVerdictOrNull(Map<String, String[]> theSearchParams, IIdType theCompartmentOwner, String theSearchParamName, RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||||
Verdict verdict = null;
|
Verdict verdict = null;
|
||||||
if (theSearchParams != null) {
|
if (theSearchParams != null) {
|
||||||
String[] values = theSearchParams.get(theSearchParamName);
|
String[] values = theSearchParams.get(theSearchParamName);
|
||||||
|
@ -611,11 +611,11 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
QualifiedParamList orParamList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextParameterValue);
|
QualifiedParamList orParamList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextParameterValue);
|
||||||
for (String next : orParamList) {
|
for (String next : orParamList) {
|
||||||
if (next.equals(theCompartmentOwner.getValue())) {
|
if (next.equals(theCompartmentOwner.getValue())) {
|
||||||
verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (next.equals(theCompartmentOwner.getIdPart())) {
|
if (next.equals(theCompartmentOwner.getIdPart())) {
|
||||||
verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ class RuleImplPatch extends BaseRule {
|
||||||
if (myAllRequests) {
|
if (myAllRequests) {
|
||||||
if (theOperation == RestOperationTypeEnum.PATCH) {
|
if (theOperation == RestOperationTypeEnum.PATCH) {
|
||||||
if (theInputResource == null && theOutputResource == null) {
|
if (theInputResource == null && theOutputResource == null) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class RuleImplUpdateHistoryRewrite extends BaseRule {
|
||||||
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags, Pointcut thePointcut) {
|
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags, Pointcut thePointcut) {
|
||||||
if (myAllRequests) {
|
if (myAllRequests) {
|
||||||
if (theRequestDetails.getId() != null && theRequestDetails.getId().hasVersionIdPart() && theOperation == RestOperationTypeEnum.UPDATE) {
|
if (theRequestDetails.getId() != null && theRequestDetails.getId().hasVersionIdPart() && theOperation == RestOperationTypeEnum.UPDATE) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,12 +113,12 @@ class SearchParameterAndValueSetRuleImpl extends RuleImplOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (myWantCode && codeMatchCount.getMatchingCodeCount() > 0) {
|
if (myWantCode && codeMatchCount.getMatchingCodeCount() > 0) {
|
||||||
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
return newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
} else if (!myWantCode) {
|
} else if (!myWantCode) {
|
||||||
boolean notFound = getMode() == PolicyEnum.ALLOW && codeMatchCount.getMatchingCodeCount() == 0;
|
boolean notFound = getMode() == PolicyEnum.ALLOW && codeMatchCount.getMatchingCodeCount() == 0;
|
||||||
boolean othersFound = getMode() == PolicyEnum.DENY && codeMatchCount.getMatchingCodeCount() < codeMatchCount.getOverallCodeCount();
|
boolean othersFound = getMode() == PolicyEnum.DENY && codeMatchCount.getMatchingCodeCount() < codeMatchCount.getOverallCodeCount();
|
||||||
if (notFound || othersFound) {
|
if (notFound || othersFound) {
|
||||||
AuthorizationInterceptor.Verdict verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
AuthorizationInterceptor.Verdict verdict = newVerdict(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, theRuleApplier);
|
||||||
if (notFound) {
|
if (notFound) {
|
||||||
troubleshootingLog
|
troubleshootingLog
|
||||||
.debug("Code was not found in VS - Verdict: {}", verdict);
|
.debug("Code was not found in VS - Verdict: {}", verdict);
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
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.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.rest.server.interceptor.auth.IAuthorizationSearchParamMatcher.MatchResult.buildMatched;
|
||||||
|
import static ca.uhn.fhir.rest.server.interceptor.auth.IAuthorizationSearchParamMatcher.MatchResult.buildUnmatched;
|
||||||
|
import static ca.uhn.fhir.rest.server.interceptor.auth.IAuthorizationSearchParamMatcher.MatchResult.buildUnsupported;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@MockitoSettings
|
||||||
|
class FhirQueryRuleTesterTest {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(FhirQueryRuleTesterTest.class);
|
||||||
|
FhirQueryRuleTester myTester = new FhirQueryRuleTester("Observation", "code=foo");
|
||||||
|
|
||||||
|
IAuthRuleTester.RuleTestRequest myTestRequest;
|
||||||
|
@Mock
|
||||||
|
IBaseResource myResource;
|
||||||
|
@Mock
|
||||||
|
RequestDetails myRequestDetails;
|
||||||
|
@Mock
|
||||||
|
IRuleApplier myRuleApplier;
|
||||||
|
@Mock
|
||||||
|
IAuthorizationSearchParamMatcher mySearchParamMatcher;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void stubConfig() {
|
||||||
|
when(myRuleApplier.getSearchParamMatcher()).thenReturn(mySearchParamMatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stubMatchResult(IAuthorizationSearchParamMatcher.MatchResult result) {
|
||||||
|
when(mySearchParamMatcher.match("Observation?code=foo", myResource)).thenReturn(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stubLogForWarning() {
|
||||||
|
when(myRuleApplier.getTroubleshootingLog()).thenReturn(ourLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchesFilter_true() {
|
||||||
|
|
||||||
|
myTestRequest = new IAuthRuleTester.RuleTestRequest(PolicyEnum.ALLOW, RestOperationTypeEnum.SEARCH_TYPE,
|
||||||
|
myRequestDetails, new IdDt("Observation/1"), myResource, myRuleApplier);
|
||||||
|
stubMatchResult(buildMatched());
|
||||||
|
|
||||||
|
boolean matches = myTester.matchesOutput(myTestRequest);
|
||||||
|
|
||||||
|
assertTrue(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notMatchesFilter_false() {
|
||||||
|
//when(myRuleApplier.getSearchParamMatcher()).thenReturn(mySearchParamMatcher);
|
||||||
|
|
||||||
|
myTestRequest = new IAuthRuleTester.RuleTestRequest(PolicyEnum.ALLOW, RestOperationTypeEnum.SEARCH_TYPE,
|
||||||
|
myRequestDetails, new IdDt("Observation/1"), myResource, myRuleApplier);
|
||||||
|
stubMatchResult(buildUnmatched());
|
||||||
|
|
||||||
|
boolean matches = myTester.matchesOutput(myTestRequest);
|
||||||
|
|
||||||
|
assertFalse(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unsupportedAllow_false() {
|
||||||
|
|
||||||
|
myTestRequest = new IAuthRuleTester.RuleTestRequest(PolicyEnum.ALLOW, RestOperationTypeEnum.SEARCH_TYPE,
|
||||||
|
myRequestDetails, new IdDt("Observation/1"), myResource, myRuleApplier);
|
||||||
|
stubMatchResult(buildUnsupported("a message"));
|
||||||
|
stubLogForWarning();
|
||||||
|
|
||||||
|
boolean matches = myTester.matchesOutput(myTestRequest);
|
||||||
|
|
||||||
|
assertFalse(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unsupportedDeny_true() {
|
||||||
|
|
||||||
|
myTestRequest = new IAuthRuleTester.RuleTestRequest(PolicyEnum.DENY, RestOperationTypeEnum.SEARCH_TYPE,
|
||||||
|
myRequestDetails, new IdDt("Observation/1"), myResource, myRuleApplier);
|
||||||
|
stubMatchResult(buildUnsupported("a message"));
|
||||||
|
stubLogForWarning();
|
||||||
|
|
||||||
|
boolean matches = myTester.matchesOutput(myTestRequest);
|
||||||
|
|
||||||
|
assertTrue(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue