Version bump to 3.3.0-SNAPSHOT
This commit is contained in:
parent
4fd3e20d06
commit
16038ece26
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>hapi-fhir-standalone-overlay-example</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.*;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AuthorizationInterceptors {
|
||||
|
||||
public class PatientResourceProvider implements IResourceProvider
|
||||
|
@ -35,7 +36,8 @@ public class AuthorizationInterceptors {
|
|||
}
|
||||
|
||||
//START SNIPPET: patientAndAdmin
|
||||
public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
|
||||
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
|
@ -127,5 +129,16 @@ public class AuthorizationInterceptors {
|
|||
}
|
||||
//END SNIPPET: conditionalUpdate
|
||||
|
||||
|
||||
public void authorizeTenantAction() {
|
||||
//START SNIPPET: authorizeTenantAction
|
||||
new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().read().resourcesOfType(Patient.class).withAnyId().forTenantIds("TENANTA").andThen()
|
||||
.build();
|
||||
}
|
||||
};
|
||||
//END SNIPPET: authorizeTenantAction
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -26,42 +26,42 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-base</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu2.1</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu3</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-r4</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -494,6 +494,17 @@
|
|||
<groupId>de.juplo</groupId>
|
||||
<artifactId>hibernate-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>derby107</id>
|
||||
<phase>process-classes</phase>
|
||||
<goals>
|
||||
<goal>create</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dialect>org.hibernate.dialect.DerbyTenSevenDialect</dialect>
|
||||
<outputFile>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/persistence_create_derby107.sql</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>postgres94</id>
|
||||
<phase>process-classes</phase>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,6 +20,15 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface IAuthRuleBuilderRuleOpClassifierFinished extends IAuthRuleFinished {
|
||||
// nothing
|
||||
|
||||
IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(String... theTenantId);
|
||||
|
||||
IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(Collection<String> theTenantId);
|
||||
|
||||
IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(String... theTenantIds);
|
||||
|
||||
IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(Collection<String> theTenantIds);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Server Framework
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 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 IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId extends IAuthRuleFinished {
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -20,23 +20,19 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
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.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
class OperationRule extends BaseRule implements IAuthRule {
|
||||
|
||||
public OperationRule(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenentApplicabilityChecker;
|
||||
private String myOperationName;
|
||||
private boolean myAppliesToServer;
|
||||
private HashSet<Class<? extends IBaseResource>> myAppliesToTypes;
|
||||
|
@ -44,70 +40,92 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
private HashSet<Class<? extends IBaseResource>> myAppliesToInstancesOfType;
|
||||
private boolean myAppliesToAnyType;
|
||||
private boolean myAppliesToAnyInstance;
|
||||
|
||||
/**
|
||||
* Must include the leading $
|
||||
*/
|
||||
public void setOperationName(String theOperationName) {
|
||||
myOperationName = theOperationName;
|
||||
public OperationRule(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
public String getOperationName() {
|
||||
return myOperationName;
|
||||
public void appliesToAnyInstance() {
|
||||
myAppliesToAnyInstance = true;
|
||||
}
|
||||
|
||||
public void appliesToAnyType() {
|
||||
myAppliesToAnyType = true;
|
||||
}
|
||||
|
||||
public void appliesToInstances(List<IIdType> theAppliesToIds) {
|
||||
myAppliesToIds = theAppliesToIds;
|
||||
}
|
||||
|
||||
public void appliesToInstancesOfType(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToInstancesOfType = theAppliesToTypes;
|
||||
}
|
||||
|
||||
public void appliesToServer() {
|
||||
myAppliesToServer = true;
|
||||
}
|
||||
|
||||
public void appliesToTypes(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToTypes = theAppliesToTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
||||
if (myTenentApplicabilityChecker != null) {
|
||||
if (!myTenentApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean applies = false;
|
||||
switch (theOperation) {
|
||||
case EXTENDED_OPERATION_SERVER:
|
||||
if (myAppliesToServer) {
|
||||
applies = true;
|
||||
}
|
||||
break;
|
||||
case EXTENDED_OPERATION_TYPE:
|
||||
if (myAppliesToAnyType) {
|
||||
applies = true;
|
||||
} else if (myAppliesToTypes != null) {
|
||||
// TODO: Convert to a map of strings and keep the result
|
||||
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
|
||||
String resName = ctx.getResourceDefinition(next).getName();
|
||||
if (resName.equals(theRequestDetails.getResourceName())) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
case EXTENDED_OPERATION_SERVER:
|
||||
if (myAppliesToServer) {
|
||||
applies = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EXTENDED_OPERATION_INSTANCE:
|
||||
if (myAppliesToAnyInstance) {
|
||||
applies = true;
|
||||
} else if (theInputResourceId != null) {
|
||||
if (myAppliesToIds != null) {
|
||||
String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue();
|
||||
for (IIdType next : myAppliesToIds) {
|
||||
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myAppliesToInstancesOfType != null) {
|
||||
break;
|
||||
case EXTENDED_OPERATION_TYPE:
|
||||
if (myAppliesToAnyType) {
|
||||
applies = true;
|
||||
} else if (myAppliesToTypes != null) {
|
||||
// TODO: Convert to a map of strings and keep the result
|
||||
for (Class<? extends IBaseResource> next : myAppliesToInstancesOfType) {
|
||||
for (Class<? extends IBaseResource> next : myAppliesToTypes) {
|
||||
String resName = ctx.getResourceDefinition(next).getName();
|
||||
if (resName.equals(theInputResourceId.getResourceType())) {
|
||||
if (resName.equals(theRequestDetails.getResourceName())) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
break;
|
||||
case EXTENDED_OPERATION_INSTANCE:
|
||||
if (myAppliesToAnyInstance) {
|
||||
applies = true;
|
||||
} else if (theInputResourceId != null) {
|
||||
if (myAppliesToIds != null) {
|
||||
String instanceId = theInputResourceId.toUnqualifiedVersionless().getValue();
|
||||
for (IIdType next : myAppliesToIds) {
|
||||
if (next.toUnqualifiedVersionless().getValue().equals(instanceId)) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (myAppliesToInstancesOfType != null) {
|
||||
// TODO: Convert to a map of strings and keep the result
|
||||
for (Class<? extends IBaseResource> next : myAppliesToInstancesOfType) {
|
||||
String resName = ctx.getResourceDefinition(next).getName();
|
||||
if (resName.equals(theInputResourceId.getResourceType())) {
|
||||
applies = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!applies) {
|
||||
|
@ -121,28 +139,19 @@ class OperationRule extends BaseRule implements IAuthRule {
|
|||
return newVerdict();
|
||||
}
|
||||
|
||||
public void appliesToServer() {
|
||||
myAppliesToServer = true;
|
||||
public String getOperationName() {
|
||||
return myOperationName;
|
||||
}
|
||||
|
||||
public void appliesToTypes(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToTypes = theAppliesToTypes;
|
||||
/**
|
||||
* Must include the leading $
|
||||
*/
|
||||
public void setOperationName(String theOperationName) {
|
||||
myOperationName = theOperationName;
|
||||
}
|
||||
|
||||
public void appliesToInstances(List<IIdType> theAppliesToIds) {
|
||||
myAppliesToIds = theAppliesToIds;
|
||||
}
|
||||
|
||||
public void appliesToInstancesOfType(HashSet<Class<? extends IBaseResource>> theAppliesToTypes) {
|
||||
myAppliesToInstancesOfType = theAppliesToTypes;
|
||||
}
|
||||
|
||||
public void appliesToAnyInstance() {
|
||||
myAppliesToAnyInstance = true;
|
||||
}
|
||||
|
||||
public void appliesToAnyType() {
|
||||
myAppliesToAnyType = true;
|
||||
public void setTenentApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenentApplicabilityChecker) {
|
||||
myTenentApplicabilityChecker = theTenentApplicabilityChecker;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -19,21 +19,25 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
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;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
public class RuleBuilder implements IAuthRuleBuilder {
|
||||
|
||||
public static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private ArrayList<IAuthRule> myRules;
|
||||
|
||||
public RuleBuilder() {
|
||||
myRules = new ArrayList<IAuthRule>();
|
||||
myRules = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,8 +57,9 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished allowAll(String theRuleName) {
|
||||
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.ALLOW_ALL));
|
||||
return new RuleBuilderFinished();
|
||||
RuleImplOp rule = new RuleImplOp(theRuleName);
|
||||
myRules.add(rule.setOp(RuleOpEnum.ALLOW_ALL));
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,11 +84,26 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished denyAll(String theRuleName) {
|
||||
myRules.add(new RuleImplOp(theRuleName).setOp(RuleOpEnum.DENY_ALL));
|
||||
return new RuleBuilderFinished();
|
||||
RuleImplOp rule = new RuleImplOp(theRuleName);
|
||||
myRules.add(rule.setOp(RuleOpEnum.DENY_ALL));
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished {
|
||||
private class RuleBuilderFinished implements IAuthRuleFinished, IAuthRuleBuilderRuleOpClassifierFinished, IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId {
|
||||
|
||||
private final RuleImplOp myOpRule;
|
||||
private final OperationRule myOperationRule;
|
||||
protected ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
RuleBuilderFinished(RuleImplOp theRule) {
|
||||
myOpRule = theRule;
|
||||
myOperationRule = null;
|
||||
}
|
||||
|
||||
public RuleBuilderFinished(OperationRule theRule) {
|
||||
myOpRule = null;
|
||||
myOperationRule = theRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilder andThen() {
|
||||
|
@ -103,6 +123,43 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
protected void doBuildRule() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(String... theTenantIds) {
|
||||
return forTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(final Collection<String> theTenantIds) {
|
||||
myTenantApplicabilityChecker = new ITenantApplicabilityChecker(){
|
||||
@Override
|
||||
public boolean applies(RequestDetails theRequest) {
|
||||
return theTenantIds.contains(theRequest.getTenantId());
|
||||
}
|
||||
};
|
||||
if (myOpRule != null) {
|
||||
myOpRule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
if (myOperationRule != null) {
|
||||
myOperationRule.setTenentApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(String... theTenantIds) {
|
||||
return notForTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(Collection<String> theTenantIds) {
|
||||
return null;// TODO: implement method body
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITenantApplicabilityChecker
|
||||
{
|
||||
boolean applies(RequestDetails theRequest);
|
||||
}
|
||||
|
||||
private class RuleBuilderRule implements IAuthRuleBuilderRule {
|
||||
|
@ -111,7 +168,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
private String myRuleName;
|
||||
private RuleOpEnum myRuleOp;
|
||||
|
||||
public RuleBuilderRule(PolicyEnum theRuleMode, String theRuleName) {
|
||||
RuleBuilderRule(PolicyEnum theRuleMode, String theRuleName) {
|
||||
myRuleMode = theRuleMode;
|
||||
myRuleName = theRuleName;
|
||||
}
|
||||
|
@ -138,7 +195,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
rule.setOp(RuleOpEnum.METADATA);
|
||||
rule.setMode(myRuleMode);
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -176,7 +233,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
private Set<?> myAppliesToTypes;
|
||||
private RestOperationTypeEnum myOperationType;
|
||||
|
||||
public RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) {
|
||||
RuleBuilderRuleConditional(RestOperationTypeEnum theOperationType) {
|
||||
myOperationType = theOperationType;
|
||||
}
|
||||
|
||||
|
@ -196,6 +253,10 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
public class RuleBuilderRuleConditionalClassifier extends RuleBuilderFinished implements IAuthRuleBuilderRuleConditionalClassifier {
|
||||
|
||||
public RuleBuilderRuleConditionalClassifier() {
|
||||
super((RuleImplOp) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBuildRule() {
|
||||
RuleImplConditional rule = new RuleImplConditional(myRuleName);
|
||||
|
@ -203,6 +264,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
rule.setOperationType(myOperationType);
|
||||
rule.setAppliesTo(myAppliesTo);
|
||||
rule.setAppliesToTypes(myAppliesToTypes);
|
||||
rule.setTenantApplicabilityChecker(myTenantApplicabilityChecker);
|
||||
myRules.add(rule);
|
||||
|
||||
}
|
||||
|
@ -221,6 +283,21 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return new RuleBuilderRuleOpClassifier();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifier resourcesOfType(Class<? extends IBaseResource> theType) {
|
||||
Validate.notNull(theType, "theType must not be null");
|
||||
|
@ -239,14 +316,14 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RuleBuilderRuleOpClassifier() {
|
||||
RuleBuilderRuleOpClassifier() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RuleBuilderRuleOpClassifier(List<IIdType> theAppliesToInstances) {
|
||||
RuleBuilderRuleOpClassifier(List<IIdType> theAppliesToInstances) {
|
||||
myAppliesToInstances = theAppliesToInstances;
|
||||
myAppliesTo = AppliesTypeEnum.INSTANCES;
|
||||
}
|
||||
|
@ -264,7 +341,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
rule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
||||
myRules.add(rule);
|
||||
|
||||
return new RuleBuilderFinished();
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -303,21 +380,6 @@ 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 {
|
||||
|
@ -352,6 +414,22 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished onAnyInstance() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToAnyInstance();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished onAnyType() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToAnyType();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onInstance(IIdType theInstanceId) {
|
||||
Validate.notNull(theInstanceId, "theInstanceId must not be null");
|
||||
|
@ -363,29 +441,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
ids.add(theInstanceId);
|
||||
rule.appliesToInstances(ids);
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToServer();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
|
||||
validateType(theType);
|
||||
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToTypes(toTypeSet(theType));
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
private void validateType(Class<? extends IBaseResource> theType) {
|
||||
Validate.notNull(theType, "theType must not be null");
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -395,7 +451,25 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
OperationRule rule = createRule();
|
||||
rule.appliesToInstancesOfType(toTypeSet(theType));
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onServer() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToServer();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinished onType(Class<? extends IBaseResource> theType) {
|
||||
validateType(theType);
|
||||
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToTypes(toTypeSet(theType));
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished(rule);
|
||||
}
|
||||
|
||||
private HashSet<Class<? extends IBaseResource>> toTypeSet(Class<? extends IBaseResource> theType) {
|
||||
|
@ -404,20 +478,8 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
return appliesToTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished onAnyType() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToAnyType();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAuthRuleFinished onAnyInstance() {
|
||||
OperationRule rule = createRule();
|
||||
rule.appliesToAnyInstance();
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
private void validateType(Class<? extends IBaseResource> theType) {
|
||||
Validate.notNull(theType, "theType must not be null");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -440,7 +502,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
rule.setOp(myRuleOp);
|
||||
rule.setTransactionAppliesToOp(TransactionAppliesToEnum.ANY_OPERATION);
|
||||
myRules.add(rule);
|
||||
return new RuleBuilderFinished();
|
||||
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.
|
||||
|
@ -20,49 +20,55 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
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.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class RuleImplConditional extends BaseRule implements IAuthRule {
|
||||
|
||||
private AppliesTypeEnum myAppliesTo;
|
||||
private Set<?> myAppliesToTypes;
|
||||
private RestOperationTypeEnum myOperationType;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
public RuleImplConditional(String theRuleName) {
|
||||
RuleImplConditional(String theRuleName) {
|
||||
super(theRuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier) {
|
||||
IRuleApplier theRuleApplier) {
|
||||
|
||||
if (theInputResourceId != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (theOperation == myOperationType) {
|
||||
switch (myAppliesTo) {
|
||||
case ALL_RESOURCES:
|
||||
case INSTANCES:
|
||||
break;
|
||||
case TYPES:
|
||||
if (theInputResource == null || !myAppliesToTypes.contains(theInputResource.getClass())) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case ALL_RESOURCES:
|
||||
case INSTANCES:
|
||||
break;
|
||||
case TYPES:
|
||||
if (theInputResource == null || !myAppliesToTypes.contains(theInputResource.getClass())) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (theRequestDetails.getConditionalUrl(myOperationType) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return newVerdict();
|
||||
}
|
||||
|
||||
|
@ -81,4 +87,8 @@ public class RuleImplConditional extends BaseRule implements IAuthRule {
|
|||
myOperationType = theOperationType;
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
private RuleOpEnum myOp;
|
||||
private TransactionAppliesToEnum myTransactionAppliesToOp;
|
||||
private List<IIdType> myAppliesToInstances;
|
||||
private RuleBuilder.ITenantApplicabilityChecker myTenantApplicabilityChecker;
|
||||
|
||||
public RuleImplOp(String theRuleName) {
|
||||
super(theRuleName);
|
||||
|
@ -60,6 +61,13 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
@Override
|
||||
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IIdType theInputResourceId, IBaseResource theOutputResource,
|
||||
IRuleApplier theRuleApplier) {
|
||||
|
||||
if (myTenantApplicabilityChecker != null) {
|
||||
if (!myTenantApplicabilityChecker.applies(theRequestDetails)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||
|
||||
IBaseResource appliesToResource;
|
||||
|
@ -275,6 +283,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
return newVerdict();
|
||||
}
|
||||
|
||||
public void setTenantApplicabilityChecker(RuleBuilder.ITenantApplicabilityChecker theTenantApplicabilityChecker) {
|
||||
myTenantApplicabilityChecker = theTenantApplicabilityChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
|
@ -282,6 +294,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("classifierCompartmentName", myClassifierCompartmentName);
|
||||
builder.append("classifierCompartmentOwners", myClassifierCompartmentOwners);
|
||||
builder.append("classifierType", myClassifierType);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-server-jpa</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
|||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -65,9 +66,10 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Before
|
||||
public void before() {
|
||||
ourCtx.setAddProfileTagWhenEncoding(AddProfileTagEnum.NEVER);
|
||||
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourServlet.getInterceptors())) {
|
||||
for (IServerInterceptor next : new ArrayList<>(ourServlet.getInterceptors())) {
|
||||
ourServlet.unregisterInterceptor(next);
|
||||
}
|
||||
ourServlet.setTenantIdentificationStrategy(null);
|
||||
ourReturn = null;
|
||||
ourHitMethod = false;
|
||||
ourConditionalCreateId = "1123";
|
||||
|
@ -236,6 +238,50 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowAllForTenant() throws Exception {
|
||||
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.deny("Rule 1").read().resourcesOfType(Patient.class).withAnyId().forTenantIds("TENANTA").andThen()
|
||||
.allowAll("Default Rule")
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by rule: Rule 1"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1/$validate");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* #528
|
||||
*/
|
||||
|
@ -244,7 +290,10 @@ public class AuthorizationInterceptorR4Test {
|
|||
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/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).andThen().denyAll().build();
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().allResources().inCompartment("Patient", new IdType("Patient/845bd9f1-3635-4866-a6c8-1ca085df5c1a"))
|
||||
.andThen().denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -269,6 +318,40 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowByCompartmentWithAnyTypeWithTenantId() throws Exception {
|
||||
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
||||
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/845bd9f1-3635-4866-a6c8-1ca085df5c1a")).forTenantIds("TENANTA")
|
||||
.andThen().denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createCarePlan(10, "845bd9f1-3635-4866-a6c8-1ca085df5c1a"));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createCarePlan(10, "FOO"));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/CarePlan/135154");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* #528
|
||||
*/
|
||||
|
@ -521,6 +604,42 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* #528
|
||||
*/
|
||||
@Test
|
||||
public void testDenyActionsNotOnTenant() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.ALLOW) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder().denyAll().notForTenantIds("TENANTA", "TENANTB").build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* #528
|
||||
*/
|
||||
|
@ -660,7 +779,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidInstanceIds() throws Exception {
|
||||
public void testInvalidInstanceIds() {
|
||||
try {
|
||||
new RuleBuilder().allow("Rule 1").write().instance((String) null);
|
||||
fail();
|
||||
|
@ -1176,6 +1295,43 @@ public class AuthorizationInterceptorR4Test {
|
|||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationTypeLevelWithTenant() throws Exception {
|
||||
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("RULE 1").operation().named("opName").onType(Patient.class).forTenantIds("TENANTA").andThen()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
// Right Tenant
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/$opName");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
// Wrong Tenant
|
||||
ourHitMethod = false;
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTC/Patient/$opName");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
assertThat(response, containsString("Access denied by default policy"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOperationTypeLevelWildcard() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -1307,6 +1463,79 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByAnyIdWithTenantId() throws Exception {
|
||||
ourServlet.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow("Rule 1").read().resourcesOfType(Patient.class).withAnyId().forTenantIds("TENANTA")
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTB/Patient/1");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createPatient(2));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient/1/_history/222");
|
||||
status = ourClient.execute(httpGet);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Collections.singletonList(createObservation(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Observation/10");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
|
||||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/TENANTA/Patient");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
ourLog.info(response);
|
||||
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByCompartmentRight() throws Exception {
|
||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
|
@ -1438,7 +1667,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation/900");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1446,7 +1675,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
ourHitMethod = false;
|
||||
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/901");
|
||||
status = ourClient.execute(httpGet);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1551,7 +1780,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
ourHitMethod = false;
|
||||
httpGet = new HttpGet(respBundle.getLink("next").getUrl());
|
||||
status = ourClient.execute(httpGet);
|
||||
respString = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
|
||||
|
@ -1640,7 +1869,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPost("http://localhost:" + ourPort + "/Observation");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(null, "Patient/1")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
}
|
||||
|
@ -1886,7 +2115,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar");
|
||||
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1924,7 +2153,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?foo=bar");
|
||||
httpPost.setEntity(createFhirResourceEntity(createPatient(null)));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1961,7 +2190,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/900");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(900, "Patient/12")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -1969,7 +2198,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPut("http://localhost:" + ourPort + "/Observation/901");
|
||||
httpPost.setEntity(createFhirResourceEntity(createObservation(901, "Patient/12")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -2008,7 +2237,6 @@ public class AuthorizationInterceptorR4Test {
|
|||
|
||||
HttpEntityEnclosingRequestBase httpPost;
|
||||
HttpResponse status;
|
||||
String response;
|
||||
|
||||
String input = "[ { \"op\": \"replace\", \"path\": \"/gender\", \"value\": \"male\" } ]";
|
||||
|
||||
|
@ -2016,7 +2244,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/900");
|
||||
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(204, status.getStatusLine().getStatusCode());
|
||||
assertTrue(ourHitMethod);
|
||||
|
||||
|
@ -2024,7 +2252,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
httpPost = new HttpPatch("http://localhost:" + ourPort + "/Patient/999");
|
||||
httpPost.setEntity(new StringEntity(input, ContentType.parse("application/json-patch+json")));
|
||||
status = ourClient.execute(httpPost);
|
||||
response = extractResponseAndClose(status);
|
||||
extractResponseAndClose(status);
|
||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||
assertFalse(ourHitMethod);
|
||||
}
|
||||
|
@ -2085,6 +2313,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DummyEncounterResourceProvider implements IResourceProvider {
|
||||
|
||||
@Operation(name = "everything", idempotent = true)
|
||||
|
@ -2103,6 +2332,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||
|
||||
@Create()
|
||||
|
@ -2118,8 +2348,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
@Delete()
|
||||
public MethodOutcome delete(@IdParam IdType theId) {
|
||||
ourHitMethod = true;
|
||||
MethodOutcome retVal = new MethodOutcome();
|
||||
return retVal;
|
||||
return new MethodOutcome();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2201,8 +2430,7 @@ public class AuthorizationInterceptorR4Test {
|
|||
for (IBaseResource next : ourReturn) {
|
||||
theRequestOperationCallback.resourceDeleted(next);
|
||||
}
|
||||
MethodOutcome retVal = new MethodOutcome();
|
||||
return retVal;
|
||||
return new MethodOutcome();
|
||||
}
|
||||
|
||||
@Operation(name = "everything", idempotent = true)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -11,7 +11,7 @@
|
|||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<name>HAPI-FHIR</name>
|
||||
<url>https://hapifhir.io</url>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -7,6 +7,20 @@
|
|||
</properties>
|
||||
<body>
|
||||
<release version="3.3.0" date="TBD">
|
||||
<action type="add">
|
||||
This release corrects an ineffiency in the JPA Server, but requires a schema
|
||||
change in order to update. Prior to this version of HAPI FHIR, a CLOB column
|
||||
containing the complete resource body was stored in two
|
||||
tables: HFJ_RESOURCE and HFJ_RES_VER. Because the same content was stored in two
|
||||
places, the database consumed more space than is needed to.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
In order to reduce this duplication, the columns have been removed from the
|
||||
HFJ_RESOURCE column. This means that on any database that is being upgraded
|
||||
to HAPI FHIR 3.2.0, you will need to remove the columns
|
||||
<![CDATA[<code>RES_TEXT</code> and <code>RES_ENCODING</code>]]> (or
|
||||
set them to nullable if you want an easy means of rolling back). Naturally
|
||||
you should back your database up prior to making this change.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix a crash in the JSON parser when parsing extensions on repeatable
|
||||
elements (e.g. Patient.address.line) where there is an extension on the
|
||||
|
|
|
@ -1,222 +1,236 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||
|
||||
<properties>
|
||||
<title>Server Security</title>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- The body of the document contains a number of sections -->
|
||||
<section name="Server Security">
|
||||
|
||||
<p>
|
||||
Security is a complex topic which goes far beyond the scope of HAPI FHIR.
|
||||
HAPI does provide mechanisms which can be used to implement security in
|
||||
your server however.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Because HAPI FHIR's REST server is based on the Servlet API, you may use any
|
||||
security mechanism which works in that environment. Some serlvet containers
|
||||
may provide security layers you can plug into. The rest of this page
|
||||
does not explore that method, but rather looks at HAPI FHIR hooks that can
|
||||
be used to implement FHIR specific security.
|
||||
</p>
|
||||
|
||||
<subsection name="Authentication vs Authorization">
|
||||
|
||||
<p>
|
||||
Background reading: <a href="https://en.wikipedia.org/wiki/Authentication">Wikipedia - Authentication</a>
|
||||
</p>
|
||||
<p>
|
||||
Server security is divided into two topics:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Authentication (AuthN):</b> Is verifying that the user is who they say they
|
||||
are. This is typically accomplished by testing a username/password in the request,
|
||||
or by checking a "bearer token" in the request.
|
||||
</li>
|
||||
<li>
|
||||
<b>Authorization (AuthZ):</b> Is verifying that the user is allowed to perform
|
||||
the given action. For example, in a FHIR application you might use AuthN to test that
|
||||
the user making a request to the FHIR server is allowed to access the server, but
|
||||
that test might determine that the requesting user is not permitted to perform
|
||||
write operations and therefore block a FHIR <code>create</code> operation. This is
|
||||
AuthN and AuthZ in action.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Authentication Interceptors">
|
||||
|
||||
<p>
|
||||
The <a href="./doc_rest_server_interceptor.html">Server Interceptor</a>
|
||||
framework can provide an easy way to test for credentials. The following
|
||||
example shows a simple interceptor which tests for HTTP Basic Auth.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="basicAuthInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
<subsection name="HTTP Basic Auth">
|
||||
|
||||
<p>
|
||||
Note that if you are implementing HTTP Basic Auth, you may want to
|
||||
return a <code>WWW-Authenticate</code> header with the response.
|
||||
The following snippet shows how to add such a header with a custom
|
||||
realm:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="basicAuthInterceptorRealm" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Authorization Interceptor">
|
||||
|
||||
<p>
|
||||
HAPI FHIR 1.5 introduced a new interceptor, the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.html">AuthorizationInterceptor</a>.
|
||||
</p>
|
||||
<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 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
|
||||
feedback and suggestions on this feature. In addition, this documentation is
|
||||
not yet complete. More examples and details will be added soon! Please get in
|
||||
touch if you'd like to help test, have suggestions, etc.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor works by allowing you to declare
|
||||
permissions based on an individual request coming in. In other
|
||||
words, you could have code that examines an incoming request and
|
||||
determines that it is being made by a Patient with ID 123. You
|
||||
could then declare that the requesting user has access to read and
|
||||
write any resource in compartment "Patient/123", which corresponds
|
||||
to any Observation, MedicationOrder etc with a subject of
|
||||
"<code>Patient/123</code>". On the other hand, another request
|
||||
might be detemrined to belong to an administrator user, and
|
||||
could be declared to be allowed to do anything.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor is used by subclassing it and then registering your
|
||||
subclass with the <code>RestfulServer</code>. The following example shows a subclassed
|
||||
interceptor implementing some basic rules:
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="patientAndAdmin" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
<subsection name="Using AuthorizationInterceptor in a REST Server">
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor works by examining the client request
|
||||
in order to determine whether "write" operations are legal, and looks at
|
||||
the response from the server in order to determine whether "read" operations
|
||||
are legal.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Read Operations">
|
||||
|
||||
<p>
|
||||
When authorizing a read operation, the AuthorizationInterceptor
|
||||
always allows client code to execute and generate a response.
|
||||
It then examines the response that would be returned before
|
||||
actually returning it to the client, and if rules do not permit
|
||||
that data to be shown to the client the interceptor aborts the
|
||||
request.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that there are performance implications to this mechanism,
|
||||
since an unauthorized user can still cause the server to fetch data
|
||||
even if they won't get to see it. This mechanism should be comprehensive
|
||||
however, since it will prevent clients from using various features
|
||||
in FHIR (e.g. <code>_include</code> or <code>_revinclude</code>) to
|
||||
"trick" the server into showing them date they shouldn't be allowed to
|
||||
see.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See the following diagram for an example of how this works.
|
||||
</p>
|
||||
|
||||
<img src="./images/hapi_authorizationinterceptor_read_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Write Operations">
|
||||
|
||||
<p>
|
||||
Write operations (create, update, etc.) are typically authorized
|
||||
by the interceptor by examining the parsed URL and making a decision
|
||||
about whether to authorize the operation before allowing Resource Provider
|
||||
code to proceed. This means that client code will not have a chance to execute
|
||||
and create resources that the client does not have permissions to create.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See the following diagram for an example of how this works.
|
||||
</p>
|
||||
|
||||
<img src="./images/hapi_authorizationinterceptor_write_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Sub-Operations">
|
||||
|
||||
<p>
|
||||
There are a number of situations where the REST framework doesn't
|
||||
actually know exactly what operation is going to be performed by
|
||||
the implementing server code. For example, if your server implements
|
||||
a <code>conditional update</code> operation, the server might not know
|
||||
which resource is actually being updated until the server code
|
||||
is executed.
|
||||
</p>
|
||||
<p>
|
||||
Because client code is actually determining which resources are
|
||||
being modified, the server can not automatically apply security
|
||||
rules against these modifications without being provided hints
|
||||
from client code.
|
||||
</p>
|
||||
<p>
|
||||
In this type of situation, it is important to manually
|
||||
notify the interceptor chain about the "sub-operation" being performed.
|
||||
The following snippet shows how to notify interceptors about
|
||||
a conditional create.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="conditionalUpdate" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</document>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||
|
||||
<properties>
|
||||
<title>Server Security</title>
|
||||
</properties>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- The body of the document contains a number of sections -->
|
||||
<section name="Server Security">
|
||||
|
||||
<p>
|
||||
Security is a complex topic which goes far beyond the scope of HAPI FHIR.
|
||||
HAPI does provide mechanisms which can be used to implement security in
|
||||
your server however.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Because HAPI FHIR's REST server is based on the Servlet API, you may use any
|
||||
security mechanism which works in that environment. Some serlvet containers
|
||||
may provide security layers you can plug into. The rest of this page
|
||||
does not explore that method, but rather looks at HAPI FHIR hooks that can
|
||||
be used to implement FHIR specific security.
|
||||
</p>
|
||||
|
||||
<subsection name="Authentication vs Authorization">
|
||||
|
||||
<p>
|
||||
Background reading: <a href="https://en.wikipedia.org/wiki/Authentication">Wikipedia - Authentication</a>
|
||||
</p>
|
||||
<p>
|
||||
Server security is divided into two topics:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Authentication (AuthN):</b> Is verifying that the user is who they say they
|
||||
are. This is typically accomplished by testing a username/password in the request,
|
||||
or by checking a "bearer token" in the request.
|
||||
</li>
|
||||
<li>
|
||||
<b>Authorization (AuthZ):</b> Is verifying that the user is allowed to perform
|
||||
the given action. For example, in a FHIR application you might use AuthN to test that
|
||||
the user making a request to the FHIR server is allowed to access the server, but
|
||||
that test might determine that the requesting user is not permitted to perform
|
||||
write operations and therefore block a FHIR <code>create</code> operation. This is
|
||||
AuthN and AuthZ in action.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Authentication Interceptors">
|
||||
|
||||
<p>
|
||||
The <a href="./doc_rest_server_interceptor.html">Server Interceptor</a>
|
||||
framework can provide an easy way to test for credentials. The following
|
||||
example shows a simple interceptor which tests for HTTP Basic Auth.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="basicAuthInterceptor" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
<subsection name="HTTP Basic Auth">
|
||||
|
||||
<p>
|
||||
Note that if you are implementing HTTP Basic Auth, you may want to
|
||||
return a <code>WWW-Authenticate</code> header with the response.
|
||||
The following snippet shows how to add such a header with a custom
|
||||
realm:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="basicAuthInterceptorRealm" />
|
||||
<param name="file" value="examples/src/main/java/example/SecurityInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<section name="Authorization Interceptor">
|
||||
|
||||
<p>
|
||||
HAPI FHIR 1.5 introduced a new interceptor, the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.html">AuthorizationInterceptor</a>.
|
||||
</p>
|
||||
<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 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
|
||||
feedback and suggestions on this feature. In addition, this documentation is
|
||||
not yet complete. More examples and details will be added soon! Please get in
|
||||
touch if you'd like to help test, have suggestions, etc.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor works by allowing you to declare
|
||||
permissions based on an individual request coming in. In other
|
||||
words, you could have code that examines an incoming request and
|
||||
determines that it is being made by a Patient with ID 123. You
|
||||
could then declare that the requesting user has access to read and
|
||||
write any resource in compartment "Patient/123", which corresponds
|
||||
to any Observation, MedicationOrder etc with a subject of
|
||||
"<code>Patient/123</code>". On the other hand, another request
|
||||
might be detemrined to belong to an administrator user, and
|
||||
could be declared to be allowed to do anything.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor is used by subclassing it and then registering your
|
||||
subclass with the <code>RestfulServer</code>. The following example shows a subclassed
|
||||
interceptor implementing some basic rules:
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="patientAndAdmin" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
<subsection name="Using AuthorizationInterceptor in a REST Server">
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor works by examining the client request
|
||||
in order to determine whether "write" operations are legal, and looks at
|
||||
the response from the server in order to determine whether "read" operations
|
||||
are legal.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Read Operations">
|
||||
|
||||
<p>
|
||||
When authorizing a read operation, the AuthorizationInterceptor
|
||||
always allows client code to execute and generate a response.
|
||||
It then examines the response that would be returned before
|
||||
actually returning it to the client, and if rules do not permit
|
||||
that data to be shown to the client the interceptor aborts the
|
||||
request.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that there are performance implications to this mechanism,
|
||||
since an unauthorized user can still cause the server to fetch data
|
||||
even if they won't get to see it. This mechanism should be comprehensive
|
||||
however, since it will prevent clients from using various features
|
||||
in FHIR (e.g. <code>_include</code> or <code>_revinclude</code>) to
|
||||
"trick" the server into showing them date they shouldn't be allowed to
|
||||
see.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See the following diagram for an example of how this works.
|
||||
</p>
|
||||
|
||||
<img src="./images/hapi_authorizationinterceptor_read_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Write Operations">
|
||||
|
||||
<p>
|
||||
Write operations (create, update, etc.) are typically authorized
|
||||
by the interceptor by examining the parsed URL and making a decision
|
||||
about whether to authorize the operation before allowing Resource Provider
|
||||
code to proceed. This means that client code will not have a chance to execute
|
||||
and create resources that the client does not have permissions to create.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
See the following diagram for an example of how this works.
|
||||
</p>
|
||||
|
||||
<img src="./images/hapi_authorizationinterceptor_write_normal.svg" alt="Write Authorization"/>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Sub-Operations">
|
||||
|
||||
<p>
|
||||
There are a number of situations where the REST framework doesn't
|
||||
actually know exactly what operation is going to be performed by
|
||||
the implementing server code. For example, if your server implements
|
||||
a <code>conditional update</code> operation, the server might not know
|
||||
which resource is actually being updated until the server code
|
||||
is executed.
|
||||
</p>
|
||||
<p>
|
||||
Because client code is actually determining which resources are
|
||||
being modified, the server can not automatically apply security
|
||||
rules against these modifications without being provided hints
|
||||
from client code.
|
||||
</p>
|
||||
<p>
|
||||
In this type of situation, it is important to manually
|
||||
notify the interceptor chain about the "sub-operation" being performed.
|
||||
The following snippet shows how to notify interceptors about
|
||||
a conditional create.
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="conditionalUpdate" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Authorizing Multitenant Servers">
|
||||
|
||||
<p>
|
||||
The AuthorizationInterceptor has the ability to direct individual
|
||||
rules as only applying to a single tenant in a multitenant
|
||||
server. The following example shows such a rule.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="authorizeTenantAction" />
|
||||
<param name="file" value="examples/src/main/java/example/AuthorizationInterceptors.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</document>
|
||||
|
|
Loading…
Reference in New Issue