Add support for conditional operations to AuthorizationInterceptor
This commit is contained in:
parent
f64337b651
commit
eee168ced6
|
@ -88,6 +88,10 @@ public enum FhirVersionEnum {
|
||||||
return ordinal() > theVersion.ordinal();
|
return ordinal() > theVersion.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isOlderThan(FhirVersionEnum theVersion) {
|
||||||
|
return ordinal() < theVersion.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given version is present on the classpath
|
* Returns true if the given version is present on the classpath
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -104,6 +104,10 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
||||||
if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
|
if (getContext().getVersion().getVersion() == FhirVersionEnum.DSTU1) {
|
||||||
resource.setId(urlId);
|
resource.setId(urlId);
|
||||||
} else {
|
} else {
|
||||||
|
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) {
|
||||||
|
resource.setId(theRequest.getId());
|
||||||
|
}
|
||||||
|
|
||||||
String matchUrl = null;
|
String matchUrl = null;
|
||||||
if (myConditionalUrlIndex != -1) {
|
if (myConditionalUrlIndex != -1) {
|
||||||
matchUrl = (String) theParams[myConditionalUrlIndex];
|
matchUrl = (String) theParams[myConditionalUrlIndex];
|
||||||
|
|
|
@ -67,29 +67,7 @@ class ConditionalParamBinder implements IParameter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||||
|
return theRequest.getConditionalUrl(myOperationType);
|
||||||
if (myOperationType == RestOperationTypeEnum.CREATE) {
|
|
||||||
String retVal = theRequest.getHeader(Constants.HEADER_IF_NONE_EXIST);
|
|
||||||
if (isBlank(retVal)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (retVal.startsWith(theRequest.getFhirServerBase())) {
|
|
||||||
retVal = retVal.substring(theRequest.getFhirServerBase().length());
|
|
||||||
}
|
|
||||||
return retVal;
|
|
||||||
} else if (myOperationType != RestOperationTypeEnum.DELETE && myOperationType != RestOperationTypeEnum.UPDATE) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?');
|
|
||||||
if (questionMarkIndex == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,10 @@ import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
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 ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
|
@ -79,10 +81,14 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInUrlForCreate", theUrlId);
|
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInUrlForCreate", theUrlId);
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||||
if (isNotBlank(theResourceId)) {
|
if (isNotBlank(theResourceId)) {
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
|
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
theResource.setId((IIdType)null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package ca.uhn.fhir.rest.method;
|
package ca.uhn.fhir.rest.method;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
@ -38,6 +40,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulResponse;
|
import ca.uhn.fhir.rest.server.IRestfulResponse;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
|
@ -52,6 +55,7 @@ public abstract class RequestDetails {
|
||||||
private String myOperation;
|
private String myOperation;
|
||||||
private Map<String, String[]> myParameters;
|
private Map<String, String[]> myParameters;
|
||||||
private byte[] myRequestContents;
|
private byte[] myRequestContents;
|
||||||
|
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
|
||||||
private String myRequestPath;
|
private String myRequestPath;
|
||||||
private RequestTypeEnum myRequestType;
|
private RequestTypeEnum myRequestType;
|
||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
|
@ -59,7 +63,6 @@ public abstract class RequestDetails {
|
||||||
private IRestfulResponse myResponse;
|
private IRestfulResponse myResponse;
|
||||||
private RestOperationTypeEnum myRestOperationType;
|
private RestOperationTypeEnum myRestOperationType;
|
||||||
private String mySecondaryOperation;
|
private String mySecondaryOperation;
|
||||||
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
|
|
||||||
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
|
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
|
||||||
private Map<Object, Object> myUserData;
|
private Map<Object, Object> myUserData;
|
||||||
protected abstract byte[] getByteStreamRequestContents();
|
protected abstract byte[] getByteStreamRequestContents();
|
||||||
|
@ -76,6 +79,40 @@ public abstract class RequestDetails {
|
||||||
return myCompleteUrl;
|
return myCompleteUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the <b>conditional URL</b> if this request has one, or <code>null</code> otherwise. For an
|
||||||
|
* update or delete method, this is the part of the URL after the <code>?</code>. For a create, this
|
||||||
|
* is the value of the <code>If-None-Exist</code> header.
|
||||||
|
*
|
||||||
|
* @param theOperationType The operation type to find the conditional URL for
|
||||||
|
* @return Returns the <b>conditional URL</b> if this request has one, or <code>null</code> otherwise
|
||||||
|
*/
|
||||||
|
public String getConditionalUrl(RestOperationTypeEnum theOperationType) {
|
||||||
|
if (theOperationType == RestOperationTypeEnum.CREATE) {
|
||||||
|
String retVal = this.getHeader(Constants.HEADER_IF_NONE_EXIST);
|
||||||
|
if (isBlank(retVal)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (retVal.startsWith(this.getFhirServerBase())) {
|
||||||
|
retVal = retVal.substring(this.getFhirServerBase().length());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
} else if (theOperationType != RestOperationTypeEnum.DELETE && theOperationType != RestOperationTypeEnum.UPDATE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getId() != null && this.getId().hasIdPart()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int questionMarkIndex = this.getCompleteUrl().indexOf('?');
|
||||||
|
if (questionMarkIndex == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getResourceName() + this.getCompleteUrl().substring(questionMarkIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fhir server base url, independant of the query being executed
|
* The fhir server base url, independant of the query being executed
|
||||||
*
|
*
|
||||||
|
@ -138,6 +175,15 @@ public abstract class RequestDetails {
|
||||||
*/
|
*/
|
||||||
public abstract Reader getReader() throws IOException;
|
public abstract Reader getReader() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an invoker that can be called from user code to advise the server interceptors
|
||||||
|
* of any nested operations being invoked within operations. This invoker acts as a proxy for
|
||||||
|
* all interceptors
|
||||||
|
*/
|
||||||
|
public IRequestOperationCallback getRequestOperationCallback() {
|
||||||
|
return myRequestOperationCallback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The part of the request URL that comes after the server base.
|
* The part of the request URL that comes after the server base.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -175,15 +221,6 @@ public abstract class RequestDetails {
|
||||||
*/
|
*/
|
||||||
public abstract String getServerBaseForRequest();
|
public abstract String getServerBaseForRequest();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an invoker that can be called from user code to advise the server interceptors
|
|
||||||
* of any nested operations being invoked within operations. This invoker acts as a proxy for
|
|
||||||
* all interceptors
|
|
||||||
*/
|
|
||||||
public IRequestOperationCallback getRequestOperationCallback() {
|
|
||||||
return myRequestOperationCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
|
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
|
||||||
return myUnqualifiedToQualifiedNames;
|
return myUnqualifiedToQualifiedNames;
|
||||||
}
|
}
|
||||||
|
@ -297,6 +334,13 @@ public abstract class RequestDetails {
|
||||||
|
|
||||||
private class RequestOperationCallback implements IRequestOperationCallback {
|
private class RequestOperationCallback implements IRequestOperationCallback {
|
||||||
|
|
||||||
|
private List<IServerInterceptor> getInterceptors() {
|
||||||
|
if (getServer() == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return getServer().getInterceptors();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resourceCreated(IBaseResource theResource) {
|
public void resourceCreated(IBaseResource theResource) {
|
||||||
for (IServerInterceptor next : getInterceptors()) {
|
for (IServerInterceptor next : getInterceptors()) {
|
||||||
|
@ -306,13 +350,6 @@ public abstract class RequestDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IServerInterceptor> getInterceptors() {
|
|
||||||
if (getServer() == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return getServer().getInterceptors();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resourceDeleted(IBaseResource theResource) {
|
public void resourceDeleted(IBaseResource theResource) {
|
||||||
for (IServerInterceptor next : getInterceptors()) {
|
for (IServerInterceptor next : getInterceptors()) {
|
||||||
|
@ -322,15 +359,6 @@ public abstract class RequestDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resourceUpdated(IBaseResource theResource) {
|
|
||||||
for (IServerInterceptor next : getInterceptors()) {
|
|
||||||
if (next instanceof IServerOperationInterceptor) {
|
|
||||||
((IServerOperationInterceptor) next).resourceUpdated(RequestDetails.this, theResource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resourcesCreated(Collection<? extends IBaseResource> theResource) {
|
public void resourcesCreated(Collection<? extends IBaseResource> theResource) {
|
||||||
for (IBaseResource next : theResource) {
|
for (IBaseResource next : theResource) {
|
||||||
|
@ -352,6 +380,15 @@ public abstract class RequestDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resourceUpdated(IBaseResource theResource) {
|
||||||
|
for (IServerInterceptor next : getInterceptors()) {
|
||||||
|
if (next instanceof IServerOperationInterceptor) {
|
||||||
|
((IServerOperationInterceptor) next).resourceUpdated(RequestDetails.this, theResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ 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 ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.annotation.Update;
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
|
@ -62,7 +63,8 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
id.setValue(locationHeader);
|
id.setValue(locationHeader);
|
||||||
if (isNotBlank(id.getResourceType())) {
|
if (isNotBlank(id.getResourceType())) {
|
||||||
if (!getResourceName().equals(id.getResourceType())) {
|
if (!getResourceName().equals(id.getResourceType())) {
|
||||||
throw new InvalidRequestException("Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
|
throw new InvalidRequestException(
|
||||||
|
"Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,8 +142,8 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInUrlForUpdate");
|
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInUrlForUpdate");
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||||
if (isBlank(theResourceId)) {
|
if (isBlank(theResourceId)) {
|
||||||
// String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInBodyForUpdate");
|
|
||||||
ourLog.warn("No resource ID found in resource body for update");
|
ourLog.warn("No resource ID found in resource body for update");
|
||||||
theResource.setId(theUrlId);
|
theResource.setId(theUrlId);
|
||||||
} else {
|
} else {
|
||||||
|
@ -151,6 +153,9 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
theResource.setId((IIdType)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
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 ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
|
@ -78,8 +79,8 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
setDefaultPolicy(theDefaultPolicy);
|
setDefaultPolicy(theDefaultPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
|
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
||||||
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theOutputResource);
|
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||||
|
|
||||||
if (decision.getDecision() == PolicyEnum.ALLOW) {
|
if (decision.getDecision() == PolicyEnum.ALLOW) {
|
||||||
return;
|
return;
|
||||||
|
@ -89,13 +90,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
|
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
||||||
List<IAuthRule> rules = buildRuleList(theRequestDetails);
|
List<IAuthRule> rules = buildRuleList(theRequestDetails);
|
||||||
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
|
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
|
||||||
|
|
||||||
Verdict verdict = null;
|
Verdict verdict = null;
|
||||||
for (IAuthRule nextRule : rules) {
|
for (IAuthRule nextRule : rules) {
|
||||||
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theOutputResource, this);
|
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this);
|
||||||
if (verdict != null) {
|
if (verdict != null) {
|
||||||
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
|
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
|
||||||
break;
|
break;
|
||||||
|
@ -126,7 +127,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation) {
|
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation, IBaseResource theRequestResource) {
|
||||||
switch (theOperation) {
|
switch (theOperation) {
|
||||||
case ADD_TAGS:
|
case ADD_TAGS:
|
||||||
case DELETE_TAGS:
|
case DELETE_TAGS:
|
||||||
|
@ -147,6 +148,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
|
|
||||||
case CREATE:
|
case CREATE:
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
|
// if (theRequestResource != null) {
|
||||||
|
// if (theRequestResource.getIdElement() != null) {
|
||||||
|
// if (theRequestResource.getIdElement().hasIdPart() == false) {
|
||||||
|
// return OperationExamineDirection.IN_UNCATEGORIZED;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
return OperationExamineDirection.IN;
|
return OperationExamineDirection.IN;
|
||||||
|
|
||||||
case META:
|
case META:
|
||||||
|
@ -204,14 +212,26 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUserOperation(RequestDetails theRequest, IBaseResource theResource, RestOperationTypeEnum operation) {
|
private void handleUserOperation(RequestDetails theRequest, IBaseResource theResource, RestOperationTypeEnum operation) {
|
||||||
applyRulesAndFailIfDeny(operation, theRequest, theResource, null);
|
applyRulesAndFailIfDeny(operation, theRequest, theResource, theResource.getIdElement(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) {
|
public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) {
|
||||||
switch (determineOperationDirection(theOperation)) {
|
IBaseResource inputResource = null;
|
||||||
|
IIdType inputResourceId = null;
|
||||||
|
|
||||||
|
switch (determineOperationDirection(theOperation, theProcessedRequest.getResource())) {
|
||||||
|
case IN_UNCATEGORIZED:
|
||||||
|
inputResourceId = theProcessedRequest.getId();
|
||||||
|
if (inputResourceId == null || inputResourceId.hasIdPart() == false) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
case IN:
|
case IN:
|
||||||
case BOTH:
|
case BOTH:
|
||||||
|
inputResource = theProcessedRequest.getResource();
|
||||||
|
inputResourceId = theProcessedRequest.getId();
|
||||||
break;
|
break;
|
||||||
case NONE:
|
case NONE:
|
||||||
case OUT:
|
case OUT:
|
||||||
|
@ -219,7 +239,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
|
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
|
||||||
applyRulesAndFailIfDeny(theOperation, requestDetails, theProcessedRequest.getResource(), null);
|
applyRulesAndFailIfDeny(theOperation, requestDetails, inputResource, inputResourceId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -236,7 +256,9 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||||
switch (determineOperationDirection(theRequestDetails.getRestOperationType())) {
|
switch (determineOperationDirection(theRequestDetails.getRestOperationType(), null)) {
|
||||||
|
case IN_UNCATEGORIZED:
|
||||||
|
return true;
|
||||||
case IN:
|
case IN:
|
||||||
case NONE:
|
case NONE:
|
||||||
return true;
|
return true;
|
||||||
|
@ -246,8 +268,6 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
FhirContext fhirContext = theRequestDetails.getServer().getFhirContext();
|
FhirContext fhirContext = theRequestDetails.getServer().getFhirContext();
|
||||||
List<IAuthRule> rules = buildRuleList(theRequestDetails);
|
|
||||||
|
|
||||||
List<IBaseResource> resources = Collections.emptyList();
|
List<IBaseResource> resources = Collections.emptyList();
|
||||||
|
|
||||||
switch (theRequestDetails.getRestOperationType()) {
|
switch (theRequestDetails.getRestOperationType()) {
|
||||||
|
@ -272,7 +292,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IBaseResource nextResponse : resources) {
|
for (IBaseResource nextResponse : resources) {
|
||||||
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, nextResponse);
|
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, null, nextResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -334,9 +354,10 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
||||||
|
|
||||||
private enum OperationExamineDirection {
|
private enum OperationExamineDirection {
|
||||||
IN,
|
IN,
|
||||||
|
IN_UNCATEGORIZED,
|
||||||
NONE,
|
NONE,
|
||||||
OUT,
|
OUT,
|
||||||
BOTH
|
BOTH,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Verdict {
|
public static class Verdict {
|
||||||
|
|
|
@ -35,7 +35,7 @@ abstract class BaseRule implements IAuthRule {
|
||||||
return myName;
|
return myName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMode(PolicyEnum theRuleMode) {
|
void setMode(PolicyEnum theRuleMode) {
|
||||||
myMode = theRuleMode;
|
myMode = theRuleMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ abstract class BaseRule implements IAuthRule {
|
||||||
return new Verdict(myMode, this);
|
return new Verdict(myMode, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PolicyEnum getMode() {
|
PolicyEnum getMode() {
|
||||||
return myMode;
|
return myMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ 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 org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||||
|
@ -37,6 +38,7 @@ public interface IAuthRule {
|
||||||
* The request
|
* The request
|
||||||
* @param theInputResource
|
* @param theInputResource
|
||||||
* The resource being input by the client, or <code>null</code>
|
* The resource being input by the client, or <code>null</code>
|
||||||
|
* @param theInputResourceId TODO
|
||||||
* @param theOutputResource
|
* @param theOutputResource
|
||||||
* The resource being returned by the server, or <code>null</code>
|
* The resource being returned by the server, or <code>null</code>
|
||||||
* @param theRuleApplier
|
* @param theRuleApplier
|
||||||
|
@ -44,7 +46,7 @@ public interface IAuthRule {
|
||||||
* nested objects in the request, such as nested requests in a transaction)
|
* nested objects in the request, such as nested requests in a transaction)
|
||||||
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
|
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
|
||||||
*/
|
*/
|
||||||
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
|
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a name for this rule, to be used in logs and error messages
|
* Returns a name for this rule, to be used in logs and error messages
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
public interface IAuthRuleBuilderAppliesTo<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule applies to resources of the given type
|
||||||
|
*/
|
||||||
|
T resourcesOfType(Class<? extends IBaseResource> theType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rule applies to all resources
|
||||||
|
*/
|
||||||
|
T allResources();
|
||||||
|
|
||||||
|
}
|
|
@ -22,11 +22,39 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
public interface IAuthRuleBuilderRule {
|
public interface IAuthRuleBuilderRule {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule applies to <code>create</code> operations with a <code>conditional</code>
|
||||||
|
* URL as a part of the request. Note that this rule will allow the conditional
|
||||||
|
* operation to proceed, but the server is expected to determine the actual target
|
||||||
|
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
|
||||||
|
* in order to authorize the actual target.
|
||||||
|
* <p>
|
||||||
|
* In other words, if the server is configured correctly, this chain will allow the
|
||||||
|
* client to perform a conditional update, but a different rule is required to actually
|
||||||
|
* authorize the target that the conditional update is determined to match.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilderRuleConditional createConditional();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This rule applies to the FHIR delete operation
|
* This rule applies to the FHIR delete operation
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOp delete();
|
IAuthRuleBuilderRuleOp delete();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule applies to <code>create</code> operations with a <code>conditional</code>
|
||||||
|
* URL as a part of the request. Note that this rule will allow the conditional
|
||||||
|
* operation to proceed, but the server is expected to determine the actual target
|
||||||
|
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
|
||||||
|
* in order to authorize the actual target.
|
||||||
|
* <p>
|
||||||
|
* In other words, if the server is configured correctly, this chain will allow the
|
||||||
|
* client to perform a conditional update, but a different rule is required to actually
|
||||||
|
* authorize the target that the conditional update is determined to match.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilderRuleConditional deleteConditional();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This rules applies to the metadata operation (retrieve the
|
* This rules applies to the metadata operation (retrieve the
|
||||||
* server's conformance statement)
|
* server's conformance statement)
|
||||||
|
@ -36,6 +64,11 @@ public interface IAuthRuleBuilderRule {
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished metadata();
|
IAuthRuleBuilderRuleOpClassifierFinished metadata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule applies to a FHIR operation (e.g. <code>$validate</code>)
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilderOperation operation();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This rule applies to any FHIR operation involving reading, including
|
* This rule applies to any FHIR operation involving reading, including
|
||||||
* <code>read</code>, <code>vread</code>, <code>search</code>, and
|
* <code>read</code>, <code>vread</code>, <code>search</code>, and
|
||||||
|
@ -49,15 +82,24 @@ public interface IAuthRuleBuilderRule {
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleTransaction transaction();
|
IAuthRuleBuilderRuleTransaction transaction();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This rule applies to <code>update</code> operations with a <code>conditional</code>
|
||||||
|
* URL as a part of the request. Note that this rule will allow the conditional
|
||||||
|
* operation to proceed, but the server is expected to determine the actual target
|
||||||
|
* of the conditional request and send a subsequent event to the {@link AuthorizationInterceptor}
|
||||||
|
* in order to authorize the actual target.
|
||||||
|
* <p>
|
||||||
|
* In other words, if the server is configured correctly, this chain will allow the
|
||||||
|
* client to perform a conditional update, but a different rule is required to actually
|
||||||
|
* authorize the target that the conditional update is determined to match.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilderRuleConditional updateConditional();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This rule applies to any FHIR operation involving writing, including
|
* This rule applies to any FHIR operation involving writing, including
|
||||||
* <code>create</code>, and <code>update</code>
|
* <code>create</code>, and <code>update</code>
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOp write();
|
IAuthRuleBuilderRuleOp write();
|
||||||
|
|
||||||
/**
|
|
||||||
* This rule applies to a FHIR operation (e.g. <code>$validate</code>)
|
|
||||||
*/
|
|
||||||
IAuthRuleBuilderOperation operation();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
public interface IAuthRuleBuilderRuleConditional extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleConditionalClassifier> {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Core Library
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2016 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface IAuthRuleBuilderRuleConditionalClassifier extends IAuthRuleFinished {
|
||||||
|
// nothing
|
||||||
|
}
|
|
@ -1,37 +1,5 @@
|
||||||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
/*
|
public interface IAuthRuleBuilderRuleOp extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleOpClassifier> {
|
||||||
* #%L
|
// nothing
|
||||||
* HAPI FHIR - Core Library
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2016 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
|
|
||||||
public interface IAuthRuleBuilderRuleOp {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rule applies to resources of the given type
|
|
||||||
*/
|
|
||||||
IAuthRuleBuilderRuleOpClassifier resourcesOfType(Class<? extends IBaseResource> theType);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rule applies to all resources
|
|
||||||
*/
|
|
||||||
IAuthRuleBuilderRuleOpClassifier allResources();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Core Library
|
* HAPI FHIR - Core Library
|
||||||
|
@ -22,15 +20,6 @@ import java.util.List;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface IAuthRuleBuilderRuleOpClassifierFinished {
|
public interface IAuthRuleBuilderRuleOpClassifierFinished extends IAuthRuleFinished {
|
||||||
/**
|
// nothing
|
||||||
* Start another rule
|
|
||||||
*/
|
|
||||||
IAuthRuleBuilder andThen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the rule list
|
|
||||||
*/
|
|
||||||
List<IAuthRule> build();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IAuthRuleFinished {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start another rule
|
||||||
|
*/
|
||||||
|
IAuthRuleBuilder andThen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the rule list
|
||||||
|
*/
|
||||||
|
List<IAuthRule> build();
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ 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 org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||||
|
@ -28,6 +29,6 @@ import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict
|
||||||
|
|
||||||
public interface IRuleApplier {
|
public interface IRuleApplier {
|
||||||
|
|
||||||
Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource);
|
Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||||
|
|
||||||
boolean applies = false;
|
boolean applies = false;
|
||||||
|
@ -67,7 +67,7 @@ class OperationRule extends BaseRule implements IAuthRule {
|
||||||
case EXTENDED_OPERATION_TYPE:
|
case EXTENDED_OPERATION_TYPE:
|
||||||
if (myAppliesToTypes != null) {
|
if (myAppliesToTypes != null) {
|
||||||
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
|
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
|
||||||
String resName = ctx.getResourceDefinition(theRequestDetails.getResourceName()).getName();
|
String resName = ctx.getResourceDefinition(next).getName();
|
||||||
if (resName.equals(theRequestDetails.getResourceName())) {
|
if (resName.equals(theRequestDetails.getResourceName())) {
|
||||||
applies = true;
|
applies = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Core Library
|
* HAPI FHIR - Core Library
|
||||||
|
@ -21,17 +19,14 @@ import java.util.ArrayList;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
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 ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
|
|
||||||
public class RuleBuilder implements IAuthRuleBuilder {
|
public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
private ArrayList<IAuthRule> myRules;
|
private ArrayList<IAuthRule> myRules;
|
||||||
|
@ -57,7 +52,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) {
|
public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) {
|
||||||
myRules.add(new Rule(theRuleName).setOp(RuleOpEnum.ALLOW_ALL));
|
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.ALLOW_ALL));
|
||||||
return new RuleBuilderFinished();
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,21 +78,30 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) {
|
public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) {
|
||||||
myRules.add(new Rule(theRuleName).setOp(RuleOpEnum.DENY_ALL));
|
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.DENY_ALL));
|
||||||
return new RuleBuilderFinished();
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class RuleBuilderFinished implements IAuthRuleBuilderRuleOpClassifierFinished {
|
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilder andThen() {
|
public IAuthRuleBuilder andThen() {
|
||||||
|
doBuildRule();
|
||||||
return RuleBuilder.this;
|
return RuleBuilder.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<IAuthRule> build() {
|
public List<IAuthRule> build() {
|
||||||
|
doBuildRule();
|
||||||
return myRules;
|
return myRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses may override
|
||||||
|
*/
|
||||||
|
protected void doBuildRule() {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RuleBuilderRule implements IAuthRuleBuilderRule {
|
private class RuleBuilderRule implements IAuthRuleBuilderRule {
|
||||||
|
@ -111,21 +115,36 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
myRuleName = theRuleName;
|
myRuleName = theRuleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleConditional createConditional() {
|
||||||
|
return new RuleBuilderRuleConditional(RestOperationTypeEnum.CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOp delete() {
|
public IAuthRuleBuilderRuleOp delete() {
|
||||||
myRuleOp = RuleOpEnum.DELETE;
|
myRuleOp = RuleOpEnum.DELETE;
|
||||||
return new RuleBuilderRuleOp();
|
return new RuleBuilderRuleOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleConditional deleteConditional() {
|
||||||
|
return new RuleBuilderRuleConditional(RestOperationTypeEnum.DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RuleBuilderFinished metadata() {
|
public RuleBuilderFinished metadata() {
|
||||||
Rule rule = new Rule(myRuleName);
|
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||||
rule.setOp(RuleOpEnum.METADATA);
|
rule.setOp(RuleOpEnum.METADATA);
|
||||||
rule.setMode(myRuleMode);
|
rule.setMode(myRuleMode);
|
||||||
myRules.add(rule);
|
myRules.add(rule);
|
||||||
return new RuleBuilderFinished();
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderOperation operation() {
|
||||||
|
return new RuleBuilderRuleOperation();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOp read() {
|
public IAuthRuleBuilderRuleOp read() {
|
||||||
myRuleOp = RuleOpEnum.READ;
|
myRuleOp = RuleOpEnum.READ;
|
||||||
|
@ -138,12 +157,58 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
return new RuleBuilderRuleTransaction();
|
return new RuleBuilderRuleTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleConditional updateConditional() {
|
||||||
|
return new RuleBuilderRuleConditional(RestOperationTypeEnum.UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOp write() {
|
public IAuthRuleBuilderRuleOp write() {
|
||||||
myRuleOp = RuleOpEnum.WRITE;
|
myRuleOp = RuleOpEnum.WRITE;
|
||||||
return new RuleBuilderRuleOp();
|
return new RuleBuilderRuleOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RuleBuilderRuleConditional implements IAuthRuleBuilderRuleConditional {
|
||||||
|
|
||||||
|
private AppliesTypeEnum myAppliesTo;
|
||||||
|
|
||||||
|
private Set<?> myAppliesToTypes;
|
||||||
|
private RestOperationTypeEnum myOperationType;
|
||||||
|
|
||||||
|
public RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) {
|
||||||
|
myOperationType = theOperationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleConditionalClassifier allResources() {
|
||||||
|
myAppliesTo = AppliesTypeEnum.ALL_RESOURCES;
|
||||||
|
return new RuleBuilderRuleConditionalClassifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleConditionalClassifier resourcesOfType(Class<? extends IBaseResource> theType) {
|
||||||
|
Validate.notNull(theType, "theType must not be null");
|
||||||
|
myAppliesTo = AppliesTypeEnum.TYPES;
|
||||||
|
myAppliesToTypes = Collections.singleton(theType);
|
||||||
|
return new RuleBuilderRuleConditionalClassifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished implements IAuthRuleBuilderRuleConditionalClassifier {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doBuildRule() {
|
||||||
|
RuleImplConditional rule = new RuleImplConditional(myRuleName);
|
||||||
|
rule.setMode(myRuleMode);
|
||||||
|
rule.setOperationType(myOperationType);
|
||||||
|
rule.setAppliesTo(myAppliesTo);
|
||||||
|
rule.setAppliesToTypes(myAppliesToTypes);
|
||||||
|
myRules.add(rule);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp {
|
private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp {
|
||||||
|
|
||||||
private AppliesTypeEnum myAppliesTo;
|
private AppliesTypeEnum myAppliesTo;
|
||||||
|
@ -171,7 +236,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
|
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
|
||||||
|
|
||||||
Rule rule = new Rule(myRuleName);
|
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||||
rule.setMode(myRuleMode);
|
rule.setMode(myRuleMode);
|
||||||
rule.setOp(myRuleOp);
|
rule.setOp(myRuleOp);
|
||||||
rule.setAppliesTo(myAppliesTo);
|
rule.setAppliesTo(myAppliesTo);
|
||||||
|
@ -222,30 +287,19 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
|
|
||||||
return new RuleBuilderRuleTransactionOp();
|
|
||||||
}
|
|
||||||
private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
|
|
||||||
Rule rule = new Rule(myRuleName);
|
|
||||||
rule.setMode(myRuleMode);
|
|
||||||
rule.setOp(myRuleOp);
|
|
||||||
rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
|
|
||||||
myRules.add(rule);
|
|
||||||
return new RuleBuilderFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
|
||||||
|
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
|
||||||
|
return new RuleBuilderRuleOperationNamed(theOperationName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderOperationNamed withAnyName() {
|
||||||
|
return new RuleBuilderRuleOperationNamed(null);
|
||||||
|
}
|
||||||
|
|
||||||
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
|
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
|
||||||
|
|
||||||
private String myOperationName;
|
private String myOperationName;
|
||||||
|
@ -258,14 +312,6 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
|
||||||
OperationRule rule = createRule();
|
|
||||||
rule.appliesToServer();
|
|
||||||
myRules.add(rule);
|
|
||||||
return new RuleBuilderFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
private OperationRule createRule() {
|
private OperationRule createRule() {
|
||||||
OperationRule rule = new OperationRule(myRuleName);
|
OperationRule rule = new OperationRule(myRuleName);
|
||||||
rule.setOperationName(myOperationName);
|
rule.setOperationName(myOperationName);
|
||||||
|
@ -273,18 +319,6 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
return rule;
|
return rule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
|
|
||||||
Validate.notNull(theType, "theType must not be null");
|
|
||||||
|
|
||||||
OperationRule rule = createRule();
|
|
||||||
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
|
|
||||||
appliesToTypes.add(theType);
|
|
||||||
rule.appliesToTypes(appliesToTypes);
|
|
||||||
myRules.add(rule);
|
|
||||||
return new RuleBuilderFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) {
|
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) {
|
||||||
Validate.notNull(theInstanceId, "theInstanceId must not be null");
|
Validate.notNull(theInstanceId, "theInstanceId must not be null");
|
||||||
|
@ -299,25 +333,53 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
return new RuleBuilderFinished();
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
||||||
|
OperationRule rule = createRule();
|
||||||
|
rule.appliesToServer();
|
||||||
|
myRules.add(rule);
|
||||||
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
|
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
|
||||||
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
|
Validate.notNull(theType, "theType must not be null");
|
||||||
return new RuleBuilderRuleOperationNamed(theOperationName);
|
|
||||||
|
OperationRule rule = createRule();
|
||||||
|
HashSet<Class<? extends IBaseResource>> appliesToTypes = new HashSet<Class<? extends IBaseResource>>();
|
||||||
|
appliesToTypes.add(theType);
|
||||||
|
rule.appliesToTypes(appliesToTypes);
|
||||||
|
myRules.add(rule);
|
||||||
|
return new RuleBuilderFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderOperationNamed withAnyName() {
|
public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
|
||||||
return new RuleBuilderRuleOperationNamed(null);
|
return new RuleBuilderRuleTransactionOp();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IAuthRuleBuilderOperation operation() {
|
public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
|
||||||
return new RuleBuilderRuleOperation();
|
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||||
}
|
rule.setMode(myRuleMode);
|
||||||
|
rule.setOp(myRuleOp);
|
||||||
|
rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
|
||||||
|
myRules.add(rule);
|
||||||
|
return new RuleBuilderFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||||
|
|
||||||
|
public class RuleImplConditional extends BaseRule implements IAuthRule {
|
||||||
|
|
||||||
|
private AppliesTypeEnum myAppliesTo;
|
||||||
|
private Set<?> myAppliesToTypes;
|
||||||
|
private RestOperationTypeEnum myOperationType;
|
||||||
|
|
||||||
|
public RuleImplConditional(String theRuleName) {
|
||||||
|
super(theRuleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||||
|
IRuleApplier theRuleApplier) {
|
||||||
|
|
||||||
|
if (theInputResourceId != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theOperation == RestOperationTypeEnum.UPDATE) {
|
||||||
|
switch (myAppliesTo) {
|
||||||
|
case ALL_RESOURCES:
|
||||||
|
break;
|
||||||
|
case TYPES:
|
||||||
|
if (theInputResource == null || !myAppliesToTypes.contains(theInputResource.getClass())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theRequestDetails.getConditionalUrl(myOperationType) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVerdict();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAppliesTo(AppliesTypeEnum theAppliesTo) {
|
||||||
|
myAppliesTo = theAppliesTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAppliesToTypes(Set<?> theAppliesToTypes) {
|
||||||
|
myAppliesToTypes = theAppliesToTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOperationType(RestOperationTypeEnum theOperationType) {
|
||||||
|
myOperationType = theOperationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ import ca.uhn.fhir.util.BundleUtil;
|
||||||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
|
|
||||||
class Rule extends BaseRule implements IAuthRule {
|
class RuleImplOp extends BaseRule implements IAuthRule {
|
||||||
|
|
||||||
private AppliesTypeEnum myAppliesTo;
|
private AppliesTypeEnum myAppliesTo;
|
||||||
private Set<?> myAppliesToTypes;
|
private Set<?> myAppliesToTypes;
|
||||||
|
@ -49,34 +49,37 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
private RuleOpEnum myOp;
|
private RuleOpEnum myOp;
|
||||||
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
||||||
|
|
||||||
public Rule(String theRuleName) {
|
public RuleImplOp(String theRuleName) {
|
||||||
super(theRuleName);
|
super(theRuleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||||
|
IRuleApplier theRuleApplier) {
|
||||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||||
|
|
||||||
IBaseResource appliesTo;
|
IBaseResource appliesToResource;
|
||||||
|
IIdType appliesToResourceId = null;
|
||||||
switch (myOp) {
|
switch (myOp) {
|
||||||
case READ:
|
case READ:
|
||||||
if (theOutputResource == null) {
|
if (theOutputResource == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
appliesTo = theOutputResource;
|
appliesToResource = theOutputResource;
|
||||||
break;
|
break;
|
||||||
case WRITE:
|
case WRITE:
|
||||||
if (theInputResource == null) {
|
if (theInputResource == null && theInputResourceId == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
appliesTo = theInputResource;
|
appliesToResource = theInputResource;
|
||||||
|
appliesToResourceId = theInputResourceId;
|
||||||
break;
|
break;
|
||||||
case DELETE:
|
case DELETE:
|
||||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||||
if (theInputResource == null) {
|
if (theInputResource == null) {
|
||||||
return newVerdict();
|
return newVerdict();
|
||||||
} else {
|
} else {
|
||||||
appliesTo = theInputResource;
|
appliesToResource = theInputResource;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -115,7 +118,7 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null);
|
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
||||||
if (newVerdict == null) {
|
if (newVerdict == null) {
|
||||||
continue;
|
continue;
|
||||||
} else if (verdict == null) {
|
} else if (verdict == null) {
|
||||||
|
@ -133,7 +136,7 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
if (nextPart.getResource() == null) {
|
if (nextPart.getResource() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, nextPart.getResource());
|
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, null, nextPart.getResource());
|
||||||
if (newVerdict == null) {
|
if (newVerdict == null) {
|
||||||
continue;
|
continue;
|
||||||
} else if (verdict == null) {
|
} else if (verdict == null) {
|
||||||
|
@ -165,9 +168,17 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
case ALL_RESOURCES:
|
case ALL_RESOURCES:
|
||||||
break;
|
break;
|
||||||
case TYPES:
|
case TYPES:
|
||||||
if (myAppliesToTypes.contains(appliesTo.getClass()) == false) {
|
if (appliesToResource != null) {
|
||||||
|
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (appliesToResourceId != null) {
|
||||||
|
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
||||||
|
if (myAppliesToTypes.contains(type) == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
|
||||||
|
@ -180,11 +191,19 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
FhirTerser t = ctx.newTerser();
|
FhirTerser t = ctx.newTerser();
|
||||||
boolean foundMatch = false;
|
boolean foundMatch = false;
|
||||||
for (IIdType next : myClassifierCompartmentOwners) {
|
for (IIdType next : myClassifierCompartmentOwners) {
|
||||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesTo, next)) {
|
if (appliesToResource != null) {
|
||||||
|
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
||||||
|
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!foundMatch) {
|
if (!foundMatch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +215,6 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
return newVerdict();
|
return newVerdict();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
|
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
|
||||||
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
|
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -238,8 +256,7 @@ class Rule extends BaseRule implements IAuthRule {
|
||||||
myClassifierType = theClassifierType;
|
myClassifierType = theClassifierType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RuleImplOp setOp(RuleOpEnum theRuleOp) {
|
||||||
public Rule setOp(RuleOpEnum theRuleOp) {
|
|
||||||
myOp = theRuleOp;
|
myOp = theRuleOp;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
|
@ -386,9 +386,9 @@ public class AbstractJaxRsResourceProviderDstu3Test {
|
||||||
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
||||||
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
|
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
|
||||||
|
|
||||||
compareResultId(1, patientCaptor.getValue());
|
assertEquals(null, patientCaptor.getValue().getIdElement().getIdPart());
|
||||||
|
assertEquals(null, patientCaptor.getValue().getIdElement().getVersionIdPart());
|
||||||
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
||||||
compareResultId(1, patientCaptor.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -70,7 +70,6 @@ import ca.uhn.fhir.util.TestUtil;
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
public class AbstractJaxRsResourceProviderTest {
|
public class AbstractJaxRsResourceProviderTest {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractJaxRsResourceProviderTest.class);
|
|
||||||
private static IGenericClient client;
|
private static IGenericClient client;
|
||||||
|
|
||||||
|
|
||||||
|
@ -372,9 +371,8 @@ public class AbstractJaxRsResourceProviderTest {
|
||||||
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
|
||||||
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
|
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
|
||||||
|
|
||||||
assertEquals("1", patientCaptor.getValue().getId().getIdPart());
|
assertEquals(null, patientCaptor.getValue().getId().getIdPart());
|
||||||
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
||||||
compareResultId(1, patientCaptor.getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
@ -166,9 +166,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||||
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart());
|
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getIdElement().getIdPart());
|
||||||
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
|
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
|
||||||
}
|
}
|
||||||
} else {
|
} else if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||||
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
|
String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart());
|
||||||
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
|
throw new InvalidRequestException(message, createErrorOperationOutcome(message, "processing"));
|
||||||
|
} else {
|
||||||
|
// As of DSTU3, ID and version in the body should be ignored for a create/update
|
||||||
|
theResource.setId("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -634,17 +634,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateTextIdFails() {
|
public void testCreateTextIdDoesntFail() {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails");
|
p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails");
|
||||||
p.addName().addFamily("Hello");
|
p.addName().addFamily("Hello");
|
||||||
p.setId("Patient/ABC");
|
p.setId("Patient/ABC");
|
||||||
try {
|
String id = myPatientDao.create(p, mySrd).getId().getIdPart();
|
||||||
myPatientDao.create(p, mySrd);
|
assertNotEquals("ABC", id);
|
||||||
fail();
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("Can not create resource with ID[ABC], ID must not be supplied"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -678,20 +674,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty());
|
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateWithIdFails() {
|
|
||||||
Patient p = new Patient();
|
|
||||||
p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails");
|
|
||||||
p.addName().addFamily("Hello");
|
|
||||||
p.setId("Patient/abc");
|
|
||||||
try {
|
|
||||||
myPatientDao.create(p, mySrd);
|
|
||||||
fail();
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("Can not create resource with ID[abc], ID must not be supplied"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateBundleAllowsDocumentAndCollection() {
|
public void testCreateBundleAllowsDocumentAndCollection() {
|
||||||
String methodName = "testCreateBundleAllowsDocumentAndCollection";
|
String methodName = "testCreateBundleAllowsDocumentAndCollection";
|
||||||
|
|
|
@ -70,6 +70,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
return new RuleBuilder()
|
return new RuleBuilder()
|
||||||
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen()
|
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen()
|
||||||
|
.allow().updateConditional().allResources()
|
||||||
.build();
|
.build();
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
|
@ -570,6 +570,107 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdAndVersionInBodyForCreate() throws IOException {
|
||||||
|
String methodName = "testIdAndVersionInBodyForCreate";
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.setId("Patient/AAA/_history/4");
|
||||||
|
pt.addName().addFamily(methodName);
|
||||||
|
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
|
||||||
|
|
||||||
|
ourLog.info("Input: {}", resource);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase + "/Patient");
|
||||||
|
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse response = ourHttpClient.execute(post);
|
||||||
|
IdType id;
|
||||||
|
try {
|
||||||
|
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response: {}", respString);
|
||||||
|
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||||
|
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
|
||||||
|
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
|
||||||
|
id = new IdType(newIdString);
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("1", id.getVersionIdPart());
|
||||||
|
assertNotEquals("AAA", id.getIdPart());
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
|
||||||
|
response = ourHttpClient.execute(get);
|
||||||
|
try {
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response: {}", respString);
|
||||||
|
assertThat(respString, containsString("<id value=\"" + id.getIdPart() + "\"/>"));
|
||||||
|
assertThat(respString, containsString("<versionId value=\"1\"/>"));
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdAndVersionInBodyForUpdate() throws IOException {
|
||||||
|
String methodName = "testIdAndVersionInBodyForUpdate";
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.setId("Patient/AAA/_history/4");
|
||||||
|
pt.addName().addFamily(methodName);
|
||||||
|
String resource = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(pt);
|
||||||
|
|
||||||
|
ourLog.info("Input: {}", resource);
|
||||||
|
|
||||||
|
HttpPost post = new HttpPost(ourServerBase + "/Patient");
|
||||||
|
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse response = ourHttpClient.execute(post);
|
||||||
|
IdType id;
|
||||||
|
try {
|
||||||
|
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response: {}", respString);
|
||||||
|
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||||
|
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
|
||||||
|
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
|
||||||
|
id = new IdType(newIdString);
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("1", id.getVersionIdPart());
|
||||||
|
assertNotEquals("AAA", id.getIdPart());
|
||||||
|
|
||||||
|
HttpPut put = new HttpPut(ourServerBase + "/Patient/" + id.getIdPart() + "/_history/1");
|
||||||
|
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
response = ourHttpClient.execute(put);
|
||||||
|
try {
|
||||||
|
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response: {}", respString);
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
|
||||||
|
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
|
||||||
|
id = new IdType(newIdString);
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("2", id.getVersionIdPart());
|
||||||
|
assertNotEquals("AAA", id.getIdPart());
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart());
|
||||||
|
response = ourHttpClient.execute(get);
|
||||||
|
try {
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info("Response: {}", respString);
|
||||||
|
assertThat(respString, containsString("<id value=\"" + id.getIdPart() + "\"/>"));
|
||||||
|
assertThat(respString, containsString("<versionId value=\"2\"/>"));
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateResourceConditionalComplex() throws IOException {
|
public void testCreateResourceConditionalComplex() throws IOException {
|
||||||
Patient pt = new Patient();
|
Patient pt = new Patient();
|
||||||
|
@ -2493,9 +2594,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
put.addHeader("Accept", Constants.CT_FHIR_JSON);
|
put.addHeader("Accept", Constants.CT_FHIR_JSON);
|
||||||
CloseableHttpResponse response = ourHttpClient.execute(put);
|
CloseableHttpResponse response = ourHttpClient.execute(put);
|
||||||
try {
|
try {
|
||||||
assertEquals(400, response.getStatusLine().getStatusCode());
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
OperationOutcome oo = myFhirCtx.newJsonParser().parseResource(OperationOutcome.class, new InputStreamReader(response.getEntity().getContent()));
|
|
||||||
assertEquals("Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"FOO\" does not match URL ID of \""+p1id.getIdPart()+"\"", oo.getIssue().get(0).getDiagnostics());
|
|
||||||
} finally {
|
} finally {
|
||||||
response.close();
|
response.close();
|
||||||
}
|
}
|
||||||
|
@ -2722,16 +2821,15 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
pt.addName().addFamily(methodName);
|
pt.addName().addFamily(methodName);
|
||||||
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
|
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
|
||||||
|
|
||||||
HttpPut post = new HttpPut(ourServerBase + "/Patient/2");
|
HttpPut post = new HttpPut(ourServerBase + "/Patient/A2");
|
||||||
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
CloseableHttpResponse response = ourHttpClient.execute(post);
|
CloseableHttpResponse response = ourHttpClient.execute(post);
|
||||||
try {
|
try {
|
||||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
ourLog.info(responseString);
|
ourLog.info(responseString);
|
||||||
assertEquals(400, response.getStatusLine().getStatusCode());
|
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||||
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
|
assertThat(responseString, containsString("/A2/"));
|
||||||
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString(
|
assertThat(responseString, not(containsString("333")));
|
||||||
"Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"333\" does not match URL ID of \"2\""));
|
|
||||||
} finally {
|
} finally {
|
||||||
response.close();
|
response.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,6 +307,8 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
||||||
myId = (IdDt) theId;
|
myId = (IdDt) theId;
|
||||||
} else if (theId != null) {
|
} else if (theId != null) {
|
||||||
myId = new IdDt(theId.getValue());
|
myId = new IdDt(theId.getValue());
|
||||||
|
} else {
|
||||||
|
myId = null;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,9 @@
|
||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
@ -25,7 +27,6 @@ import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
@ -52,7 +53,7 @@ public class UpdateDstu3Test {
|
||||||
|
|
||||||
HttpResponse status = ourClient.execute(httpPost);
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
ourLog.info("Response was:\n{}", responseContent);
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
@ -78,7 +79,7 @@ public class UpdateDstu3Test {
|
||||||
|
|
||||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
try {
|
try {
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
ourLog.info("Response was:\n{}", responseContent);
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ public class UpdateDstu3Test {
|
||||||
|
|
||||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
try {
|
try {
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
ourLog.info("Response was:\n{}", responseContent);
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
@ -117,28 +118,22 @@ public class UpdateDstu3Test {
|
||||||
public void testUpdateWrongUrlInBody() throws Exception {
|
public void testUpdateWrongUrlInBody() throws Exception {
|
||||||
|
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.setId("3");
|
patient.setId("Patient/3/_history/4");
|
||||||
patient.addIdentifier().setValue("002");
|
patient.addIdentifier().setValue("002");
|
||||||
|
|
||||||
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001");
|
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/1/_history/2");
|
||||||
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
|
||||||
HttpResponse status = ourClient.execute(httpPost);
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
ourLog.info("Response was:\n{}", responseContent);
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
|
||||||
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
assertEquals(
|
assertEquals("http://localhost:" + ourPort + "/Patient/1/_history/002", status.getFirstHeader("location").getValue());
|
||||||
"Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"3\" does not match URL ID of \"001\"",
|
assertEquals("Patient/1/_history/2", ourId.getValue());
|
||||||
oo.getIssue().get(0).getDiagnostics());
|
|
||||||
|
|
||||||
assertEquals(400, status.getStatusLine().getStatusCode());
|
|
||||||
assertNull(status.getFirstHeader("location"));
|
|
||||||
assertNull(status.getFirstHeader("content-location"));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
|
|
@ -161,6 +161,21 @@
|
||||||
if it contained custom fields that also used custom
|
if it contained custom fields that also used custom
|
||||||
types. Thanks to GitHub user @sjanic for reporting!
|
types. Thanks to GitHub user @sjanic for reporting!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Servers in STU3 mode will now ignore any ID or VersionID found in the
|
||||||
|
resource body provided by the client when processing FHIR
|
||||||
|
<![CDATA[<code>update</code>]]> operations. This change has been made
|
||||||
|
because the FHIR specification now requires servers to ignore
|
||||||
|
these values. Note that as a result of this change, resources passed
|
||||||
|
to <![CDATA[<code>@Update</code>]]> methods will always have
|
||||||
|
<![CDATA[<code>null</code>]]> ID
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Add new methods to
|
||||||
|
<![CDATA[<code>AuthorizationInterceptor</code>]]>
|
||||||
|
which allow user code to declare support for conditional
|
||||||
|
create, update, and delete.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.6" date="2016-07-07">
|
<release version="1.6" date="2016-07-07">
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
|
|
@ -91,8 +91,10 @@
|
||||||
<p>
|
<p>
|
||||||
This interceptor can help with the complicated task of determining whether a user
|
This interceptor can help with the complicated task of determining whether a user
|
||||||
has the appropriate permission to perform a given task on a FHIR server. This is
|
has the appropriate permission to perform a given task on a FHIR server. This is
|
||||||
done by declaring
|
done by declaring a set of rules that can selectively allow (whitelist) and/or selectively
|
||||||
|
block (blacklist) requests.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="doc_info_bubble">
|
<p class="doc_info_bubble">
|
||||||
AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet
|
AuthorizationInterceptor is a new feature in HAPI FHIR, and has not yet
|
||||||
been heavily tested. Use with caution, and do lots of testing! We welcome
|
been heavily tested. Use with caution, and do lots of testing! We welcome
|
||||||
|
|
Loading…
Reference in New Issue