Add support for patch in AuthorizationInterceptor
This commit is contained in:
parent
bbf47454b3
commit
495fd9f68e
|
@ -4,6 +4,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
|
@ -140,5 +141,21 @@ public class AuthorizationInterceptors {
|
|||
}
|
||||
};
|
||||
//END SNIPPET: authorizeTenantAction
|
||||
|
||||
|
||||
//START SNIPPET: patchAll
|
||||
new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
// Authorize patch requests
|
||||
.allow().patch().allRequests().andThen()
|
||||
// Authorize actual writes that patch may perform
|
||||
.allow().write().allResources().inCompartment("Patient", new IdType("Patient/123")).andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
//END SNIPPET: patchAll
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,64 +40,6 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
unregisterInterceptors();
|
||||
}
|
||||
|
||||
/**
|
||||
* See #778
|
||||
*/
|
||||
@Test
|
||||
public void testReadingObservationAccessRight() {
|
||||
Practitioner practitioner1 = new Practitioner();
|
||||
final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Practitioner practitioner2 = new Practitioner();
|
||||
final IIdType practitionerId2 = myClient.create().resource(practitioner2).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
final IIdType patientId = myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
// allow write all Observation resource
|
||||
// allow read only Observation resource in which it has a practitioner1 or practitioner2 compartment
|
||||
return new RuleBuilder().allow()
|
||||
.write()
|
||||
.resourcesOfType(Observation.class)
|
||||
.withAnyId()
|
||||
.andThen()
|
||||
.allow()
|
||||
.read()
|
||||
.resourcesOfType(Observation.class)
|
||||
.inCompartment("Practitioner", Arrays.asList(practitionerId1, practitionerId2))
|
||||
.andThen()
|
||||
.denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(ObservationStatus.FINAL);
|
||||
obs1.setPerformer(
|
||||
Arrays.asList(new Reference(practitionerId1), new Reference(practitionerId2)));
|
||||
IIdType oid1 = myClient.create().resource(obs1).execute().getId().toUnqualified();
|
||||
|
||||
// Observation with practitioner1 and practitioner1 as the Performer -> should have the read access
|
||||
myClient.read().resource(Observation.class).withId(oid1).execute();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(ObservationStatus.FINAL);
|
||||
obs2.setSubject(new Reference(patientId));
|
||||
IIdType oid2 = myClient.create().resource(obs2).execute().getId().toUnqualified();
|
||||
|
||||
// Observation with patient as the subject -> read access should be blocked
|
||||
try {
|
||||
myClient.read().resource(Observation.class).withId(oid2).execute();
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See #667
|
||||
*/
|
||||
|
@ -455,13 +397,11 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
});
|
||||
|
||||
|
||||
|
||||
// Create a bundle that will be used as a transaction
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
|
||||
|
||||
|
||||
String encounterId = "123-123";
|
||||
String encounterSystem = "http://our.internal.code.system/encounter";
|
||||
Encounter encounter = new Encounter();
|
||||
|
@ -523,8 +463,117 @@ public class AuthorizationInterceptorResourceProviderR4Test extends BaseResource
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchWithinCompartment() {
|
||||
Patient pt1 = new Patient();
|
||||
pt1.setActive(true);
|
||||
final IIdType pid1 = myClient.create().resource(pt1).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(ObservationStatus.FINAL);
|
||||
obs1.setSubject(new Reference(pid1));
|
||||
IIdType oid1 = myClient.create().resource(obs1).execute().getId().toUnqualified();
|
||||
|
||||
Patient pt2 = new Patient();
|
||||
pt2.setActive(false);
|
||||
final IIdType pid2 = myClient.create().resource(pt2).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(ObservationStatus.FINAL);
|
||||
obs2.setSubject(new Reference(pid2));
|
||||
IIdType oid2 = myClient.create().resource(obs2).execute().getId().toUnqualified();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().patch().allRequests().andThen()
|
||||
.allow().write().allResources().inCompartment("Patient", pid1).andThen()
|
||||
.allow().read().allResources().withAnyId().andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
String patchBody = "[\n" +
|
||||
" { \"op\": \"replace\", \"path\": \"Observation/status\", \"value\": \"amended\" }\n" +
|
||||
" ]";
|
||||
|
||||
// Allowed
|
||||
myClient.patch().withBody(patchBody).withId(oid1).execute();
|
||||
obs1 = myClient.read().resource(Observation.class).withId(oid1.toUnqualifiedVersionless()).execute();
|
||||
assertEquals(ObservationStatus.AMENDED, obs1.getStatus());
|
||||
|
||||
// Denied
|
||||
try {
|
||||
myClient.patch().withBody(patchBody).withId(oid2).execute();
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
obs2 = myClient.read().resource(Observation.class).withId(oid2.toUnqualifiedVersionless()).execute();
|
||||
assertEquals(ObservationStatus.FINAL, obs2.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #778
|
||||
*/
|
||||
@Test
|
||||
public void testReadingObservationAccessRight() {
|
||||
Practitioner practitioner1 = new Practitioner();
|
||||
final IIdType practitionerId1 = myClient.create().resource(practitioner1).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Practitioner practitioner2 = new Practitioner();
|
||||
final IIdType practitionerId2 = myClient.create().resource(practitioner2).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
final IIdType patientId = myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
// allow write all Observation resource
|
||||
// allow read only Observation resource in which it has a practitioner1 or practitioner2 compartment
|
||||
return new RuleBuilder().allow()
|
||||
.write()
|
||||
.resourcesOfType(Observation.class)
|
||||
.withAnyId()
|
||||
.andThen()
|
||||
.allow()
|
||||
.read()
|
||||
.resourcesOfType(Observation.class)
|
||||
.inCompartment("Practitioner", Arrays.asList(practitionerId1, practitionerId2))
|
||||
.andThen()
|
||||
.denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
Observation obs1 = new Observation();
|
||||
obs1.setStatus(ObservationStatus.FINAL);
|
||||
obs1.setPerformer(
|
||||
Arrays.asList(new Reference(practitionerId1), new Reference(practitionerId2)));
|
||||
IIdType oid1 = myClient.create().resource(obs1).execute().getId().toUnqualified();
|
||||
|
||||
// Observation with practitioner1 and practitioner1 as the Performer -> should have the read access
|
||||
myClient.read().resource(Observation.class).withId(oid1).execute();
|
||||
|
||||
Observation obs2 = new Observation();
|
||||
obs2.setStatus(ObservationStatus.FINAL);
|
||||
obs2.setSubject(new Reference(patientId));
|
||||
IIdType oid2 = myClient.create().resource(obs2).execute().getId().toUnqualified();
|
||||
|
||||
// Observation with patient as the subject -> read access should be blocked
|
||||
try {
|
||||
myClient.read().resource(Observation.class).withId(oid2).execute();
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterInterceptors() {
|
||||
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourRestServer.getInterceptors())) {
|
||||
for (IServerInterceptor next : new ArrayList<>(ourRestServer.getInterceptors())) {
|
||||
if (next instanceof AuthorizationInterceptor) {
|
||||
ourRestServer.unregisterInterceptor(next);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public enum AuthorizationFlagsEnum {
|
|||
* version, this flag was the default and there was no ability to
|
||||
* proactively block compartment read access.
|
||||
*/
|
||||
NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS;
|
||||
NO_NOT_PROACTIVELY_BLOCK_COMPARTMENT_READ_ACCESS,
|
||||
|
||||
ALLOW_PATCH_REQUEST_UNCHALLENGED;
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
* @param theRequestDetails The individual request currently being applied
|
||||
*/
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new ArrayList<IAuthRule>();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private OperationExamineDirection determineOperationDirection(RestOperationTypeEnum theOperation, IBaseResource theRequestResource) {
|
||||
|
@ -407,7 +407,9 @@ public class AuthorizationInterceptor extends ServerOperationInterceptorAdapter
|
|||
private final IAuthRule myDecidingRule;
|
||||
private final PolicyEnum myDecision;
|
||||
|
||||
public Verdict(PolicyEnum theDecision, IAuthRule theDecidingRule) {
|
||||
Verdict(PolicyEnum theDecision, IAuthRule theDecidingRule) {
|
||||
Validate.notNull(theDecision);
|
||||
|
||||
myDecision = theDecision;
|
||||
myDecidingRule = theDecidingRule;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* 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.
|
||||
|
@ -35,6 +35,7 @@ abstract class BaseRule implements IAuthRule {
|
|||
private String myName;
|
||||
private PolicyEnum myMode;
|
||||
private List<IAuthRuleTester> myTesters;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
BaseRule(String theRuleName) {
|
||||
myName = theRuleName;
|
||||
|
@ -51,7 +52,7 @@ abstract class BaseRule implements IAuthRule {
|
|||
public void addTesters(List<IAuthRuleTester> theTesters) {
|
||||
theTesters.forEach(this::addTester);
|
||||
}
|
||||
|
||||
|
||||
boolean applyTesters(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IIdType theInputResourceId, IBaseResource theInputResource, IBaseResource theOutputResource) {
|
||||
boolean retVal = true;
|
||||
if (theOutputResource == null) {
|
||||
|
@ -69,8 +70,9 @@ abstract class BaseRule implements IAuthRule {
|
|||
return myMode;
|
||||
}
|
||||
|
||||
void setMode(PolicyEnum theRuleMode) {
|
||||
BaseRule setMode(PolicyEnum theRuleMode) {
|
||||
myMode = theRuleMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,6 +80,14 @@ abstract class BaseRule implements IAuthRule {
|
|||
return myName;
|
||||
}
|
||||
|
||||
public RuleBuilder.ITenantApplicabilityChecker getTenantApplicabilityChecker() {
|
||||
return myTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
public final void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
public List<IAuthRuleTester> getTesters() {
|
||||
if (myTesters == null) {
|
||||
return Collections.emptyList();
|
||||
|
@ -85,6 +95,16 @@ abstract class BaseRule implements IAuthRule {
|
|||
return Collections.unmodifiableList(myTesters);
|
||||
}
|
||||
|
||||
public boolean isOtherTenant(RequestDetails theRequestDetails) {
|
||||
boolean otherTenant = false;
|
||||
if (getTenantApplicabilityChecker() != null) {
|
||||
if (!getTenantApplicabilityChecker().applies(theRequestDetails)) {
|
||||
otherTenant = true;
|
||||
}
|
||||
}
|
||||
return otherTenant;
|
||||
}
|
||||
|
||||
Verdict newVerdict() {
|
||||
return new Verdict(myMode, this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public interface IAuthRuleBuilderPatch {
|
||||
|
||||
/**
|
||||
* With this setting, all <a href="http://hl7.org/fhir/http.html#patch">patch</a> requests will be permitted
|
||||
* to proceed. This rule will not permit the
|
||||
* {@link ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor#resourceCreated(RequestDetails, IBaseResource)}
|
||||
* and
|
||||
* {@link ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor#resourceUpdated(RequestDetails, IBaseResource, IBaseResource)}
|
||||
* methods if your server supports {@link ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor}.
|
||||
* In that case, additional rules are generally required in order to
|
||||
* permit write operations.
|
||||
*/
|
||||
IAuthRuleFinished allRequests();
|
||||
|
||||
}
|
|
@ -69,6 +69,11 @@ public interface IAuthRuleBuilderRule {
|
|||
*/
|
||||
IAuthRuleBuilderOperation operation();
|
||||
|
||||
/**
|
||||
* This rule applies to a FHIR patch operation
|
||||
*/
|
||||
IAuthRuleBuilderPatch patch();
|
||||
|
||||
/**
|
||||
* This rule applies to any FHIR operation involving reading, including
|
||||
* <code>read</code>, <code>vread</code>, <code>search</code>, and
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* 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.
|
||||
|
@ -33,7 +33,6 @@ import java.util.Set;
|
|||
|
||||
class OperationRule extends BaseRule implements IAuthRule {
|
||||
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
private String myOperationName;
|
||||
private boolean myAppliesToServer;
|
||||
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
|
||||
|
@ -43,35 +42,35 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
private boolean myAppliesToAnyInstance;
|
||||
private boolean myAppliesAtAnyLevel;
|
||||
|
||||
public OperationRule(String theRuleName) {
|
||||
OperationRule(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
public void appliesAtAnyLevel(boolean theAppliesAtAnyLevel) {
|
||||
void appliesAtAnyLevel(boolean theAppliesAtAnyLevel) {
|
||||
myAppliesAtAnyLevel = theAppliesAtAnyLevel;
|
||||
}
|
||||
|
||||
public void appliesToAnyInstance() {
|
||||
void appliesToAnyInstance() {
|
||||
myAppliesToAnyInstance = true;
|
||||
}
|
||||
|
||||
public void appliesToAnyType() {
|
||||
void appliesToAnyType() {
|
||||
myAppliesToAnyType = true;
|
||||
}
|
||||
|
||||
public void appliesToInstances(List<IIdType> theAppliesToIds) {
|
||||
void appliesToInstances(List<IIdType> theAppliesToIds) {
|
||||
myAppliesToIds = theAppliesToIds;
|
||||
}
|
||||
|
||||
public void appliesToInstancesOfType(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
void appliesToInstancesOfType(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToInstancesOfType = theAppliesToTypes;
|
||||
}
|
||||
|
||||
public void appliesToServer() {
|
||||
void appliesToServer() {
|
||||
myAppliesToServer = true;
|
||||
}
|
||||
|
||||
public void appliesToTypes(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
void appliesToTypes(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToTypes = theAppliesToTypes;
|
||||
}
|
||||
|
||||
|
@ -79,10 +78,8 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
if (isOtherTenant(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean applies = false;
|
||||
|
@ -203,8 +200,4 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
myOperationName = theOperationName;
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|||
|
||||
public class RuleBuilder implements IAuthRuleBuilder {
|
||||
|
||||
public static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private ArrayList<IAuthRule> myRules;
|
||||
|
||||
public RuleBuilder() {
|
||||
|
@ -95,19 +95,12 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished, IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId {
|
||||
|
||||
private final RuleImplOp myOpRule;
|
||||
private final OperationRule myOperationRule;
|
||||
protected ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
private final BaseRule myOpRule;
|
||||
ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
private List<IAuthRuleTester> myTesters;
|
||||
|
||||
RuleBuilderFinished(RuleImplOp theRule) {
|
||||
RuleBuilderFinished(BaseRule theRule) {
|
||||
myOpRule = theRule;
|
||||
myOperationRule = null;
|
||||
}
|
||||
|
||||
public RuleBuilderFinished(OperationRule theRule) {
|
||||
myOpRule = null;
|
||||
myOperationRule = theRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,16 +129,11 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(final Collection<String> theTenantIds) {
|
||||
setTenantApplicabilityChecker(new ITenantApplicabilityChecker() {
|
||||
@Override
|
||||
public boolean applies(RequestDetails theRequest) {
|
||||
return theTenantIds.contains(theRequest.getTenantId());
|
||||
}
|
||||
});
|
||||
setTenantApplicabilityChecker(theRequest -> theTenantIds.contains(theRequest.getTenantId()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<IAuthRuleTester> getTesters() {
|
||||
List<IAuthRuleTester> getTesters() {
|
||||
if (myTesters == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -159,23 +147,13 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(final Collection<String> theTenantIds) {
|
||||
setTenantApplicabilityChecker(new ITenantApplicabilityChecker() {
|
||||
@Override
|
||||
public boolean applies(RequestDetails theRequest) {
|
||||
return !theTenantIds.contains(theRequest.getTenantId());
|
||||
}
|
||||
});
|
||||
setTenantApplicabilityChecker(theRequest -> !theTenantIds.contains(theRequest.getTenantId()));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setTenantApplicabilityChecker(ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
if (myOpRule != null) {
|
||||
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
if (myOperationRule != null) {
|
||||
myOperationRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -184,12 +162,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
myTesters = new ArrayList<>();
|
||||
}
|
||||
myTesters.add(theTester);
|
||||
if (myOperationRule != null) {
|
||||
myOperationRule.addTester(theTester);
|
||||
}
|
||||
if (myOpRule != null) {
|
||||
myOpRule.addTester(theTester);
|
||||
}
|
||||
myOpRule.addTester(theTester);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -236,6 +209,12 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderRuleOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderPatch patch() {
|
||||
myRuleOp = RuleOpEnum.PATCH;
|
||||
return new PatchBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOp read() {
|
||||
myRuleOp = RuleOpEnum.READ;
|
||||
|
@ -286,8 +265,8 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished implements IAuthRuleBuilderRuleConditionalClassifier {
|
||||
|
||||
public RuleBuilderRuleConditionalClassifier() {
|
||||
super((RuleImplOp) null);
|
||||
RuleBuilderRuleConditionalClassifier() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -329,7 +308,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
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();
|
||||
return new RuleBuilderRuleOpClassifier(Collections.singletonList(theId)).finished();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -435,7 +414,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
private String myOperationName;
|
||||
|
||||
public RuleBuilderRuleOperationNamed(String theOperationName) {
|
||||
RuleBuilderRuleOperationNamed(String theOperationName) {
|
||||
if (theOperationName != null && !theOperationName.startsWith("$")) {
|
||||
myOperationName = '$' + theOperationName;
|
||||
} else {
|
||||
|
@ -553,6 +532,17 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
}
|
||||
|
||||
private class PatchBuilder implements IAuthRuleBuilderPatch {
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished allRequests() {
|
||||
BaseRule rule = new RuleImplPatch(myRuleName)
|
||||
.setAllRequests(true)
|
||||
.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* 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.
|
||||
|
@ -33,7 +33,6 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
private AppliesTypeEnum myAppliesTo;
|
||||
private Set<?> myAppliesToTypes;
|
||||
private RestOperationTypeEnum myOperationType;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
RuleImplConditional(String theRuleName) {
|
||||
super(theRuleName);
|
||||
|
@ -43,6 +42,10 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
|
||||
if (isOtherTenant(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (theInputResourceId != null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -63,8 +66,8 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
if (getTenantApplicabilityChecker() != null) {
|
||||
if (!getTenantApplicabilityChecker().applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +94,4 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
myOperationType = theOperationType;
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
private RuleOpEnum myOp;
|
||||
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
||||
private List<IIdType> myAppliesToInstances;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -70,10 +69,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
if (isOtherTenant(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
@ -161,7 +158,6 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
case DELETE_TAGS:
|
||||
case META_ADD:
|
||||
case META_DELETE:
|
||||
case PATCH:
|
||||
appliesToResource = theInputResource;
|
||||
appliesToResourceId = theInputResourceId;
|
||||
break;
|
||||
|
@ -454,6 +450,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
|
||||
IBaseBundle request = (IBaseBundle) theInputResource;
|
||||
String bundleType = BundleUtil.getBundleType(theContext, request);
|
||||
|
||||
//noinspection EnumSwitchStatementWhichMissesCases
|
||||
switch (theOp) {
|
||||
case TRANSACTION:
|
||||
return "transaction".equals(bundleType);
|
||||
|
@ -493,9 +491,6 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
return this;
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -504,7 +499,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
builder.append("transactionAppliesToOp", myTransactionAppliesToOp);
|
||||
builder.append("appliesTo", myAppliesTo);
|
||||
builder.append("appliesToTypes", myAppliesToTypes);
|
||||
builder.append("appliesToTenant", myTenantApplicabilityChecker);
|
||||
builder.append("appliesToTenant", getTenantApplicabilityChecker());
|
||||
builder.append("classifierCompartmentName", myClassifierCompartmentName);
|
||||
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
|
||||
builder.append("classifierType", myClassifierType);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
class RuleImplPatch extends BaseRule {
|
||||
private boolean myAllRequests;
|
||||
|
||||
RuleImplPatch(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthorizationInterceptor.Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier, Set<AuthorizationFlagsEnum> theFlags) {
|
||||
if (isOtherTenant(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (myAllRequests) {
|
||||
if (theOperation == RestOperationTypeEnum.PATCH) {
|
||||
if (theInputResource == null && theOutputResource == null) {
|
||||
return newVerdict();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
RuleImplPatch setAllRequests(boolean theAllRequests) {
|
||||
myAllRequests = theAllRequests;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -29,5 +29,6 @@ enum RuleOpEnum {
|
|||
METADATA,
|
||||
BATCH,
|
||||
DELETE,
|
||||
OPERATION
|
||||
OPERATION,
|
||||
PATCH
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
|||
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
@ -49,6 +50,9 @@ import java.util.concurrent.TimeUnit;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AuthorizationInterceptorR4Test {
|
||||
|
||||
|
@ -84,6 +88,16 @@ public class AuthorizationInterceptorR4Test {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private Resource createDiagnosticReport(Integer theId, String theSubjectId) {
|
||||
DiagnosticReport retVal = new DiagnosticReport();
|
||||
if (theId != null) {
|
||||
retVal.setId(new IdType("DiagnosticReport", (long) theId));
|
||||
}
|
||||
retVal.getCode().setText("OBS");
|
||||
retVal.setSubject(new Reference(theSubjectId));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private HttpEntity createFhirResourceEntity(IBaseResource theResource) {
|
||||
String out = ourCtx.newJsonParser().encodeResourceToString(theResource);
|
||||
return new StringEntity(out, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8"));
|
||||
|
@ -99,16 +113,6 @@ public class AuthorizationInterceptorR4Test {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private Resource createDiagnosticReport(Integer theId, String theSubjectId) {
|
||||
DiagnosticReport retVal = new DiagnosticReport();
|
||||
if (theId != null) {
|
||||
retVal.setId(new IdType("DiagnosticReport", (long) theId));
|
||||
}
|
||||
retVal.getCode().setText("OBS");
|
||||
retVal.setSubject(new Reference(theSubjectId));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Organization createOrganization(int theIndex) {
|
||||
Organization retVal = new Organization();
|
||||
retVal.setId("" + theIndex);
|
||||
|
@ -1622,6 +1626,56 @@ public class AuthorizationInterceptorR4Test {
|
|||
assertEquals(false, ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchAllowed() throws IOException {
|
||||
Observation obs = new Observation();
|
||||
obs.setSubject(new Reference("Patient/999"));
|
||||
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().patch().allRequests().andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
String patchBody = "[\n" +
|
||||
" { \"op\": \"replace\", \"path\": \"Observation/status\", \"value\": \"amended\" }\n" +
|
||||
" ]";
|
||||
HttpPatch patch = new HttpPatch("http://localhost:" + ourPort + "/Observation/123");
|
||||
patch.setEntity(new StringEntity(patchBody, ContentType.create(Constants.CT_JSON_PATCH, Charsets.UTF_8)));
|
||||
CloseableHttpResponse status = ourClient.execute(patch);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(204, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchNotAllowed() throws IOException {
|
||||
Observation obs = new Observation();
|
||||
obs.setSubject(new Reference("Patient/999"));
|
||||
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().metadata().andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
String patchBody = "[\n" +
|
||||
" { \"op\": \"replace\", \"path\": \"Observation/status\", \"value\": \"amended\" }\n" +
|
||||
" ]";
|
||||
HttpPatch patch = new HttpPatch("http://localhost:" + ourPort + "/Observation/123");
|
||||
patch.setEntity(new StringEntity(patchBody, ContentType.create(Constants.CT_JSON_PATCH, Charsets.UTF_8)));
|
||||
CloseableHttpResponse status = ourClient.execute(patch);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByAnyId() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -1816,6 +1870,43 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentReadByIdParam() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentReadByPatientParam() throws Exception {
|
||||
|
@ -1856,45 +1947,6 @@ public class AuthorizationInterceptorR4Test {
|
|||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentReadByIdParam() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/1")).andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(1));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=Patient/2");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentRight() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -2856,6 +2908,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").patch().allRequests().andThen()
|
||||
.allow("Rule 1").write().instance("Patient/900").andThen()
|
||||
.build();
|
||||
}
|
||||
|
@ -2873,14 +2926,6 @@ public class AuthorizationInterceptorR4Test {
|
|||
extractResponseAndClose(status);
|
||||
assertEquals(204, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999");
|
||||
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
|
||||
status = ourClient.execute(httpPost);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
@ -2989,11 +3034,12 @@ public class AuthorizationInterceptorR4Test {
|
|||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return DiagnosticReport.class;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Resource> search(
|
||||
@OptionalParam(name = "subject") ReferenceParam theSubject,
|
||||
@OptionalParam(name = "patient") ReferenceParam thePatient
|
||||
) {
|
||||
) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
|
@ -3041,6 +3087,12 @@ public class AuthorizationInterceptorR4Test {
|
|||
return (Parameters) new Parameters().setId("1");
|
||||
}
|
||||
|
||||
@Patch
|
||||
public MethodOutcome patch(@IdParam IdType theId, PatchTypeEnum thePatchType, @ResourceParam String theBody) {
|
||||
ourHitMethod = true;
|
||||
return new MethodOutcome().setId(theId.withVersion("2"));
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public Observation read(@IdParam IdType theId) {
|
||||
ourHitMethod = true;
|
||||
|
@ -3183,7 +3235,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
}
|
||||
|
||||
@Search()
|
||||
public List<Resource> search(@OptionalParam(name="_id") IdType theIdParam) {
|
||||
public List<Resource> search(@OptionalParam(name = "_id") IdType theIdParam) {
|
||||
ourHitMethod = true;
|
||||
return ourReturn;
|
||||
}
|
||||
|
|
|
@ -100,6 +100,12 @@
|
|||
could cause an error about the incorrect FHIR version. Thanks to
|
||||
Rob Hausam for the pull request!
|
||||
</action>
|
||||
<action type="add">
|
||||
A new method has been added to AuthorizationInterceptor that can be used to
|
||||
create rules allowing FHIR patch operations. See
|
||||
<![CDATA[<a href="http://hapifhir.io/doc_rest_server_security.html#Authorizing_Patch_Operations">Authorizing Patch Operations</a>]]>
|
||||
for more information.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.4.0" date="2018-05-28">
|
||||
<action type="add">
|
||||
|
|
|
@ -215,6 +215,29 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Patch Operations">
|
||||
<p>
|
||||
The FHIR <a href="http://hl7.org/fhir/http.html#patch">patch</a> operation
|
||||
presents a challenge for authorization, as the incoming request often contains
|
||||
very little detail about what is being modified.
|
||||
</p>
|
||||
<p>
|
||||
In order to properly enforce authorization on a server that
|
||||
allows the patch operation, a rule may be added that allows all
|
||||
patch requests, as shown below.
|
||||
</p>
|
||||
<p>
|
||||
This should be combined with server support for
|
||||
<a href="http://hapifhir.io/doc_rest_server_security.html#Authorizing_Sub-Operations">Authorizing Sub-Operations</a>
|
||||
as shown above.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="patchAll" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Multitenant Servers">
|
||||
|
||||
<p>
|
||||
|
|
Loading…
Reference in New Issue