@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.
package/error.json ADDED
@@ -0,0 +1,148 @@
1
+ {
2
+ "errors": [
3
+ {
4
+ "key": "Unable To Save To Database",
5
+ "icode": "AD.100",
6
+ "displayString": "Error on saving to Database $VARIABLE$",
7
+ "recommendation": "Verify the database connectivity and credentials"
8
+ },
9
+ {
10
+ "key": "Database Error",
11
+ "icode": "AD.101",
12
+ "displayString": "Database Error: $VARIABLE$",
13
+ "recommendation": "Verify the database connectivity, schema and permissions"
14
+ },
15
+ {
16
+ "key": "Database Credentials",
17
+ "icode": "AD.102",
18
+ "displayString": "Database Error: Invalid Credentials",
19
+ "recommendation": "Verify the database credentials"
20
+ },
21
+ {
22
+ "key": "No Queue Item",
23
+ "icode": "AD.110",
24
+ "displayString": "Queue Item $VARIABLE$ not found in queue",
25
+ "recommendation": "Make sure the queue is working properly"
26
+ },
27
+ {
28
+ "key": "Unable To Create Queue",
29
+ "icode": "AD.111",
30
+ "displayString": "Unable to create queue: $VARIABLE$",
31
+ "recommendation": "Verify access to the database, redis or system memory"
32
+ },
33
+ {
34
+ "key": "Queue Full",
35
+ "icode": "AD.112",
36
+ "displayString": "Request $VARIABLE$ Transaction $VARIABLE$ rejected - queue full at $VARIABLE$",
37
+ "recommendation": "Make sure the queue is working properly"
38
+ },
39
+ {
40
+ "key": "Unable To Clear Queue",
41
+ "icode": "AD.113",
42
+ "displayString": "Unable to clear queue: $VARIABLE$",
43
+ "recommendation": "Make sure the queue exists"
44
+ },
45
+ {
46
+ "key": "Unable To Claim Turn",
47
+ "icode": "AD.114",
48
+ "displayString": "Request $VARIABLE$ Transaction $VARIABLE$ unable to claim license: $VARIABLE$",
49
+ "recommendation": "Make sure the queue is working properly"
50
+ },
51
+ {
52
+ "key": "Unable To Free Turn",
53
+ "icode": "AD.115",
54
+ "displayString": "Request $VARIABLE$ Transaction $VARIABLE$ unable to free license: $VARIABLE$",
55
+ "recommendation": "Make sure the queue is working properly"
56
+ },
57
+ {
58
+ "key": "Item In Wrong State",
59
+ "icode": "AD.116",
60
+ "displayString": "Queue item is in the wrong state: $VARIABLE$ ",
61
+ "recommendation": "Make sure the queue is working properly"
62
+ },
63
+ {
64
+ "key": "Missing Data",
65
+ "icode": "AD.300",
66
+ "displayString": "$VARIABLE$ is required",
67
+ "recommendation": "Please provide the required data"
68
+ },
69
+ {
70
+ "key": "Missing File",
71
+ "icode": "AD.301",
72
+ "displayString": "Can not open file $VARIABLE$",
73
+ "recommendation": "Verify the file exists and has proper permissions"
74
+ },
75
+ {
76
+ "key": "Invalid Action File",
77
+ "icode": "AD.302",
78
+ "displayString": "Invalid action file: $VARIABLE$ in $VARIABLE$",
79
+ "recommendation": "Verify the action file"
80
+ },
81
+ {
82
+ "key": "Unsupported Protocol",
83
+ "icode": "AD.303",
84
+ "displayString": "Protocol $VARIABLE$ not currently supported",
85
+ "recommendation": "Verify the protocol on the action"
86
+ },
87
+ {
88
+ "key": "Invalid Schema File",
89
+ "icode": "AD.304",
90
+ "displayString": "Invalid schema file: $VARIABLE$ in $VARIABLE$",
91
+ "recommendation": "Verify the action file"
92
+ },
93
+ {
94
+ "key": "Query Not Translated",
95
+ "icode": "AD.310",
96
+ "displayString": "Query Not Translated",
97
+ "recommendation": "Verify the information passed in the query object"
98
+ },
99
+ {
100
+ "key": "Payload Not Translated",
101
+ "icode": "AD.311",
102
+ "displayString": "Payload Not Translated",
103
+ "recommendation": "Verify the information passed in the payload object"
104
+ },
105
+ {
106
+ "key": "Schema Validation Failure",
107
+ "icode": "AD.312",
108
+ "displayString": "Schema validation failed on $VARIABLE$",
109
+ "recommendation": "Verify the information provided is in the correct format with everything required"
110
+ },
111
+ {
112
+ "key": "Unable To Authenticate",
113
+ "icode": "AD.400",
114
+ "displayString": "Unable to authenticate with $VARIABLE$, response $VARIABLE$",
115
+ "recommendation": "Verify the user credentials and check the response for more information"
116
+ },
117
+ {
118
+ "key": "Unable To Encode",
119
+ "icode": "AD.401",
120
+ "displayString": "Can not perform base64 encoding on $VARIABLE$",
121
+ "recommendation": "Check the authentication format"
122
+ },
123
+ {
124
+ "key": "Unable To Get Token",
125
+ "icode": "AD.402",
126
+ "displayString": "Unable to get token for user: $VARIABLE$",
127
+ "recommendation": "Verify the user credentials and the system information"
128
+ },
129
+ {
130
+ "key": "Error On Request",
131
+ "icode": "AD.500",
132
+ "displayString": "Error $VARIABLE$ received on request",
133
+ "recommendation": "Verify the request is accurate via debug logs and postman"
134
+ },
135
+ {
136
+ "key": "Request Timeout",
137
+ "icode": "AD.501",
138
+ "displayString": "The Adapter has run out of time for the request",
139
+ "recommendation": "Increase your adapter request.attempt_timeout property"
140
+ },
141
+ {
142
+ "key": "Caught Exception",
143
+ "icode": "AD.900",
144
+ "displayString": "Caught Exception $VARIABLE$",
145
+ "recommendation": "Evaluate why the exception took place"
146
+ }
147
+ ]
148
+ }
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ const RequestHandler = require('./lib/requestHandler');
2
+ const PropertyUtility = require('./lib/propertyUtil');
3
+
4
+ module.exports = {
5
+ RequestHandler,
6
+ PropertyUtility
7
+ };
@@ -0,0 +1,4083 @@
1
+ /* @copyright Itential, LLC 2018 */
2
+
3
+ // Set globals
4
+ /* global adapters brokers g_redis log */
5
+ /* eslint class-methods-use-this:warn */
6
+ /* eslint consistent-return:warn */
7
+ /* eslint import/no-dynamic-require:warn */
8
+ /* eslint no-underscore-dangle: [2, { "allow": ["_id"] }] */
9
+ /* eslint no-unused-vars:warn */
10
+ /* eslint no-use-before-define:warn */
11
+ /* eslint prefer-destructuring:warn */
12
+ /* eslint prefer-object-spread:warn */
13
+ /* eslint no-param-reassign:warn */
14
+
15
+ /* NodeJS internal API utilities */
16
+ const uuid = require('uuid');
17
+ const os = require('os');
18
+ const fs = require('fs');
19
+ const jwt = require('jsonwebtoken');
20
+
21
+ /* Fetch in the other needed components */
22
+ const path = require('path');
23
+ const https = require('https');
24
+ const http = require('http');
25
+ const cookieHandler = require('cookie');
26
+ const jsonQuery = require('json-query');
27
+ const xml2js = require('xml2js');
28
+ const querystring = require('querystring');
29
+ const HttpsProxyAgent = require('https-proxy-agent');
30
+ const SocksProxyAgent = require('socks-proxy-agent');
31
+ const AsyncLockCl = require('async-lock');
32
+ const FormData = require('form-data');
33
+
34
+ const ThrottleCl = require(path.join(__dirname, '/throttle.js'));
35
+
36
+ const allowFailover = 'AD.300';
37
+ const noFailover = 'AD.500';
38
+ let transUtilInst = null;
39
+ let propUtilInst = null;
40
+ let dbUtilInst = null;
41
+
42
+ // Global Variables - From Properties
43
+ let props = {};
44
+ let host = null;
45
+ let port = null;
46
+ let basepath = null;
47
+ let version = null;
48
+ let authMethod = null;
49
+ let authField = null;
50
+ let authFormat = null;
51
+ let authLogging = false;
52
+ let username = null;
53
+ let password = null;
54
+ let clientId = null;
55
+ let clientSecret = null;
56
+ let grantType = null;
57
+ let staticToken = null;
58
+ let tokenUserField = 'username';
59
+ let tokenPwdField = 'password';
60
+ let tokenResField = 'token';
61
+ let tokenTimeout = -1;
62
+ let tokenError = 401;
63
+ let tokenPath = null;
64
+ let tokenCache = 'local';
65
+ const tokenList = [];
66
+ const tokenlock = 0;
67
+ let stub = false;
68
+ let protocol = 'http';
69
+ let healthcheckpath = null;
70
+ let throttleEnabled = false;
71
+ let numberPhs = 1;
72
+ let ecdhAuto = false;
73
+ let sslEnabled = false;
74
+ let sslAcceptInvalid = false;
75
+ let sslCAFile = null;
76
+ let sslKeyFile = null;
77
+ let sslCertFile = null;
78
+ let sslCiphers = null;
79
+ let secureProtocol = null;
80
+ let proxyEnabled = false;
81
+ let proxyHost = null;
82
+ let proxyPort = null;
83
+ let proxyProtocol = 'http';
84
+ let proxyUser = null;
85
+ let proxyPassword = null;
86
+ let numRetries = 3;
87
+ let numRedirects = 0;
88
+ let limitRetryError = [0];
89
+ let attemptTimeout = 5000;
90
+ let healthcheckOnTimeout = true;
91
+ let globalRequest = null;
92
+ let archiving = false;
93
+ let returnRequest = false;
94
+ let healthy = false;
95
+ const healthlock = 0;
96
+ let healthcheck = false;
97
+ const healthchecklock = 0;
98
+
99
+ // Other global variables
100
+ let id = null;
101
+ let phInstance = null;
102
+ let requestId = 0;
103
+ let archiveColl = '_results';
104
+ const NS_PER_SEC = 1e9;
105
+ let throttleEng = null;
106
+ let tlock = null;
107
+ let hlock = null;
108
+ let hclock = null;
109
+
110
+ let crest = null;
111
+ let cacheHHead = null;
112
+ let cacheHSchema = null;
113
+ let cacheHPay = null;
114
+
115
+ /* CONNECTOR ENGINE INTERNAL FUNCTIONS */
116
+ /** Wait for adapter-mongo to be available.
117
+ * @summary adapter may load before adapter-mongo but it requires UPDATE: test if dbUtil object can connect.
118
+ * mongo to be available before. This function loops until mongo is available.
119
+ */
120
+ function waitForMongo() {
121
+ const origin = `${id}-connectorRest-waitForMongo`;
122
+ log.trace(origin);
123
+
124
+ return new Promise((resolve, reject) => {
125
+ const INTERVAL = 5000;
126
+ let count = 0;
127
+
128
+ // Run this interval until we can connect to the Mongo Adapter
129
+ const intervalObject = setInterval(() => {
130
+ count += INTERVAL / 1000;
131
+
132
+ // try to see if mongo is available
133
+ return dbUtilInst.connect((alive, db) => {
134
+ if (!alive) {
135
+ log.warn(`${origin}: No database connections available`);
136
+ if (count === 100 * (INTERVAL / 1000)) {
137
+ log.warn(`${origin}: attempted mongo enough times - will be saving to local JSONs`);
138
+ resolve();
139
+ }
140
+ } else {
141
+ log.info(`${origin}: dbUtil reports a status = success will now be able to connect.`);
142
+ db.close();
143
+ resolve();
144
+ clearInterval(intervalObject);
145
+ }
146
+ log.info(`${origin}: Waiting for dbUtil to be available. Seconds passed: ${count}.`);
147
+ });
148
+ }, INTERVAL);
149
+ });
150
+ }
151
+
152
+ /** Wait for system to be available.
153
+ * @summary assumes System has gone down and we are not able to connect
154
+ * to it and receive any data
155
+ */
156
+ function waitForSystem(callProperties) {
157
+ const origin = `${id}-connectorRest-waitForSystem`;
158
+ log.trace(origin);
159
+
160
+ return new Promise((resolve, reject) => {
161
+ // if not running healthchecks on timeout just return
162
+ if (callProperties && callProperties.host) {
163
+ log.debug(`${origin}: skipping healthcheck after abort based on changed host`);
164
+ resolve();
165
+ } else if (callProperties && callProperties.request && Object.hasOwnProperty.call(callProperties.request, 'healthcheck_on_timeout')) {
166
+ if (!callProperties.request.healthcheck_on_timeout) {
167
+ log.debug(`${origin}: skipping healthcheck after abort based on call propertues`);
168
+ resolve();
169
+ }
170
+ } else if (!healthcheckOnTimeout) {
171
+ log.debug(`${origin}: skipping healthcheck after abort based on configuration`);
172
+ resolve();
173
+ }
174
+
175
+ let useTimeout = attemptTimeout;
176
+ if (callProperties && callProperties.request && callProperties.request.attempt_timeout) {
177
+ useTimeout = callProperties.request.attempt_timeout;
178
+ }
179
+
180
+ // Find out if currently healthy
181
+ hlock.acquire(healthlock, (doneH) => {
182
+ doneH(healthy);
183
+ }, (retH) => {
184
+ // healthy
185
+ if (retH) {
186
+ log.debug(`${origin}: skipping healthcheck since another command has executed successfully`);
187
+ resolve();
188
+ } else {
189
+ // unhealthy
190
+ let count = 0;
191
+
192
+ // Run this interval until we can connect to the System
193
+ const intervalObject = setInterval(() => {
194
+ count += useTimeout / 1000;
195
+
196
+ // Lock the healthcheck so only one healthcheck at a time
197
+ hclock.acquire(healthchecklock, (doneHC) => {
198
+ // if not already running
199
+ if (!healthcheck) {
200
+ healthcheck = true;
201
+ doneHC(true);
202
+ } else {
203
+ doneHC(false);
204
+ }
205
+ }, (retHC) => {
206
+ // need to run healthcheck
207
+ if (retHC) {
208
+ crest.healthCheck(cacheHSchema, cacheHPay, cacheHHead, callProperties, (hc, error) => {
209
+ if (hc) {
210
+ if (hc === 'fail') {
211
+ log.error(`${origin}: healthcheck reports a status = fail with error ${JSON.stringify(error)}`);
212
+
213
+ if (count === 100 * (useTimeout / 1000)) {
214
+ log.error(`${origin}: attempted healthcheck enough times - failing`);
215
+
216
+ // turn healthcheck false - since no longer active
217
+ hclock.acquire(healthchecklock, (doneHC2) => {
218
+ healthcheck = false;
219
+ doneHC2(true);
220
+ }, (retHC2) => {
221
+ reject();
222
+ });
223
+ }
224
+ } else if (hc === 'success') {
225
+ log.info(`${origin}: healthcheck reports a status = success. adapter will now restart processing.`);
226
+
227
+ // turn healthcheck false - since no longer active
228
+ hclock.acquire(healthchecklock, (doneHC3) => {
229
+ healthcheck = false;
230
+ doneHC3(true);
231
+ }, (retHC3) => {
232
+ resolve();
233
+ clearInterval(intervalObject);
234
+ });
235
+ }
236
+ }
237
+
238
+ log.info(`${origin}: Waiting for system to be available. Seconds passed: ${count}.`);
239
+ });
240
+ }
241
+ });
242
+ }, useTimeout);
243
+ }
244
+ });
245
+ });
246
+ }
247
+
248
+ /*
249
+ * INTERNAL FUNCTION: Get the best match for the moc k data response
250
+ */
251
+ function matchMock(uriPath, method, type, mockresponses, respDatatype) {
252
+ // Go through the mock data keys to find the proper data to return
253
+ for (let p = 0; p < mockresponses.length; p += 1) {
254
+ // is this the mock data for this call
255
+ if (Object.hasOwnProperty.call(mockresponses[p], 'name')
256
+ && uriPath === mockresponses[p].name) {
257
+ if (Object.hasOwnProperty.call(mockresponses[p], 'method')
258
+ && method.toUpperCase() === mockresponses[p].method.toUpperCase()) {
259
+ if (Object.hasOwnProperty.call(mockresponses[p], 'type')
260
+ && type.toUpperCase() === mockresponses[p].type.toUpperCase()) {
261
+ // This is the mock data we really want as it best matches the request
262
+ const specificResp = {
263
+ status: 'success',
264
+ code: 200,
265
+ response: {}
266
+ };
267
+
268
+ if (Object.hasOwnProperty.call(mockresponses[p], 'response')) {
269
+ // if a status is defined in the response, use it
270
+ if (Object.hasOwnProperty.call(mockresponses[p].response, 'response')
271
+ && Object.hasOwnProperty.call(mockresponses[p].response, 'status')) {
272
+ specificResp.code = mockresponses[p].response.status;
273
+
274
+ if (respDatatype && respDatatype.toUpperCase() === 'XML2JSON') {
275
+ specificResp.response = mockresponses[p].response.response;
276
+ } else {
277
+ specificResp.response = JSON.stringify(mockresponses[p].response.response);
278
+ }
279
+ } else if (respDatatype && respDatatype.toUpperCase() === 'XML2JSON') {
280
+ // if no response field, assume that the entire data is the response
281
+ specificResp.response = mockresponses[p].response;
282
+ } else {
283
+ specificResp.response = JSON.stringify(mockresponses[p].response);
284
+ }
285
+ }
286
+
287
+ return specificResp;
288
+ }
289
+ }
290
+ }
291
+ }
292
+
293
+ return null;
294
+ }
295
+
296
+ /*
297
+ * INTERNAL FUNCTION: recursively inspect body data if heirarchical
298
+ */
299
+ function checkBodyData(uriPath, method, reqBdObj, mockresponses, respDatatype) {
300
+ let specificResp = null;
301
+
302
+ if (reqBdObj) {
303
+ const reqBKeys = Object.keys(reqBdObj);
304
+
305
+ // go through each key in the passed in object
306
+ for (let k = 0; k < reqBKeys.length; k += 1) {
307
+ const bVal = reqBdObj[reqBKeys[k]];
308
+
309
+ if (bVal !== undefined && bVal !== null && bVal !== '') {
310
+ // if the field is an object and not an array - recursively call with the new field value
311
+ if (typeof bVal === 'object' && !Array.isArray(bVal)) {
312
+ specificResp = checkBodyData(uriPath, method, bVal, mockresponses, respDatatype);
313
+ } else if (Array.isArray(bVal) && bVal.length > 0 && (typeof bVal[0] === 'object')) {
314
+ // if the field is an array containing objects - recursively call with each object in the array
315
+ for (let a = 0; a < bVal.length; a += 1) {
316
+ specificResp = checkBodyData(uriPath, method, bVal[a], mockresponses, respDatatype);
317
+
318
+ // if the data match is found break the for loop - will return below
319
+ if (specificResp !== null) {
320
+ break;
321
+ }
322
+ }
323
+ } else if (Array.isArray(bVal)) {
324
+ // if an array of data, need to check each data in the array
325
+ for (let a = 0; a < bVal.length; a += 1) {
326
+ // should match fieldName-fieldValue
327
+ const compStr = `${reqBKeys[k]}-${bVal[a]}`;
328
+ specificResp = matchMock(uriPath, method, compStr, mockresponses, respDatatype);
329
+
330
+ // if the data match is found break the for loop - will return below
331
+ if (specificResp !== null) {
332
+ break;
333
+ }
334
+ }
335
+ } else {
336
+ // should match fieldName-fieldValue
337
+ const compStr = `${reqBKeys[k]}-${bVal}`;
338
+ specificResp = matchMock(uriPath, method, compStr, mockresponses, respDatatype);
339
+ }
340
+
341
+ if (specificResp !== null) {
342
+ break;
343
+ }
344
+ }
345
+ }
346
+ }
347
+ return specificResp;
348
+ }
349
+
350
+ /*
351
+ * INTERNAL FUNCTION: return stub results based on the path and method
352
+ */
353
+ function returnStub(request, entitySchema, callProperties) {
354
+ const origin = `${id}-connectorRest-returnStub`;
355
+ log.trace(origin);
356
+ const uriPath = request.origPath;
357
+ const method = request.header.method.toUpperCase();
358
+ const reqBody = request.body;
359
+ const reqPath = request.header.path;
360
+
361
+ // these logs are very useful when debugging - however there is the potential for credentials to be exposed.
362
+ if (authLogging) {
363
+ log.debug(`FULL STUB REQUEST: ${JSON.stringify(request.header)}`);
364
+ log.debug(`FULL STUB BODY: ${request.body}`);
365
+ }
366
+
367
+ const callResp = {
368
+ status: 'success',
369
+ code: 200,
370
+ response: '{}'
371
+ };
372
+
373
+ // if there is no entity schema this should be a healthcheck
374
+ if (!entitySchema) {
375
+ log.error(`${origin}: healthcheck? no entity schema - no mock data to return`);
376
+ return callResp;
377
+ }
378
+
379
+ log.trace(`${origin}: ${JSON.stringify(entitySchema)}`);
380
+
381
+ // if there are no mock responses defined in the entity schema, stubbing will not work
382
+ if (!Object.hasOwnProperty.call(entitySchema, 'mockresponses')) {
383
+ callResp.status = 'failure';
384
+ callResp.code = 400;
385
+ callResp.message = 'no mock data defined in entity scheme - no mock data to return';
386
+ delete callResp.response;
387
+
388
+ log.error(`${origin}: ${callResp.message}`);
389
+ return callResp;
390
+ }
391
+
392
+ const mockresponses = entitySchema.mockresponses;
393
+ let specificResp = null;
394
+
395
+ // if there is a request body, see if there is something that matches a specific input
396
+ if (reqBody && (!entitySchema || !entitySchema.requestDatatype
397
+ || entitySchema.requestDatatype.toUpperCase() === 'JSON' || entitySchema.requestDatatype.toUpperCase() === 'URLENCODE')) {
398
+ let reqBdObj = null;
399
+ if (entitySchema && entitySchema.requestDatatype && entitySchema.requestDatatype.toUpperCase() === 'URLENCODE') {
400
+ reqBdObj = querystring.parse(reqBody.trim());
401
+ } else {
402
+ reqBdObj = JSON.parse(reqBody.trim());
403
+ }
404
+
405
+ specificResp = checkBodyData(uriPath, method, reqBdObj, mockresponses, entitySchema.responseDatatype);
406
+
407
+ if (specificResp !== null) {
408
+ return specificResp;
409
+ }
410
+ }
411
+
412
+ // if there are path variables, see if there is something that matches a specific variable
413
+ if (uriPath.indexOf('{pathv') >= 0) {
414
+ const uriTemp = uriPath.split('?');
415
+ const actTemp = reqPath.split('?');
416
+ uriTemp[0] = uriTemp[0].replace(/{pathv/g, '/{pathv');
417
+ uriTemp[0] = uriTemp[0].replace(/{version/g, '/{version');
418
+ uriTemp[0] = uriTemp[0].replace(/{base/g, '/{base');
419
+ uriTemp[0] = uriTemp[0].replace(/\/\//g, '/');
420
+
421
+ // remove basepath from both paths
422
+ // get rid of base path from the uriPath
423
+ uriTemp[0] = uriTemp[0].replace(/\/{base_path}/g, '');
424
+ // if a base path was added to the request, remove it
425
+ if (callProperties && callProperties.base_path && callProperties.base_path !== '/') {
426
+ actTemp[0] = actTemp[0].replace(callProperties.base_path, '');
427
+ } else if (basepath && basepath !== '/') {
428
+ actTemp[0] = actTemp[0].replace(basepath, '');
429
+ }
430
+
431
+ // remove version from both paths
432
+ // get rid of version from the uriPath
433
+ uriTemp[0] = uriTemp[0].replace(/\/{version}/g, '');
434
+ // if a version was added to the request, remove it
435
+ if (callProperties && callProperties.version) {
436
+ actTemp[0] = actTemp[0].replace(`/${callProperties.version}`, '');
437
+ } else if (version && version !== '/') {
438
+ actTemp[0] = actTemp[0].replace(`/${version}`, '');
439
+ }
440
+
441
+ const uriArray = uriTemp[0].split('/');
442
+ const actArray = actTemp[0].split('/');
443
+
444
+ // the number of items in both should be the same
445
+ if (uriArray.length === actArray.length) {
446
+ for (let i = 0; i < uriArray.length; i += 1) {
447
+ if (uriArray[i].indexOf('{pathv') >= 0) {
448
+ specificResp = matchMock(uriPath, method, actArray[i], mockresponses, entitySchema.responseDatatype);
449
+
450
+ if (specificResp !== null) {
451
+ return specificResp;
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }
457
+
458
+ // if there are queiries or options, see if there is something that matches a specific input
459
+ if (reqPath.indexOf('?') >= 0) {
460
+ const queries = reqPath.substring(reqPath.indexOf('?') + 1);
461
+ const queryArr = queries.split('&');
462
+
463
+ // go through each query parameter
464
+ for (let q = 0; q < queryArr.length; q += 1) {
465
+ let qval = queryArr[q];
466
+ if (qval !== undefined && qval !== null && qval !== '') {
467
+ // stringifies it - in case it was not a string
468
+ qval = `${qval}`;
469
+ specificResp = matchMock(uriPath, method, qval, mockresponses, entitySchema.responseDatatype);
470
+
471
+ if (specificResp !== null) {
472
+ return specificResp;
473
+ }
474
+ }
475
+ }
476
+ }
477
+
478
+ // if there is a request body, see if there is a specific response for body
479
+ if (reqBody) {
480
+ specificResp = matchMock(uriPath, method, 'WITHBODY', mockresponses, entitySchema.responseDatatype);
481
+
482
+ if (specificResp !== null) {
483
+ return specificResp;
484
+ }
485
+ }
486
+
487
+ // if there are path variables, see if there is a specific response for path vars
488
+ if (uriPath.indexOf('{pathv') >= 0) {
489
+ const uriTemp = uriPath.split('?');
490
+ const actTemp = reqPath.split('?');
491
+ uriTemp[0] = uriTemp[0].replace(/{pathv/g, '/{pathv');
492
+ uriTemp[0] = uriTemp[0].replace(/{version/g, '/{version');
493
+ uriTemp[0] = uriTemp[0].replace(/{base_path}/g, '/{base_path}');
494
+ uriTemp[0] = uriTemp[0].replace(/\/\//g, '/');
495
+
496
+ // remove basepath from both paths
497
+ // get rid of base path from the uriPath
498
+ uriTemp[0] = uriTemp[0].replace(/\/{base_path}/g, '');
499
+ // if a base path was added to the request, remove it
500
+ if (callProperties && callProperties.base_path && callProperties.base_path !== '/') {
501
+ actTemp[0] = actTemp[0].replace(callProperties.base_path, '');
502
+ } else if (basepath && basepath !== '/') {
503
+ actTemp[0] = actTemp[0].replace(basepath, '');
504
+ }
505
+
506
+ // remove version from both paths
507
+ // get rid of version from the uriPath
508
+ uriTemp[0] = uriTemp[0].replace(/\/{version}/g, '');
509
+ // if a version was added to the request, remove it
510
+ if (callProperties && callProperties.version) {
511
+ actTemp[0] = actTemp[0].replace(`/${callProperties.version}`, '');
512
+ } else if (version && version !== '/') {
513
+ actTemp[0] = actTemp[0].replace(`/${version}`, '');
514
+ }
515
+
516
+ const uriArray = uriTemp[0].split('/');
517
+ const actArray = actTemp[0].split('/');
518
+
519
+ // the number of items in both should be the same
520
+ if (uriArray.length === actArray.length) {
521
+ let cnt = 1;
522
+ for (let i = 0; i < uriArray.length; i += 1) {
523
+ if (uriArray[i].indexOf('{pathv') >= 0) {
524
+ specificResp = matchMock(uriPath, method, `WITHPATHV${cnt}`, mockresponses, entitySchema.responseDatatype);
525
+
526
+ if (specificResp !== null) {
527
+ return specificResp;
528
+ }
529
+ cnt += 1;
530
+ }
531
+ }
532
+ }
533
+ }
534
+
535
+ // if there are queiries or options, see if there is a specific response for query or options
536
+ if (uriPath.indexOf('?') >= 0) {
537
+ specificResp = matchMock(uriPath, method, 'WITHQUERY', mockresponses, entitySchema.responseDatatype);
538
+
539
+ if (specificResp !== null) {
540
+ return specificResp;
541
+ }
542
+
543
+ specificResp = matchMock(uriPath, method, 'WITHOPTIONS', mockresponses, entitySchema.responseDatatype);
544
+
545
+ if (specificResp !== null) {
546
+ return specificResp;
547
+ }
548
+ }
549
+
550
+ specificResp = matchMock(uriPath, method, 'DEFAULT', mockresponses, entitySchema.responseDatatype);
551
+
552
+ if (specificResp !== null) {
553
+ return specificResp;
554
+ }
555
+
556
+ callResp.status = 'failure';
557
+ callResp.code = 400;
558
+ callResp.message = `no mock data for ${uriPath} - no mock data to return`;
559
+ delete callResp.response;
560
+
561
+ log.error(`${origin}: ${callResp.message}`);
562
+ return callResp;
563
+ }
564
+
565
+ /*
566
+ * INTERNAL FUNCTION: makeRequest makes the actual call to System
567
+ */
568
+ function makeRequest(request, entitySchema, callProperties, startTrip, attempt, callback) {
569
+ const origin = `${id}-connectorRest-makeRequest`;
570
+ log.trace(origin);
571
+ let myTimeout = attemptTimeout;
572
+ const roundTTime = startTrip || process.hrtime();
573
+ const stTime = new Date().getTime();
574
+
575
+ // remove any double slashes that may be in the path - can happen if base path is / or ends in a /
576
+ request.header.path = request.header.path.replace(/\/\//g, '/');
577
+
578
+ // if there is a timeout from the action (schema) use it instead
579
+ if (entitySchema && entitySchema.timeout && entitySchema.timeout > 0) {
580
+ myTimeout = entitySchema.timeout;
581
+ }
582
+ if (callProperties && callProperties.request && callProperties.request.attempt_timeout) {
583
+ myTimeout = callProperties.request.attempt_timeout;
584
+ }
585
+
586
+ // if we need ecdhCurve set to auto
587
+ if (callProperties && callProperties.ssl && Object.hasOwnProperty.call(callProperties.ssl, 'ecdhCurve')) {
588
+ if (callProperties.ssl.ecdhCurve) {
589
+ request.header.ecdhCurve = 'auto';
590
+ }
591
+ } else if (ecdhAuto) {
592
+ request.header.ecdhCurve = 'auto';
593
+ }
594
+
595
+ // STUB CODE - this is good for an initial test - set stub in Pronghorn property file to true
596
+ if (callProperties && Object.hasOwnProperty.call(callProperties, 'stub')) {
597
+ if (callProperties.stub) {
598
+ return callback(returnStub(request, entitySchema, callProperties));
599
+ }
600
+ } else if (stub) {
601
+ return callback(returnStub(request, entitySchema, callProperties));
602
+ }
603
+ let useProt = http;
604
+ let protStr = 'http';
605
+
606
+ try {
607
+ // determine proper protocol for primary request
608
+ if (callProperties && callProperties.protocol) {
609
+ if (callProperties.protocol === 'https') {
610
+ log.debug(`${origin}: Switching to https protocol`);
611
+ useProt = https;
612
+ protStr = 'https';
613
+ }
614
+ } else if (entitySchema && entitySchema.sso && entitySchema.sso.protocol) {
615
+ // this is for single signon - should we limit to token???
616
+ if (entitySchema.sso.protocol === 'https') {
617
+ log.debug(`${origin}: Switching to https protocol`);
618
+ useProt = https;
619
+ protStr = 'https';
620
+ }
621
+ } else if (protocol === 'https') {
622
+ log.debug(`${origin}: Switching to https protocol`);
623
+ useProt = https;
624
+ protStr = 'https';
625
+ }
626
+
627
+ // add the proxy information to the request
628
+ if (callProperties && callProperties.proxy && Object.hasOwnProperty.call(callProperties.proxy, 'enabled')) {
629
+ if (callProperties.proxy.enabled) {
630
+ let proxy = `${callProperties.proxy.protocol}://${callProperties.proxy.host}:${callProperties.proxy.port}`;
631
+ if (callProperties.proxy.username && callProperties.proxy.password) {
632
+ proxy = `${callProperties.proxy.protocol}://${callProperties.proxy.username}:${callProperties.proxy.password}@${callProperties.proxy.host}:${callProperties.proxy.port}`;
633
+ }
634
+ request.header.agent = new HttpsProxyAgent(proxy);
635
+
636
+ if (callProperties.proxy.protocol.indexOf('socks') >= 0) {
637
+ request.header.agent = new SocksProxyAgent(proxy);
638
+ }
639
+ }
640
+ } else if (proxyEnabled) {
641
+ let proxy = `${proxyProtocol}://${proxyHost}:${proxyPort}`;
642
+ if (proxyUser && proxyPassword) {
643
+ proxy = `${callProperties.proxy.protocol}://${proxyUser}:${proxyPassword}@${callProperties.proxy.host}:${callProperties.proxy.port}`;
644
+ }
645
+ request.header.agent = new HttpsProxyAgent(proxy);
646
+
647
+ if (proxyProtocol.indexOf('socks') >= 0) {
648
+ request.header.agent = new SocksProxyAgent(proxy);
649
+ }
650
+ }
651
+
652
+ // if the request uses form-data type, need to add it to the headers
653
+ const formData = new FormData();
654
+ if (entitySchema.requestDatatype.toUpperCase() === 'FORM') {
655
+ // need to convert request.body back to JSON
656
+ let mybody = request.body;
657
+ if (typeof mybody === 'string') {
658
+ mybody = JSON.parse(request.body);
659
+ }
660
+ // add the data for each field into the form
661
+ const mykeys = Object.keys(mybody);
662
+ for (let k = 0; k < mykeys.length; k += 1) {
663
+ if (mykeys[k] === 'file') {
664
+ let fileVal = mybody[mykeys[k]];
665
+ if (fileVal.indexOf('@') === 0) {
666
+ // if there are multiple parts - first part is file, other part can be name
667
+ const filePart = fileVal.split(';');
668
+ let fileName = null;
669
+ fileVal = fs.readFileSync(`node_modules/@itentialopensource/adapter-forwardnetworks/uploads/${filePart[0].substring(1)}`);
670
+
671
+ // see if we have a filename that we should add to the formdata
672
+ for (let p = 1; p < filePart.length; p += 1) {
673
+ if (filePart[p].indexOf('name=') === 0) {
674
+ fileName = filePart[p].substring(5);
675
+ }
676
+ }
677
+ if (fileName) {
678
+ // with filename
679
+ log.debug(`APPENDING: ${mykeys[k]}, WITH VALUE ${fileVal}, AND NAME ${fileName}`);
680
+ formData.append(mykeys[k], fileVal, fileName);
681
+ } else {
682
+ // with read in file but no file name
683
+ log.debug(`APPENDING: ${mykeys[k]}, WITH VALUE ${fileVal}`);
684
+ formData.append(mykeys[k], fileVal);
685
+ }
686
+ } else {
687
+ // no file or file name just contents - original
688
+ log.debug(`APPENDING: ${mykeys[k]}, WITH VALUE ${fileVal}`);
689
+ formData.append(mykeys[k], fileVal);
690
+ }
691
+ } else {
692
+ log.debug(`APPENDING: ${mykeys[k]}, WITH VALUE ${mybody[mykeys[k]]}`);
693
+ formData.append(mykeys[k], mybody[mykeys[k]]);
694
+ }
695
+ }
696
+ // get the new headers
697
+ request.header.headers = Object.assign(request.header.headers, formData.getHeaders());
698
+ // seems to not use Content-Type so fix that
699
+ if (request.header.headers['content-type']) {
700
+ request.header.headers['Content-Type'] = request.header.headers['content-type'];
701
+ }
702
+ delete request.header.headers['Content-length'];
703
+ log.debug(`${origin}: Changing Headers for formData: ${JSON.stringify(request.header.headers)}`);
704
+ }
705
+
706
+ // these logs are very useful when debugging - however there is the potential for credentials to be exposed.
707
+ if (authLogging) {
708
+ log.debug(`FULL REQUEST: ${JSON.stringify(request.header)}`);
709
+ log.debug(`FULL BODY: ${request.body}`);
710
+ }
711
+
712
+ // make the call to System
713
+ const httpRequest = useProt.request(request.header, (res) => {
714
+ let respStr = '';
715
+ res.setEncoding('utf8');
716
+
717
+ // process data from response
718
+ res.on('data', (replyData) => {
719
+ respStr += replyData;
720
+ });
721
+
722
+ // process end of response
723
+ res.on('end', () => {
724
+ const tripDiff = process.hrtime(roundTTime);
725
+ const tripEnd = `${Math.round(((tripDiff[0] * NS_PER_SEC) + tripDiff[1]) / 1000000)}ms`;
726
+ const callResp = {
727
+ status: 'success',
728
+ code: res.statusCode,
729
+ headers: res.headers,
730
+ response: respStr,
731
+ request: {
732
+ protocol: protStr,
733
+ host: request.header.hostname,
734
+ port: request.header.port,
735
+ uri: request.header.path,
736
+ method: request.header.method,
737
+ payload: request.body
738
+ },
739
+ redirects: attempt,
740
+ tripTime: tripEnd
741
+ };
742
+
743
+ // if there was a cookie or biscotti returned, need to set cookie on the new request
744
+ if (request.header.headers) {
745
+ const headKeys = Object.keys(request.header.headers);
746
+ for (let k = 0; k < headKeys.length; k += 1) {
747
+ if (headKeys[k].toLowerCase() === 'cookie') {
748
+ callResp.requestCookie = request.header.headers[headKeys[k]];
749
+ if (!callResp.headers['set-cookie']) {
750
+ callResp.headers['set-cookie'] = request.header.headers[headKeys[k]];
751
+ }
752
+ }
753
+ if (headKeys[k].toLowerCase() === 'x-biscotti') {
754
+ if (!callResp.headers['x-biscotti']) {
755
+ callResp.headers['x-biscotti'] = request.header.headers[headKeys[k]];
756
+ }
757
+ }
758
+ }
759
+ }
760
+
761
+ // handling redirects
762
+ let useRedirect = numRedirects;
763
+ if (callProperties && callProperties.request && typeof callProperties.request.number_redirects === 'number') {
764
+ useRedirect = Number(callProperties.request.number_redirects);
765
+ }
766
+
767
+ if (attempt < useRedirect && res.statusCode >= 300 && res.statusCode <= 308 && res.headers.location) {
768
+ // retries = 0 go here, retries = 1 it doesn't
769
+ const newProp = Object.assign({}, callProperties);
770
+ const newRequest = Object.assign({}, request);
771
+ const nextAtt = attempt + 1;
772
+
773
+ // if there is a protocol on the new location use it
774
+ if (res.headers.location.indexOf('://') >= 0) {
775
+ const protSplit = res.headers.location.split('://');
776
+ newProp.protocol = protSplit[0];
777
+
778
+ if (protSplit.length > 1) {
779
+ const pathSplit = protSplit[1].split('/');
780
+
781
+ // grab the path
782
+ if (pathSplit.length > 1) {
783
+ newRequest.header.path = `/${protSplit[1].substring(protSplit[1].indexOf('/'))}`;
784
+ } else {
785
+ newRequest.header.path = '';
786
+ }
787
+
788
+ const portSplit = pathSplit[0].split(':');
789
+
790
+ // grab the hostname
791
+ newRequest.header.hostname = `${portSplit[0]}`;
792
+
793
+ // grab the port
794
+ if (portSplit.length > 1) {
795
+ newRequest.header.port = portSplit[1];
796
+ } else if (newProp.protocol === 'https') {
797
+ newRequest.header.port = 443;
798
+ } else {
799
+ newRequest.header.port = 80;
800
+ }
801
+ }
802
+ } else {
803
+ // must just have the path - no protocol, host or port
804
+ newRequest.header.path = res.headers.location;
805
+ }
806
+
807
+ // if there was a cookie or biscotti returned, need to set cookie on the new request
808
+ if (res.headers) {
809
+ const headKeys = Object.keys(res.headers);
810
+ for (let k = 0; k < headKeys.length; k += 1) {
811
+ if (headKeys[k].toLowerCase() === 'set-cookie') {
812
+ newRequest.header.headers.Cookie = res.headers[headKeys[k]];
813
+ }
814
+ if (headKeys[k].toLowerCase() === 'x-biscotti') {
815
+ newRequest.header.headers['x-biscotti'] = res.headers[headKeys[k]];
816
+ }
817
+ }
818
+ }
819
+
820
+ // make the modified request
821
+ log.debug(`${origin}: REDIRECTING TO - ${res.headers.location}`);
822
+ return makeRequest(newRequest, entitySchema, newProp, roundTTime, nextAtt, callback);
823
+ }
824
+
825
+ // callResp.timeouts = myTimeout;
826
+ // turn healthy true - since valid return
827
+ hlock.acquire(healthlock, (doneH2) => {
828
+ healthy = true;
829
+ doneH2(true);
830
+ }, (retH2) => {
831
+ log.debug(`${origin}: CALL RETURN ${JSON.stringify(callResp)}`);
832
+ useProt = undefined;
833
+ callResp.reqHdr = request.header.headers;
834
+ return callback(callResp);
835
+ });
836
+ });
837
+ });
838
+
839
+ // handle post error
840
+ httpRequest.on('error', (cerror) => {
841
+ const tripDiff = process.hrtime(roundTTime);
842
+ const tripEnd = `${Math.round(((tripDiff[0] * NS_PER_SEC) + tripDiff[1]) / 1000000)}ms`;
843
+ const callResp = {
844
+ status: 'failure',
845
+ code: -1,
846
+ message: cerror,
847
+ request: {
848
+ protocol: protStr,
849
+ host: request.header.hostname,
850
+ port: request.header.port,
851
+ uri: request.header.path,
852
+ method: request.header.method
853
+ },
854
+ redirects: attempt,
855
+ tripTime: tripEnd
856
+ };
857
+ let didTimeout = 0;
858
+
859
+ // if the connection was reset - need to determine if client or server cut it
860
+ if (cerror.code === 'ECONNRESET') {
861
+ const endTime = new Date().getTime();
862
+
863
+ // see if this is a client timeout
864
+ if (endTime - stTime >= myTimeout) {
865
+ callResp.code = -2;
866
+ didTimeout += 1;
867
+ }
868
+ }
869
+
870
+ // turn healthy false - since error return
871
+ hlock.acquire(healthlock, (doneH2) => {
872
+ healthy = false;
873
+ doneH2(true);
874
+ }, (retH2) => {
875
+ log.debug(`${origin}: ERROR RETURN ${JSON.stringify(callResp)}`);
876
+ useProt = undefined;
877
+ callResp.timeouts = didTimeout;
878
+ callResp.reqHdr = request.header.headers;
879
+ return callback(callResp);
880
+ });
881
+ });
882
+
883
+ httpRequest.setTimeout(myTimeout, () => {
884
+ httpRequest.abort();
885
+ }, myTimeout);
886
+
887
+ // write to the http request data to the body if not a get (gets do not have data in the body)
888
+ if ((request.header.method !== 'GET' || entitySchema.sendGetBody)
889
+ && ((typeof request.body === 'object' && Object.keys(request.body).length > 0)
890
+ || (typeof request.body === 'string' && request.body !== '{}') || entitySchema.sendEmpty)) {
891
+ if (entitySchema.requestDatatype.toUpperCase() === 'FORM') {
892
+ // pass the formData to the request
893
+ log.debug(`${origin}: Sending request body through formData`);
894
+ log.debug(`Form Buffer: ${formData.getBuffer().toString()}`);
895
+ formData.pipe(httpRequest);
896
+ } else {
897
+ // pass the body to the request
898
+ log.debug(`${origin}: Sending request body normally`);
899
+ httpRequest.write(request.body);
900
+ }
901
+ }
902
+
903
+ // end the http request
904
+ httpRequest.end();
905
+ } catch (e) {
906
+ // handle any exception
907
+ const tripDiff = process.hrtime(roundTTime);
908
+ const tripEnd = `${Math.round(((tripDiff[0] * NS_PER_SEC) + tripDiff[1]) / 1000000)}ms`;
909
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue during make request');
910
+ errorObj.metrics = {
911
+ code: 'translate_error',
912
+ redirects: attempt,
913
+ tripTime: tripEnd
914
+ };
915
+ errorObj.request = {
916
+ protocol: protStr,
917
+ host: request.header.hostname,
918
+ port: request.header.port,
919
+ uri: request.header.path,
920
+ method: request.header.method,
921
+ payload: request.body
922
+ };
923
+ return callback(null, errorObj);
924
+ }
925
+ }
926
+
927
+ /*
928
+ * INTERNAL FUNCTION: finds the token in the object that was returned
929
+ */
930
+ function findPrimaryTokenInResult(result, front, end) {
931
+ const origin = `${id}-connectorRest-findPrimaryTokenInResult`;
932
+ log.trace(origin);
933
+ let token = null;
934
+
935
+ if (!result) {
936
+ return token;
937
+ }
938
+
939
+ // if the result is a string, need to assume that is the token
940
+ if (typeof result === 'string') {
941
+ let modRes = result;
942
+ // if we need to pull out part of the string
943
+ if (front && result.indexOf(front) > 0) {
944
+ const stInd = result.indexOf(front) + front.length;
945
+ modRes = result.substring(stInd);
946
+ }
947
+ if (end && modRes.indexOf(end) > 0) {
948
+ modRes = modRes.substring(0, modRes.indexOf(end));
949
+ }
950
+ return modRes;
951
+ }
952
+
953
+ // if the token is in this object
954
+ if (result.token) {
955
+ // if we need to pull out part of the string
956
+ if (typeof result.token === 'string') {
957
+ let modRes = result.token;
958
+ if (front && modRes.indexOf(front) > 0) {
959
+ const stInd = modRes.indexOf(front) + front.length;
960
+ modRes = modRes.substring(stInd);
961
+ }
962
+ if (end && modRes.indexOf(end) > 0) {
963
+ modRes = modRes.substring(0, modRes.indexOf(end));
964
+ }
965
+ return modRes;
966
+ }
967
+
968
+ return result.token;
969
+ }
970
+
971
+ // need to check objects within this object
972
+ const keys = Object.keys(result);
973
+
974
+ for (let k = 0; k < keys.length; k += 1) {
975
+ if (typeof result[keys[k]] === 'object') {
976
+ const found = findPrimaryTokenInResult(result[keys[k]], front, end);
977
+
978
+ // if we found the token
979
+ if (found !== null) {
980
+ token = found;
981
+ break;
982
+ }
983
+ }
984
+ }
985
+
986
+ // return the found token
987
+ return token;
988
+ }
989
+
990
+ /*
991
+ * INTERNAL FUNCTION: finds the second token in the object that was returned
992
+ */
993
+ function findSecondaryTokenInResult(result, front, end) {
994
+ const origin = `${id}-connectorRest-findSecondaryTokenInResult`;
995
+ log.trace(origin);
996
+ let tokenp2 = null;
997
+
998
+ if (!result) {
999
+ return tokenp2;
1000
+ }
1001
+
1002
+ // if the result is a string, need to assume that is the second token
1003
+ if (typeof result === 'string') {
1004
+ let modRes = result;
1005
+ // if we need to pull out part of the string
1006
+ if (front && result.indexOf(front) > 0) {
1007
+ const stInd = result.indexOf(front) + front.length;
1008
+ modRes = result.substring(stInd);
1009
+ }
1010
+ if (end && modRes.indexOf(end) > 0) {
1011
+ modRes = modRes.substring(0, modRes.indexOf(end));
1012
+ }
1013
+ return modRes;
1014
+ }
1015
+
1016
+ // if the second token is in this object
1017
+ if (result.tokenp2) {
1018
+ // if we need to pull out part of the string
1019
+ if (typeof result.tokenp2 === 'string') {
1020
+ let modRes = result.tokenp2;
1021
+ if (front && modRes.indexOf(front) > 0) {
1022
+ const stInd = modRes.indexOf(front) + front.length;
1023
+ modRes = modRes.substring(stInd);
1024
+ }
1025
+ if (end && modRes.indexOf(end) > 0) {
1026
+ modRes = modRes.substring(0, modRes.indexOf(end));
1027
+ }
1028
+ return modRes;
1029
+ }
1030
+
1031
+ return result.tokenp2;
1032
+ }
1033
+
1034
+ // need to check objects within this object
1035
+ const keys = Object.keys(result);
1036
+
1037
+ for (let k = 0; k < keys.length; k += 1) {
1038
+ if (typeof result[keys[k]] === 'object') {
1039
+ const found = findSecondaryTokenInResult(result[keys[k]]);
1040
+
1041
+ // if we found the second token
1042
+ if (found !== null) {
1043
+ tokenp2 = found;
1044
+ break;
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ // return the found second token
1050
+ return tokenp2;
1051
+ }
1052
+
1053
+ /*
1054
+ * INTERNAL FUNCTION: finds the expiration in the object that was returned
1055
+ */
1056
+ function findExpireInResult(result) {
1057
+ const origin = `${id}-connectorRest-findExpireInResult`;
1058
+ log.trace(origin);
1059
+ let expire = null;
1060
+
1061
+ if (!result) {
1062
+ return expire;
1063
+ }
1064
+
1065
+ // if the expires is in this object
1066
+ if (result.expires) {
1067
+ return new Date(result.expires).getTime();
1068
+ }
1069
+
1070
+ // need to check objects within this object
1071
+ const keys = Object.keys(result);
1072
+
1073
+ for (let k = 0; k < keys; k += 1) {
1074
+ if (typeof result[k] === 'object') {
1075
+ const found = findExpireInResult(result[k]);
1076
+
1077
+ // if we found the expiration
1078
+ if (found !== null) {
1079
+ expire = new Date(result).getTime();
1080
+ break;
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+ // return the found expiration
1086
+ return expire;
1087
+ }
1088
+
1089
+ /*
1090
+ * INTERNAL FUNCTION: makes the request and processes the response
1091
+ * for the request to get the token
1092
+ */
1093
+ function getToken(reqPath, options, tokenSchema, bodyString, callProperties, callback) {
1094
+ const origin = `${id}-connectorRest-getToken`;
1095
+ log.trace(origin);
1096
+
1097
+ try {
1098
+ // no need for orig path since we handled the stub case
1099
+ const request = {
1100
+ header: options,
1101
+ body: bodyString,
1102
+ origPath: tokenSchema.entitypath
1103
+ };
1104
+
1105
+ // set stub if that is the mode we are in
1106
+ let useStub = false;
1107
+ if (callProperties && Object.hasOwnProperty.call(callProperties, 'stub')) {
1108
+ useStub = callProperties.stub;
1109
+ } else if (stub) {
1110
+ useStub = stub;
1111
+ }
1112
+
1113
+ // if there is a mock result, return that
1114
+ if (useStub && tokenSchema) {
1115
+ // get the data from the mock data file
1116
+ const tokenResp = returnStub(request, tokenSchema, callProperties);
1117
+
1118
+ // if the request failed, return the error
1119
+ if (tokenResp.code < 200 || tokenResp.code > 299) {
1120
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Authenticate', ['Token', tokenResp.code], null, null, null);
1121
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1122
+ return callback(null, errorObj);
1123
+ }
1124
+
1125
+ if (!tokenSchema.responseDatatype || tokenSchema.responseDatatype === 'JSON') {
1126
+ tokenResp.response = JSON.parse(tokenResp.response);
1127
+ }
1128
+ log.debug(`${origin}: ${JSON.stringify(tokenResp.response)}`);
1129
+
1130
+ // return the token from the token schema
1131
+ let translated = null;
1132
+ if (typeof tokenResp.response !== 'string') {
1133
+ translated = transUtilInst.mapFromOutboundEntity(tokenResp.response, tokenSchema.responseSchema);
1134
+ } else {
1135
+ translated = tokenResp.response;
1136
+ }
1137
+
1138
+ // if what we got back is an array, just return the first element
1139
+ // should only have one token!!!
1140
+ if (translated && Array.isArray(translated)) {
1141
+ translated[0].front = tokenSchema.responseSchema.properties.token.front;
1142
+ translated[0].end = tokenSchema.responseSchema.properties.token.end;
1143
+ return callback(translated[0]);
1144
+ }
1145
+
1146
+ // return the token that we find in the translated object
1147
+ translated.front = tokenSchema.responseSchema.properties.token.front;
1148
+ translated.end = tokenSchema.responseSchema.properties.token.end;
1149
+ return callback(translated);
1150
+ }
1151
+
1152
+ if (useStub || reqPath === tokenPath || (tokenSchema && reqPath === tokenSchema.entitypath)) {
1153
+ // do not make the call to return a token if the request is actually
1154
+ // to get a token. Getting a token to get a token -- that should go
1155
+ // direct to make request
1156
+ return callback({ token: 'faketoken' });
1157
+ }
1158
+
1159
+ log.debug(`${origin}: OPTIONS: ${JSON.stringify(options)}`);
1160
+
1161
+ // request the token
1162
+ return makeRequest(request, tokenSchema, callProperties, null, 0, (result, merror) => {
1163
+ if (merror) {
1164
+ return callback(null, merror);
1165
+ }
1166
+
1167
+ // if the request is a redirect, try to pull token but log a warning
1168
+ let handleTokenRedirect = false;
1169
+ if (result.code >= 300 && result.code <= 308 && numRedirects === 0) {
1170
+ log.warn(`${origin}: Going to attempt to get token from redirect message!`);
1171
+ handleTokenRedirect = true;
1172
+ }
1173
+
1174
+ // if the request failed, return the error
1175
+ if ((result.code < 200 || result.code > 299) && !handleTokenRedirect) {
1176
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Authenticate', ['Token', result.code], null, null, null);
1177
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1178
+ return callback(null, errorObj);
1179
+ }
1180
+
1181
+ if (!tokenSchema) {
1182
+ // if no token schema, can not determine what to return
1183
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Authenticate', ['Token', result.code], null, null, null);
1184
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1185
+ return callback(null, errorObj);
1186
+ }
1187
+
1188
+ // parse the token out of the result
1189
+ const currResult = {
1190
+ token: null,
1191
+ tokenp2: null,
1192
+ front: tokenSchema.responseSchema.properties.token.front,
1193
+ end: tokenSchema.responseSchema.properties.token.end
1194
+ };
1195
+
1196
+ // process primary token from header
1197
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1198
+ && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'HEADER') {
1199
+ if (!tokenSchema.responseSchema.properties.token.external_name) {
1200
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Primary Token', ['Primary Token', result.code], null, null, null);
1201
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1202
+ return callback(null, errorObj);
1203
+ }
1204
+
1205
+ const exName = tokenSchema.responseSchema.properties.token.external_name.toLowerCase();
1206
+ const headKeys = Object.keys(result.headers);
1207
+ let fullToken = null;
1208
+ let setCookie = null;
1209
+
1210
+ // go through and find the token
1211
+ for (let h = 0; h < headKeys.length; h += 1) {
1212
+ if (headKeys[h].toLowerCase() === exName) {
1213
+ fullToken = result.headers[headKeys[h]];
1214
+ currResult.token = result.headers[headKeys[h]];
1215
+ }
1216
+ if (headKeys[h].toLowerCase() === 'set-cookie') {
1217
+ setCookie = result.headers[headKeys[h]];
1218
+ }
1219
+ }
1220
+
1221
+ // if the token is in the requestToken
1222
+ if (exName === 'requestcookie' && result.requestCookie) {
1223
+ currResult.token = result.requestCookie;
1224
+ }
1225
+
1226
+ // if the exName field is an array
1227
+ if (exName === 'set-cookie' && fullToken && Array.isArray(fullToken)) {
1228
+ currResult.token = fullToken[0];
1229
+ for (let ex = 1; ex < fullToken.length; ex += 1) {
1230
+ currResult.token += `; ${fullToken[ex]}`;
1231
+ }
1232
+ }
1233
+
1234
+ // if the token has been returned in the cookie
1235
+ if (exName.substring(0, 11) === 'set-cookie.') {
1236
+ const fname = exName.substring(11).toLowerCase();
1237
+ let thisCook = null;
1238
+
1239
+ // if the cookie is an array - usual case
1240
+ if (setCookie && Array.isArray(setCookie)) {
1241
+ // go through the array looking for the defined token field
1242
+ for (let sc = 0; sc < setCookie.length; sc += 1) {
1243
+ // parses the cookie into an object
1244
+ thisCook = cookieHandler.parse(setCookie[sc]);
1245
+ const cookKeys = Object.keys(thisCook);
1246
+ let set = false;
1247
+
1248
+ // go through the cookie to find the token
1249
+ for (let h = 0; h < cookKeys.length; h += 1) {
1250
+ if (cookKeys[h].toLowerCase() === fname) {
1251
+ currResult.token = thisCook[cookKeys[h]];
1252
+ set = true;
1253
+ break;
1254
+ }
1255
+ }
1256
+ if (set) {
1257
+ break;
1258
+ }
1259
+ }
1260
+ } else if (setCookie) {
1261
+ // if the cookie is just one string, parse into an object
1262
+ thisCook = cookieHandler.parse(setCookie);
1263
+ const cookKeys = Object.keys(thisCook);
1264
+
1265
+ // go through the cookie to find the token
1266
+ for (let h = 0; h < cookKeys.length; h += 1) {
1267
+ if (cookKeys[h].toLowerCase() === fname) {
1268
+ currResult.token = thisCook[cookKeys[h]];
1269
+ break;
1270
+ }
1271
+ }
1272
+ }
1273
+ }
1274
+ }
1275
+
1276
+ // process second token from header
1277
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1278
+ && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'HEADER') {
1279
+ if (!tokenSchema.responseSchema.properties.tokenp2.external_name) {
1280
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Secondary Token', ['Secondary Token', result.code], null, null, null);
1281
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1282
+ return callback(null, errorObj);
1283
+ }
1284
+
1285
+ const exName = tokenSchema.responseSchema.properties.tokenp2.external_name.toLowerCase();
1286
+ const headKeys = Object.keys(result.headers);
1287
+ let fullToken = null;
1288
+ let setCookie = null;
1289
+
1290
+ // go through and find the token
1291
+ for (let h = 0; h < headKeys.length; h += 1) {
1292
+ if (headKeys[h].toLowerCase() === exName) {
1293
+ fullToken = result.headers[headKeys[h]];
1294
+ currResult.tokenp2 = result.headers[headKeys[h]];
1295
+ }
1296
+ if (headKeys[h].toLowerCase() === 'set-cookie') {
1297
+ setCookie = result.headers[headKeys[h]];
1298
+ }
1299
+ }
1300
+
1301
+ // if the token is in the requestToken
1302
+ if (exName === 'requestcookie' && result.requestCookie) {
1303
+ currResult.token = result.requestCookie;
1304
+ }
1305
+
1306
+ // if the exName field is an array
1307
+ if (exName === 'set-cookie' && fullToken && Array.isArray(fullToken)) {
1308
+ currResult.tokenp2 = fullToken[0];
1309
+ for (let ex = 1; ex < fullToken.length; ex += 1) {
1310
+ currResult.tokenp2 += `; ${fullToken[ex]}`;
1311
+ }
1312
+ }
1313
+
1314
+ // if the token has been returned in the cookie
1315
+ if (exName.substring(0, 11) === 'set-cookie.') {
1316
+ const fname = exName.substring(11).toLowerCase();
1317
+ let thisCook = null;
1318
+
1319
+ // if the cookie is an array - usual case
1320
+ if (setCookie && Array.isArray(setCookie)) {
1321
+ // go through the array looking for the defined token field
1322
+ for (let sc = 0; sc < setCookie.length; sc += 1) {
1323
+ // parses the cookie into an object
1324
+ thisCook = cookieHandler.parse(setCookie[sc]);
1325
+ const cookKeys = Object.keys(thisCook);
1326
+ let set = false;
1327
+
1328
+ // go through the cookie to find the token
1329
+ for (let h = 0; h < cookKeys.length; h += 1) {
1330
+ if (cookKeys[h].toLowerCase() === fname) {
1331
+ currResult.tokenp2 = thisCook[cookKeys[h]];
1332
+ set = true;
1333
+ break;
1334
+ }
1335
+ }
1336
+ if (set) {
1337
+ break;
1338
+ }
1339
+ }
1340
+ } else if (setCookie) {
1341
+ // if the cookie is just one string, parse into an object
1342
+ thisCook = cookieHandler.parse(setCookie);
1343
+ const cookKeys = Object.keys(thisCook);
1344
+
1345
+ // go through the cookie to find the token
1346
+ for (let h = 0; h < cookKeys.length; h += 1) {
1347
+ if (cookKeys[h].toLowerCase() === fname) {
1348
+ currResult.tokenp2 = thisCook[cookKeys[h]];
1349
+ break;
1350
+ }
1351
+ }
1352
+ }
1353
+ }
1354
+ }
1355
+
1356
+ // process the body
1357
+ // if response is just a string
1358
+ if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'PLAIN') {
1359
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1360
+ && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1361
+ currResult.token = result.response;
1362
+
1363
+ // if we got a stringified string - we can remove the double quotes wrapping it
1364
+ if (currResult.token.substring(0, 1) === '"' && currResult.token.substring(-1, 1) === '"') {
1365
+ currResult.token = currResult.token.substring(1, currResult.token.length - 1);
1366
+ }
1367
+ }
1368
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1369
+ && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1370
+ currResult.tokenp2 = result.response;
1371
+
1372
+ // if we got a stringified string - we can remove the double quotes wrapping it
1373
+ if (currResult.token.substring(0, 1) === '"' && currResult.token.substring(-1, 1) === '"') {
1374
+ currResult.token = currResult.token.substring(1, currResult.token.length - 1);
1375
+ }
1376
+ }
1377
+
1378
+ // return the string as there is no other processing needed
1379
+ return callback(currResult);
1380
+ }
1381
+ if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'XML') {
1382
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1383
+ && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1384
+ currResult.token = result.response;
1385
+ }
1386
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1387
+ && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1388
+ currResult.tokenp2 = result.response;
1389
+ }
1390
+
1391
+ // return the xml as there is no other processing needed
1392
+ return callback(currResult);
1393
+ }
1394
+
1395
+ // if response can be put into a JSON object
1396
+ let tempResult = null;
1397
+ if (result.response) {
1398
+ if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'XML2JSON') {
1399
+ try {
1400
+ const parser = new xml2js.Parser({ explicitArray: false, attrkey: '_attr' });
1401
+ parser.parseString(result.response, (error, presult) => {
1402
+ if (error) {
1403
+ log.warn(`${origin}: Unable to parse xml to json ${error}`);
1404
+ return callback(parser.toJson(presult));
1405
+ }
1406
+ tempResult = presult;
1407
+ });
1408
+ } catch (ex) {
1409
+ log.warn(`${origin}: Unable to get json from xml ${ex}`);
1410
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1411
+ && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1412
+ currResult.token = result.response;
1413
+ }
1414
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1415
+ && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1416
+ currResult.tokenp2 = result.response;
1417
+ }
1418
+ }
1419
+ }
1420
+ if (Object.hasOwnProperty.call(tokenSchema, 'responseDatatype') && tokenSchema.responseDatatype.toUpperCase() === 'URLENCODE') {
1421
+ tempResult = querystring.parse(result.response.trim());
1422
+ } else {
1423
+ try {
1424
+ tempResult = JSON.parse(result.response.trim());
1425
+ } catch (exc) {
1426
+ log.warn(exc);
1427
+ }
1428
+ }
1429
+ }
1430
+
1431
+ // at this point if there is nothing in tempResult nothing further to do
1432
+ if (!tempResult) {
1433
+ return callback(currResult);
1434
+ }
1435
+
1436
+ // need to see if there is a response key
1437
+ if (tokenSchema.responseObjects) {
1438
+ let tKey = null;
1439
+
1440
+ // it should be an array - then take the first element of array
1441
+ if (Array.isArray(tokenSchema.responseObjects)) {
1442
+ if (tokenSchema.responseObjects[0].key) {
1443
+ tKey = tokenSchema.responseObjects[0].key;
1444
+ }
1445
+ } else if (tokenSchema.responseObjects.key) {
1446
+ tKey = tokenSchema.responseObjects.key;
1447
+ }
1448
+
1449
+ // if we found a key, use it
1450
+ if (tKey) {
1451
+ tempResult = jsonQuery(tKey, { data: tempResult }).value;
1452
+ }
1453
+ }
1454
+
1455
+ // the token should not be an array so if it is, return the 0 item.
1456
+ if (Array.isArray(tempResult)) {
1457
+ tempResult = tempResult[0];
1458
+ }
1459
+
1460
+ // return the token from the token schema
1461
+ let translated = transUtilInst.mapFromOutboundEntity(tempResult, tokenSchema.responseSchema);
1462
+
1463
+ // if what we got back is an array, just return the first element
1464
+ // should only have one token!!!
1465
+ if (translated && Array.isArray(translated)) {
1466
+ translated = translated[0];
1467
+ }
1468
+
1469
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.token
1470
+ && tokenSchema.responseSchema.properties.token.placement && tokenSchema.responseSchema.properties.token.placement.toUpperCase() === 'BODY') {
1471
+ currResult.token = translated.token;
1472
+ }
1473
+ if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.tokenp2
1474
+ && tokenSchema.responseSchema.properties.tokenp2.placement && tokenSchema.responseSchema.properties.tokenp2.placement.toUpperCase() === 'BODY') {
1475
+ currResult.tokenp2 = translated.tokenp2;
1476
+ }
1477
+
1478
+ // return the token that we find in the translated object
1479
+ return callback(currResult);
1480
+ });
1481
+ } catch (e) {
1482
+ // handle any exception
1483
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue retrieving a token');
1484
+ return callback(null, errorObj);
1485
+ }
1486
+ }
1487
+
1488
+ /*
1489
+ * INTERNAL FUNCTION: prepares a request to get a token from the system
1490
+ */
1491
+ function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
1492
+ const origin = `${id}-connectorRest-buildTokenRequest`;
1493
+ log.trace(origin);
1494
+
1495
+ try {
1496
+ // Get the entity schema from the file system
1497
+ return propUtilInst.getEntitySchema('.system', 'getToken', this.dbUtil, (tokenSchema, healthError) => {
1498
+ if (healthError || !tokenSchema || Object.keys(tokenSchema).length === 0) {
1499
+ log.debug(`${origin}: Using adapter properties for token information`);
1500
+ tokenSchema = null;
1501
+ } else {
1502
+ log.debug(`${origin}: Using action and schema for token information`);
1503
+ }
1504
+
1505
+ // prepare the additional headers we received
1506
+ let thisAHdata = null;
1507
+
1508
+ if (tokenSchema) {
1509
+ thisAHdata = transUtilInst.mergeObjects(thisAHdata, tokenSchema.headers);
1510
+ }
1511
+ if (globalRequest) {
1512
+ thisAHdata = transUtilInst.mergeObjects(thisAHdata, globalRequest.addlHeaders);
1513
+ }
1514
+
1515
+ // set up the right credentials - passed in overrides default
1516
+ let useUser = username;
1517
+ let usePass = password;
1518
+
1519
+ if (callProperties && callProperties.authentication && callProperties.authentication.username) {
1520
+ useUser = callProperties.authentication.username;
1521
+ }
1522
+ if (callProperties && callProperties.authentication && callProperties.authentication.password) {
1523
+ usePass = callProperties.authentication.password;
1524
+ }
1525
+
1526
+ // if no header data passed in create empty - will add data below
1527
+ if (!thisAHdata) {
1528
+ thisAHdata = {};
1529
+ } else {
1530
+ const hKeys = Object.keys(thisAHdata);
1531
+
1532
+ for (let h = 0; h < hKeys.length; h += 1) {
1533
+ let tempStr = thisAHdata[hKeys[h]];
1534
+
1535
+ // replace username variable
1536
+ if (tempStr.indexOf('{username}') >= 0) {
1537
+ tempStr = tempStr.replace('{username}', useUser);
1538
+ }
1539
+ // replace password variable
1540
+ if (tempStr.indexOf('{password}') >= 0) {
1541
+ tempStr = tempStr.replace('{password}', usePass);
1542
+ }
1543
+ thisAHdata[hKeys[h]] = tempStr;
1544
+
1545
+ // handle any base64 encoding required on the authStr
1546
+ if (thisAHdata[hKeys[h]].indexOf('{b64}') >= 0) {
1547
+ // get the range to be encoded
1548
+ const sIndex = thisAHdata[hKeys[h]].indexOf('{b64}');
1549
+ const eIndex = thisAHdata[hKeys[h]].indexOf('{/b64}');
1550
+
1551
+ // if start but no end - return an error
1552
+ if (sIndex >= 0 && eIndex < sIndex + 5) {
1553
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Encode', [thisAHdata[hKeys[h]]], null, null, null);
1554
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1555
+ return callback(null, errorObj);
1556
+ }
1557
+
1558
+ // get the string to be encoded
1559
+ const bufString = thisAHdata[hKeys[h]].substring(sIndex + 5, eIndex);
1560
+
1561
+ // encode the string
1562
+ const encString = Buffer.from(bufString).toString('base64');
1563
+ let tempAuthStr = '';
1564
+
1565
+ // build the new auth field with the encoded string
1566
+ if (sIndex > 0) {
1567
+ // add the start of the string that did not need encoding
1568
+ tempAuthStr = thisAHdata[hKeys[h]].substring(0, sIndex);
1569
+ }
1570
+ // add the encoded string
1571
+ tempAuthStr += encString;
1572
+ if (eIndex + 5 < thisAHdata[hKeys[h]].length) {
1573
+ // add the end of the string that did not need encoding
1574
+ tempAuthStr += thisAHdata[hKeys[h]].substring(eIndex + 6);
1575
+ }
1576
+
1577
+ // put the temp string into the auth string we will add to the request
1578
+ thisAHdata[hKeys[h]] = tempAuthStr;
1579
+ }
1580
+ }
1581
+ }
1582
+
1583
+ // set the Content Type headers based on the type of request data for the call
1584
+ if (thisAHdata['Content-Type'] === undefined || thisAHdata['Content-Type'] === null) {
1585
+ if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'PLAIN') {
1586
+ // add the Plain headers if they were not set already
1587
+ thisAHdata['Content-Type'] = 'text/plain';
1588
+ } else if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'XML') {
1589
+ // add the XML headers if they were not set already
1590
+ thisAHdata['Content-Type'] = 'application/xml';
1591
+ } else if (tokenSchema && tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'URLENCODE') {
1592
+ // add the URLENCODE headers if they were not set already
1593
+ thisAHdata['Content-Type'] = 'application/x-www-form-urlencoded';
1594
+ } else {
1595
+ // add the JSON headers if they were not set already
1596
+ thisAHdata['Content-Type'] = 'application/json';
1597
+ }
1598
+ }
1599
+ // set the Accept headers based on the type of response data for the call
1600
+ if (thisAHdata.Accept === undefined || thisAHdata.Accept === null) {
1601
+ if (tokenSchema && tokenSchema.responseDatatype && tokenSchema.responseDatatype.toUpperCase() === 'PLAIN') {
1602
+ // add the Plain headers if they were not set already
1603
+ thisAHdata.Accept = 'text/plain';
1604
+ } else if (tokenSchema && tokenSchema.responseDatatype && tokenSchema.responseDatatype.toUpperCase() === 'XML') {
1605
+ // add the XML headers if they were not set already
1606
+ thisAHdata.Accept = 'application/xml';
1607
+ } else if (tokenSchema && tokenSchema.responseDatatype && tokenSchema.responseDatatype.toUpperCase() === 'URLENCODE') {
1608
+ // add the URLENCODE headers if they were not set already
1609
+ thisAHdata.Accept = 'application/x-www-form-urlencoded';
1610
+ } else {
1611
+ // add the JSON headers if they were not set already
1612
+ thisAHdata.Accept = 'application/json';
1613
+ }
1614
+ }
1615
+
1616
+ if (thisAHdata.Accept === '') {
1617
+ delete thisAHdata.Accept;
1618
+ }
1619
+ if (thisAHdata['Content-Type'] === '') {
1620
+ delete thisAHdata['Content-Type'];
1621
+ }
1622
+
1623
+ // set up the options for the call to get incidents - default is all
1624
+ const options = {
1625
+ hostname: host,
1626
+ port,
1627
+ path: tokenPath,
1628
+ method: 'POST',
1629
+ headers: thisAHdata
1630
+ };
1631
+
1632
+ // passed in properties override defaults
1633
+ if (callProperties && callProperties.host) {
1634
+ options.hostname = callProperties.host;
1635
+ }
1636
+ if (callProperties && callProperties.port) {
1637
+ options.port = callProperties.port;
1638
+ }
1639
+
1640
+ // specific token properties override everything (Single Sign On System)
1641
+ if (tokenSchema && tokenSchema.sso && tokenSchema.sso.host) {
1642
+ options.hostname = tokenSchema.sso.host;
1643
+ }
1644
+ if (tokenSchema && tokenSchema.sso && tokenSchema.sso.port) {
1645
+ options.port = tokenSchema.sso.port;
1646
+ }
1647
+
1648
+ // If there is a token schema, need to take the data from there
1649
+ if (tokenSchema) {
1650
+ options.path = tokenSchema.entitypath;
1651
+ options.method = tokenSchema.method;
1652
+ }
1653
+
1654
+ // if the path has a base path parameter in it, need to replace it
1655
+ let bpathStr = '{base_path}';
1656
+ if (options.path.indexOf(bpathStr) >= 0) {
1657
+ // be able to support this if the base path has a slash before it or not
1658
+ if (options.path.indexOf('/{base_path}') >= 0) {
1659
+ bpathStr = '/{base_path}';
1660
+ }
1661
+
1662
+ // replace with base path if we have one, otherwise remove base path
1663
+ if (callProperties && callProperties.base_path) {
1664
+ // if no leading /, insert one
1665
+ if (callProperties.base_path.indexOf('/') !== 0) {
1666
+ options.path = options.path.replace(bpathStr, `/${callProperties.base_path}`);
1667
+ } else {
1668
+ options.path = options.path.replace(bpathStr, callProperties.base_path);
1669
+ }
1670
+ } else if (basepath) {
1671
+ // if no leading /, insert one
1672
+ if (basepath.indexOf('/') !== 0) {
1673
+ options.path = options.path.replace(bpathStr, `/${basepath}`);
1674
+ } else {
1675
+ options.path = options.path.replace(bpathStr, basepath);
1676
+ }
1677
+ } else {
1678
+ options.path = options.path.replace(bpathStr, '');
1679
+ }
1680
+ }
1681
+
1682
+ // if the path has a version parameter in it, need to replace it
1683
+ let versStr = '{version}';
1684
+ if (options.path.indexOf(versStr) >= 0) {
1685
+ // be able to support this if the version has a slash before it or not
1686
+ if (options.path.indexOf('/{version}') >= 0) {
1687
+ versStr = '/{version}';
1688
+ }
1689
+
1690
+ // replace with version if we have one, otherwise remove version
1691
+ if (callProperties && callProperties.version) {
1692
+ options.path = options.path.replace(versStr, `/${encodeURIComponent(callProperties.version)}`);
1693
+ } else if (version) {
1694
+ options.path = options.path.replace(versStr, `/${encodeURIComponent(version)}`);
1695
+ } else {
1696
+ options.path = options.path.replace(versStr, '');
1697
+ }
1698
+ }
1699
+
1700
+ // if there are URI path variables that have been provided, need to add
1701
+ // them to the path
1702
+ if (reqBody && reqBody.uriPathVars && reqBody.uriPathVars.length > 0) {
1703
+ for (let p = 0; p < reqBody.uriPathVars.length; p += 1) {
1704
+ const vnum = p + 1;
1705
+ const holder = `pathv${vnum.toString()}`;
1706
+ const hindex = options.path.indexOf(holder);
1707
+
1708
+ // if path variable is in the url, replace it!!!
1709
+ if (hindex >= 0 && reqBody.uriPathVars[p] !== null && reqBody.uriPathVars[p] !== '') {
1710
+ // with the provided id
1711
+ let idString = '';
1712
+
1713
+ // check if the current URI path ends with a slash (may require
1714
+ // slash at end)
1715
+ if (options.path[hindex - 2] === '/' || options.path[hindex - 2] === ':') {
1716
+ // ends with a slash need to add slash to end
1717
+ idString = encodeURIComponent(reqBody.uriPathVars[p]);
1718
+ } else {
1719
+ // otherwise add / to start
1720
+ idString = '/';
1721
+ idString += encodeURIComponent(reqBody.uriPathVars[p]);
1722
+ }
1723
+
1724
+ // replace the id in url with the id string
1725
+ options.path = options.path.replace(`{${holder}}`, idString);
1726
+ }
1727
+ }
1728
+ }
1729
+
1730
+ // need to remove all of the remaining path holders from the URI
1731
+ while (options.path.indexOf('{pathv') >= 0) {
1732
+ let sIndex = options.path.indexOf('{pathv');
1733
+ const eIndex = options.path.indexOf('}', sIndex);
1734
+
1735
+ // if there is a / before the {pathv} need to remove it
1736
+ if (options.path[sIndex - 1] === '/' || options.path[sIndex - 1] === ':') {
1737
+ sIndex -= 1;
1738
+ }
1739
+
1740
+ if (sIndex > 0) {
1741
+ // add the start of the path
1742
+ let tempStr = options.path.substring(0, sIndex);
1743
+
1744
+ if (eIndex < options.path.length) {
1745
+ // add the end of the path
1746
+ tempStr += options.path.substring(eIndex + 1);
1747
+ }
1748
+
1749
+ options.path = tempStr;
1750
+ } else if (eIndex > 0 && eIndex < options.path.length) {
1751
+ // add the end of the path
1752
+ options.path = options.path.substring(eIndex + 1);
1753
+ } else {
1754
+ // should not get here - there is some issue in the uripath - missing
1755
+ // an end or the path is just {pathv#}
1756
+ // add the specific pieces of the error object
1757
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Invalid Action File', ['missing entity path', '.system/getToken'], null, null, null);
1758
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1759
+ return callback(null, errorObj);
1760
+ }
1761
+ }
1762
+
1763
+ // remove the path vars from the reqBody
1764
+ const actReqBody = Object.assign({}, reqBody);
1765
+ if (actReqBody && actReqBody.uriPathVars) {
1766
+ delete actReqBody.uriPathVars;
1767
+ }
1768
+
1769
+ // if ssl enabled add the options for ssl
1770
+ if (callProperties && callProperties.ssl && Object.hasOwnProperty.call(callProperties.ssl, 'enabled')) {
1771
+ if (callProperties.ssl.enabled) {
1772
+ if (callProperties.ssl.accept_invalid_cert) {
1773
+ // if we are accepting invalid certificates (ok for lab not so much production)
1774
+ options.rejectUnauthorized = false;
1775
+ } else {
1776
+ // if we are not accepting invalid certs, need the ca file in the options
1777
+ try {
1778
+ options.rejectUnauthorized = true;
1779
+ options.ca = [fs.readFileSync(callProperties.ssl.ca_file)];
1780
+ } catch (e) {
1781
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [callProperties.ssl.ca_file], null, null, null);
1782
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1783
+ return callback(null, errorObj);
1784
+ }
1785
+ }
1786
+
1787
+ if (callProperties.ssl.ciphers) {
1788
+ options.ciphers = callProperties.ssl.ciphers;
1789
+ }
1790
+ if (callProperties.ssl.secure_protocol) {
1791
+ options.secureProtocol = callProperties.ssl.secure_protocol;
1792
+ }
1793
+
1794
+ log.info(`${origin}: Connector SSL connections enabled`);
1795
+ }
1796
+ } else if (sslEnabled) {
1797
+ if (sslAcceptInvalid) {
1798
+ // if we are accepting invalid certificates (ok for lab not so much production)
1799
+ options.rejectUnauthorized = false;
1800
+ } else {
1801
+ // if we are not accepting invalid certs, need the ca file in the options
1802
+ try {
1803
+ options.rejectUnauthorized = true;
1804
+ options.ca = [fs.readFileSync(sslCAFile)];
1805
+ } catch (e) {
1806
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCAFile], null, null, null);
1807
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1808
+ return callback(null, errorObj);
1809
+ }
1810
+ // if there is a cert file, try to read in a cert file in the options
1811
+ if (sslCertFile) {
1812
+ try {
1813
+ options.cert = [fs.readFileSync(sslCertFile)];
1814
+ } catch (e) {
1815
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCertFile], null, null, null);
1816
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1817
+ return callback(null, errorObj);
1818
+ }
1819
+ }
1820
+ // if there is a key file, try to read in a key file in the options
1821
+ if (sslKeyFile) {
1822
+ try {
1823
+ options.key = [fs.readFileSync(sslKeyFile)];
1824
+ } catch (e) {
1825
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslKeyFile], null, null, null);
1826
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
1827
+ return callback(null, errorObj);
1828
+ }
1829
+ }
1830
+ }
1831
+
1832
+ if (sslCiphers) {
1833
+ options.ciphers = sslCiphers;
1834
+ }
1835
+ if (secureProtocol) {
1836
+ options.secureProtocol = secureProtocol;
1837
+ }
1838
+
1839
+ log.info(`${origin}: Connector SSL connections enabled`);
1840
+ }
1841
+
1842
+ const reqData = {};
1843
+ let bodyString = null;
1844
+
1845
+ // if we have been passed a schema and the Authorization is in a header
1846
+ if (tokenSchema && tokenSchema.headers && tokenSchema.headers.Authorization) {
1847
+ // request the token
1848
+ options.headers['Content-length'] = 0;
1849
+ return getToken(reqPath, options, tokenSchema, '', callProperties, callback);
1850
+ }
1851
+ if (tokenSchema) {
1852
+ // if this is a get, need to put the username on the url
1853
+ if (options.path.indexOf('/{username}') >= 0) {
1854
+ options.path = options.path.replace('/{username}', useUser);
1855
+ } else {
1856
+ options.path = options.path.replace('{username}', useUser);
1857
+ }
1858
+
1859
+ // if this is a get, need to put the password on the url
1860
+ if (options.path.indexOf('/{password}') >= 0) {
1861
+ options.path = options.path.replace('/{password}', usePass);
1862
+ } else {
1863
+ options.path = options.path.replace('{password}', usePass);
1864
+ }
1865
+
1866
+ // if this is not a get, need to add the info to the request
1867
+ if (options.method !== 'GET') {
1868
+ let creds = {
1869
+ username: useUser,
1870
+ password: usePass
1871
+ };
1872
+ if (clientId) {
1873
+ creds.client_id = clientId;
1874
+ }
1875
+ if (clientSecret) {
1876
+ creds.client_secret = clientSecret;
1877
+ }
1878
+ if (grantType) {
1879
+ creds.grant_type = grantType;
1880
+ }
1881
+ if (callProperties && callProperties.authentication && callProperties.authentication.client_id) {
1882
+ creds.client_id = callProperties.authentication.client_id;
1883
+ }
1884
+ if (callProperties && callProperties.authentication && callProperties.authentication.client_secret) {
1885
+ creds.client_secret = callProperties.authentication.client_secret;
1886
+ }
1887
+ if (callProperties && callProperties.authentication && callProperties.authentication.grant_type) {
1888
+ creds.grant_type = callProperties.authentication.grant_type;
1889
+ }
1890
+
1891
+ // if there is body data to add to the token request body
1892
+ creds = transUtilInst.mergeObjects(actReqBody, creds);
1893
+
1894
+ if (globalRequest) {
1895
+ creds = transUtilInst.mergeObjects(creds, globalRequest.authData);
1896
+ }
1897
+
1898
+ // if there is body data to add to the token request body
1899
+ if (actReqBody) {
1900
+ const bodyKey = Object.keys(actReqBody);
1901
+
1902
+ for (let k = 0; k < bodyKey.length; k += 1) {
1903
+ creds[bodyKey[k]] = actReqBody[bodyKey[k]];
1904
+ }
1905
+ }
1906
+
1907
+ // map the data we received to an Entity - will get back the defaults
1908
+ const tokenEntity = transUtilInst.mapToOutboundEntity(creds, tokenSchema.requestSchema);
1909
+ bodyString = tokenEntity;
1910
+
1911
+ // if it is JSON or URLENCODE need to put body into right format
1912
+ if (!tokenSchema.requestDatatype || tokenSchema.requestDatatype.toUpperCase() === 'JSON' || tokenSchema.requestDatatype.toUpperCase() === 'FORM') {
1913
+ bodyString = JSON.stringify(tokenEntity);
1914
+ } else if (tokenSchema.requestDatatype && tokenSchema.requestDatatype.toUpperCase() === 'URLENCODE') {
1915
+ bodyString = querystring.stringify(tokenEntity);
1916
+ }
1917
+
1918
+ // if there is a body, set the content length of the body and add it to
1919
+ // the header
1920
+ if (Object.keys(tokenEntity).length > 0) {
1921
+ options.headers['Content-length'] = Buffer.byteLength(bodyString);
1922
+ }
1923
+
1924
+ // request the token
1925
+ return getToken(reqPath, options, tokenSchema, bodyString, callProperties, callback);
1926
+ }
1927
+ } else {
1928
+ // set the user and password for the token request
1929
+ reqData[tokenUserField] = useUser;
1930
+ reqData[tokenPwdField] = usePass;
1931
+ bodyString = reqData;
1932
+
1933
+ // since not a get call, convert reqData to a string and add length
1934
+ bodyString = JSON.stringify(reqData);
1935
+ options.headers['Content-length'] = Buffer.byteLength(bodyString);
1936
+ }
1937
+
1938
+ // request the token
1939
+ return getToken(reqPath, options, tokenSchema, bodyString, callProperties, callback);
1940
+ });
1941
+ } catch (e) {
1942
+ // handle any exception
1943
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue requesting token');
1944
+ return callback(null, errorObj);
1945
+ }
1946
+ }
1947
+
1948
+ /*
1949
+ * INTERNAL FUNCTION: addTokenItem is used to add a token item to the
1950
+ * memory based on the user provided
1951
+ */
1952
+ function addTokenItem(user, reqBody, token, timeout, callback) {
1953
+ const origin = `${id}-connectorRest-addTokenItem`;
1954
+ log.trace(origin);
1955
+
1956
+ try {
1957
+ // there is already a lock from the get, so no need to lock the tokenList
1958
+ const now = new Date();
1959
+
1960
+ // create the token item
1961
+ let tkey = `${id}__%%__${user}`;
1962
+ const tokenItem = {
1963
+ adapter: id,
1964
+ username: user,
1965
+ token,
1966
+ start: now.getTime(),
1967
+ expire: timeout
1968
+ };
1969
+
1970
+ if (reqBody) {
1971
+ tkey += `__%%__${JSON.stringify(reqBody)}`;
1972
+ }
1973
+
1974
+ tokenItem.tkey = tkey;
1975
+
1976
+ // add the token item to the tokenlist and return it
1977
+ if (tokenCache === 'local') {
1978
+ tokenList.push(tokenItem);
1979
+ return callback(token);
1980
+ }
1981
+
1982
+ // try to add the token item to the redis local memory
1983
+ return g_redis.set(tkey, JSON.stringify(tokenItem), (err) => {
1984
+ if (err) {
1985
+ log.error(`${origin}: ${tkey} not stored in redis`);
1986
+ }
1987
+
1988
+ // return the token
1989
+ return callback(token);
1990
+ });
1991
+ } catch (e) {
1992
+ // handle any exception
1993
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue adding token');
1994
+ return callback(null, errorObj);
1995
+ }
1996
+ }
1997
+
1998
+ /*
1999
+ * INTERNAL FUNCTION: retrieve the token from cache and determine if
2000
+ * it valid. if valid, return it otherwise return null
2001
+ */
2002
+ function validToken(user, reqBody, invalidToken, callback) {
2003
+ const origin = `${id}-connectorRest-validToken`;
2004
+ log.trace(origin);
2005
+
2006
+ try {
2007
+ // time to see if the token is expired
2008
+ const expireCheck = new Date().getTime() + 60000;
2009
+
2010
+ // get the token from redis
2011
+ let tkey = `${id}__%%__${user}`;
2012
+
2013
+ if (reqBody) {
2014
+ tkey += `__%%__${JSON.stringify(reqBody)}`;
2015
+ }
2016
+
2017
+ // determine where to get the token
2018
+ if (tokenCache === 'redis') {
2019
+ return g_redis.get(tkey, (err, res) => {
2020
+ if (err) {
2021
+ log.error(`${origin}: Error on retrieve token for ${tkey}`);
2022
+ }
2023
+
2024
+ // if there was no token returned
2025
+ if (!res) {
2026
+ return callback(null);
2027
+ }
2028
+
2029
+ const parsedToken = JSON.parse(res);
2030
+
2031
+ // if the token expired (or will expire within a minute),
2032
+ // or it is invalid (failed) remove it from the token list
2033
+ if (parsedToken.expire < expireCheck || parsedToken.token.token === invalidToken) {
2034
+ return g_redis.del(tkey, (derr) => {
2035
+ if (derr) {
2036
+ log.debug(`${origin}: Expired token for ${tkey} not removed from redis`);
2037
+ }
2038
+
2039
+ return callback(null);
2040
+ });
2041
+ }
2042
+
2043
+ // return the retrieved token
2044
+ return callback(parsedToken.token);
2045
+ });
2046
+ }
2047
+
2048
+ let retToken = null;
2049
+
2050
+ // find this user's token in the list
2051
+ for (let i = 0; i < tokenList.length; i += 1) {
2052
+ if (tokenList[i].tkey === tkey) {
2053
+ // if the token expired (or will expire within a minute),
2054
+ // or it is invalid (failed) remove it from the token list
2055
+ if (tokenList[i].expire < expireCheck
2056
+ || tokenList[i].token.token === invalidToken) {
2057
+ tokenList.splice(i, 1);
2058
+ break;
2059
+ }
2060
+
2061
+ retToken = tokenList[i].token;
2062
+ break;
2063
+ }
2064
+ }
2065
+ return callback(retToken);
2066
+ } catch (e) {
2067
+ // handle any exception
2068
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue validating cached token');
2069
+ return callback(null, errorObj);
2070
+ }
2071
+ }
2072
+
2073
+ /*
2074
+ * INTERNAL FUNCTION: getTokenItem is used to retrieve a token items from
2075
+ * memory based on the user provided
2076
+ */
2077
+ function getTokenItem(pathForToken, user, reqBody, invalidToken, callProperties, callback) {
2078
+ const origin = `${id}-connectorRest-getTokenItem`;
2079
+ log.trace(origin);
2080
+
2081
+ try {
2082
+ // Lock the tokenList while getting the token
2083
+ return tlock.acquire(tokenlock, (done) => {
2084
+ validToken(user, reqBody, invalidToken, (retToken, verror) => {
2085
+ if (verror) {
2086
+ done(null, verror);
2087
+ }
2088
+
2089
+ // If valid token found, return it and skip the rest
2090
+ if (retToken !== null) {
2091
+ done(retToken, null);
2092
+ } else {
2093
+ // No valid token found, Need to get a new token and add it to the token list
2094
+ buildTokenRequest(pathForToken, reqBody, callProperties, (dyntoken, berror) => {
2095
+ if (berror) {
2096
+ done(null, berror);
2097
+ }
2098
+
2099
+ let timeout = tokenTimeout;
2100
+
2101
+ // if we should use the timeout from the token request
2102
+ if (timeout === 0) {
2103
+ timeout = findExpireInResult(dyntoken);
2104
+ } else {
2105
+ // otherwise add the timeout to the current time
2106
+ timeout += new Date().getTime();
2107
+ }
2108
+
2109
+ const tokenObj = {
2110
+ token: findPrimaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end),
2111
+ tokenp2: findSecondaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end)
2112
+ };
2113
+
2114
+ // if this is worth caching
2115
+ if (timeout && timeout > 0) {
2116
+ // since this is adding the token for future use, do not care when it comes back
2117
+ addTokenItem(user, reqBody, tokenObj, timeout, (addedtoken, aerror) => {
2118
+ if (aerror) {
2119
+ done(null, aerror);
2120
+ }
2121
+ done(tokenObj, null);
2122
+ });
2123
+ } else {
2124
+ done(tokenObj, null);
2125
+ }
2126
+ });
2127
+ }
2128
+ });
2129
+ }, (ret, error) => {
2130
+ if (error) {
2131
+ return callback(null, error);
2132
+ }
2133
+
2134
+ return callback(ret);
2135
+ });
2136
+ } catch (e) {
2137
+ // handle any exception
2138
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue getting token item');
2139
+ return callback(null, errorObj);
2140
+ }
2141
+ }
2142
+
2143
+ /*
2144
+ * INTERNAL FUNCTION: addAuthToRequest determines the place to add the authentication
2145
+ * and adds the authentication string
2146
+ */
2147
+ function addAuthToRequest(request, authStrs, callProperties, callback) {
2148
+ const origin = `${id}-connectorRest-addAuthToRequest`;
2149
+ log.trace(origin);
2150
+
2151
+ try {
2152
+ const newAuthStrs = [];
2153
+ for (let a = 0; a < authStrs.length; a += 1) {
2154
+ // handle any base64 encoding required on the authStrs
2155
+ if (authStrs[a].indexOf('{b64}') >= 0) {
2156
+ // get the range to be encoded
2157
+ const sIndex = authStrs[a].indexOf('{b64}');
2158
+ const eIndex = authStrs[a].indexOf('{/b64}');
2159
+
2160
+ // if start but no end - return an error
2161
+ if (sIndex >= 0 && eIndex < sIndex + 5) {
2162
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Encode', [authStrs[a]], null, null, null);
2163
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2164
+ return callback(null, errorObj);
2165
+ }
2166
+
2167
+ // get the string to be encoded
2168
+ const bufString = authStrs[a].substring(sIndex + 5, eIndex);
2169
+
2170
+ // encode the string
2171
+ const encString = Buffer.from(bufString).toString('base64');
2172
+ let tempAuthStr = '';
2173
+
2174
+ // build the new auth field with the encoded string
2175
+ if (sIndex > 0) {
2176
+ // add the start of the string that did not need encoding
2177
+ tempAuthStr = authStrs[a].substring(0, sIndex);
2178
+ }
2179
+ // add the encoded string
2180
+ tempAuthStr += encString;
2181
+ if (eIndex + 5 < authStrs[a].length) {
2182
+ // add the end of the string that did not need encoding
2183
+ tempAuthStr += authStrs[a].substring(eIndex + 6);
2184
+ }
2185
+
2186
+ // put the temp string into the auth string we will add to the request
2187
+ newAuthStrs.push(tempAuthStr);
2188
+ } else {
2189
+ newAuthStrs.push(authStrs[a]);
2190
+ }
2191
+ }
2192
+
2193
+ // add the authentication field to the request
2194
+ let useAuthField = authField;
2195
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field) {
2196
+ if (Array.isArray(callProperties.authentication.auth_field)) {
2197
+ useAuthField = callProperties.authentication.auth_field;
2198
+ } else {
2199
+ useAuthField = [callProperties.authentication.auth_field];
2200
+ }
2201
+ }
2202
+
2203
+ // Auth Strings need to be at least as long as auth fields so if less, buffer with the first auth string
2204
+ if (useAuthField.length > newAuthStrs.length) {
2205
+ for (let t = newAuthStrs.length; t < useAuthField.length; t += 1) {
2206
+ newAuthStrs.push(newAuthStrs[0]);
2207
+ }
2208
+ }
2209
+
2210
+ // need to go through and put in place all of the auth strings.
2211
+ for (let af = 0; af < useAuthField.length; af += 1) {
2212
+ const authPath = useAuthField[af].split('.');
2213
+
2214
+ // if the token is going to be put in the url (url.xxxx)
2215
+ if (authPath[0] === 'urlpath') {
2216
+ // if there is already query info, insert the token
2217
+ if (request.header.path.indexOf('?') >= 0) {
2218
+ let temp = request.header.path.substring(0, request.header.path.indexOf('?'));
2219
+ temp += `/${newAuthStrs[af]}?`;
2220
+ temp += request.header.path.substring(request.header.path.indexOf('?') + 1);
2221
+ request.header.path = temp;
2222
+ } else {
2223
+ // append the token
2224
+ request.header.path += `/${newAuthStrs[af]}`;
2225
+ }
2226
+ } else if (authPath[0] === 'url') {
2227
+ // if there is already query info, insert the token
2228
+ if (request.header.path.indexOf('?') >= 0) {
2229
+ let temp = request.header.path.substring(0, request.header.path.indexOf('?'));
2230
+ temp += `?${newAuthStrs[af]}&`;
2231
+ temp += request.header.path.substring(request.header.path.indexOf('?') + 1);
2232
+ request.header.path = temp;
2233
+ } else {
2234
+ // append the token
2235
+ request.header.path += `?${newAuthStrs[af]}`;
2236
+ }
2237
+ } else if (authPath[0] === 'body' && typeof request.body === 'string') {
2238
+ // if adding to body and it is already stringified
2239
+ let jbody = JSON.parse(request.body);
2240
+ // get to the field in the object (header.xxxx or body.xxxxx)
2241
+ for (let a = 1; a < authPath.length; a += 1) {
2242
+ // if we are at the point to add the token
2243
+ if (a === authPath.length - 1) {
2244
+ jbody[authPath[a]] = newAuthStrs[af];
2245
+ } else {
2246
+ // if the field is not defined in the request, add it
2247
+ if (jbody[authPath[a]] === undefined) {
2248
+ jbody[authPath[a]] = {};
2249
+ }
2250
+
2251
+ // set the jbody to the next field in the path
2252
+ jbody = jbody[authPath[a]];
2253
+ }
2254
+ }
2255
+ request.body = JSON.stringify(jbody);
2256
+ request.header.headers['Content-length'] = Buffer.byteLength(request.body);
2257
+ } else {
2258
+ let tempField = request;
2259
+
2260
+ // get to the field in the object (header.xxxx or body.xxxxx)
2261
+ for (let a = 0; a < authPath.length; a += 1) {
2262
+ // if we are at the point to add the token
2263
+ if (a === authPath.length - 1) {
2264
+ tempField[authPath[a]] = newAuthStrs[af];
2265
+ } else {
2266
+ // if the field is not defined in the request, add it
2267
+ if (tempField[authPath[a]] === undefined) {
2268
+ tempField[authPath[a]] = {};
2269
+ }
2270
+
2271
+ // set the tempField to the next field in the path
2272
+ tempField = tempField[authPath[a]];
2273
+ }
2274
+ }
2275
+ }
2276
+ }
2277
+
2278
+ // auth string is already in the request object
2279
+ return callback(newAuthStrs);
2280
+ } catch (e) {
2281
+ // handle any exception
2282
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue adding authentication');
2283
+ return callback(null, errorObj);
2284
+ }
2285
+ }
2286
+
2287
+ /*
2288
+ * INTERNAL FUNCTION: requestAuthenticate determines the authentication for System,
2289
+ * and takes appropriate action to authenticate and then makes the request
2290
+ */
2291
+ function requestAuthenticate(request, entitySchema, invalidToken, callProperties, callback) {
2292
+ const origin = `${id}-connectorRest-requestAuthenticate`;
2293
+ log.trace(origin);
2294
+
2295
+ try {
2296
+ // set up the right credentials - passed in overrides default
2297
+ let useUser = username;
2298
+ let usePass = password;
2299
+
2300
+ if (callProperties && callProperties.authentication && callProperties.authentication.username) {
2301
+ useUser = callProperties.authentication.username;
2302
+ }
2303
+ if (callProperties && callProperties.authentication && callProperties.authentication.password) {
2304
+ usePass = callProperties.authentication.password;
2305
+ }
2306
+
2307
+ if (authMethod === 'request_token') {
2308
+ // are we working with reusing tokens until they expire?
2309
+ if (tokenTimeout >= 0) {
2310
+ // get the token from the token list
2311
+ return getTokenItem(request.header.path, useUser, request.authData, invalidToken, callProperties, (tres, terror) => {
2312
+ if (terror) {
2313
+ return callback(null, terror);
2314
+ }
2315
+
2316
+ // if we got a valid token, use it
2317
+ if (!tres || !tres.token) {
2318
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Token', [useUser], null, null, null);
2319
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2320
+ return callback(null, errorObj);
2321
+ }
2322
+
2323
+ // format the authentication string
2324
+ log.debug(`${origin}: ${JSON.stringify(tres)} being used for user: ${useUser}`);
2325
+ const authStrs = [];
2326
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field_format) {
2327
+ if (Array.isArray(callProperties.authentication.auth_field_format)) {
2328
+ for (let a = 0; a < callProperties.authentication.auth_field_format.length; a += 1) {
2329
+ let authStr = callProperties.authentication.auth_field_format[a].replace('{token}', tres.token);
2330
+ authStr = authStr.replace('{tokenp2}', tres.tokenp2);
2331
+ authStr = authStr.replace('{username}', useUser);
2332
+ authStr = authStr.replace('{password}', usePass);
2333
+ authStrs.push(authStr);
2334
+ }
2335
+ } else {
2336
+ let authStr = callProperties.authentication.auth_field_format.replace('{token}', tres.token);
2337
+ authStr = authStr.replace('{tokenp2}', tres.tokenp2);
2338
+ authStr = authStr.replace('{username}', useUser);
2339
+ authStr = authStr.replace('{password}', usePass);
2340
+ authStrs.push(authStr);
2341
+ }
2342
+ } else {
2343
+ for (let a = 0; a < authFormat.length; a += 1) {
2344
+ let authStr = authFormat[a].replace('{token}', tres.token);
2345
+ authStr = authStr.replace('{tokenp2}', tres.tokenp2);
2346
+ authStr = authStr.replace('{username}', useUser);
2347
+ authStr = authStr.replace('{password}', usePass);
2348
+ authStrs.push(authStr);
2349
+ }
2350
+ }
2351
+
2352
+ return addAuthToRequest(request, authStrs, callProperties, (authReq, aerror) => {
2353
+ if (aerror) {
2354
+ return callback(aerror);
2355
+ }
2356
+
2357
+ request.tokenUsed = tres.token;
2358
+
2359
+ // actually make the request now that the authentication has been added
2360
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2361
+ });
2362
+ });
2363
+ }
2364
+
2365
+ // if no token timeout then no token list - just get the token and continue
2366
+ return buildTokenRequest(request.header.path, request.authData, callProperties, (dyntoken, berror) => {
2367
+ if (berror) {
2368
+ return callback(null, berror);
2369
+ }
2370
+
2371
+ // format the authentication string
2372
+ const tokenObj = {
2373
+ token: findPrimaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end),
2374
+ tokenp2: findSecondaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end)
2375
+ };
2376
+
2377
+ if (!tokenObj || !tokenObj.token) {
2378
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Get Token', [useUser], null, null, null);
2379
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2380
+ return callback(null, errorObj);
2381
+ }
2382
+
2383
+ log.debug(`${origin}: ${JSON.stringify(tokenObj)} being used for user: ${useUser}`);
2384
+ const authStrs = [];
2385
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field_format) {
2386
+ if (Array.isArray(callProperties.authentication.auth_field_format)) {
2387
+ for (let a = 0; a < callProperties.authentication.auth_field_format.length; a += 1) {
2388
+ let authStr = callProperties.authentication.auth_field_format[a].replace('{token}', tokenObj.token);
2389
+ authStr = authStr.replace('{tokenp2}', tokenObj.tokenp2);
2390
+ authStr = authStr.replace('{username}', useUser);
2391
+ authStr = authStr.replace('{password}', usePass);
2392
+ authStrs.push(authStr);
2393
+ }
2394
+ } else {
2395
+ let authStr = callProperties.authentication.auth_field_format.replace('{token}', tokenObj.token);
2396
+ authStr = authStr.replace('{tokenp2}', tokenObj.tokenp2);
2397
+ authStr = authStr.replace('{username}', useUser);
2398
+ authStr = authStr.replace('{password}', usePass);
2399
+ authStrs.push(authStr);
2400
+ }
2401
+ } else {
2402
+ for (let a = 0; a < authFormat.length; a += 1) {
2403
+ let authStr = authFormat[a].replace('{token}', tokenObj.token);
2404
+ authStr = authStr.replace('{tokenp2}', tokenObj.tokenp2);
2405
+ authStr = authStr.replace('{username}', useUser);
2406
+ authStr = authStr.replace('{password}', usePass);
2407
+ authStrs.push(authStr);
2408
+ }
2409
+ }
2410
+
2411
+ return addAuthToRequest(request, authStrs, callProperties, (authReq, aerror) => {
2412
+ if (aerror) {
2413
+ return callback(aerror);
2414
+ }
2415
+
2416
+ // actually make the request now that the authentication has been added
2417
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2418
+ });
2419
+ });
2420
+ }
2421
+
2422
+ if (authMethod === 'jwt_token') {
2423
+ // format the authentication string
2424
+ let useToken = staticToken;
2425
+
2426
+ // create payload and secret (from prepoerties)
2427
+ const secret = usePass;
2428
+ const payload = {
2429
+ iss: useUser,
2430
+ exp: new Date().getTime() + (tokenTimeout / 1000) + 30
2431
+ };
2432
+
2433
+ // sign token with API Secret
2434
+ useToken = jwt.sign(payload, secret);
2435
+
2436
+ const authStrs = [];
2437
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field_format) {
2438
+ if (Array.isArray(callProperties.authentication.auth_field_format)) {
2439
+ for (let a = 0; a < callProperties.authentication.auth_field_format.length; a += 1) {
2440
+ let authStr = callProperties.authentication.auth_field_format[a].replace('{token}', useToken);
2441
+ authStr = authStr.replace('{username}', useUser);
2442
+ authStr = authStr.replace('{password}', usePass);
2443
+ authStrs.push(authStr);
2444
+ }
2445
+ } else {
2446
+ let authStr = callProperties.authentication.auth_field_format.replace('{token}', useToken);
2447
+ authStr = authStr.replace('{username}', useUser);
2448
+ authStr = authStr.replace('{password}', usePass);
2449
+ authStrs.push(authStr);
2450
+ }
2451
+ } else {
2452
+ for (let a = 0; a < authFormat.length; a += 1) {
2453
+ let authStr = authFormat[a].replace('{token}', useToken);
2454
+ authStr = authStr.replace('{username}', useUser);
2455
+ authStr = authStr.replace('{password}', usePass);
2456
+ authStrs.push(authStr);
2457
+ }
2458
+ }
2459
+
2460
+ return addAuthToRequest(request, authStrs, callProperties, (authReq, aerror) => {
2461
+ if (aerror) {
2462
+ return callback(aerror);
2463
+ }
2464
+
2465
+ // actually make the request now that the authentication has been added
2466
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2467
+ });
2468
+ }
2469
+
2470
+ if (authMethod === 'static_token') {
2471
+ // format the authentication string
2472
+ let useToken = staticToken;
2473
+ if (callProperties && callProperties.authentication && callProperties.authentication.token) {
2474
+ useToken = callProperties.authentication.token;
2475
+ }
2476
+
2477
+ const authStrs = [];
2478
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field_format) {
2479
+ if (Array.isArray(callProperties.authentication.auth_field_format)) {
2480
+ for (let a = 0; a < callProperties.authentication.auth_field_format.length; a += 1) {
2481
+ let authStr = callProperties.authentication.auth_field_format[a].replace('{token}', useToken);
2482
+ authStr = authStr.replace('{username}', useUser);
2483
+ authStr = authStr.replace('{password}', usePass);
2484
+ authStrs.push(authStr);
2485
+ }
2486
+ } else {
2487
+ let authStr = callProperties.authentication.auth_field_format.replace('{token}', useToken);
2488
+ authStr = authStr.replace('{username}', useUser);
2489
+ authStr = authStr.replace('{password}', usePass);
2490
+ authStrs.push(authStr);
2491
+ }
2492
+ } else {
2493
+ for (let a = 0; a < authFormat.length; a += 1) {
2494
+ let authStr = authFormat[a].replace('{token}', useToken);
2495
+ authStr = authStr.replace('{username}', useUser);
2496
+ authStr = authStr.replace('{password}', usePass);
2497
+ authStrs.push(authStr);
2498
+ }
2499
+ }
2500
+
2501
+ return addAuthToRequest(request, authStrs, callProperties, (authReq, aerror) => {
2502
+ if (aerror) {
2503
+ return callback(aerror);
2504
+ }
2505
+
2506
+ // actually make the request now that the authentication has been added
2507
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2508
+ });
2509
+ }
2510
+
2511
+ if (authMethod === 'basic user_password') {
2512
+ // format the authentication string
2513
+ let useToken = staticToken;
2514
+ if (callProperties && callProperties.authentication && callProperties.authentication.token) {
2515
+ useToken = callProperties.authentication.token;
2516
+ }
2517
+
2518
+ const authStrs = [];
2519
+ if (callProperties && callProperties.authentication && callProperties.authentication.auth_field_format) {
2520
+ if (Array.isArray(callProperties.authentication.auth_field_format)) {
2521
+ for (let a = 0; a < callProperties.authentication.auth_field_format.length; a += 1) {
2522
+ let authStr = callProperties.authentication.auth_field_format[a].replace('{token}', useToken);
2523
+ authStr = authStr.replace('{username}', useUser);
2524
+ authStr = authStr.replace('{password}', usePass);
2525
+ authStrs.push(authStr);
2526
+ }
2527
+ } else {
2528
+ let authStr = callProperties.authentication.auth_field_format.replace('{token}', useToken);
2529
+ authStr = authStr.replace('{username}', useUser);
2530
+ authStr = authStr.replace('{password}', usePass);
2531
+ authStrs.push(authStr);
2532
+ }
2533
+ } else {
2534
+ for (let a = 0; a < authFormat.length; a += 1) {
2535
+ let authStr = authFormat[a].replace('{token}', useToken);
2536
+ authStr = authStr.replace('{username}', useUser);
2537
+ authStr = authStr.replace('{password}', usePass);
2538
+ authStrs.push(authStr);
2539
+ }
2540
+ }
2541
+
2542
+ return addAuthToRequest(request, authStrs, callProperties, (authReq, aerror) => {
2543
+ if (aerror) {
2544
+ return callback(aerror);
2545
+ }
2546
+
2547
+ // actually make the request now that the authentication has been added
2548
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2549
+ });
2550
+ }
2551
+ // if no_authentication, there is no change to the request!
2552
+
2553
+ // actual call to make the request
2554
+ return makeRequest(request, entitySchema, callProperties, null, 0, callback);
2555
+ } catch (e) {
2556
+ // handle any exception
2557
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue authentication');
2558
+ return callback(null, errorObj);
2559
+ }
2560
+ }
2561
+
2562
+ /*
2563
+ * INTERNAL FUNCTION: archiveResult is used to archive the results of the request along with
2564
+ * the times that were taken at various steps in the process (wait, request and overall)
2565
+ */
2566
+ function archiveResult(reqResult, callback) {
2567
+ const origin = `${id}-connectorRest-archiveResult`;
2568
+ log.trace(origin);
2569
+
2570
+ try {
2571
+ if (!archiving) {
2572
+ return 'skipped';
2573
+ }
2574
+
2575
+ // Add an Id to the result object that we are going to store in the database
2576
+ const data = reqResult;
2577
+ data._id = uuid.v4();
2578
+
2579
+ dbUtilInst.findAndModify(archiveColl, { _id: data._id }, null, data, true, null, true, (error, result) => {
2580
+ if (error) {
2581
+ const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Save To Database', [error], null, null, null);
2582
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2583
+ return callback(null, errorObj);
2584
+ }
2585
+
2586
+ return callback(result);
2587
+ });
2588
+
2589
+ // save the result object to the database
2590
+ // return brokers.persistence.save(archiveColl, data._id, data, (result, error) => {
2591
+ // if (error) {
2592
+ // const errorObj = transUtilInst.formatErrorObject(origin, 'Unable To Save To Database', [error], null, null, null);
2593
+ // log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2594
+ // return callback(null, errorObj);
2595
+ // }
2596
+
2597
+ // return callback(result);
2598
+ // });
2599
+ } catch (e) {
2600
+ // handle any exception
2601
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue archiving');
2602
+ return callback(null, errorObj);
2603
+ }
2604
+ }
2605
+
2606
+ /*
2607
+ * INTERNAL FUNCTION: handleEndResponse prepares the response to send back
2608
+ */
2609
+ function handleEndResponse(request, makeResp, overallTime, callback) {
2610
+ const origin = `${id}-connectorRest-handleEndResponse`;
2611
+ log.trace(origin);
2612
+
2613
+ try {
2614
+ log.info(`${origin}: Request call to ${request.header.method} ${request.header.path}: Call took: ${makeResp.tripTime || 0}`);
2615
+
2616
+ const reqResult = {
2617
+ phInstance,
2618
+ request,
2619
+ overallEnd: makeResp.tripTime || 0,
2620
+ response: makeResp
2621
+ };
2622
+
2623
+ // archive the results
2624
+ try {
2625
+ archiveResult(reqResult, (archived, aerror) => {
2626
+ if (aerror) {
2627
+ log.spam(`${origin}: Error Archived: ${JSON.stringify(aerror)}`);
2628
+ } else {
2629
+ log.spam(`${origin}: Result Archived: ${JSON.stringify(archived)}`);
2630
+ }
2631
+ });
2632
+ } catch (ex) {
2633
+ log.spam(`${origin}: Swallowing the archive error`);
2634
+ }
2635
+
2636
+ // If the request was successful
2637
+ if (makeResp && makeResp.code && Number(makeResp.code) >= 200 && Number(makeResp.code) <= 299) {
2638
+ const newResp = {
2639
+ icode: `AD.${makeResp.code}`,
2640
+ response: makeResp.response,
2641
+ headers: makeResp.headers,
2642
+ requestCookie: makeResp.requestCookie,
2643
+ reqHdr: makeResp.reqHdr,
2644
+ metrics: {
2645
+ code: makeResp.code,
2646
+ timeouts: makeResp.timeouts || 0,
2647
+ redirects: makeResp.redirects || 0,
2648
+ retries: makeResp.retries || 0,
2649
+ tripTime: parseFloat(makeResp.tripTime) || 0,
2650
+ isThrottling: false
2651
+ }
2652
+ };
2653
+ if (returnRequest) {
2654
+ newResp.request = makeResp.request;
2655
+ }
2656
+
2657
+ // return the response object
2658
+ return callback(newResp);
2659
+ }
2660
+
2661
+ // if not successful - check to see if response or error
2662
+ let errorObj = {};
2663
+ if (makeResp) {
2664
+ if (!returnRequest && makeResp.request) {
2665
+ delete makeResp.request;
2666
+ }
2667
+
2668
+ if (makeResp.code === -2) {
2669
+ errorObj = transUtilInst.formatErrorObject(origin, 'Request Timeout', [makeResp.code], makeResp.code, makeResp, null);
2670
+ } else {
2671
+ errorObj = transUtilInst.formatErrorObject(origin, 'Error On Request', [makeResp.code], makeResp.code, makeResp, null);
2672
+ }
2673
+
2674
+ errorObj.metrics = {
2675
+ code: makeResp.code,
2676
+ timeouts: makeResp.timeouts || 0,
2677
+ redirects: makeResp.redirects || 0,
2678
+ retries: makeResp.retries || 0,
2679
+ tripTime: parseFloat(makeResp.tripTime) || 0,
2680
+ isThrottling: false
2681
+ };
2682
+ } else {
2683
+ errorObj = transUtilInst.formatErrorObject(origin, 'Error On Request', ['unknown'], null, null, null);
2684
+ errorObj.metrics = {
2685
+ code: 'unknown',
2686
+ timeouts: 0,
2687
+ redirects: 0,
2688
+ retries: 0,
2689
+ tripTime: 0,
2690
+ isThrottling: false
2691
+ };
2692
+ }
2693
+ errorObj.response = makeResp.response;
2694
+
2695
+ if (makeResp && makeResp.code === -1) {
2696
+ if (typeof errorObj.IAPerror.raw_response === 'object') {
2697
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}: ${JSON.stringify(errorObj.IAPerror.raw_response)}`);
2698
+ } else {
2699
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}: ${errorObj.IAPerror.raw_response}`);
2700
+ }
2701
+ } else {
2702
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2703
+ }
2704
+
2705
+ return callback(null, errorObj);
2706
+ } catch (e) {
2707
+ // handle any exception
2708
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue handling response');
2709
+ errorObj.metrics = {};
2710
+ return callback(null, errorObj);
2711
+ }
2712
+ }
2713
+
2714
+ /*
2715
+ * INTERNAL FUNCTION: handleEndThrottleResponse prepares the response to send back
2716
+ * specifically if throttling is turned on - to clean up the request in the queue
2717
+ */
2718
+ function handleEndThrottleResponse(request, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, callback) {
2719
+ const origin = `${id}-connectorRest-handleEndThrottleResponse`;
2720
+ log.trace(origin);
2721
+
2722
+ const reqDiff = process.hrtime(reqTime);
2723
+ const reqEnd = (reqDiff[0] * NS_PER_SEC) + reqDiff[1];
2724
+ const overallDiff = process.hrtime(overallTime);
2725
+ const overallEnd = `${Math.round(((overallDiff[0] * NS_PER_SEC) + overallDiff[1]) / 1000000)}ms`;
2726
+
2727
+ try {
2728
+ log.info(`${origin}: Request ${claimedLic.request_id} Transaction ${myTransTime} call to ${request.header.method} ${request.header.path}: Call took ${reqEnd} - Overall Time: ${overallEnd}`);
2729
+
2730
+ // finish the turn so the space can be freed
2731
+ return throttleEng.finishTurn(claimedLic, reqEnd, (fres, ferror) => {
2732
+ const reqResult = {
2733
+ phInstance,
2734
+ requestId: claimedLic.request_id,
2735
+ transNum: claimedLic.transNum,
2736
+ request,
2737
+ start: claimedLic.start,
2738
+ end: new Date().getTime(),
2739
+ waitEnd,
2740
+ reqEnd,
2741
+ overallEnd,
2742
+ response: makeResp
2743
+ };
2744
+
2745
+ if (!ferror) {
2746
+ reqResult.end = fres.end;
2747
+ }
2748
+
2749
+ // archive the results
2750
+ try {
2751
+ archiveResult(reqResult, (archived, aerror) => {
2752
+ if (aerror) {
2753
+ log.spam(`${origin}: Error Archived: ${JSON.stringify(aerror)}`);
2754
+ } else {
2755
+ log.spam(`${origin}: Result Archived: ${JSON.stringify(archived)}`);
2756
+ }
2757
+ });
2758
+ } catch (ex) {
2759
+ log.spam(`${origin}: Swallowing the archive error`);
2760
+ }
2761
+
2762
+ // If the request was successful
2763
+ if (makeResp !== null && makeResp.code !== undefined
2764
+ && Number(makeResp.code) >= 200 && Number(makeResp.code) <= 299) {
2765
+ const newResp = {
2766
+ icode: `AD.${makeResp.code}`,
2767
+ response: makeResp.response,
2768
+ headers: makeResp.headers,
2769
+ requestCookie: makeResp.requestCookie,
2770
+ reqHdr: makeResp.reqHdr,
2771
+ event: claimedLic.event,
2772
+ metrics: {
2773
+ code: makeResp.code,
2774
+ timeouts: makeResp.timeouts || 0,
2775
+ redirects: makeResp.redirects || 0,
2776
+ retries: makeResp.retries || 0,
2777
+ tripTime: parseFloat(makeResp.tripTime) || 0,
2778
+ queueTime: waitEnd,
2779
+ isThrottling: true
2780
+ }
2781
+ };
2782
+ if (returnRequest) {
2783
+ newResp.request = makeResp.request;
2784
+ }
2785
+
2786
+ // return the response object
2787
+ return callback(newResp);
2788
+ }
2789
+
2790
+ // if not successful - check to see if response or error
2791
+ let errorObj = {};
2792
+ if (makeResp) {
2793
+ if (!returnRequest && makeResp.request) {
2794
+ delete makeResp.request;
2795
+ }
2796
+
2797
+ if (makeResp.code === -2) {
2798
+ errorObj = transUtilInst.formatErrorObject(origin, 'Request Timeout', [makeResp.code], makeResp.code, makeResp, null);
2799
+ } else {
2800
+ errorObj = transUtilInst.formatErrorObject(origin, 'Error On Request', [makeResp.code], makeResp.code, makeResp, null);
2801
+ }
2802
+
2803
+ errorObj.metrics = {
2804
+ code: makeResp.code,
2805
+ timeouts: makeResp.timeouts || 0,
2806
+ redirects: makeResp.redirects || 0,
2807
+ retries: makeResp.retries || 0,
2808
+ tripTime: parseFloat(makeResp.tripTime) || 0,
2809
+ queueTime: waitEnd,
2810
+ isThrottling: true
2811
+ };
2812
+ } else {
2813
+ errorObj = transUtilInst.formatErrorObject(origin, 'Error On Request', ['unknown'], null, null, null);
2814
+ errorObj.metrics = {
2815
+ code: 'unknown',
2816
+ timeouts: 0,
2817
+ redirects: 0,
2818
+ retries: 0,
2819
+ tripTime: 0,
2820
+ queueTime: waitEnd,
2821
+ isThrottling: true
2822
+ };
2823
+ }
2824
+ errorObj.response = makeResp.response;
2825
+
2826
+ if (makeResp && makeResp.code === -1) {
2827
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}: ${errorObj.IAPerror.raw_response}`);
2828
+ } else {
2829
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
2830
+ }
2831
+
2832
+ errorObj.event = claimedLic.event;
2833
+ return callback(null, errorObj);
2834
+ });
2835
+ } catch (e) {
2836
+ // handle any exception
2837
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue handling response');
2838
+ errorObj.metrics = {};
2839
+ errorObj.event = claimedLic.event;
2840
+ return callback(null, errorObj);
2841
+ }
2842
+ }
2843
+
2844
+ /*
2845
+ * INTERNAL FUNCTION: re-attempt after an invalid response and figure out
2846
+ * how to handle the new response.
2847
+ */
2848
+ function retryInvalidResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback) {
2849
+ const origin = `${id}-connectorRest-retryInvalidResponse`;
2850
+ log.trace(origin);
2851
+
2852
+ try {
2853
+ // Need to get a new token and retry the request
2854
+ return requestAuthenticate(request, entitySchema, request.tokenUsed, callProperties, (mres, merror) => {
2855
+ if (merror) {
2856
+ return callback(null, merror);
2857
+ }
2858
+
2859
+ let retryError = limitRetryError;
2860
+ let retries = numRetries;
2861
+ if (callProperties && callProperties.request && callProperties.request.limit_retry_error) {
2862
+ if (typeof callProperties.request.limit_retry_error === 'number'
2863
+ || typeof callProperties.request.limit_retry_error === 'string') {
2864
+ retryError = [Number(callProperties.request.limit_retry_error)];
2865
+ } else if (Array.isArray(callProperties.request.limit_retry_error)) {
2866
+ retryError = [];
2867
+ for (let l = 0; l < callProperties.request.limit_retry_error.length; l += 1) {
2868
+ if (typeof callProperties.request.limit_retry_error[l] === 'number') {
2869
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
2870
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
2871
+ && callProperties.request.limit_retry_error[l].indexOf('-') < 0) {
2872
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
2873
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
2874
+ && callProperties.request.limit_retry_error[l].indexOf('-') >= 0) {
2875
+ const srange = Number(callProperties.request.limit_retry_error[l].split('-')[0]);
2876
+ const erange = Number(callProperties.request.limit_retry_error[l].split('-')[1]);
2877
+ for (let r = srange; r <= erange; r += 1) {
2878
+ retryError.push(r);
2879
+ }
2880
+ }
2881
+ }
2882
+ }
2883
+ }
2884
+ if (callProperties && callProperties.request && callProperties.request.number_retries) {
2885
+ retries = callProperties.request.number_retries;
2886
+ }
2887
+
2888
+ // if the response is valid - good data or legitimate data error
2889
+ // stop trying if we have tried enough
2890
+ if (numTries > retries || (mres !== null && mres.code !== undefined && !retryError.includes(Number(mres.code))
2891
+ && (authMethod !== 'request_token' || Number(mres.code) !== Number(tokenError)))) {
2892
+ const newresp = mres;
2893
+ newresp.retries = numTries;
2894
+
2895
+ if (throttleEnabled && (!callProperties || !callProperties.host)) {
2896
+ return handleEndThrottleResponse(request, myTransTime, claimedLic, newresp, reqTime, overallTime, waitEnd, callback);
2897
+ }
2898
+
2899
+ return handleEndResponse(request, newresp, overallTime, callback);
2900
+ }
2901
+ if (mres !== null && mres.code !== undefined && Number(mres.code) === Number(tokenError) && authMethod === 'request_token') {
2902
+ // if we took a invalid token error - System out of licenses - try again
2903
+ return handleInvalidToken(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, numTries + 1, entitySchema, callback);
2904
+ }
2905
+ if (mres !== null && mres.code !== undefined && retryError.includes(Number(mres.code))) {
2906
+ // if we took a throughput limitation - System out of licenses - try again
2907
+ return handleLimitResponse(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, numTries + 1, entitySchema, callback);
2908
+ }
2909
+
2910
+ // if we took an http error - like aborting
2911
+ return handleAbortResponse(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, numTries + 1, entitySchema, callback);
2912
+ });
2913
+ } catch (e) {
2914
+ // handle any exception
2915
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue retrying invalid');
2916
+ return callback(null, errorObj);
2917
+ }
2918
+ }
2919
+
2920
+ /*
2921
+ * INTERNAL FUNCTION: handleInvalidToken handles issues when a token has timed out
2922
+ */
2923
+ function handleInvalidToken(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback) {
2924
+ const origin = `${id}-connectorRest-handleInvalidToken`;
2925
+ log.trace(origin);
2926
+ let reqDiff = process.hrtime(reqTime);
2927
+ let reqEnd = (reqDiff[0] * NS_PER_SEC) + reqDiff[1];
2928
+ log.warn(`${origin}: Request ${claimedLic.request_id} Transaction ${myTransTime} invalid token appear to be hit after ${reqEnd} nanoseconds - Entering handling invalid token`);
2929
+ reqDiff = undefined;
2930
+ reqEnd = undefined;
2931
+
2932
+ try {
2933
+ // Need to retry the request - will pull new token
2934
+ return retryInvalidResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback);
2935
+ } catch (e) {
2936
+ // handle any exception
2937
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue handling invalid token');
2938
+ return callback(null, errorObj);
2939
+ }
2940
+ }
2941
+
2942
+ /*
2943
+ * INTERNAL FUNCTION: handleLimitResponse handles throughput issues with System
2944
+ */
2945
+ function handleLimitResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback) {
2946
+ const origin = `${id}-connectorRest-handleLimitResponse`;
2947
+ log.trace(origin);
2948
+ let reqDiff = process.hrtime(reqTime);
2949
+ let reqEnd = (reqDiff[0] * NS_PER_SEC) + reqDiff[1];
2950
+ log.warn(`${origin}: Request ${claimedLic.request_id} Transaction ${myTransTime} limits appear to be hit after ${reqEnd} nanoseconds - Entering handling limiting requests`);
2951
+ reqDiff = undefined;
2952
+ reqEnd = undefined;
2953
+ let myTimeout = attemptTimeout;
2954
+
2955
+ // if there is a timeout from the action (schema) use it instead
2956
+ if (entitySchema && entitySchema.timeout && entitySchema.timeout > 0) {
2957
+ myTimeout = entitySchema.timeout;
2958
+ }
2959
+ if (callProperties && callProperties.request && callProperties.request.attempt_timeout) {
2960
+ myTimeout = callProperties.request.attempt_timeout;
2961
+ }
2962
+
2963
+ try {
2964
+ // Run this after an interval to see if throughput issues clear
2965
+ const timeoutObject = setTimeout(() => {
2966
+ log.debug(`${origin}: Processing Re-request`);
2967
+
2968
+ // nothing to do but retry
2969
+ return retryInvalidResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback);
2970
+ }, myTimeout / 5);
2971
+ } catch (e) {
2972
+ // handle any exception
2973
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue handling limit');
2974
+ return callback(null, errorObj);
2975
+ }
2976
+ }
2977
+
2978
+ /*
2979
+ * INTERNAL FUNCTION: handleAbortResponse handles timeout issues with System
2980
+ * like when the system has died
2981
+ */
2982
+ function handleAbortResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback) {
2983
+ const origin = `${id}-connectorRest-handleAbortResponse`;
2984
+ log.trace(origin);
2985
+ let reqDiff = process.hrtime(reqTime);
2986
+ let reqEnd = (reqDiff[0] * NS_PER_SEC) + reqDiff[1];
2987
+ log.warn(`${origin}: Request ${claimedLic.request_id} Transaction ${myTransTime} aborted after ${reqEnd} nanoseconds - Entering handling aborted requests`);
2988
+ reqDiff = undefined;
2989
+ reqEnd = undefined;
2990
+
2991
+ try {
2992
+ // wait for System to come back with a good healthcheck
2993
+ return waitForSystem(callProperties)
2994
+ .then(() => {
2995
+ log.debug(`${origin}: Cleared to reattempt`);
2996
+
2997
+ // restart the current request
2998
+ return retryInvalidResponse(request, callProperties, myTransTime, claimedLic, makeResp, reqTime, overallTime, waitEnd, numTries, entitySchema, callback);
2999
+ });
3000
+ } catch (e) {
3001
+ // handle any exception
3002
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue handling abort');
3003
+ return callback(null, errorObj);
3004
+ }
3005
+ }
3006
+
3007
+ /*
3008
+ * INTERNAL FUNCTION: noQueueRequest is used to perform normal requests.
3009
+ * Normal being request that can run as they come in - there are no license or
3010
+ * throttling limitations on these requests.
3011
+ */
3012
+ function noQueueRequest(request, callProperties, overallTime, entitySchema, callback) {
3013
+ const origin = `${id}-connectorRest-noQueueRequest`;
3014
+ log.trace(origin);
3015
+
3016
+ try {
3017
+ // call to make the request
3018
+ return requestAuthenticate(request, entitySchema, null, callProperties, (mres, merror) => {
3019
+ if (merror) {
3020
+ return callback(null, merror);
3021
+ }
3022
+
3023
+ let retryError = limitRetryError;
3024
+ let retries = numRetries;
3025
+ if (callProperties && callProperties.request && callProperties.request.limit_retry_error) {
3026
+ if (typeof callProperties.request.limit_retry_error === 'number'
3027
+ || typeof callProperties.request.limit_retry_error === 'string') {
3028
+ retryError = [Number(callProperties.request.limit_retry_error)];
3029
+ } else if (Array.isArray(callProperties.request.limit_retry_error)) {
3030
+ retryError = [];
3031
+ for (let l = 0; l < callProperties.request.limit_retry_error.length; l += 1) {
3032
+ if (typeof callProperties.request.limit_retry_error[l] === 'number') {
3033
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
3034
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
3035
+ && callProperties.request.limit_retry_error[l].indexOf('-') < 0) {
3036
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
3037
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
3038
+ && callProperties.request.limit_retry_error[l].indexOf('-') >= 0) {
3039
+ const srange = Number(callProperties.request.limit_retry_error[l].split('-')[0]);
3040
+ const erange = Number(callProperties.request.limit_retry_error[l].split('-')[1]);
3041
+ for (let r = srange; r <= erange; r += 1) {
3042
+ retryError.push(r);
3043
+ }
3044
+ }
3045
+ }
3046
+ }
3047
+ }
3048
+ if (callProperties && callProperties.request && callProperties.request.number_retries) {
3049
+ retries = callProperties.request.number_retries;
3050
+ }
3051
+
3052
+ // if invalid token, handle that and retry
3053
+ if (mres !== null && mres.code !== undefined && Number(mres.code) === Number(tokenError)
3054
+ && authMethod === 'request_token') {
3055
+ // if we took an invalid token - try one more time
3056
+ return handleInvalidToken(request, callProperties, 0, { request_id: 0 }, mres, overallTime, overallTime, 0, 1, entitySchema, callback);
3057
+ }
3058
+
3059
+ // if the response is valid - good data or legitimate data error
3060
+ if (retries < 1 || (mres !== null && mres.code !== undefined && !retryError.includes(Number(mres.code))
3061
+ && (authMethod !== 'request_token' || Number(mres.code) !== Number(tokenError)))) {
3062
+ return handleEndResponse(request, mres, overallTime, callback);
3063
+ }
3064
+
3065
+ if (mres !== null && mres.code !== undefined && retryError.includes(Number(mres.code))) {
3066
+ // if we took a out of licenses - try one more time
3067
+ return handleLimitResponse(request, callProperties, 0, { request_id: 0 }, mres, overallTime, overallTime, 0, 1, entitySchema, callback);
3068
+ }
3069
+
3070
+ // if we took an http error - like aborting
3071
+ return handleAbortResponse(request, callProperties, 0, { request_id: 0 }, mres, overallTime, overallTime, 0, 1, entitySchema, callback);
3072
+ });
3073
+ } catch (e) {
3074
+ // handle any exception
3075
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue with request');
3076
+ return callback(null, errorObj);
3077
+ }
3078
+ }
3079
+
3080
+ /*
3081
+ * INTERNAL FUNCTION: makeThrottleRequest is used to perform throttled request.
3082
+ * Throttled request means that Pronghorn is only allowed to run X concurrent
3083
+ * requests to the system.
3084
+ *
3085
+ * It will put the request in a queue item, wait until
3086
+ * it can get its turn, then run the request and return the results
3087
+ */
3088
+ function queueThrottleRequest(request, callProperties, myRequestId, myTransTime, priority, event, overallTime, entitySchema, callback) {
3089
+ const origin = `${id}-connectorRest-queueThrottleRequest`;
3090
+ log.trace(`${origin} - ${myRequestId}`);
3091
+
3092
+ try {
3093
+ // request place in Queue
3094
+ return throttleEng.requestQueueItem(myRequestId, myTransTime, priority, event, (myItem, qerror) => {
3095
+ if (qerror) {
3096
+ return callback(null, qerror);
3097
+ }
3098
+
3099
+ let waitStart = process.hrtime();
3100
+
3101
+ // check if it is my turn
3102
+ return throttleEng.waitingMyTurn(myItem, (claimedLic, werror) => {
3103
+ let waitDiff = process.hrtime(waitStart);
3104
+ const waitEnd = (waitDiff[0] * NS_PER_SEC) + waitDiff[1];
3105
+ log.debug(`${origin}: Request ${myRequestId} Transaction ${myTransTime} wait took: ${waitEnd} nanoseconds`);
3106
+ const reqTime = process.hrtime();
3107
+
3108
+ if (werror) {
3109
+ return callback(null, werror);
3110
+ }
3111
+
3112
+ // clean up used variables from memory setting to undefined minimizes memory
3113
+ waitStart = undefined;
3114
+ waitDiff = undefined;
3115
+
3116
+ // call to make the request
3117
+ return requestAuthenticate(request, entitySchema, null, callProperties, (mres, merror) => {
3118
+ if (merror) {
3119
+ return callback(null, merror);
3120
+ }
3121
+
3122
+ let retryError = limitRetryError;
3123
+ let retries = numRetries;
3124
+ if (callProperties && callProperties.request && callProperties.request.limit_retry_error) {
3125
+ if (typeof callProperties.request.limit_retry_error === 'number'
3126
+ || typeof callProperties.request.limit_retry_error === 'string') {
3127
+ retryError = [Number(callProperties.request.limit_retry_error)];
3128
+ } else if (Array.isArray(callProperties.request.limit_retry_error)) {
3129
+ retryError = [];
3130
+ for (let l = 0; l < callProperties.request.limit_retry_error.length; l += 1) {
3131
+ if (typeof callProperties.request.limit_retry_error[l] === 'number') {
3132
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
3133
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
3134
+ && callProperties.request.limit_retry_error[l].indexOf('-') < 0) {
3135
+ retryError.push(Number(callProperties.request.limit_retry_error[l]));
3136
+ } else if (typeof callProperties.request.limit_retry_error[l] === 'string'
3137
+ && callProperties.request.limit_retry_error[l].indexOf('-') >= 0) {
3138
+ const srange = Number(callProperties.request.limit_retry_error[l].split('-')[0]);
3139
+ const erange = Number(callProperties.request.limit_retry_error[l].split('-')[1]);
3140
+ for (let r = srange; r <= erange; r += 1) {
3141
+ retryError.push(r);
3142
+ }
3143
+ }
3144
+ }
3145
+ }
3146
+ }
3147
+ if (callProperties && callProperties.request && callProperties.request.number_retries) {
3148
+ retries = callProperties.request.number_retries;
3149
+ }
3150
+
3151
+ // if invalid token, handle that and retry
3152
+ if (mres !== null && mres.code !== undefined && Number(mres.code) === Number(tokenError)
3153
+ && authMethod === 'request_token') {
3154
+ // if we took an invalid token - try one more time
3155
+ return handleInvalidToken(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, 1, entitySchema, callback);
3156
+ }
3157
+
3158
+ // if the response is valid - good data or legitimate data error
3159
+ if (retries < 1 || (mres !== null && mres.code !== undefined && !retryError.includes(Number(mres.code))
3160
+ && (authMethod !== 'request_token' || Number(mres.code) !== Number(tokenError)))) {
3161
+ return handleEndThrottleResponse(request, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, callback);
3162
+ }
3163
+
3164
+ if (mres !== null && mres.code !== undefined && retryError.includes(Number(mres.code))) {
3165
+ // if we took a out of licenses - try one more time
3166
+ return handleLimitResponse(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, 1, entitySchema, callback);
3167
+ }
3168
+
3169
+ // if we took an http error - like aborting
3170
+ return handleAbortResponse(request, callProperties, myTransTime, claimedLic, mres, reqTime, overallTime, waitEnd, 1, entitySchema, callback);
3171
+ });
3172
+ });
3173
+ });
3174
+ } catch (e) {
3175
+ // handle any exception
3176
+ const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue with queue request');
3177
+ return callback(null, errorObj);
3178
+ }
3179
+ }
3180
+
3181
+ class ConnectorRest {
3182
+ /**
3183
+ * Connector
3184
+ * @constructor
3185
+ */
3186
+ constructor(prongid, properties, transUtilCl, propUtilCl, dbUtilCl) {
3187
+ this.myid = prongid;
3188
+ id = prongid;
3189
+
3190
+ this.transUtil = transUtilCl;
3191
+ transUtilInst = this.transUtil;
3192
+ this.propUtil = propUtilCl;
3193
+ propUtilInst = this.propUtil;
3194
+ this.dbUtil = dbUtilCl;
3195
+ dbUtilInst = this.dbUtil;
3196
+
3197
+ // this uniquely identifies this adapter on this pronghorn system
3198
+ phInstance = `${id}-${os.hostname()}`;
3199
+ archiveColl = id + archiveColl;
3200
+ this.tlockInst = new AsyncLockCl();
3201
+ tlock = this.tlockInst;
3202
+ this.hlockInst = new AsyncLockCl();
3203
+ hlock = this.hlockInst;
3204
+ this.hclockInst = new AsyncLockCl();
3205
+ hclock = this.hclockInst;
3206
+ this.slock = new AsyncLockCl();
3207
+ crest = this;
3208
+
3209
+ // set up the properties I care about
3210
+ this.refreshProperties(properties);
3211
+
3212
+ this.throttleEngInst = new ThrottleCl(id, properties, this.transUtil, this.dbUtil);
3213
+ throttleEng = this.throttleEngInst;
3214
+
3215
+ this.connect((status) => {
3216
+ const origin = `${this.myid}-connectorRest-constructor`;
3217
+ log.info(`${origin}: connect status ${status}`);
3218
+ });
3219
+ }
3220
+
3221
+ /* CONNECTOR ENGINE EXTERNAL FUNCTIONS */
3222
+ /**
3223
+ * refreshProperties is used to set up all of the properties for the connector.
3224
+ * It allows properties to be changed later by simply calling refreshProperties rather
3225
+ * than having to restart the connector.
3226
+ *
3227
+ * @function refreshProperties
3228
+ * @param {Object} properties - an object containing all of the properties
3229
+ */
3230
+ refreshProperties(properties) {
3231
+ const origin = `${this.myid}-connectorRest-refreshProperties`;
3232
+ log.trace(origin);
3233
+ props = properties;
3234
+
3235
+ if (!props) {
3236
+ log.error(`${origin}: Connector received no properties!`);
3237
+ return;
3238
+ }
3239
+
3240
+ // REPLACE WITH SCHEMA VALIDATOR???
3241
+ // get all of my properties - use for initial connectivity check
3242
+ // set the host (required - default is null)
3243
+ if (typeof props.host === 'string') {
3244
+ host = props.host;
3245
+ }
3246
+
3247
+ // set the port (required - default is null)
3248
+ if (typeof props.port === 'number' || typeof props.port === 'string') {
3249
+ port = Number(props.port);
3250
+ }
3251
+
3252
+ // set the base path (optional - default is null)
3253
+ if (typeof props.base_path === 'string') {
3254
+ basepath = props.base_path;
3255
+ }
3256
+
3257
+ // set the version (optional - default is null)
3258
+ if (typeof props.version === 'string') {
3259
+ version = props.version;
3260
+ }
3261
+
3262
+ if (props.authentication) {
3263
+ // set the authentication method (required - default is null)
3264
+ if (typeof props.authentication.auth_method === 'string') {
3265
+ authMethod = props.authentication.auth_method;
3266
+ }
3267
+
3268
+ // set the username (required - default is null)
3269
+ if (typeof props.authentication.username === 'string') {
3270
+ username = props.authentication.username;
3271
+ }
3272
+
3273
+ // set the password (required - default is null)
3274
+ if (typeof props.authentication.password === 'string') {
3275
+ password = props.authentication.password;
3276
+ }
3277
+
3278
+ // set the static token (required - default is null)
3279
+ if (typeof props.authentication.token === 'string') {
3280
+ staticToken = props.authentication.token;
3281
+ }
3282
+
3283
+ // set the token user field (required - default is username)
3284
+ if (typeof props.authentication.token_user_field === 'string') {
3285
+ tokenUserField = props.authentication.token_user_field;
3286
+ }
3287
+
3288
+ // set the token password field (required - default is password)
3289
+ if (typeof props.authentication.token_password_field === 'string') {
3290
+ tokenPwdField = props.authentication.token_password_field;
3291
+ }
3292
+
3293
+ // set the token result field (required - default is token)
3294
+ if (typeof props.authentication.token_result_field === 'string') {
3295
+ tokenResField = props.authentication.token_result_field;
3296
+ }
3297
+
3298
+ // set the token URI path (required - default is null)
3299
+ if (typeof props.authentication.token_URI_path === 'string') {
3300
+ tokenPath = props.authentication.token_URI_path;
3301
+ }
3302
+
3303
+ // set the invalid token error (optional - default is null)
3304
+ if (typeof props.authentication.invalid_token_error === 'number'
3305
+ || typeof props.authentication.invalid_token_error === 'string') {
3306
+ tokenError = Number(props.authentication.invalid_token_error);
3307
+ }
3308
+
3309
+ // set the token timeout (optional - default is null)
3310
+ if (typeof props.authentication.token_timeout === 'number'
3311
+ || typeof props.authentication.token_timeout === 'string') {
3312
+ tokenTimeout = Number(props.authentication.token_timeout);
3313
+ }
3314
+
3315
+ // set the token cache (required - default is local)
3316
+ if (typeof props.authentication.token_cache === 'string') {
3317
+ tokenCache = props.authentication.token_cache;
3318
+ }
3319
+
3320
+ // set the auth field (required - default is null)
3321
+ if (typeof props.authentication.auth_field === 'string') {
3322
+ authField = [props.authentication.auth_field];
3323
+ } else if (Array.isArray(props.authentication.auth_field)) {
3324
+ authField = props.authentication.auth_field;
3325
+ }
3326
+
3327
+ // set the auth format (required - default is null)
3328
+ if (typeof props.authentication.auth_field_format === 'string') {
3329
+ authFormat = [props.authentication.auth_field_format];
3330
+ } else if (Array.isArray(props.authentication.auth_field_format)) {
3331
+ authFormat = props.authentication.auth_field_format;
3332
+ }
3333
+
3334
+ // set the auth logging (optional - default is false)
3335
+ if (typeof props.authentication.auth_logging === 'boolean') {
3336
+ authLogging = props.authentication.auth_logging;
3337
+ }
3338
+
3339
+ // set the client id (required - default is null)
3340
+ if (typeof props.authentication.client_id === 'string') {
3341
+ clientId = props.authentication.client_id;
3342
+ }
3343
+
3344
+ // set the client secret (required - default is null)
3345
+ if (typeof props.authentication.client_secret === 'string') {
3346
+ clientSecret = props.authentication.client_secret;
3347
+ }
3348
+
3349
+ // set the grant type (required - default is null)
3350
+ if (typeof props.authentication.grant_type === 'string') {
3351
+ grantType = props.authentication.grant_type;
3352
+ }
3353
+ }
3354
+
3355
+ // set the stub mode (optional - default is false)
3356
+ if (typeof props.stub === 'boolean') {
3357
+ stub = props.stub;
3358
+ }
3359
+
3360
+ // set the protocol (optional - default is http)
3361
+ if (typeof props.protocol === 'string') {
3362
+ protocol = props.protocol;
3363
+ }
3364
+
3365
+ // set the healthcheck path (required - default is null)
3366
+ if (props.healthcheck) {
3367
+ if (typeof props.healthcheck.URI_Path === 'string') {
3368
+ healthcheckpath = props.healthcheck.URI_Path;
3369
+ }
3370
+ }
3371
+
3372
+ if (props.throttle) {
3373
+ // set the throttle enabled (optional - default is false)
3374
+ if (typeof props.throttle.throttle_enabled === 'boolean') {
3375
+ throttleEnabled = props.throttle.throttle_enabled;
3376
+ }
3377
+
3378
+ // set the throttle number of pronghorns (optional - default is 1)
3379
+ if (props.throttle.number_pronghorns && Number(props.throttle.number_pronghorns) >= 1) {
3380
+ numberPhs = Number(props.throttle.number_pronghorns);
3381
+ }
3382
+ }
3383
+
3384
+ if (props.request) {
3385
+ // set the number of redirects (optional - default is 0)
3386
+ if (typeof props.request.number_redirects === 'number'
3387
+ || typeof props.request.number_redirects === 'string') {
3388
+ numRedirects = Number(props.request.number_redirects);
3389
+ }
3390
+
3391
+ // set the number of retries (optional - default is 3)
3392
+ if (typeof props.request.number_retries === 'number'
3393
+ || typeof props.request.number_retries === 'string') {
3394
+ numRetries = Number(props.request.number_retries);
3395
+ }
3396
+
3397
+ // set the request retry error (optional - default is 0)
3398
+ if (typeof props.request.limit_retry_error === 'number'
3399
+ || typeof props.request.limit_retry_error === 'string') {
3400
+ limitRetryError = [Number(props.request.limit_retry_error)];
3401
+ } else if (Array.isArray(props.request.limit_retry_error)) {
3402
+ limitRetryError = [];
3403
+ for (let l = 0; l < props.request.limit_retry_error.length; l += 1) {
3404
+ if (typeof props.request.limit_retry_error[l] === 'number') {
3405
+ limitRetryError.push(Number(props.request.limit_retry_error[l]));
3406
+ } else if (typeof props.request.limit_retry_error[l] === 'string'
3407
+ && props.request.limit_retry_error[l].indexOf('-') < 0) {
3408
+ limitRetryError.push(Number(props.request.limit_retry_error[l]));
3409
+ } else if (typeof props.request.limit_retry_error[l] === 'string'
3410
+ && props.request.limit_retry_error[l].indexOf('-') >= 0) {
3411
+ const srange = Number(props.request.limit_retry_error[l].split('-')[0]);
3412
+ const erange = Number(props.request.limit_retry_error[l].split('-')[1]);
3413
+ for (let r = srange; r <= erange; r += 1) {
3414
+ limitRetryError.push(r);
3415
+ }
3416
+ }
3417
+ }
3418
+ }
3419
+
3420
+ // set the request attempt timeout (optional - default is 5000)
3421
+ if (typeof props.request.attempt_timeout === 'number'
3422
+ || typeof props.request.attempt_timeout === 'string') {
3423
+ attemptTimeout = Number(props.request.attempt_timeout);
3424
+ }
3425
+
3426
+ // set the request archiving flag (optional - default is false)
3427
+ if (props.request.global_request && typeof props.request.global_request === 'object') {
3428
+ globalRequest = props.request.global_request;
3429
+ }
3430
+
3431
+ // set the request healthcheck on timeout flag (optional - default is false)
3432
+ if (typeof props.request.healthcheck_on_timeout === 'boolean') {
3433
+ healthcheckOnTimeout = props.request.healthcheck_on_timeout;
3434
+ }
3435
+
3436
+ // set the request archiving flag (optional - default is false)
3437
+ if (typeof props.request.archiving === 'boolean') {
3438
+ archiving = props.request.archiving;
3439
+ }
3440
+
3441
+ // set the request return request (optional - default is false)
3442
+ if (typeof props.request.return_request === 'boolean') {
3443
+ returnRequest = props.request.return_request;
3444
+ }
3445
+ }
3446
+
3447
+ if (props.proxy) {
3448
+ // set the proxy enabled (optional - default is false)
3449
+ if (typeof props.proxy.enabled === 'boolean') {
3450
+ proxyEnabled = props.proxy.enabled;
3451
+ }
3452
+
3453
+ // set the proxy host (optional - default is null)
3454
+ if (typeof props.proxy.host === 'string') {
3455
+ proxyHost = props.proxy.host;
3456
+ }
3457
+
3458
+ // set the proxy port (optional - default is null)
3459
+ if (typeof props.proxy.port === 'number' || typeof props.proxy.port === 'string') {
3460
+ proxyPort = Number(props.proxy.port);
3461
+ }
3462
+
3463
+ // set the proxy protocol (optional - default is same as protocol)
3464
+ if (typeof props.proxy.protocol === 'string') {
3465
+ proxyProtocol = props.proxy.protocol;
3466
+ } else {
3467
+ proxyProtocol = protocol;
3468
+ }
3469
+
3470
+ // set the proxy username (optional - default is null)
3471
+ if (typeof props.proxy.username === 'string') {
3472
+ proxyUser = props.proxy.username;
3473
+ }
3474
+
3475
+ // set the proxy password (optional - default is null)
3476
+ if (typeof props.proxy.password === 'string') {
3477
+ proxyPassword = props.proxy.password;
3478
+ }
3479
+ }
3480
+
3481
+ if (props.ssl) {
3482
+ // set the ssl enabled (optional - default is false)
3483
+ if (typeof props.ssl.enabled === 'boolean') {
3484
+ sslEnabled = props.ssl.enabled;
3485
+ }
3486
+
3487
+ // set the ssl ecdhCurve (optional - default is false)
3488
+ if (typeof props.ssl.ecdhCurve === 'string' && props.ssl.ecdhCurve === 'auto') {
3489
+ ecdhAuto = true;
3490
+ }
3491
+
3492
+ // set the ssl accept invalid cert (optional - default is false)
3493
+ if (typeof props.ssl.accept_invalid_cert === 'boolean') {
3494
+ sslAcceptInvalid = props.ssl.accept_invalid_cert;
3495
+ }
3496
+
3497
+ // set the ssl ca file (optional - default is null)
3498
+ if (typeof props.ssl.ca_file === 'string') {
3499
+ sslCAFile = props.ssl.ca_file;
3500
+ }
3501
+
3502
+ // set the ssl key file (optional - default is null)
3503
+ if (typeof props.ssl.key_file === 'string') {
3504
+ sslKeyFile = props.ssl.key_file;
3505
+ }
3506
+
3507
+ // set the ssl cert file (optional - default is null)
3508
+ if (typeof props.ssl.cert_file === 'string') {
3509
+ sslCertFile = props.ssl.cert_file;
3510
+ }
3511
+
3512
+ // set the ssl ciphers (optional - default is null)
3513
+ if (typeof props.ssl.ciphers === 'string') {
3514
+ sslCiphers = props.ssl.ciphers;
3515
+ }
3516
+
3517
+ // set the ssl ciphers (optional - default is null)
3518
+ if (typeof props.ssl.secure_protocol === 'string') {
3519
+ secureProtocol = props.ssl.secure_protocol;
3520
+ }
3521
+ }
3522
+
3523
+ // if this is truly a refresh and we hae a throttle engine, refresh it
3524
+ if (this.throttleEngInst) {
3525
+ this.throttleEngInst.refreshProperties(properties);
3526
+ }
3527
+ }
3528
+
3529
+ /**
3530
+ * Connect function is used to verify that everything required to connect and communicate
3531
+ * has been provided.
3532
+ *
3533
+ * @function connect
3534
+ * @param {Function} callback - a callback function to return the result - connected?
3535
+ */
3536
+ connect(callback) {
3537
+ const origin = `${this.myid}-connectorRest-connect`;
3538
+ log.trace(origin);
3539
+
3540
+ // if throttling with the database or archiving, need to make sure the database is there
3541
+ if ((throttleEnabled && numberPhs > 1) || archiving) {
3542
+ // CLEAN UP LOCK & LICENSES
3543
+ return waitForMongo()
3544
+ .then(() => {
3545
+ if (archiving) {
3546
+ const colRes = this.dbUtil.createCollection(archiveColl, (err, res) => {
3547
+ if (err) {
3548
+ log.error(`${origin}: collection ${archiveColl} could not be created.`);
3549
+ return callback(false);
3550
+ }
3551
+ log.info(`${origin}: Result collection check passed`);
3552
+ });
3553
+ }
3554
+
3555
+ if (throttleEnabled) {
3556
+ this.throttleEngInst.verifyReady((res, rerror) => {
3557
+ if (rerror) {
3558
+ return callback(false);
3559
+ }
3560
+
3561
+ log.debug(`${origin}: Throttle verify - ${res}`);
3562
+ });
3563
+ }
3564
+
3565
+ // Make sure all the properties are loaded
3566
+ if (!host) {
3567
+ log.error(`${origin}: FAILED TO LOAD - missing host`);
3568
+ return callback(false);
3569
+ }
3570
+ if (!port) {
3571
+ log.error(`${origin}: FAILED TO LOAD - missing port`);
3572
+ return callback(false);
3573
+ }
3574
+ if (authMethod === 'static_token') {
3575
+ if (!staticToken) {
3576
+ log.error(`${origin}: FAILED TO LOAD - missing static token`);
3577
+ return callback(false);
3578
+ }
3579
+ } else if (authMethod === 'request_token' || authMethod === 'basic user_password') {
3580
+ if (!username) {
3581
+ log.error(`${origin}: FAILED TO LOAD - missing username`);
3582
+ return callback(false);
3583
+ }
3584
+ if (!password) {
3585
+ log.error(`${origin}: FAILED TO LOAD - missing password`);
3586
+ return callback(false);
3587
+ }
3588
+ }
3589
+ if (sslEnabled && !sslAcceptInvalid && !sslCAFile) {
3590
+ log.error(`${origin}: FAILED TO LOAD - missing required CA File`);
3591
+ return callback(false);
3592
+ }
3593
+
3594
+ return callback(true);
3595
+ });
3596
+ }
3597
+
3598
+ // if throttling without database - just need to verify throttle engine is ready
3599
+ if (throttleEnabled) {
3600
+ this.throttleEngInst.verifyReady((res, rerror) => {
3601
+ if (rerror) {
3602
+ return callback(false);
3603
+ }
3604
+
3605
+ log.debug(`${origin}: Throttle verify - ${res}`);
3606
+ });
3607
+ }
3608
+
3609
+ // Make sure all the properties are loaded
3610
+ if (!host) {
3611
+ log.error(`${origin}: FAILED TO LOAD - missing host`);
3612
+ return callback(false);
3613
+ }
3614
+ if (!port) {
3615
+ log.error(`${origin}: FAILED TO LOAD - missing port`);
3616
+ return callback(false);
3617
+ }
3618
+ if (authMethod === 'static_token') {
3619
+ if (!staticToken) {
3620
+ log.error(`${origin}: FAILED TO LOAD - missing static token`);
3621
+ return callback(false);
3622
+ }
3623
+ } else if (authMethod === 'request_token' || authMethod === 'basic user_password') {
3624
+ if (!username) {
3625
+ log.error(`${origin}: FAILED TO LOAD - missing username`);
3626
+ return callback(false);
3627
+ }
3628
+ if (!password) {
3629
+ log.error(`${origin}: FAILED TO LOAD - missing password`);
3630
+ return callback(false);
3631
+ }
3632
+ }
3633
+ if (sslEnabled && !sslAcceptInvalid && !sslCAFile) {
3634
+ log.error(`${origin}: FAILED TO LOAD - missing required CA File`);
3635
+ return callback(false);
3636
+ }
3637
+
3638
+ return callback(true);
3639
+ }
3640
+
3641
+ /**
3642
+ * HealthCheck function is used to provide Pronghorn the status of this adapter.
3643
+ *
3644
+ * @function healthCheck
3645
+ * @param {Object} healthSchema - the schema for the healthcheck (optional)
3646
+ * @param {String} payload - the contents to send with the healthcheck
3647
+ * (optional)
3648
+ * @param {Object} headers - this allows for additional headers to
3649
+ * be added to the request. (optional)
3650
+ * Can be a stringified Object.
3651
+ * @param {Object} callProperties - properties to override on this call (optional)
3652
+ * @param {Function} callback - a callback function to return the result of the healthcheck
3653
+ */
3654
+ healthCheck(healthSchema, payload, headers, callProperties, callback) {
3655
+ const origin = `${this.myid}-connectorRest-healthCheck`;
3656
+ log.trace(origin);
3657
+
3658
+ try {
3659
+ // healthcheck can not be throttled and put in queue because it would be blocked
3660
+ // so build the request for a simple call and make the request
3661
+ const options = {
3662
+ hostname: host,
3663
+ port,
3664
+ path: healthcheckpath,
3665
+ method: 'GET',
3666
+ headers
3667
+ };
3668
+
3669
+ // passed in properties override defaults
3670
+ if (callProperties && callProperties.host) {
3671
+ options.hostname = callProperties.host;
3672
+ }
3673
+ if (callProperties && callProperties.port) {
3674
+ options.port = callProperties.port;
3675
+ }
3676
+
3677
+ // save headers in memory
3678
+ if (headers) {
3679
+ // save it in memory
3680
+ cacheHHead = headers;
3681
+ }
3682
+
3683
+ // if there is a healthcheck schema, over ride the properties
3684
+ if (healthSchema) {
3685
+ options.path = healthSchema.entitypath;
3686
+ options.method = healthSchema.method;
3687
+
3688
+ // save it in memory
3689
+ cacheHSchema = healthSchema;
3690
+ }
3691
+
3692
+ // if the path has a base path parameter in it, need to replace it
3693
+ let bpathStr = '{base_path}';
3694
+ if (options.path.indexOf(bpathStr) >= 0) {
3695
+ // be able to support this if the base path has a slash before it or not
3696
+ if (options.path.indexOf('/{base_path}') >= 0) {
3697
+ bpathStr = '/{base_path}';
3698
+ }
3699
+
3700
+ // replace with base path if we have one, otherwise remove base path
3701
+ if (callProperties && callProperties.base_path) {
3702
+ // if no leading /, insert one
3703
+ if (callProperties.base_path.indexOf('/') !== 0) {
3704
+ options.path = options.path.replace(bpathStr, `/${callProperties.base_path}`);
3705
+ } else {
3706
+ options.path = options.path.replace(bpathStr, callProperties.base_path);
3707
+ }
3708
+ } else if (basepath) {
3709
+ // if no leading /, insert one
3710
+ if (basepath.indexOf('/') !== 0) {
3711
+ options.path = options.path.replace(bpathStr, `/${basepath}`);
3712
+ } else {
3713
+ options.path = options.path.replace(bpathStr, basepath);
3714
+ }
3715
+ } else {
3716
+ options.path = options.path.replace(bpathStr, '');
3717
+ }
3718
+ }
3719
+
3720
+ // if the path has a version parameter in it, need to replace it
3721
+ let versStr = '{version}';
3722
+ if (options.path.indexOf(versStr) >= 0) {
3723
+ // be able to support this if the version has a slash before it or not
3724
+ if (options.path.indexOf('/{version}') >= 0) {
3725
+ versStr = '/{version}';
3726
+ }
3727
+
3728
+ // replace with version if we have one, otherwise remove version
3729
+ if (callProperties && callProperties.version) {
3730
+ options.path = options.path.replace(versStr, `/${encodeURIComponent(callProperties.version)}`);
3731
+ } else if (version) {
3732
+ options.path = options.path.replace(versStr, `/${encodeURIComponent(version)}`);
3733
+ } else {
3734
+ options.path = options.path.replace(versStr, '');
3735
+ }
3736
+ }
3737
+
3738
+ // if ssl enabled add the options for ssl
3739
+ if (callProperties && callProperties.ssl && Object.hasOwnProperty.call(callProperties.ssl, 'enabled')) {
3740
+ if (callProperties.ssl.enabled) {
3741
+ if (callProperties.ssl.accept_invalid_cert) {
3742
+ // if we are accepting invalid certificates (ok for lab not so much production)
3743
+ options.rejectUnauthorized = false;
3744
+ } else {
3745
+ // if we are not accepting invalid certs, need the ca file in the options
3746
+ try {
3747
+ options.rejectUnauthorized = true;
3748
+ options.ca = [fs.readFileSync(callProperties.ssl.ca_file)];
3749
+ } catch (e) {
3750
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [callProperties.ssl.ca_file], null, null, null);
3751
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3752
+ return callback(null, errorObj);
3753
+ }
3754
+ }
3755
+
3756
+ if (callProperties.ssl.ciphers) {
3757
+ options.ciphers = callProperties.ssl.ciphers;
3758
+ }
3759
+ if (callProperties.ssl.secure_protocol) {
3760
+ options.secureProtocol = callProperties.ssl.secure_protocol;
3761
+ }
3762
+
3763
+ log.info(`${origin}: Connector SSL connections enabled`);
3764
+ }
3765
+ } else if (sslEnabled) {
3766
+ if (sslAcceptInvalid) {
3767
+ // if we are accepting invalid certificates (ok for lab not so much production)
3768
+ options.rejectUnauthorized = false;
3769
+ } else {
3770
+ // if we are not accepting invalid certs, need the ca file in the options
3771
+ try {
3772
+ options.rejectUnauthorized = true;
3773
+ options.ca = [fs.readFileSync(sslCAFile)];
3774
+ } catch (e) {
3775
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCAFile], null, null, null);
3776
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3777
+ return callback(null, errorObj);
3778
+ }
3779
+ // if there is a cert file, try to read in a cert file in the options
3780
+ if (sslCertFile) {
3781
+ try {
3782
+ options.cert = [fs.readFileSync(sslCertFile)];
3783
+ } catch (e) {
3784
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCertFile], null, null, null);
3785
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3786
+ return callback(null, errorObj);
3787
+ }
3788
+ }
3789
+ // if there is a key file, try to read in a key file in the options
3790
+ if (sslKeyFile) {
3791
+ try {
3792
+ options.key = [fs.readFileSync(sslKeyFile)];
3793
+ } catch (e) {
3794
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslKeyFile], null, null, null);
3795
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3796
+ return callback(null, errorObj);
3797
+ }
3798
+ }
3799
+ }
3800
+
3801
+ if (sslCiphers) {
3802
+ options.ciphers = sslCiphers;
3803
+ }
3804
+ if (secureProtocol) {
3805
+ options.secureProtocol = secureProtocol;
3806
+ }
3807
+
3808
+ log.info(`${origin}: Connector SSL connections enabled`);
3809
+ }
3810
+
3811
+ log.debug(`${origin}: HEALTHCHECK OPTIONS: ${JSON.stringify(options)}`);
3812
+
3813
+ if (payload !== undefined && payload !== null && payload !== '') {
3814
+ log.debug(`${origin}: REQUEST: ${payload}`);
3815
+
3816
+ // save it in memory
3817
+ cacheHPay = payload;
3818
+ }
3819
+
3820
+ // handle ipv6 hostnames
3821
+ if (host.indexOf('[') === 0) {
3822
+ options.host = host;
3823
+ options.hostname = options.hostname.substring(1, options.hostname.length - 1);
3824
+ options.family = 6;
3825
+ }
3826
+
3827
+ const request = {
3828
+ header: options,
3829
+ body: payload
3830
+ };
3831
+
3832
+ // if there is a healthcheck schema, over ride the properties
3833
+ if (healthSchema !== null) {
3834
+ request.origPath = healthSchema.entitypath;
3835
+ }
3836
+
3837
+ // call to make the request
3838
+ return requestAuthenticate(request, healthSchema, null, callProperties, (pres, perror) => {
3839
+ if (perror) {
3840
+ return callback(null, perror);
3841
+ }
3842
+
3843
+ if (pres !== null && pres.code !== undefined
3844
+ && (Number(pres.code) < 200 || Number(pres.code) > 299)) {
3845
+ let errorObj = null;
3846
+
3847
+ if (pres.code === -2) {
3848
+ errorObj = transUtilInst.formatErrorObject(origin, 'Request Timeout', [pres.code], pres.code, pres, null);
3849
+ } else {
3850
+ errorObj = transUtilInst.formatErrorObject(origin, 'Error On Request', [pres.code], pres.code, pres, null);
3851
+ }
3852
+
3853
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3854
+ return callback(null, errorObj);
3855
+ }
3856
+ const returnObj = {
3857
+ code: 'AD.200'
3858
+ };
3859
+
3860
+ returnObj.response = pres;
3861
+ return callback(returnObj);
3862
+ });
3863
+ } catch (e) {
3864
+ // handle any exception
3865
+ const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue with healthcheck');
3866
+ return callback(null, errorObj);
3867
+ }
3868
+ }
3869
+
3870
+ /**
3871
+ * Generic request handler for System requests. Will take in the incoming request which includes
3872
+ * a path, a method and an optional user and password. This information will be used to set up
3873
+ * the correct request to the system. It will then process the request and return the results.
3874
+ *
3875
+ * @function performRequest
3876
+ * @param {Object} incoming - the information for the request. Must have a path and a method.
3877
+ * Can optionally have user and passwd (required)
3878
+ * @param {String} entitySchema - the entity schema (required)
3879
+ * @param {Object} callProperties - properties to override on this call (optional)
3880
+ * @param {Function} callback - a callback function to return the result of the request
3881
+ */
3882
+ performRequest(incoming, entitySchema, callProperties, callback) {
3883
+ const origin = `${this.myid}-connectorRest-performRequest`;
3884
+ log.trace(origin);
3885
+ const overallTime = process.hrtime();
3886
+
3887
+ try {
3888
+ // verify data was received
3889
+ if (incoming === null) {
3890
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing Data', ['request'], null, null, null);
3891
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3892
+ return callback(null, errorObj);
3893
+ }
3894
+ if (incoming.path === undefined || incoming.path === null || incoming.path === '') {
3895
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing Data', ['request path'], null, null, null);
3896
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3897
+ return callback(null, errorObj);
3898
+ }
3899
+ if (incoming.method === undefined || incoming.method === null || incoming.method === '') {
3900
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing Data', ['request method'], null, null, null);
3901
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3902
+ return callback(null, errorObj);
3903
+ }
3904
+
3905
+ // set up the options for the call
3906
+ let options = {
3907
+ hostname: host,
3908
+ port,
3909
+ path: incoming.path,
3910
+ method: incoming.method,
3911
+ headers: incoming.addlHeaders
3912
+ };
3913
+
3914
+ // passed in properties override defaults
3915
+ if (callProperties && callProperties.host) {
3916
+ options.hostname = callProperties.host;
3917
+ }
3918
+ if (callProperties && callProperties.port) {
3919
+ options.port = callProperties.port;
3920
+ }
3921
+
3922
+ // if ssl enabled add the options for ssl
3923
+ if (callProperties && callProperties.ssl && Object.hasOwnProperty.call(callProperties.ssl, 'enabled')) {
3924
+ if (callProperties.ssl.enabled) {
3925
+ if (callProperties.ssl.accept_invalid_cert) {
3926
+ // if we are accepting invalid certificates (ok for lab not so much production)
3927
+ options.rejectUnauthorized = false;
3928
+ } else {
3929
+ // if we are not accepting invalid certs, need the ca file in the options
3930
+ try {
3931
+ options.rejectUnauthorized = true;
3932
+ options.ca = [fs.readFileSync(callProperties.ssl.ca_file)];
3933
+ } catch (e) {
3934
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [callProperties.ssl.ca_file], null, null, null);
3935
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3936
+ return callback(null, errorObj);
3937
+ }
3938
+ }
3939
+
3940
+ if (callProperties.ssl.ciphers) {
3941
+ options.ciphers = callProperties.ssl.ciphers;
3942
+ }
3943
+ if (callProperties.ssl.secure_protocol) {
3944
+ options.secureProtocol = callProperties.ssl.secure_protocol;
3945
+ }
3946
+
3947
+ log.info(`${origin}: Connector SSL connections enabled`);
3948
+ }
3949
+ } else if (sslEnabled) {
3950
+ if (sslAcceptInvalid) {
3951
+ // if we are accepting invalid certificates (ok for lab not so much production)
3952
+ options.rejectUnauthorized = false;
3953
+ } else {
3954
+ // if we are not accepting invalid certs, need the ca file in the options
3955
+ try {
3956
+ options.rejectUnauthorized = true;
3957
+ options.ca = [fs.readFileSync(sslCAFile)];
3958
+ } catch (e) {
3959
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCAFile], null, null, null);
3960
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3961
+ return callback(null, errorObj);
3962
+ }
3963
+ // if there is a cert file, try to read in a cert file in the options
3964
+ if (sslCertFile) {
3965
+ try {
3966
+ options.cert = [fs.readFileSync(sslCertFile)];
3967
+ } catch (e) {
3968
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslCertFile], null, null, null);
3969
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3970
+ return callback(null, errorObj);
3971
+ }
3972
+ }
3973
+ // if there is a key file, try to read in a key file in the options
3974
+ if (sslKeyFile) {
3975
+ try {
3976
+ options.key = [fs.readFileSync(sslKeyFile)];
3977
+ } catch (e) {
3978
+ const errorObj = this.transUtil.formatErrorObject(origin, 'Missing File', [sslKeyFile], null, null, null);
3979
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
3980
+ return callback(null, errorObj);
3981
+ }
3982
+ }
3983
+ }
3984
+
3985
+ if (sslCiphers) {
3986
+ options.ciphers = sslCiphers;
3987
+ }
3988
+ if (secureProtocol) {
3989
+ options.secureProtocol = secureProtocol;
3990
+ }
3991
+
3992
+ log.info(`${origin}: Connector SSL connections enabled`);
3993
+ }
3994
+
3995
+ log.debug(`${origin}: OPTIONS: ${JSON.stringify(options)}`);
3996
+
3997
+ if (incoming.body !== undefined && incoming.body !== null && incoming.body !== '') {
3998
+ log.debug(`${origin}:REQUEST: ${incoming.body}`);
3999
+ }
4000
+
4001
+ const request = {
4002
+ header: options,
4003
+ body: incoming.body,
4004
+ origPath: incoming.origPath
4005
+ };
4006
+
4007
+ // if there is additional authentication data, add it to the request
4008
+ if (incoming.authData) {
4009
+ request.authData = incoming.authData;
4010
+ }
4011
+
4012
+ // clean up used variables from memory setting to undefined minimizes memory
4013
+ options = undefined;
4014
+
4015
+ // if throttling is not enables or we are talking to a different host than the default
4016
+ if (!throttleEnabled || (callProperties && callProperties.host)) {
4017
+ return noQueueRequest(request, callProperties, overallTime, entitySchema, callback);
4018
+ }
4019
+
4020
+ let myRequest = requestId;
4021
+ let myTransTime = new Date().getTime();
4022
+
4023
+ // Lock the global to increment it and reset the local
4024
+ return this.slock.acquire(requestId, (done) => {
4025
+ requestId += 1;
4026
+ myRequest = requestId;
4027
+ myTransTime = new Date().getTime();
4028
+ done(true);
4029
+ }, (ret) => {
4030
+ if (ret) {
4031
+ return queueThrottleRequest(request, callProperties, myRequest, myTransTime, incoming.priority, incoming.event, overallTime, entitySchema, callback);
4032
+ }
4033
+
4034
+ return queueThrottleRequest(request, callProperties, myRequest, myTransTime, incoming.priority, incoming.event, overallTime, entitySchema, callback);
4035
+ });
4036
+ } catch (e) {
4037
+ // handle any exception
4038
+ const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue performing the request');
4039
+ return callback(null, errorObj);
4040
+ }
4041
+ }
4042
+
4043
+ /**
4044
+ * getQueue is used to get information for all of the requests currently in the queue.
4045
+ *
4046
+ * @function getQueue
4047
+ * @param {Function} callback - a callback function to return the queue
4048
+ */
4049
+ getQueue(callback) {
4050
+ const origin = `${this.myid}-connectorRest-getQueue`;
4051
+ log.trace(origin);
4052
+
4053
+ try {
4054
+ if (!throttleEnabled) {
4055
+ log.error(`${origin}: Throttling not enabled, no queue`);
4056
+ return [];
4057
+ }
4058
+
4059
+ return throttleEng.getQueue(callback);
4060
+ } catch (e) {
4061
+ // handle any exception
4062
+ const errorObj = this.transUtil.checkAndReturn(e, origin, 'Issue getting queue');
4063
+ return callback(null, errorObj);
4064
+ }
4065
+ }
4066
+
4067
+ /**
4068
+ * getQueue is used to get information for all of the requests currently in the queue.
4069
+ *
4070
+ * @function makeTokenRequest
4071
+ * @param {String} pathForToken - dummy path ad it is a call (required)
4072
+ * @param {String} user - the user logging in (required)
4073
+ * @param {Object} reqBody - extra data to add to the payload (optional)
4074
+ * @param {String} invalidToken - used if we had a bad token (optional)
4075
+ * @param {String} callProperties - properties to override the adapter properties (optional)
4076
+ * @param {Function} callback - a callback function to return the queue
4077
+ */
4078
+ makeTokenRequest(pathForToken, user, reqBody, invalidToken, callProperties, callback) {
4079
+ return getTokenItem(pathForToken, user, reqBody, invalidToken, callProperties, callback);
4080
+ }
4081
+ }
4082
+
4083
+ module.exports = ConnectorRest;