diff --git a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfo.java b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfo.java index c01996ad67..f35f740326 100644 --- a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfo.java +++ b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfo.java @@ -98,6 +98,7 @@ class AuditInfo { static final int QUEUE_NAME = 38; static final int STATUS = 39; static final int FHIR_WEB_APP_NAME = 40; + static final int ARCHIVE_USER_ID = 41; private final String[] fields; @@ -143,7 +144,8 @@ class AuditInfo { encode(i.findSCP), encode(i.queueName), encode(i.status), - encode(i.fhirWebAppName) + encode(i.fhirWebAppName), + encode(i.archiveUserID) }; } diff --git a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfoBuilder.java b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfoBuilder.java index 84baafbb8d..554119509e 100644 --- a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfoBuilder.java +++ b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditInfoBuilder.java @@ -101,6 +101,7 @@ class AuditInfoBuilder { final String queueName; final String status; final String fhirWebAppName; + final String archiveUserID; static class Builder { private String callingHost; @@ -144,6 +145,7 @@ static class Builder { private String queueName; private String status; private String fhirWebAppName; + private String archiveUserID; Builder callingHost(String val) { callingHost = val; @@ -337,6 +339,10 @@ Builder fhirWebAppName(String val) { fhirWebAppName = val; return this; } + Builder archiveUserID(String val) { + archiveUserID = val; + return this; + } AuditInfoBuilder build() { return new AuditInfoBuilder(this); } @@ -388,6 +394,7 @@ private AuditInfoBuilder(Builder builder) { queueName = builder.queueName; status = builder.status; fhirWebAppName = builder.fhirWebAppName; + archiveUserID = builder.archiveUserID; } private static String idWithIssuer(ArchiveDeviceExtension arcDev, String cx) { diff --git a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditService.java b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditService.java index 94f488c174..9d87831ef1 100644 --- a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditService.java +++ b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/AuditService.java @@ -170,7 +170,7 @@ private void aggregateAuditMessage(AuditLogger auditLogger, Path path) throws Ex auditExternalRetrieve(auditLogger, path, eventType); break; case LDAP_CHANGES: - auditSoftwareConfiguration(auditLogger, path, eventType); + SoftwareConfigurationAuditService.audit(auditLogger, path, eventType); break; case QUEUE_EVENT: TaskAuditService.audit(auditLogger, path, eventType); @@ -429,27 +429,31 @@ void spoolBulkTasksEvent(BulkTaskEvent bulkTasksEvent) { } void spoolSoftwareConfiguration(SoftwareConfiguration softwareConfiguration) { - String callingUser = softwareConfiguration.getRequest() != null - ? KeycloakContext.valueOf(softwareConfiguration.getRequest()).getUserName() - : softwareConfiguration.getDeviceName(); try { - writeSpoolFile( - SoftwareConfigurationAuditService.auditInfo(softwareConfiguration, callingUser), - AuditUtils.EventType.LDAP_CHNGS, + if (softwareConfiguration.getRequest() == null) { + writeSpoolFile(AuditUtils.EventType.LDAP_CHNGS.name(), + new AuditInfoBuilder.Builder() + .archiveUserID(softwareConfiguration.getDeviceName()) + .toAuditInfo(), + softwareConfiguration.getLdapDiff().toString().getBytes()); + return; + } + + HttpServletRequestInfo httpServletRequestInfo = HttpServletRequestInfo.valueOf(softwareConfiguration.getRequest()); + writeSpoolFile(AuditUtils.EventType.LDAP_CHNGS.name(), + new AuditInfoBuilder.Builder() + .callingUserID(httpServletRequestInfo.requesterUserID) + .callingHost(httpServletRequestInfo.requesterHost) + .calledUserID(httpServletRequestInfo.requestURI) + .archiveUserID(softwareConfiguration.getDeviceName()) + .toAuditInfo(), softwareConfiguration.getLdapDiff().toString().getBytes()); } catch (Exception e) { - LOG.info("Failed to spool Software Configuration Changes for [Device={}] done by [CallingUser={}]\n", - softwareConfiguration.getDeviceName(), callingUser, e); + LOG.info("Failed to spool Software Configuration Changes for [Device={}] \n", + softwareConfiguration.getDeviceName(), e); } } - private void auditSoftwareConfiguration(AuditLogger auditLogger, Path path, AuditUtils.EventType eventType) - throws Exception { - emitAuditMessage( - SoftwareConfigurationAuditService.auditMsg(auditLogger, path, eventType), - auditLogger); - } - void spoolExternalRetrieve(ExternalRetrieveContext ctx) { try { writeSpoolFile(AuditUtils.EventType.INST_RETRV, null, @@ -1447,6 +1451,48 @@ void spoolQStarVerification(QStarVerification qStarVerification) { } } + //, byte[]... data + void writeSpoolFile(String fileName, AuditInfo auditInfo, byte[]... data) { + if (auditInfo == null) { + LOG.info("Attempt to write empty file : " + fileName); + return; + } + + FileTime eventTime = null; + AuditLoggerDeviceExtension ext = device.getDeviceExtension(AuditLoggerDeviceExtension.class); + for (AuditLogger auditLogger : ext.getAuditLoggers()) { + if (!auditLogger.isInstalled()) + continue; + + try { + Path dir = toDirPath(auditLogger); + Files.createDirectories(dir); + Path filePath = Files.createTempFile(dir, fileName, null); + try (BufferedOutputStream out = new BufferedOutputStream( + Files.newOutputStream(filePath, StandardOpenOption.APPEND))) { + try (SpoolFileWriter writer = new SpoolFileWriter(Files.newBufferedWriter( + filePath, StandardCharsets.UTF_8, StandardOpenOption.APPEND))) { + writer.writeLine(auditInfo); + } + out.write(data[0]); + if (data.length > 1 && data[1].length > 0) + out.write(data[1]); + } + + if (eventTime == null) + eventTime = Files.getLastModifiedTime(filePath); + else + Files.setLastModifiedTime(filePath, eventTime); + + if (!getArchiveDevice().isAuditAggregate()) + auditAndProcessFile(auditLogger, filePath); + } catch (Exception e) { + LOG.info("Failed to write [AuditSpoolFile={}] at [AuditLogger={}]\n", + fileName, auditLogger.getCommonName(), e); + } + } + } + void writeSpoolFile(String fileName, boolean aggregate, AuditInfo... auditInfos) { if (auditInfos == null) { LOG.info("Attempt to write empty file : " + fileName); diff --git a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/ParticipantObjectID.java b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/ParticipantObjectID.java index 8b51221bcc..9c985d1137 100644 --- a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/ParticipantObjectID.java +++ b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/ParticipantObjectID.java @@ -226,17 +226,4 @@ private static ParticipantObjectDescriptionBuilder participantObjDesc(InstanceIn .acc(instanceInfo.getAcc()) .mpps(instanceInfo.getMppsArray()); } - - static ParticipantObjectIdentification softwareConfParticipant(SpoolFileReader reader, AuditInfo auditInfo) { - return new ParticipantObjectIdentificationBuilder( - auditInfo.getField(AuditInfo.CALLED_USERID), - AuditMessages.ParticipantObjectIDTypeCode.DeviceName, - AuditMessages.ParticipantObjectTypeCode.SystemObject, - null) - .detail(AuditMessages.createParticipantObjectDetail("Alert Description", - !reader.getInstanceLines().isEmpty() - ? String.join("\n", reader.getInstanceLines()) - : null)) - .build(); - } } diff --git a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/SoftwareConfigurationAuditService.java b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/SoftwareConfigurationAuditService.java index 1ff9662362..d7ccd57cad 100644 --- a/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/SoftwareConfigurationAuditService.java +++ b/dcm4chee-arc-audit/src/main/java/org/dcm4chee/arc/audit/SoftwareConfigurationAuditService.java @@ -39,67 +39,96 @@ */ package org.dcm4chee.arc.audit; -import jakarta.servlet.http.HttpServletRequest; -import org.dcm4che3.audit.ActiveParticipant; -import org.dcm4che3.audit.ActiveParticipantBuilder; -import org.dcm4che3.audit.AuditMessage; -import org.dcm4che3.audit.AuditMessages; +import org.dcm4che3.audit.*; import org.dcm4che3.net.audit.AuditLogger; -import org.dcm4chee.arc.event.SoftwareConfiguration; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * @author Vrinda Nayak * @author Gunter Zeilinger * @since Oct 2018 */ -class SoftwareConfigurationAuditService { +class SoftwareConfigurationAuditService extends AuditService { - static AuditInfoBuilder auditInfo(SoftwareConfiguration softwareConfiguration, String callingUser) { - HttpServletRequest request = softwareConfiguration.getRequest(); - return request != null - ? buildSoftwareConfAuditForWeb(request, callingUser) - : new AuditInfoBuilder.Builder().calledUserID(callingUser).build(); + static void audit(AuditLogger auditLogger, Path path, AuditUtils.EventType eventType) { + SpoolFileReader reader = new SpoolFileReader(path); + AuditInfo auditInfo = new AuditInfo(reader.getMainInfo()); + EventIdentification eventIdentification = getEventIdentification(auditInfo, eventType); + eventIdentification.setEventDateTime(getEventTime(path, auditLogger)); + String archiveUserID = auditInfo.getField(AuditInfo.ARCHIVE_USER_ID); + ParticipantObjectIdentification archivePOI = archive(archiveUserID); + if (auditInfo.getField(AuditInfo.CALLING_USERID) == null) { + ActiveParticipant archive = archive(archiveUserID, AuditMessages.UserIDTypeCode.DeviceName, auditLogger); + emitAuditMessage(auditLogger, eventIdentification, Collections.singletonList(archive), archivePOI); + return; + } + + List activeParticipants = new ArrayList<>(); + activeParticipants.add(archive(auditInfo.getField(AuditInfo.CALLED_USERID), + AuditMessages.UserIDTypeCode.URI, + auditLogger)); + activeParticipants.add(requestor(auditInfo)); + emitAuditMessage(auditLogger, eventIdentification, activeParticipants, archivePOI); } - private static AuditInfoBuilder buildSoftwareConfAuditForWeb(HttpServletRequest request, String callingUser) { - return new AuditInfoBuilder.Builder() - .callingUserID(callingUser) - .callingHost(request.getRemoteAddr()) - .calledUserID(request.getRequestURI()) - .build(); + private static EventIdentification getEventIdentification(AuditInfo auditInfo, AuditUtils.EventType eventType) { + String outcome = auditInfo.getField(AuditInfo.OUTCOME); + EventIdentification ei = new EventIdentification(); + ei.setEventID(eventType.eventID); + ei.setEventActionCode(eventType.eventActionCode); + ei.setEventOutcomeDescription(outcome); + ei.setEventOutcomeIndicator(outcome == null + ? AuditMessages.EventOutcomeIndicator.Success + : AuditMessages.EventOutcomeIndicator.MinorFailure); + ei.getEventTypeCode().add(eventType.eventTypeCode); + return ei; } - static AuditMessage auditMsg(AuditLogger auditLogger, Path path, AuditUtils.EventType eventType) { - SpoolFileReader reader = new SpoolFileReader(path); - AuditInfo auditInfo = new AuditInfo(reader.getMainInfo()); - return AuditMessages.createMessage( - EventID.toEventIdentification(auditLogger, path, eventType, auditInfo), - activeParticipants(auditLogger, auditInfo), - ParticipantObjectID.softwareConfParticipant(reader, auditInfo)); + private static ParticipantObjectIdentification archive(String archiveUserID) { + ParticipantObjectIdentification archive = new ParticipantObjectIdentification(); + archive.setParticipantObjectID(archiveUserID); + archive.setParticipantObjectIDTypeCode(AuditMessages.ParticipantObjectIDTypeCode.DeviceName); + archive.setParticipantObjectTypeCode(AuditMessages.ParticipantObjectTypeCode.SystemObject); + return archive; } - private static ActiveParticipant[] activeParticipants(AuditLogger auditLogger, AuditInfo auditInfo) { - ActiveParticipant[] activeParticipants = new ActiveParticipant[2]; - String callingUserID = auditInfo.getField(AuditInfo.CALLING_USERID); - String calledUserID = auditInfo.getField(AuditInfo.CALLED_USERID); - if (callingUserID != null) { - activeParticipants[0] = new ActiveParticipantBuilder(calledUserID, getLocalHostName(auditLogger)) - .userIDTypeCode(AuditMessages.UserIDTypeCode.URI).build(); - activeParticipants[1] - = new ActiveParticipantBuilder(callingUserID, auditInfo.getField(AuditInfo.CALLING_HOST)) - .userIDTypeCode(AuditMessages.userIDTypeCode(callingUserID)) - .isRequester().build(); - } else - activeParticipants[0] = new ActiveParticipantBuilder(calledUserID, getLocalHostName(auditLogger)) - .userIDTypeCode(AuditMessages.UserIDTypeCode.DeviceName) - .isRequester().build(); - return activeParticipants; + private static ActiveParticipant archive( + String archiveUserID, AuditMessages.UserIDTypeCode archiveUserIDTypeCode, AuditLogger auditLogger) { + ActiveParticipant archive = new ActiveParticipant(); + archive.setUserID(archiveUserID); + archive.setUserIDTypeCode(archiveUserIDTypeCode); + archive.setAlternativeUserID(AuditLogger.processID()); + archive.setUserTypeCode(AuditMessages.UserTypeCode.Application); + archive.setUserIsRequestor(archiveUserIDTypeCode == AuditMessages.UserIDTypeCode.DeviceName); + String auditLoggerHostName = auditLogger.getConnections().get(0).getHostname(); + archive.setNetworkAccessPointID(auditLoggerHostName); + archive.setNetworkAccessPointTypeCode( + AuditMessages.isIP(auditLoggerHostName) + ? AuditMessages.NetworkAccessPointTypeCode.IPAddress + : AuditMessages.NetworkAccessPointTypeCode.MachineName); + return archive; } - private static String getLocalHostName(AuditLogger auditLogger) { - return auditLogger.getConnections().get(0).getHostname(); + private static ActiveParticipant requestor(AuditInfo auditInfo) { + String requestorID = auditInfo.getField(AuditInfo.CALLING_USERID); + String requestorHost = auditInfo.getField(AuditInfo.CALLING_HOST); + ActiveParticipant requestor = new ActiveParticipant(); + requestor.setUserID(requestorID); + requestor.setUserIDTypeCode( + AuditMessages.isIP(requestorID) + ? AuditMessages.UserIDTypeCode.NodeID + : AuditMessages.UserIDTypeCode.PersonID); + requestor.setUserIsRequestor(true); + requestor.setNetworkAccessPointID(requestorHost); + requestor.setNetworkAccessPointTypeCode( + AuditMessages.isIP(requestorHost) + ? AuditMessages.NetworkAccessPointTypeCode.IPAddress + : AuditMessages.NetworkAccessPointTypeCode.MachineName); + return requestor; } }