@itentialopensource/adapter-utils 5.5.1 → 5.7.0
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/CHANGELOG.md +18 -0
- package/lib/connectorRest.js +310 -36
- package/lib/propertyUtil.js +5 -3
- package/lib/restHandler.js +46 -63
- package/package.json +4 -4
- package/refs?service=git-upload-pack +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
|
|
2
|
+
## 5.7.0 [08-21-2024]
|
|
3
|
+
|
|
4
|
+
* Update gzip logic
|
|
5
|
+
|
|
6
|
+
See merge request itentialopensource/adapter-utils!297
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 5.6.0 [07-31-2024]
|
|
11
|
+
|
|
12
|
+
* Add abilities to refresh token and set msa query
|
|
13
|
+
|
|
14
|
+
Closes ADAPT-3629
|
|
15
|
+
|
|
16
|
+
See merge request itentialopensource/adapter-utils!294
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
2
20
|
## 5.5.1 [07-30-2024]
|
|
3
21
|
|
|
4
22
|
* Fix gzip conditional in propertyUtils
|
package/lib/connectorRest.js
CHANGED
|
@@ -108,6 +108,9 @@ const healthchecklock = 0;
|
|
|
108
108
|
let encodeUri = true;
|
|
109
109
|
let authQueryEncode = null;
|
|
110
110
|
let addCreds = true;
|
|
111
|
+
let refTokenReq = null;
|
|
112
|
+
let refTokenTimeout = -1;
|
|
113
|
+
let runRefreshToken = false;
|
|
111
114
|
|
|
112
115
|
// Other global variables
|
|
113
116
|
let id = null;
|
|
@@ -777,34 +780,61 @@ function makeRequest(request, entitySchema, callProperties, startTrip, attempt,
|
|
|
777
780
|
// make the call to System
|
|
778
781
|
const httpRequest = useProt.request(request.header, (res) => {
|
|
779
782
|
let respStr = '';
|
|
780
|
-
res.
|
|
781
|
-
|
|
783
|
+
if (res.headers['content-encoding'] !== 'gzip') {
|
|
784
|
+
res.setEncoding('utf8');
|
|
785
|
+
}
|
|
786
|
+
const chunks = [];
|
|
782
787
|
// process data from response
|
|
783
788
|
res.on('data', (replyData) => {
|
|
789
|
+
chunks.push(replyData);
|
|
784
790
|
respStr += replyData;
|
|
785
791
|
});
|
|
786
792
|
|
|
787
793
|
// process end of response
|
|
788
794
|
res.on('end', () => {
|
|
795
|
+
let buffer = '';
|
|
796
|
+
if (res.headers['content-encoding'] === 'gzip') {
|
|
797
|
+
buffer = Buffer.concat(chunks);
|
|
798
|
+
}
|
|
799
|
+
|
|
789
800
|
const tripDiff = process.hrtime(roundTTime);
|
|
790
801
|
const tripEnd = `${Math.round(((tripDiff[0] * NS_PER_SEC) + tripDiff[1]) / 1000000)}ms`;
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
802
|
+
let callResp = {};
|
|
803
|
+
if (res.headers['content-encoding'] === 'gzip') {
|
|
804
|
+
callResp = {
|
|
805
|
+
status: 'success',
|
|
806
|
+
code: res.statusCode,
|
|
807
|
+
headers: res.headers,
|
|
808
|
+
response: buffer,
|
|
809
|
+
request: {
|
|
810
|
+
protocol: protStr,
|
|
811
|
+
host: request.header.hostname,
|
|
812
|
+
port: request.header.port,
|
|
813
|
+
uri: request.header.path,
|
|
814
|
+
method: request.header.method,
|
|
815
|
+
payload: request.body
|
|
816
|
+
},
|
|
817
|
+
redirects: attempt,
|
|
818
|
+
tripTime: tripEnd
|
|
819
|
+
};
|
|
820
|
+
} else {
|
|
821
|
+
callResp = {
|
|
822
|
+
status: 'success',
|
|
823
|
+
code: res.statusCode,
|
|
824
|
+
headers: res.headers,
|
|
825
|
+
response: respStr,
|
|
826
|
+
request: {
|
|
827
|
+
protocol: protStr,
|
|
828
|
+
host: request.header.hostname,
|
|
829
|
+
port: request.header.port,
|
|
830
|
+
uri: request.header.path,
|
|
831
|
+
method: request.header.method,
|
|
832
|
+
payload: request.body
|
|
833
|
+
},
|
|
834
|
+
redirects: attempt,
|
|
835
|
+
tripTime: tripEnd
|
|
836
|
+
};
|
|
837
|
+
}
|
|
808
838
|
// if there was a cookie or biscotti returned, need to set cookie on the new request
|
|
809
839
|
if (request.header.headers) {
|
|
810
840
|
const headKeys = Object.keys(request.header.headers);
|
|
@@ -1269,6 +1299,7 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
|
|
|
1269
1299
|
const currResult = {
|
|
1270
1300
|
token: null,
|
|
1271
1301
|
tokenp2: null,
|
|
1302
|
+
refreshToken: null,
|
|
1272
1303
|
front: tokenSchema.responseSchema.properties.token.front,
|
|
1273
1304
|
end: tokenSchema.responseSchema.properties.token.end
|
|
1274
1305
|
};
|
|
@@ -1558,6 +1589,10 @@ async function getToken(reqPath, options, tokenSchema, bodyString, callPropertie
|
|
|
1558
1589
|
&& tokenSchema.responseSchema.properties.expires.placement && tokenSchema.responseSchema.properties.expires.placement.toUpperCase() === 'BODY') {
|
|
1559
1590
|
currResult.expires = translated.expires;
|
|
1560
1591
|
}
|
|
1592
|
+
if (tokenSchema.responseSchema && tokenSchema.responseSchema.properties && tokenSchema.responseSchema.properties.refreshToken
|
|
1593
|
+
&& tokenSchema.responseSchema.properties.refreshToken.placement && tokenSchema.responseSchema.properties.refreshToken.placement.toUpperCase() === 'BODY') {
|
|
1594
|
+
currResult.refreshToken = translated.refreshToken;
|
|
1595
|
+
}
|
|
1561
1596
|
// return the token that we find in the translated object
|
|
1562
1597
|
return resolve(currResult);
|
|
1563
1598
|
});
|
|
@@ -1587,6 +1622,9 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
|
|
|
1587
1622
|
if (callProperties && callProperties.mfa) {
|
|
1588
1623
|
entity = callProperties.mfa.stepAtionName;
|
|
1589
1624
|
}
|
|
1625
|
+
if (runRefreshToken) {
|
|
1626
|
+
entity = 'getRefreshToken';
|
|
1627
|
+
}
|
|
1590
1628
|
// Get the entity schema from the file system
|
|
1591
1629
|
return propUtilInst.getEntitySchema('.system', entity, choosepath, this.dbUtil, async (tokenSchema, healthError) => {
|
|
1592
1630
|
if (healthError || !tokenSchema || Object.keys(tokenSchema).length === 0) {
|
|
@@ -2062,19 +2100,37 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
|
|
|
2062
2100
|
options.path = options.path.replace('{password}', usePass);
|
|
2063
2101
|
}
|
|
2064
2102
|
|
|
2065
|
-
// if this is not a get, need to add the info to the request
|
|
2066
2103
|
let creds = {};
|
|
2067
|
-
if (authMethod === 'multi_step_authentication') {
|
|
2068
|
-
const { stepBody, stepHeaders } = callProperties.mfa;
|
|
2104
|
+
if (authMethod === 'multi_step_authentication' && !runRefreshToken) {
|
|
2105
|
+
const { stepBody, stepHeaders, stepQuery } = callProperties.mfa;
|
|
2069
2106
|
Object.assign(creds, stepBody);
|
|
2070
2107
|
Object.assign(options.headers, stepHeaders);
|
|
2108
|
+
// If there are query variables, add them to the path
|
|
2109
|
+
if (Object.keys(stepQuery).length > 0) {
|
|
2110
|
+
let mfaQueryString = '';
|
|
2111
|
+
if (encodeUri === true) {
|
|
2112
|
+
mfaQueryString += querystring.stringify(stepQuery);
|
|
2113
|
+
} else {
|
|
2114
|
+
const mfaQqueryKeys = Object.keys(stepQuery);
|
|
2115
|
+
for (let k = 0; k < mfaQqueryKeys.length; k += 1) {
|
|
2116
|
+
if (k > 0) {
|
|
2117
|
+
mfaQueryString += '&';
|
|
2118
|
+
}
|
|
2119
|
+
mfaQueryString += `${mfaQqueryKeys[k]}=${stepQuery[mfaQqueryKeys[k]]}`;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
// append to the path
|
|
2123
|
+
if (options.path.indexOf('?') < 0) {
|
|
2124
|
+
options.path = `${options.path}?${mfaQueryString}`;
|
|
2125
|
+
} else {
|
|
2126
|
+
options.path = `${options.path}&${mfaQueryString}`;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2071
2129
|
}
|
|
2130
|
+
|
|
2131
|
+
// if this is not a get, need to add the info to the request
|
|
2072
2132
|
if (options.method !== 'GET') {
|
|
2073
|
-
if (authMethod
|
|
2074
|
-
const { stepBody, stepHeaders } = callProperties.mfa;
|
|
2075
|
-
Object.assign(creds, stepBody);
|
|
2076
|
-
Object.assign(options.headers, stepHeaders);
|
|
2077
|
-
} else {
|
|
2133
|
+
if (authMethod !== 'multi_step_authentication') {
|
|
2078
2134
|
creds = {
|
|
2079
2135
|
username: useUser,
|
|
2080
2136
|
password: usePass
|
|
@@ -2139,6 +2195,34 @@ async function buildTokenRequest(reqPath, reqBody, callProperties, callback) {
|
|
|
2139
2195
|
}
|
|
2140
2196
|
}
|
|
2141
2197
|
|
|
2198
|
+
// Update request info if run refresh token
|
|
2199
|
+
if (runRefreshToken && callProperties.refRequest && Object.keys(callProperties.refRequest).length > 0) {
|
|
2200
|
+
const { headers, query, body } = callProperties.refRequest;
|
|
2201
|
+
Object.assign(options.headers, headers);
|
|
2202
|
+
Object.assign(creds, body);
|
|
2203
|
+
// If there are query variables, add them to the path
|
|
2204
|
+
if (Object.keys(query).length > 0) {
|
|
2205
|
+
let refTokenQueryString = '';
|
|
2206
|
+
if (encodeUri === true) {
|
|
2207
|
+
refTokenQueryString += querystring.stringify(query);
|
|
2208
|
+
} else {
|
|
2209
|
+
const refTokenQueryKeys = Object.keys(query);
|
|
2210
|
+
for (let k = 0; k < refTokenQueryKeys.length; k += 1) {
|
|
2211
|
+
if (k > 0) {
|
|
2212
|
+
refTokenQueryString += '&';
|
|
2213
|
+
}
|
|
2214
|
+
refTokenQueryString += `${refTokenQueryKeys[k]}=${query[refTokenQueryKeys[k]]}`;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
// append to the path
|
|
2218
|
+
if (options.path.indexOf('?') < 0) {
|
|
2219
|
+
options.path = `${options.path}?${refTokenQueryString}`;
|
|
2220
|
+
} else {
|
|
2221
|
+
options.path = `${options.path}&${refTokenQueryString}`;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2142
2226
|
// map the data we received to an Entity - will get back the defaults
|
|
2143
2227
|
const tokenEntity = transUtilInst.mapToOutboundEntity(creds, tokenSchema.requestSchema);
|
|
2144
2228
|
bodyString = tokenEntity;
|
|
@@ -2267,7 +2351,12 @@ function addTokenItem(user, reqBody, token, timeout, callback) {
|
|
|
2267
2351
|
|
|
2268
2352
|
// add the token item to the tokenlist and return it
|
|
2269
2353
|
if (tokenCache === 'local') {
|
|
2270
|
-
tokenList.
|
|
2354
|
+
const index = tokenList.findIndex((obj) => obj.tkey === tokenItem.tkey);
|
|
2355
|
+
if (index === -1) {
|
|
2356
|
+
tokenList.push(tokenItem);
|
|
2357
|
+
} else {
|
|
2358
|
+
tokenList[index] = tokenItem;
|
|
2359
|
+
}
|
|
2271
2360
|
return callback(token);
|
|
2272
2361
|
}
|
|
2273
2362
|
|
|
@@ -2344,13 +2433,21 @@ function validToken(user, reqBody, invalidToken, callback) {
|
|
|
2344
2433
|
if (tokenList[i].tkey === tkey) {
|
|
2345
2434
|
// if the token expired (or will expire within a minute),
|
|
2346
2435
|
// or it is invalid (failed) remove it from the token list
|
|
2347
|
-
if (tokenList[i].expire < expireCheck
|
|
2348
|
-
|| tokenList[i].token.token === invalidToken) {
|
|
2436
|
+
if ((tokenList[i].expire < expireCheck
|
|
2437
|
+
|| tokenList[i].token.token === invalidToken) && !tokenList[i].token.refreshToken) {
|
|
2349
2438
|
tokenList.splice(i, 1);
|
|
2350
2439
|
break;
|
|
2440
|
+
} else if ((tokenList[i].expire < expireCheck
|
|
2441
|
+
|| tokenList[i].token.token === invalidToken) && tokenList[i].token.refreshToken) {
|
|
2442
|
+
// Just keep refreshToken for future token refresh
|
|
2443
|
+
delete tokenList[i].expire;
|
|
2444
|
+
delete tokenList[i].token.token;
|
|
2445
|
+
delete tokenList[i].token.tokenp2;
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2448
|
+
if (tokenList[i].token.token || tokenList[i].token.tokenp2) {
|
|
2449
|
+
retToken = tokenList[i].token;
|
|
2351
2450
|
}
|
|
2352
|
-
|
|
2353
|
-
retToken = tokenList[i].token;
|
|
2354
2451
|
break;
|
|
2355
2452
|
}
|
|
2356
2453
|
}
|
|
@@ -2362,6 +2459,71 @@ function validToken(user, reqBody, invalidToken, callback) {
|
|
|
2362
2459
|
}
|
|
2363
2460
|
}
|
|
2364
2461
|
|
|
2462
|
+
/*
|
|
2463
|
+
* INTERNAL FUNCTION: retrieve the refresh token from cache and determine if
|
|
2464
|
+
* it valid. if valid, return it otherwise return null
|
|
2465
|
+
*/
|
|
2466
|
+
function validRefreshToken(user, reqBody, invalidToken, callback) {
|
|
2467
|
+
const origin = `${id}-connectorRest-validRefreshToken`;
|
|
2468
|
+
log.trace(origin);
|
|
2469
|
+
|
|
2470
|
+
try {
|
|
2471
|
+
// get the token from redis
|
|
2472
|
+
let tkey = `${id}__%%__${user}`;
|
|
2473
|
+
|
|
2474
|
+
if (reqBody) {
|
|
2475
|
+
tkey += `__%%__${JSON.stringify(reqBody)}`;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
if (refTokenReq && refTokenReq.refresh_token && refTokenReq.refresh_token.token_timeout) {
|
|
2479
|
+
refTokenTimeout = refTokenReq.refresh_token.token_timeout;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// Return null if refresh token timeout is -1
|
|
2483
|
+
if (refTokenTimeout === -1) {
|
|
2484
|
+
return callback(null);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
const currentTime = new Date().getTime();
|
|
2488
|
+
|
|
2489
|
+
// To check if refresh token will expire within a minute
|
|
2490
|
+
const refTokenTimeoutMillis = refTokenTimeout - 60000;
|
|
2491
|
+
|
|
2492
|
+
let refToken = null;
|
|
2493
|
+
|
|
2494
|
+
// find user's refresh token in the list
|
|
2495
|
+
for (let i = 0; i < tokenList.length; i += 1) {
|
|
2496
|
+
if (tokenList[i].tkey === tkey) {
|
|
2497
|
+
if (currentTime - tokenList[i].start < refTokenTimeoutMillis && tokenList[i].token.refreshToken) {
|
|
2498
|
+
refToken = tokenList[i].token.refreshToken;
|
|
2499
|
+
break;
|
|
2500
|
+
}
|
|
2501
|
+
break;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
return callback(refToken);
|
|
2505
|
+
} catch (e) {
|
|
2506
|
+
// handle any exception
|
|
2507
|
+
const errorObj = transUtilInst.checkAndReturn(e, origin, 'Issue validating cached refresh token');
|
|
2508
|
+
return callback(null, errorObj);
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
function getCachedRefToken(cachedTokenIdentifier, invalidToken) {
|
|
2513
|
+
const origin = `${id}-connectorRest-getCachedRefToken`;
|
|
2514
|
+
log.trace(origin);
|
|
2515
|
+
|
|
2516
|
+
return new Promise((resolve, reject) => {
|
|
2517
|
+
validRefreshToken(id, cachedTokenIdentifier, invalidToken, (refToken, error) => {
|
|
2518
|
+
if (error) {
|
|
2519
|
+
reject(error);
|
|
2520
|
+
} else {
|
|
2521
|
+
resolve(refToken);
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2365
2527
|
/*
|
|
2366
2528
|
* INTERNAL FUNCTION: getTokenItem is used to retrieve a token items from
|
|
2367
2529
|
* memory based on the user provided
|
|
@@ -2382,6 +2544,27 @@ function getTokenItem(pathForToken, user, reqBody, invalidToken, callProperties,
|
|
|
2382
2544
|
if (retToken !== null) {
|
|
2383
2545
|
done(retToken, null);
|
|
2384
2546
|
} else {
|
|
2547
|
+
// If primary token not found or invalid, check if there is refresh token
|
|
2548
|
+
validRefreshToken(user, reqBody, invalidToken, (refToken, rerror) => {
|
|
2549
|
+
if (rerror) {
|
|
2550
|
+
done(null, rerror);
|
|
2551
|
+
}
|
|
2552
|
+
if (refToken !== null) {
|
|
2553
|
+
log.debug(`${origin}-returning cached refresh token`);
|
|
2554
|
+
if (!callProperties) {
|
|
2555
|
+
callProperties = {};
|
|
2556
|
+
}
|
|
2557
|
+
callProperties.refRequest = {};
|
|
2558
|
+
buildRefTokenData(refToken, callProperties);
|
|
2559
|
+
// Check if getRefreshToken entity exists
|
|
2560
|
+
propUtilInst.getEntitySchema('.system', 'getRefreshToken', choosepath, this.dbUtil, async (tokenSchema, error) => {
|
|
2561
|
+
// Refresh the token if getRefreshToken entity exists
|
|
2562
|
+
if (tokenSchema) {
|
|
2563
|
+
runRefreshToken = true;
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
} else runRefreshToken = false;
|
|
2567
|
+
});
|
|
2385
2568
|
// No valid token found, Need to get a new token and add it to the token list
|
|
2386
2569
|
buildTokenRequest(pathForToken, reqBody, callProperties, (dyntoken, berror) => {
|
|
2387
2570
|
if (berror) {
|
|
@@ -2405,7 +2588,8 @@ function getTokenItem(pathForToken, user, reqBody, invalidToken, callProperties,
|
|
|
2405
2588
|
|
|
2406
2589
|
const tokenObj = {
|
|
2407
2590
|
token: findPrimaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end),
|
|
2408
|
-
tokenp2: findSecondaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end)
|
|
2591
|
+
tokenp2: findSecondaryTokenInResult(dyntoken, dyntoken.front, dyntoken.end),
|
|
2592
|
+
refreshToken: dyntoken.refreshToken
|
|
2409
2593
|
};
|
|
2410
2594
|
|
|
2411
2595
|
// if this is worth caching
|
|
@@ -2679,6 +2863,24 @@ function buildMfaSso(prop, mfaStepConfiguration, callProperties) {
|
|
|
2679
2863
|
}
|
|
2680
2864
|
}
|
|
2681
2865
|
|
|
2866
|
+
function buildMfaQuery(prop, mfaStepConfiguration, callProperties) {
|
|
2867
|
+
const origin = `${id}-connectorRest-buildMfaQuery`;
|
|
2868
|
+
log.trace(origin);
|
|
2869
|
+
const fullPropertyValue = mfaStepConfiguration.requestFields[prop];
|
|
2870
|
+
// Check if fullPropertyValue has reference in curly braces (e.g., {getSession.responseFields.session})
|
|
2871
|
+
let propertyValue = fullPropertyValue;
|
|
2872
|
+
const matching = searchTextInCurlyBraces(fullPropertyValue);
|
|
2873
|
+
const queryParam = prop.split('.')[1];
|
|
2874
|
+
if (matching) {
|
|
2875
|
+
const matchedText = matching[0];
|
|
2876
|
+
propertyValue = matching[1];
|
|
2877
|
+
const resolvedValue = getReferencedMfaValue(propertyValue);
|
|
2878
|
+
callProperties.mfa.stepQuery[queryParam] = fullPropertyValue.replace(matchedText, resolvedValue);
|
|
2879
|
+
} else {
|
|
2880
|
+
callProperties.mfa.stepQuery[queryParam] = mfaStepConfiguration.requestFields[prop];
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2682
2884
|
/* */
|
|
2683
2885
|
function getReferencedMfaValue(propertyValue) {
|
|
2684
2886
|
const origin = `${id}-connectorRest-getReferencedMfaValue`;
|
|
@@ -2702,6 +2904,7 @@ function buildMfaStepData(step, callProperties) {
|
|
|
2702
2904
|
const mfaStepConfiguration = multiStepAuthCalls[step];
|
|
2703
2905
|
callProperties.mfa.stepBody = {};
|
|
2704
2906
|
callProperties.mfa.stepHeaders = {};
|
|
2907
|
+
callProperties.mfa.stepQuery = {};
|
|
2705
2908
|
if (mfaStepConfiguration.sso) {
|
|
2706
2909
|
callProperties.mfa.host = '';
|
|
2707
2910
|
callProperties.mfa.port = '';
|
|
@@ -2715,6 +2918,8 @@ function buildMfaStepData(step, callProperties) {
|
|
|
2715
2918
|
Object.keys(mfaStepConfiguration.requestFields).forEach((prop) => {
|
|
2716
2919
|
if (prop.startsWith('header')) {
|
|
2717
2920
|
buildMfaHeader(prop, mfaStepConfiguration, callProperties);
|
|
2921
|
+
} else if (prop.startsWith('query')) {
|
|
2922
|
+
buildMfaQuery(prop, mfaStepConfiguration, callProperties);
|
|
2718
2923
|
} else {
|
|
2719
2924
|
buildMfaBody(prop, mfaStepConfiguration, callProperties);
|
|
2720
2925
|
}
|
|
@@ -2722,6 +2927,35 @@ function buildMfaStepData(step, callProperties) {
|
|
|
2722
2927
|
}
|
|
2723
2928
|
}
|
|
2724
2929
|
|
|
2930
|
+
function buildRefTokenData(cachedRefToken, callProperties) {
|
|
2931
|
+
const origin = `${id}-connectorRest-buildRefTokenData`;
|
|
2932
|
+
log.trace(origin);
|
|
2933
|
+
if (refTokenReq && refTokenReq.requestFields && Object.keys(refTokenReq.requestFields).length > 0) {
|
|
2934
|
+
callProperties.refRequest.headers = {};
|
|
2935
|
+
callProperties.refRequest.query = {};
|
|
2936
|
+
callProperties.refRequest.body = {};
|
|
2937
|
+
Object.keys(refTokenReq.requestFields).forEach((prop) => {
|
|
2938
|
+
if (prop.startsWith('header')) {
|
|
2939
|
+
const headerName = prop.split('.')[1];
|
|
2940
|
+
callProperties.refRequest.headers[headerName] = refTokenReq.requestFields[prop];
|
|
2941
|
+
} else if (prop.startsWith('query')) {
|
|
2942
|
+
const queryName = prop.split('.')[1];
|
|
2943
|
+
callProperties.refRequest.query[queryName] = refTokenReq.requestFields[prop];
|
|
2944
|
+
} else {
|
|
2945
|
+
callProperties.refRequest.body[prop] = refTokenReq.requestFields[prop];
|
|
2946
|
+
}
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
if (refTokenReq && refTokenReq.refresh_token && Object.keys(refTokenReq.refresh_token).length > 0) {
|
|
2951
|
+
if (refTokenReq.refresh_token.placement && refTokenReq.refresh_token.placement.toLowerCase() === 'query') {
|
|
2952
|
+
callProperties.refRequest.query.refreshToken = cachedRefToken;
|
|
2953
|
+
} else {
|
|
2954
|
+
callProperties.refRequest.body.refreshToken = cachedRefToken;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2725
2959
|
/* */
|
|
2726
2960
|
async function translateMfaStepResult(step, stepResult, callProperties) {
|
|
2727
2961
|
const origin = `${id}-connectorRest-translateMfaStepResult`;
|
|
@@ -2738,7 +2972,7 @@ async function translateMfaStepResult(step, stepResult, callProperties) {
|
|
|
2738
2972
|
allProperties.forEach((prop) => {
|
|
2739
2973
|
if (Object.prototype.hasOwnProperty.call(stepResult, prop)) {
|
|
2740
2974
|
const externalName = tokenSchema.responseSchema.properties[prop].external_name;
|
|
2741
|
-
if (!stepResult[prop]) {
|
|
2975
|
+
if (!stepResult[prop] && prop !== 'refreshToken') {
|
|
2742
2976
|
log.error(`No step-${step + 1} result for responseSchema (prop=>external_name): (${prop}=>${externalName}) found in step response`);
|
|
2743
2977
|
return reject(new Error(`Response schema for step-${step + 1} misconfiguration`));
|
|
2744
2978
|
}
|
|
@@ -2820,8 +3054,43 @@ async function getMfaFinalTokens(request, callProperties, invalidToken) {
|
|
|
2820
3054
|
log.debug(`${origin}-returning cached MFA token`);
|
|
2821
3055
|
finalTokens = cachedToken;
|
|
2822
3056
|
return;
|
|
2823
|
-
}
|
|
2824
|
-
//
|
|
3057
|
+
}
|
|
3058
|
+
// Get cached refresh token if primary token is not valid
|
|
3059
|
+
const cachedRefToken = await getCachedRefToken(cachedTokenIdentifier, invalidToken)
|
|
3060
|
+
.catch((error) => {
|
|
3061
|
+
log.error(error);
|
|
3062
|
+
throw error;
|
|
3063
|
+
});
|
|
3064
|
+
if (cachedRefToken) {
|
|
3065
|
+
log.debug(`${origin}-returning cached MFA refresh token`);
|
|
3066
|
+
if (!callProperties) {
|
|
3067
|
+
callProperties = {};
|
|
3068
|
+
}
|
|
3069
|
+
callProperties.refRequest = {};
|
|
3070
|
+
buildRefTokenData(cachedRefToken, callProperties);
|
|
3071
|
+
propUtilInst.getEntitySchema('.system', 'getRefreshToken', choosepath, this.dbUtil, async (tokenSchema, error) => {
|
|
3072
|
+
if (error || !tokenSchema || Object.keys(tokenSchema).length === 0) {
|
|
3073
|
+
tokenSchema = null;
|
|
3074
|
+
}
|
|
3075
|
+
// If cached refresh token is valid and there is action for getRefreshToken, run refresh token
|
|
3076
|
+
if (tokenSchema) {
|
|
3077
|
+
runRefreshToken = true;
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
if (runRefreshToken) {
|
|
3081
|
+
finalTokens = await buildTokenRequest(request.header.path, request.authData, callProperties).catch((error) => { // eslint-disable-line no-await-in-loop
|
|
3082
|
+
log.error(error);
|
|
3083
|
+
throw error;
|
|
3084
|
+
});
|
|
3085
|
+
if (finalTokens) {
|
|
3086
|
+
await cacheMfaToken(cachedTokenIdentifier, finalTokens).catch((error) => log.error(error));
|
|
3087
|
+
}
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
runRefreshToken = false;
|
|
3092
|
+
mfaStepsResults.length = 0;
|
|
3093
|
+
// no cached primary token or refresh token found, trigger auth steps to obtain new token
|
|
2825
3094
|
for (let step = 0; step < multiStepAuthCalls.length; step += 1) {
|
|
2826
3095
|
const stepAtionName = `MFA_Step_${step + 1}`;
|
|
2827
3096
|
log.debug(`${origin}-Executing MFA step-${step + 1}, action: ${stepAtionName}`);
|
|
@@ -4028,6 +4297,11 @@ class ConnectorRest {
|
|
|
4028
4297
|
if (typeof props.authentication.addCreds === 'boolean') {
|
|
4029
4298
|
addCreds = props.authentication.addCreds;
|
|
4030
4299
|
}
|
|
4300
|
+
|
|
4301
|
+
// set the refresh token request (optional - default is null)
|
|
4302
|
+
if (props.authentication.refresh_token_request && typeof props.authentication.refresh_token_request === 'object') {
|
|
4303
|
+
refTokenReq = props.authentication.refresh_token_request;
|
|
4304
|
+
}
|
|
4031
4305
|
}
|
|
4032
4306
|
|
|
4033
4307
|
// set the stub mode (optional - default is false)
|
package/lib/propertyUtil.js
CHANGED
|
@@ -534,8 +534,8 @@ class AdapterPropertyUtil {
|
|
|
534
534
|
}
|
|
535
535
|
if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
|
|
536
536
|
&& entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON'
|
|
537
|
-
&& entitySchema.
|
|
538
|
-
&& entitySchema.
|
|
537
|
+
&& entitySchema.responseDatatype.toUpperCase() !== 'GZIP2XML2JSON' && entitySchema.responseDatatype.toUpperCase() !== 'GZIP2XML'
|
|
538
|
+
&& entitySchema.responseDatatype.toUpperCase() !== 'GZIP2JSON' && entitySchema.responseDatatype.toUpperCase() !== 'GZIP2PLAIN') {
|
|
539
539
|
entitySchema.responseDatatype = 'PLAIN';
|
|
540
540
|
}
|
|
541
541
|
|
|
@@ -952,7 +952,9 @@ class AdapterPropertyUtil {
|
|
|
952
952
|
entitySchema.requestDatatype = 'PLAIN';
|
|
953
953
|
}
|
|
954
954
|
if (entitySchema.responseDatatype.toUpperCase() !== 'JSON' && entitySchema.responseDatatype.toUpperCase() !== 'XML'
|
|
955
|
-
&& entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON'
|
|
955
|
+
&& entitySchema.responseDatatype.toUpperCase() !== 'URLENCODE' && entitySchema.responseDatatype.toUpperCase() !== 'XML2JSON'
|
|
956
|
+
&& entitySchema.responseDatatype.toUpperCase() !== 'GZIP2XML2JSON' && entitySchema.responseDatatype.toUpperCase() !== 'GZIP2XML'
|
|
957
|
+
&& entitySchema.responseDatatype.toUpperCase() !== 'GZIP2JSON' && entitySchema.responseDatatype.toUpperCase() !== 'GZIP2PLAIN') {
|
|
956
958
|
entitySchema.responseDatatype = 'PLAIN';
|
|
957
959
|
}
|
|
958
960
|
|
package/lib/restHandler.js
CHANGED
|
@@ -8,7 +8,7 @@ const querystring = require('querystring');
|
|
|
8
8
|
const jsonQuery = require('json-query');
|
|
9
9
|
const jsonxml = require('jsontoxml');
|
|
10
10
|
const xml2js = require('xml2js');
|
|
11
|
-
const
|
|
11
|
+
const zlib = require('zlib');
|
|
12
12
|
|
|
13
13
|
const globalSchema = JSON.parse(require('fs').readFileSync(require('path').join(__dirname, '/../schemas/globalSchema.json')));
|
|
14
14
|
|
|
@@ -53,19 +53,6 @@ function matchResponse(uriPath, method, type, mockresponses) {
|
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/*
|
|
57
|
-
* INTERNAL FUNCTION: Parse XML
|
|
58
|
-
*/
|
|
59
|
-
function parseXML(xmlString, callback) {
|
|
60
|
-
const parser = new xml2js.Parser({ explicitArray: false, attrkey: '_attr' });
|
|
61
|
-
parser.parseString(xmlString, (error, result) => {
|
|
62
|
-
if (error) {
|
|
63
|
-
return callback(error);
|
|
64
|
-
}
|
|
65
|
-
return callback(null, result);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
56
|
/*
|
|
70
57
|
* INTERNAL FUNCTION: recursively inspect body data if heirarchical
|
|
71
58
|
*/
|
|
@@ -491,62 +478,58 @@ function handleRestRequest(request, entityId, entitySchema, callProperties, filt
|
|
|
491
478
|
if (entitySchema && entitySchema.responseDatatype && entitySchema.responseDatatype.toUpperCase() === 'URLENCODE') {
|
|
492
479
|
// return the response
|
|
493
480
|
retResponse = querystring.parse(resObj.response.trim());
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|| entitySchema.responseDatatype.toUpperCase() === 'GZIP2XML2JSON')) {
|
|
497
|
-
// If response is GZIP
|
|
481
|
+
} if (entitySchema && entitySchema.responseDatatype
|
|
482
|
+
&& ['GZIP2PLAIN', 'GZIP2JSON', 'GZIP2XML', 'GZIP2XML2JSON'].includes(entitySchema.responseDatatype.toUpperCase())) {
|
|
498
483
|
const responseDatatype = entitySchema.responseDatatype.toUpperCase();
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
484
|
+
|
|
485
|
+
return zlib.gunzip(resObj.response, (err, unzipresponse) => {
|
|
486
|
+
if (err) {
|
|
487
|
+
log.warn(`${origin}: Error inflating gzip response: ${err}`);
|
|
488
|
+
return callback(retObject);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const responseBody = unzipresponse.toString('utf8'); // Convert buffer to string
|
|
492
|
+
if (responseDatatype === 'GZIP2PLAIN') {
|
|
493
|
+
retObject.response = responseBody;
|
|
494
|
+
log.debug(`${origin}: RESPONSE: ${retObject.response}`);
|
|
495
|
+
return callback(retObject);
|
|
496
|
+
} if (responseDatatype === 'GZIP2JSON') {
|
|
497
|
+
try {
|
|
498
|
+
retObject.response = JSON.parse(responseBody);
|
|
499
|
+
log.debug(`${origin}: RESPONSE: ${retObject.response}`);
|
|
500
|
+
return callback(retObject); // Parse responseBody as JSON
|
|
501
|
+
} catch (jsonErr) {
|
|
502
|
+
log.warn(`${origin}: Error parsing JSON response: ${jsonErr}`);
|
|
510
503
|
return callback(retObject);
|
|
511
504
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
// Optionally convert XML to JSON
|
|
517
|
-
try {
|
|
518
|
-
const parser = new xml2js.Parser({ explicitArray: false, attrkey: '_attr' });
|
|
519
|
-
return parser.parseString(xmlResult, (err, result) => {
|
|
520
|
-
if (err) {
|
|
521
|
-
log.warn(`${origin}: Unable to parse xml to json ${err}`);
|
|
522
|
-
return callback(retObject);
|
|
523
|
-
}
|
|
524
|
-
// Logic to convert keys specified to array if object
|
|
525
|
-
if (xmlArrayKeys) {
|
|
526
|
-
setArrays(result, xmlArrayKeys);
|
|
527
|
-
}
|
|
528
|
-
retObject.response = result;
|
|
529
|
-
return callback(retObject);
|
|
530
|
-
});
|
|
531
|
-
} catch (ex) {
|
|
532
|
-
log.warn(`${origin}: Unable to get json from xml ${ex}`);
|
|
505
|
+
} else if (responseDatatype === 'GZIP2XML' || responseDatatype === 'GZIP2XML2JSON') {
|
|
506
|
+
xml2js.parseString(responseBody, { explicitArray: false, attrkey: '_attr' }, (xmlErr, xmlResult) => {
|
|
507
|
+
if (xmlErr) {
|
|
508
|
+
log.warn(`${origin}: Error parsing XML response: ${xmlErr}`);
|
|
533
509
|
return callback(retObject);
|
|
534
510
|
}
|
|
535
|
-
}
|
|
536
511
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
512
|
+
retObject.response = xmlResult;
|
|
513
|
+
|
|
514
|
+
if (responseDatatype === 'GZIP2XML2JSON') {
|
|
515
|
+
// Optionally convert XML to JSON, already done above by xml2js
|
|
516
|
+
if (xmlArrayKeys) {
|
|
517
|
+
setArrays(retObject.response, xmlArrayKeys); // Handle array conversion
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return callback(retObject);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
548
523
|
return callback(retObject);
|
|
549
|
-
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
// process the response - parse it
|
|
527
|
+
try {
|
|
528
|
+
retResponse = JSON.parse(resObj.response.trim());
|
|
529
|
+
} catch (ex) {
|
|
530
|
+
// otherwise log parse failure and return the unparsed response
|
|
531
|
+
log.warn(`${origin}: An error occurred parsing the resulting JSON: ${ex}: Actual Response is: ${resObj}`);
|
|
532
|
+
return callback(retObject);
|
|
550
533
|
}
|
|
551
534
|
|
|
552
535
|
// Make the call to translate the received Entity to Pronghorn Entity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itentialopensource/adapter-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"description": "Itential Adapter Utility Libraries",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"postinstall": "node utils/setup.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"ajv": "^8.12.0",
|
|
29
29
|
"async-lock": "^1.4.0",
|
|
30
|
-
"aws-sdk": "^2.
|
|
30
|
+
"aws-sdk": "^2.1665.0",
|
|
31
31
|
"aws4": "^1.9.1",
|
|
32
32
|
"cookie": "^0.5.0",
|
|
33
33
|
"crypto-js": "^4.1.1",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"jsontoxml": "^1.0.1",
|
|
40
40
|
"jsonwebtoken": "^9.0.1",
|
|
41
41
|
"mongodb": "^3.7.4",
|
|
42
|
-
"pako": "^2.1.0",
|
|
43
42
|
"querystring": "^0.2.0",
|
|
44
43
|
"readline-sync": "^1.4.10",
|
|
45
44
|
"socks-proxy-agent": "^8.0.1",
|
|
46
45
|
"uuid": "^9.0.0",
|
|
47
|
-
"xml2js": "^0.6.0"
|
|
46
|
+
"xml2js": "^0.6.0",
|
|
47
|
+
"zlib": "^1.0.5"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"chai": "^4.3.7",
|
|
Binary file
|