All files / src auditing.coffee

26.58% Statements 21/79
2.7% Branches 1/37
0% Functions 0/28
27.78% Lines 20/72
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 1601x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x                                                                                           1x   1x                                                               1x                                           1x           1x                         1x                   1x                            
logger = require 'winston'
syslogParser = require('glossy').Parse
parseString = require('xml2js').parseString
firstCharLowerCase = require('xml2js').processors.firstCharLowerCase
Audit = require('./model/audits').Audit
AuditMeta = require('./model/audits').AuditMeta
tlsAuthentication = require "./middleware/tlsAuthentication"
dgram = require 'dgram'
tls = require 'tls'
net = require 'net'
config = require "./config/config"
config.auditing = config.get('auditing')
 
 
parseAuditRecordFromXML = (xml, callback) ->
  # DICOM mappers
  csdCodeToCode = (name) -> if name is 'csd-code' then 'code' else name
  originalTextToDisplayName = (name) -> if name is 'originalText' then 'displayName' else name
 
  options =
    mergeAttrs: true,
    explicitArray: false
    tagNameProcessors: [firstCharLowerCase]
    attrNameProcessors: [firstCharLowerCase, csdCodeToCode, originalTextToDisplayName]
 
  parseString xml, options, (err, result) ->
    return callback err if err
 
    if not result?.auditMessage
      return callback new Error 'Document is not a valid AuditMessage'
 
    audit = {}
 
    if result.auditMessage.eventIdentification
      audit.eventIdentification = result.auditMessage.eventIdentification
 
    audit.activeParticipant = []
    if result.auditMessage.activeParticipant
      # xml2js will only use an array if multiple items exist (explicitArray: false), else it's an object
      if result.auditMessage.activeParticipant instanceof Array
        for ap in result.auditMessage.activeParticipant
          audit.activeParticipant.push ap
      else
        audit.activeParticipant.push result.auditMessage.activeParticipant
 
    if result.auditMessage.auditSourceIdentification
      audit.auditSourceIdentification = result.auditMessage.auditSourceIdentification
 
    audit.participantObjectIdentification = []
    if result.auditMessage.participantObjectIdentification
      # xml2js will only use an array if multiple items exist (explicitArray: false), else it's an object
      if result.auditMessage.participantObjectIdentification instanceof Array
        for poi in result.auditMessage.participantObjectIdentification
          audit.participantObjectIdentification.push poi
      else
        audit.participantObjectIdentification.push result.auditMessage.participantObjectIdentification
 
    callback null, audit
 
 
codeInArray = (code, arr) -> (code in arr.map (a) -> a.code)
 
exports.processAuditMeta = processAuditMeta = (audit, callback) ->
  AuditMeta.findOne {}, (err, auditMeta) ->
    if err
      logger.error err
      return callback()
 
    if not auditMeta then auditMeta = new AuditMeta()
 
    if audit.eventIdentification?.eventTypeCode?.code and not codeInArray audit.eventIdentification.eventTypeCode.code, auditMeta.eventType
      auditMeta.eventType.push audit.eventIdentification.eventTypeCode
 
    if audit.eventIdentification?.eventID?.code and not codeInArray audit.eventIdentification.eventID.code, auditMeta.eventID
      auditMeta.eventID.push audit.eventIdentification.eventID
 
    if audit.activeParticipant
      for activeParticipant in audit.activeParticipant
        if activeParticipant.roleIDCode?.code and not codeInArray activeParticipant.roleIDCode.code, auditMeta.activeParticipantRoleID
          auditMeta.activeParticipantRoleID.push activeParticipant.roleIDCode
 
    if audit.participantObjectIdentification
      for participantObject in audit.participantObjectIdentification
        if participantObject.participantObjectIDTypeCode?.code and not codeInArray participantObject.participantObjectIDTypeCode.code, auditMeta.participantObjectIDTypeCode
          auditMeta.participantObjectIDTypeCode.push participantObject.participantObjectIDTypeCode
 
    if audit.auditSourceIdentification?.auditSourceID and audit.auditSourceIdentification.auditSourceID not in auditMeta.auditSourceID
      auditMeta.auditSourceID.push audit.auditSourceIdentification.auditSourceID
 
    auditMeta.save (err) ->
      if err then logger.error err
      callback()
 
 
exports.processAudit = processAudit = (msg, callback=(->)) ->
  parsedMsg = syslogParser.parse(msg)
 
  if not parsedMsg or not parsedMsg.message
    logger.info 'Invalid message received'
    return callback()
 
  parseAuditRecordFromXML parsedMsg.message, (xmlErr, result) ->
    audit = new Audit result
 
    audit.rawMessage = msg
    audit.syslog = parsedMsg
    delete audit.syslog.originalMessage
    delete audit.syslog.message
 
    audit.save (saveErr) ->
      if saveErr then logger.error "An error occurred while processing the audit entry: #{saveErr}"
      if xmlErr then logger.info "Failed to parse message as an AuditMessage XML document: #{xmlErr}"
 
      processAuditMeta audit, callback
 
 
sendUDPAudit = (msg, callback) ->
  client = dgram.createSocket('udp4')
  client.send msg, 0, msg.length, config.auditing.auditEvents.port, config.auditing.auditEvents.host, (err) ->
    client.close()
    callback err
 
sendTLSAudit = (msg, callback) ->
  tlsAuthentication.getServerOptions true, (err, options) ->
    return callback err if err
 
    client = tls.connect config.auditing.auditEvents.port, config.auditing.auditEvents.host, options, ->
      if not client.authorized then return callback client.authorizationError
 
      client.write "#{msg.length} #{msg}"
      client.end()
 
    client.on 'error', (err) -> logger.error err
    client.on 'close', -> callback()
 
sendTCPAudit = (msg, callback) ->
  client = net.connect config.auditing.auditEvents.port, config.auditing.auditEvents.host, ->
    client.write "#{msg.length} #{msg}"
    client.end()
 
  client.on 'error', (err) -> logger.error
  client.on 'close', -> callback()
 
 
# Send an audit event
exports.sendAuditEvent = (msg, callback=(->)) ->
  done = (err) ->
    if err then logger.error err
    callback()
 
  if not config.auditing?.auditEvents?
    return done new Error 'Unable to record audit event: Missing config.auditing.auditEvents'
 
  switch config.auditing.auditEvents.interface
    when 'internal' then processAudit msg, done
    when 'udp' then sendUDPAudit msg, done
    when 'tls' then sendTLSAudit msg, done
    when 'tcp' then sendTCPAudit msg, done
    else done new Error "Invalid audit event interface '#{config.auditing.auditEvents.interface}'"