UNPKG

openhim-core

Version:

The OpenHIM core application that provides logging and routing of http requests

514 lines (406 loc) 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllMediators = getAllMediators; exports.getMediator = getMediator; exports.addMediator = addMediator; exports.removeMediator = removeMediator; exports.heartbeat = heartbeat; exports.setConfig = setConfig; exports.loadDefaultChannels = loadDefaultChannels; var _winston = _interopRequireDefault(require("winston")); var _semver = _interopRequireDefault(require("semver")); var _atnaAudit = _interopRequireDefault(require("atna-audit")); var _channels = require("../model/channels"); var _mediators = require("../model/mediators"); var authorisation = _interopRequireWildcard(require("./authorisation")); var utils = _interopRequireWildcard(require("../utils")); var auditing = _interopRequireWildcard(require("../auditing")); function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const mask = '**********'; function maskPasswords(defs, config) { if (!config) { return; } return defs.forEach(d => { if (d.type === 'password' && config[d.param]) { if (d.array) { config[d.param] = config[d.param].map(() => mask); } else { config[d.param] = mask; } } if (d.type === 'struct' && config[d.param]) { return maskPasswords(d.template, config[d.param]); } }); } function restoreMaskedPasswords(defs, maskedConfig, config) { if (!maskedConfig || !config) { return; } return defs.forEach(d => { if (d.type === 'password' && maskedConfig[d.param] && config[d.param]) { if (d.array) { maskedConfig[d.param].forEach((p, i) => { if (p === mask) { maskedConfig[d.param][i] = config[d.param][i]; } }); } else if (maskedConfig[d.param] === mask) { maskedConfig[d.param] = config[d.param]; } } if (d.type === 'struct' && maskedConfig[d.param] && config[d.param]) { return restoreMaskedPasswords(d.template, maskedConfig[d.param], config[d.param]); } }); } async function getAllMediators(ctx) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getAllMediators denied.`, 'info'); return; } try { const mediator = await _mediators.MediatorModelAPI.find().exec(); maskPasswords(mediator.configDefs, mediator.config); ctx.body = mediator; } catch (err) { utils.logAndSetResponse(ctx, 500, `Could not fetch mediators via the API: ${err}`, 'error'); } } async function getMediator(ctx, mediatorURN) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getMediator denied.`, 'info'); return; } const urn = unescape(mediatorURN); try { const result = await _mediators.MediatorModelAPI.findOne({ urn }).exec(); if (result === null) { ctx.status = 404; } else { maskPasswords(result.configDefs, result.config); ctx.body = result; } } catch (err) { utils.logAndSetResponse(ctx, 500, `Could not fetch mediator using UUID ${urn} via the API: ${err}`, 'error'); } } function constructError(message, name) { const err = new Error(message); err.name = name; return err; } function validateConfigDef(def) { if (def.type === 'struct' && !def.template) { throw constructError(`Must specify a template for struct param '${def.param}'`, 'ValidationError'); } else if (def.type === 'struct') { for (const templateItem of Array.from(def.template)) { if (!templateItem.param) { throw constructError(`Must specify field 'param' in template definition for param '${def.param}'`, 'ValidationError'); } if (!templateItem.type) { throw constructError(`Must specify field 'type' in template definition for param '${def.param}'`, 'ValidationError'); } if (templateItem.type === 'struct') { throw constructError(`May not recursively specify 'struct' in template definitions (param '${def.param}')`, 'ValidationError'); } } } else if (def.type === 'option') { if (!utils.typeIsArray(def.values)) { throw constructError(`Expected field 'values' to be an array (option param '${def.param}')`, 'ValidationError'); } if (def.values == null || def.values.length === 0) { throw constructError(`Must specify a values array for option param '${def.param}'`, 'ValidationError'); } } } // validations additional to the mongoose schema validation const validateConfigDefs = configDefs => Array.from(configDefs).map(def => validateConfigDef(def)); async function addMediator(ctx) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to addMediator denied.`, 'info'); return; } try { let mediatorHost = 'unknown'; const mediator = ctx.request.body; if (mediator != null && mediator.endpoints != null && mediator.endpoints.length > 0 && mediator.endpoints[0].host != null) { mediatorHost = mediator.endpoints[0].host; } // audit mediator start let audit = _atnaAudit.default.construct.appActivityAudit(true, mediator.name, mediatorHost, 'system'); audit = _atnaAudit.default.construct.wrapInSyslog(audit); auditing.sendAuditEvent(audit, () => _winston.default.info(`Processed internal mediator start audit for: ${mediator.name} - ${mediator.urn}`)); if (!mediator.urn) { throw constructError('URN is required', 'ValidationError'); } if (!mediator.version || !_semver.default.valid(mediator.version)) { throw constructError('Version is required. Must be in SemVer form x.y.z', 'ValidationError'); } if (mediator.configDefs) { validateConfigDefs(mediator.configDefs); if (mediator.config != null) { validateConfig(mediator.configDefs, mediator.config); } } const existing = await _mediators.MediatorModelAPI.findOne({ urn: mediator.urn }).exec(); if (existing != null) { if (_semver.default.gt(mediator.version, existing.version)) { // update the mediator if (mediator.config != null && existing.config != null) { // if some config already exists, add only config that didn't exist previously for (const param in mediator.config) { if (existing.config[param] != null) { mediator.config[param] = existing.config[param]; } } } await _mediators.MediatorModelAPI.findByIdAndUpdate(existing._id, mediator).exec(); } } else { // this is a new mediator validate and save it if (!mediator.endpoints || mediator.endpoints.length < 1) { throw constructError('At least 1 endpoint is required', 'ValidationError'); } await new _mediators.MediatorModelAPI(mediator).save(); } ctx.status = 201; _winston.default.info(`User ${ctx.authenticated.email} created mediator with urn ${mediator.urn}`); } catch (err) { if (err.name === 'ValidationError') { utils.logAndSetResponse(ctx, 400, `Could not add Mediator via the API: ${err}`, 'error'); } else { utils.logAndSetResponse(ctx, 500, `Could not add Mediator via the API: ${err}`, 'error'); } } } async function removeMediator(ctx, urn) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeMediator denied.`, 'info'); return; } urn = unescape(urn); try { await _mediators.MediatorModelAPI.findOneAndRemove({ urn }).exec(); ctx.body = `Mediator with urn ${urn} has been successfully removed by ${ctx.authenticated.email}`; return _winston.default.info(`Mediator with urn ${urn} has been successfully removed by ${ctx.authenticated.email}`); } catch (err) { return utils.logAndSetResponse(ctx, 500, `Could not remove Mediator by urn ${urn} via the API: ${err}`, 'error'); } } async function heartbeat(ctx, urn) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeMediator denied.`, 'info'); return; } urn = unescape(urn); try { const mediator = await _mediators.MediatorModelAPI.findOne({ urn }).exec(); if (mediator == null) { ctx.status = 404; return; } const heartbeat = ctx.request.body; if ((heartbeat != null ? heartbeat.uptime : undefined) == null) { ctx.status = 400; return; } if (mediator._configModifiedTS > mediator._lastHeartbeat || (heartbeat != null ? heartbeat.config : undefined) === true) { // Return config if it has changed since last heartbeat ctx.body = mediator.config; } else { ctx.body = ''; } // set internal properties if (heartbeat != null) { const update = { _lastHeartbeat: new Date(), _uptime: heartbeat.uptime }; await _mediators.MediatorModelAPI.findByIdAndUpdate(mediator._id, update).exec(); } ctx.status = 200; } catch (err) { utils.logAndSetResponse(ctx, 500, `Could not process mediator heartbeat (urn: ${urn}): ${err}`, 'error'); } } function validateConfigField(param, def, field) { switch (def.type) { case 'string': if (typeof field !== 'string') { throw constructError(`Expected config param ${param} to be a string.`, 'ValidationError'); } break; case 'bigstring': if (typeof field !== 'string') { throw constructError(`Expected config param ${param} to be a large string.`, 'ValidationError'); } break; case 'number': if (typeof field !== 'number') { throw constructError(`Expected config param ${param} to be a number.`, 'ValidationError'); } break; case 'bool': if (typeof field !== 'boolean') { throw constructError(`Expected config param ${param} to be a boolean.`, 'ValidationError'); } break; case 'option': if (def.values.indexOf(field) === -1) { throw constructError(`Expected config param ${param} to be one of ${def.values}`, 'ValidationError'); } break; case 'map': if (typeof field !== 'object') { throw constructError(`Expected config param ${param} to be an object.`, 'ValidationError'); } for (const k in field) { const v = field[k]; if (typeof v !== 'string') { throw constructError(`Expected config param ${param} to only contain string values.`, 'ValidationError'); } } break; case 'struct': if (typeof field !== 'object') { throw constructError(`Expected config param ${param} to be an object.`, 'ValidationError'); } const templateFields = def.template.map(tp => tp.param); for (const paramField in field) { if (!Array.from(templateFields).includes(paramField)) { throw constructError(`Field ${paramField} is not defined in template definition for config param ${param}.`, 'ValidationError'); } } break; case 'password': if (typeof field !== 'string') { throw constructError(`Expected config param ${param} to be a string representing a password.`, 'ValidationError'); } break; default: _winston.default.debug(`Unhandled validation case ${def.type}`, 'ValidationError'); break; } } function validateConfig(configDef, config) { // reduce to a single true or false value, start assuming valid Object.keys(config).every(param => { // find the matching def if there is one const matchingDefs = configDef.filter(def => def.param === param); // fail if there isn't a matching def if (matchingDefs.length === 0) { throw constructError(`No config definition found for parameter ${param}`, 'ValidationError'); } // validate the param against the defs return matchingDefs.map(def => { if (def.array) { if (!utils.typeIsArray(config[param])) { throw constructError(`Expected config param ${param} to be an array of type ${def.type}`, 'ValidationError'); } return Array.from(config[param]).map((field, i) => validateConfigField(`${param}[${i}]`, def, field)); } else { return validateConfigField(param, def, config[param]); } }); }); } if (process.env.NODE_ENV === 'test') { exports.validateConfig = validateConfig; } async function setConfig(ctx, urn) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeMediator denied.`, 'info'); return; } urn = unescape(urn); const config = ctx.request.body; try { const mediator = await _mediators.MediatorModelAPI.findOne({ urn }).exec(); if (mediator == null) { ctx.status = 404; ctx.body = 'No mediator found for this urn.'; return; } try { restoreMaskedPasswords(mediator.configDefs, config, mediator.config); validateConfig(mediator.configDefs, config); } catch (error) { ctx.status = 400; ctx.body = error.message; return; } await _mediators.MediatorModelAPI.findOneAndUpdate({ urn }, { config: ctx.request.body, _configModifiedTS: new Date() }).exec(); ctx.status = 200; } catch (error) { utils.logAndSetResponse(ctx, 500, `Could not set mediator config (urn: ${urn}): ${error}`, 'error'); } } function saveDefaultChannelConfig(channels, authenticated) { const promises = []; for (const channel of Array.from(channels)) { delete channel._id; for (const route of Array.from(channel.routes)) { delete route._id; } channel.updatedBy = utils.selectAuditFields(authenticated); promises.push(new _channels.ChannelModelAPI(channel).save()); } return promises; } async function loadDefaultChannels(ctx, urn) { // Must be admin if (!authorisation.inGroup('admin', ctx.authenticated)) { utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to removeMediator denied.`, 'info'); return; } urn = unescape(urn); const channels = ctx.request.body; try { const mediator = await _mediators.MediatorModelAPI.findOne({ urn }).lean().exec(); if (mediator == null) { ctx.status = 404; ctx.body = 'No mediator found for this urn.'; return; } if (channels == null || channels.length === 0) { await Promise.all(saveDefaultChannelConfig(mediator.defaultChannelConfig, ctx.authenticated)); } else { const filteredChannelConfig = mediator.defaultChannelConfig.filter(channel => Array.from(channels).includes(channel.name)); if (filteredChannelConfig.length < channels.length) { utils.logAndSetResponse(ctx, 400, `Could not load mediator default channel config, one or more channels in the request body not found in the mediator config (urn: ${urn})`, 'error'); return; } else { await Promise.all(saveDefaultChannelConfig(filteredChannelConfig, ctx.authenticated)); } } ctx.status = 201; } catch (err) { _winston.default.debug(err.stack); utils.logAndSetResponse(ctx, 500, `Could not load mediator default channel config (urn: ${urn}): ${err}`, 'error'); } } //# sourceMappingURL=mediators.js.map