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();
|
||||
}
|
||||
|
||||
public boolean isOlderThan(FhirVersionEnum theVersion) {
|
||||
return ordinal() < theVersion.ordinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
resource.setId(urlId);
|
||||
} else {
|
||||
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) {
|
||||
resource.setId(theRequest.getId());
|
||||
}
|
||||
|
||||
String matchUrl = null;
|
||||
if (myConditionalUrlIndex != -1) {
|
||||
matchUrl = (String) theParams[myConditionalUrlIndex];
|
||||
|
|
|
@ -67,29 +67,7 @@ class ConditionalParamBinder implements IParameter {
|
|||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||
|
||||
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);
|
||||
return theRequest.getConditionalUrl(myOperationType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,8 +27,10 @@ import java.util.Collections;
|
|||
import java.util.Set;
|
||||
|
||||
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.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
|
@ -79,9 +81,13 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
|||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInUrlForCreate", theUrlId);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
if (isNotBlank(theResourceId)) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
|
||||
throw new InvalidRequestException(msg);
|
||||
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||
if (isNotBlank(theResourceId)) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "idInBodyForCreate", theResourceId);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
} else {
|
||||
theResource.setId((IIdType)null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.rest.method;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IRestfulResponse;
|
||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
@ -52,6 +55,7 @@ public abstract class RequestDetails {
|
|||
private String myOperation;
|
||||
private Map<String, String[]> myParameters;
|
||||
private byte[] myRequestContents;
|
||||
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
|
||||
private String myRequestPath;
|
||||
private RequestTypeEnum myRequestType;
|
||||
private String myResourceName;
|
||||
|
@ -59,7 +63,6 @@ public abstract class RequestDetails {
|
|||
private IRestfulResponse myResponse;
|
||||
private RestOperationTypeEnum myRestOperationType;
|
||||
private String mySecondaryOperation;
|
||||
private IRequestOperationCallback myRequestOperationCallback = new RequestOperationCallback();
|
||||
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
|
||||
private Map<Object, Object> myUserData;
|
||||
protected abstract byte[] getByteStreamRequestContents();
|
||||
|
@ -76,6 +79,40 @@ public abstract class RequestDetails {
|
|||
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
|
||||
*
|
||||
|
@ -138,6 +175,15 @@ public abstract class RequestDetails {
|
|||
*/
|
||||
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.
|
||||
* <p>
|
||||
|
@ -175,15 +221,6 @@ public abstract class RequestDetails {
|
|||
*/
|
||||
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() {
|
||||
return myUnqualifiedToQualifiedNames;
|
||||
}
|
||||
|
@ -297,6 +334,13 @@ public abstract class RequestDetails {
|
|||
|
||||
private class RequestOperationCallback implements IRequestOperationCallback {
|
||||
|
||||
private List<IServerInterceptor> getInterceptors() {
|
||||
if (getServer() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return getServer().getInterceptors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(IBaseResource theResource) {
|
||||
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
|
||||
public void resourceDeleted(IBaseResource theResource) {
|
||||
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
|
||||
public void resourcesCreated(Collection<? extends IBaseResource> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -30,6 +30,7 @@ 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.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
|
@ -62,7 +63,8 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
|||
id.setValue(locationHeader);
|
||||
if (isNotBlank(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,17 +142,20 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
|||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInUrlForUpdate");
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
if (isBlank(theResourceId)) {
|
||||
// String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInBodyForUpdate");
|
||||
ourLog.warn("No resource ID found in resource body for update");
|
||||
theResource.setId(theUrlId);
|
||||
} else {
|
||||
if (!theResourceId.equals(theUrlId)) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "incorrectIdForUpdate", theResourceId, theUrlId);
|
||||
throw new InvalidRequestException(msg);
|
||||
if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||
if (isBlank(theResourceId)) {
|
||||
ourLog.warn("No resource ID found in resource body for update");
|
||||
theResource.setId(theUrlId);
|
||||
} else {
|
||||
if (!theResourceId.equals(theUrlId)) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "incorrectIdForUpdate", theResourceId, theUrlId);
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
theResource.setId((IIdType)null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
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.model.api.Bundle;
|
||||
|
@ -78,8 +79,8 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
setDefaultPolicy(theDefaultPolicy);
|
||||
}
|
||||
|
||||
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
|
||||
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theOutputResource);
|
||||
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource) {
|
||||
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource);
|
||||
|
||||
if (decision.getDecision() == PolicyEnum.ALLOW) {
|
||||
return;
|
||||
|
@ -89,13 +90,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
}
|
||||
|
||||
@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);
|
||||
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
|
||||
|
||||
Verdict verdict = null;
|
||||
for (IAuthRule nextRule : rules) {
|
||||
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theOutputResource, this);
|
||||
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theInputResourceId, theOutputResource, this);
|
||||
if (verdict != null) {
|
||||
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
|
||||
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) {
|
||||
case ADD_TAGS:
|
||||
case DELETE_TAGS:
|
||||
|
@ -147,6 +148,13 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
|
||||
case CREATE:
|
||||
case UPDATE:
|
||||
// if (theRequestResource != null) {
|
||||
// if (theRequestResource.getIdElement() != null) {
|
||||
// if (theRequestResource.getIdElement().hasIdPart() == false) {
|
||||
// return OperationExamineDirection.IN_UNCATEGORIZED;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return OperationExamineDirection.IN;
|
||||
|
||||
case META:
|
||||
|
@ -204,14 +212,26 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
}
|
||||
|
||||
private void handleUserOperation(RequestDetails theRequest, IBaseResource theResource, RestOperationTypeEnum operation) {
|
||||
applyRulesAndFailIfDeny(operation, theRequest, theResource, null);
|
||||
applyRulesAndFailIfDeny(operation, theRequest, theResource, theResource.getIdElement(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 BOTH:
|
||||
inputResource = theProcessedRequest.getResource();
|
||||
inputResourceId = theProcessedRequest.getId();
|
||||
break;
|
||||
case NONE:
|
||||
case OUT:
|
||||
|
@ -219,7 +239,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
}
|
||||
|
||||
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
|
||||
applyRulesAndFailIfDeny(theOperation, requestDetails, theProcessedRequest.getResource(), null);
|
||||
applyRulesAndFailIfDeny(theOperation, requestDetails, inputResource, inputResourceId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -236,7 +256,9 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
|
||||
@Override
|
||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||
switch (determineOperationDirection(theRequestDetails.getRestOperationType())) {
|
||||
switch (determineOperationDirection(theRequestDetails.getRestOperationType(), null)) {
|
||||
case IN_UNCATEGORIZED:
|
||||
return true;
|
||||
case IN:
|
||||
case NONE:
|
||||
return true;
|
||||
|
@ -246,8 +268,6 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
}
|
||||
|
||||
FhirContext fhirContext = theRequestDetails.getServer().getFhirContext();
|
||||
List<IAuthRule> rules = buildRuleList(theRequestDetails);
|
||||
|
||||
List<IBaseResource> resources = Collections.emptyList();
|
||||
|
||||
switch (theRequestDetails.getRestOperationType()) {
|
||||
|
@ -272,7 +292,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
}
|
||||
|
||||
for (IBaseResource nextResponse : resources) {
|
||||
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, nextResponse);
|
||||
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, null, nextResponse);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -334,9 +354,10 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
|
|||
|
||||
private enum OperationExamineDirection {
|
||||
IN,
|
||||
IN_UNCATEGORIZED,
|
||||
NONE,
|
||||
OUT,
|
||||
BOTH
|
||||
BOTH,
|
||||
}
|
||||
|
||||
public static class Verdict {
|
||||
|
|
|
@ -35,7 +35,7 @@ abstract class BaseRule implements IAuthRule {
|
|||
return myName;
|
||||
}
|
||||
|
||||
public void setMode(PolicyEnum theRuleMode) {
|
||||
void setMode(PolicyEnum theRuleMode) {
|
||||
myMode = theRuleMode;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ abstract class BaseRule implements IAuthRule {
|
|||
return new Verdict(myMode, this);
|
||||
}
|
||||
|
||||
public PolicyEnum getMode() {
|
||||
PolicyEnum getMode() {
|
||||
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.IIdType;
|
||||
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
|
@ -37,6 +38,7 @@ public interface IAuthRule {
|
|||
* The request
|
||||
* @param theInputResource
|
||||
* The resource being input by the client, or <code>null</code>
|
||||
* @param theInputResourceId TODO
|
||||
* @param theOutputResource
|
||||
* The resource being returned by the server, or <code>null</code>
|
||||
* @param theRuleApplier
|
||||
|
@ -44,7 +46,7 @@ public interface IAuthRule {
|
|||
* 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
|
||||
*/
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
* server's conformance statement)
|
||||
|
@ -36,6 +64,11 @@ public interface IAuthRuleBuilderRule {
|
|||
*/
|
||||
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
|
||||
* <code>read</code>, <code>vread</code>, <code>search</code>, and
|
||||
|
@ -49,15 +82,24 @@ public interface IAuthRuleBuilderRule {
|
|||
*/
|
||||
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
|
||||
* <code>create</code>, and <code>update</code>
|
||||
*/
|
||||
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;
|
||||
|
||||
/*
|
||||
* #%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%
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
public interface IAuthRuleBuilderRuleOp extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleOpClassifier> {
|
||||
// nothing
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -22,15 +20,6 @@ import java.util.List;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
public interface IAuthRuleBuilderRuleOpClassifierFinished {
|
||||
/**
|
||||
* Start another rule
|
||||
*/
|
||||
IAuthRuleBuilder andThen();
|
||||
|
||||
/**
|
||||
* Build the rule list
|
||||
*/
|
||||
List<IAuthRule> build();
|
||||
|
||||
public interface IAuthRuleBuilderRuleOpClassifierFinished extends IAuthRuleFinished {
|
||||
// nothing
|
||||
}
|
||||
|
|
|
@ -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.IIdType;
|
||||
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
|
@ -28,6 +29,6 @@ import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict
|
|||
|
||||
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
|
||||
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();
|
||||
|
||||
boolean applies = false;
|
||||
|
@ -67,7 +67,7 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
case EXTENDED_OPERATION_TYPE:
|
||||
if (myAppliesToTypes != null) {
|
||||
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
|
||||
String resName = ctx.getResourceDefinition(theRequestDetails.getResourceName()).getName();
|
||||
String resName = ctx.getResourceDefinition(next).getName();
|
||||
if (resName.equals(theRequestDetails.getResourceName())) {
|
||||
applies = true;
|
||||
break;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -12,7 +10,7 @@ import java.util.ArrayList;
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -21,17 +19,14 @@ import java.util.ArrayList;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
|
||||
public class RuleBuilder implements IAuthRuleBuilder {
|
||||
|
||||
private ArrayList<IAuthRule> myRules;
|
||||
|
@ -57,7 +52,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -83,21 +78,30 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
|
||||
private final class RuleBuilderFinished implements IAuthRuleBuilderRuleOpClassifierFinished {
|
||||
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished {
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilder andThen() {
|
||||
doBuildRule();
|
||||
return RuleBuilder.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IAuthRule> build() {
|
||||
doBuildRule();
|
||||
return myRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may override
|
||||
*/
|
||||
protected void doBuildRule() {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
private class RuleBuilderRule implements IAuthRuleBuilderRule {
|
||||
|
@ -111,21 +115,36 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
myRuleName = theRuleName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleConditional createConditional() {
|
||||
return new RuleBuilderRuleConditional(RestOperationTypeEnum.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOp delete() {
|
||||
myRuleOp = RuleOpEnum.DELETE;
|
||||
return new RuleBuilderRuleOp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleConditional deleteConditional() {
|
||||
return new RuleBuilderRuleConditional(RestOperationTypeEnum.DELETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuleBuilderFinished metadata() {
|
||||
Rule rule = new Rule(myRuleName);
|
||||
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||
rule.setOp(RuleOpEnum.METADATA);
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderOperation operation() {
|
||||
return new RuleBuilderRuleOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOp read() {
|
||||
myRuleOp = RuleOpEnum.READ;
|
||||
|
@ -138,12 +157,58 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderRuleTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleConditional updateConditional() {
|
||||
return new RuleBuilderRuleConditional(RestOperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOp write() {
|
||||
myRuleOp = RuleOpEnum.WRITE;
|
||||
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 AppliesTypeEnum myAppliesTo;
|
||||
|
@ -171,7 +236,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
|
||||
|
||||
Rule rule = new Rule(myRuleName);
|
||||
RuleImplOp rule = new RuleImplOp(myRuleName);
|
||||
rule.setMode(myRuleMode);
|
||||
rule.setOp(myRuleOp);
|
||||
rule.setAppliesTo(myAppliesTo);
|
||||
|
@ -222,29 +287,18 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
|
||||
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
|
||||
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
|
||||
return new RuleBuilderRuleOperationNamed(theOperationName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
||||
@Override
|
||||
public IAuthRuleBuilderOperationNamed withAnyName() {
|
||||
return new RuleBuilderRuleOperationNamed(null);
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleOperationNamed implements IAuthRuleBuilderOperationNamed {
|
||||
|
||||
|
@ -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() {
|
||||
OperationRule rule = new OperationRule(myRuleName);
|
||||
rule.setOperationName(myOperationName);
|
||||
|
@ -273,18 +319,6 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
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
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) {
|
||||
Validate.notNull(theInstanceId, "theInstanceId must not be null");
|
||||
|
@ -299,25 +333,53 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToServer();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderOperationNamed named(String theOperationName) {
|
||||
Validate.notBlank(theOperationName, "theOperationName must not be null or empty");
|
||||
return new RuleBuilderRuleOperationNamed(theOperationName);
|
||||
}
|
||||
@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
|
||||
public IAuthRuleBuilderOperationNamed withAnyName() {
|
||||
return new RuleBuilderRuleOperationNamed(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderOperation operation() {
|
||||
return new RuleBuilderRuleOperation();
|
||||
private class RuleBuilderRuleTransaction implements IAuthRuleBuilderRuleTransaction {
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleTransactionOp withAnyOperation() {
|
||||
return new RuleBuilderRuleTransactionOp();
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleTransactionOp implements IAuthRuleBuilderRuleTransactionOp {
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished andApplyNormalRules() {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -39,7 +39,7 @@ import ca.uhn.fhir.util.BundleUtil;
|
|||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
class Rule extends BaseRule implements IAuthRule {
|
||||
class RuleImplOp extends BaseRule implements IAuthRule {
|
||||
|
||||
private AppliesTypeEnum myAppliesTo;
|
||||
private Set<?> myAppliesToTypes;
|
||||
|
@ -49,34 +49,37 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
private RuleOpEnum myOp;
|
||||
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
||||
|
||||
public Rule(String theRuleName) {
|
||||
public RuleImplOp(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
IBaseResource appliesTo;
|
||||
IBaseResource appliesToResource;
|
||||
IIdType appliesToResourceId = null;
|
||||
switch (myOp) {
|
||||
case READ:
|
||||
if (theOutputResource == null) {
|
||||
return null;
|
||||
}
|
||||
appliesTo = theOutputResource;
|
||||
appliesToResource = theOutputResource;
|
||||
break;
|
||||
case WRITE:
|
||||
if (theInputResource == null) {
|
||||
if (theInputResource == null && theInputResourceId == null) {
|
||||
return null;
|
||||
}
|
||||
appliesTo = theInputResource;
|
||||
appliesToResource = theInputResource;
|
||||
appliesToResourceId = theInputResourceId;
|
||||
break;
|
||||
case DELETE:
|
||||
if (theOperation == RestOperationTypeEnum.DELETE) {
|
||||
if (theInputResource == null) {
|
||||
return newVerdict();
|
||||
} else {
|
||||
appliesTo = theInputResource;
|
||||
appliesToResource = theInputResource;
|
||||
}
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null);
|
||||
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null, null);
|
||||
if (newVerdict == null) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
|
@ -133,7 +136,7 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
if (nextPart.getResource() == null) {
|
||||
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) {
|
||||
continue;
|
||||
} else if (verdict == null) {
|
||||
|
@ -165,8 +168,16 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
case ALL_RESOURCES:
|
||||
break;
|
||||
case TYPES:
|
||||
if (myAppliesToTypes.contains(appliesTo.getClass()) == false) {
|
||||
return null;
|
||||
if (appliesToResource != null) {
|
||||
if (myAppliesToTypes.contains(appliesToResource.getClass()) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null) {
|
||||
Class<? extends IBaseResource> type = theRequestDetails.getServer().getFhirContext().getResourceDefinition(appliesToResourceId.getResourceType()).getImplementingClass();
|
||||
if (myAppliesToTypes.contains(type) == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -180,9 +191,17 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
FhirTerser t = ctx.newTerser();
|
||||
boolean foundMatch = false;
|
||||
for (IIdType next : myClassifierCompartmentOwners) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesTo, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
if (appliesToResource != null) {
|
||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, appliesToResource, next)) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (appliesToResourceId != null && appliesToResourceId.hasResourceType() && appliesToResourceId.hasIdPart()) {
|
||||
if (appliesToResourceId.toUnqualifiedVersionless().getValue().equals(next.toUnqualifiedVersionless().getValue())) {
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundMatch) {
|
||||
|
@ -196,7 +215,6 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
return newVerdict();
|
||||
}
|
||||
|
||||
|
||||
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
|
||||
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
|
||||
return false;
|
||||
|
@ -238,8 +256,7 @@ class Rule extends BaseRule implements IAuthRule {
|
|||
myClassifierType = theClassifierType;
|
||||
}
|
||||
|
||||
|
||||
public Rule setOp(RuleOpEnum theRuleOp) {
|
||||
public RuleImplOp setOp(RuleOpEnum theRuleOp) {
|
||||
myOp = theRuleOp;
|
||||
return this;
|
||||
}
|
|
@ -386,9 +386,9 @@ public class AbstractJaxRsResourceProviderDstu3Test {
|
|||
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();
|
||||
|
||||
compareResultId(1, patientCaptor.getValue());
|
||||
assertEquals(null, patientCaptor.getValue().getIdElement().getIdPart());
|
||||
assertEquals(null, patientCaptor.getValue().getIdElement().getVersionIdPart());
|
||||
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
||||
compareResultId(1, patientCaptor.getValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
|
@ -70,7 +70,6 @@ import ca.uhn.fhir.util.TestUtil;
|
|||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class AbstractJaxRsResourceProviderTest {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AbstractJaxRsResourceProviderTest.class);
|
||||
private static IGenericClient client;
|
||||
|
||||
|
||||
|
@ -372,9 +371,8 @@ public class AbstractJaxRsResourceProviderTest {
|
|||
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();
|
||||
|
||||
assertEquals("1", patientCaptor.getValue().getId().getIdPart());
|
||||
assertEquals(null, patientCaptor.getValue().getId().getIdPart());
|
||||
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
|
||||
compareResultId(1, patientCaptor.getValue());
|
||||
}
|
||||
|
||||
@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());
|
||||
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());
|
||||
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
|
||||
public void testCreateTextIdFails() {
|
||||
public void testCreateTextIdDoesntFail() {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails");
|
||||
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"));
|
||||
}
|
||||
String id = myPatientDao.create(p, mySrd).getId().getIdPart();
|
||||
assertNotEquals("ABC", id);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -678,20 +674,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
|||
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
|
||||
public void testCreateBundleAllowsDocumentAndCollection() {
|
||||
String methodName = "testCreateBundleAllowsDocumentAndCollection";
|
||||
|
|
|
@ -70,6 +70,7 @@ public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResou
|
|||
//@formatter:off
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen()
|
||||
.allow().updateConditional().allResources()
|
||||
.build();
|
||||
//@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
|
||||
public void testCreateResourceConditionalComplex() throws IOException {
|
||||
Patient pt = new Patient();
|
||||
|
@ -2493,9 +2594,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
put.addHeader("Accept", Constants.CT_FHIR_JSON);
|
||||
CloseableHttpResponse response = ourHttpClient.execute(put);
|
||||
try {
|
||||
assertEquals(400, 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());
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
@ -2722,16 +2821,15 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
pt.addName().addFamily(methodName);
|
||||
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")));
|
||||
CloseableHttpResponse response = ourHttpClient.execute(post);
|
||||
try {
|
||||
String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(responseString);
|
||||
assertEquals(400, response.getStatusLine().getStatusCode());
|
||||
OperationOutcome oo = myFhirCtx.newXmlParser().parseResource(OperationOutcome.class, responseString);
|
||||
assertThat(oo.getIssue().get(0).getDiagnostics(), containsString(
|
||||
"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\""));
|
||||
assertEquals(201, response.getStatusLine().getStatusCode());
|
||||
assertThat(responseString, containsString("/A2/"));
|
||||
assertThat(responseString, not(containsString("333")));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
|
|
|
@ -307,6 +307,8 @@ public abstract class BaseResource extends BaseElement implements IResource {
|
|||
myId = (IdDt) theId;
|
||||
} else if (theId != null) {
|
||||
myId = new IdDt(theId.getValue());
|
||||
} else {
|
||||
myId = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,9 @@
|
|||
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 org.apache.commons.io.IOUtils;
|
||||
|
@ -25,7 +27,6 @@ import org.junit.BeforeClass;
|
|||
import org.junit.Test;
|
||||
|
||||
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.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
|
@ -52,7 +53,7 @@ public class UpdateDstu3Test {
|
|||
|
||||
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());
|
||||
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
|
@ -78,7 +79,7 @@ public class UpdateDstu3Test {
|
|||
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
|
@ -101,7 +102,7 @@ public class UpdateDstu3Test {
|
|||
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
|
@ -117,28 +118,22 @@ public class UpdateDstu3Test {
|
|||
public void testUpdateWrongUrlInBody() throws Exception {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("3");
|
||||
patient.setId("Patient/3/_history/4");
|
||||
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")));
|
||||
|
||||
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());
|
||||
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
|
||||
OperationOutcome oo = ourCtx.newXmlParser().parseResource(OperationOutcome.class, responseContent);
|
||||
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 \"3\" does not match URL ID of \"001\"",
|
||||
oo.getIssue().get(0).getDiagnostics());
|
||||
|
||||
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||
assertNull(status.getFirstHeader("location"));
|
||||
assertNull(status.getFirstHeader("content-location"));
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/1/_history/002", status.getFirstHeader("location").getValue());
|
||||
assertEquals("Patient/1/_history/2", ourId.getValue());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
|
@ -161,6 +161,21 @@
|
|||
if it contained custom fields that also used custom
|
||||
types. Thanks to GitHub user @sjanic for reporting!
|
||||
</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 version="1.6" date="2016-07-07">
|
||||
<action type="fix">
|
||||
|
|
|
@ -91,8 +91,10 @@
|
|||
<p>
|
||||
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
|
||||
done by declaring
|
||||
done by declaring a set of rules that can selectively allow (whitelist) and/or selectively
|
||||
block (blacklist) requests.
|
||||
</p>
|
||||
|
||||
<p class="doc_info_bubble">
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue