# 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}'"