@itentialopensource/adapter-utils 4.44.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1012 @@
1
+ /* @copyright Itential, LLC 2018-9 */
2
+
3
+ // Set globals
4
+ /* global log */
5
+
6
+ /* NodeJS internal utilities */
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // The crypto libraries
11
+ const cryptoJS = require('crypto-js');
12
+
13
+ class AdapterPropertyUtil {
14
+ /**
15
+ * Adapter Translator Utility
16
+ * @constructor
17
+ */
18
+ constructor(prongId, directory) {
19
+ this.myid = prongId;
20
+ this.baseDir = directory;
21
+ }
22
+
23
+ // GENERIC UTILITY CALLS FOR PROPERTIES
24
+ /**
25
+ * @summary Get the entity schema and information for the action
26
+ *
27
+ * @function getEntitySchemaFromFS
28
+ * @param {String} entityName - the name of the entity (required)
29
+ * @param {String} actionName - the name of the action to take (required)
30
+ *
31
+ * @return {Object} entitySchema - the entity schema object
32
+ */
33
+ getEntitySchemaFromFS(entityName, actionName) {
34
+ const origin = `${this.myid}-propertyUtil-getEntitySchemaFromFS`;
35
+ log.trace(origin);
36
+
37
+ // create the generic part of an error object
38
+ const errorObj = {
39
+ origin
40
+ };
41
+
42
+ try {
43
+ // verify required data
44
+ if (!entityName || typeof entityName !== 'string') {
45
+ // add the specific pieces of the error object
46
+ errorObj.type = 'Missing Data';
47
+ errorObj.vars = ['Entity'];
48
+ log.error(`${origin}: Entity is required to get entity schema for action`);
49
+ throw new Error(JSON.stringify(errorObj));
50
+ }
51
+ if (!actionName || typeof actionName !== 'string') {
52
+ // add the specific pieces of the error object
53
+ errorObj.type = 'Missing Data';
54
+ errorObj.vars = ['Action'];
55
+
56
+ // log (if not system entity) and throw the error
57
+ if (entityName !== '.system') {
58
+ log.error(`${origin}: Action is required to get entity schema for action`);
59
+ }
60
+ throw new Error(JSON.stringify(errorObj));
61
+ }
62
+
63
+ // get the path for the specific action file
64
+ const actionFile = path.join(this.baseDir, `/entities/${entityName}/action.json`);
65
+
66
+ // if the file does not exist - error
67
+ if (!fs.existsSync(actionFile)) {
68
+ // add the specific pieces of the error object
69
+ errorObj.type = 'Missing File';
70
+ errorObj.vars = [actionFile];
71
+
72
+ // log (if not system entity) and throw the error
73
+ if (entityName !== '.system') {
74
+ log.error(`${origin}: Could not find file - ${actionFile}`);
75
+ }
76
+ throw new Error(JSON.stringify(errorObj));
77
+ }
78
+
79
+ // Read the action from the file system
80
+ const entityActions = JSON.parse(fs.readFileSync(actionFile, 'utf-8'));
81
+
82
+ // handle possible errors in the action file
83
+ if (!entityActions || typeof entityActions !== 'object') {
84
+ // add the specific pieces of the error object
85
+ errorObj.type = 'Invalid Action File';
86
+ errorObj.vars = ['invalid format', actionFile];
87
+
88
+ // log (if not system entity) and throw the error
89
+ if (entityName !== '.system') {
90
+ log.error(`${origin}: Invalid entity action file, please verify file: ${actionFile}`);
91
+ }
92
+ throw new Error(JSON.stringify(errorObj));
93
+ }
94
+ if (!entityActions.actions || !Array.isArray(entityActions.actions)) {
95
+ // add the specific pieces of the error object
96
+ errorObj.type = 'Invalid Action File';
97
+ errorObj.vars = ['missing array of actions', actionFile];
98
+
99
+ // log (if not system entity) and throw the error
100
+ if (entityName !== '.system') {
101
+ log.error(`${origin}: Invalid action file syntax ${actionFile} - must contain an array of actions`);
102
+ }
103
+ throw new Error(JSON.stringify(errorObj));
104
+ }
105
+
106
+ let actionInfo = null;
107
+
108
+ // get the specific action information
109
+ for (let i = 0; i < entityActions.actions.length; i += 1) {
110
+ if (entityActions.actions[i].name === actionName) {
111
+ actionInfo = entityActions.actions[i];
112
+ }
113
+ }
114
+
115
+ // if there are no actions - invalid
116
+ if (actionInfo === null) {
117
+ // add the specific pieces of the error object
118
+ errorObj.type = 'Invalid Action File';
119
+ errorObj.vars = ['missing action', actionFile];
120
+
121
+ // log (if not system entity) and throw the error
122
+ if (entityName !== '.system') {
123
+ log.error(`${origin}: Entity ${entityName} - action file missing action: ${actionName}`);
124
+ }
125
+ throw new Error(JSON.stringify(errorObj));
126
+ }
127
+
128
+ // verify required action info - protocol, method, entitypath and schema
129
+ if (!actionInfo.protocol) {
130
+ // add the specific pieces of the error object
131
+ errorObj.type = 'Invalid Action File';
132
+ errorObj.vars = ['missing protocol', actionFile];
133
+
134
+ // log (if not system entity) and throw the error
135
+ if (entityName !== '.system') {
136
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing protocol`);
137
+ }
138
+ throw new Error(JSON.stringify(errorObj));
139
+ }
140
+ if (!actionInfo.method) {
141
+ // add the specific pieces of the error object
142
+ errorObj.type = 'Invalid Action File';
143
+ errorObj.vars = ['missing method', actionFile];
144
+
145
+ // log (if not system entity) and throw the error
146
+ if (entityName !== '.system') {
147
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing method`);
148
+ }
149
+ throw new Error(JSON.stringify(errorObj));
150
+ }
151
+ if (!actionInfo.entitypath) {
152
+ // add the specific pieces of the error object
153
+ errorObj.type = 'Invalid Action File';
154
+ errorObj.vars = ['missing entity path', actionFile];
155
+
156
+ // log (if not system entity) and throw the error
157
+ if (entityName !== '.system') {
158
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing entity path`);
159
+ }
160
+ throw new Error(JSON.stringify(errorObj));
161
+ }
162
+ if (!actionInfo.schema && (!actionInfo.requestSchema || !actionInfo.responseSchema)) {
163
+ // add the specific pieces of the error object
164
+ errorObj.type = 'Invalid Action File';
165
+ errorObj.vars = ['missing schema', actionFile];
166
+
167
+ // log (if not system entity) and throw the error
168
+ if (entityName !== '.system') {
169
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing schema`);
170
+ }
171
+ throw new Error(JSON.stringify(errorObj));
172
+ }
173
+
174
+ // get the schema file name
175
+ let reqSchemaName = actionInfo.schema;
176
+ let respSchemaName = actionInfo.schema;
177
+
178
+ // if there is a separate request schema file it overrides the default schema
179
+ if (actionInfo.requestSchema) {
180
+ reqSchemaName = actionInfo.requestSchema;
181
+ }
182
+ // if there is a separate response schema file it overrides the default schema
183
+ if (actionInfo.responseSchema) {
184
+ respSchemaName = actionInfo.responseSchema;
185
+ }
186
+
187
+ // get the path for the specific schema file
188
+ const reqSchemaFile = path.join(this.baseDir, `/entities/${entityName}/${reqSchemaName}`);
189
+ const respSchemaFile = path.join(this.baseDir, `/entities/${entityName}/${respSchemaName}`);
190
+
191
+ // if the file does not exist - error
192
+ if (!fs.existsSync(reqSchemaFile)) {
193
+ // add the specific pieces of the error object
194
+ errorObj.type = 'Missing File';
195
+ errorObj.vars = [reqSchemaFile];
196
+
197
+ // log (if not system entity) and throw the error
198
+ if (entityName !== '.system') {
199
+ log.error(`${origin}: Could not find file - ${reqSchemaFile}`);
200
+ }
201
+ throw new Error(JSON.stringify(errorObj));
202
+ }
203
+
204
+ // if the file does not exist - error
205
+ if (!fs.existsSync(respSchemaFile)) {
206
+ // add the specific pieces of the error object
207
+ errorObj.type = 'Missing File';
208
+ errorObj.vars = [respSchemaFile];
209
+
210
+ // log (if not system entity) and throw the error
211
+ if (entityName !== '.system') {
212
+ log.error(`${origin}: Could not find file - ${respSchemaFile}`);
213
+ }
214
+ throw new Error(JSON.stringify(errorObj));
215
+ }
216
+
217
+ // Read the entity schema from the file system
218
+ const entitySchema = {
219
+ requestSchema: JSON.parse(fs.readFileSync(reqSchemaFile, 'utf-8')),
220
+ responseSchema: JSON.parse(fs.readFileSync(respSchemaFile, 'utf-8'))
221
+ };
222
+
223
+ // handle possible errors on the schema
224
+ if (!entitySchema.requestSchema || typeof entitySchema.requestSchema !== 'object') {
225
+ // add the specific pieces of the error object
226
+ errorObj.type = 'Invalid Schema File';
227
+ errorObj.vars = ['invalid format', reqSchemaFile];
228
+
229
+ // log (if not system entity) and throw the error
230
+ if (entityName !== '.system') {
231
+ log.error(`${origin}: Invalid entity request schema, please verify file: ${reqSchemaFile}`);
232
+ }
233
+ throw new Error(JSON.stringify(errorObj));
234
+ }
235
+ if (!entitySchema.responseSchema || typeof entitySchema.responseSchema !== 'object') {
236
+ // add the specific pieces of the error object
237
+ errorObj.type = 'Invalid Schema File';
238
+ errorObj.vars = ['invalid format', respSchemaFile];
239
+
240
+ // log (if not system entity) and throw the error
241
+ if (entityName !== '.system') {
242
+ log.error(`${origin}: Invalid entity response schema, please verify file: ${respSchemaFile}`);
243
+ }
244
+ throw new Error(JSON.stringify(errorObj));
245
+ }
246
+
247
+ // Merge the information into the entity schema
248
+ entitySchema.protocol = actionInfo.protocol;
249
+ entitySchema.method = actionInfo.method;
250
+ entitySchema.timeout = actionInfo.timeout;
251
+ entitySchema.entitypath = actionInfo.entitypath;
252
+ entitySchema.querykey = '?';
253
+ entitySchema.responseObjects = [];
254
+ entitySchema.mockresponses = [];
255
+
256
+ // if info provided, replace the defaults
257
+ if (actionInfo.querykey) {
258
+ entitySchema.querykey = actionInfo.querykey;
259
+ }
260
+ if (actionInfo.responseObjects) {
261
+ entitySchema.responseObjects = actionInfo.responseObjects;
262
+ }
263
+ if (actionInfo.headers) {
264
+ entitySchema.headers = actionInfo.headers;
265
+ }
266
+ if (actionInfo.requestDatatype) {
267
+ entitySchema.requestDatatype = actionInfo.requestDatatype;
268
+ } else if (actionInfo.datatype) {
269
+ entitySchema.requestDatatype = actionInfo.datatype;
270
+ } else {
271
+ entitySchema.requestDatatype = 'JSON';
272
+ }
273
+ if (actionInfo.responseDatatype) {
274
+ entitySchema.responseDatatype = actionInfo.responseDatatype;
275
+ } else if (actionInfo.datatype) {
276
+ entitySchema.responseDatatype = actionInfo.datatype;
277
+ } else {
278
+ entitySchema.responseDatatype = 'JSON';
279
+ }
280
+ if (Object.hasOwnProperty.call(actionInfo, 'sendEmpty')) {
281
+ entitySchema.sendEmpty = actionInfo.sendEmpty;
282
+ }
283
+ if (Object.hasOwnProperty.call(actionInfo, 'sendGetBody')) {
284
+ entitySchema.sendGetBody = actionInfo.sendGetBody;
285
+ }
286
+ if (actionInfo.sso) {
287
+ entitySchema.sso = actionInfo.sso;
288
+ }
289
+
290
+ // need to make sure we have supported datatypes - PLAIN is best if not supported - no translation or encoding
291
+ if (entitySchema.requestDatatype.toUpperCase() !== 'JSON' && entitySchema.requestDatatype.toUpperCase() !== 'XML'
292
+ && entitySchema.requestDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.requestDatatype.toUpperCase() !== 'FORM'
293
+ && entitySchema.requestDatatype.toUpperCase() !== 'JSON2XML') {
294
+ entitySchema.requestDatatype = 'PLAIN';
295
+ }
296
+ if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
297
+ && entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON') {
298
+ entitySchema.responseDatatype = 'PLAIN';
299
+ }
300
+
301
+ // go through each response object to see if there is mock data
302
+ for (let i = 0; i < entitySchema.responseObjects.length; i += 1) {
303
+ entitySchema.responseObjects[i].name = entitySchema.entitypath;
304
+ entitySchema.responseObjects[i].method = entitySchema.method;
305
+
306
+ // if there is mock data, read the mock data from the file system
307
+ if (entitySchema.responseObjects[i].mockFile) {
308
+ const mockResponse = {
309
+ name: entitySchema.responseObjects[i].name,
310
+ method: entitySchema.responseObjects[i].method,
311
+ type: entitySchema.responseObjects[i].type,
312
+ file: entitySchema.responseObjects[i].mockFile
313
+ };
314
+
315
+ // get the mock file name
316
+ const tempName = entitySchema.responseObjects[i].mockFile;
317
+
318
+ // if the file name was provided
319
+ if (tempName) {
320
+ const mockFileName = path.join(this.baseDir, `/entities/${entityName}/${tempName}`);
321
+
322
+ // if the file does not exist - throw warning
323
+ if (fs.existsSync(mockFileName)) {
324
+ // set the normal headers based on the type of data for the call
325
+ if (entitySchema.responseDatatype && entitySchema.responseDatatype.toUpperCase() === 'PLAIN') {
326
+ // read the mock date from the file system
327
+ mockResponse.response = fs.readFileSync(mockFileName, 'utf-8');
328
+ } else if (entitySchema.responseDatatype && (entitySchema.responseDatatype.toUpperCase() === 'XML'
329
+ || entitySchema.responseDatatype.toUpperCase() === 'XML2JSON')) {
330
+ // read the mock date from the file system
331
+ mockResponse.response = fs.readFileSync(mockFileName, 'utf-8');
332
+ } else {
333
+ // read the mock date from the file system
334
+ try {
335
+ // parse the mockdata file to store it as an object
336
+ mockResponse.response = JSON.parse(fs.readFileSync(mockFileName, 'utf-8'));
337
+ } catch (excep) {
338
+ log.warn(`${origin}: Could not parse file - ${mockFileName}`);
339
+ mockResponse.response = '';
340
+ }
341
+ }
342
+ } else {
343
+ log.warn(`${origin}: Could not find file - ${mockFileName}`);
344
+ mockResponse.response = null;
345
+ }
346
+ } else {
347
+ mockResponse.response = null;
348
+ }
349
+
350
+ // add the response to the array of mock responses
351
+ entitySchema.mockresponses.push(mockResponse);
352
+ }
353
+ }
354
+
355
+ // return the entity schema
356
+ return entitySchema;
357
+ } catch (e) {
358
+ let internal = null;
359
+ errorObj.type = 'Caught Exception';
360
+ errorObj.vars = [];
361
+ errorObj.exception = e;
362
+
363
+ // determine if we already had an internal message
364
+ try {
365
+ internal = JSON.parse(e.message);
366
+ } catch (ex) {
367
+ // message was not internal
368
+ log.error(`${origin}: Issue parsing entity schema: ${e}`);
369
+ internal = null;
370
+ }
371
+
372
+ // return the appropriate error message
373
+ if (internal && internal.origin && internal.type) {
374
+ throw e;
375
+ } else {
376
+ throw new Error(JSON.stringify(errorObj));
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * @summary Build the entitySchema structure from DB config
383
+ *
384
+ * @function getEntitySchemaFromDB
385
+ * @param {Object} dbObj - the database connect information (optional)
386
+ * @param {String} entityName - the name of the entity (required)
387
+ * @param {String} actionName - the name of the action to take (required)
388
+ *
389
+ * @return {Object} entitySchema - the entity schema object
390
+ */
391
+ getEntitySchemaFromDB(dbObj, entityName, actionName, dbUtils, callback) {
392
+ const origin = `${this.myid}-propertyUtil-getEntitySchemaFromDB`;
393
+ log.trace(origin);
394
+
395
+ // create the generic part of an error object
396
+ const errorObj = {
397
+ origin,
398
+ isError: true
399
+ };
400
+
401
+ try {
402
+ // verify required data
403
+ if (!entityName || typeof entityName !== 'string') {
404
+ // add the specific pieces of the error object
405
+ errorObj.type = 'Missing Data';
406
+ errorObj.vars = ['Entity'];
407
+ log.error(`${origin}: Entity is required to get entity schema for action`);
408
+ errorObj.error = [`${origin}: Entity is required to get entity schema for action`];
409
+ return callback(null, errorObj);
410
+ }
411
+ if (!actionName || typeof actionName !== 'string') {
412
+ // add the specific pieces of the error object
413
+ errorObj.type = 'Missing Data';
414
+ errorObj.vars = ['Action'];
415
+
416
+ // log (if not system entity) and return the error
417
+ if (entityName !== '.system') {
418
+ log.error(`${origin}: Action is required to get entity schema for action`);
419
+ errorObj.error = [`${origin}: Action is required to get entity schema for action`];
420
+ }
421
+ return callback(null, errorObj);
422
+ }
423
+
424
+ let entitySchema = null;
425
+
426
+ // set up the options-filter
427
+ const dbOpts = {
428
+ filter: {
429
+ id: this.myid,
430
+ entity: entityName
431
+ }
432
+ };
433
+
434
+ // call to get the adapter schema from the database
435
+ return dbUtils.find('adapter_configs', dbOpts, dbObj, null, (dbError, dbResult) => {
436
+ if (dbError) {
437
+ // add the specific pieces of the error object
438
+ errorObj.type = 'Database Error';
439
+ errorObj.vars = ['dbError'];
440
+ log.error(`${origin}: Database Error: ${dbError}`);
441
+ errorObj.error = [`${origin}: Database Error: ${dbError}`];
442
+ return callback(null, errorObj);
443
+ }
444
+ if (!dbResult) {
445
+ // add the specific pieces of the error object
446
+ errorObj.type = 'Missing Data';
447
+ errorObj.vars = ['Entity'];
448
+ log.error(`${origin}: Entity was not found in the database`);
449
+ errorObj.error = [`${origin}: Entity was not found in the database`];
450
+ return callback(null, errorObj);
451
+ }
452
+
453
+ // Read the action from the entity object
454
+ const entityActions = dbResult[0];
455
+
456
+ // handle possible errors in the action file
457
+ if (!entityActions || typeof entityActions !== 'object') {
458
+ // add the specific pieces of the error object
459
+ errorObj.type = 'Invalid Action File';
460
+ errorObj.vars = ['invalid format', entityName];
461
+
462
+ // log (if not system entity) and return the error
463
+ if (entityName !== '.system') {
464
+ log.error(`${origin}: Invalid entity action file, please verify file: ${entityName}`);
465
+ errorObj.error = [`${origin}: Invalid entity action file, please verify file: ${entityName}`];
466
+ }
467
+ return callback(null, errorObj);
468
+ }
469
+ if (!entityActions.actions || !Array.isArray(entityActions.actions)) {
470
+ // add the specific pieces of the error object
471
+ errorObj.type = 'Invalid Action File';
472
+ errorObj.vars = ['missing array of actions', entityName];
473
+
474
+ // log (if not system entity) and return the error
475
+ if (entityName !== '.system') {
476
+ log.error(`${origin}: Invalid action file syntax ${entityName} - must contain an array of actions`);
477
+ errorObj.error = [`${origin}: Invalid action file syntax ${entityName} - must contain an array of actions`];
478
+ }
479
+ return callback(null, errorObj);
480
+ }
481
+
482
+ let actionInfo = null;
483
+
484
+ // get the specific action information
485
+ for (let i = 0; i < entityActions.actions.length; i += 1) {
486
+ if (entityActions.actions[i].name === actionName) {
487
+ actionInfo = entityActions.actions[i];
488
+ }
489
+ }
490
+
491
+ // if there are no actions - invalid
492
+ if (actionInfo === null) {
493
+ // add the specific pieces of the error object
494
+ errorObj.type = 'Invalid Action File';
495
+ errorObj.vars = ['missing action', entityName];
496
+
497
+ // log (if not system entity) and return the error
498
+ if (entityName !== '.system') {
499
+ log.error(`${origin}: Entity ${entityName} - action file missing action: ${actionName}`);
500
+ errorObj.error = [`${origin}: Entity ${entityName} - action file missing action: ${actionName}`];
501
+ }
502
+ return callback(null, errorObj);
503
+ }
504
+
505
+ // verify required action info - protocol, method, entitypath and schema
506
+ if (!actionInfo.protocol) {
507
+ // add the specific pieces of the error object
508
+ errorObj.type = 'Invalid Action File';
509
+ errorObj.vars = ['missing protocol', entityName];
510
+
511
+ // log (if not system entity) and return the error
512
+ if (entityName !== '.system') {
513
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing protocol`);
514
+ errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing protocol`];
515
+ }
516
+ return callback(null, errorObj);
517
+ }
518
+ if (!actionInfo.method) {
519
+ // add the specific pieces of the error object
520
+ errorObj.type = 'Invalid Action File';
521
+ errorObj.vars = ['missing method', entityName];
522
+
523
+ // log (if not system entity) and return the error
524
+ if (entityName !== '.system') {
525
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing method`);
526
+ errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing method`];
527
+ }
528
+ return callback(null, errorObj);
529
+ }
530
+ if (!actionInfo.entitypath) {
531
+ // add the specific pieces of the error object
532
+ errorObj.type = 'Invalid Action File';
533
+ errorObj.vars = ['missing entity path', entityName];
534
+
535
+ // log (if not system entity) and return the error
536
+ if (entityName !== '.system') {
537
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing entity path`);
538
+ errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing entity path`];
539
+ }
540
+ return callback(null, errorObj);
541
+ }
542
+ if (!actionInfo.schema && (!actionInfo.requestSchema || !actionInfo.responseSchema)) {
543
+ // add the specific pieces of the error object
544
+ errorObj.type = 'Invalid Action File';
545
+ errorObj.vars = ['missing schema', entityName];
546
+
547
+ // log (if not system entity) and return the error
548
+ if (entityName !== '.system') {
549
+ log.error(`${origin}: Entity ${entityName} action ${actionName} - missing schema`);
550
+ errorObj.error = [`${origin}: Entity ${entityName} action ${actionName} - missing schema`];
551
+ }
552
+ return callback(null, errorObj);
553
+ }
554
+
555
+ // get the schema file name
556
+ let reqSchemaName = actionInfo.schema;
557
+ let respSchemaName = actionInfo.schema;
558
+
559
+ // if there is a separate request schema file it overrides the default schema
560
+ if (actionInfo.requestSchema) {
561
+ reqSchemaName = actionInfo.requestSchema;
562
+ }
563
+ // if there is a separate response schema file it overrides the default schema
564
+ if (actionInfo.responseSchema) {
565
+ respSchemaName = actionInfo.responseSchema;
566
+ }
567
+
568
+ if (!entityActions.schema || !Array.isArray(entityActions.schema)) {
569
+ // add the specific pieces of the error object
570
+ errorObj.type = 'Invalid Schema File';
571
+ errorObj.vars = ['missing array of schemas', reqSchemaName];
572
+
573
+ // log (if not system entity) and return the error
574
+ if (entityName !== '.system') {
575
+ log.error(`${origin}: Invalid schema file syntax ${reqSchemaName} - must contain an array of schemas`);
576
+ errorObj.error = [`${origin}: Invalid schema file syntax ${reqSchemaName} - must contain an array of schemas`];
577
+ }
578
+ return callback(null, errorObj);
579
+ }
580
+
581
+ let reqSchemaInfo = null;
582
+ let respSchemaInfo = null;
583
+
584
+ // get the specific action information
585
+ for (let i = 0; i < entityActions.schema.length; i += 1) {
586
+ if (entityActions.schema[i].name === reqSchemaName) {
587
+ reqSchemaInfo = entityActions.schema[i].schema;
588
+ }
589
+ if (entityActions.schema[i].name === respSchemaName) {
590
+ respSchemaInfo = entityActions.schema[i].schema;
591
+ }
592
+ }
593
+
594
+ // if there are no actions - invalid
595
+ if (reqSchemaInfo === null) {
596
+ // add the specific pieces of the error object
597
+ errorObj.type = 'Missing File';
598
+ errorObj.vars = [reqSchemaName];
599
+
600
+ // log (if not system entity) and return the error
601
+ if (entityName !== '.system') {
602
+ log.error(`${origin}: Could not find file - ${reqSchemaName}`);
603
+ errorObj.error = [`${origin}: Could not find file - ${reqSchemaName}`];
604
+ }
605
+ return callback(null, errorObj);
606
+ }
607
+
608
+ // if there are no actions - invalid
609
+ if (respSchemaInfo === null) {
610
+ // add the specific pieces of the error object
611
+ errorObj.type = 'Missing File';
612
+ errorObj.vars = [respSchemaName];
613
+
614
+ // log (if not system entity) and return the error
615
+ if (entityName !== '.system') {
616
+ log.error(`${origin}: Could not find file - ${respSchemaName}`);
617
+ errorObj.error = [`${origin}: Could not find file - ${respSchemaName}`];
618
+ }
619
+ return callback(null, errorObj);
620
+ }
621
+
622
+ // handle possible errors on the schema
623
+ if (typeof reqSchemaInfo !== 'object') {
624
+ // add the specific pieces of the error object
625
+ errorObj.type = 'Invalid Schema File';
626
+ errorObj.vars = ['invalid format', reqSchemaInfo];
627
+
628
+ // log (if not system entity) and return the error
629
+ if (entityName !== '.system') {
630
+ log.error(`${origin}: Invalid entity request schema, please verify file: ${reqSchemaInfo}`);
631
+ errorObj.error = [`${origin}: Invalid entity request schema, please verify file: ${reqSchemaInfo}`];
632
+ }
633
+ return callback(null, errorObj);
634
+ }
635
+ if (typeof respSchemaInfo !== 'object') {
636
+ // add the specific pieces of the error object
637
+ errorObj.type = 'Invalid Schema File';
638
+ errorObj.vars = ['invalid format', respSchemaInfo];
639
+
640
+ // log (if not system entity) and return the error
641
+ if (entityName !== '.system') {
642
+ log.error(`${origin}: Invalid entity response schema, please verify file: ${respSchemaInfo}`);
643
+ errorObj.error = [`${origin}: Invalid entity response schema, please verify file: ${respSchemaInfo}`];
644
+ }
645
+ return callback(null, errorObj);
646
+ }
647
+
648
+ // Read the entity schema from the file system
649
+ entitySchema = {
650
+ requestSchema: reqSchemaInfo,
651
+ responseSchema: respSchemaInfo
652
+ };
653
+
654
+ // Merge the information into the entity schema
655
+ entitySchema.protocol = actionInfo.protocol;
656
+ entitySchema.method = actionInfo.method;
657
+ entitySchema.timeout = actionInfo.timeout;
658
+ entitySchema.entitypath = actionInfo.entitypath;
659
+ entitySchema.querykey = '?';
660
+ entitySchema.responseObjects = [];
661
+ entitySchema.mockresponses = [];
662
+
663
+ // if info provided, replace the defaults
664
+ if (actionInfo.querykey) {
665
+ entitySchema.querykey = actionInfo.querykey;
666
+ }
667
+ if (actionInfo.responseObjects) {
668
+ entitySchema.responseObjects = actionInfo.responseObjects;
669
+ }
670
+ if (actionInfo.headers) {
671
+ entitySchema.headers = actionInfo.headers;
672
+ }
673
+ if (actionInfo.requestDatatype) {
674
+ entitySchema.requestDatatype = actionInfo.requestDatatype;
675
+ } else if (actionInfo.datatype) {
676
+ entitySchema.requestDatatype = actionInfo.datatype;
677
+ } else {
678
+ entitySchema.requestDatatype = 'JSON';
679
+ }
680
+ if (actionInfo.responseDatatype) {
681
+ entitySchema.responseDatatype = actionInfo.responseDatatype;
682
+ } else if (actionInfo.datatype) {
683
+ entitySchema.responseDatatype = actionInfo.datatype;
684
+ } else {
685
+ entitySchema.responseDatatype = 'JSON';
686
+ }
687
+ if (Object.hasOwnProperty.call(actionInfo, 'sendEmpty')) {
688
+ entitySchema.sendEmpty = actionInfo.sendEmpty;
689
+ }
690
+ if (Object.hasOwnProperty.call(actionInfo, 'sendGetBody')) {
691
+ entitySchema.sendGetBody = actionInfo.sendGetBody;
692
+ }
693
+ if (actionInfo.sso) {
694
+ entitySchema.sso = actionInfo.sso;
695
+ }
696
+
697
+ // need to make sure we have supported datatypes - PLAIN is best if not supported - no translation or encoding
698
+ if (entitySchema.requestDatatype.toUpperCase() !== 'JSON' && entitySchema.requestDatatype.toUpperCase() !== 'XML'
699
+ && entitySchema.requestDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.requestDatatype.toUpperCase() !== 'FORM'
700
+ && entitySchema.requestDatatype.toUpperCase() !== 'JSON2XML') {
701
+ entitySchema.requestDatatype = 'PLAIN';
702
+ }
703
+ if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
704
+ && entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON') {
705
+ entitySchema.responseDatatype = 'PLAIN';
706
+ }
707
+
708
+ // go through each response object to see if there is mock data
709
+ for (let i = 0; i < entitySchema.responseObjects.length; i += 1) {
710
+ entitySchema.responseObjects[i].name = entitySchema.entitypath;
711
+ entitySchema.responseObjects[i].method = entitySchema.method;
712
+
713
+ // if there is mock data, read the mock data from the file system
714
+ if (entitySchema.responseObjects[i].mockFile) {
715
+ const mockResponse = {
716
+ name: entitySchema.responseObjects[i].name,
717
+ method: entitySchema.responseObjects[i].method,
718
+ type: entitySchema.responseObjects[i].type,
719
+ file: entitySchema.responseObjects[i].mockFile
720
+ };
721
+
722
+ // get the mock file name
723
+ const tempName = entitySchema.responseObjects[i].mockFile;
724
+
725
+ // if the file name was provided
726
+ if (tempName) {
727
+ // need to get just the file name
728
+ const mockFileArray = tempName.split('/');
729
+ const mockFileName = mockFileArray[mockFileArray.length - 1];
730
+ const mockResp = entityActions.mockdatafiles[mockFileName];
731
+
732
+ if (mockResp) {
733
+ // set the normal headers based on the type of data for the call
734
+ if (entitySchema.responseDatatype && entitySchema.responseDatatype.toUpperCase() === 'PLAIN') {
735
+ // read the mock date from the file system
736
+ mockResponse.response = mockResp;
737
+ } else if (entitySchema.responseDatatype && (entitySchema.responseDatatype.toUpperCase() === 'XML'
738
+ || entitySchema.responseDatatype.toUpperCase() === 'XML2JSON')) {
739
+ // read the mock date from the file system
740
+ mockResponse.response = mockResp;
741
+ } else {
742
+ // read the mock date from the file system
743
+ try {
744
+ // parse the mockdata file to store it as an object
745
+ mockResponse.response = mockResp;
746
+ } catch (excep) {
747
+ log.warn(`${origin}: Could not parse file - ${mockFileName}`);
748
+ mockResponse.response = '';
749
+ }
750
+ }
751
+ } else {
752
+ log.warn(`${origin}: Could not find file - ${mockFileName}`);
753
+ mockResponse.response = null;
754
+ }
755
+ } else {
756
+ mockResponse.response = null;
757
+ }
758
+
759
+ // add the response to the array of mock responses
760
+ entitySchema.mockresponses.push(mockResponse);
761
+ }
762
+ }
763
+
764
+ // return the entity schema
765
+ return callback(entitySchema, null);
766
+ });
767
+ } catch (e) {
768
+ errorObj.type = 'Caught Exception';
769
+ errorObj.vars = [];
770
+ errorObj.exception = e;
771
+
772
+ // return the error message
773
+ log.info(`unable to get adapter config from database: ${e}`);
774
+ return callback(null, errorObj);
775
+ }
776
+ }
777
+
778
+ /**
779
+ * @summary Get the entity schema and information for the action
780
+ *
781
+ * @function getEntitySchema
782
+ * @param {String} entityName - the name of the entity (required)
783
+ * @param {String} actionName - the name of the action to take (required)
784
+ *
785
+ * @return {Object} entitySchema - the entity schema object
786
+ */
787
+ getEntitySchema(entityName, actionName, dbUtils, callback) {
788
+ const origin = `${this.myid}-propertyUtil-getEntitySchema`;
789
+ log.trace(origin);
790
+
791
+ // need to try to get the entity schema from the adapter database
792
+ try {
793
+ // call to get the adapter schema from the database
794
+ return this.getEntitySchemaFromDB(null, entityName, actionName, dbUtils, (dbresp, dberror) => {
795
+ // if we got an error back - just means db config not in place
796
+ if (dberror || !dbresp || (dbresp && Object.keys(dbresp).length === 0)) {
797
+ log.debug('unable to get adapter config from adapter database');
798
+
799
+ // Temporarily hardcode the adapter properties
800
+ const iapDB = {
801
+ dburl: 'mongodb://127.0.0.1:27017',
802
+ dboptions: {},
803
+ database: 'pronghorn'
804
+ };
805
+
806
+ // need to try to get the entity schema from the iap database
807
+ return this.getEntitySchemaFromDB(iapDB, entityName, actionName, dbUtils, (iapdbresp, iapdberror) => {
808
+ // if we got an error back - just means db config not in place
809
+ if (iapdberror || !iapdbresp || (iapdbresp && Object.keys(iapdbresp).length === 0)) {
810
+ log.debug('unable to get adapter config from iap database');
811
+
812
+ // need to try to get the entity schema from the filesystem
813
+ log.debug('returning adapter config from file system');
814
+ try {
815
+ return callback(this.getEntitySchemaFromFS(entityName, actionName), null);
816
+ } catch (exc) {
817
+ log.error('Exception caught on File System');
818
+ return callback(null, exc);
819
+ }
820
+ }
821
+
822
+ // return the iap db config
823
+ log.debug(`returning adapter config from iap database ${iapdbresp}`);
824
+ return callback(iapdbresp, null);
825
+ });
826
+ }
827
+
828
+ // return the adapter db config
829
+ log.debug(`returning adapter config from adapter database ${dbresp}`);
830
+ return callback(dbresp, null);
831
+ });
832
+ } catch (exaddb) {
833
+ log.debug(`unable to get adapter config: ${exaddb}`);
834
+ return callback(null, exaddb);
835
+ }
836
+ }
837
+
838
+ /**
839
+ * @summary Takes in propertiesSchema and creates an object with all of the
840
+ * defaults set.
841
+ *
842
+ * @function setDefaults
843
+ * @param {Object} propSchema - the proeprty schema
844
+ *
845
+ * @return {Object} the object with default values from the property schema
846
+ */
847
+ setDefaults(propSchema) {
848
+ const origin = `${this.myid}-propertyUtil-setDefaults`;
849
+ log.trace(origin);
850
+
851
+ const defaults = {};
852
+
853
+ // verify the input for the method
854
+ if (!propSchema || !propSchema.properties) {
855
+ return defaults;
856
+ }
857
+
858
+ const propKeys = Object.keys(propSchema.properties);
859
+
860
+ // loop through all of the properties in the schema
861
+ for (let k = 0; k < propKeys.length; k += 1) {
862
+ const thisProp = propSchema.properties[propKeys[k]];
863
+
864
+ // if this key is to a reference
865
+ if (thisProp.$ref) {
866
+ const refs = thisProp.$ref.split('/');
867
+
868
+ // references should be to a sub of another section like definitions
869
+ // like - #/definitions/credentials
870
+ if (refs.length >= 3) {
871
+ // recursive call with reference object
872
+ defaults[propKeys[k]] = this.setDefaults(propSchema[refs[1]][refs[2]]);
873
+ }
874
+ } else if (Object.hasOwnProperty.call(thisProp, 'default')) {
875
+ // if there is a default put into the object
876
+ defaults[propKeys[k]] = thisProp.default;
877
+ }
878
+ }
879
+
880
+ // return the defaults
881
+ return defaults;
882
+ }
883
+
884
+ /**
885
+ * @summary Takes in properties and the secondary properties and merges them so the returned
886
+ * object has secondary properties where no primary property values were provided.
887
+ *
888
+ * @function mergeProperties
889
+ * @param {Object} properties - the primary propererties (required)
890
+ * @param {Object} secondary - the secondary propererties (required)
891
+ *
892
+ * @return {Object} the properties with the merged in secondaries
893
+ */
894
+ mergeProperties(properties, secondary) {
895
+ const origin = `${this.myid}-propertyUtil-mergeProperties`;
896
+ log.trace(origin);
897
+
898
+ // verify the input for the method
899
+ if (!properties || typeof properties !== 'object') {
900
+ return secondary;
901
+ }
902
+ if (!secondary || typeof secondary !== 'object') {
903
+ return properties;
904
+ }
905
+
906
+ const combinedProps = secondary;
907
+ const propKeys = Object.keys(properties);
908
+
909
+ // loop through all of the primary properties to insert them into the conbined data
910
+ for (let k = 0; k < propKeys.length; k += 1) {
911
+ const thisProp = properties[propKeys[k]];
912
+
913
+ // if this key is to an object
914
+ if (thisProp && typeof thisProp === 'object' && combinedProps[propKeys[k]]) {
915
+ // recursive call with primary and secondary object
916
+ combinedProps[propKeys[k]] = this.mergeProperties(thisProp, combinedProps[propKeys[k]]);
917
+ } else if ((thisProp !== undefined && thisProp !== null && thisProp !== '')
918
+ || (combinedProps[propKeys[k]] === undefined || combinedProps[propKeys[k]] === null || combinedProps[propKeys[k]] === '')) {
919
+ // if no secondary or primary has value merge it - overriding the secondary
920
+ combinedProps[propKeys[k]] = thisProp;
921
+ }
922
+ }
923
+
924
+ // return the merged properties
925
+ return combinedProps;
926
+ }
927
+
928
+ /**
929
+ * @summary Takes in property text and an encoding/encryption and returns the resulting
930
+ * encoded/encrypted string
931
+ *
932
+ * @function encryptProperty
933
+ * @param {String} property - the property to encrypt (required)
934
+ * @param {String} technique - the technique to use to encrypt (required)
935
+ *
936
+ * @return {String} the encrypted/encoded string
937
+ */
938
+ encryptProperty(property, technique) {
939
+ const origin = `${this.myid}-propertyUtil-encryptProperty`;
940
+ log.trace(origin);
941
+
942
+ try {
943
+ // verify the input for the method
944
+ if (!property) {
945
+ return null;
946
+ }
947
+ if (!technique) {
948
+ return property;
949
+ }
950
+
951
+ // if encoding, return the encoded string
952
+ if (technique.toUpperCase() === 'BASE64') {
953
+ return `{code}${Buffer.from(property).toString('base64')}`;
954
+ }
955
+ // if encrypting, return the encrypted string
956
+ if (technique.toUpperCase() === 'ENCRYPT') {
957
+ return `{crypt}${cryptoJS.AES.encrypt(property, this.myid)}`;
958
+ }
959
+
960
+ log.warn(`${origin}: Encyrpt technique ${technique} not supported!`);
961
+ return property;
962
+ } catch (e) {
963
+ log.error(`${origin}: Encyrpt technique ${technique} took exception: ${e}`);
964
+ return null;
965
+ }
966
+ }
967
+
968
+ /**
969
+ * @summary Takes in encrypted or encoded property and decodes/decrypts it to return
970
+ * the actual property
971
+ *
972
+ * @function decryptProperty
973
+ * @param {String} property - the property to decrypt
974
+ *
975
+ * @return {String} the string
976
+ */
977
+ decryptProperty(property) {
978
+ const origin = `${this.myid}-propertyUtil-decryptProperty`;
979
+ log.trace(origin);
980
+
981
+ try {
982
+ // verify the input for the method
983
+ if (!property) {
984
+ return null;
985
+ }
986
+
987
+ if (property.indexOf('{code}') === 0) {
988
+ // remove the start
989
+ const b64prop = property.substring(6);
990
+
991
+ // decode the string
992
+ return Buffer.from(b64prop, 'base64').toString();
993
+ }
994
+
995
+ if (property.indexOf('{crypt}') === 0) {
996
+ // remove the start
997
+ const cryptprop = property.substring(7);
998
+
999
+ // decrypt the string
1000
+ return cryptoJS.AES.decrypt(cryptprop, this.myid).toString(cryptoJS.enc.Utf8);
1001
+ }
1002
+
1003
+ log.warn(`${origin}: Invalid property ${property} - should start with {code} or {crypt}!`);
1004
+ return property;
1005
+ } catch (e) {
1006
+ log.error(`${origin}: Decrypt property took exception: ${e}`);
1007
+ return null;
1008
+ }
1009
+ }
1010
+ }
1011
+
1012
+ module.exports = AdapterPropertyUtil;