@rockcarver/frodo-lib 0.12.1 → 0.12.2-2

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.
Files changed (159) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/cjs/api/BaseApi.js +36 -8
  3. package/cjs/api/BaseApi.js.map +1 -1
  4. package/cjs/api/NodeApi.js +190 -0
  5. package/cjs/api/NodeApi.js.map +1 -0
  6. package/cjs/api/NodeApi.test.js.map +1 -0
  7. package/cjs/api/OAuth2ClientApi.js +3 -1
  8. package/cjs/api/OAuth2ClientApi.js.map +1 -1
  9. package/cjs/api/SecretsApi.js +59 -29
  10. package/cjs/api/SecretsApi.js.map +1 -1
  11. package/cjs/api/StartupApi.js +21 -8
  12. package/cjs/api/StartupApi.js.map +1 -1
  13. package/cjs/api/StartupApi.test.js.map +1 -0
  14. package/cjs/api/TreeApi.js +30 -161
  15. package/cjs/api/TreeApi.js.map +1 -1
  16. package/cjs/api/TreeApi.test.js.map +1 -0
  17. package/cjs/api/VariablesApi.js +35 -20
  18. package/cjs/api/VariablesApi.js.map +1 -1
  19. package/cjs/index.js +15 -5
  20. package/cjs/index.js.map +1 -1
  21. package/cjs/ops/AuthenticateOps.js +1 -1
  22. package/cjs/ops/AuthenticateOps.js.map +1 -1
  23. package/cjs/ops/IdpOps.js +1 -1
  24. package/cjs/ops/IdpOps.js.map +1 -1
  25. package/cjs/ops/JourneyOps.js +233 -204
  26. package/cjs/ops/JourneyOps.js.map +1 -1
  27. package/cjs/ops/OAuth2ClientOps.js +27 -20
  28. package/cjs/ops/OAuth2ClientOps.js.map +1 -1
  29. package/cjs/ops/SecretsOps.js +9 -7
  30. package/cjs/ops/SecretsOps.js.map +1 -1
  31. package/cjs/ops/StartupOps.js +61 -71
  32. package/cjs/ops/StartupOps.js.map +1 -1
  33. package/cjs/ops/VariablesOps.js +3 -3
  34. package/cjs/ops/VariablesOps.js.map +1 -1
  35. package/cjs/ops/utils/Console.js +3 -2
  36. package/cjs/ops/utils/Console.js.map +1 -1
  37. package/cjs/ops/utils/ExportImportUtils.js +1 -0
  38. package/cjs/ops/utils/ExportImportUtils.js.map +1 -1
  39. package/esm/api/BaseApi.mjs +35 -7
  40. package/esm/api/NodeApi.mjs +114 -0
  41. package/esm/api/NodeApi.test.mjs +105 -0
  42. package/esm/api/OAuth2ClientApi.mjs +4 -2
  43. package/esm/api/SecretsApi.mjs +59 -29
  44. package/esm/api/StartupApi.mjs +18 -8
  45. package/esm/api/StartupApi.test.mjs +56 -0
  46. package/esm/api/TreeApi.mjs +29 -99
  47. package/esm/api/TreeApi.test.mjs +175 -0
  48. package/esm/api/VariablesApi.mjs +35 -20
  49. package/esm/index.mjs +7 -5
  50. package/esm/ops/AuthenticateOps.mjs +1 -1
  51. package/esm/ops/IdpOps.mjs +1 -1
  52. package/esm/ops/JourneyOps.mjs +186 -154
  53. package/esm/ops/OAuth2ClientOps.mjs +9 -4
  54. package/esm/ops/SecretsOps.mjs +9 -7
  55. package/esm/ops/StartupOps.mjs +59 -62
  56. package/esm/ops/VariablesOps.mjs +3 -3
  57. package/esm/ops/utils/Console.mjs +3 -2
  58. package/esm/ops/utils/ExportImportUtils.mjs +1 -0
  59. package/package.json +7 -4
  60. package/types/api/AuthenticateApi.d.ts +2 -0
  61. package/types/api/AuthenticateApi.d.ts.map +1 -0
  62. package/types/api/BaseApi.d.ts +50 -0
  63. package/types/api/BaseApi.d.ts.map +1 -0
  64. package/types/api/CirclesOfTrustApi.d.ts +24 -0
  65. package/types/api/CirclesOfTrustApi.d.ts.map +1 -0
  66. package/types/api/EmailTemplateApi.d.ts +22 -0
  67. package/types/api/EmailTemplateApi.d.ts.map +1 -0
  68. package/types/api/IdmConfigApi.d.ts +39 -0
  69. package/types/api/IdmConfigApi.d.ts.map +1 -0
  70. package/types/api/LogApi.d.ts +4 -0
  71. package/types/api/LogApi.d.ts.map +1 -0
  72. package/types/api/ManagedObjectApi.d.ts +21 -0
  73. package/types/api/ManagedObjectApi.d.ts.map +1 -0
  74. package/types/api/NodeApi.d.ts +38 -0
  75. package/types/api/NodeApi.d.ts.map +1 -0
  76. package/types/api/OAuth2ClientApi.d.ts +18 -0
  77. package/types/api/OAuth2ClientApi.d.ts.map +1 -0
  78. package/types/api/OAuth2OIDCApi.d.ts +22 -0
  79. package/types/api/OAuth2OIDCApi.d.ts.map +1 -0
  80. package/types/api/OAuth2ProviderApi.d.ts +5 -0
  81. package/types/api/OAuth2ProviderApi.d.ts.map +1 -0
  82. package/types/api/RealmApi.d.ts +30 -0
  83. package/types/api/RealmApi.d.ts.map +1 -0
  84. package/types/api/Saml2Api.d.ts +52 -0
  85. package/types/api/Saml2Api.d.ts.map +1 -0
  86. package/types/api/ScriptApi.d.ts +24 -0
  87. package/types/api/ScriptApi.d.ts.map +1 -0
  88. package/types/api/SecretsApi.d.ts +10 -0
  89. package/types/api/SecretsApi.d.ts.map +1 -0
  90. package/types/api/ServerInfoApi.d.ts +10 -0
  91. package/types/api/ServerInfoApi.d.ts.map +1 -0
  92. package/types/api/SocialIdentityProvidersApi.d.ts +31 -0
  93. package/types/api/SocialIdentityProvidersApi.d.ts.map +1 -0
  94. package/types/api/StartupApi.d.ts +14 -0
  95. package/types/api/StartupApi.d.ts.map +1 -0
  96. package/types/api/ThemeApi.d.ts +54 -0
  97. package/types/api/ThemeApi.d.ts.map +1 -0
  98. package/types/api/TreeApi.d.ts +24 -0
  99. package/types/api/TreeApi.d.ts.map +1 -0
  100. package/types/api/VariablesApi.d.ts +32 -0
  101. package/types/api/VariablesApi.d.ts.map +1 -0
  102. package/types/api/utils/ApiUtils.d.ts +29 -0
  103. package/types/api/utils/ApiUtils.d.ts.map +1 -0
  104. package/types/api/utils/Base64.d.ts +30 -0
  105. package/types/api/utils/Base64.d.ts.map +1 -0
  106. package/types/index.d.ts +26 -0
  107. package/types/index.d.ts.map +1 -0
  108. package/types/ops/AdminOps.d.ts +11 -0
  109. package/types/ops/AdminOps.d.ts.map +1 -0
  110. package/types/ops/AuthenticateOps.d.ts +6 -0
  111. package/types/ops/AuthenticateOps.d.ts.map +1 -0
  112. package/types/ops/CirclesOfTrustOps.d.ts +40 -0
  113. package/types/ops/CirclesOfTrustOps.d.ts.map +1 -0
  114. package/types/ops/ConnectionProfileOps.d.ts +47 -0
  115. package/types/ops/ConnectionProfileOps.d.ts.map +1 -0
  116. package/types/ops/EmailTemplateOps.d.ts +40 -0
  117. package/types/ops/EmailTemplateOps.d.ts.map +1 -0
  118. package/types/ops/IdmOps.d.ts +27 -0
  119. package/types/ops/IdmOps.d.ts.map +1 -0
  120. package/types/ops/IdpOps.d.ts +45 -0
  121. package/types/ops/IdpOps.d.ts.map +1 -0
  122. package/types/ops/JourneyOps.d.ts +151 -0
  123. package/types/ops/JourneyOps.d.ts.map +1 -0
  124. package/types/ops/LogOps.d.ts +5 -0
  125. package/types/ops/LogOps.d.ts.map +1 -0
  126. package/types/ops/ManagedObjectOps.d.ts +14 -0
  127. package/types/ops/ManagedObjectOps.d.ts.map +1 -0
  128. package/types/ops/OAuth2ClientOps.d.ts +24 -0
  129. package/types/ops/OAuth2ClientOps.d.ts.map +1 -0
  130. package/types/ops/OrganizationOps.d.ts +11 -0
  131. package/types/ops/OrganizationOps.d.ts.map +1 -0
  132. package/types/ops/RealmOps.d.ts +22 -0
  133. package/types/ops/RealmOps.d.ts.map +1 -0
  134. package/types/ops/SamlOps.d.ts +51 -0
  135. package/types/ops/SamlOps.d.ts.map +1 -0
  136. package/types/ops/ScriptOps.d.ts +30 -0
  137. package/types/ops/ScriptOps.d.ts.map +1 -0
  138. package/types/ops/SecretsOps.d.ts +63 -0
  139. package/types/ops/SecretsOps.d.ts.map +1 -0
  140. package/types/ops/StartupOps.d.ts +25 -0
  141. package/types/ops/StartupOps.d.ts.map +1 -0
  142. package/types/ops/ThemeOps.d.ts +66 -0
  143. package/types/ops/ThemeOps.d.ts.map +1 -0
  144. package/types/ops/VariablesOps.d.ts +39 -0
  145. package/types/ops/VariablesOps.d.ts.map +1 -0
  146. package/types/ops/utils/Console.d.ts +63 -0
  147. package/types/ops/utils/Console.d.ts.map +1 -0
  148. package/types/ops/utils/DataProtection.d.ts +6 -0
  149. package/types/ops/utils/DataProtection.d.ts.map +1 -0
  150. package/types/ops/utils/ExportImportUtils.d.ts +22 -0
  151. package/types/ops/utils/ExportImportUtils.d.ts.map +1 -0
  152. package/types/ops/utils/OpsUtils.d.ts +27 -0
  153. package/types/ops/utils/OpsUtils.d.ts.map +1 -0
  154. package/types/ops/utils/Wordwrap.d.ts +1 -0
  155. package/types/ops/utils/Wordwrap.d.ts.map +1 -0
  156. package/types/storage/SessionStorage.d.ts +47 -0
  157. package/types/storage/SessionStorage.d.ts.map +1 -0
  158. package/types/storage/StaticStorage.d.ts +14 -0
  159. package/types/storage/StaticStorage.d.ts.map +1 -0
@@ -0,0 +1,11 @@
1
+ export declare function listOAuth2CustomClients(): Promise<any>;
2
+ export declare function listOAuth2AdminClients(): Promise<any>;
3
+ export declare function listNonOAuth2AdminStaticUserMappings(showProtected: any): Promise<any>;
4
+ export declare function addAutoIdStaticUserMapping(): Promise<void>;
5
+ export declare function grantOAuth2ClientAdminPrivileges(clientId: any): Promise<void>;
6
+ export declare function revokeOAuth2ClientAdminPrivileges(clientId: any): Promise<void>;
7
+ export declare function createOAuth2ClientWithAdminPrivileges(clientId: any, clientSecret: any): Promise<void>;
8
+ export declare function createLongLivedToken(clientId: any, clientSecret: any, scope: any, secret: any, lifetime: any): Promise<any>;
9
+ export declare function removeStaticUserMapping(subject: any): Promise<void>;
10
+ export declare function hideGenericExtensionAttributes(includeCustomized: any, dryRun: any): Promise<void>;
11
+ export declare function showGenericExtensionAttributes(includeCustomized: any, dryRun: any): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/AdminOps.ts"],"names":[],"mappings":"AAsFA,wBAAsB,uBAAuB,iBAW5C;AAwBD,wBAAsB,sBAAsB,iBAiC3C;AA2CD,wBAAsB,oCAAoC,CAAC,aAAa,KAAA,gBA4BvE;AAsKD,wBAAsB,0BAA0B,kBA+C/C;AAED,wBAAsB,gCAAgC,CAAC,QAAQ,KAAA,iBAsB9D;AAoHD,wBAAsB,iCAAiC,CAAC,QAAQ,KAAA,iBAsB/D;AAED,wBAAsB,qCAAqC,CACzD,QAAQ,KAAA,EACR,YAAY,KAAA,iBAeb;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,KAAA,EACR,YAAY,KAAA,EACZ,KAAK,KAAA,EACL,MAAM,KAAA,EACN,QAAQ,KAAA,gBAwCT;AAED,wBAAsB,uBAAuB,CAAC,OAAO,KAAA,iBAyBpD;AAED,wBAAsB,8BAA8B,CAClD,iBAAiB,KAAA,EACjB,MAAM,KAAA,iBA0CP;AAED,wBAAsB,8BAA8B,CAClD,iBAAiB,KAAA,EACjB,MAAM,KAAA,iBA0CP","file":"AdminOps.d.ts","sourcesContent":["import fs from 'fs';\nimport _ from 'lodash';\nimport {\n getOAuth2Clients,\n getOAuth2Client,\n putOAuth2Client,\n} from '../api/OAuth2ClientApi';\nimport { getConfigEntity, putConfigEntity } from '../api/IdmConfigApi';\nimport { isEqualJson, getRealmManagedUser } from './utils/OpsUtils';\nimport { getRealmManagedOrganization } from './OrganizationOps';\nimport { getOAuth2Provider } from '../api/OAuth2ProviderApi';\nimport { putSecret } from '../api/SecretsApi';\nimport { clientCredentialsGrant } from '../api/OAuth2OIDCApi';\nimport { printMessage } from './utils/Console';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst OAUTH2_CLIENT = JSON.parse(\n fs.readFileSync(\n path.resolve(__dirname, './templates/OAuth2ClientTemplate.json'),\n 'utf8'\n )\n);\nconst GENERIC_EXTENSION_ATTRIBUTES = JSON.parse(\n fs.readFileSync(\n path.resolve(\n __dirname,\n './templates/cloud/GenericExtensionAttributesTemplate.json'\n ),\n 'utf8'\n )\n);\n\nconst protectedClients = ['ui', 'idm-provisioning'];\nconst protectedSubjects = ['amadmin', 'autoid-resource-server'];\n\nconst privilegedScopes = [\n 'am-introspect-all-tokens',\n 'fr:idm:*',\n 'fr:idc:esv:*',\n];\n// const privilegedUsers = ['openidm-admin'];\nconst privilegedRoles = [\n 'internal/role/openidm-authorized',\n 'internal/role/openidm-admin',\n];\n\nconst adminScopes = ['fr:idm:*', 'fr:idc:esv:*'];\nconst adminDefaultScopes = ['fr:idm:*'];\nconst adminRoles = [\n 'internal/role/openidm-authorized',\n 'internal/role/openidm-admin',\n];\nconst autoIdRoles = [\n 'internal/role/platform-provisioning',\n 'internal/role/openidm-authorized',\n 'internal/role/openidm-admin',\n];\n\n/*\n * List all oauth2 clients, which have a corresponding staticUserMapping\n * in the IDM authentication.json:\n {\n \"_id\": \"authentication\",\n \"rsFilter\": {\n ...\n \"staticUserMapping\": [\n {\n \"subject\": \"someOauth2ClientID\",\n \"localUser\": \"internal/user/openidm-admin\",\n \"userRoles\": \"authzRoles/*\",\n \"roles\": [\n \"internal/role/openidm-authorized\",\n \"internal/role/openidm-admin\"\n ]\n },\n {\n \"subject\": \"RCSClient\",\n \"localUser\": \"internal/user/idm-provisioning\"\n }\n ]\n }\n }\n */\nexport async function listOAuth2CustomClients() {\n let clients = (await getOAuth2Clients()).data.result;\n clients = clients\n .map((client) => client._id)\n .filter((client) => !protectedClients.includes(client));\n const authentication = (await getConfigEntity('authentication')).data;\n const subjects = authentication.rsFilter.staticUserMapping\n .map((mapping) => mapping.subject)\n .filter((subject) => !protectedSubjects.includes(subject));\n const adminClients = subjects.filter((subject) => clients.includes(subject));\n return adminClients;\n}\n\n/*\n * List all oauth2 clients, which have the fr:idm:* scope and a \n * corresponding staticUserMapping in the IDM authentication.json\n * and are assigned admin privileges:\n {\n \"_id\": \"authentication\",\n \"rsFilter\": {\n ...\n \"staticUserMapping\": [\n {\n \"subject\": \"someOauth2ClientID\",\n \"localUser\": \"internal/user/openidm-admin\",\n \"userRoles\": \"authzRoles/*\",\n \"roles\": [\n \"internal/role/openidm-authorized\",\n \"internal/role/openidm-admin\"\n ]\n }\n ]\n }\n }\n */\nexport async function listOAuth2AdminClients() {\n let clients = (await getOAuth2Clients()).data.result;\n clients = clients\n .filter((client) => {\n let isPrivileged = false;\n if (client.coreOAuth2ClientConfig.scopes) {\n client.coreOAuth2ClientConfig.scopes.forEach((scope) => {\n if (privilegedScopes.includes(scope)) {\n isPrivileged = true;\n }\n });\n }\n return isPrivileged;\n })\n .map((client) => client._id)\n .filter((client) => !protectedClients.includes(client));\n const authentication = (await getConfigEntity('authentication')).data;\n const subjects = authentication.rsFilter.staticUserMapping\n .filter((mapping) => {\n let isPrivileged = false;\n if (mapping.roles) {\n mapping.roles.forEach((role) => {\n if (privilegedRoles.includes(role)) {\n isPrivileged = true;\n }\n });\n }\n return isPrivileged;\n })\n .map((mapping) => mapping.subject)\n .filter((subject) => !protectedSubjects.includes(subject));\n const adminClients = subjects.filter((subject) => clients.includes(subject));\n return adminClients;\n}\n\n/*\n * List all static user mappings that are not oauth2 clients in authentication.json\n * and are assigned admin privileges:\n {\n \"_id\": \"authentication\",\n \"rsFilter\": {\n ...\n \"staticUserMapping\": [\n {\n \"subject\": \"amadmin\",\n \"localUser\": \"internal/user/openidm-admin\",\n \"userRoles\": \"authzRoles/*\",\n \"roles\": [\n \"internal/role/openidm-authorized\",\n \"internal/role/openidm-admin\"\n ]\n },\n {\n \"subject\": \"idm-provisioning\",\n \"localUser\": \"internal/user/idm-provisioning\",\n \"roles\": [\n \"internal/role/platform-provisioning\"\n ]\n },\n {\n \"subject\": \"RCSClient\",\n \"localUser\": \"internal/user/idm-provisioning\"\n },\n {\n \"subject\": \"autoid-resource-server\",\n \"localUser\": \"internal/user/idm-provisioning\",\n \"roles\": [\n \"internal/role/platform-provisioning\",\n \"internal/role/openidm-authorized\",\n \"internal/role/openidm-admin\"\n ]\n }\n ]\n }\n }\n */\nexport async function listNonOAuth2AdminStaticUserMappings(showProtected) {\n let clients = (await getOAuth2Clients()).data.result;\n clients = clients\n .map((client) => client._id)\n .filter((client) => !protectedClients.includes(client));\n const authentication = (await getConfigEntity('authentication')).data;\n let subjects = authentication.rsFilter.staticUserMapping\n .filter((mapping) => {\n let isPrivileged = false;\n if (mapping.roles) {\n mapping.roles.forEach((role) => {\n if (privilegedRoles.includes(role)) {\n isPrivileged = true;\n }\n });\n }\n return isPrivileged;\n })\n .map((mapping) => mapping.subject);\n if (!showProtected) {\n subjects = subjects.filter(\n (subject) => !protectedSubjects.includes(subject)\n );\n }\n const adminSubjects = subjects.filter(\n (subject) => !clients.includes(subject)\n );\n return adminSubjects;\n}\n\nasync function getDynamicClientRegistrationScope() {\n const provider = (await getOAuth2Provider()).data;\n return provider.clientDynamicRegistrationConfig\n .dynamicClientRegistrationScope;\n}\n\nasync function addAdminScopes(clientId, client) {\n const modClient = client;\n const allAdminScopes = adminScopes.concat([\n await getDynamicClientRegistrationScope(),\n ]);\n let addScopes = [];\n if (\n modClient.coreOAuth2ClientConfig.scopes &&\n modClient.coreOAuth2ClientConfig.scopes.value\n ) {\n addScopes = allAdminScopes.filter((scope) => {\n let add = false;\n if (!modClient.coreOAuth2ClientConfig.scopes.value.includes(scope)) {\n add = true;\n }\n return add;\n });\n modClient.coreOAuth2ClientConfig.scopes.value =\n modClient.coreOAuth2ClientConfig.scopes.value.concat(addScopes);\n } else {\n modClient.coreOAuth2ClientConfig.scopes.value = allAdminScopes;\n }\n let addDefaultScope = false;\n if (\n modClient.coreOAuth2ClientConfig.defaultScopes &&\n modClient.coreOAuth2ClientConfig.defaultScopes.value\n ) {\n if (modClient.coreOAuth2ClientConfig.defaultScopes.value.length === 0) {\n addDefaultScope = true;\n modClient.coreOAuth2ClientConfig.defaultScopes.value = adminDefaultScopes;\n } else {\n printMessage(\n `Client \"${clientId}\" already has default scopes configured, not adding admin default scope.`\n );\n }\n }\n if (addScopes.length > 0 || addDefaultScope) {\n printMessage(`Adding admin scopes to client \"${clientId}\"...`);\n } else {\n printMessage(`Client \"${clientId}\" already has admin scopes.`);\n }\n return modClient;\n}\n\nfunction addClientCredentialsGrantType(clientId, client) {\n const modClient = client;\n let modified = false;\n if (\n modClient.advancedOAuth2ClientConfig.grantTypes &&\n modClient.advancedOAuth2ClientConfig.grantTypes.value\n ) {\n if (\n !modClient.advancedOAuth2ClientConfig.grantTypes.value.includes(\n 'client_credentials'\n )\n ) {\n modified = true;\n modClient.advancedOAuth2ClientConfig.grantTypes.value.push(\n 'client_credentials'\n );\n }\n } else {\n modClient.advancedOAuth2ClientConfig.grantTypes.value = [\n 'client_credentials',\n ];\n }\n modClient.advancedOAuth2ClientConfig.grantTypes.inherited = false;\n if (modified) {\n printMessage(\n `Adding client credentials grant type to client \"${clientId}\"...`\n );\n } else {\n printMessage(\n `Client \"${clientId}\" already has client credentials grant type.`\n );\n }\n return modClient;\n}\n\nasync function addAdminStaticUserMapping(name) {\n let authentication = {};\n try {\n authentication = (await getConfigEntity('authentication')).data;\n } catch (error) {\n printMessage(\n `Error reading IDM authentication configuration: ${error.message}`,\n 'error'\n );\n }\n let needsAdminMapping = true;\n let addRoles = [];\n const mappings = authentication['rsFilter']['staticUserMapping'].map(\n (mapping) => {\n // ignore mappings for other subjects\n if (mapping.subject !== name) {\n return mapping;\n }\n needsAdminMapping = false;\n addRoles = adminRoles.filter((role) => {\n let add = false;\n if (!mapping.roles.includes(role)) {\n add = true;\n }\n return add;\n });\n const newMapping = mapping;\n newMapping.roles = newMapping.roles.concat(addRoles);\n return newMapping;\n }\n );\n if (needsAdminMapping) {\n printMessage(`Creating static user mapping for client \"${name}\"...`);\n mappings.push({\n subject: name,\n localUser: 'internal/user/openidm-admin',\n userRoles: 'authzRoles/*',\n roles: adminRoles,\n });\n }\n authentication['rsFilter']['staticUserMapping'] = mappings;\n if (addRoles.length > 0 || needsAdminMapping) {\n printMessage(\n `Adding admin roles to static user mapping for client \"${name}\"...`\n );\n try {\n await putConfigEntity('authentication', authentication);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n } else {\n printMessage(\n `Static user mapping for client \"${name}\" already has admin roles.`\n );\n }\n}\n\n/*\n * Add AutoId static user mapping to authentication.json to enable dashboards and other AutoId-based functionality.\n {\n \"_id\": \"authentication\",\n \"rsFilter\": {\n ...\n \"staticUserMapping\": [\n ...\n {\n \"subject\": \"autoid-resource-server\",\n \"localUser\": \"internal/user/idm-provisioning\",\n \"roles\": [\n \"internal/role/platform-provisioning\",\n \"internal/role/openidm-authorized\",\n \"internal/role/openidm-admin\"\n ]\n }\n ]\n }\n }\n */\nexport async function addAutoIdStaticUserMapping() {\n const name = 'autoid-resource-server';\n const authentication = (await getConfigEntity('authentication')).data;\n let needsAdminMapping = true;\n let addRoles = [];\n const mappings = authentication.rsFilter.staticUserMapping.map((mapping) => {\n // ignore mappings for other subjects\n if (mapping.subject !== name) {\n return mapping;\n }\n needsAdminMapping = false;\n addRoles = autoIdRoles.filter((role) => {\n let add = false;\n if (!mapping.roles.includes(role)) {\n add = true;\n }\n return add;\n });\n const newMapping = mapping;\n newMapping.roles = newMapping.roles.concat(addRoles);\n return newMapping;\n });\n if (needsAdminMapping) {\n printMessage(`Creating static user mapping for AutoId client \"${name}\"...`);\n mappings.push({\n subject: name,\n localUser: 'internal/user/idm-provisioning',\n userRoles: 'authzRoles/*',\n roles: autoIdRoles,\n });\n }\n authentication.rsFilter.staticUserMapping = mappings;\n if (addRoles.length > 0 || needsAdminMapping) {\n printMessage(\n `Adding required roles to static user mapping for AutoId client \"${name}\"...`\n );\n try {\n await putConfigEntity('authentication', authentication);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n } else {\n printMessage(\n `Static user mapping for AutoId client \"${name}\" already has all required roles.`\n );\n }\n}\n\nexport async function grantOAuth2ClientAdminPrivileges(clientId) {\n let client = (await getOAuth2Client(clientId)).data;\n if (client.coreOAuth2ClientConfig.clientName.value.length === 0) {\n client.coreOAuth2ClientConfig.clientName.value = [clientId];\n }\n if (\n client.advancedOAuth2ClientConfig.descriptions.value.length === 0 ||\n client.advancedOAuth2ClientConfig.descriptions.value[0].startsWith(\n 'Modified by Frodo'\n ) ||\n client.advancedOAuth2ClientConfig.descriptions.value[0].startsWith(\n 'Created by Frodo'\n )\n ) {\n client.advancedOAuth2ClientConfig.descriptions.value = [\n `Modified by Frodo on ${new Date().toLocaleString()}`,\n ];\n }\n client = await addAdminScopes(clientId, client);\n client = addClientCredentialsGrantType(clientId, client);\n await putOAuth2Client(clientId, client);\n await addAdminStaticUserMapping(clientId);\n}\n\nasync function removeAdminScopes(name, client) {\n const modClient = client;\n const allAdminScopes = adminScopes.concat([\n await getDynamicClientRegistrationScope(),\n ]);\n let finalScopes = [];\n if (\n modClient.coreOAuth2ClientConfig.scopes &&\n modClient.coreOAuth2ClientConfig.scopes.value\n ) {\n finalScopes = modClient.coreOAuth2ClientConfig.scopes.value.filter(\n (scope) => !allAdminScopes.includes(scope)\n );\n }\n if (\n modClient.coreOAuth2ClientConfig.scopes.value.length > finalScopes.length\n ) {\n printMessage(`Removing admin scopes from client \"${name}\"...`);\n modClient.coreOAuth2ClientConfig.scopes.value = finalScopes;\n } else {\n printMessage(`Client \"${name}\" has no admin scopes.`);\n }\n let finalDefaultScopes = [];\n if (\n modClient.coreOAuth2ClientConfig.defaultScopes &&\n modClient.coreOAuth2ClientConfig.defaultScopes.value\n ) {\n finalDefaultScopes =\n modClient.coreOAuth2ClientConfig.defaultScopes.value.filter(\n (scope) => !adminDefaultScopes.includes(scope)\n );\n }\n if (\n modClient.coreOAuth2ClientConfig.defaultScopes.value.length >\n finalDefaultScopes.length\n ) {\n printMessage(`Removing admin default scopes from client \"${name}\"...`);\n modClient.coreOAuth2ClientConfig.defaultScopes.value = finalDefaultScopes;\n } else {\n printMessage(`Client \"${name}\" has no admin default scopes.`);\n }\n return modClient;\n}\n\nfunction removeClientCredentialsGrantType(clientId, client) {\n const modClient = client;\n let modified = false;\n let finalGrantTypes = [];\n if (\n modClient.advancedOAuth2ClientConfig.grantTypes &&\n modClient.advancedOAuth2ClientConfig.grantTypes.value\n ) {\n finalGrantTypes =\n modClient.advancedOAuth2ClientConfig.grantTypes.value.filter(\n (grantType) => grantType !== 'client_credentials'\n );\n modified =\n modClient.advancedOAuth2ClientConfig.grantTypes.value.length >\n finalGrantTypes.length;\n }\n if (modified) {\n printMessage(\n `Removing client credentials grant type from client \"${clientId}\"...`\n );\n modClient.advancedOAuth2ClientConfig.grantTypes.value = finalGrantTypes;\n } else {\n printMessage(\n `Client \"${clientId}\" does not allow client credentials grant type.`\n );\n }\n return modClient;\n}\n\nasync function removeAdminStaticUserMapping(name) {\n const authentication = (await getConfigEntity('authentication')).data;\n let finalRoles = [];\n let removeMapping = false;\n let modified = false;\n const mappings = authentication.rsFilter.staticUserMapping\n .map((mapping) => {\n // ignore mappings for other subjects\n if (mapping.subject !== name) {\n return mapping;\n }\n finalRoles = mapping.roles.filter((role) => !adminRoles.includes(role));\n const newMapping = mapping;\n removeMapping = finalRoles.length === 0; // if there are no more roles left on this mapping, flag it for removal\n modified = mapping.roles.length > finalRoles.length; // if there were roles removed, set modified flag\n newMapping.roles = finalRoles;\n return newMapping;\n })\n .filter((mapping) => mapping.subject !== name || !removeMapping);\n authentication.rsFilter.staticUserMapping = mappings;\n if (modified || removeMapping) {\n if (removeMapping) {\n printMessage(`Removing static user mapping for client \"${name}\"...`);\n } else {\n printMessage(\n `Removing admin roles from static user mapping for client \"${name}\"...`\n );\n }\n try {\n await putConfigEntity('authentication', authentication);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n } else {\n printMessage(\n `Static user mapping for client \"${name}\" has no admin roles.`\n );\n }\n}\n\nexport async function revokeOAuth2ClientAdminPrivileges(clientId) {\n let client = (await getOAuth2Client(clientId)).data;\n if (client.coreOAuth2ClientConfig.clientName.value.length === 0) {\n client.coreOAuth2ClientConfig.clientName.value = [clientId];\n }\n if (\n client.advancedOAuth2ClientConfig.descriptions.value.length === 0 ||\n client.advancedOAuth2ClientConfig.descriptions.value[0].startsWith(\n 'Modified by Frodo'\n ) ||\n client.advancedOAuth2ClientConfig.descriptions.value[0].startsWith(\n 'Created by Frodo'\n )\n ) {\n client.advancedOAuth2ClientConfig.descriptions.value = [\n `Modified by Frodo on ${new Date().toLocaleString()}`,\n ];\n }\n client = await removeAdminScopes(clientId, client);\n client = removeClientCredentialsGrantType(clientId, client);\n await putOAuth2Client(clientId, client);\n await removeAdminStaticUserMapping(clientId);\n}\n\nexport async function createOAuth2ClientWithAdminPrivileges(\n clientId,\n clientSecret\n) {\n let client = OAUTH2_CLIENT;\n client.coreOAuth2ClientConfig.userpassword = clientSecret;\n client.coreOAuth2ClientConfig.clientName.value = [clientId];\n client.advancedOAuth2ClientConfig.descriptions.value = [\n `Created by Frodo on ${new Date().toLocaleString()}`,\n ];\n try {\n client = await addAdminScopes(clientId, client);\n await putOAuth2Client(clientId, client);\n await addAdminStaticUserMapping(clientId);\n } catch (error) {\n printMessage(`Error creating oauth2 client: ${error.message}`, 'error');\n }\n}\n\nexport async function createLongLivedToken(\n clientId,\n clientSecret,\n scope,\n secret,\n lifetime\n) {\n // get oauth2 client\n const client = (await getOAuth2Client(clientId)).data;\n client.userpassword = clientSecret;\n // remember current lifetime\n const rememberedLifetime =\n client.coreOAuth2ClientConfig.accessTokenLifetime.value || 3600;\n // set long token lifetime\n client.coreOAuth2ClientConfig.accessTokenLifetime.value = lifetime;\n await putOAuth2Client(clientId, client);\n const response = (await clientCredentialsGrant(clientId, clientSecret, scope))\n .data;\n const expires = new Date().getTime() + 1000 * response.expires_in;\n response.expires_on = new Date(expires).toLocaleString();\n // reset token lifetime\n client.coreOAuth2ClientConfig.accessTokenLifetime.value = rememberedLifetime;\n await putOAuth2Client(clientId, client);\n // create secret with token as value\n const description = 'Long-lived admin token';\n try {\n await putSecret(secret, response.access_token, description);\n response.secret = secret;\n } catch (error) {\n if (\n _.get(error, 'response.data.code') === 400 &&\n _.get(error, 'response.data.message') ===\n 'Failed to create secret, the secret already exists'\n ) {\n const newSecret = `${secret}-${expires}`;\n printMessage(\n `esv '${secret}' already exists, using ${newSecret}`,\n 'warn'\n );\n await putSecret(newSecret, response.access_token, description);\n response.secret = newSecret;\n }\n }\n delete response.access_token;\n return response;\n}\n\nexport async function removeStaticUserMapping(subject) {\n const authentication = (await getConfigEntity('authentication')).data;\n let removeMapping = false;\n const mappings = authentication.rsFilter.staticUserMapping.filter(\n (mapping) => {\n // find the subject and flag it\n if (mapping.subject === subject) {\n removeMapping = true;\n }\n // ignore mappings for other subjects\n return mapping.subject !== subject;\n }\n );\n authentication.rsFilter.staticUserMapping = mappings;\n if (removeMapping) {\n printMessage(`Removing static user mapping for subject \"${subject}\"...`);\n try {\n await putConfigEntity('authentication', authentication);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n } else {\n printMessage(`No static user mapping for subject \"${subject}\" found.`);\n }\n}\n\nexport async function hideGenericExtensionAttributes(\n includeCustomized,\n dryRun\n) {\n const managed = (await getConfigEntity('managed')).data;\n const propertyNames = Object.keys(GENERIC_EXTENSION_ATTRIBUTES);\n const updatedObjects = managed.objects.map((object) => {\n // ignore all other objects\n if (object.name !== getRealmManagedUser()) {\n return object;\n }\n propertyNames.forEach((name) => {\n if (\n isEqualJson(\n GENERIC_EXTENSION_ATTRIBUTES[name],\n object.schema.properties[name],\n ['viewable', 'usageDescription']\n ) ||\n includeCustomized\n ) {\n if (object.schema.properties[name].viewable) {\n printMessage(`${name}: hide`);\n // eslint-disable-next-line no-param-reassign\n object.schema.properties[name].viewable = false;\n } else {\n printMessage(`${name}: ignore (already hidden)`);\n }\n } else {\n printMessage(`${name}: skip (customized)`);\n }\n });\n return object;\n });\n managed.objects = updatedObjects;\n if (dryRun) {\n printMessage('Dry-run only. Changes are not saved.');\n } else {\n try {\n await putConfigEntity('managed', managed);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n }\n}\n\nexport async function showGenericExtensionAttributes(\n includeCustomized,\n dryRun\n) {\n const managed = (await getConfigEntity('managed')).data;\n const propertyNames = Object.keys(GENERIC_EXTENSION_ATTRIBUTES);\n const updatedObjects = managed.objects.map((object) => {\n // ignore all other objects\n if (object.name !== getRealmManagedUser()) {\n return object;\n }\n propertyNames.forEach((name) => {\n if (\n isEqualJson(\n GENERIC_EXTENSION_ATTRIBUTES[name],\n object.schema.properties[name],\n ['viewable', 'usageDescription']\n ) ||\n includeCustomized\n ) {\n if (!object.schema.properties[name].viewable) {\n printMessage(`${name}: show`);\n // eslint-disable-next-line no-param-reassign\n object.schema.properties[name].viewable = true;\n } else {\n printMessage(`${name}: ignore (already showing)`);\n }\n } else {\n printMessage(`${name}: skip (customized)`);\n }\n });\n return object;\n });\n managed.objects = updatedObjects;\n if (dryRun) {\n printMessage('Dry-run only. Changes are not saved.');\n } else {\n try {\n await putConfigEntity('managed', managed);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n }\n}\n\nasync function repairOrgModelUser(dryRun) {\n const managed = (await getConfigEntity('managed')).data;\n const RDVPs = ['memberOfOrgIDs'];\n let repairData = false;\n const updatedObjects = managed.objects.map((object) => {\n // ignore all other objects\n if (object.name !== getRealmManagedUser()) {\n return object;\n }\n printMessage(`${object.name}: checking...`);\n RDVPs.forEach((name) => {\n if (!object.schema.properties[name].queryConfig.flattenProperties) {\n printMessage(`- ${name}: repairing - needs flattening`, 'warn');\n // eslint-disable-next-line no-param-reassign\n object.schema.properties[name].queryConfig.flattenProperties = true;\n repairData = true;\n } else {\n printMessage(`- ${name}: OK`);\n }\n });\n return object;\n });\n managed.objects = updatedObjects;\n if (!dryRun) {\n try {\n await putConfigEntity('managed', managed);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n }\n return repairData;\n}\n\nasync function repairOrgModelOrg(dryRun) {\n const managed = (await getConfigEntity('managed')).data;\n const RDVPs = [\n 'adminIDs',\n 'ownerIDs',\n 'parentAdminIDs',\n 'parentOwnerIDs',\n 'parentIDs',\n ];\n let repairData = false;\n const updatedObjects = managed.objects.map((object) => {\n // ignore all other objects\n if (object.name !== getRealmManagedOrganization()) {\n return object;\n }\n printMessage(`${object.name}: checking...`);\n RDVPs.forEach((name) => {\n if (!object.schema.properties[name].queryConfig.flattenProperties) {\n printMessage(`- ${name}: repairing - needs flattening`, 'warn');\n // eslint-disable-next-line no-param-reassign\n object.schema.properties[name].queryConfig.flattenProperties = true;\n repairData = true;\n } else {\n printMessage(`- ${name}: OK`);\n }\n });\n return object;\n });\n managed.objects = updatedObjects;\n if (!dryRun) {\n try {\n await putConfigEntity('managed', managed);\n } catch (putConfigEntityError) {\n printMessage(putConfigEntityError, 'error');\n printMessage(`Error: ${putConfigEntityError}`, 'error');\n }\n }\n return repairData;\n}\n\n// async function repairOrgModelData(dryRun = false) {\n// if (!dryRun) {\n// // const rootOrgs = await findRootOrganizations();\n// }\n// }\n\n// async function extendOrgModelPermissins(dryRun = false) {\n// if (!dryRun) {\n// // const rootOrgs = await findRootOrganizations();\n// }\n// }\n\n// export async function repairOrgModel(\n// excludeCustomized,\n// extendPermissions,\n// dryRun\n// ) {\n// let repairData = false;\n// repairData = repairData || (await repairOrgModelUser(dryRun));\n// repairData = repairData || (await repairOrgModelOrg(dryRun));\n// if (repairData) {\n// await repairOrgModelData(dryRun);\n// }\n// if (extendPermissions) {\n// await extendOrgModelPermissins(dryRun);\n// }\n// if (dryRun) {\n// printMessage('Dry-run only. Changes are not saved.', 'warn');\n// }\n// }\n\n// suggested by John K.\n// export async function removeRealmNameFromManagedObjectLabels() {}\n// export async function addRealmNameToManagedObjectLabels() {}\n// export async function cleanUpPostmanArtifacts() {}\n// export async function createSampleThemes() {}\n"]}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Get tokens
3
+ * @param {boolean} save true to save a connection profile upon successful authentication, false otherwise
4
+ * @returns {boolean} true if tokens were successfully obtained, false otherwise
5
+ */
6
+ export declare function getTokens(save?: boolean): Promise<boolean>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/AuthenticateOps.ts"],"names":[],"mappings":"AAySA;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,UAAQ,oBAoC3C","file":"AuthenticateOps.d.ts","sourcesContent":["import url from 'url';\nimport { createHash, randomBytes } from 'crypto';\nimport readlineSync from 'readline-sync';\nimport { encodeBase64Url } from '../api/utils/Base64';\nimport storage from '../storage/SessionStorage';\nimport * as global from '../storage/StaticStorage';\nimport { printMessage } from './utils/Console';\nimport { getServerInfo, getServerVersionInfo } from '../api/ServerInfoApi';\nimport { step } from '../api/AuthenticateApi';\nimport { accessToken, authorize } from '../api/OAuth2OIDCApi';\nimport {\n getConnectionProfile,\n saveConnectionProfile,\n} from './ConnectionProfileOps';\n\nconst adminClientPassword = 'doesnotmatter';\nconst redirectUrlTemplate = '/platform/appAuthHelperRedirect.html';\n\nconst idmAdminScope = 'fr:idm:* openid';\n\nlet adminClientId = 'idmAdminClient';\n\n/**\n * Helper function to get cookie name\n * @returns {String} cookie name\n */\nasync function getCookieName() {\n try {\n return (await getServerInfo()).data.cookieName;\n } catch (error) {\n printMessage(`Error getting cookie name: ${error}`, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to determine if this is a setup mfa prompt in the ID Cloud tenant admin login journey\n * @param {Object} payload response from the previous authentication journey step\n * @returns {Object} an object indicating if 2fa is required and the original payload\n */\nfunction checkAndHandle2FA(payload) {\n // let skippable = false;\n if ('callbacks' in payload) {\n for (const element of payload.callbacks) {\n if (element.type === 'HiddenValueCallback') {\n if (element.input[0].value.includes('skip')) {\n // skippable = true;\n element.input[0].value = 'Skip';\n return {\n need2fa: true,\n payload,\n };\n }\n }\n if (element.type === 'NameCallback') {\n if (element.output[0].value.includes('code')) {\n // skippable = false;\n printMessage('2FA is enabled and required for this user...');\n const code = readlineSync.question(`${element.output[0].value}: `);\n element.input[0].value = code;\n return {\n need2fa: true,\n payload,\n };\n }\n }\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n }\n // console.info(\"NO2FA\");\n return {\n need2fa: false,\n payload,\n };\n}\n\n/**\n * Helper function to set the default realm by deployment type\n * @param {String} deploymentType deployment type\n */\nfunction determineDefaultRealm(deploymentType) {\n if (storage.session.getRealm() === global.DEFAULT_REALM_KEY) {\n storage.session.setRealm(global.DEPLOYMENT_TYPE_REALM_MAP[deploymentType]);\n }\n}\n\n/**\n * Helper function to determine the deployment type\n * @returns {String} deployment type\n */\nasync function determineDeploymentType() {\n const fidcClientId = 'idmAdminClient';\n const forgeopsClientId = 'idm-admin-ui';\n\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n\n const config = {\n maxRedirects: 0,\n };\n let bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${fidcClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n\n let deploymentType = global.CLASSIC_DEPLOYMENT_TYPE_KEY;\n try {\n await authorize(bodyFormData, config);\n } catch (e) {\n if (e.response && e.response.status === 302) {\n printMessage('ForgeRock Identity Cloud ', 'info', false);\n deploymentType = global.CLOUD_DEPLOYMENT_TYPE_KEY;\n } else {\n try {\n bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${forgeopsClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${challenge}&code_challenge_method=${challengeMethod}`;\n await authorize(bodyFormData, config);\n } catch (ex) {\n if (ex.response.status === 302) {\n adminClientId = forgeopsClientId;\n printMessage('ForgeOps deployment ', 'info', false);\n deploymentType = global.FORGEOPS_DEPLOYMENT_TYPE_KEY;\n } else {\n printMessage('Classic deployment ', 'info', false);\n }\n }\n }\n printMessage('detected.');\n }\n determineDefaultRealm(deploymentType);\n return deploymentType;\n}\n\n/**\n * Helper function to extract the semantic version string from a version info object\n * @param {Object} versionInfo version info object\n * @returns {String} semantic version\n */\nasync function getSemanticVersion(versionInfo) {\n if ('version' in versionInfo) {\n const versionString = versionInfo.version;\n const rx = /([\\d]\\.[\\d]\\.[\\d](\\.[\\d])*)/g;\n const version = versionString.match(rx);\n return version[0];\n }\n throw new Error('Cannot extract semantic version from version info object.');\n}\n\n/**\n * Helper function to authenticate and obtain and store session cookie\n * @returns {String} empty string or null\n */\nasync function authenticate() {\n storage.session.setCookieName(await getCookieName());\n try {\n const config = {\n headers: {\n 'X-OpenAM-Username': storage.session.getUsername(),\n 'X-OpenAM-Password': storage.session.getPassword(),\n },\n };\n const response1 = (await step({}, config)).data;\n const skip2FA = checkAndHandle2FA(response1);\n let response2 = {};\n if (skip2FA.need2fa) {\n response2 = (await step(skip2FA.payload)).data;\n } else {\n response2 = skip2FA.payload;\n }\n if ('tokenId' in response2) {\n storage.session.setCookieValue(response2['tokenId']);\n if (!storage.session.getDeploymentType()) {\n storage.session.setDeploymentType(await determineDeploymentType());\n } else {\n determineDefaultRealm(storage.session.getDeploymentType());\n }\n const versionInfo = (await getServerVersionInfo()).data;\n printMessage(`Connected to ${versionInfo.fullVersion}`);\n const version = await getSemanticVersion(versionInfo);\n storage.session.setAmVersion(version);\n return '';\n }\n printMessage(`error authenticating`, 'error');\n printMessage('+++ likely cause, bad credentials!!! +++', 'error');\n return null;\n } catch (e) {\n if (e.response && e.response.status === 401) {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ likely cause, bad credentials +++', 'error');\n }\n if (e.message && e.message === 'self signed certificate') {\n printMessage(`error authenticating - ${e.message}`, 'error');\n printMessage('+++ use -k, --insecure option to allow +++', 'error');\n } else {\n printMessage(`error authenticating - ${e.message}`, 'error');\n }\n return null;\n }\n}\n\n/**\n * Helper function to obtain an oauth2 authorization code\n * @param {String} redirectURL oauth2 redirect uri\n * @param {String} codeChallenge PKCE code challenge\n * @param {String} codeChallengeMethod PKCE code challenge method\n * @returns {String} oauth2 authorization code or null\n */\nasync function getAuthCode(redirectURL, codeChallenge, codeChallengeMethod) {\n try {\n const bodyFormData = `redirect_uri=${redirectURL}&scope=${idmAdminScope}&response_type=code&client_id=${adminClientId}&csrf=${storage.session.getCookieValue()}&decision=allow&code_challenge=${codeChallenge}&code_challenge_method=${codeChallengeMethod}`;\n const config = {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n };\n const response = await authorize(bodyFormData, config);\n if (response.status < 200 || response.status > 399) {\n printMessage('error getting auth code', 'error');\n printMessage(\n 'likely cause: mismatched parameters with OAuth client config',\n 'error'\n );\n return null;\n }\n const redirectLocationURL = response.request.res.responseUrl;\n const queryObject = url.parse(redirectLocationURL, true).query;\n if ('code' in queryObject) {\n return queryObject.code;\n }\n printMessage('auth code not found', 'error');\n return null;\n } catch (error) {\n printMessage(`error getting auth code - ${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n return null;\n }\n}\n\n/**\n * Helper function to obtain oauth2 access token\n * @returns {String} empty string or null\n */\nasync function getAccessToken() {\n try {\n const verifier = encodeBase64Url(randomBytes(32));\n const challenge = encodeBase64Url(\n createHash('sha256').update(verifier).digest()\n );\n const challengeMethod = 'S256';\n const redirectURL = url.resolve(\n storage.session.getTenant(),\n redirectUrlTemplate\n );\n const authCode = await getAuthCode(redirectURL, challenge, challengeMethod);\n if (authCode == null) {\n printMessage('error getting auth code', 'error');\n return null;\n }\n let response = null;\n if (\n storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY\n ) {\n const config = {\n auth: {\n username: adminClientId,\n password: adminClientPassword,\n },\n };\n const bodyFormData = `redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData, config);\n } else {\n const bodyFormData = `client_id=${adminClientId}&redirect_uri=${redirectURL}&grant_type=authorization_code&code=${authCode}&code_verifier=${verifier}`;\n response = await accessToken(bodyFormData);\n }\n if (response.status < 200 || response.status > 399) {\n printMessage(`access token call returned ${response.status}`, 'error');\n return null;\n }\n if ('access_token' in response.data) {\n storage.session.setBearerToken(response.data.access_token);\n return '';\n }\n printMessage(\"can't get access token\", 'error');\n return null;\n } catch (e) {\n printMessage('error getting access token - ', 'error');\n return null;\n }\n}\n\n/**\n * Get tokens\n * @param {boolean} save true to save a connection profile upon successful authentication, false otherwise\n * @returns {boolean} true if tokens were successfully obtained, false otherwise\n */\nexport async function getTokens(save = false) {\n let credsFromParameters = true;\n // if username/password on cli are empty, try to read from connections.json\n if (\n storage.session.getUsername() == null &&\n storage.session.getPassword() == null\n ) {\n credsFromParameters = false;\n const conn = await getConnectionProfile();\n if (conn) {\n storage.session.setTenant(conn.tenant);\n storage.session.setUsername(conn.username);\n storage.session.setPassword(conn.password);\n } else {\n return false;\n }\n }\n await authenticate();\n if (\n storage.session.getCookieValue() &&\n !storage.session.getBearerToken() &&\n (storage.session.getDeploymentType() === global.CLOUD_DEPLOYMENT_TYPE_KEY ||\n storage.session.getDeploymentType() ===\n global.FORGEOPS_DEPLOYMENT_TYPE_KEY)\n ) {\n await getAccessToken();\n }\n if (save && storage.session.getCookieValue() && credsFromParameters) {\n // valid cookie, which means valid username/password combo. Save it in connections.json\n saveConnectionProfile();\n return true;\n }\n if (!storage.session.getCookieValue()) {\n return false;\n }\n return true;\n}\n"]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * List entity providers
3
+ * @param {String} long Long list format with details
4
+ */
5
+ export declare function listCirclesOfTrust(long?: boolean): Promise<void>;
6
+ /**
7
+ * Export a single circle of trust to file
8
+ * @param {String} cotId circle of trust id/name
9
+ * @param {String} file Optional filename
10
+ */
11
+ export declare function exportCircleOfTrust(cotId: any, file?: any): Promise<void>;
12
+ /**
13
+ * Export all circles of trust to one file
14
+ * @param {String} file Optional filename
15
+ */
16
+ export declare function exportCirclesOfTrustToFile(file?: any): Promise<void>;
17
+ /**
18
+ * Export all circles of trust to individual files
19
+ */
20
+ export declare function exportCirclesOfTrustToFiles(): Promise<void>;
21
+ /**
22
+ * Import a SAML circle of trust by id/name from file
23
+ * @param {String} cotId Circle of trust id/name
24
+ * @param {String} file Import file name
25
+ */
26
+ export declare function importCircleOfTrust(cotId: any, file: any): Promise<void>;
27
+ /**
28
+ * Import first SAML circle of trust from file
29
+ * @param {String} file Import file name
30
+ */
31
+ export declare function importFirstCircleOfTrust(file: any): Promise<void>;
32
+ /**
33
+ * Import all SAML circles of trust from file
34
+ * @param {String} file Import file name
35
+ */
36
+ export declare function importCirclesOfTrustFromFile(file: any): Promise<void>;
37
+ /**
38
+ * Import all SAML circles of trust from all *.cot.saml.json files in the current directory
39
+ */
40
+ export declare function importCirclesOfTrustFromFiles(): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/CirclesOfTrustOps.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,UAAQ,iBAqCpD;AAYD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,KAAA,EAAE,IAAI,MAAO,iBAuB3D;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,MAAO,iBAiC3D;AAED;;GAEG;AACH,wBAAsB,2BAA2B,kBAyBhD;AAYD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,KAAA,EAAE,IAAI,KAAA,iBA4BpD;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,KAAA,iBA4BlD;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,KAAA,iBA6BtD;AAED;;GAEG;AACH,wBAAsB,6BAA6B,kBA6ClD","file":"CirclesOfTrustOps.d.ts","sourcesContent":["import fs from 'fs';\nimport _ from 'lodash';\nimport {\n createTable,\n printMessage,\n createProgressIndicator,\n updateProgressIndicator,\n stopProgressIndicator,\n} from './utils/Console';\nimport {\n getCirclesOfTrust,\n getCircleOfTrust,\n createCircleOfTrust,\n} from '../api/CirclesOfTrustApi';\nimport {\n getRealmString,\n getTypedFilename,\n saveJsonToFile,\n validateImport,\n} from './utils/ExportImportUtils';\n\n// use a function vs a template variable to avoid problems in loops\nfunction getFileDataTemplate() {\n return {\n meta: {},\n script: {},\n saml: {\n hosted: {},\n remote: {},\n metadata: {},\n cot: {},\n },\n };\n}\n\n/**\n * List entity providers\n * @param {String} long Long list format with details\n */\nexport async function listCirclesOfTrust(long = false) {\n let cotList = [];\n try {\n const response = await getCirclesOfTrust();\n if (response.status < 200 || response.status > 399) {\n printMessage(response, 'data');\n printMessage(`getCirclesOfTrust: ${response.status}`, 'error');\n }\n cotList = response.data.result;\n } catch (error) {\n printMessage(`getCirclesOfTrust ERROR: ${error}`, 'error');\n printMessage(error, 'data');\n }\n cotList.sort((a, b) => a._id.localeCompare(b._id));\n if (!long) {\n cotList.forEach((cot) => {\n printMessage(`${cot._id}`, 'data');\n });\n } else {\n const table = createTable([\n 'Name'['brightCyan'],\n 'Description'['brightCyan'],\n 'Status'['brightCyan'],\n 'Trusted Providers'['brightCyan'],\n ]);\n cotList.forEach((cot) => {\n table.push([\n cot._id,\n cot.description,\n cot.status,\n cot.trustedProviders\n .map((provider) => provider.split('|')[0])\n .join('\\n'),\n ]);\n });\n printMessage(table.toString());\n }\n}\n\n/**\n * Include dependencies in the export file\n * @param {Object} cotData Object representing a SAML circle of trust\n * @param {Object} fileData File data object to add dependencies to\n */\nasync function exportDependencies(cotData, fileData) {\n // TODO: Export dependencies\n return [cotData, fileData];\n}\n\n/**\n * Export a single circle of trust to file\n * @param {String} cotId circle of trust id/name\n * @param {String} file Optional filename\n */\nexport async function exportCircleOfTrust(cotId, file = null) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(cotId, 'cot.saml');\n }\n createProgressIndicator(1, `Exporting circle of trust ${cotId}`);\n getCircleOfTrust(cotId)\n .then(async (response) => {\n const cotData = _.cloneDeep(response.data);\n delete cotData._rev;\n updateProgressIndicator(`Exporting ${cotId}`);\n const fileData = getFileDataTemplate();\n fileData.saml.cot[cotId] = cotData;\n await exportDependencies(cotData, fileData);\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `Exported ${cotId.brightCyan} to ${fileName.brightCyan}.`\n );\n })\n .catch((err) => {\n stopProgressIndicator(`${err}`);\n printMessage(err, 'error');\n });\n}\n\n/**\n * Export all circles of trust to one file\n * @param {String} file Optional filename\n */\nexport async function exportCirclesOfTrustToFile(file = null) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(\n `all${getRealmString()}CirclesOfTrust`,\n 'cot.saml'\n );\n }\n const fileData = getFileDataTemplate();\n let allCotData = [];\n try {\n const response = await getCirclesOfTrust();\n if (response.status < 200 || response.status > 399) {\n printMessage(response, 'data');\n printMessage(`getCirclesOfTrust: ${response.status}`, 'error');\n }\n allCotData = _.cloneDeep(response.data.result);\n createProgressIndicator(allCotData.length, 'Exporting circles of trust');\n for (const cotData of allCotData) {\n delete cotData._rev;\n updateProgressIndicator(`Exporting circle of trust ${cotData._id}`);\n // eslint-disable-next-line no-await-in-loop\n await exportDependencies(cotData, fileData);\n fileData.saml.cot[cotData._id] = cotData;\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `${allCotData.length} circle(s) of trust exported to ${fileName}.`\n );\n } catch (error) {\n printMessage(`getCirclesOfTrust ERROR: ${error}`, 'error');\n printMessage(error, 'data');\n }\n}\n\n/**\n * Export all circles of trust to individual files\n */\nexport async function exportCirclesOfTrustToFiles() {\n let allCotData = [];\n try {\n const response = await getCirclesOfTrust();\n if (response.status < 200 || response.status > 399) {\n printMessage(response, 'data');\n printMessage(`getCirclesOfTrust: ${response.status}`, 'error');\n }\n allCotData = _.cloneDeep(response.data.result);\n createProgressIndicator(allCotData.length, 'Exporting circles of trust');\n for (const cotData of allCotData) {\n delete cotData._rev;\n updateProgressIndicator(`Exporting circle of trust ${cotData._id}`);\n const fileName = getTypedFilename(cotData._id, 'cot.saml');\n const fileData = getFileDataTemplate();\n // eslint-disable-next-line no-await-in-loop\n await exportDependencies(cotData, fileData);\n fileData.saml.cot[cotData._id] = cotData;\n saveJsonToFile(fileData, fileName);\n }\n stopProgressIndicator(`${allCotData.length} providers exported.`);\n } catch (error) {\n printMessage(`getCirclesOfTrust ERROR: ${error}`, 'error');\n printMessage(error, 'data');\n }\n}\n\n/**\n * Include dependencies from the import file\n * @param {Object} cotData Object representing a SAML circle of trust\n * @param {Object} fileData File data object to read dependencies from\n */\nasync function importDependencies(cotData, fileData) {\n // TODO: Import dependencies\n return [cotData, fileData];\n}\n\n/**\n * Import a SAML circle of trust by id/name from file\n * @param {String} cotId Circle of trust id/name\n * @param {String} file Import file name\n */\nexport async function importCircleOfTrust(cotId, file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, 'Importing circle of trust...');\n const cotData = _.get(fileData, ['saml', 'cot', cotId]);\n if (cotData) {\n updateProgressIndicator(`Importing ${cotId}`);\n await importDependencies(cotData, fileData);\n createCircleOfTrust(cotData)\n .then(() => {\n stopProgressIndicator(`Successfully imported ${cotId}.`);\n })\n .catch((createProviderErr) => {\n stopProgressIndicator(`Error importing ${cotId}.`);\n printMessage(`Error importing ${cotId}`, 'error');\n printMessage(createProviderErr.response.data, 'error');\n });\n } else {\n stopProgressIndicator(\n `Circle of trust ${cotId.brightCyan} not found in ${file.brightCyan}!`\n );\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import first SAML circle of trust from file\n * @param {String} file Import file name\n */\nexport async function importFirstCircleOfTrust(file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, 'Importing circle of trust...');\n for (const cotId in fileData.saml.cot) {\n if ({}.hasOwnProperty.call(fileData.saml.cot, cotId)) {\n const cotData = _.cloneDeep(fileData.saml.cot[cotId]);\n updateProgressIndicator(`Importing ${cotId}`);\n // eslint-disable-next-line no-await-in-loop\n await importDependencies(cotData, fileData);\n createCircleOfTrust(cotData)\n .then(() => {\n stopProgressIndicator(`Successfully imported ${cotId}.`);\n })\n .catch((createCircleOfTrustErr) => {\n stopProgressIndicator(`Error importing ${cotId}.`);\n printMessage(`Error importing ${cotId}`, 'error');\n printMessage(createCircleOfTrustErr.response.data, 'error');\n });\n break;\n }\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import all SAML circles of trust from file\n * @param {String} file Import file name\n */\nexport async function importCirclesOfTrustFromFile(file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(\n Object.keys(fileData.saml.cot).length,\n 'Importing circles of trust...'\n );\n for (const cotId in fileData.saml.cot) {\n if ({}.hasOwnProperty.call(fileData.saml.cot, cotId)) {\n const cotData = _.cloneDeep(fileData.saml.cot[cotId]);\n // eslint-disable-next-line no-await-in-loop\n await importDependencies(cotData, fileData);\n try {\n // eslint-disable-next-line no-await-in-loop\n await createCircleOfTrust(cotData);\n updateProgressIndicator(`Imported ${cotId}`);\n } catch (createCircleOfTrustErr) {\n printMessage(`\\nError importing ${cotId}`, 'error');\n printMessage(createCircleOfTrustErr.response.data, 'error');\n }\n }\n }\n stopProgressIndicator(`Circles of trust imported.`);\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import all SAML circles of trust from all *.cot.saml.json files in the current directory\n */\nexport async function importCirclesOfTrustFromFiles() {\n const names = fs.readdirSync('.');\n const jsonFiles = names.filter((name) =>\n name.toLowerCase().endsWith('.cot.saml.json')\n );\n createProgressIndicator(jsonFiles.length, 'Importing circles or trust...');\n let total = 0;\n let totalErrors = 0;\n for (const file of jsonFiles) {\n const data = fs.readFileSync(file, 'utf8');\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n total += _.keys(fileData.saml.cot).length;\n let errors = 0;\n for (const cotId in fileData.saml.cot) {\n if ({}.hasOwnProperty.call(fileData.saml.cot, cotId)) {\n const cotData = _.cloneDeep(fileData.saml.cot[cotId]);\n // eslint-disable-next-line no-await-in-loop\n await importDependencies(cotData, fileData);\n try {\n // eslint-disable-next-line no-await-in-loop\n await createCircleOfTrust(cotData);\n // updateProgressIndicator(`Imported ${cotId}`);\n } catch (createCircleOfTrustErr) {\n errors += 1;\n printMessage(`\\nError importing ${cotId}`, 'error');\n printMessage(createCircleOfTrustErr.response.data, 'error');\n }\n }\n }\n totalErrors += errors;\n updateProgressIndicator(\n `Imported ${\n _.keys(fileData.saml.cot).length - errors\n } circle(s) of trust from ${file}`\n );\n } else {\n printMessage(`Validation of ${file} failed!`, 'error');\n }\n }\n stopProgressIndicator(\n `Imported ${total - totalErrors} of ${total} circle(s) of trust from ${\n jsonFiles.length\n } file(s).`\n );\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Get connection profiles file name
3
+ * @returns {String} connection profiles file name
4
+ */
5
+ export declare function getConnectionProfilesPath(): string;
6
+ /**
7
+ * List connection profiles
8
+ * @param {boolean} long Long list format with details
9
+ */
10
+ export declare function listConnectionProfiles(long?: boolean): void;
11
+ /**
12
+ * Initialize connection profiles
13
+ */
14
+ export declare function initConnectionProfiles(): void;
15
+ /**
16
+ * Get connection profile by host
17
+ * @param {String} host host tenant host url or unique substring
18
+ * @returns {Object} connection profile or null
19
+ */
20
+ export declare function getConnectionProfileByHost(host: any): Promise<{
21
+ tenant: any;
22
+ username: any;
23
+ password: any;
24
+ key: any;
25
+ secret: any;
26
+ }>;
27
+ /**
28
+ * Get connection profile
29
+ * @returns {Object} connection profile or null
30
+ */
31
+ export declare function getConnectionProfile(): Promise<{
32
+ tenant: any;
33
+ username: any;
34
+ password: any;
35
+ key: any;
36
+ secret: any;
37
+ }>;
38
+ /**
39
+ * Save connection profile
40
+ */
41
+ export declare function saveConnectionProfile(): Promise<void>;
42
+ /**
43
+ * Delete connection profile
44
+ * @param {String} host host tenant host url or unique substring
45
+ */
46
+ export declare function deleteConnectionProfile(host: any): void;
47
+ export declare function describeConnectionProfile(host: any, showSecrets: any): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/ConnectionProfileOps.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAmBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,UAAQ,QA2BlD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,SAkCrC;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,KAAA;;;;;;GA4BpD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB;;;;;;GAEzC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,kBAkC1C;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,KAAA,QAwB3C;AAED,wBAAsB,yBAAyB,CAAC,IAAI,KAAA,EAAE,WAAW,KAAA,iBAuBhE","file":"ConnectionProfileOps.d.ts","sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport storage from '../storage/SessionStorage';\nimport DataProtection from './utils/DataProtection';\nimport { createObjectTable, createTable, printMessage } from './utils/Console';\nimport { FRODO_CONNECTION_PROFILES_PATH_KEY } from '../storage/StaticStorage';\n\nconst dataProtection = new DataProtection();\n\nconst fileOptions = {\n indentation: 4,\n};\n\n/**\n * Get connection profiles file name\n * @returns {String} connection profiles file name\n */\nexport function getConnectionProfilesPath(): string {\n return (\n storage.session.getConnectionProfilesPath() ||\n process.env[FRODO_CONNECTION_PROFILES_PATH_KEY] ||\n `${os.homedir()}/.frodo/.frodorc`\n );\n}\n\n/**\n * Find connection profile\n * @param {Object} connectionProfiles connection profile object\n * @param {String} host tenant host url or unique substring\n * @returns {Object} connection profile object or null\n */\nfunction findConnectionProfile(connectionProfiles, host) {\n for (const tenant in connectionProfiles) {\n if (tenant.includes(host)) {\n const profile = connectionProfiles[tenant];\n profile.tenant = tenant;\n return profile;\n }\n }\n return null;\n}\n\n/**\n * List connection profiles\n * @param {boolean} long Long list format with details\n */\nexport function listConnectionProfiles(long = false) {\n const filename = getConnectionProfilesPath();\n try {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n if (long) {\n const table = createTable(['Host', 'Username', 'Log API Key']);\n Object.keys(connectionsData).forEach((c) => {\n table.push([\n c,\n connectionsData[c].username,\n connectionsData[c].logApiKey,\n ]);\n });\n printMessage(table.toString(), 'data');\n } else {\n Object.keys(connectionsData).forEach((c) => {\n printMessage(`${c}`, 'data');\n });\n }\n printMessage(\n 'Any unique substring of a saved host can be used as the value for host parameter in all commands',\n 'info'\n );\n } catch (e) {\n printMessage(`No connections found in ${filename} (${e.message})`, 'error');\n }\n}\n\n/**\n * Initialize connection profiles\n */\nexport function initConnectionProfiles() {\n // create connections.json file if it doesn't exist\n const filename = getConnectionProfilesPath();\n const folderName = path.dirname(filename);\n if (!fs.existsSync(folderName)) {\n fs.mkdirSync(folderName, { recursive: true });\n if (!fs.existsSync(filename)) {\n fs.writeFileSync(\n filename,\n JSON.stringify({}, null, fileOptions.indentation)\n );\n }\n }\n // encrypt the password from clear text to aes-256-GCM\n else {\n const data = fs.readFileSync(filename, 'utf8');\n const connectionsData = JSON.parse(data);\n let convert = false;\n Object.keys(connectionsData).forEach(async (conn) => {\n if (connectionsData[conn].password) {\n convert = true;\n connectionsData[conn].encodedPassword = await dataProtection.encrypt(\n connectionsData[conn].password\n ); // Buffer.from(connectionsData[conn].password).toString('base64');\n delete connectionsData[conn].password;\n }\n });\n if (convert) {\n fs.writeFileSync(\n filename,\n JSON.stringify(connectionsData, null, fileOptions.indentation)\n );\n }\n }\n}\n\n/**\n * Get connection profile by host\n * @param {String} host host tenant host url or unique substring\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfileByHost(host) {\n try {\n const filename = getConnectionProfilesPath();\n const connectionsData = JSON.parse(fs.readFileSync(filename, 'utf8'));\n const profile = findConnectionProfile(connectionsData, host);\n if (!profile) {\n printMessage(\n `Profile for ${host} not found. Please specify credentials on command line`,\n 'error'\n );\n return null;\n }\n return {\n tenant: profile.tenant,\n username: profile.username ? profile.username : null,\n password: profile.encodedPassword\n ? await dataProtection.decrypt(profile.encodedPassword)\n : null,\n key: profile.logApiKey ? profile.logApiKey : null,\n secret: profile.logApiSecret ? profile.logApiSecret : null,\n };\n } catch (e) {\n printMessage(\n `Can not read saved connection info, please specify credentials on command line: ${e}`,\n 'error'\n );\n return null;\n }\n}\n\n/**\n * Get connection profile\n * @returns {Object} connection profile or null\n */\nexport async function getConnectionProfile() {\n return getConnectionProfileByHost(storage.session.getTenant());\n}\n\n/**\n * Save connection profile\n */\nexport async function saveConnectionProfile() {\n const filename = getConnectionProfilesPath();\n printMessage(`Saving creds in ${filename}...`);\n let connectionsData = {};\n let existingData = {};\n try {\n fs.statSync(filename);\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n if (connectionsData[storage.session.getTenant()]) {\n existingData = connectionsData[storage.session.getTenant()];\n printMessage(\n `Updating connection profile ${storage.session.getTenant()}`\n );\n } else\n printMessage(`Adding connection profile ${storage.session.getTenant()}`);\n } catch (e) {\n printMessage(\n `Creating connection profiles file ${filename} with ${storage.session.getTenant()}`\n );\n }\n if (storage.session.getUsername())\n existingData['username'] = storage.session.getUsername();\n if (storage.session.getPassword())\n existingData['encodedPassword'] = await dataProtection.encrypt(\n storage.session.getPassword()\n ); // Buffer.from(storage.session.getPassword()).toString('base64');\n if (storage.session.getLogApiKey())\n existingData['logApiKey'] = storage.session.getLogApiKey();\n if (storage.session.getLogApiSecret())\n existingData['logApiSecret'] = storage.session.getLogApiSecret();\n connectionsData[storage.session.getTenant()] = existingData;\n\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n}\n\n/**\n * Delete connection profile\n * @param {String} host host tenant host url or unique substring\n */\nexport function deleteConnectionProfile(host) {\n const filename = getConnectionProfilesPath();\n let connectionsData = {};\n fs.stat(filename, (err) => {\n if (err == null) {\n const data = fs.readFileSync(filename, 'utf8');\n connectionsData = JSON.parse(data);\n const profile = findConnectionProfile(connectionsData, host);\n if (profile) {\n printMessage(`Deleting connection profile ${profile.tenant}`);\n delete connectionsData[profile.tenant];\n fs.writeFileSync(filename, JSON.stringify(connectionsData, null, 2));\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n } else if (err.code === 'ENOENT') {\n printMessage(`Connection profile file ${filename} not found`);\n } else {\n printMessage(\n `Error in deleting connection profile: ${err.code}`,\n 'error'\n );\n }\n });\n}\n\nexport async function describeConnectionProfile(host, showSecrets) {\n const profile = await getConnectionProfileByHost(host);\n if (profile) {\n if (!showSecrets) {\n delete profile.password;\n delete profile.secret;\n }\n if (!profile.key) {\n delete profile.key;\n delete profile.secret;\n }\n const keyMap = {\n tenant: 'Host',\n username: 'Username',\n password: 'Password',\n key: 'Log API Key',\n secret: 'Log API Secret',\n };\n const table = createObjectTable(profile, keyMap);\n printMessage(table.toString(), 'data');\n } else {\n printMessage(`No connection profile ${host} found`);\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * List email templates
3
+ * @param {boolean} long Long list format with details
4
+ */
5
+ export declare function listEmailTemplates(long?: boolean): Promise<void>;
6
+ /**
7
+ * Export a single email template to file
8
+ * @param {String} templateId email template id
9
+ * @param {String} file optional filename
10
+ */
11
+ export declare function exportEmailTemplateToFile(templateId: any, file: any): Promise<void>;
12
+ /**
13
+ * Export all email templates to file
14
+ * @param {String} file optional filename
15
+ */
16
+ export declare function exportEmailTemplatesToFile(file: any): Promise<void>;
17
+ /**
18
+ * Export all email templates to separate files
19
+ */
20
+ export declare function exportEmailTemplatesToFiles(): Promise<void>;
21
+ /**
22
+ * Import email template by id from file
23
+ * @param {String} templateId email template id
24
+ * @param {String} file optional filename
25
+ */
26
+ export declare function importEmailTemplateFromFile(templateId: any, file: any): Promise<void>;
27
+ /**
28
+ * Import all email templates from file
29
+ * @param {String} file optional filename
30
+ */
31
+ export declare function importEmailTemplatesFromFile(file: any): Promise<void>;
32
+ /**
33
+ * Import all email templates from separate files
34
+ */
35
+ export declare function importEmailTemplatesFromFiles(): Promise<void>;
36
+ /**
37
+ * Import first email template from file
38
+ * @param {String} file optional filename
39
+ */
40
+ export declare function importFirstEmailTemplateFromFile(file: any): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/EmailTemplateOps.ts"],"names":[],"mappings":"AAuCA;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,UAAQ,iBAmDpD;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,UAAU,KAAA,EAAE,IAAI,KAAA,iBAqB/D;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,KAAA,iBA2BpD;AAED;;GAEG;AACH,wBAAsB,2BAA2B,kBAsBhD;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,UAAU,KAAA,EAAE,IAAI,KAAA,iBA+BjE;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,KAAA,iBA8BtD;AAED;;GAEG;AACH,wBAAsB,6BAA6B,kBAyClD;AAED;;;GAGG;AACH,wBAAsB,gCAAgC,CAAC,IAAI,KAAA,iBA4B1D","file":"EmailTemplateOps.d.ts","sourcesContent":["import fs from 'fs';\nimport {\n EMAIL_TEMPLATE_TYPE,\n getEmailTemplate,\n getEmailTemplates,\n putEmailTemplate,\n} from '../api/EmailTemplateApi';\nimport {\n createProgressIndicator,\n createTable,\n printMessage,\n stopProgressIndicator,\n updateProgressIndicator,\n} from './utils/Console';\nimport {\n getTypedFilename,\n saveJsonToFile,\n validateImport,\n} from './utils/ExportImportUtils';\nimport wordwrap from './utils/Wordwrap';\n\n/**\n * Regex to remove email template type from id\n */\nconst regexEmailTemplateType = new RegExp(`${EMAIL_TEMPLATE_TYPE}/`, 'g');\n\n/**\n * Maintain the file type centrally\n */\nconst EMAIL_TEMPLATE_FILE_TYPE = 'template.email';\n\n// use a function vs a template variable to avoid problems in loops\nfunction getFileDataTemplate() {\n return {\n meta: {},\n emailTemplate: {},\n };\n}\n\n/**\n * List email templates\n * @param {boolean} long Long list format with details\n */\nexport async function listEmailTemplates(long = false) {\n let emailTemplates = [];\n try {\n emailTemplates = (await getEmailTemplates()).data.result;\n } catch (error) {\n printMessage(`${error.message}`, 'error');\n printMessage(error.response.data, 'error');\n }\n emailTemplates.sort((a, b) => a._id.localeCompare(b._id));\n if (!long) {\n for (const [, emailTemplate] of emailTemplates.entries()) {\n printMessage(\n `${emailTemplate._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '')}`,\n 'data'\n );\n }\n } else {\n const table = createTable([\n 'Id'['brightCyan'],\n 'Name'['brightCyan'],\n 'Status'['brightCyan'],\n 'Locale(s)'['brightCyan'],\n 'From'['brightCyan'],\n 'Subject'['brightCyan'],\n ]);\n emailTemplates.forEach((emailTemplate) => {\n table.push([\n // Id\n `${emailTemplate._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '')}`,\n // Name\n `${emailTemplate.displayName ? emailTemplate.displayName : ''}`,\n // Status\n emailTemplate.enabled === false\n ? 'disabled'['brightRed']\n : 'enabled'['brightGreen'],\n // Locale(s)\n `${emailTemplate.defaultLocale}${\n Object.keys(emailTemplate.subject).length > 1\n ? ` (${Object.keys(emailTemplate.subject)\n .filter((locale) => locale !== emailTemplate.defaultLocale)\n .join(',')})`\n : ''\n }`,\n // From\n `${emailTemplate.from ? emailTemplate.from : ''}`,\n // Subject\n wordwrap(emailTemplate.subject[emailTemplate.defaultLocale], 40),\n ]);\n });\n printMessage(table.toString(), 'data');\n }\n}\n\n/**\n * Export a single email template to file\n * @param {String} templateId email template id\n * @param {String} file optional filename\n */\nexport async function exportEmailTemplateToFile(templateId, file) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(templateId, EMAIL_TEMPLATE_FILE_TYPE);\n }\n createProgressIndicator(1, `Exporting ${templateId}`);\n getEmailTemplate(templateId)\n .then(async (response) => {\n const templateData = response.data;\n updateProgressIndicator(`Writing file ${fileName}`);\n const fileData = getFileDataTemplate();\n fileData.emailTemplate[templateId] = templateData;\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `Exported ${templateId.brightCyan} to ${fileName.brightCyan}.`\n );\n })\n .catch((err) => {\n stopProgressIndicator(`${err}`);\n printMessage(err, 'error');\n });\n}\n\n/**\n * Export all email templates to file\n * @param {String} file optional filename\n */\nexport async function exportEmailTemplatesToFile(file) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(`allEmailTemplates`, EMAIL_TEMPLATE_FILE_TYPE);\n }\n const fileData = getFileDataTemplate();\n getEmailTemplates()\n .then((response) => {\n const templates = response.data.result;\n createProgressIndicator(\n response.data.resultCount,\n 'Exporting email templates'\n );\n for (const template of templates) {\n const templateId = template._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '');\n updateProgressIndicator(`Exporting ${templateId}`);\n fileData.emailTemplate[templateId] = template;\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `${response.data.resultCount} templates exported to ${fileName}.`\n );\n })\n .catch((err) => {\n stopProgressIndicator(`${err}`);\n printMessage(err, 'error');\n });\n}\n\n/**\n * Export all email templates to separate files\n */\nexport async function exportEmailTemplatesToFiles() {\n getEmailTemplates()\n .then((response) => {\n const templates = response.data.result;\n createProgressIndicator(\n response.data.resultCount,\n 'Exporting email templates'\n );\n for (const template of templates) {\n const templateId = template._id.replace(`${EMAIL_TEMPLATE_TYPE}/`, '');\n const fileName = getTypedFilename(templateId, EMAIL_TEMPLATE_FILE_TYPE);\n const fileData = getFileDataTemplate();\n updateProgressIndicator(`Exporting ${templateId}`);\n fileData.emailTemplate[templateId] = template;\n saveJsonToFile(fileData, fileName);\n }\n stopProgressIndicator(`${response.data.resultCount} templates exported.`);\n })\n .catch((err) => {\n stopProgressIndicator(`${err}`);\n printMessage(err, 'error');\n });\n}\n\n/**\n * Import email template by id from file\n * @param {String} templateId email template id\n * @param {String} file optional filename\n */\nexport async function importEmailTemplateFromFile(templateId, file) {\n // eslint-disable-next-line no-param-reassign\n templateId = templateId.replaceAll(`${EMAIL_TEMPLATE_TYPE}/`, '');\n fs.readFile(file, 'utf8', (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, `Importing ${templateId}`);\n if (fileData.emailTemplate[templateId]) {\n putEmailTemplate(templateId, fileData.emailTemplate[templateId])\n .then(() => {\n updateProgressIndicator(`Importing ${templateId}`);\n stopProgressIndicator(`Imported ${templateId}`);\n })\n .catch((putEmailTemplateError) => {\n stopProgressIndicator(`${putEmailTemplateError}`);\n printMessage(putEmailTemplateError, 'error');\n });\n } else {\n stopProgressIndicator(\n `Email template ${templateId.brightCyan} not found in ${file.brightCyan}!`\n );\n printMessage(\n `Email template ${templateId.brightCyan} not found in ${file.brightCyan}!`,\n 'error'\n );\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import all email templates from file\n * @param {String} file optional filename\n */\nexport async function importEmailTemplatesFromFile(file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(\n Object.keys(fileData.emailTemplate).length,\n `Importing email templates`\n );\n for (const id in fileData.emailTemplate) {\n if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {\n const templateId = id.replace(regexEmailTemplateType, '');\n try {\n // eslint-disable-next-line no-await-in-loop\n await putEmailTemplate(\n templateId,\n fileData.emailTemplate[templateId]\n );\n updateProgressIndicator(`Imported ${templateId}`);\n } catch (putEmailTemplateError) {\n printMessage(`\\nError importing ${templateId}`, 'error');\n printMessage(putEmailTemplateError.response.data, 'error');\n }\n }\n }\n stopProgressIndicator(`Done.`);\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import all email templates from separate files\n */\nexport async function importEmailTemplatesFromFiles() {\n const names = fs.readdirSync('.');\n const jsonFiles = names.filter((name) =>\n name.toLowerCase().endsWith(`${EMAIL_TEMPLATE_FILE_TYPE}.json`)\n );\n createProgressIndicator(jsonFiles.length, 'Importing email templates...');\n let total = 0;\n let totalErrors = 0;\n for (const file of jsonFiles) {\n const data = fs.readFileSync(file, 'utf8');\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n total += Object.keys(fileData.emailTemplate).length;\n let errors = 0;\n for (const id in fileData.emailTemplate) {\n if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {\n const templateId = id.replace(regexEmailTemplateType, '');\n try {\n // eslint-disable-next-line no-await-in-loop\n await putEmailTemplate(\n templateId,\n fileData.emailTemplate[templateId]\n );\n } catch (putEmailTemplateError) {\n errors += 1;\n printMessage(`\\nError importing ${templateId}`, 'error');\n printMessage(putEmailTemplateError.response.data, 'error');\n }\n }\n }\n totalErrors += errors;\n updateProgressIndicator(`Imported ${file}`);\n } else {\n printMessage(`Validation of ${file} failed!`, 'error');\n }\n }\n stopProgressIndicator(\n `Imported ${total - totalErrors} of ${total} email template(s) from ${\n jsonFiles.length\n } file(s).`\n );\n}\n\n/**\n * Import first email template from file\n * @param {String} file optional filename\n */\nexport async function importFirstEmailTemplateFromFile(file) {\n fs.readFile(file, 'utf8', (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, `Importing first email template`);\n for (const id in fileData.emailTemplate) {\n if ({}.hasOwnProperty.call(fileData.emailTemplate, id)) {\n putEmailTemplate(\n id.replace(regexEmailTemplateType, ''),\n fileData.emailTemplate[id]\n )\n .then(() => {\n updateProgressIndicator(`Imported ${id}`);\n stopProgressIndicator(`Imported ${id}`);\n })\n .catch((putEmailTemplateError) => {\n stopProgressIndicator(`Error importing email template ${id}`);\n printMessage(`\\nError importing email template ${id}`, 'error');\n printMessage(putEmailTemplateError.response.data, 'error');\n });\n break;\n }\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * List all IDM configuration objects
3
+ */
4
+ export declare function listAllConfigEntities(): Promise<void>;
5
+ /**
6
+ * Export an IDM configuration object.
7
+ * @param {String} id the desired configuration object
8
+ * @param {String} file optional export file
9
+ */
10
+ export declare function exportConfigEntity(id: any, file: any): Promise<void>;
11
+ /**
12
+ * Export all IDM configuration objects into separate JSON files in a directory specified by <directory>
13
+ * @param {String} directory export directory
14
+ */
15
+ export declare function exportAllRawConfigEntities(directory: any): Promise<void>;
16
+ /**
17
+ * Export all IDM configuration objects
18
+ * @param {String} directory export directory
19
+ * @param {String} entitiesFile JSON file that specifies the config entities to export/import
20
+ * @param {String} envFile File that defines environment specific variables for replacement during configuration export/import
21
+ */
22
+ export declare function exportAllConfigEntities(directory: any, entitiesFile: any, envFile: any): Promise<void>;
23
+ /**
24
+ * Count number of managed objects of a given type
25
+ * @param {String} type managed object type, e.g. alpha_user
26
+ */
27
+ export declare function countManagedObjects(type: any): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/IdmOps.ts"],"names":[],"mappings":"AAiBA;;GAEG;AACH,wBAAsB,qBAAqB,kBAgB1C;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,KAAA,EAAE,IAAI,KAAA,iBAYhD;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,SAAS,KAAA,iBAgEzD;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,KAAA,EACT,YAAY,KAAA,EACZ,OAAO,KAAA,iBAyER;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,KAAA,iBAuB7C","file":"IdmOps.d.ts","sourcesContent":["/* eslint-disable no-await-in-loop */\nimport fs from 'fs';\nimport fse from 'fs-extra';\nimport replaceall from 'replaceall';\nimport propertiesReader from 'properties-reader';\nimport {\n getAllConfigEntities,\n getConfigEntity,\n queryAllManagedObjectsByType,\n} from '../api/IdmConfigApi';\nimport {\n printMessage,\n createProgressIndicator,\n stopProgressIndicator,\n} from './utils/Console';\nimport { getTypedFilename } from './utils/ExportImportUtils';\n\n/**\n * List all IDM configuration objects\n */\nexport async function listAllConfigEntities() {\n let configEntities = [];\n try {\n configEntities = (await getAllConfigEntities()).data;\n } catch (getAllConfigEntitiesError) {\n printMessage(getAllConfigEntitiesError, 'error');\n printMessage(\n `Error getting config entities: ${getAllConfigEntitiesError}`,\n 'error'\n );\n }\n if ('configurations' in configEntities) {\n configEntities['configurations'].forEach((configEntity) => {\n printMessage(`${configEntity._id}`, 'data');\n });\n }\n}\n\n/**\n * Export an IDM configuration object.\n * @param {String} id the desired configuration object\n * @param {String} file optional export file\n */\nexport async function exportConfigEntity(id, file) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(`${id}`, 'idm');\n }\n const configEntity = (await getConfigEntity(id)).data;\n fs.writeFile(fileName, JSON.stringify(configEntity, null, 2), (err) => {\n if (err) {\n return printMessage(`ERROR - can't save ${id} export to file`, 'error');\n }\n return '';\n });\n}\n\n/**\n * Export all IDM configuration objects into separate JSON files in a directory specified by <directory>\n * @param {String} directory export directory\n */\nexport async function exportAllRawConfigEntities(directory) {\n let configEntities = [];\n try {\n configEntities = (await getAllConfigEntities()).data;\n } catch (getAllConfigEntitiesError) {\n printMessage(getAllConfigEntitiesError, 'error');\n printMessage(\n `Error getting config entities: ${getAllConfigEntitiesError}`,\n 'error'\n );\n }\n if ('configurations' in configEntities) {\n if (!fs.existsSync(directory)) {\n fs.mkdirSync(directory);\n }\n createProgressIndicator(\n undefined,\n 'Exporting config objects...',\n 'indeterminate'\n );\n const entityPromises = [];\n configEntities['configurations'].forEach((x) => {\n entityPromises.push(\n getConfigEntity(x._id)\n .then((response) => response.data)\n .catch((getConfigEntityError) => {\n if (\n !(\n getConfigEntityError.response.status === 403 &&\n getConfigEntityError.response.data.message ===\n 'This operation is not available in ForgeRock Identity Cloud.'\n )\n ) {\n printMessage(getConfigEntityError, 'error');\n printMessage(\n `Error getting config entity: ${getConfigEntityError}`,\n 'error'\n );\n }\n })\n );\n });\n Promise.all(entityPromises).then((result) => {\n // console.log(result);\n result.forEach((item) => {\n if (item != null) {\n fse.outputFile(\n `${directory}/${item._id}.json`,\n JSON.stringify(item, null, 2),\n // eslint-disable-next-line consistent-return\n (err) => {\n if (err) {\n return printMessage(\n `ERROR - can't save config ${item._id} to file - ${err}`,\n 'error'\n );\n }\n }\n );\n }\n });\n stopProgressIndicator(null, 'success');\n });\n }\n}\n\n/**\n * Export all IDM configuration objects\n * @param {String} directory export directory\n * @param {String} entitiesFile JSON file that specifies the config entities to export/import\n * @param {String} envFile File that defines environment specific variables for replacement during configuration export/import\n */\nexport async function exportAllConfigEntities(\n directory,\n entitiesFile,\n envFile\n) {\n let entriesToExport = [];\n // read list of entities to export\n fs.readFile(entitiesFile, 'utf8', async (err, data) => {\n if (err) throw err;\n const entriesData = JSON.parse(data);\n entriesToExport = entriesData.idm;\n // console.log(`entriesToExport ${entriesToExport}`);\n\n // read list of configs to parameterize for environment specific values\n const envParams = propertiesReader(envFile);\n\n let configEntities = [];\n try {\n configEntities = (await getAllConfigEntities()).data;\n } catch (getAllConfigEntitiesError) {\n printMessage(getAllConfigEntitiesError, 'error');\n printMessage(\n `Error getting config entities: ${getAllConfigEntitiesError}`,\n 'error'\n );\n }\n if ('configurations' in configEntities) {\n // create export directory if not exist\n if (!fs.existsSync(directory)) {\n fs.mkdirSync(directory);\n }\n createProgressIndicator(\n undefined,\n 'Exporting config objects...',\n 'indeterminate'\n );\n const entityPromises = [];\n configEntities['configurations'].forEach((x) => {\n if (entriesToExport.includes(x._id)) {\n // console.log(`- ${x._id}`);\n entityPromises.push(\n getConfigEntity(x._id).then((response) => response.data)\n );\n }\n });\n Promise.all(entityPromises).then((result) => {\n // console.log(result);\n result.forEach((item) => {\n if (item != null) {\n let configEntityString = JSON.stringify(item, null, 2);\n envParams.each((key, value) => {\n configEntityString = replaceall(\n value,\n `\\${${key}}`,\n configEntityString\n );\n });\n fse.outputFile(\n `${directory}/${item._id}.json`,\n JSON.stringify(item, null, 2),\n // eslint-disable-next-line consistent-return\n (error) => {\n if (err) {\n return printMessage(\n `ERROR - can't save config ${item._id} to file - ${error}`,\n 'error'\n );\n }\n }\n );\n }\n });\n stopProgressIndicator(null, 'success');\n });\n }\n });\n}\n\n/**\n * Count number of managed objects of a given type\n * @param {String} type managed object type, e.g. alpha_user\n */\nexport async function countManagedObjects(type) {\n let count = 0;\n let result = {\n result: [],\n resultCount: 0,\n pagedResultsCookie: null,\n totalPagedResultsPolicy: 'NONE',\n totalPagedResults: -1,\n remainingPagedResults: -1,\n };\n try {\n do {\n result = (\n await queryAllManagedObjectsByType(type, [], result.pagedResultsCookie)\n )['data'];\n count += result.resultCount;\n // printMessage(result);\n } while (result.pagedResultsCookie);\n printMessage(`${type}: ${count}`);\n } catch (error) {\n printMessage(error.response.data, 'error');\n printMessage(`Error querying managed objects by type: ${error}`, 'error');\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * List providers
3
+ */
4
+ export declare function listProviders(): Promise<void>;
5
+ /**
6
+ * Get social identity provider by id
7
+ * @param {String} id social identity provider id/name
8
+ * @returns {Promise} a promise that resolves a social identity provider object
9
+ */
10
+ export declare function getSocialIdentityProviderById(id: any): Promise<any>;
11
+ /**
12
+ * Export provider by id
13
+ * @param {String} id provider id/name
14
+ * @param {String} file optional export file name
15
+ */
16
+ export declare function exportProvider(id: any, file?: string): Promise<void>;
17
+ /**
18
+ * Export all providers
19
+ * @param {String} file optional export file name
20
+ */
21
+ export declare function exportProvidersToFile(file: any): Promise<void>;
22
+ /**
23
+ * Export all providers to individual files
24
+ */
25
+ export declare function exportProvidersToFiles(): Promise<void>;
26
+ /**
27
+ * Import provider by id/name
28
+ * @param {String} id provider id/name
29
+ * @param {String} file import file name
30
+ */
31
+ export declare function importProviderById(id: any, file: any): Promise<void>;
32
+ /**
33
+ * Import first provider from file
34
+ * @param {String} file import file name
35
+ */
36
+ export declare function importFirstProvider(file: any): Promise<void>;
37
+ /**
38
+ * Import all providers from file
39
+ * @param {String} file import file name
40
+ */
41
+ export declare function importProvidersFromFile(file: any): Promise<void>;
42
+ /**
43
+ * Import providers from *.idp.json files in current working directory
44
+ */
45
+ export declare function importProvidersFromFiles(): Promise<void>;
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/IdpOps.ts"],"names":[],"mappings":"AA+BA;;GAEG;AACH,wBAAsB,aAAa,kBAYlC;AAED;;;;GAIG;AACH,wBAAsB,6BAA6B,CAAC,EAAE,KAAA,gBAcrD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,EAAE,KAAA,EAAE,IAAI,SAAK,iBAwBjD;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,KAAA,iBAsB/C;AAED;;GAEG;AACH,wBAAsB,sBAAsB,kBAkB3C;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,KAAA,EAAE,IAAI,KAAA,iBAkDhD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,KAAA,iBA6C7C;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,KAAA,iBAuCjD;AAED;;GAEG;AACH,wBAAsB,wBAAwB,kBAsC7C","file":"IdpOps.d.ts","sourcesContent":["import fs from 'fs';\nimport {\n getSocialIdentityProviders,\n putProviderByTypeAndId,\n} from '../api/SocialIdentityProvidersApi';\nimport { getScript } from '../api/ScriptApi';\nimport { createOrUpdateScript } from './ScriptOps';\nimport {\n convertBase64TextToArray,\n convertTextArrayToBase64,\n getRealmString,\n getTypedFilename,\n saveJsonToFile,\n validateImport,\n} from './utils/ExportImportUtils';\nimport {\n printMessage,\n createProgressIndicator,\n updateProgressIndicator,\n stopProgressIndicator,\n} from './utils/Console';\n\n// use a function vs a template variable to avoid problems in loops\nfunction getFileDataTemplate() {\n return {\n meta: {},\n script: {},\n idp: {},\n };\n}\n\n/**\n * List providers\n */\nexport async function listProviders() {\n getSocialIdentityProviders()\n .then((response) => {\n response.data.result.sort((a, b) => a._id.localeCompare(b._id));\n response.data.result.forEach((socialIdentityProvider) => {\n printMessage(`${socialIdentityProvider._id}`, 'data');\n });\n })\n .catch((err) => {\n printMessage(`listProviders ERROR: ${err.message}`, 'error');\n printMessage(err, 'error');\n });\n}\n\n/**\n * Get social identity provider by id\n * @param {String} id social identity provider id/name\n * @returns {Promise} a promise that resolves a social identity provider object\n */\nexport async function getSocialIdentityProviderById(id) {\n return getSocialIdentityProviders().then((response) => {\n const foundProviders = response.data.result.filter(\n (provider) => provider._id === id\n );\n switch (foundProviders.length) {\n case 1:\n return foundProviders[0];\n case 0:\n throw new Error(`Provider '${id}' not found`);\n default:\n throw new Error(`${foundProviders.length} providers '${id}' found`);\n }\n });\n}\n\n/**\n * Export provider by id\n * @param {String} id provider id/name\n * @param {String} file optional export file name\n */\nexport async function exportProvider(id, file = '') {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(id, 'idp');\n }\n createProgressIndicator(1, `Exporting ${id}`);\n try {\n const idpData = await getSocialIdentityProviderById(id);\n updateProgressIndicator(`Writing file ${fileName}`);\n const fileData = getFileDataTemplate();\n fileData.idp[idpData._id] = idpData;\n if (idpData.transform) {\n const scriptData = (await getScript(idpData.transform)).data;\n scriptData.script = convertBase64TextToArray(scriptData.script);\n fileData.script[idpData.transform] = scriptData;\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `Exported ${id['brightCyan']} to ${fileName['brightCyan']}.`\n );\n } catch (err) {\n stopProgressIndicator(`${err}`);\n printMessage(`${err}`, 'error');\n }\n}\n\n/**\n * Export all providers\n * @param {String} file optional export file name\n */\nexport async function exportProvidersToFile(file) {\n let fileName = file;\n if (!fileName) {\n fileName = getTypedFilename(`all${getRealmString()}Providers`, 'idp');\n }\n const fileData = getFileDataTemplate();\n const allIdpsData = (await getSocialIdentityProviders()).data.result;\n createProgressIndicator(allIdpsData.length, 'Exporting providers');\n for (const idpData of allIdpsData) {\n updateProgressIndicator(`Exporting provider ${idpData._id}`);\n fileData.idp[idpData._id] = idpData;\n if (idpData.transform) {\n // eslint-disable-next-line no-await-in-loop\n const scriptData = (await getScript(idpData.transform)).data;\n scriptData.script = convertBase64TextToArray(scriptData.script);\n fileData.script[idpData.transform] = scriptData;\n }\n }\n saveJsonToFile(fileData, fileName);\n stopProgressIndicator(\n `${allIdpsData.length} providers exported to ${fileName}.`\n );\n}\n\n/**\n * Export all providers to individual files\n */\nexport async function exportProvidersToFiles() {\n const allIdpsData = await (await getSocialIdentityProviders()).data.result;\n // printMessage(allIdpsData, 'data');\n createProgressIndicator(allIdpsData.length, 'Exporting providers');\n for (const idpData of allIdpsData) {\n updateProgressIndicator(`Writing provider ${idpData._id}`);\n const fileName = getTypedFilename(idpData._id, 'idp');\n const fileData = getFileDataTemplate();\n fileData.idp[idpData._id] = idpData;\n if (idpData.transform) {\n // eslint-disable-next-line no-await-in-loop\n const scriptData = (await getScript(idpData.transform)).data;\n scriptData.script = convertBase64TextToArray(scriptData.script);\n fileData.script[idpData.transform] = scriptData;\n }\n saveJsonToFile(fileData, fileName);\n }\n stopProgressIndicator(`${allIdpsData.length} providers exported.`);\n}\n\n/**\n * Import provider by id/name\n * @param {String} id provider id/name\n * @param {String} file import file name\n */\nexport async function importProviderById(id, file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, 'Importing provider...');\n let found = false;\n for (const idpId in fileData.idp) {\n if ({}.hasOwnProperty.call(fileData.idp, idpId)) {\n if (idpId === id) {\n found = true;\n updateProgressIndicator(`Importing ${fileData.idp[idpId]._id}`);\n const scriptId = fileData.idp[idpId].transform;\n const scriptData = fileData.script[scriptId];\n if (scriptId && scriptData) {\n scriptData.script = convertTextArrayToBase64(scriptData.script);\n // eslint-disable-next-line no-await-in-loop\n await createOrUpdateScript(\n fileData.idp[idpId].transform,\n fileData.script[fileData.idp[idpId].transform]\n );\n }\n putProviderByTypeAndId(\n fileData.idp[idpId]._type._id,\n idpId,\n fileData.idp[idpId]\n )\n .then(() => {\n stopProgressIndicator(`Successfully imported provider ${id}.`);\n })\n .catch((importProviderErr) => {\n stopProgressIndicator(\n `Error importing provider ${fileData.idp[idpId]._id}`\n );\n printMessage(`\\nError importing provider ${id}`, 'error');\n printMessage(importProviderErr.response.data, 'error');\n });\n break;\n }\n }\n }\n if (!found) {\n stopProgressIndicator(\n `Provider ${id.brightCyan} not found in ${file.brightCyan}!`\n );\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import first provider from file\n * @param {String} file import file name\n */\nexport async function importFirstProvider(file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(1, 'Importing provider...');\n for (const idpId in fileData.idp) {\n if ({}.hasOwnProperty.call(fileData.idp, idpId)) {\n updateProgressIndicator(`Importing ${fileData.idp[idpId]._id}`);\n const scriptId = fileData.idp[idpId].transform;\n const scriptData = fileData.script[scriptId];\n if (scriptId && scriptData) {\n scriptData.script = convertTextArrayToBase64(scriptData.script);\n // eslint-disable-next-line no-await-in-loop\n await createOrUpdateScript(\n fileData.idp[idpId].transform,\n fileData.script[fileData.idp[idpId].transform]\n );\n }\n putProviderByTypeAndId(\n fileData.idp[idpId]._type._id,\n idpId,\n fileData.idp[idpId]\n ).then((result) => {\n if (result == null) {\n stopProgressIndicator(\n `Error importing provider ${fileData.idp[idpId]._id}`\n );\n printMessage(\n `Error importing provider ${fileData.idp[idpId]._id}`,\n 'error'\n );\n } else {\n stopProgressIndicator(\n `Successfully imported provider ${fileData.idp[idpId]._id}.`\n );\n }\n });\n break;\n }\n }\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import all providers from file\n * @param {String} file import file name\n */\nexport async function importProvidersFromFile(file) {\n fs.readFile(file, 'utf8', async (err, data) => {\n if (err) throw err;\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n createProgressIndicator(\n Object.keys(fileData.idp).length,\n 'Importing providers...'\n );\n for (const idpId in fileData.idp) {\n if ({}.hasOwnProperty.call(fileData.idp, idpId)) {\n const scriptId = fileData.idp[idpId].transform;\n const scriptData = fileData.script[scriptId];\n if (scriptId && scriptData) {\n scriptData.script = convertTextArrayToBase64(scriptData.script);\n // eslint-disable-next-line no-await-in-loop\n await createOrUpdateScript(\n fileData.idp[idpId].transform,\n fileData.script[fileData.idp[idpId].transform]\n );\n }\n // eslint-disable-next-line no-await-in-loop\n const result = await putProviderByTypeAndId(\n fileData.idp[idpId]._type._id,\n idpId,\n fileData.idp[idpId]\n );\n if (!result) {\n updateProgressIndicator(\n `Successfully imported ${fileData.idp[idpId].name}`\n );\n }\n }\n }\n stopProgressIndicator(`Providers imported.`);\n } else {\n printMessage('Import validation failed...', 'error');\n }\n });\n}\n\n/**\n * Import providers from *.idp.json files in current working directory\n */\nexport async function importProvidersFromFiles() {\n const names = fs.readdirSync('.');\n const jsonFiles = names.filter((name) =>\n name.toLowerCase().endsWith('.idp.json')\n );\n\n createProgressIndicator(jsonFiles.length, 'Importing providers...');\n let total = 0;\n for (const file of jsonFiles) {\n const data = fs.readFileSync(file, 'utf8');\n const fileData = JSON.parse(data);\n if (validateImport(fileData.meta)) {\n const count = Object.keys(fileData.idp).length;\n total += count;\n for (const idpId in fileData.idp) {\n if ({}.hasOwnProperty.call(fileData.idp, idpId)) {\n // eslint-disable-next-line no-await-in-loop\n const result = await putProviderByTypeAndId(\n fileData.idp[idpId]._type._id,\n idpId,\n fileData.idp[idpId]\n );\n if (result == null) {\n printMessage(\n `Error importing ${count} providers from ${file}`,\n 'error'\n );\n }\n }\n }\n updateProgressIndicator(`Imported ${count} provider(s) from ${file}`);\n } else {\n printMessage(`Validation of ${file} failed!`, 'error');\n }\n }\n stopProgressIndicator(\n `Finished importing ${total} provider(s) from ${jsonFiles.length} file(s).`\n );\n}\n"]}
@@ -0,0 +1,151 @@
1
+ interface SingleTreeExportTemplate {
2
+ meta?: Record<string, unknown>;
3
+ innerNodes?: Record<string, unknown>;
4
+ innernodes?: Record<string, unknown>;
5
+ nodes?: Record<string, unknown>;
6
+ scripts?: Record<string, unknown>;
7
+ emailTemplates?: Record<string, unknown>;
8
+ socialIdentityProviders?: Record<string, unknown>;
9
+ themes?: unknown[];
10
+ saml2Entities?: Record<string, unknown>;
11
+ circlesOfTrust?: Record<string, unknown>;
12
+ tree: Record<string, unknown>;
13
+ }
14
+ /**
15
+ * Export options
16
+ */
17
+ interface ExportOptions {
18
+ /**
19
+ * Where applicable, use string arrays to store multi-line text (e.g. scripts).
20
+ */
21
+ useStringArrays: boolean;
22
+ /**
23
+ * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).
24
+ */
25
+ deps: boolean;
26
+ /**
27
+ * Verbose output during command execution. May or may not produce additional output.
28
+ */
29
+ verbose: boolean;
30
+ }
31
+ /**
32
+ * Create export data for a tree with all its nodes and dependencies. The export data can be written to a file as is.
33
+ * @param {string} treeId tree id/name
34
+ * @param {ExportOptions} options export options
35
+ * @returns {Promise<SingleTreeExportTemplate>} a promise that resolves to an object containing the tree and all its nodes and dependencies
36
+ */
37
+ export declare function exportTree(treeId: string, options?: ExportOptions): Promise<SingleTreeExportTemplate>;
38
+ /**
39
+ * Get all the journeys/trees without all their nodes and dependencies.
40
+ * @returns {Promise<unknown[]>} a promise that resolves to an array of journey objects
41
+ */
42
+ export declare function getJourneys(): Promise<unknown[]>;
43
+ /**
44
+ * Export journey by id/name to file
45
+ * @param {string} journeyId journey id/name
46
+ * @param {string} file optional export file name
47
+ * @param {ExportOptions} options export options
48
+ */
49
+ export declare function exportJourneyToFile(journeyId: string, file: string, options: ExportOptions): Promise<void>;
50
+ /**
51
+ * Export all journeys to file
52
+ * @param {string} file optional export file name
53
+ * @param {ExportOptions} options export options
54
+ */
55
+ export declare function exportJourneysToFile(file: string, options: ExportOptions): Promise<void>;
56
+ /**
57
+ * Export all journeys to separate files
58
+ * @param {ExportOptions} options export options
59
+ */
60
+ export declare function exportJourneysToFiles(options: ExportOptions): Promise<void>;
61
+ /**
62
+ * Import options
63
+ */
64
+ interface ImportOptions {
65
+ /**
66
+ * Generate new UUIDs for all nodes during import.
67
+ */
68
+ reUuid: boolean;
69
+ /**
70
+ * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes).
71
+ */
72
+ deps: boolean;
73
+ /**
74
+ * Verbose output during command execution. May or may not produce additional output.
75
+ */
76
+ verbose: boolean;
77
+ }
78
+ /**
79
+ * Helper to import a tree with all dependencies from an import data object (typically read from a file)
80
+ * @param {SingleTreeExportTemplate} treeObject tree object containing tree and all its dependencies
81
+ * @param {ImportOptions} options import options
82
+ */
83
+ export declare function importTree(treeObject: SingleTreeExportTemplate, options: ImportOptions): Promise<void>;
84
+ /**
85
+ * Import a journey from file
86
+ * @param {string} journeyId journey id/name
87
+ * @param {string} file import file name
88
+ * @param {ImportOptions} options import options
89
+ */
90
+ export declare function importJourneyFromFile(journeyId: string, file: string, options: ImportOptions): Promise<void>;
91
+ /**
92
+ * Import first journey from file
93
+ * @param {string} file import file name
94
+ * @param {ImportOptions} options import options
95
+ */
96
+ export declare function importFirstJourneyFromFile(file: string, options: ImportOptions): Promise<void>;
97
+ /**
98
+ * Import all journeys from file
99
+ * @param {string} file import file name
100
+ * @param {ImportOptions} options import options
101
+ */
102
+ export declare function importJourneysFromFile(file: string, options: ImportOptions): Promise<void>;
103
+ /**
104
+ * Import all journeys from separate files
105
+ * @param {ImportOptions} options import options
106
+ */
107
+ export declare function importJourneysFromFiles(options: ImportOptions): Promise<void>;
108
+ /**
109
+ * Describe a tree
110
+ * @param {Object} treeData tree
111
+ * @returns {Object} an object describing the tree
112
+ */
113
+ export declare function describeTree(treeData: any): {};
114
+ /**
115
+ * Find all node configuration objects that are no longer referenced by any tree
116
+ * @returns {Promise<unknown[]>} a promise that resolves to an array of orphaned nodes
117
+ */
118
+ export declare function findOrphanedNodes(): Promise<unknown[]>;
119
+ /**
120
+ * Remove orphaned nodes
121
+ * @param {[Object]} orphanedNodes Pass in an array of orphaned node configuration objects to remove
122
+ * @returns {Promise<unknown[]>} a promise that resolves to an array nodes that encountered errors deleting
123
+ */
124
+ export declare function removeOrphanedNodes(orphanedNodes: unknown[]): Promise<unknown[]>;
125
+ /**
126
+ * Analyze if a journey contains any custom nodes considering the detected or the overridden version.
127
+ * @param {Object} journey Journey/tree configuration object
128
+ * @returns {boolean} True if the journey/tree contains any custom nodes, false otherwise.
129
+ */
130
+ export declare function isCustom(journey: any): Promise<boolean>;
131
+ /**
132
+ * List all the journeys/trees
133
+ * @param {boolean} long Long version, all the fields
134
+ * @param {boolean} analyze Analyze journeys/trees for custom nodes (expensive)
135
+ * @returns {Promise<unknown[]>} a promise that resolves to an array journey objects
136
+ */
137
+ export declare function listJourneys(long?: boolean, analyze?: boolean): Promise<unknown[]>;
138
+ /**
139
+ * Delete a journey
140
+ * @param {string} journeyId journey id/name
141
+ * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info
142
+ */
143
+ export declare function deleteJourney(journeyId: string, options: any, progress?: boolean): Promise<{
144
+ nodes: {};
145
+ }>;
146
+ /**
147
+ * Delete all journeys
148
+ * @param {Object} options deep=true also delete all the nodes and inner nodes, verbose=true print verbose info
149
+ */
150
+ export declare function deleteJourneys(options: any): Promise<{}>;
151
+ export {};