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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 | 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
| 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'
|