Get security interceptor working

This commit is contained in:
jamesagnew 2016-04-16 10:22:43 -04:00
parent 533c0c87ab
commit 3f2b5fdeb7
13 changed files with 635 additions and 133 deletions

View File

@ -441,6 +441,16 @@ public interface IServerInterceptor {
myResource = theObject;
}
/**
* This method may be invoked by user code to notify interceptors that a nested
* operation is being invoked which is denoted by this request details.
*/
public void notifyIncomingRequestPreHandled(RestOperationTypeEnum theOperationType) {
for (IServerInterceptor next : getRequestDetails().getServer().getInterceptors()) {
next.incomingRequestPreHandled(theOperationType, this);
}
}
}
}

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import ca.uhn.fhir.util.BundleUtil;
@ -49,7 +50,7 @@ import ca.uhn.fhir.util.CoverageIgnore;
* inspect requests and responses to determine whether the calling user
* has permission to perform the given action.
*/
public class AuthorizationInterceptor extends InterceptorAdapter implements IServerOperationInterceptor {
public class AuthorizationInterceptor extends InterceptorAdapter implements IServerOperationInterceptor, IRuleApplier {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptor.class);
@ -72,9 +73,8 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
setDefaultPolicy(theDefaultPolicy);
}
private void applyRulesAndFailIfDeny(List<IAuthRule> theRules, RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
ourLog.trace("Applying {} rules to render an auth decision for operation {}", theRules.size(), theOperation);
Verdict decision = applyRulesAndReturnDecision(theRules, theOperation, theRequestDetails, theInputResource, theOutputResource);
private void applyRulesAndFailIfDeny(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
Verdict decision = applyRulesAndReturnDecision(theOperation, theRequestDetails, theInputResource, theOutputResource);
if (decision.getDecision() == PolicyEnum.ALLOW) {
return;
@ -83,38 +83,26 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
handleDeny(decision);
}
private Verdict applyRulesAndReturnDecision(List<IAuthRule> theRules, RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
PolicyEnum result = null;
// PolicyEnum preference = null;
IAuthRule decidingRule = null;
for (IAuthRule nextRule : theRules) {
RuleVerdictEnum decision = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theOutputResource);
switch (decision) {
case NO_DECISION:
continue;
case ALLOW:
result = PolicyEnum.ALLOW;
decidingRule = nextRule;
break;
case DENY:
result = PolicyEnum.DENY;
decidingRule = nextRule;
break;
}
if (result != null) {
ourLog.trace("Rule {} returned decision {}", nextRule, result);
@Override
public Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
List<IAuthRule> rules = buildRuleList(theRequestDetails);
ourLog.trace("Applying {} rules to render an auth decision for operation {}", rules.size(), theOperation);
Verdict verdict = null;
for (IAuthRule nextRule : rules) {
verdict = nextRule.applyRule(theOperation, theRequestDetails, theInputResource, theOutputResource, this);
if (verdict != null) {
ourLog.trace("Rule {} returned decision {}", nextRule, verdict.getDecision());
break;
}
}
if (result == null) {
if (verdict == null) {
ourLog.trace("No rules returned a decision, applying default {}", myDefaultPolicy);
result = myDefaultPolicy;
return new Verdict(myDefaultPolicy, null);
}
return new Verdict(result, decidingRule);
return verdict;
}
/**
@ -197,22 +185,21 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
* Handle an access control verdict of {@link PolicyEnum#DENY}.
* <p>
* Subclasses may override to implement specific behaviour, but default is to
* throw {@link AuthenticationException} (HTTP 401) with error message citing the
* throw {@link ForbiddenOperationException} (HTTP 403) with error message citing the
* rule name which trigered failure
* </p>
*/
protected void handleDeny(Verdict decision) {
if (decision.getDecidingRule() != null) {
String ruleName = defaultString(decision.getDecidingRule().getName(), "(unnamed rule)");
throw new AuthenticationException("Access denied by rule: " + ruleName);
throw new ForbiddenOperationException("Access denied by rule: " + ruleName);
} else {
throw new AuthenticationException("Access denied by default policy (no applicable rules)");
throw new ForbiddenOperationException("Access denied by default policy (no applicable rules)");
}
}
private void handleUserOperation(RequestDetails theRequest, IBaseResource theResource, RestOperationTypeEnum operation) {
List<IAuthRule> rules = buildRuleList(theRequest);
applyRulesAndFailIfDeny(rules, operation, theRequest, theResource, null);
applyRulesAndFailIfDeny(operation, theRequest, theResource, null);
}
@Override
@ -227,8 +214,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
RequestDetails requestDetails = theProcessedRequest.getRequestDetails();
List<IAuthRule> rules = buildRuleList(requestDetails);
applyRulesAndFailIfDeny(rules, theOperation, requestDetails, theProcessedRequest.getResource(), null);
applyRulesAndFailIfDeny(theOperation, requestDetails, theProcessedRequest.getResource(), null);
}
@Override
@ -281,7 +267,7 @@ public class AuthorizationInterceptor extends InterceptorAdapter implements ISer
}
for (IBaseResource nextResponse : resources) {
applyRulesAndFailIfDeny(rules, theRequestDetails.getRestOperationType(), theRequestDetails, null, nextResponse);
applyRulesAndFailIfDeny(theRequestDetails.getRestOperationType(), theRequestDetails, null, nextResponse);
}
return true;

View File

@ -24,6 +24,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
public interface IAuthRule {
@ -38,9 +39,12 @@ public interface IAuthRule {
* The resource being input by the client, or <code>null</code>
* @param theOutputResource
* The resource being returned by the server, or <code>null</code>
* @return Returns a policy decision, or {@link RuleVerdictEnum#NO_DECISION} if the rule does not apply
* @param theRuleApplier
* The rule applying module (this can be used by rules to apply the rule set to
* nested objects in the request, such as nested requests in a transaction)
* @return Returns a policy decision, or <code>null</code> if the rule does not apply
*/
RuleVerdictEnum applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource);
Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier);
/**
* Returns a name for this rule, to be used in logs and error messages

View File

@ -22,6 +22,11 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
public interface IAuthRuleBuilderRule {
/**
* This rule applies to the FHIR delete operation
*/
IAuthRuleBuilderRuleOp delete();
/**
* This rules applies to the metadata operation (retrieve the
* server's conformance statement)

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
public interface IRuleApplier {
Verdict applyRulesAndReturnDecision(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource);
}

View File

@ -21,16 +21,23 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
*/
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor.Verdict;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.BundleUtil.BundleEntryParts;
import ca.uhn.fhir.util.FhirTerser;
class Rule implements IAuthRule {
@ -40,7 +47,7 @@ class Rule implements IAuthRule {
private String myClassifierCompartmentName;
private Collection<? extends IIdType> myClassifierCompartmentOwners;
private ClassifierTypeEnum myClassifierType;
private RuleVerdictEnum myMode;
private PolicyEnum myMode;
private String myName;
private RuleOpEnum myOp;
private TransactionAppliesToEnum myTransactionAppliesToOp;
@ -50,33 +57,107 @@ class Rule implements IAuthRule {
}
@Override
public RuleVerdictEnum applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource) {
public Verdict applyRule(RestOperationTypeEnum theOperation, RequestDetails theRequestDetails, IBaseResource theInputResource, IBaseResource theOutputResource, IRuleApplier theRuleApplier) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
IBaseResource appliesTo;
switch (myOp) {
case READ:
if (theOutputResource == null) {
return null;
}
appliesTo = theOutputResource;
break;
case WRITE:
if (theInputResource == null) {
return null;
}
appliesTo = theInputResource;
break;
case DELETE:
if (theOperation == RestOperationTypeEnum.DELETE) {
if (theInputResource == null) {
return new Verdict(myMode, this);
} else {
appliesTo = theInputResource;
}
} else {
return null;
}
break;
case BATCH:
case TRANSACTION:
if (requestAppliesToTransaction(ctx, myOp, theInputResource)) {
return myMode;
if (theInputResource != null && requestAppliesToTransaction(ctx, myOp, theInputResource)) {
if (myMode == PolicyEnum.DENY) {
return new Verdict(PolicyEnum.DENY, this);
} else {
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
Verdict verdict = null;
for (BundleEntryParts nextPart : inputResources) {
IBaseResource inputResource = nextPart.getResource();
RestOperationTypeEnum operation = null;
if (nextPart.getRequestType() == RequestTypeEnum.GET) {
continue;
}
if (nextPart.getRequestType() == RequestTypeEnum.POST) {
operation = RestOperationTypeEnum.CREATE;
} else if (nextPart.getRequestType() == RequestTypeEnum.PUT) {
operation = RestOperationTypeEnum.UPDATE;
} else {
throw new InvalidRequestException("Can not handle transaction with operation of type " + nextPart.getRequestType());
}
/*
* This is basically just being conservative - Be careful of transactions containing
* nested operations and nested transactions. We block the by default. At some point
* it would be nice to be more nuanced here.
*/
RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource());
if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) {
throw new InvalidRequestException("Can not handle transaction with nested resource of type " + resourceDef.getName());
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(operation, theRequestDetails, inputResource, null);
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
return verdict;
}
} else if (theOutputResource != null) {
List<BundleEntryParts> inputResources = BundleUtil.toListOfEntries(ctx, (IBaseBundle) theInputResource);
Verdict verdict = null;
for (BundleEntryParts nextPart : inputResources) {
if (nextPart.getResource() == null) {
continue;
}
Verdict newVerdict = theRuleApplier.applyRulesAndReturnDecision(RestOperationTypeEnum.READ, theRequestDetails, null, nextPart.getResource());
if (newVerdict == null) {
continue;
} else if (verdict == null) {
verdict = newVerdict;
} else if (verdict.getDecision() == PolicyEnum.ALLOW && newVerdict.getDecision() == PolicyEnum.DENY) {
verdict = newVerdict;
}
}
return verdict;
} else {
return RuleVerdictEnum.NO_DECISION;
return null;
}
case ALLOW_ALL:
return RuleVerdictEnum.ALLOW;
return new Verdict(PolicyEnum.ALLOW, this);
case DENY_ALL:
return RuleVerdictEnum.DENY;
return new Verdict(PolicyEnum.DENY, this);
case METADATA:
if (theOperation == RestOperationTypeEnum.METADATA) {
return myMode;
return new Verdict(myMode, this);
} else {
return RuleVerdictEnum.NO_DECISION;
return null;
}
default:
// Should not happen
@ -88,7 +169,7 @@ class Rule implements IAuthRule {
break;
case TYPES:
if (myAppliesToTypes.contains(appliesTo.getClass()) == false) {
return RuleVerdictEnum.NO_DECISION;
return null;
}
break;
default:
@ -108,17 +189,21 @@ class Rule implements IAuthRule {
}
}
if (!foundMatch) {
return RuleVerdictEnum.NO_DECISION;
return null;
}
break;
default:
throw new IllegalStateException("Unable to apply security to event of applies to type " + myAppliesTo);
}
return myMode;
return new Verdict(myMode, this);
}
private boolean requestAppliesToTransaction(FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) {
if (!"Bundle".equals(theContext.getResourceDefinition(theInputResource).getName())) {
return false;
}
IBaseBundle request = (IBaseBundle) theInputResource;
String bundleType = BundleUtil.getBundleType(theContext, request);
switch (theOp) {
@ -160,7 +245,7 @@ class Rule implements IAuthRule {
myClassifierType = theClassifierType;
}
public void setMode(RuleVerdictEnum theRuleMode) {
public void setMode(PolicyEnum theRuleMode) {
myMode = theRuleMode;
}

View File

@ -46,7 +46,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
@Override
public IAuthRuleBuilderRule allow(String theRuleName) {
return new RuleBuilderRule(RuleVerdictEnum.ALLOW, theRuleName);
return new RuleBuilderRule(PolicyEnum.ALLOW, theRuleName);
}
@Override
@ -72,7 +72,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
@Override
public IAuthRuleBuilderRule deny(String theRuleName) {
return new RuleBuilderRule(RuleVerdictEnum.DENY, theRuleName);
return new RuleBuilderRule(PolicyEnum.DENY, theRuleName);
}
@Override
@ -101,15 +101,21 @@ public class RuleBuilder implements IAuthRuleBuilder {
private class RuleBuilderRule implements IAuthRuleBuilderRule {
private RuleOpEnum myRuleOp;
private RuleVerdictEnum myRuleMode;
private PolicyEnum myRuleMode;
private String myRuleName;
private RuleOpEnum myRuleOp;
public RuleBuilderRule(RuleVerdictEnum theRuleMode, String theRuleName) {
public RuleBuilderRule(PolicyEnum theRuleMode, String theRuleName) {
myRuleMode = theRuleMode;
myRuleName = theRuleName;
}
@Override
public IAuthRuleBuilderRuleOp delete() {
myRuleOp = RuleOpEnum.DELETE;
return new RuleBuilderRuleOp();
}
@Override
public RuleBuilderFinished metadata() {
Rule rule = new Rule(myRuleName);
@ -118,13 +124,13 @@ public class RuleBuilder implements IAuthRuleBuilder {
myRules.add(rule);
return new RuleBuilderFinished();
}
@Override
public IAuthRuleBuilderRuleOp read() {
myRuleOp = RuleOpEnum.READ;
return new RuleBuilderRuleOp();
}
@Override
public IAuthRuleBuilderRuleTransaction transaction() {
myRuleOp = RuleOpEnum.TRANSACTION;
@ -136,7 +142,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
myRuleOp = RuleOpEnum.WRITE;
return new RuleBuilderRuleOp();
}
private class RuleBuilderRuleOp implements IAuthRuleBuilderRuleOp {
private AppliesTypeEnum myAppliesTo;

View File

@ -27,5 +27,6 @@ enum RuleOpEnum {
DENY_ALL,
TRANSACTION,
METADATA,
BATCH
BATCH,
DELETE
}

View File

@ -1,27 +0,0 @@
package ca.uhn.fhir.rest.server.interceptor.auth;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
enum RuleVerdictEnum {
ALLOW,
DENY,
NO_DECISION
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.util;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -33,33 +35,13 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
/**
* Fetch resources from a bundle
*/
public class BundleUtil {
/**
* Extract all of the resources from a given bundle
*/
public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
for (IBase nextEntry : entries) {
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
retVal.add((IBaseResource) next);
}
}
return retVal;
}
@SuppressWarnings("unchecked")
public static List<Pair<String, IBaseResource>> getBundleEntryUrlsAndResources(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
@ -96,7 +78,7 @@ public class BundleUtil {
return retVal;
}
public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("type");
@ -108,4 +90,96 @@ public class BundleUtil {
return null;
}
/**
* Extract all of the resources from a given bundle
*/
public static List<BundleEntryParts> toListOfEntries(FhirContext theContext, IBaseBundle theBundle) {
List<BundleEntryParts> retVal = new ArrayList<BundleEntryParts>();
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request");
BaseRuntimeElementCompositeDefinition<?> requestElem = (BaseRuntimeElementCompositeDefinition<?>) requestChild.getChildByName("request");
BaseRuntimeChildDefinition urlChild = requestElem.getChildByName("url");
BaseRuntimeChildDefinition methodChild = requestElem.getChildByName("method");
IBaseResource resource = null;
String url = null;
RequestTypeEnum requestType = null;
for (IBase nextEntry : entries) {
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
resource = (IBaseResource) next;
}
for (IBase nextRequest : requestChild.getAccessor().getValues(nextEntry)) {
for (IBase nextUrl : urlChild.getAccessor().getValues(nextRequest)) {
url = ((IPrimitiveType<?>)nextUrl).getValueAsString();
}
for (IBase nextUrl : methodChild.getAccessor().getValues(nextRequest)) {
String methodString = ((IPrimitiveType<?>)nextUrl).getValueAsString();
if (isNotBlank(methodString)) {
requestType = RequestTypeEnum.valueOf(methodString);
}
}
}
/*
* All 3 might be null - That's ok because we still want to know the
* order in the original bundle.
*/
retVal.add(new BundleEntryParts(requestType, url, resource));
}
return retVal;
}
/**
* Extract all of the resources from a given bundle
*/
public static List<IBaseResource> toListOfResources(FhirContext theContext, IBaseBundle theBundle) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle);
BaseRuntimeChildDefinition entryChild = def.getChildByName("entry");
List<IBase> entries = entryChild.getAccessor().getValues(theBundle);
BaseRuntimeElementCompositeDefinition<?> entryChildElem = (BaseRuntimeElementCompositeDefinition<?>) entryChild.getChildByName("entry");
BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource");
for (IBase nextEntry : entries) {
for (IBase next : resourceChild.getAccessor().getValues(nextEntry)) {
retVal.add((IBaseResource) next);
}
}
return retVal;
}
public static class BundleEntryParts
{
private final RequestTypeEnum myRequestType;
private final IBaseResource myResource;
private final String myUrl;
public BundleEntryParts(RequestTypeEnum theRequestType, String theUrl, IBaseResource theResource) {
super();
myRequestType = theRequestType;
myUrl = theUrl;
myResource = theResource;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
public IBaseResource getResource() {
return myResource;
}
public String getUrl() {
return myUrl;
}
}
}

View File

@ -665,18 +665,21 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
theProvider.setSearchResultDao(mySearchResultDao);
}
protected void notifyInterceptors(RestOperationTypeEnum operationType, ActionRequestDetails requestDetails) {
protected void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails requestDetails) {
if (requestDetails.getId() != null && requestDetails.getId().hasResourceType() && isNotBlank(requestDetails.getResourceType())) {
if (requestDetails.getId().getResourceType().equals(requestDetails.getResourceType()) == false) {
throw new InternalErrorException("Inconsistent server state - Resource types don't match: " + requestDetails.getId().getResourceType() + " / " + requestDetails.getResourceType());
}
}
requestDetails.notifyIncomingRequestPreHandled(theOperationType);
List<IServerInterceptor> interceptors = getConfig().getInterceptors();
if (interceptors == null) {
return;
}
for (IServerInterceptor next : interceptors) {
next.incomingRequestPreHandled(operationType, requestDetails);
next.incomingRequestPreHandled(theOperationType, requestDetails);
}
}

View File

@ -0,0 +1,267 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.hl7.fhir.dstu3.model.BaseResource;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.Bundle.SearchEntryMode;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Device;
import org.hl7.fhir.dstu3.model.DiagnosticOrder;
import org.hl7.fhir.dstu3.model.DocumentManifest;
import org.hl7.fhir.dstu3.model.DocumentReference;
import org.hl7.fhir.dstu3.model.Encounter;
import org.hl7.fhir.dstu3.model.Encounter.EncounterClass;
import org.hl7.fhir.dstu3.model.Encounter.EncounterLocationComponent;
import org.hl7.fhir.dstu3.model.Encounter.EncounterState;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ImagingStudy;
import org.hl7.fhir.dstu3.model.InstantType;
import org.hl7.fhir.dstu3.model.Location;
import org.hl7.fhir.dstu3.model.Medication;
import org.hl7.fhir.dstu3.model.MedicationOrder;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.Practitioner;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.dstu3.model.TemporalPrecisionEnum;
import org.hl7.fhir.dstu3.model.UnsignedIntType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
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.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
public class AuthorizationInterceptorResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(AuthorizationInterceptorResourceProviderDstu3Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Override
public void before() throws Exception {
super.before();
myDaoConfig.setAllowMultipleDelete(true);
unregisterInterceptors();
}
private void unregisterInterceptors() {
for (IServerInterceptor next : new ArrayList<IServerInterceptor>(ourRestServer.getInterceptors())) {
if (next instanceof AuthorizationInterceptor) {
ourRestServer.unregisterInterceptor(next);
}
}
}
@Test
public void testCreateConditional() {
Patient patient = new Patient();
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.addName().addFamily("Tester").addGiven("Raghad");
final MethodOutcome output1 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute();
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/" + output1.getId().getIdPart())).andThen()
.build();
//@formatter:on
}
});
patient = new Patient();
patient.setId(output1.getId().toUnqualifiedVersionless());
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.addName().addFamily("Tester").addGiven("Raghad");
MethodOutcome output2 = ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|100").execute();
assertEquals(output1.getId().getIdPart(), output2.getId().getIdPart());
patient = new Patient();
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.addName().addFamily("Tester").addGiven("Raghad");
try {
ourClient.update().resource(patient).conditionalByUrl("Patient?identifier=http://uhn.ca/mrns|101").execute();
fail();
} catch (ForbiddenOperationException e) {
assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage());
}
patient = new Patient();
patient.setId("999");
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
patient.addName().addFamily("Tester").addGiven("Raghad");
try {
ourClient.update().resource(patient).execute();
fail();
} catch (ForbiddenOperationException e) {
assertEquals("HTTP 403 Forbidden: Access denied by default policy (no applicable rules)", e.getMessage());
}
}
@Test
public void testDeleteResourceConditional() throws IOException {
String methodName = "testDeleteResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
final IdType id;
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdType(newIdString);
} finally {
response.close();
}
pt = new Patient();
pt.addName().addFamily("FOOFOOFOO");
resource = myFhirCtx.newXmlParser().encodeResourceToString(pt);
post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(post);
final IdType id2;
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id2 = new IdType(newIdString);
} finally {
response.close();
}
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 2").delete().allResources().inCompartment("Patient", new IdDt("Patient/" + id.getIdPart())).andThen()
.build();
//@formatter:on
}
});
HttpDelete delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName);
response = ourHttpClient.execute(delete);
try {
assertEquals(204, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
delete = new HttpDelete(ourServerBase + "/Patient?name=FOOFOOFOO");
response = ourHttpClient.execute(delete);
try {
assertEquals(403, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
}
}

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
@ -152,7 +153,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by rule: Rule 1"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@ -189,7 +190,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by rule: Default Rule"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@ -219,8 +220,7 @@ public class AuthorizationInterceptorDstu2Test {
}
@Test
@Ignore // not done yet
public void testBatchWhenTransactionAllowed() throws Exception {
public void testBatchWhenOnlyTransactionAllowed() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
@ -236,7 +236,7 @@ public class AuthorizationInterceptorDstu2Test {
Bundle input = new Bundle();
input.setType(BundleTypeEnum.BATCH);
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient");
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(HTTPVerbEnum.POST);
Bundle output = new Bundle();
output.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
@ -252,7 +252,80 @@ public class AuthorizationInterceptorDstu2Test {
httpPost.setEntity(createFhirResourceEntity(input));
status = ourClient.execute(httpPost);
extractResponseAndClose(status);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
}
@Test
public void testBatchWhenTransactionReadDenied() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.build();
//@formatter:on
}
});
Bundle input = new Bundle();
input.setType(BundleTypeEnum.BATCH);
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(HTTPVerbEnum.POST);
Bundle output = new Bundle();
output.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
output.addEntry().setResource(createPatient(2));
HttpPost httpPost;
HttpResponse status;
String response;
ourReturn = Arrays.asList((IResource)output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
status = ourClient.execute(httpPost);
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
}
@Test
public void testBatchWhenTransactionWrongBundleType() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@Override
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
//@formatter:off
return new RuleBuilder()
.allow("Rule 1").transaction().withAnyOperation().andApplyNormalRules().andThen()
.allow("Rule 2").write().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.allow("Rule 2").read().allResources().inCompartment("Patient", new IdDt("Patient/1")).andThen()
.build();
//@formatter:on
}
});
Bundle input = new Bundle();
input.setType(BundleTypeEnum.COLLECTION);
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(HTTPVerbEnum.POST);
Bundle output = new Bundle();
output.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
output.addEntry().setResource(createPatient(1));
HttpPost httpPost;
HttpResponse status;
String response;
ourReturn = Arrays.asList((IResource)output);
ourHitMethod = false;
httpPost = new HttpPost("http://localhost:" + ourPort + "/");
httpPost.setEntity(createFhirResourceEntity(input));
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertThat(response, containsString("Invalid transaction bundle type: collection"));
}
@Test
@ -277,7 +350,7 @@ public class AuthorizationInterceptorDstu2Test {
httpGet = new HttpGet("http://localhost:" + ourPort + "/metadata");
status = ourClient.execute(httpGet);
extractResponseAndClose(status);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
}
@Test
@ -312,7 +385,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2"));
@ -322,7 +395,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
@ -332,7 +405,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@ -405,7 +478,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createObservation(10, "Patient/2"));
@ -415,7 +488,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createPatient(1), createObservation(10, "Patient/2"));
@ -425,7 +498,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourReturn = Arrays.asList(createPatient(2), createObservation(10, "Patient/1"));
@ -435,7 +508,7 @@ public class AuthorizationInterceptorDstu2Test {
response = extractResponseAndClose(status);
ourLog.info(response);
assertThat(response, containsString("Access denied by default policy (no applicable rules)"));
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
}
@ -458,7 +531,7 @@ public class AuthorizationInterceptorDstu2Test {
Bundle input = new Bundle();
input.setType(BundleTypeEnum.TRANSACTION);
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient");
input.addEntry().setResource(createPatient(1)).getRequest().setUrl("/Patient").setMethod(HTTPVerbEnum.PUT);
Bundle output = new Bundle();
output.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
@ -500,7 +573,7 @@ public class AuthorizationInterceptorDstu2Test {
status = ourClient.execute(httpPost);
String response = extractResponseAndClose(status);
assertEquals("Access denied by default policy (no applicable rules)", response);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
@ -509,7 +582,7 @@ public class AuthorizationInterceptorDstu2Test {
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals("Access denied by default policy (no applicable rules)", response);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
@ -543,7 +616,7 @@ public class AuthorizationInterceptorDstu2Test {
httpDelete = new HttpDelete("http://localhost:" + ourPort + "/Patient/2");
status = ourClient.execute(httpDelete);
extractResponseAndClose(status);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
ourHitMethod = false;
@ -578,7 +651,7 @@ public class AuthorizationInterceptorDstu2Test {
status = ourClient.execute(httpPost);
String response = extractResponseAndClose(status);
assertEquals("Access denied by default policy (no applicable rules)", response);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
ourHitMethod = false;
@ -603,10 +676,12 @@ public class AuthorizationInterceptorDstu2Test {
status = ourClient.execute(httpPost);
response = extractResponseAndClose(status);
assertEquals("Access denied by default policy (no applicable rules)", response);
assertEquals(401, status.getStatusLine().getStatusCode());
assertEquals(403, status.getStatusLine().getStatusCode());
assertFalse(ourHitMethod);
}
@AfterClass
public static void afterClassClearContext() throws Exception {