AuthorizationInterceptor can now allow make read or write authorization decisions on a resource by instance ID
This commit is contained in:
parent
645cf3aca7
commit
44c0075409
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
*/
|
||||
|
||||
enum AppliesTypeEnum {
|
||||
ALL_RESOURCES, TYPES
|
||||
ALL_RESOURCES, TYPES, INSTANCES
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
@ -24,14 +24,14 @@ 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();
|
||||
|
||||
/**
|
||||
* Rule applies to resources of the given type
|
||||
*/
|
||||
T resourcesOfType(Class<? extends IBaseResource> theType);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -21,5 +23,39 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
*/
|
||||
|
||||
public interface IAuthRuleBuilderRuleOp extends IAuthRuleBuilderAppliesTo<IAuthRuleBuilderRuleOpClassifier> {
|
||||
// nothing
|
||||
|
||||
/**
|
||||
* Rule applies to the resource with the given ID (e.g. <code>Patient/123</code>)
|
||||
* <p>
|
||||
* See the following examples which show how theId is interpreted:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><b><code>http://example.com/Patient/123</code></b> - Any Patient resource with the ID "123" will be matched (note: the base URL part is ignored)</li>
|
||||
* <li><b><code>Patient/123</code></b> - Any Patient resource with the ID "123" will be matched</li>
|
||||
* <li><b><code>123</code></b> - Any resource of any type with the ID "123" will be matched</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param theId The ID of the resource to apply (e.g. <code>Patient/123</code>)
|
||||
* @throws IllegalArgumentException If theId does not contain an ID with at least an ID part
|
||||
* @throws NullPointerException If theId is null
|
||||
*/
|
||||
IAuthRuleFinished instance(String theId);
|
||||
|
||||
/**
|
||||
* Rule applies to the resource with the given ID (e.g. <code>Patient/123</code>)
|
||||
* <p>
|
||||
* See the following examples which show how theId is interpreted:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><b><code>http://example.com/Patient/123</code></b> - Any Patient resource with the ID "123" will be matched (note: the base URL part is ignored)</li>
|
||||
* <li><b><code>Patient/123</code></b> - Any Patient resource with the ID "123" will be matched</li>
|
||||
* <li><b><code>123</code></b> - Any resource of any type with the ID "123" will be matched</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param theId The ID of the resource to apply (e.g. <code>Patient/123</code>)
|
||||
* @throws IllegalArgumentException If theId does not contain an ID with at least an ID part
|
||||
* @throws NullPointerException If theId is null
|
||||
*/
|
||||
IAuthRuleFinished instance(IIdType theId);
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ 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.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
|
||||
public class RuleBuilder implements IAuthRuleBuilder {
|
||||
|
@ -233,6 +234,22 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
private ClassifierTypeEnum myClassifierType;
|
||||
private String myInCompartmentName;
|
||||
private Collection<? extends IIdType> myInCompartmentOwners;
|
||||
private List<IIdType> myAppliesToInstances;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RuleBuilderRuleOpClassifier() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RuleBuilderRuleOpClassifier(List<IIdType> theAppliesToInstances) {
|
||||
myAppliesToInstances = theAppliesToInstances;
|
||||
myAppliesTo = AppliesTypeEnum.INSTANCES;
|
||||
}
|
||||
|
||||
private IAuthRuleBuilderRuleOpClassifierFinished finished() {
|
||||
|
||||
|
@ -241,6 +258,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
rule.setOp(myRuleOp);
|
||||
rule.setAppliesTo(myAppliesTo);
|
||||
rule.setAppliesToTypes(myAppliesToTypes);
|
||||
rule.setAppliesToInstances(myAppliesToInstances);
|
||||
rule.setClassifierType(myClassifierType);
|
||||
rule.setClassifierCompartmentName(myInCompartmentName);
|
||||
rule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
||||
|
@ -285,6 +303,21 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished instance(String theId) {
|
||||
Validate.notBlank(theId, "theId must not be null or empty");
|
||||
return instance(new IdDt(theId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished instance(IIdType theId) {
|
||||
Validate.notNull(theId, "theId must not be null");
|
||||
Validate.notBlank(theId.getValue(), "theId.getValue() must not be null or empty");
|
||||
Validate.notBlank(theId.getIdPart(), "theId must contain an ID part");
|
||||
|
||||
return new RuleBuilderRuleOpClassifier(Arrays.asList(theId)).finished();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class RuleBuilderRuleOperation implements IAuthRuleBuilderOperation {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
|
@ -41,7 +43,7 @@ import ca.uhn.fhir.util.BundleUtil;
|
|||
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
class RuleImplOp extends BaseRule /*implements IAuthRule*/ {
|
||||
class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||
|
||||
private AppliesTypeEnum myAppliesTo;
|
||||
private Set<?> myAppliesToTypes;
|
||||
|
@ -50,6 +52,7 @@ class RuleImplOp extends BaseRule /*implements IAuthRule*/ {
|
|||
private ClassifierTypeEnum myClassifierType;
|
||||
private RuleOpEnum myOp;
|
||||
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
||||
private List<IIdType> myAppliesToInstances;
|
||||
|
||||
public RuleImplOp(String theRuleName) {
|
||||
super(theRuleName);
|
||||
|
@ -72,7 +75,7 @@ class RuleImplOp extends BaseRule /*implements IAuthRule*/ {
|
|||
appliesToResourceId = theInputResourceId;
|
||||
appliesToResourceType = theInputResourceId.getResourceType();
|
||||
break;
|
||||
// return new Verdict(PolicyEnum.ALLOW, this);
|
||||
// return new Verdict(PolicyEnum.ALLOW, this);
|
||||
case SEARCH_SYSTEM:
|
||||
case SEARCH_TYPE:
|
||||
case HISTORY_INSTANCE:
|
||||
|
@ -195,6 +198,21 @@ class RuleImplOp extends BaseRule /*implements IAuthRule*/ {
|
|||
}
|
||||
|
||||
switch (myAppliesTo) {
|
||||
case INSTANCES:
|
||||
if (appliesToResourceId != null) {
|
||||
for (IIdType next : myAppliesToInstances) {
|
||||
if (isNotBlank(next.getResourceType())) {
|
||||
if (!next.getResourceType().equals(appliesToResourceId.getResourceType())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!next.getIdPart().equals(appliesToResourceId.getIdPart())) {
|
||||
continue;
|
||||
}
|
||||
return newVerdict();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case ALL_RESOURCES:
|
||||
if (appliesToResourceType != null) {
|
||||
return new Verdict(PolicyEnum.ALLOW, this);
|
||||
|
@ -317,4 +335,8 @@ class RuleImplOp extends BaseRule /*implements IAuthRule*/ {
|
|||
myTransactionAppliesToOp = theOp;
|
||||
}
|
||||
|
||||
public void setAppliesToInstances(List<IIdType> theAppliesToInstances) {
|
||||
myAppliesToInstances = theAppliesToInstances;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,10 +2,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -1559,7 +1556,7 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
httpPost.setEntity(createFhirResourceEntity(createObservation(10, "Patient/1")));
|
||||
status = ourClient.execute(httpPost);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
|
@ -1684,6 +1681,102 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInstanceIds() throws Exception {
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance((String)null);
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId must not be null or empty", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance("");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("theId must not be null or empty", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance("Observation/");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals("theId must contain an ID part", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance(new IdDt());
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance(new IdDt(""));
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId.getValue() must not be null or empty", e.getMessage());
|
||||
}
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance(new IdDt("Observation", (String)null));
|
||||
fail();
|
||||
} catch (NullPointerException e) {
|
||||
assertEquals("theId must contain an ID part", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWriteByInstance() throws Exception {
|
||||
ourConditionalCreateId = "1";
|
||||
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
//@formatter:off
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").write().instance("Observation/900").andThen()
|
||||
.allow("Rule 1").write().instance("901").andThen()
|
||||
.build();
|
||||
//@formatter:on
|
||||
}
|
||||
});
|
||||
|
||||
HttpEntityEnclosingRequestBase httpPost;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/900");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(900, "Patient/12")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/901");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(901, "Patient/12")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/900")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertEquals(ERR403, response);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
|
||||
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertEquals(ERR403, response);
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
|
@ -1821,7 +1914,6 @@ public class AuthorizationInterceptorDstu2Test {
|
|||
}
|
||||
|
||||
MethodOutcome retVal = new MethodOutcome();
|
||||
retVal.setCreated(true);
|
||||
retVal.setResource(theResource);
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
CORS headers. Thanks to GitHub user @elnin0815 for
|
||||
the pull request!
|
||||
</action>
|
||||
<action type="add">
|
||||
AuthorizationInterceptor can now allow make read or write
|
||||
authorization decisions on a resource by instance ID
|
||||
</action>
|
||||
</release>
|
||||
<release version="2.3" date="2017-03-18">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue