UNPKG

openhim-core

Version:

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

355 lines (284 loc) 12.8 kB
Channel = require('../model/channels').Channel Mediator = require('../model/mediators').Mediator Q = require 'q' logger = require 'winston' authorisation = require './authorisation' semver = require 'semver' atna = require 'atna-audit' utils = require "../utils" auditing = require '../auditing' mask = '**********' maskPasswords = (defs, config) -> if not config return defs.forEach (d) -> if d.type is 'password' and config[d.param] if d.array config[d.param] = config[d.param].map -> mask else config[d.param] = mask if d.type is 'struct' and config[d.param] maskPasswords d.template, config[d.param] restoreMaskedPasswords = (defs, maskedConfig, config) -> if not maskedConfig or not config return defs.forEach (d) -> if d.type is 'password' and maskedConfig[d.param] and config[d.param] if d.array maskedConfig[d.param].forEach (p, i) -> if p is mask maskedConfig[d.param][i] = config[d.param][i] else if maskedConfig[d.param] is mask maskedConfig[d.param] = config[d.param] if d.type is 'struct' and maskedConfig[d.param] and config[d.param] restoreMaskedPasswords d.template, maskedConfig[d.param], config[d.param] exports.getAllMediators = -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getAllMediators denied.", 'info' return try m = yield Mediator.find().exec() maskPasswords m.configDefs, m.config this.body = m catch err logAndSetResponse this, 500, "Could not fetch mediators via the API: #{err}", 'error' exports.getMediator = (mediatorURN) -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to getMediator denied.", 'info' return urn = unescape mediatorURN try result = yield Mediator.findOne({ "urn": urn }).exec() if result == null this.status = 404 else maskPasswords result.configDefs, result.config this.body = result catch err logAndSetResponse this, 500, "Could not fetch mediator using UUID #{urn} via the API: #{err}", 'error' constructError = (message, name) -> err = new Error message err.name = name return err validateConfigDef = (def) -> if def.type is 'struct' and not def.template throw constructError "Must specify a template for struct param '#{def.param}'", 'ValidationError' else if def.type is 'struct' for templateItem in def.template if not templateItem.param throw constructError "Must specify field 'param' in template definition for param '#{def.param}'", 'ValidationError' if not templateItem.type throw constructError "Must specify field 'type' in template definition for param '#{def.param}'", 'ValidationError' if templateItem.type is 'struct' throw constructError "May not recursively specify 'struct' in template definitions (param '#{def.param}')", 'ValidationError' validateConfigDef templateItem else if def.type is 'option' if not utils.typeIsArray def.values throw constructError "Expected field 'values' to be an array (option param '#{def.param}')", 'ValidationError' if not def.values? or def.values.length is 0 throw constructError "Must specify a values array for option param '#{def.param}'", 'ValidationError' # validations additional to the mongoose schema validation validateConfigDefs = (configDefs) -> validateConfigDef def for def in configDefs exports.addMediator = -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to addMediator denied.", 'info' return try mediator = this.request.body if mediator?.endpoints?[0]?.host? mediatorHost = mediator.endpoints[0].host else mediatorHost = 'unknown' # audit mediator start audit = atna.appActivityAudit true, mediator.name, mediatorHost, 'system' audit = atna.wrapInSyslog audit auditing.sendAuditEvent audit, -> logger.info "Processed internal mediator start audit for: #{mediator.name} - #{mediator.urn}" if not mediator.urn throw constructError 'URN is required', 'ValidationError' if not mediator.version or not semver.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? validateConfig mediator.configDefs, mediator.config existing = yield Mediator.findOne({urn: mediator.urn}).exec() if existing? if semver.gt(mediator.version, existing.version) # update the mediator if mediator.config? and existing.config? # if some config already exists, add only config that didn't exist previously for param, val of mediator.config if existing.config[param]? mediator.config[param] = existing.config[param] yield Mediator.findByIdAndUpdate(existing._id, mediator).exec() else # this is a new mediator validate and save it if not mediator.endpoints or mediator.endpoints.length < 1 throw constructError 'At least 1 endpoint is required', 'ValidationError' yield Q.ninvoke(new Mediator(mediator), 'save') this.status = 201 logger.info "User #{this.authenticated.email} created mediator with urn #{mediator.urn}" catch err if err.name is 'ValidationError' utils.logAndSetResponse this, 400, "Could not add Mediator via the API: #{err}", 'error' else utils.logAndSetResponse this, 500, "Could not add Mediator via the API: #{err}", 'error' exports.removeMediator = (urn) -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeMediator denied.", 'info' return urn = unescape urn try yield Mediator.findOneAndRemove({ urn: urn }).exec() this.body = "Mediator with urn #{urn} has been successfully removed by #{this.authenticated.email}" logger.info "Mediator with urn #{urn} has been successfully removed by #{this.authenticated.email}" catch err utils.logAndSetResponse this, 500, "Could not remove Mediator by urn #{urn} via the API: #{err}", 'error' exports.heartbeat = (urn) -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeMediator denied.", 'info' return urn = unescape urn try mediator = yield Mediator.findOne({ urn: urn }).exec() if not mediator? this.status = 404 return heartbeat = this.request.body if not heartbeat?.uptime? this.status = 400 return if mediator._configModifiedTS > mediator._lastHeartbeat or heartbeat?.config is true # Return config if it has changed since last heartbeat this.body = mediator.config else this.body = "" # set internal properties if heartbeat? update = _lastHeartbeat: new Date() _uptime: heartbeat.uptime yield Mediator.findByIdAndUpdate(mediator._id, update).exec() this.status = 200 catch err utils.logAndSetResponse this, 500, "Could not process mediator heartbeat (urn: #{urn}): #{err}", 'error' validateConfigField = (param, def, field) -> switch def.type when 'string' if typeof field isnt 'string' throw constructError "Expected config param #{param} to be a string.", 'ValidationError' when 'bigstring' if typeof field isnt 'string' throw constructError "Expected config param #{param} to be a large string.", 'ValidationError' when 'number' if typeof field isnt 'number' throw constructError "Expected config param #{param} to be a number.", 'ValidationError' when 'bool' if typeof field isnt 'boolean' throw constructError "Expected config param #{param} to be a boolean.", 'ValidationError' when 'option' if (def.values.indexOf field) is -1 throw constructError "Expected config param #{param} to be one of #{def.values}", 'ValidationError' when 'map' if typeof field isnt 'object' throw constructError "Expected config param #{param} to be an object.", 'ValidationError' for k, v of field if typeof v isnt 'string' throw constructError "Expected config param #{param} to only contain string values.", 'ValidationError' when 'struct' if typeof field isnt 'object' throw constructError "Expected config param #{param} to be an object.", 'ValidationError' templateFields = (def.template.map (tp) -> tp.param) for paramField of field if paramField not in templateFields throw constructError "Field #{paramField} is not defined in template definition for config param #{param}.", 'ValidationError' when 'password' if typeof field isnt 'string' throw constructError "Expected config param #{param} to be a string representing a password.", 'ValidationError' validateConfig = (configDef, config) -> # reduce to a single true or false value, start assuming valid return Object.keys(config).every (param) -> # find the matching def if there is one matchingDefs = configDef.filter (def) -> return def.param is param # fail if there isn't a matching def if matchingDefs.length is 0 throw constructError "No config definition found for parameter #{param}", 'ValidationError' # validate the param against the defs matchingDefs.map (def) -> if def.array if not utils.typeIsArray config[param] throw constructError "Expected config param #{param} to be an array of type #{def.type}", 'ValidationError' for field, i in config[param] validateConfigField "#{param}[#{i}]", def, field else validateConfigField param, def, config[param] if process.env.NODE_ENV == "test" exports.validateConfig = validateConfig exports.setConfig = (urn) -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeMediator denied.", 'info' return urn = unescape urn config = this.request.body try mediator = yield Mediator.findOne({ urn: urn }).exec() if not mediator? this.status = 404 this.body = 'No mediator found for this urn.' return try restoreMaskedPasswords mediator.configDefs, config, mediator.config validateConfig mediator.configDefs, config catch err this.status = 400 this.body = err.message return yield Mediator.findOneAndUpdate({ urn: urn }, { config: this.request.body, _configModifiedTS: new Date() }).exec() this.status = 200 catch err utils.logAndSetResponse this, 500, "Could not set mediator config (urn: #{urn}): #{err}", 'error' saveDefaultChannelConfig = (channels) -> promises = [] for channel in channels delete channel._id for route in channel.routes delete route._id promises.push new Channel(channel).save() return promises exports.loadDefaultChannels = (urn) -> # Must be admin if not authorisation.inGroup 'admin', this.authenticated utils.logAndSetResponse this, 403, "User #{this.authenticated.email} is not an admin, API access to removeMediator denied.", 'info' return urn = unescape urn channels = this.request.body try mediator = yield Mediator.findOne({ urn: urn }).lean().exec() if not mediator? this.status = 404 this.body = 'No mediator found for this urn.' return if not channels? or channels.length is 0 yield Q.all saveDefaultChannelConfig(mediator.defaultChannelConfig) else filteredChannelConfig = mediator.defaultChannelConfig.filter (channel) -> return channel.name in channels if filteredChannelConfig.length < channels.length utils.logAndSetResponse this, 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 yield Q.all saveDefaultChannelConfig(filteredChannelConfig) this.status = 201 catch err logger.debug err.stack utils.logAndSetResponse this, 500, "Could not load mediator default channel config (urn: #{urn}): #{err}", 'error'