updated IResourceAuditor interface

This commit is contained in:
lmds1 2014-11-10 18:09:11 -05:00
parent 025db56fe9
commit 5f3a366ab0
10 changed files with 193 additions and 90 deletions

View File

@ -38,20 +38,24 @@ public class UserInfoInterceptor implements IClientInterceptor {
public static final String HEADER_USER_ID = "fhir-user-id";
public static final String HEADER_USER_NAME = "fhir-user-name";
public static final String HEADER_APPLICATION_NAME = "fhir-app-name";
private String myUserId;
private String myUserName;
private String myAppName;
public UserInfoInterceptor(String theUserId, String theUserName) {
public UserInfoInterceptor(String theUserId, String theUserName, String theAppName) {
super();
myUserId = theUserId;
myUserName = theUserName;
myAppName = theAppName;
}
@Override
public void interceptRequest(HttpRequestBase theRequest) {
if(myUserId != null) theRequest.addHeader(HEADER_USER_ID, myUserId);
if(myUserName != null) theRequest.addHeader(HEADER_USER_NAME, myUserName);
if(myUserName != null) theRequest.addHeader(HEADER_USER_NAME, myUserName);
if(myAppName != null) theRequest.addHeader(HEADER_APPLICATION_NAME, myAppName);
}
@Override

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
@ -29,17 +29,53 @@ import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
public interface IResourceAuditor<T extends IResource> {
/**
* @return the resource to be audited
*/
public T getResource();
/**
* @param resource the resource to be audited by this auditor
*/
public void setResource(T resource);
/**
* @return true if this resource is to be audited, false otherwise
*/
public boolean isAuditable();
/**
* An instance-specific descriptor of the Participant Object ID audited, such as a person's name
* @return the descriptive name of the resource object
*/
public String getName();
/**
* @return the identifier of the resource to be audited
*/
public BaseIdentifierDt getIdentifier();
/**
* @return the SecurityEventObjectTypeEnum of this resource
*/
public SecurityEventObjectTypeEnum getType();
/**
* @return a text description of the resource
*/
public String getDescription();
//public List<ObjectDetail> getDetail(); //see operationoutcome -- need to move specifics insto dstu and keep base objects in core
/**
* @return a map of additional details to be audited
*/
public Map<String, String> getDetail();
/**
* Denotes policy-defined sensitivity for the Participant Object ID such as VIP, HIV status, mental health status or similar topics
* @return the sensitivity of this resource
*/
public SecurityEventObjectSensitivityEnum getSensitivity();
}

View File

@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectSensitivityEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
@ -41,7 +40,7 @@ public class DiagnosticReportAuditor implements IResourceAuditor<DiagnosticRepor
@Override
public String getName() {
if(myDiagnosticReport != null){
return myDiagnosticReport.getName().getText().getValue();
return "Diagnostic Report: " + myDiagnosticReport.getName().getText().getValue();
}
return null;
}
@ -75,25 +74,15 @@ public class DiagnosticReportAuditor implements IResourceAuditor<DiagnosticRepor
}
@Override
public List<ObjectDetail> getDetail() {
List<ObjectDetail> details = new ArrayList<ObjectDetail>();
details.add(makeObjectDetail("dateIssued", myDiagnosticReport.getIssued().getValueAsString()));
details.add(makeObjectDetail("version", myDiagnosticReport.getId().getVersionIdPart()));
public Map<String, String> getDetail() {
Map<String, String> details = new HashMap<String, String>();
details.put("dateIssued", myDiagnosticReport.getIssued().getValueAsString());
details.put("version", myDiagnosticReport.getId().getVersionIdPart());
return details;
}
@Override
public SecurityEventObjectSensitivityEnum getSensitivity() {
return null; //no sensitivity indicated
}
private ObjectDetail makeObjectDetail(String type, String value) {
ObjectDetail detail = new ObjectDetail();
if(type != null)
detail.setType(type);
if(value != null)
detail.setValue(value.getBytes());
return detail;
}
}
}

