openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
404 lines (334 loc) • 20 kB
text/coffeescript
should = require "should"
auditing = require "../../lib/auditing"
Audit = require("../../lib/model/audits").Audit
AuditMeta = require("../../lib/model/audits").AuditMeta
testUtils = require '../testUtils'
dgram = require 'dgram'
fs = require 'fs'
config = require '../../lib/config/config'
config.auditing = config.get('auditing')
testAuditParticipantQuery = """
TVNIfF5+XCZ8b3BlbmhpbXxvcGVuaGltLW1lZGlhdG9yLW9oaWUteGRzfHBpeHxwaXh8MjAxNTAzMDUxMjUyMzErMDIwMHx8UUJQXlEyM15RQlBfUTIxfGJiMDczYjg1LTU3YTktNDBiYS05MjkxLTE1ZDIxMThkNDhmM3xQfDIuNQ1RUER8SUhFIFBJWCBRdWVyeXxmZmQ4ZTlmNy1hYzJiLTQ2MjUtYmQ4MC1kZTcwNDU5MmQ5ZjN8MTExMTExMTExMV5eXiYxLjIuMyZJU09eUEl8Xl5eRUNJRCZFQ0lEJklTT15QSQ1SQ1B8SQ0=
"""
exports.testAuditMessage = testAudit = """
<85>1 2015-03-05T12:52:31.358+02:00 Hanness-MBP.jembi.local java 9293 IHE+RFC-3881 - <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AuditMessage>
<EventIdentification EventActionCode="E" EventDateTime="2015-03-05T12:52:31.356+02:00" EventOutcomeIndicator="0">
<EventID code="110112" displayName="Query" codeSystemName="DCM"/>
<EventTypeCode code="ITI-9" displayName="PIX Query" codeSystemName="IHE Transactions"/>
</EventIdentification>
<ActiveParticipant UserID="openhim-mediator-ohie-xds|openhim" AlternativeUserID="9293" UserIsRequestor="true" NetworkAccessPointID="192.168.1.111" NetworkAccessPointTypeCode="2">
<RoleIDCode code="110153" displayName="Source" codeSystemName="DCM"/>
</ActiveParticipant>
<ActiveParticipant UserID="pix|pix" AlternativeUserID="2100" UserIsRequestor="false" NetworkAccessPointID="localhost" NetworkAccessPointTypeCode="1">
<RoleIDCode code="110152" displayName="Destination" codeSystemName="DCM"/>
</ActiveParticipant>
<AuditSourceIdentification AuditSourceID="openhim"/>
<ParticipantObjectIdentification ParticipantObjectID="fc133984036647e^^^&1.3.6.1.4.1.21367.2005.13.20.3000&ISO" ParticipantObjectTypeCode="1" ParticipantObjectTypeCodeRole="1">
<ParticipantObjectIDTypeCode code="2" displayName="PatientNumber" codeSystemName="RFC-3881"/>
</ParticipantObjectIdentification>
<ParticipantObjectIdentification ParticipantObjectID="c7bd7244-29bc-4ab5-80ee-74b56eed9db0" ParticipantObjectTypeCode="2" ParticipantObjectTypeCodeRole="24">
<ParticipantObjectIDTypeCode code="ITI-9" displayName="PIX Query" codeSystemName="IHE Transactions"/>
<ParticipantObjectQuery>#{testAuditParticipantQuery}</ParticipantObjectQuery>
<ParticipantObjectDetail type="MSH-10" value="YmIwNzNiODUtNTdhOS00MGJhLTkyOTEtMTVkMjExOGQ0OGYz"/>
</ParticipantObjectIdentification>
</AuditMessage>
"""
# an example from IHE http://ihewiki.wustl.edu/wiki/index.php/Syslog_Collector
testAuditIHE_RFC3881 = """
<85>1 2010-12-17T15:12:04.287-06:00 cabig-h1 OHT 521 IHE+RFC-3881 -
<?xml version="1.0" encoding="UTF-8"?>
<AuditMessage>
<EventIdentification EventDateTime="2010-12-17T15:12:04.287-06:00"
EventOutcomeIndicator="0"
EventActionCode="E">
<EventID code="110114" codeSystemName="DCM"
displayName="UserAuthenticated" />
<EventTypeCode code="110122" codeSystemName="DCM"
displayName="Login" />
</EventIdentification>
<ActiveParticipant UserID="fe80::5999:d1ef:63de:a8bb%11"
UserIsRequestor="true"
NetworkAccessPointTypeCode="1"
NetworkAccessPointID="125.20.175.12">
<RoleIDCode code="110150" codeSystemName="DCM"
displayName="Application" />
</ActiveParticipant>
<ActiveParticipant UserID="farley.granger@wb.com" UserIsRequestor="true"/>
<AuditSourceIdentification AuditEnterpriseSiteID="End User"
AuditSourceID="farley.granger@wb.com">
<AuditSourceTypeCode code="1" />
</AuditSourceIdentification>
</AuditMessage>
"""
# an example from IHE http://ihewiki.wustl.edu/wiki/index.php/Syslog_Collector
testAuditIHE_DICOM = """
<85>1 2013-10-17T15:12:04.287-06:00 cabig-h1 OHT 521 IHE+DICOM -
<?xml version="1.0" encoding="UTF-8"?>
<AuditMessage>
<EventIdentification EventDateTime="2013-10-17T15:12:04.287-06:00"
EventOutcomeIndicator="0"
EventActionCode="E">
<EventID csd-code="110114" codeSystemName="DCM" originalText="UserAuthenticated" />
<EventTypeCode csd-code="110122" codeSystemName="DCM" originalText="Login" />
</EventIdentification>
<ActiveParticipant UserID="fe80::5999:d1ef:63de:a8bb%11"
UserIsRequestor="true"
NetworkAccessPointTypeCode="1"
NetworkAccessPointID="125.20.175.12">
<RoleIDCode csd-code="110150" codeSystemName="DCM" originalText="Application" />
</ActiveParticipant>
<ActiveParticipant UserID="farley.granger@wb.com" UserIsRequestor="true"/>
<AuditSourceIdentification code="1"
AuditEnterpriseSiteID="End User" AuditSourceID="farley.granger@wb.com"/>
</AuditMessage>
"""
describe "Auditing", ->
beforeEach (done) -> Audit.remove {}, -> AuditMeta.remove {}, -> done()
describe ".processAudit", ->
validateSyslog = (syslog) ->
syslog.should.exist
syslog.msgID.should.be.equal 'IHE+RFC-3881'
syslog.pid.should.be.equal '9293'
syslog.appName.should.be.equal 'java'
syslog.host.should.be.equal 'Hanness-MBP.jembi.local'
syslog.time.should.exist
syslog.type.should.be.equal 'RFC5424'
syslog.severity.should.be.equal 'notice'
syslog.facility.should.be.equal 'sec'
syslog.severityID.should.be.equal 5
syslog.facilityID.should.be.equal 10
syslog.prival.should.be.equal 85
it "should parse audit message and persist it to the database", (done) ->
auditing.processAudit testAudit, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly testAudit
validateSyslog audits[0].syslog
audits[0].eventIdentification.should.exist
audits[0].eventIdentification.eventDateTime.should.exist
audits[0].eventIdentification.eventOutcomeIndicator.should.be.equal '0'
audits[0].eventIdentification.eventActionCode.should.be.equal 'E'
audits[0].eventIdentification.eventID.code.should.be.equal '110112'
audits[0].eventIdentification.eventID.displayName.should.be.equal 'Query'
audits[0].eventIdentification.eventID.codeSystemName.should.be.equal 'DCM'
audits[0].eventIdentification.eventTypeCode.code.should.be.equal 'ITI-9'
audits[0].eventIdentification.eventTypeCode.displayName.should.be.equal 'PIX Query'
audits[0].eventIdentification.eventTypeCode.codeSystemName.should.be.equal 'IHE Transactions'
audits[0].activeParticipant.length.should.be.exactly 2
audits[0].activeParticipant[0].userID.should.be.equal 'openhim-mediator-ohie-xds|openhim'
audits[0].activeParticipant[0].alternativeUserID.should.be.equal '9293'
audits[0].activeParticipant[0].userIsRequestor.should.be.equal 'true'
audits[0].activeParticipant[0].networkAccessPointID.should.be.equal '192.168.1.111'
audits[0].activeParticipant[0].networkAccessPointTypeCode.should.be.equal '2'
audits[0].activeParticipant[0].roleIDCode.code.should.be.equal '110153'
audits[0].activeParticipant[0].roleIDCode.displayName.should.be.equal 'Source'
audits[0].activeParticipant[0].roleIDCode.codeSystemName.should.be.equal 'DCM'
audits[0].activeParticipant[1].userID.should.be.equal 'pix|pix'
audits[0].activeParticipant[1].alternativeUserID.should.be.equal '2100'
audits[0].activeParticipant[1].userIsRequestor.should.be.equal 'false'
audits[0].activeParticipant[1].networkAccessPointID.should.be.equal 'localhost'
audits[0].activeParticipant[1].networkAccessPointTypeCode.should.be.equal '1'
audits[0].activeParticipant[1].roleIDCode.code.should.be.equal '110152'
audits[0].activeParticipant[1].roleIDCode.displayName.should.be.equal 'Destination'
audits[0].activeParticipant[1].roleIDCode.codeSystemName.should.be.equal 'DCM'
audits[0].auditSourceIdentification.should.exist
audits[0].auditSourceIdentification.auditSourceID.should.be.equal 'openhim'
audits[0].participantObjectIdentification.length.should.be.exactly 2
audits[0].participantObjectIdentification[0].participantObjectID.should.be.equal 'fc133984036647e^^^&1.3.6.1.4.1.21367.2005.13.20.3000&ISO'
audits[0].participantObjectIdentification[0].participantObjectTypeCode.should.be.equal '1'
audits[0].participantObjectIdentification[0].participantObjectTypeCodeRole.should.be.equal '1'
audits[0].participantObjectIdentification[0].participantObjectIDTypeCode.code.should.be.equal '2'
audits[0].participantObjectIdentification[0].participantObjectIDTypeCode.displayName.should.be.equal 'PatientNumber'
audits[0].participantObjectIdentification[0].participantObjectIDTypeCode.codeSystemName.should.be.equal 'RFC-3881'
audits[0].participantObjectIdentification[1].participantObjectID.should.be.equal 'c7bd7244-29bc-4ab5-80ee-74b56eed9db0'
audits[0].participantObjectIdentification[1].participantObjectTypeCode.should.be.equal '2'
audits[0].participantObjectIdentification[1].participantObjectTypeCodeRole.should.be.equal '24'
audits[0].participantObjectIdentification[1].participantObjectIDTypeCode.code.should.be.equal 'ITI-9'
audits[0].participantObjectIdentification[1].participantObjectIDTypeCode.displayName.should.be.equal 'PIX Query'
audits[0].participantObjectIdentification[1].participantObjectIDTypeCode.codeSystemName.should.be.equal 'IHE Transactions'
audits[0].participantObjectIdentification[1].participantObjectQuery.should.be.equal testAuditParticipantQuery
audits[0].participantObjectIdentification[1].participantObjectDetail.should.exist
audits[0].participantObjectIdentification[1].participantObjectDetail.type.should.be.equal 'MSH-10'
audits[0].participantObjectIdentification[1].participantObjectDetail.value.should.be.equal 'YmIwNzNiODUtNTdhOS00MGJhLTkyOTEtMTVkMjExOGQ0OGYz'
done()
it "should still persist to the database even if the audit includes a non-xml message", (done) ->
nonXmlAudit = "<85>1 2015-03-05T12:52:31.358+02:00 Hanness-MBP.jembi.local java 9293 IHE+RFC-3881 - this is a message?>"
auditing.processAudit nonXmlAudit, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly nonXmlAudit
validateSyslog audits[0].syslog
done()
it "should still persist to the database even if the audit includes an unexpected type of xml message", (done) ->
nonXmlAudit = "<85>1 2015-03-05T12:52:31.358+02:00 Hanness-MBP.jembi.local java 9293 IHE+RFC-3881 - <data>data</data>?>"
auditing.processAudit nonXmlAudit, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly nonXmlAudit
validateSyslog audits[0].syslog
done()
it "should reject bad messages", (done) ->
badAudit = "this message is a garbage message"
auditing.processAudit badAudit, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 0
done()
it "should populate audit meta collection with filter fields", (done) ->
auditing.processAudit testAudit, ->
AuditMeta.findOne {}, (err, auditMeta) ->
return done err if err
auditMeta.eventID.should.exist
auditMeta.eventID.length.should.be.exactly 1
auditMeta.eventID[0].code.should.be.equal '110112'
auditMeta.eventID[0].displayName.should.be.equal 'Query'
auditMeta.eventID[0].codeSystemName.should.be.equal 'DCM'
auditMeta.eventType.should.exist
auditMeta.eventType.length.should.be.exactly 1
auditMeta.eventType[0].code.should.be.equal 'ITI-9'
auditMeta.eventType[0].displayName.should.be.equal 'PIX Query'
auditMeta.eventType[0].codeSystemName.should.be.equal 'IHE Transactions'
auditMeta.activeParticipantRoleID.should.exist
auditMeta.activeParticipantRoleID.length.should.be.exactly 2
auditMeta.activeParticipantRoleID[0].code.should.be.equal '110153'
auditMeta.activeParticipantRoleID[0].displayName.should.be.equal 'Source'
auditMeta.activeParticipantRoleID[0].codeSystemName.should.be.equal 'DCM'
auditMeta.activeParticipantRoleID[1].code.should.be.equal '110152'
auditMeta.activeParticipantRoleID[1].displayName.should.be.equal 'Destination'
auditMeta.activeParticipantRoleID[1].codeSystemName.should.be.equal 'DCM'
auditMeta.participantObjectIDTypeCode.should.exist
auditMeta.participantObjectIDTypeCode.length.should.be.exactly 2
auditMeta.participantObjectIDTypeCode[0].code.should.be.equal '2'
auditMeta.participantObjectIDTypeCode[0].displayName.should.be.equal 'PatientNumber'
auditMeta.participantObjectIDTypeCode[0].codeSystemName.should.be.equal 'RFC-3881'
auditMeta.participantObjectIDTypeCode[1].code.should.be.equal 'ITI-9'
auditMeta.participantObjectIDTypeCode[1].displayName.should.be.equal 'PIX Query'
auditMeta.participantObjectIDTypeCode[1].codeSystemName.should.be.equal 'IHE Transactions'
auditMeta.auditSourceID.should.exist
auditMeta.auditSourceID.length.should.be.exactly 1
auditMeta.auditSourceID[0].should.be.equal 'openhim'
done()
it "should not duplicate filter fields in audit meta collection", (done) ->
auditing.processAudit testAudit, ->
auditing.processAudit testAudit, ->
AuditMeta.findOne {}, (err, auditMeta) ->
return done err if err
auditMeta.eventID.length.should.be.exactly 1
auditMeta.eventType.length.should.be.exactly 1
auditMeta.activeParticipantRoleID.length.should.be.exactly 2
auditMeta.participantObjectIDTypeCode.length.should.be.exactly 2
auditMeta.auditSourceID.length.should.be.exactly 1
done()
describe "IHE Samples", ->
validateIHEAudit = (type, audit) ->
audit.syslog.should.exist
audit.syslog.msgID.should.be.equal type
audit.syslog.pid.should.be.equal '521'
audit.syslog.appName.should.be.equal 'OHT'
audit.syslog.host.should.be.equal 'cabig-h1'
audit.syslog.time.should.exist
audit.syslog.type.should.be.equal 'RFC5424'
audit.syslog.severity.should.be.equal 'notice'
audit.syslog.facility.should.be.equal 'sec'
audit.syslog.severityID.should.be.equal 5
audit.syslog.facilityID.should.be.equal 10
audit.syslog.prival.should.be.equal 85
audit.eventIdentification.should.exist
audit.eventIdentification.eventDateTime.should.exist
audit.eventIdentification.eventOutcomeIndicator.should.be.equal '0'
audit.eventIdentification.eventActionCode.should.be.equal 'E'
audit.eventIdentification.eventID.code.should.be.equal '110114'
audit.eventIdentification.eventID.displayName.should.be.equal 'UserAuthenticated'
audit.eventIdentification.eventID.codeSystemName.should.be.equal 'DCM'
audit.eventIdentification.eventTypeCode.code.should.be.equal '110122'
audit.eventIdentification.eventTypeCode.displayName.should.be.equal 'Login'
audit.eventIdentification.eventTypeCode.codeSystemName.should.be.equal 'DCM'
audit.activeParticipant.length.should.be.exactly 2
audit.activeParticipant[0].userID.should.be.equal 'fe80::5999:d1ef:63de:a8bb%11'
audit.activeParticipant[0].userIsRequestor.should.be.equal 'true'
audit.activeParticipant[0].networkAccessPointID.should.be.equal '125.20.175.12'
audit.activeParticipant[0].networkAccessPointTypeCode.should.be.equal '1'
audit.activeParticipant[0].roleIDCode.code.should.be.equal '110150'
audit.activeParticipant[0].roleIDCode.displayName.should.be.equal 'Application'
audit.activeParticipant[0].roleIDCode.codeSystemName.should.be.equal 'DCM'
audit.activeParticipant[1].userID.should.be.equal 'farley.granger@wb.com'
audit.activeParticipant[1].userIsRequestor.should.be.equal 'true'
audit.auditSourceIdentification.should.exist
audit.auditSourceIdentification.auditSourceID.should.be.equal 'farley.granger@wb.com'
audit.auditSourceIdentification.auditEnterpriseSiteID.should.be.equal 'End User'
it "should parse IHE sample RFC3881 audit message and persist it to the database", (done) ->
auditing.processAudit testAuditIHE_RFC3881, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly testAuditIHE_RFC3881
validateIHEAudit 'IHE+RFC-3881', audits[0]
done()
it "should parse IHE sample DICOM audit message and persist it to the database", (done) ->
auditing.processAudit testAuditIHE_DICOM, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly testAuditIHE_DICOM
validateIHEAudit 'IHE+DICOM', audits[0]
done()
describe '.sendAuditEvent', ->
testString = 'hello - this is a test'
_restore = null
before (done) ->
_restore = JSON.stringify config.auditing.auditEvents
ca = [fs.readFileSync 'test/resources/server-tls/cert.pem']
testUtils.setupTestKeystore null, null, ca, -> done()
after (done) ->
config.auditing.auditEvents = JSON.parse _restore
testUtils.cleanupTestKeystore -> done()
it 'should process audit internally', (done) ->
config.auditing.auditEvents.interface = 'internal'
auditing.sendAuditEvent testAudit, ->
Audit.find {}, (err, audits) ->
return done err if err
audits.length.should.be.exactly 1
audits[0].rawMessage.should.be.exactly testAudit
done()
it 'should send an audit event via UDP', (done) ->
server = dgram.createSocket 'udp4'
server.on 'listening', ->
config.auditing.auditEvents.interface = 'udp'
config.auditing.auditEvents.port = 6050
auditing.sendAuditEvent testString, ->
server.on 'message', (msg, rinfo) ->
"#{msg}".should.be.exactly testString
server.close()
done()
server.on 'error', done
server.bind port: 6050
it 'should send an audit event via TLS', (done) ->
called = {}
validate = (data) ->
"#{data}".should.be.exactly "#{testString.length} #{testString}"
called['called-tls'] = true
afterSetup = (server) ->
auditing.sendAuditEvent testString, ->
called.should.have.property 'called-tls'
server.close()
done()
config.auditing.auditEvents.interface = 'tls'
config.auditing.auditEvents.port = 6051
testUtils.createMockTLSServerWithMutualAuth 6051, testString, 'ok', 'not-ok', afterSetup, validate
it 'should send an audit event via TCP', (done) ->
called = {}
validate = (data) ->
"#{data}".should.be.exactly "#{testString.length} #{testString}"
called['called-tcp'] = true
afterSetup = (server) ->
auditing.sendAuditEvent testString, ->
called.should.have.property 'called-tcp'
server.close()
done()
config.auditing.auditEvents.interface = 'tcp'
config.auditing.auditEvents.port = 6052
testUtils.createMockTCPServer 6052, testString, 'ok', 'not-ok', afterSetup, validate