Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
bb6e392e2d
|
@ -11,6 +11,7 @@ ca.uhn.fhir.context.RuntimeResourceDefinition.typeWrongVersion=This context is f
|
||||||
ca.uhn.fhir.rest.client.impl.BaseClient.ioExceptionDuringOperation=Encountered IOException when performing {0} to URL {1} - {2}
|
ca.uhn.fhir.rest.client.impl.BaseClient.ioExceptionDuringOperation=Encountered IOException when performing {0} to URL {1} - {2}
|
||||||
ca.uhn.fhir.rest.client.impl.BaseClient.failedToParseResponse=Failed to parse response from server when performing {0} to URL {1} - {2}
|
ca.uhn.fhir.rest.client.impl.BaseClient.failedToParseResponse=Failed to parse response from server when performing {0} to URL {1} - {2}
|
||||||
|
|
||||||
|
|
||||||
ca.uhn.fhir.rest.client.impl.GenericClient.cantDetermineRequestType=Unable to determing encoding of request (body does not appear to be valid XML or JSON)
|
ca.uhn.fhir.rest.client.impl.GenericClient.cantDetermineRequestType=Unable to determing encoding of request (body does not appear to be valid XML or JSON)
|
||||||
ca.uhn.fhir.rest.client.impl.GenericClient.noPagingLinkFoundInBundle=Can not perform paging operation because no link was found in Bundle with relation "{0}"
|
ca.uhn.fhir.rest.client.impl.GenericClient.noPagingLinkFoundInBundle=Can not perform paging operation because no link was found in Bundle with relation "{0}"
|
||||||
ca.uhn.fhir.rest.client.impl.GenericClient.noVersionIdForVread=No version specified in URL for 'vread' operation: {0}
|
ca.uhn.fhir.rest.client.impl.GenericClient.noVersionIdForVread=No version specified in URL for 'vread' operation: {0}
|
||||||
|
@ -19,6 +20,8 @@ ca.uhn.fhir.rest.client.impl.GenericClient.cannotDetermineResourceTypeFromUri=Un
|
||||||
ca.uhn.fhir.rest.client.impl.RestfulClientFactory.failedToRetrieveConformance=Failed to retrieve the server metadata statement during client initialization. URL used was {0}
|
ca.uhn.fhir.rest.client.impl.RestfulClientFactory.failedToRetrieveConformance=Failed to retrieve the server metadata statement during client initialization. URL used was {0}
|
||||||
ca.uhn.fhir.rest.client.impl.RestfulClientFactory.wrongVersionInConformance=The server at base URL "{0}" returned a conformance statement indicating that it supports FHIR version "{1}" which corresponds to {2}, but this client is configured to use {3} (via the FhirContext).
|
ca.uhn.fhir.rest.client.impl.RestfulClientFactory.wrongVersionInConformance=The server at base URL "{0}" returned a conformance statement indicating that it supports FHIR version "{1}" which corresponds to {2}, but this client is configured to use {3} (via the FhirContext).
|
||||||
|
|
||||||
|
ca.uhn.fhir.rest.server.interceptor.auth.RuleImplOp.invalidRequestBundleTypeForTransaction=Invalid request Bundle.type value for transaction: {0}
|
||||||
|
|
||||||
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.incorrectIdForUpdate=Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of "{0}" does not match URL ID of "{1}"
|
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.incorrectIdForUpdate=Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of "{0}" does not match URL ID of "{1}"
|
||||||
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.noIdInBodyForUpdate=Can not update resource, resource body must contain an ID element for update (PUT) operation
|
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.noIdInBodyForUpdate=Can not update resource, resource body must contain an ID element for update (PUT) operation
|
||||||
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.noIdInUrlForUpdate=Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])
|
ca.uhn.fhir.rest.server.method.BaseOutcomeReturningMethodBindingWithResourceParam.noIdInUrlForUpdate=Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])
|
||||||
|
|
|
@ -407,7 +407,13 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
b.append("rule", myDecidingRule.getName());
|
String ruleName;
|
||||||
|
if (myDecidingRule != null) {
|
||||||
|
ruleName = myDecidingRule.getName();
|
||||||
|
} else {
|
||||||
|
ruleName = "(none)";
|
||||||
|
}
|
||||||
|
b.append("rule", ruleName);
|
||||||
b.append("decision", myDecision.name());
|
b.append("decision", myDecision.name());
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ 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.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||||
import ca.uhn.fhir.util.BundleUtil;
|
import ca.uhn.fhir.util.BundleUtil;
|
||||||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||||
|
@ -24,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -35,9 +38,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -466,16 +469,18 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseBundle request = (IBaseBundle) theInputResource;
|
IBaseBundle request = (IBaseBundle) theInputResource;
|
||||||
String bundleType = BundleUtil.getBundleType(theContext, request);
|
String bundleType = defaultString(BundleUtil.getBundleType(theContext, request));
|
||||||
|
|
||||||
//noinspection EnumSwitchStatementWhichMissesCases
|
//noinspection EnumSwitchStatementWhichMissesCases
|
||||||
switch (theOp) {
|
if (theOp == RuleOpEnum.TRANSACTION) {
|
||||||
case TRANSACTION:
|
if ("transaction".equals(bundleType) || "batch".equals(bundleType)) {
|
||||||
return "transaction".equals(bundleType)
|
return true;
|
||||||
|| "batch".equals(bundleType);
|
} else {
|
||||||
default:
|
String msg = theContext.getLocalizer().getMessage(RuleImplOp.class, "invalidRequestBundleTypeForTransaction", '"' + bundleType + '"');
|
||||||
return false;
|
throw new UnprocessableEntityException(msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAppliesTo(AppliesTypeEnum theAppliesTo) {
|
public void setAppliesTo(AppliesTypeEnum theAppliesTo) {
|
||||||
|
|
|
@ -46,6 +46,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
@ -645,6 +646,13 @@ public class AuthorizationInterceptorDstu3Test {
|
||||||
.denyAll("Default Rule")
|
.denyAll("Default Rule")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleDeny(Verdict decision) {
|
||||||
|
// Make sure the toString() method on Verdict never fails
|
||||||
|
ourLog.info("Denying with decision: {}", decision);
|
||||||
|
super.handleDeny(decision);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
HttpGet httpGet;
|
HttpGet httpGet;
|
||||||
|
@ -688,6 +696,65 @@ public class AuthorizationInterceptorDstu3Test {
|
||||||
assertFalse(ourHitMethod);
|
assertFalse(ourHitMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDenyAllByDefault() throws Exception {
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow().read().resourcesOfType(Patient.class).withAnyId().andThen()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleDeny(Verdict decision) {
|
||||||
|
// Make sure the toString() method on Verdict never fails
|
||||||
|
ourLog.info("Denying with decision: {}", decision);
|
||||||
|
super.handleDeny(decision);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpGet httpGet;
|
||||||
|
HttpResponse status;
|
||||||
|
String response;
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
ourReturn = Collections.singletonList(createPatient(2));
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
extractResponseAndClose(status);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
assertTrue(ourHitMethod);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/10");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
response = extractResponseAndClose(status);
|
||||||
|
ourLog.info(response);
|
||||||
|
assertThat(response, containsString("Access denied by default policy"));
|
||||||
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
|
assertFalse(ourHitMethod);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$validate");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
response = extractResponseAndClose(status);
|
||||||
|
ourLog.info(response);
|
||||||
|
assertThat(response, containsString("Access denied by default policy"));
|
||||||
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
|
assertFalse(ourHitMethod);
|
||||||
|
|
||||||
|
ourHitMethod = false;
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/$opName");
|
||||||
|
status = ourClient.execute(httpGet);
|
||||||
|
response = extractResponseAndClose(status);
|
||||||
|
ourLog.info(response);
|
||||||
|
assertThat(response, containsString("Access denied by default policy"));
|
||||||
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
|
assertFalse(ourHitMethod);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* #528
|
* #528
|
||||||
*/
|
*/
|
||||||
|
@ -2459,6 +2526,85 @@ public class AuthorizationInterceptorDstu3Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithSearch() throws IOException {
|
||||||
|
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
|
||||||
|
.allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen()
|
||||||
|
.denyAll("deny all")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request is a transaction with 1 search
|
||||||
|
Bundle requestBundle = new Bundle();
|
||||||
|
requestBundle.setType(Bundle.BundleType.TRANSACTION);
|
||||||
|
String patientId = "10000003857";
|
||||||
|
Bundle.BundleEntryComponent bundleEntryComponent = requestBundle.addEntry();
|
||||||
|
Bundle.BundleEntryRequestComponent bundleEntryRequestComponent = new Bundle.BundleEntryRequestComponent();
|
||||||
|
bundleEntryRequestComponent.setMethod(Bundle.HTTPVerb.GET);
|
||||||
|
bundleEntryRequestComponent.setUrl(ResourceType.Patient + "?identifier=" + patientId);
|
||||||
|
bundleEntryComponent.setRequest(bundleEntryRequestComponent);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Response is a transaction response containing the search results
|
||||||
|
*/
|
||||||
|
Bundle searchResponseBundle = new Bundle();
|
||||||
|
Patient patent = new Patient();
|
||||||
|
patent.setActive(true);
|
||||||
|
patent.setId("Patient/123");
|
||||||
|
searchResponseBundle.addEntry().setResource(patent);
|
||||||
|
|
||||||
|
Bundle responseBundle = new Bundle();
|
||||||
|
responseBundle
|
||||||
|
.addEntry()
|
||||||
|
.setResource(searchResponseBundle);
|
||||||
|
ourReturn = Collections.singletonList(responseBundle);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||||
|
httpPost.setEntity(createFhirResourceEntity(requestBundle));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
String resp = extractResponseAndClose(status);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithNoBundleType() throws IOException {
|
||||||
|
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
return new RuleBuilder()
|
||||||
|
.allow("transactions").transaction().withAnyOperation().andApplyNormalRules().andThen()
|
||||||
|
.allow("read patient").read().resourcesOfType(Patient.class).withAnyId().andThen()
|
||||||
|
.denyAll("deny all")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request is a transaction with 1 search
|
||||||
|
Bundle requestBundle = new Bundle();
|
||||||
|
String patientId = "10000003857";
|
||||||
|
Bundle.BundleEntryComponent bundleEntryComponent = requestBundle.addEntry();
|
||||||
|
Bundle.BundleEntryRequestComponent bundleEntryRequestComponent = new Bundle.BundleEntryRequestComponent();
|
||||||
|
bundleEntryRequestComponent.setMethod(Bundle.HTTPVerb.GET);
|
||||||
|
bundleEntryRequestComponent.setUrl(ResourceType.Patient + "?identifier=" + patientId);
|
||||||
|
bundleEntryComponent.setRequest(bundleEntryRequestComponent);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||||
|
httpPost.setEntity(createFhirResourceEntity(requestBundle));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
String resp = extractResponseAndClose(status);
|
||||||
|
assertEquals(422, status.getStatusLine().getStatusCode());
|
||||||
|
assertThat(resp, containsString("Invalid request Bundle.type value for transaction: \\\"\\\""));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See #762
|
* See #762
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -243,6 +243,10 @@
|
||||||
new setting on the DaoConfig. This can be used to force a total to
|
new setting on the DaoConfig. This can be used to force a total to
|
||||||
always be calculated for searches, including large ones.
|
always be calculated for searches, including large ones.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
AuthorizationInterceptor now rejects transactions with an invalid or unset request
|
||||||
|
using an HTTP 422 response Bundle type instead of silently refusing to authorize them.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue