@rockcarver/frodo-lib 0.12.3 → 0.12.5-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.
@@ -5,6 +5,7 @@ import { convertBase64TextToArray, getTypedFilename, saveJsonToFile, getRealmStr
5
5
  import { getRealmManagedUser, replaceAll } from './utils/OpsUtils';
6
6
  import storage from '../storage/SessionStorage';
7
7
  import { getNode, putNode, deleteNode, getNodeTypes, getNodesByType } from '../api/NodeApi';
8
+ import { isCloudOnlyNode, isCustomNode, isPremiumNode } from './NodeOps';
8
9
  import { getTrees, getTree, putTree, deleteTree } from '../api/TreeApi';
9
10
  import { getEmailTemplate, putEmailTemplate } from '../api/EmailTemplateApi';
10
11
  import { getScript } from '../api/ScriptApi';
@@ -1128,19 +1129,16 @@ export async function importJourneysFromFiles(options) {
1128
1129
  importAllJourneys(allJourneysData.trees, options);
1129
1130
  }
1130
1131
  /**
1131
- * Describe a tree
1132
- * @param {Object} journeyData tree
1133
- * @returns {Object} an object describing the tree
1132
+ * Describe a journey
1133
+ * @param {SingleJourneyExportTemplate} journeyData journey export object
1134
1134
  */
1135
1135
 
1136
1136
  export function describeJourney(journeyData) {
1137
- const treeMap = {};
1137
+ var _journeyData$themes;
1138
+
1138
1139
  const nodeTypeMap = {};
1139
- const scriptsMap = {};
1140
- const emailTemplatesMap = {};
1141
- treeMap['treeName'] = journeyData.tree._id;
1142
1140
 
1143
- for (const [, nodeData] of Object.entries(journeyData.nodes)) {
1141
+ for (const nodeData of Object.values(journeyData.nodes)) {
1144
1142
  if (nodeTypeMap[nodeData['_type']['_id']]) {
1145
1143
  nodeTypeMap[nodeData['_type']['_id']] += 1;
1146
1144
  } else {
@@ -1148,7 +1146,7 @@ export function describeJourney(journeyData) {
1148
1146
  }
1149
1147
  }
1150
1148
 
1151
- for (const [, nodeData] of Object.entries(journeyData.innerNodes)) {
1149
+ for (const nodeData of Object.values(journeyData.innerNodes)) {
1152
1150
  if (nodeTypeMap[nodeData['_type']['_id']]) {
1153
1151
  nodeTypeMap[nodeData['_type']['_id']] += 1;
1154
1152
  } else {
@@ -1156,44 +1154,69 @@ export function describeJourney(journeyData) {
1156
1154
  }
1157
1155
  }
1158
1156
 
1159
- for (const [, scriptData] of Object.entries(journeyData.scripts)) {
1160
- scriptsMap[scriptData['name']] = scriptData['description'];
1161
- }
1157
+ printMessage(`\n${journeyData.tree._id}`, 'data');
1158
+ const line = Array(journeyData.tree._id['length']).fill('=').join('');
1159
+ printMessage(line);
1162
1160
 
1163
- for (const [id, data] of Object.entries(journeyData.emailTemplates)) {
1164
- emailTemplatesMap[id] = data['displayName'];
1161
+ if (journeyData.tree.description) {
1162
+ printMessage(`\n${journeyData.tree.description}\n`, 'data');
1165
1163
  }
1166
1164
 
1167
- treeMap['nodeTypes'] = nodeTypeMap;
1168
- treeMap['scripts'] = scriptsMap;
1169
- treeMap['emailTemplates'] = emailTemplatesMap;
1170
- printMessage(`\nJourney: ${treeMap['treeName']}`, 'data');
1171
- printMessage('========');
1172
1165
  printMessage('\nNodes:', 'data');
1173
1166
 
1174
- if (Object.entries(treeMap['nodeTypes']).length) {
1175
- for (const [name, count] of Object.entries(treeMap['nodeTypes'])) {
1176
- printMessage(`- ${name}: ${count}`, 'data');
1167
+ if (Object.entries(nodeTypeMap).length) {
1168
+ for (const [name, count] of Object.entries(nodeTypeMap)) {
1169
+ printMessage(`- [${String(count)['brightCyan']}] ${name}`, 'data');
1170
+ }
1171
+ }
1172
+
1173
+ if ((_journeyData$themes = journeyData.themes) !== null && _journeyData$themes !== void 0 && _journeyData$themes.length) {
1174
+ printMessage('\nThemes:', 'data');
1175
+
1176
+ for (const themeData of journeyData.themes) {
1177
+ printMessage(`- [${themeData['_id']['brightCyan']}] ${themeData['name']}`, 'data');
1177
1178
  }
1178
1179
  }
1179
1180
 
1180
- if (Object.entries(treeMap['scripts']).length) {
1181
+ if (Object.entries(journeyData.scripts).length) {
1181
1182
  printMessage('\nScripts:', 'data');
1182
1183
 
1183
- for (const [name, desc] of Object.entries(treeMap['scripts'])) {
1184
- printMessage(`- ${name}: ${desc}`, 'data');
1184
+ for (const scriptData of Object.values(journeyData.scripts)) {
1185
+ printMessage(`- [${scriptData['_id'].brightCyan}] ${scriptData['name']}`, 'data');
1185
1186
  }
1186
1187
  }
1187
1188
 
1188
- if (Object.entries(treeMap['emailTemplates']).length) {
1189
+ if (Object.entries(journeyData.emailTemplates).length) {
1189
1190
  printMessage('\nEmail Templates:', 'data');
1190
1191
 
1191
- for (const [id] of Object.entries(treeMap['emailTemplates'])) {
1192
- printMessage(`- ${id}`, 'data');
1192
+ for (const templateData of Object.values(journeyData.emailTemplates)) {
1193
+ printMessage(`- ${templateData['_id'].split('/')[1]}`, 'data');
1194
+ }
1195
+ }
1196
+
1197
+ if (Object.entries(journeyData.socialIdentityProviders).length) {
1198
+ printMessage('\nSocial Identity Providers:', 'data');
1199
+
1200
+ for (const socialIdpData of Object.values(journeyData.socialIdentityProviders)) {
1201
+ printMessage(`- ${socialIdpData['_id']} [${socialIdpData['_type']['_id'].brightCyan}]`, 'data');
1202
+ }
1203
+ }
1204
+
1205
+ if (Object.entries(journeyData.saml2Entities).length) {
1206
+ printMessage('\nSAML2 Entity Providers:', 'data');
1207
+
1208
+ for (const entityProviderData of Object.values(journeyData.saml2Entities)) {
1209
+ printMessage(`- ${entityProviderData['entityId']}`, 'data');
1193
1210
  }
1194
1211
  }
1195
1212
 
1196
- return treeMap;
1213
+ if (Object.entries(journeyData.circlesOfTrust).length) {
1214
+ printMessage('\nSAML2 Circles Of Trust:', 'data');
1215
+
1216
+ for (const cotData of Object.values(journeyData.circlesOfTrust)) {
1217
+ printMessage(`- ${cotData['_id']}`, 'data');
1218
+ }
1219
+ }
1197
1220
  }
1198
1221
  /**
1199
1222
  * Find all node configuration objects that are no longer referenced by any tree
@@ -1297,93 +1320,82 @@ export async function removeOrphanedNodes(orphanedNodes) {
1297
1320
  stopProgressIndicator(`Removed ${orphanedNodes.length} orphaned nodes.`);
1298
1321
  return errorNodes;
1299
1322
  }
1300
- const OOTB_NODE_TYPES_7 = ['AcceptTermsAndConditionsNode', 'AccountActiveDecisionNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousSessionUpgradeNode', 'AnonymousUserNode', 'AttributeCollectorNode', 'AttributePresentDecisionNode', 'AttributeValueDecisionNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'ConsentNode', 'CookiePresenceDecisionNode', 'CreateObjectNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'DeviceGeoFencingNode', 'DeviceLocationMatchNode', 'DeviceMatchNode', 'DeviceProfileCollectorNode', 'DeviceSaveNode', 'DeviceTamperingVerificationNode', 'DisplayUserNameNode', 'EmailSuspendNode', 'EmailTemplateNode', 'IdentifyExistingUserNode', 'IncrementLoginCountNode', 'InnerTreeEvaluatorNode', 'IotAuthenticationNode', 'IotRegistrationNode', 'KbaCreateNode', 'KbaDecisionNode', 'KbaVerifyNode', 'LdapDecisionNode', 'LoginCountDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PatchObjectNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProfileCompletenessDecisionNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'QueryFilterDecisionNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RequiredAttributesDecisionNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SelectIdPNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'SocialProviderHandlerNode', 'TermsAndConditionsDecisionNode', 'TimeSinceDecisionNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'ValidatedPasswordNode', 'ValidatedUsernameNode', 'WebAuthnAuthenticationNode', 'WebAuthnDeviceStorageNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode', 'product-CertificateCollectorNode', 'product-CertificateUserExtractorNode', 'product-CertificateValidationNode', 'product-KerberosNode', 'product-ReCaptchaNode', 'product-Saml2Node', 'product-WriteFederationInformationNode'];
1301
- const OOTB_NODE_TYPES_7_1 = ['PushRegistrationNode', 'GetAuthenticatorAppNode', 'MultiFactorRegistrationOptionsNode', 'OptOutMultiFactorAuthenticationNode'].concat(OOTB_NODE_TYPES_7);
1302
- const OOTB_NODE_TYPES_7_2 = ['OathRegistrationNode', 'OathTokenVerifierNode', 'PassthroughAuthenticationNode', 'ConfigProviderNode', 'DebugNode'].concat(OOTB_NODE_TYPES_7_1);
1303
- const OOTB_NODE_TYPES_6_5 = ['AbstractSocialAuthLoginNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousUserNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'CookiePresenceDecisionNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'InnerTreeEvaluatorNode', 'LdapDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'WebAuthnAuthenticationNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode'];
1304
- const OOTB_NODE_TYPES_6 = ['AbstractSocialAuthLoginNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousUserNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'CookiePresenceDecisionNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'InnerTreeEvaluatorNode', 'LdapDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'WebAuthnAuthenticationNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode'];
1305
1323
  /**
1306
1324
  * Analyze if a journey contains any custom nodes considering the detected or the overridden version.
1307
- * @param {Object} journey Journey/tree configuration object
1325
+ * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object
1308
1326
  * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.
1309
1327
  */
1310
1328
 
1311
- export async function isCustom(journey) {
1312
- let ootbNodeTypes = [];
1313
- const nodeList = journey.nodes;
1314
-
1315
- switch (storage.session.getAmVersion()) {
1316
- case '7.1.0':
1317
- ootbNodeTypes = OOTB_NODE_TYPES_7_1.slice(0);
1318
- break;
1319
-
1320
- case '7.2.0':
1321
- ootbNodeTypes = OOTB_NODE_TYPES_7_2.slice(0);
1322
- break;
1323
-
1324
- case '7.0.0':
1325
- case '7.0.1':
1326
- case '7.0.2':
1327
- ootbNodeTypes = OOTB_NODE_TYPES_7.slice(0);
1328
- break;
1329
-
1330
- case '6.5.3':
1331
- case '6.5.2.3':
1332
- case '6.5.2.2':
1333
- case '6.5.2.1':
1334
- case '6.5.2':
1335
- case '6.5.1':
1336
- case '6.5.0.2':
1337
- case '6.5.0.1':
1338
- ootbNodeTypes = OOTB_NODE_TYPES_6_5.slice(0);
1339
- break;
1340
-
1341
- case '6.0.0.7':
1342
- case '6.0.0.6':
1343
- case '6.0.0.5':
1344
- case '6.0.0.4':
1345
- case '6.0.0.3':
1346
- case '6.0.0.2':
1347
- case '6.0.0.1':
1348
- case '6.0.0':
1349
- ootbNodeTypes = OOTB_NODE_TYPES_6.slice(0);
1350
- break;
1351
-
1352
- default:
1329
+ export function isCustomJourney(journey) {
1330
+ const nodeList = Object.values(journey.nodes).concat(Object.values(journey.innerNodes));
1331
+
1332
+ for (const node of nodeList) {
1333
+ if (isCustomNode(node['_type']['_id'])) {
1353
1334
  return true;
1335
+ }
1354
1336
  }
1355
1337
 
1356
- const results = [];
1338
+ return false;
1339
+ }
1340
+ /**
1341
+ * Analyze if a journey contains any premium nodes considering the detected or the overridden version.
1342
+ * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object
1343
+ * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.
1344
+ */
1357
1345
 
1358
- for (const node in nodeList) {
1359
- if ({}.hasOwnProperty.call(nodeList, node)) {
1360
- if (!ootbNodeTypes.includes(nodeList[node].nodeType)) {
1361
- return true;
1362
- }
1346
+ export function isPremiumJourney(journey) {
1347
+ const nodeList = Object.values(journey.nodes).concat(Object.values(journey.innerNodes));
1363
1348
 
1364
- if (containerNodes.includes(nodeList[node].nodeType)) {
1365
- results.push(await getNode(node, nodeList[node].nodeType));
1366
- }
1349
+ for (const node of nodeList) {
1350
+ if (isPremiumNode(node['_type']['_id'])) {
1351
+ return true;
1367
1352
  }
1368
1353
  }
1369
1354
 
1370
- const pageNodes = await Promise.all(results);
1371
- let custom = false;
1355
+ return false;
1356
+ }
1357
+ /**
1358
+ * Analyze if a journey contains any cloud-only nodes considering the detected or the overridden version.
1359
+ * @param {SingleJourneyExportTemplate} journey Journey/tree configuration object
1360
+ * @returns {boolean} True if the journey/tree contains any cloud-only nodes, false otherwise.
1361
+ */
1372
1362
 
1373
- for (const pageNode of pageNodes) {
1374
- if (pageNode != null) {
1375
- for (const pnode of pageNode.nodes) {
1376
- if (!ootbNodeTypes.includes(pnode.nodeType)) {
1377
- custom = true;
1378
- }
1379
- }
1380
- } else {
1381
- printMessage(`isCustom ERROR: can't get ${nodeList[pageNode].nodeType} with id ${pageNode} in ${journey._id}`, 'error');
1382
- custom = false;
1363
+ export function isCloudOnlyJourney(journey) {
1364
+ const nodeList = Object.values(journey.nodes).concat(Object.values(journey.innerNodes));
1365
+
1366
+ for (const node of nodeList) {
1367
+ if (isCloudOnlyNode(node['_type']['_id'])) {
1368
+ return true;
1383
1369
  }
1384
1370
  }
1385
1371
 
1386
- return custom;
1372
+ return false;
1373
+ }
1374
+ /**
1375
+ * Get a journey's classifications, which can be one or multiple of:
1376
+ * - standard: can run on any instance of a ForgeRock platform
1377
+ * - cloud: utilize nodes, which are exclusively available in the ForgeRock Identity Cloud
1378
+ * - premium: utilizes nodes, which come at a premium
1379
+ * @param {SingleJourneyExportTemplate} journey journey export data
1380
+ * @returns {string[]} an array of one or multiple classifications
1381
+ */
1382
+
1383
+ export function getJourneyClassification(journey) {
1384
+ const classifications = [];
1385
+ const premium = isPremiumJourney(journey);
1386
+ const custom = isCustomJourney(journey);
1387
+ const cloud = isCloudOnlyJourney(journey);
1388
+
1389
+ if (custom) {
1390
+ classifications.push('custom');
1391
+ } else if (cloud) {
1392
+ classifications.push('cloud');
1393
+ } else {
1394
+ classifications.push('standard');
1395
+ }
1396
+
1397
+ if (premium) classifications.push('premium');
1398
+ return classifications;
1387
1399
  }
1388
1400
  /**
1389
1401
  * List all the journeys/trees
@@ -1394,39 +1406,47 @@ export async function isCustom(journey) {
1394
1406
 
1395
1407
  export async function listJourneys(long = false, analyze = false) {
1396
1408
  let journeys = [];
1409
+ journeys = await getJourneys();
1397
1410
 
1398
- try {
1399
- journeys = (await getTrees()).result;
1400
- } catch (error) {
1401
- printMessage(`${error.message}`, 'error');
1402
- printMessage(error.response.data, 'error');
1403
- }
1411
+ if (!long && !analyze) {
1412
+ for (const journeyStub of journeys) {
1413
+ printMessage(`${journeyStub['_id']}`, 'data');
1414
+ }
1415
+ } else {
1416
+ if (!analyze) {
1417
+ const table = createTable(['Name', 'Status', 'Tags']);
1404
1418
 
1405
- journeys.sort((a, b) => a._id.localeCompare(b._id));
1406
- let customTrees = Array(journeys.length).fill(false);
1419
+ for (const journeyStub of journeys) {
1420
+ var _journeyStub$uiConfig;
1407
1421
 
1408
- if (analyze) {
1409
- const results = [];
1422
+ table.push([`${journeyStub._id}`, journeyStub.enabled === false ? 'disabled'['brightRed'] : 'enabled'['brightGreen'], (_journeyStub$uiConfig = journeyStub.uiConfig) !== null && _journeyStub$uiConfig !== void 0 && _journeyStub$uiConfig.categories ? wordwrap(JSON.parse(journeyStub.uiConfig.categories).join(', '), 60) : '']);
1423
+ }
1410
1424
 
1411
- for (const journey of journeys) {
1412
- results.push(isCustom(journey));
1413
- }
1425
+ printMessage(table.toString(), 'data');
1426
+ } else {
1427
+ createProgressIndicator(0, 'Retrieving details of all journeys...', 'indeterminate');
1428
+ const exportPromises = [];
1429
+
1430
+ for (const journeyStub of journeys) {
1431
+ exportPromises.push(exportJourney(journeyStub['_id'], {
1432
+ useStringArrays: false,
1433
+ deps: false,
1434
+ verbose: false
1435
+ }));
1436
+ }
1414
1437
 
1415
- customTrees = await Promise.all(results);
1416
- }
1438
+ const journeyExports = await Promise.all(exportPromises);
1439
+ stopProgressIndicator('Retrieved details of all journeys.', 'success');
1440
+ const table = createTable(['Name', 'Status', 'Classification', 'Tags']);
1417
1441
 
1418
- if (!long) {
1419
- for (const [i, journey] of journeys.entries()) {
1420
- printMessage(`${customTrees[i] ? '*' : ''}${journey._id}`, 'data');
1421
- }
1422
- } else {
1423
- const table = createTable(['Name'['brightCyan'], 'Status'['brightCyan'], 'Tags'['brightCyan']]);
1442
+ for (const journeyExport of journeyExports) {
1443
+ var _journeyExport$tree$u;
1424
1444
 
1425
- for (const [i, journey] of journeys.entries()) {
1426
- table.push([`${customTrees[i] ? '*'['brightRed'] : ''}${journey._id}`, journey.enabled === false ? 'disabled'['brightRed'] : 'enabled'['brightGreen'], journey.uiConfig && journey.uiConfig.categories ? wordwrap(JSON.parse(journey.uiConfig.categories).join(', '), 60) : '']);
1427
- }
1445
+ table.push([`${journeyExport.tree._id}`, journeyExport.tree.enabled === false ? 'disabled'['brightRed'] : 'enabled'['brightGreen'], getJourneyClassification(journeyExport).join(', '), (_journeyExport$tree$u = journeyExport.tree.uiConfig) !== null && _journeyExport$tree$u !== void 0 && _journeyExport$tree$u.categories ? wordwrap(JSON.parse(journeyExport.tree.uiConfig.categories).join(', '), 60) : '']);
1446
+ }
1428
1447
 
1429
- printMessage(table.toString(), 'data');
1448
+ printMessage(table.toString(), 'data');
1449
+ }
1430
1450
  }
1431
1451
 
1432
1452
  return journeys;
@@ -0,0 +1,294 @@
1
+ import _ from 'lodash';
2
+ import storage from '../storage/SessionStorage';
3
+ import { getNode, deleteNode, getNodeTypes, getNodesByType } from '../api/NodeApi';
4
+ import { getTrees } from '../api/TreeApi';
5
+ import { printMessage, createProgressIndicator, updateProgressIndicator, stopProgressIndicator } from './utils/Console';
6
+ import { getProviderByLocationAndId, getProviderMetadata } from '../api/Saml2Api';
7
+ import { encodeBase64Url } from '../api/utils/Base64';
8
+ const containerNodes = ['PageNode', 'CustomPageNode'];
9
+ const scriptedNodes = ['ConfigProviderNode', 'ScriptedDecisionNode', 'ClientScriptNode', 'SocialProviderHandlerNode', 'CustomScriptNode'];
10
+ const emailTemplateNodes = ['EmailSuspendNode', 'EmailTemplateNode'];
11
+ const emptyScriptPlaceholder = '[Empty]';
12
+ /**
13
+ * Helper to get all SAML2 dependencies for a given node object
14
+ * @param {Object} nodeObject node object
15
+ * @param {[Object]} allProviders array of all saml2 providers objects
16
+ * @param {[Object]} allCirclesOfTrust array of all circle of trust objects
17
+ * @returns {Promise} a promise that resolves to an object containing a saml2 dependencies
18
+ */
19
+
20
+ async function getSaml2NodeDependencies(nodeObject, allProviders, allCirclesOfTrust) {
21
+ const samlProperties = ['metaAlias', 'idpEntityId'];
22
+ const saml2EntityPromises = [];
23
+
24
+ for (const samlProperty of samlProperties) {
25
+ // In the following line nodeObject[samlProperty] will look like '/alpha/iSPAzure'.
26
+ const entityId = samlProperty === 'metaAlias' ? _.last(nodeObject[samlProperty].split('/')) : nodeObject[samlProperty];
27
+
28
+ const entity = _.find(allProviders, {
29
+ entityId
30
+ });
31
+
32
+ if (entity) {
33
+ try {
34
+ const providerResponse = await getProviderByLocationAndId(entity.location, entity._id);
35
+ /**
36
+ * Adding entityLocation here to the entityResponse because the import tool
37
+ * needs to know whether the saml2 entity is remote or not (this will be removed
38
+ * from the config before importing see updateSaml2Entity and createSaml2Entity functions).
39
+ * Importing a remote saml2 entity is a slightly different request (see createSaml2Entity).
40
+ */
41
+
42
+ providerResponse.entityLocation = entity.location;
43
+
44
+ if (entity.location === 'remote') {
45
+ // get the xml representation of this entity and add it to the entityResponse;
46
+ const metaDataResponse = await getProviderMetadata(providerResponse.entityId);
47
+ providerResponse.base64EntityXML = encodeBase64Url(metaDataResponse);
48
+ }
49
+
50
+ saml2EntityPromises.push(providerResponse);
51
+ } catch (error) {
52
+ printMessage(error.message, 'error');
53
+ }
54
+ }
55
+ }
56
+
57
+ try {
58
+ const saml2EntitiesPromisesResults = await Promise.all(saml2EntityPromises);
59
+ const saml2Entities = [];
60
+
61
+ for (const saml2Entity of saml2EntitiesPromisesResults) {
62
+ if (saml2Entity) {
63
+ saml2Entities.push(saml2Entity);
64
+ }
65
+ }
66
+
67
+ const samlEntityIds = _.map(saml2Entities, saml2EntityConfig => `${saml2EntityConfig.entityId}|saml2`);
68
+
69
+ const circlesOfTrust = _.filter(allCirclesOfTrust, circleOfTrust => {
70
+ let hasEntityId = false;
71
+
72
+ for (const trustedProvider of circleOfTrust.trustedProviders) {
73
+ if (!hasEntityId && samlEntityIds.includes(trustedProvider)) {
74
+ hasEntityId = true;
75
+ }
76
+ }
77
+
78
+ return hasEntityId;
79
+ });
80
+
81
+ const saml2NodeDependencies = {
82
+ saml2Entities,
83
+ circlesOfTrust
84
+ };
85
+ return saml2NodeDependencies;
86
+ } catch (error) {
87
+ printMessage(error.message, 'error');
88
+ const saml2NodeDependencies = {
89
+ saml2Entities: [],
90
+ circlesOfTrust: []
91
+ };
92
+ return saml2NodeDependencies;
93
+ }
94
+ } // export async function getTreeNodes(treeObject) {
95
+ // const nodeList = Object.entries(treeObject.nodes);
96
+ // const results = await Promise.allSettled(
97
+ // nodeList.map(
98
+ // async ([nodeId, nodeInfo]) => await getNode(nodeId, nodeInfo['nodeType'])
99
+ // )
100
+ // );
101
+ // const nodes = results.filter((r) => r.status === 'fulfilled');
102
+ // nodes.map((f) => {
103
+ // return f.status;
104
+ // });
105
+ // const failedList = results.filter((r) => r.status === 'rejected');
106
+ // return nodes;
107
+ // }
108
+
109
+ /**
110
+ * Find all node configuration objects that are no longer referenced by any tree
111
+ * @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes
112
+ */
113
+
114
+
115
+ export async function findOrphanedNodes() {
116
+ const allNodes = [];
117
+ const orphanedNodes = [];
118
+ let types = [];
119
+ const allJourneys = (await getTrees()).result;
120
+ let errorMessage = '';
121
+ const errorTypes = [];
122
+ createProgressIndicator(undefined, `Counting total nodes...`, 'indeterminate');
123
+
124
+ try {
125
+ types = (await getNodeTypes()).result;
126
+ } catch (error) {
127
+ printMessage('Error retrieving all available node types:', 'error');
128
+ printMessage(error.response.data, 'error');
129
+ return [];
130
+ }
131
+
132
+ for (const type of types) {
133
+ try {
134
+ // eslint-disable-next-line no-await-in-loop, no-loop-func
135
+ const nodes = (await getNodesByType(type._id)).result;
136
+
137
+ for (const node of nodes) {
138
+ allNodes.push(node);
139
+ updateProgressIndicator(`${allNodes.length} total nodes${errorMessage}`);
140
+ }
141
+ } catch (error) {
142
+ errorTypes.push(type._id);
143
+ errorMessage = ` (Skipped type(s): ${errorTypes})`['yellow'];
144
+ updateProgressIndicator(`${allNodes.length} total nodes${errorMessage}`);
145
+ }
146
+ }
147
+
148
+ if (errorTypes.length > 0) {
149
+ stopProgressIndicator(`${allNodes.length} total nodes${errorMessage}`, 'warn');
150
+ } else {
151
+ stopProgressIndicator(`${allNodes.length} total nodes`, 'success');
152
+ }
153
+
154
+ createProgressIndicator(undefined, 'Counting active nodes...', 'indeterminate');
155
+ const activeNodes = [];
156
+
157
+ for (const journey of allJourneys) {
158
+ for (const nodeId in journey.nodes) {
159
+ if ({}.hasOwnProperty.call(journey.nodes, nodeId)) {
160
+ activeNodes.push(nodeId);
161
+ updateProgressIndicator(`${activeNodes.length} active nodes`);
162
+ const node = journey.nodes[nodeId];
163
+
164
+ if (containerNodes.includes(node.nodeType)) {
165
+ const containerNode = await getNode(nodeId, node.nodeType);
166
+
167
+ for (const innerNode of containerNode.nodes) {
168
+ activeNodes.push(innerNode._id);
169
+ updateProgressIndicator(`${activeNodes.length} active nodes`);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ stopProgressIndicator(`${activeNodes.length} active nodes`, 'success');
177
+ createProgressIndicator(undefined, 'Calculating orphaned nodes...', 'indeterminate');
178
+ const diff = allNodes.filter(x => !activeNodes.includes(x._id));
179
+
180
+ for (const orphanedNode of diff) {
181
+ orphanedNodes.push(orphanedNode);
182
+ }
183
+
184
+ stopProgressIndicator(`${orphanedNodes.length} orphaned nodes`, 'success');
185
+ return orphanedNodes;
186
+ }
187
+ /**
188
+ * Remove orphaned nodes
189
+ * @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove
190
+ * @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting
191
+ */
192
+
193
+ export async function removeOrphanedNodes(orphanedNodes) {
194
+ const errorNodes = [];
195
+ createProgressIndicator(orphanedNodes.length, 'Removing orphaned nodes...');
196
+
197
+ for (const node of orphanedNodes) {
198
+ updateProgressIndicator(`Removing ${node['_id']}...`);
199
+
200
+ try {
201
+ // eslint-disable-next-line no-await-in-loop
202
+ await deleteNode(node['_id'], node['_type']['_id']);
203
+ } catch (deleteError) {
204
+ errorNodes.push(node);
205
+ printMessage(` ${deleteError}`, 'error');
206
+ }
207
+ }
208
+
209
+ stopProgressIndicator(`Removed ${orphanedNodes.length} orphaned nodes.`);
210
+ return errorNodes;
211
+ }
212
+ const OOTB_NODE_TYPES_7 = ['AcceptTermsAndConditionsNode', 'AccountActiveDecisionNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousSessionUpgradeNode', 'AnonymousUserNode', 'AttributeCollectorNode', 'AttributePresentDecisionNode', 'AttributeValueDecisionNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'ConsentNode', 'CookiePresenceDecisionNode', 'CreateObjectNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'DeviceGeoFencingNode', 'DeviceLocationMatchNode', 'DeviceMatchNode', 'DeviceProfileCollectorNode', 'DeviceSaveNode', 'DeviceTamperingVerificationNode', 'DisplayUserNameNode', 'EmailSuspendNode', 'EmailTemplateNode', 'IdentifyExistingUserNode', 'IncrementLoginCountNode', 'InnerTreeEvaluatorNode', 'IotAuthenticationNode', 'IotRegistrationNode', 'KbaCreateNode', 'KbaDecisionNode', 'KbaVerifyNode', 'LdapDecisionNode', 'LoginCountDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PatchObjectNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProfileCompletenessDecisionNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'QueryFilterDecisionNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RequiredAttributesDecisionNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SelectIdPNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'SocialProviderHandlerNode', 'TermsAndConditionsDecisionNode', 'TimeSinceDecisionNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'ValidatedPasswordNode', 'ValidatedUsernameNode', 'WebAuthnAuthenticationNode', 'WebAuthnDeviceStorageNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode', 'product-CertificateCollectorNode', 'product-CertificateUserExtractorNode', 'product-CertificateValidationNode', 'product-KerberosNode', 'product-ReCaptchaNode', 'product-Saml2Node', 'product-WriteFederationInformationNode'];
213
+ const OOTB_NODE_TYPES_7_1 = ['PushRegistrationNode', 'GetAuthenticatorAppNode', 'MultiFactorRegistrationOptionsNode', 'OptOutMultiFactorAuthenticationNode'].concat(OOTB_NODE_TYPES_7);
214
+ const OOTB_NODE_TYPES_7_2 = ['OathRegistrationNode', 'OathTokenVerifierNode', 'PassthroughAuthenticationNode', 'ConfigProviderNode', 'DebugNode'].concat(OOTB_NODE_TYPES_7_1);
215
+ const OOTB_NODE_TYPES_7_3 = [].concat(OOTB_NODE_TYPES_7_2);
216
+ const OOTB_NODE_TYPES_6_5 = ['AbstractSocialAuthLoginNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousUserNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'CookiePresenceDecisionNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'InnerTreeEvaluatorNode', 'LdapDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'WebAuthnAuthenticationNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode'];
217
+ const OOTB_NODE_TYPES_6 = ['AbstractSocialAuthLoginNode', 'AccountLockoutNode', 'AgentDataStoreDecisionNode', 'AnonymousUserNode', 'AuthLevelDecisionNode', 'ChoiceCollectorNode', 'CookiePresenceDecisionNode', 'CreatePasswordNode', 'DataStoreDecisionNode', 'InnerTreeEvaluatorNode', 'LdapDecisionNode', 'MessageNode', 'MetadataNode', 'MeterNode', 'ModifyAuthLevelNode', 'OneTimePasswordCollectorDecisionNode', 'OneTimePasswordGeneratorNode', 'OneTimePasswordSmsSenderNode', 'OneTimePasswordSmtpSenderNode', 'PageNode', 'PasswordCollectorNode', 'PersistentCookieDecisionNode', 'PollingWaitNode', 'ProvisionDynamicAccountNode', 'ProvisionIdmAccountNode', 'PushAuthenticationSenderNode', 'PushResultVerifierNode', 'RecoveryCodeCollectorDecisionNode', 'RecoveryCodeDisplayNode', 'RegisterLogoutWebhookNode', 'RemoveSessionPropertiesNode', 'RetryLimitDecisionNode', 'ScriptedDecisionNode', 'SessionDataNode', 'SetFailureUrlNode', 'SetPersistentCookieNode', 'SetSessionPropertiesNode', 'SetSuccessUrlNode', 'SocialFacebookNode', 'SocialGoogleNode', 'SocialNode', 'SocialOAuthIgnoreProfileNode', 'SocialOpenIdConnectNode', 'TimerStartNode', 'TimerStopNode', 'UsernameCollectorNode', 'WebAuthnAuthenticationNode', 'WebAuthnRegistrationNode', 'ZeroPageLoginNode'];
218
+ const CLOUD_ONLY_NODE_TYPES = ['IdentityStoreDecisionNode'];
219
+ const PREMIUM_NODE_TYPES = ['AutonomousAccessSignalNode', 'AutonomousAccessDecisionNode', 'AutonomousAccessResultNode'];
220
+ /**
221
+ * Analyze if a node is a premium node.
222
+ * @param {string} nodeType Node type
223
+ * @returns {boolean} True if the node type is premium, false otherwise.
224
+ */
225
+
226
+ export function isPremiumNode(nodeType) {
227
+ return PREMIUM_NODE_TYPES.includes(nodeType);
228
+ }
229
+ /**
230
+ * Analyze if a node is a cloud-only node.
231
+ * @param {string} nodeType Node type
232
+ * @returns {boolean} True if the node type is cloud-only, false otherwise.
233
+ */
234
+
235
+ export function isCloudOnlyNode(nodeType) {
236
+ return CLOUD_ONLY_NODE_TYPES.includes(nodeType);
237
+ }
238
+ /**
239
+ * Analyze if a node is custom.
240
+ * @param {string} nodeType Node type
241
+ * @returns {boolean} True if the node type is custom, false otherwise.
242
+ */
243
+
244
+ export function isCustomNode(nodeType) {
245
+ let ootbNodeTypes = [];
246
+
247
+ switch (storage.session.getAmVersion()) {
248
+ case '7.1.0':
249
+ ootbNodeTypes = OOTB_NODE_TYPES_7_1.slice(0);
250
+ break;
251
+
252
+ case '7.2.0':
253
+ ootbNodeTypes = OOTB_NODE_TYPES_7_2.slice(0);
254
+ break;
255
+
256
+ case '7.3.0':
257
+ ootbNodeTypes = OOTB_NODE_TYPES_7_3.slice(0);
258
+ break;
259
+
260
+ case '7.0.0':
261
+ case '7.0.1':
262
+ case '7.0.2':
263
+ ootbNodeTypes = OOTB_NODE_TYPES_7.slice(0);
264
+ break;
265
+
266
+ case '6.5.3':
267
+ case '6.5.2.3':
268
+ case '6.5.2.2':
269
+ case '6.5.2.1':
270
+ case '6.5.2':
271
+ case '6.5.1':
272
+ case '6.5.0.2':
273
+ case '6.5.0.1':
274
+ ootbNodeTypes = OOTB_NODE_TYPES_6_5.slice(0);
275
+ break;
276
+
277
+ case '6.0.0.7':
278
+ case '6.0.0.6':
279
+ case '6.0.0.5':
280
+ case '6.0.0.4':
281
+ case '6.0.0.3':
282
+ case '6.0.0.2':
283
+ case '6.0.0.1':
284
+ case '6.0.0':
285
+ ootbNodeTypes = OOTB_NODE_TYPES_6.slice(0);
286
+ break;
287
+
288
+ default:
289
+ return true;
290
+ }
291
+
292
+ return !ootbNodeTypes.includes(nodeType) && !isPremiumNode(nodeType) && !isCloudOnlyNode(nodeType);
293
+ }
294
+ //# sourceMappingURL=NodeOps.js.map