All files / src/middleware requestMatching.coffee

42.35% Statements 36/85
6.25% Branches 1/16
0% Functions 0/16
41.67% Lines 35/84
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 1382x 2x 2x 2x 2x 2x 2x 2x 2x   2x 2x 2x 2x 2x   2x 2x   2x                             2x       2x         2x             2x                       2x             2x       2x                             2x           2x       2x       2x                       2x                                 2x 2x 2x 2x 2x 2x  
Q = require "q"
xpath = require "xpath"
dom = require("xmldom").DOMParser
logger = require "winston"
config = require '../config/config'
utils = require '../utils'
auditing = require '../auditing'
Channels = require('../model/channels')
Channel = Channels.Channel
 
statsdServer = config.get 'statsd'
application = config.get 'application'
himSourceID = config.get('auditing').auditEvents.auditSourceID
SDC = require 'statsd-client'
os = require 'os'
 
domain = "#{os.hostname()}.#{application.name}.appMetrics"
sdc = new SDC statsdServer
 
matchContent = (channel, ctx) ->
  if channel.matchContentRegex
    return matchRegex channel.matchContentRegex, ctx.body
  else if channel.matchContentXpath and channel.matchContentValue
    return matchXpath channel.matchContentXpath, channel.matchContentValue, ctx.body
  else if channel.matchContentJson and channel.matchContentValue
    return matchJsonPath channel.matchContentJson, channel.matchContentValue, ctx.body
  else if channel.matchContentXpath or channel.matchContentJson
    # if only the match expression is given, deny access
    # this is an invalid channel
    logger.error "Channel with name '#{channel.name}' is invalid as it has a content match expression but no value to match"
    return false
  else
    return true
 
matchRegex = (regexPat, body) ->
  regex = new RegExp regexPat
  return regex.test body.toString()
 
matchXpath = (xpathStr, val, xml) ->
  doc = new dom().parseFromString(xml.toString())
  xpathVal = xpath.select(xpathStr, doc).toString()
  return val == xpathVal
 
matchJsonPath = (jsonPath, val, json) ->
  jsonObj = JSON.parse json.toString()
  jsonVal = getJSONValByString jsonObj, jsonPath
  return val == jsonVal.toString()
 
# taken from http://stackoverflow.com/a/6491621/588776
# readbility improved from the stackoverflow answer
getJSONValByString = (jsonObj, jsonPath) ->
  jsonPath = jsonPath.replace(/\[(\w+)\]/g, '.$1')  # convert indexes to properties
  jsonPath = jsonPath.replace(/^\./, '')            # strip a leading dot
  parts = jsonPath.split('.')
  while parts.length
    part = parts.shift()
    if part of jsonObj
      jsonObj = jsonObj[part]
    else
      return
  return jsonObj
 
extractContentType = (ctHeader) ->
  index = ctHeader.indexOf ';'
  if index isnt -1
    return ctHeader.substring(0, index).trim()
  else
    return ctHeader.trim()
 
matchUrlPattern = (channel, ctx) ->
  pat = new RegExp channel.urlPattern
  return pat.test ctx.request.path
 
matchContentTypes = (channel, ctx) ->
  if channel.matchContentTypes?.length > 0
    if ctx.request.header and ctx.request.header['content-type']
      ct = extractContentType ctx.request.header['content-type']
      if ct in channel.matchContentTypes
        return true
      else
        # deny access to channel if the content type doesnt match
        return false
    else
      # deny access to channel if the content type isnt set
      return false
  else
    return true # don't match on content type if this channel doesn't require it
 
matchFunctions = [
  matchUrlPattern,
  matchContent,
  matchContentTypes
]
 
matchChannel = (channel, ctx) ->
  matchFunctions.every (matchFunc) ->
    return matchFunc channel, ctx
 
findMatchingChannel = (channels, ctx) ->
  return channels.find (channel) ->
    return matchChannel channel, ctx
 
matchRequest = (ctx, done) ->
  utils.getAllChannelsInPriorityOrder (err, channels) ->
    if err
      ctx.response.status = 500
      logger.error 'Could not fetch OpenHIM channels', err
      return done()
 
    channels = channels.filter Channels.isChannelEnabled
 
    match = findMatchingChannel channels, ctx
    done null, match
 
exports.koaMiddleware = (next) ->
  startTime = new Date() if statsdServer.enabled
  matchReq = Q.denodeify matchRequest
  match = yield matchReq this
 
  if match?
    logger.info "The channel that matches the request #{this.request.path} is: #{match.name}"
    this.matchingChannel = match
  else
    logger.info "No channel matched the request #{this.request.path}"
 
  sdc.timing "#{domain}.authorisationMiddleware", startTime if statsdServer.enabled
  yield next
 
# export private functions for unit testing
# note: you cant spy on these method because of this :(
if process.env.NODE_ENV == "test"
  exports.matchContent = matchContent
  exports.matchRegex = matchRegex
  exports.matchXpath = matchXpath
  exports.matchJsonPath = matchJsonPath
  exports.extractContentType = extractContentType
  exports.matchRequest = matchRequest