View File

@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectSensitivityEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
@ -54,7 +53,7 @@ public class EncounterAuditor implements IResourceAuditor<Encounter> {
String id = myEncounter.getIdentifierFirstRep().getValue().getValue();
String system = myEncounter.getIdentifierFirstRep().getSystem().getValueAsString();
String service = myEncounter.getServiceProvider().getDisplay().getValue();
return id + "/" + system + ": " + service;
return "Encounter: " + id + "/" + system + ": " + service;
}
return null;
}
@ -79,31 +78,22 @@ public class EncounterAuditor implements IResourceAuditor<Encounter> {
String status = myEncounter.getStatus().getValueAsString();
String startDate = myEncounter.getPeriod().getStart().getValueAsString();
String endDate = myEncounter.getPeriod().getEnd().getValueAsString();
return type + ": " + status +", "+ startDate + " - " + endDate;
return "Encounter: " + type + ": " + status +", "+ startDate + " - " + endDate;
}
return null;
}
@Override
public List<ObjectDetail> getDetail() {
List<ObjectDetail> details = new ArrayList<ObjectDetail>();
details.add(makeObjectDetail("startDate", myEncounter.getPeriod().getStart().getValueAsString()));
details.add(makeObjectDetail("endDate", myEncounter.getPeriod().getEnd().getValueAsString()));
details.add(makeObjectDetail("service", myEncounter.getServiceProvider().getDisplay().getValue()));
details.add(makeObjectDetail("type", myEncounter.getTypeFirstRep().getText().getValue()));
details.add(makeObjectDetail("status", myEncounter.getStatus().getValueAsString()));
public Map<String, String> getDetail() {
Map<String, String> details = new HashMap<String, String>();
details.put("startDate", myEncounter.getPeriod().getStart().getValueAsString());
details.put("endDate", myEncounter.getPeriod().getEnd().getValueAsString());
details.put("service", myEncounter.getServiceProvider().getDisplay().getValue());
details.put("type", myEncounter.getTypeFirstRep().getText().getValue());
details.put("status", myEncounter.getStatus().getValueAsString());
return details;
}
private ObjectDetail makeObjectDetail(String type, String value) {
ObjectDetail detail = new ObjectDetail();
if(type != null)
detail.setType(type);
if(value != null)
detail.setValue(value.getBytes());
return detail;
}
@Override
public SecurityEventObjectSensitivityEnum getSensitivity() {
//override this method to provide sensitivity information about the visit

View File

@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Medication;
import ca.uhn.fhir.model.dstu.resource.MedicationPrescription;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectSensitivityEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
@ -44,10 +43,10 @@ public class MedicationPrescriptionAuditor implements IResourceAuditor<Medicatio
if(myMedicationPrescription.getMedication() != null){
Medication m = (Medication) myMedicationPrescription.getMedication().getResource();
if(m != null){
return m.getName().getValue();
return "Medication Prescription: " + m.getName().getValue();
}
}
return myMedicationPrescription.getId().getValueAsString(); //if we don't have a medication name, use the id as the name
return "Medication Prescription: " + myMedicationPrescription.getId().getValueAsString(); //if we don't have a medication name, use the id as the name
}
return ""; //no medication prescription, nothing to do here
}
@ -81,7 +80,7 @@ public class MedicationPrescriptionAuditor implements IResourceAuditor<Medicatio
}
@Override
public List<ObjectDetail> getDetail() {
public Map<String, String> getDetail() {
return null; //no additional details required for audit?
}

View File

@ -20,12 +20,11 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Medication;
import ca.uhn.fhir.model.dstu.resource.MedicationStatement;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectSensitivityEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
@ -43,9 +42,9 @@ public class MedicationStatementAuditor implements IResourceAuditor<MedicationSt
if(myMedicationStatement != null){
Medication m = (Medication) myMedicationStatement.getMedication().getResource();
if(m != null){
return m.getName().getValue();
return "Medication Statement: " + m.getName().getValue();
}
return myMedicationStatement.getId().getValueAsString(); //if we don't have a medication name, use the id as the name
return "Medication Statement: " + myMedicationStatement.getId().getValueAsString(); //if we don't have a medication name, use the id as the name
}
return null;
}
@ -79,7 +78,7 @@ public class MedicationStatementAuditor implements IResourceAuditor<MedicationSt
}
@Override
public List<ObjectDetail> getDetail() {
public Map<String, String> getDetail() {
return null; //no additional details required for audit?
}

View File

@ -20,13 +20,12 @@ package ca.uhn.fhir.rest.server.audit;
* #L%
*/
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectSensitivityEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
@ -52,7 +51,7 @@ public class PatientAuditor implements IResourceAuditor<Patient> {
@Override
public String getName() {
if(myPatient != null){
return myPatient.getNameFirstRep().getText().getValue();
return "Patient: " + myPatient.getNameFirstRep().getText().getValue();
}
return null;
}
@ -76,22 +75,17 @@ public class PatientAuditor implements IResourceAuditor<Patient> {
}
@Override
public List<ObjectDetail> getDetail() {
public Map<String, String> getDetail() {
if(myPatient != null){
List<IdentifierDt> ids = myPatient.getIdentifier();
if(ids != null && !ids.isEmpty()){
List<ObjectDetail> detailList = new ArrayList<ObjectDetail>(ids.size());
Map<String, String> detailMap = new HashMap<String, String>();
for(IdentifierDt id: ids){
ObjectDetail detail = new ObjectDetail();
detail.setType(id.getSystem().getValueAsString());
try {
detail.setValue(id.getValue().getValueAsString().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
detail.setValue(id.getValue().getValueAsString().getBytes());
}
detailList.add(detail);
String key = id.getSystem().getValueAsString();
String value = id.getValue().getValueAsString();
detailMap.put(key, value);
}
return detailList;
return detailMap;
}
}

View File

@ -26,6 +26,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -35,19 +36,23 @@ import org.apache.commons.lang3.NotImplementedException;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.Event;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectDetail;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectElement;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.Participant;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ParticipantNetwork;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.Source;
import ca.uhn.fhir.model.dstu.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventActionEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectLifecycleEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventOutcomeEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventParticipantNetworkTypeEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventSourceTypeEnum;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.client.interceptor.UserInfoInterceptor;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants;
@ -68,12 +73,20 @@ public class AuditingInterceptor extends InterceptorAdapter {
private IAuditDataStore myDataStore = null;
private Map<String, Class<? extends IResourceAuditor<? extends IResource>>> myAuditableResources = new HashMap<String, Class<? extends IResourceAuditor<? extends IResource>>>();
private boolean myClientParamsOptional = false;
protected String mySiteId;
public AuditingInterceptor() {
myClientParamsOptional = false;
mySiteId = "NONE";
myClientParamsOptional = false;
}
public AuditingInterceptor(boolean theClientParamsOptional){
public AuditingInterceptor(String theSiteId) {
mySiteId = theSiteId;
myClientParamsOptional = false;
}
public AuditingInterceptor(String theSiteId, boolean theClientParamsOptional){
mySiteId = theSiteId;
myClientParamsOptional = theClientParamsOptional;
}
@ -91,8 +104,14 @@ public class AuditingInterceptor extends InterceptorAdapter {
auditEvent.setEvent(getEventInfo(theRequestDetails));
//get user info from request if available
Participant participant = getParticipant(theServletRequest);
if(participant == null) return true; //no user to audit - throws exception if client params are required
if(participant == null){
log.debug("No participant to audit");
return true; //no user to audit - throws exception if client params are required
}
List<Participant> participants = new ArrayList<SecurityEvent.Participant>(1);
participants.add(participant);
auditEvent.setParticipant(participants);
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getResourceOperationType());
byte[] query = getQueryFromRequestDetails(theRequestDetails);
List<ObjectElement> auditableObjects = new ArrayList<SecurityEvent.ObjectElement>();
@ -101,8 +120,14 @@ public class AuditingInterceptor extends InterceptorAdapter {
ObjectElement auditableObject = getObjectElement(resource, lifecycle , query);
if(auditableObject != null) auditableObjects.add(auditableObject);
}
if(!auditableObjects.isEmpty()) return true; //no PHI to audit
if(auditableObjects.isEmpty()){
log.debug("No auditable resources to audit.");
return true; //no PHI to audit
}else{
log.debug("Auditing " + auditableObjects.size() + " resources.");
}
auditEvent.setObject(auditableObjects);
auditEvent.setSource(getSourceElement(theServletRequest));
store(auditEvent);
return true; //success
}catch(Exception e){
@ -126,15 +151,26 @@ public class AuditingInterceptor extends InterceptorAdapter {
//get user info from request if available
Participant participant = getParticipant(theServletRequest);
if(participant == null) return true; //no user to audit - throws exception if client params are required
if(participant == null){
log.debug("No participant to audit");
return true; //no user to audit - throws exception if client params are required
}
List<Participant> participants = new ArrayList<SecurityEvent.Participant>(1);
participants.add(participant);
auditEvent.setParticipant(participants);
byte[] query = getQueryFromRequestDetails(theRequestDetails);
SecurityEventObjectLifecycleEnum lifecycle = mapResourceTypeToSecurityLifecycle(theRequestDetails.getResourceOperationType());
ObjectElement auditableObject = getObjectElement(theResponseObject, lifecycle , query);
if(auditableObject == null) return true; //nothing to audit
if(auditableObject == null){
log.debug("No auditable resources to audit");
return true;
}
List<ObjectElement> auditableObjects = new ArrayList<SecurityEvent.ObjectElement>(1);
auditableObjects.add(auditableObject);
auditEvent.setObject(auditableObjects);
auditEvent.setSource(getSourceElement(theServletRequest));
log.debug("Auditing one resource.");
store(auditEvent);
return true;
}catch(Exception e){
@ -143,7 +179,6 @@ public class AuditingInterceptor extends InterceptorAdapter {
}
}
protected void store(SecurityEvent auditEvent) throws Exception {
if(myDataStore == null) throw new InternalErrorException("No data store provided to persist audit events");
myDataStore.store(auditEvent);
@ -182,7 +217,8 @@ public class AuditingInterceptor extends InterceptorAdapter {
protected ObjectElement getObjectElement(IResource resource, SecurityEventObjectLifecycleEnum lifecycle, byte[] query) throws InstantiationException, IllegalAccessException {
String resourceType = resource.getResourceName();
if(myAuditableResources.containsKey(resourceType)){
if(myAuditableResources.containsKey(resourceType)){
log.debug("Found auditable resource of type: " + resourceType);
@SuppressWarnings("unchecked")
IResourceAuditor<IResource> auditableResource = (IResourceAuditor<IResource>) myAuditableResources.get(resourceType).newInstance();
auditableResource.setResource(resource);
@ -192,19 +228,44 @@ public class AuditingInterceptor extends InterceptorAdapter {
object.setLifecycle(lifecycle);
object.setQuery(query);
object.setName(auditableResource.getName());
object.setIdentifier(auditableResource.getIdentifier());
object.setIdentifier((IdentifierDt) auditableResource.getIdentifier());
object.setType(auditableResource.getType());
object.setDescription(auditableResource.getDescription());
object.setDetail(auditableResource.getDetail());
object.setSensitivity(auditableResource.getSensitivity());
Map<String, String> detailMap = auditableResource.getDetail();
if(detailMap != null && !detailMap.isEmpty()){
List<ObjectDetail> details = new ArrayList<SecurityEvent.ObjectDetail>();
for(Entry<String, String> entry: detailMap.entrySet()){
ObjectDetail detail = makeObjectDetail(entry.getKey(), entry.getValue());
details.add(detail);
}
object.setDetail(details);
}
object.setSensitivity(auditableResource.getSensitivity());
return object;
}
}
}else{
log.debug("Resource is not auditable");
}
}else{
log.debug("No auditor configured for resource type " + resourceType);
}
return null; //not something we care to audit
}
protected ObjectDetail makeObjectDetail(String type, String value) {
ObjectDetail detail = new ObjectDetail();
if(type != null)
detail.setType(type);
if(value != null)
detail.setValue(value.getBytes());
return detail;
}
protected Participant getParticipant(HttpServletRequest theServletRequest) throws InvalidRequestException, NotImplementedException {
if(theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION) != null && theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION).startsWith("OAuth")){
if(myClientParamsOptional){
log.debug("OAuth request received but no auditing required.");
return null;
}
//TODO: get user info from token
throw new NotImplementedException("OAuth user auditing not yet implemented.");
}else { //no auth or basic auth or anything else, use HTTP headers for user info
@ -224,7 +285,38 @@ public class AuditingInterceptor extends InterceptorAdapter {
network.setIdentifier(userIp);
return participant;
}
}
protected Source getSourceElement(HttpServletRequest theServletRequest) {
if(theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION) != null && theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION).startsWith("OAuth")){
if(myClientParamsOptional) return null; //no auditing required
//TODO: get application info from token
throw new NotImplementedException("OAuth auditing not yet implemented.");
}else { //no auth or basic auth or anything else, use HTTP headers for audit info
String appId = theServletRequest.getHeader(UserInfoInterceptor.HEADER_APPLICATION_NAME);
Source source = new Source();
source.setIdentifier(appId);
source.setType(getAccessType(theServletRequest));
source.setSite(getSiteId(theServletRequest));
return source;
}
}
protected StringDt getSiteId(HttpServletRequest theServletRequest) {
return new StringDt(mySiteId); //override this method to pull the site id from the request info
}
protected List<CodingDt> getAccessType(HttpServletRequest theServletRequest) {
List<CodingDt> types = new ArrayList<CodingDt>();
if(theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION) != null && theServletRequest.getHeader(Constants.HEADER_AUTHORIZATION).startsWith("OAuth")){
types.add(new CodingDt(SecurityEventSourceTypeEnum.USER_DEVICE.getSystem(), SecurityEventSourceTypeEnum.USER_DEVICE.getCode()));
}else{
String userId = theServletRequest.getHeader(UserInfoInterceptor.HEADER_USER_ID);
String appId = theServletRequest.getHeader(UserInfoInterceptor.HEADER_APPLICATION_NAME);
if (userId == null && appId != null) types.add(new CodingDt(SecurityEventSourceTypeEnum.APPLICATION_SERVER.getSystem(), SecurityEventSourceTypeEnum.APPLICATION_SERVER.getCode()));
else types.add(new CodingDt(SecurityEventSourceTypeEnum.USER_DEVICE.getSystem(), SecurityEventSourceTypeEnum.USER_DEVICE.getCode()));
}
return types;
}
protected SecurityEventActionEnum mapResourceTypeToSecurityEventAction(RestfulOperationTypeEnum resourceOperationType) {

View File

@ -201,7 +201,7 @@ public class ResourceWithExtensionsA extends BaseResource {
}
@Override
public ResourceTypeEnum getResourceType() {
public String getResourceName() {
return null; //not implemented
}

View File

@ -108,8 +108,8 @@ public class Patient extends BaseResource implements IResource {
@Override
public ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.PATIENT;
public String getResourceName() {
return Patient.class.getName();
}