openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
184 lines (146 loc) • 5.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.koaMiddleware = koaMiddleware;
var _xpath = require('xpath');
var _xpath2 = _interopRequireDefault(_xpath);
var _xmldom = require('xmldom');
var _winston = require('winston');
var _winston2 = _interopRequireDefault(_winston);
var _statsdClient = require('statsd-client');
var _statsdClient2 = _interopRequireDefault(_statsdClient);
var _os = require('os');
var _os2 = _interopRequireDefault(_os);
var _config = require('../config');
var _utils = require('../utils');
var utils = _interopRequireWildcard(_utils);
var _channels = require('../model/channels');
var Channels = _interopRequireWildcard(_channels);
var _util = require('util');
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const statsdServer = _config.config.get('statsd');
const application = _config.config.get('application');
const domain = `${_os2.default.hostname()}.${application.name}.appMetrics`;
const sdc = new _statsdClient2.default(statsdServer);
function matchContent(channel, ctx) {
if (channel.matchContentRegex) {
return matchRegex(channel.matchContentRegex, ctx.body);
} else if (channel.matchContentXpath && channel.matchContentValue) {
return matchXpath(channel.matchContentXpath, channel.matchContentValue, ctx.body);
} else if (channel.matchContentJson && channel.matchContentValue) {
return matchJsonPath(channel.matchContentJson, channel.matchContentValue, ctx.body);
} else if (channel.matchContentXpath || channel.matchContentJson) {
// if only the match expression is given, deny access
// this is an invalid channel
_winston2.default.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;
}
}
function matchRegex(regexPat, body) {
const regex = new RegExp(regexPat);
return regex.test(body.toString());
}
function matchXpath(xpathStr, val, xml) {
const doc = new _xmldom.DOMParser().parseFromString(xml.toString());
const xpathVal = _xpath2.default.select(xpathStr, doc).toString();
return val === xpathVal;
}
function matchJsonPath(jsonPath, val, json) {
const jsonObj = JSON.parse(json.toString());
const jsonVal = getJSONValByString(jsonObj, jsonPath);
return val === jsonVal.toString();
}
// taken from http://stackoverflow.com/a/6491621/588776
// readbility improved from the stackoverflow answer
function getJSONValByString(jsonObj, jsonPath) {
jsonPath = jsonPath.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
jsonPath = jsonPath.replace(/^\./, ''); // strip a leading dot
const parts = jsonPath.split('.');
while (parts.length) {
const part = parts.shift();
if (part in jsonObj) {
jsonObj = jsonObj[part];
} else {
return;
}
}
return jsonObj;
}
function extractContentType(ctHeader) {
const index = ctHeader.indexOf(';');
if (index !== -1) {
return ctHeader.substring(0, index).trim();
} else {
return ctHeader.trim();
}
}
function matchUrlPattern(channel, ctx) {
const pat = new RegExp(channel.urlPattern);
return pat.test(ctx.request.path);
}
function matchContentTypes(channel, ctx) {
if ((channel.matchContentTypes != null ? channel.matchContentTypes.length : undefined) > 0) {
if (ctx.request.header && ctx.request.header['content-type']) {
const ct = extractContentType(ctx.request.header['content-type']);
if (Array.from(channel.matchContentTypes).includes(ct)) {
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
}
}
// Needs to be mutable for testing
// eslint-disable-next-line
let matchFunctions = [matchUrlPattern, matchContent, matchContentTypes];
const matchChannel = (channel, ctx) => matchFunctions.every(matchFunc => matchFunc(channel, ctx));
const findMatchingChannel = (channels, ctx) => channels.find(channel => matchChannel(channel, ctx));
const matchRequest = (ctx, done) => utils.getAllChannelsInPriorityOrder((err, channels) => {
if (err) {
ctx.response.status = 500;
_winston2.default.error('Could not fetch OpenHIM channels', err);
return done();
}
channels = channels.filter(Channels.isChannelEnabled);
const match = findMatchingChannel(channels, ctx);
return done(null, match);
});
async function koaMiddleware(ctx, next) {
let startTime;
if (statsdServer.enabled) {
startTime = new Date();
}
const matchReq = (0, _util.promisify)(matchRequest);
const match = await matchReq(ctx);
if (match != null) {
_winston2.default.info(`The channel that matches the request ${ctx.request.path} is: ${match.name}`);
ctx.matchingChannel = match;
} else {
_winston2.default.info(`No channel matched the request ${ctx.request.path}`);
}
if (statsdServer.enabled) {
sdc.timing(`${domain}.authorisationMiddleware`, startTime);
}
await 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;
}
//# sourceMappingURL=requestMatching.js.map