From d13621abb46d1258b68fb9d4dd730a0eafe608e2 Mon Sep 17 00:00:00 2001 From: Volker Scheuber Date: Tue, 15 Aug 2023 21:14:16 -0500 Subject: [PATCH] reorganize types and order imports --- .eslintrc | 16 +- package-lock.json | 99 +- package.json | 3 +- src/api/AgentApi.ts | 19 +- src/api/ApiTypes.ts | 250 +--- src/api/AuthenticateApi.ts | 3 +- src/api/BaseApi.ts | 14 +- src/api/CirclesOfTrustApi.ts | 14 +- src/api/IdmConfigApi.ts | 5 +- src/api/IdmSystemApi.ts | 5 +- src/api/ManagedObjectApi.ts | 5 +- src/api/NodeApi.ts | 100 +- src/api/OAuth2ClientApi.ts | 55 +- src/api/OAuth2OIDCApi.ts | 11 +- src/api/OAuth2ProviderApi.ts | 8 +- src/api/PoliciesApi.ts | 43 +- src/api/PolicySetApi.ts | 12 +- src/api/RealmApi.ts | 5 +- src/api/ResourceTypesApi.ts | 10 +- src/api/Saml2Api.ts | 3 +- src/api/ScriptApi.ts | 38 +- src/api/ServerInfoApi.ts | 3 +- src/api/ServiceApi.ts | 42 +- src/api/SocialIdentityProvidersApi.ts | 13 +- src/api/TreeApi.ts | 18 +- src/api/cloud/AdminFederationProvidersApi.ts | 20 +- src/api/cloud/EnvInfoApi.ts | 3 +- src/api/cloud/FeatureApi.ts | 5 +- src/api/cloud/LogApi.ts | 37 +- src/api/cloud/SecretsApi.ts | 5 +- src/api/cloud/StartupApi.ts | 3 +- src/api/cloud/VariablesApi.ts | 5 +- src/lib/FrodoLib.ts | 37 +- src/ops/AdminOps.ts | 28 +- src/ops/AgentOps.ts | 22 +- src/ops/ApplicationOps.ts | 20 +- src/ops/AuthenticateOps.ts | 22 +- src/ops/CirclesOfTrustOps.ts | 27 +- src/ops/ConnectionProfileOps.ts | 15 +- src/ops/IdmConfigOps.ts | 14 +- src/ops/IdpOps.ts | 28 +- src/ops/InfoOps.ts | 6 +- src/ops/JoseOps.ts | 1 + src/ops/JourneyOps.test.ts | 10 +- src/ops/JourneyOps.ts | 1095 ++++++++++-------- src/ops/ManagedObjectOps.ts | 14 +- src/ops/NodeOps.ts | 296 ++++- src/ops/OAuth2ClientOps.ts | 20 +- src/ops/OAuth2OidcOps.ts | 5 +- src/ops/OAuth2ProviderOps.ts | 6 +- src/ops/OpsTypes.ts | 124 -- src/ops/OrganizationOps.ts | 6 +- src/ops/PolicyOps.ts | 28 +- src/ops/PolicySetOps.ts | 30 +- src/ops/RealmOps.ts | 8 +- src/ops/ResourceTypeOps.ts | 12 +- src/ops/Saml2Ops.ts | 30 +- src/ops/ScriptOps.ts | 23 +- src/ops/ServiceOps.ts | 12 +- src/ops/ThemeOps.ts | 19 +- src/ops/VersionUtils.ts | 4 +- src/ops/cloud/AdminFederationOps.ts | 14 +- src/ops/cloud/FeatureOps.ts | 2 +- src/ops/cloud/LogOps.ts | 16 +- src/ops/cloud/SecretsOps.ts | 12 +- src/ops/cloud/ServiceAccountOps.ts | 6 +- src/ops/cloud/StartupOps.ts | 10 +- src/ops/cloud/VariablesOps.ts | 4 +- src/shared/State.ts | 3 +- src/test/mocks/ForgeRockApiMockEngine.ts | 1 + src/utils/AutoSetupPolly.ts | 15 +- src/utils/DataProtection.ts | 5 +- src/utils/ExportImportUtils.test.ts | 74 -- src/utils/ExportImportUtils.ts | 11 +- src/utils/ScriptValidationUtils.ts | 5 +- src/utils/SetupPollyForFrodoLib.ts | 11 +- 76 files changed, 1636 insertions(+), 1387 deletions(-) diff --git a/.eslintrc b/.eslintrc index 209bdbbf8..20cbb8acf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,11 +17,23 @@ "requireConfigFile": false, "project": "./tsconfig.json" }, - "plugins": ["prettier", "jest", "@typescript-eslint", "deprecation"], + "plugins": [ + "prettier", + "jest", + "@typescript-eslint", + "deprecation", + "simple-import-sort", + "import" + ], "rules": { "@typescript-eslint/no-explicit-any": "off", "prettier/prettier": ["error"], - "deprecation/deprecation": "warn" + "deprecation/deprecation": "warn", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error" }, "env": { "jest": true, diff --git a/package-lock.json b/package-lock.json index 7aef043a6..2d112048f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,9 +56,10 @@ "eslint": "^8.28.0", "eslint-config-prettier": "^8.4.0", "eslint-plugin-deprecation": "^1.5.0", - "eslint-plugin-import": "^2.25.4", + "eslint-plugin-import": "^2.28.0", "eslint-plugin-jest": "^27.1.5", "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^10.0.0", "jest": "^29.3.1", "jest-jasmine2": "^29.3.1", "loglevel": "^1.8.1", @@ -3771,6 +3772,25 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -5094,26 +5114,29 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", "dev": true, "dependencies": { "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", "array.prototype.flat": "^1.3.1", "array.prototype.flatmap": "^1.3.1", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", + "eslint-module-utils": "^2.8.0", "has": "^1.0.3", - "is-core-module": "^2.11.0", + "is-core-module": "^2.12.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" @@ -5188,6 +5211,15 @@ } } }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-10.0.0.tgz", + "integrity": "sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -6514,9 +6546,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -9274,6 +9306,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -9916,12 +9977,12 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10103,9 +10164,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 1d7ec43b5..e4a8c5840 100644 --- a/package.json +++ b/package.json @@ -158,9 +158,10 @@ "eslint": "^8.28.0", "eslint-config-prettier": "^8.4.0", "eslint-plugin-deprecation": "^1.5.0", - "eslint-plugin-import": "^2.25.4", + "eslint-plugin-import": "^2.28.0", "eslint-plugin-jest": "^27.1.5", "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-simple-import-sort": "^10.0.0", "jest": "^29.3.1", "jest-jasmine2": "^29.3.1", "loglevel": "^1.8.1", diff --git a/src/api/AgentApi.ts b/src/api/AgentApi.ts index 8ca1ad03f..45ccbccc3 100644 --- a/src/api/AgentApi.ts +++ b/src/api/AgentApi.ts @@ -1,10 +1,12 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; -import { deleteDeepByKey } from '../utils/JsonUtils'; + import { State } from '../shared/State'; import { debugMessage } from '../utils/Console'; -import { AgentSkeleton, AgentType } from './ApiTypes'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { deleteDeepByKey } from '../utils/JsonUtils'; +import { type IdObjectSkeletonInterface } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; +import { type AmServiceType } from './ServiceApi'; const getAgentTypesURLTemplate = '%s/json%s/realm-config/agents?_action=getAllTypes'; @@ -24,6 +26,15 @@ const getApiConfig = () => { }; }; +export type GatewayAgentType = 'IdentityGatewayAgent'; +export type JavaAgentType = 'J2EEAgent'; +export type WebAgentType = 'WebAgent'; +export type AgentType = GatewayAgentType | JavaAgentType | WebAgentType; + +export type AgentSkeleton = IdObjectSkeletonInterface & { + _type: AmServiceType; +}; + /** * Get agent types * @returns {Promise} a promise that resolves to an object containing an array of agent types diff --git a/src/api/ApiTypes.ts b/src/api/ApiTypes.ts index d019de2af..6e8fbbef8 100644 --- a/src/api/ApiTypes.ts +++ b/src/api/ApiTypes.ts @@ -14,108 +14,6 @@ export interface IdObjectSkeletonInterface extends NoIdObjectSkeletonInterface { _id?: string; } -// export interface PagedResults { -// // eslint-disable-next-line @typescript-eslint/no-explicit-any -// result: any[]; -// resultCount: number; -// pagedResultsCookie: string; -// totalPagedResultsPolicy: string; -// totalPagedResults: number; -// remainingPagedResults: number; -// } - -export interface UiConfigInterface { - categories: string; -} - -export type AdminFederationConfigSkeleton = IdObjectSkeletonInterface & { - groups: { - claim: string; - mappings: { - 'super-admins': string[]; - 'tenant-admins': string[]; - }; - }; -}; - -export interface NodeRefSkeletonInterface { - connections: Record; - displayName: string; - nodeType: string; - x: number; - y: number; -} - -export interface InnerNodeRefSkeletonInterface { - _id: string; - displayName: string; - nodeType: string; -} - -export type TreeSkeleton = IdObjectSkeletonInterface & { - entryNodeId: string; - nodes: Record; - identityResource?: string; - uiConfig?: UiConfigInterface; - enabled?: boolean; -}; - -export type AmServiceType = IdObjectSkeletonInterface & { - name: string; -}; - -export type NodeSkeleton = IdObjectSkeletonInterface & { - _type: AmServiceType; - nodes?: InnerNodeRefSkeletonInterface[]; - tree?: string; - identityResource?: string; -}; - -export type PolicySetSkeleton = NoIdObjectSkeletonInterface & { - name: string; - resourceTypeUuids: string[]; -}; - -export type ResourceTypeSkeleton = NoIdObjectSkeletonInterface & { - uuid: string; - name: string; -}; - -export type PolicyConditionType = - | 'Script' - | 'AMIdentityMembership' - | 'IPv6' - | 'IPv4' - | 'SimpleTime' - | 'LEAuthLevel' - | 'LDAPFilter' - | 'AuthScheme' - | 'Session' - | 'AND' - | 'AuthenticateToRealm' - | 'ResourceEnvIP' - | 'Policy' - | 'OAuth2Scope' - | 'SessionProperty' - | 'OR' - | 'Transaction' - | 'NOT' - | 'AuthLevel' - | 'AuthenticateToService'; - -export type PolicyCondition = NoIdObjectSkeletonInterface & { - type: PolicyConditionType; - condition?: PolicyCondition; - conditions?: PolicyCondition[]; -}; - -export type PolicySkeleton = IdObjectSkeletonInterface & { - name: string; - applicationName: string; - condition?: PolicyCondition; - resourceTypeUuid: string; -}; - export type ReadableStrings = string[]; export type WritableStrings = { @@ -123,155 +21,15 @@ export type WritableStrings = { value: string[]; }; -export type OAuth2ClientSkeleton = IdObjectSkeletonInterface & { - overrideOAuth2ClientConfig?: { - [k: string]: string | number | boolean | string[] | object | undefined; - }; - advancedOAuth2ClientConfig?: { - descriptions: { - inherited: boolean; - value: string[]; - }; - grantTypes?: ReadableStrings | WritableStrings; - [k: string]: string | number | boolean | string[] | object | undefined; - }; - signEncOAuth2ClientConfig?: { - [k: string]: string | number | boolean | string[] | object | undefined; - }; - coreOpenIDClientConfig?: { - [k: string]: string | number | boolean | string[] | object | undefined; - }; - coreOAuth2ClientConfig?: { - userpassword?: string; - clientName?: { - inherited: boolean; - value: string[]; - }; - accessTokenLifetime?: { - inherited: boolean; - value: number; - }; - scopes?: ReadableStrings | WritableStrings; - defaultScopes?: { - value: string[]; - [k: string]: string | number | boolean | string[] | object | undefined; - }; - [k: string]: string | number | boolean | string[] | object | undefined; - }; - coreUmaClientConfig?: { - [k: string]: string | number | boolean | string[] | object | undefined; - }; - _type: AmServiceType; -}; - -export type AmServiceSkeleton = IdObjectSkeletonInterface & { - _type: AmServiceType; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; +export type QueryResult = { + result: Type[]; }; -export interface ServiceNextDescendentResponse { - result: ServiceNextDescendent; -} - -export interface ServiceNextDescendent { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -} - -export interface FullService extends AmServiceSkeleton { - nextDescendents?: ServiceNextDescendent[]; -} - -export type GatewayAgentType = 'IdentityGatewayAgent'; -export type JavaAgentType = 'J2EEAgent'; -export type WebAgentType = 'WebAgent'; -export type AgentType = GatewayAgentType | JavaAgentType | WebAgentType; - -export type AgentSkeleton = IdObjectSkeletonInterface & { - _type: AmServiceType; -}; - -export type ThemeSkeleton = IdObjectSkeletonInterface & { - name: string; - isDefault: boolean; - linkedTrees: string[]; -}; - -export type UiThemeRealmObject = IdObjectSkeletonInterface & { - name: string; - realm: Map; -}; - -export type ScriptLanguage = 'GROOVY' | 'JAVASCRIPT'; - -export type ScriptContext = - | 'OAUTH2_ACCESS_TOKEN_MODIFICATION' - | 'AUTHENTICATION_CLIENT_SIDE' - | 'AUTHENTICATION_TREE_DECISION_NODE' - | 'AUTHENTICATION_SERVER_SIDE' - | 'SOCIAL_IDP_PROFILE_TRANSFORMATION' - | 'OAUTH2_VALIDATE_SCOPE' - | 'CONFIG_PROVIDER_NODE' - | 'OAUTH2_AUTHORIZE_ENDPOINT_DATA_PROVIDER' - | 'OAUTH2_EVALUATE_SCOPE' - | 'POLICY_CONDITION' - | 'OIDC_CLAIMS' - | 'SAML2_IDP_ADAPTER' - | 'SAML2_IDP_ATTRIBUTE_MAPPER' - | 'OAUTH2_MAY_ACT'; - -export type ScriptSkeleton = IdObjectSkeletonInterface & { - name: string; - description: string; - default: boolean; - script: string | string[]; - language: ScriptLanguage; - context: ScriptContext; - createdBy: string; - creationDate: number; - lastModifiedBy: string; - lastModifiedDate: number; -}; - -export type CircleOfTrustSkeleton = IdObjectSkeletonInterface & { - status?: string; - trustedProviders?: string[]; - _type?: AmServiceType; -}; - -export type PagedResult = { - result: Result[]; +export type PagedResult = { + result: Type[]; resultCount: number; pagedResultsCookie: string; totalPagedResultsPolicy: 'EXACT'; totalPagedResults: number; remainingPagedResults: number; }; - -export type LogApiKey = { - name: string; - api_key_id: string; - api_key_secret?: string; - created_at: string; -}; - -export type LogEventPayloadSkeleton = NoIdObjectSkeletonInterface & { - context: string; - level: string; - logger: string; - mdc: { - transactionId: string; - }; - message: string; - thread: string; - timestamp: string; - transactionId: string; -}; - -export type LogEventSkeleton = NoIdObjectSkeletonInterface & { - payload: string | LogEventPayloadSkeleton; - timestamp: string; - type: string; - source: string; -}; diff --git a/src/api/AuthenticateApi.ts b/src/api/AuthenticateApi.ts index 7e8f04bec..2a91884c7 100644 --- a/src/api/AuthenticateApi.ts +++ b/src/api/AuthenticateApi.ts @@ -1,7 +1,8 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; + import { State } from '../shared/State'; import { getRealmPath } from '../utils/ForgeRockUtils'; +import { generateAmApi } from './BaseApi'; const authenticateUrlTemplate = '%s/json%s/authenticate'; const authenticateWithServiceUrlTemplate = `${authenticateUrlTemplate}?authIndexType=service&authIndexValue=%s`; diff --git a/src/api/BaseApi.ts b/src/api/BaseApi.ts index 0cb3bf721..a242173fc 100644 --- a/src/api/BaseApi.ts +++ b/src/api/BaseApi.ts @@ -1,17 +1,17 @@ -import axios, { AxiosInstance, AxiosProxyConfig } from 'axios'; import Agent from 'agentkeepalive'; +import axios, { AxiosInstance, AxiosProxyConfig } from 'axios'; import axiosRetry from 'axios-retry'; -import HttpsProxyAgent from 'https-proxy-agent'; -import url from 'url'; +import { randomUUID } from 'crypto'; import fs from 'fs'; +import HttpsProxyAgent from 'https-proxy-agent'; import path from 'path'; -import { fileURLToPath } from 'url'; -import { curlirizeMessage, printMessage } from '../utils/Console'; +import url, { fileURLToPath } from 'url'; + import _curlirize from '../ext/axios-curlirize/curlirize'; -import { randomUUID } from 'crypto'; -import { setupPollyForFrodoLib } from '../utils/SetupPollyForFrodoLib'; import StateImpl, { State } from '../shared/State'; +import { curlirizeMessage, printMessage } from '../utils/Console'; import { mergeDeep } from '../utils/JsonUtils'; +import { setupPollyForFrodoLib } from '../utils/SetupPollyForFrodoLib'; if (process.env.FRODO_MOCK) { setupPollyForFrodoLib({ state: StateImpl({}) }); diff --git a/src/api/CirclesOfTrustApi.ts b/src/api/CirclesOfTrustApi.ts index de9152e99..4a11f3e0b 100644 --- a/src/api/CirclesOfTrustApi.ts +++ b/src/api/CirclesOfTrustApi.ts @@ -1,9 +1,11 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; + import { State } from '../shared/State'; -import { CircleOfTrustSkeleton, PagedResult } from './ApiTypes'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { cloneDeep } from '../utils/JsonUtils'; +import { type IdObjectSkeletonInterface, type PagedResult } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; +import { type AmServiceType } from './ServiceApi'; const circleOfTrustByIdURLTemplate = '%s/json%s/realm-config/federation/circlesoftrust/%s'; @@ -18,6 +20,12 @@ const getApiConfig = () => { }; }; +export type CircleOfTrustSkeleton = IdObjectSkeletonInterface & { + status?: string; + trustedProviders?: string[]; + _type?: AmServiceType; +}; + /** * Get all circles of trust * @returns {Promise>} a promise that resolves to an array of circles of trust objects diff --git a/src/api/IdmConfigApi.ts b/src/api/IdmConfigApi.ts index 1ee4619c8..54cdea8a0 100644 --- a/src/api/IdmConfigApi.ts +++ b/src/api/IdmConfigApi.ts @@ -1,12 +1,13 @@ import util from 'util'; -import { generateIdmApi } from './BaseApi'; -import { getHostBaseUrl } from '../utils/ForgeRockUtils'; + import { State } from '../shared/State'; +import { getHostBaseUrl } from '../utils/ForgeRockUtils'; import { IdObjectSkeletonInterface, NoIdObjectSkeletonInterface, PagedResult, } from './ApiTypes'; +import { generateIdmApi } from './BaseApi'; const idmAllConfigURLTemplate = '%s/openidm/config'; const idmConfigURLTemplate = '%s/openidm/config/%s'; diff --git a/src/api/IdmSystemApi.ts b/src/api/IdmSystemApi.ts index 34a6d2b70..c50fbb3f3 100644 --- a/src/api/IdmSystemApi.ts +++ b/src/api/IdmSystemApi.ts @@ -1,7 +1,8 @@ import util from 'util'; -import { generateIdmApi } from './BaseApi'; -import { getHostBaseUrl } from '../utils/ForgeRockUtils'; + import { State } from '../shared/State'; +import { getHostBaseUrl } from '../utils/ForgeRockUtils'; +import { generateIdmApi } from './BaseApi'; const testConnectorServersURLTemplate = '%s/openidm/system?_action=testConnectorServers'; diff --git a/src/api/ManagedObjectApi.ts b/src/api/ManagedObjectApi.ts index d0494f7d6..75df38ddd 100644 --- a/src/api/ManagedObjectApi.ts +++ b/src/api/ManagedObjectApi.ts @@ -1,8 +1,9 @@ import util from 'util'; -import { generateIdmApi } from './BaseApi'; -import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; + import { State } from '../shared/State'; import { getHostBaseUrl } from '../utils/ForgeRockUtils'; +import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; +import { generateIdmApi } from './BaseApi'; const createManagedObjectURLTemplate = '%s/openidm/managed/%s?_action=create'; const managedObjectByIdURLTemplate = '%s/openidm/managed/%s/%s'; diff --git a/src/api/NodeApi.ts b/src/api/NodeApi.ts index 08358887c..a0d799ae6 100644 --- a/src/api/NodeApi.ts +++ b/src/api/NodeApi.ts @@ -1,9 +1,16 @@ import util from 'util'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { deleteDeepByKey } from '../utils/JsonUtils'; +import { + type IdObjectSkeletonInterface, + type NoIdObjectSkeletonInterface, + type PagedResult, + type QueryResult, +} from './ApiTypes'; import { generateAmApi } from './BaseApi'; -import { State } from '../shared/State'; -import { NoIdObjectSkeletonInterface, NodeSkeleton } from './ApiTypes'; +import { type AmServiceType } from './ServiceApi'; const queryAllNodeTypesURLTemplate = '%s/json%s/realm-config/authentication/authenticationtrees/nodes?_action=getAllTypes'; @@ -13,6 +20,8 @@ const queryAllNodesURLTemplate = '%s/json%s/realm-config/authentication/authenticationtrees/nodes?_action=nextdescendents'; const nodeURLTemplate = '%s/json%s/realm-config/authentication/authenticationtrees/nodes/%s/%s'; +const createNodeURLTemplate = + '%s/json%s/realm-config/authentication/authenticationtrees/nodes/%s?_action=create'; const apiVersion = 'protocol=2.1,resource=1.0'; const getNodeApiConfig = () => { @@ -21,11 +30,47 @@ const getNodeApiConfig = () => { }; }; +export interface NodeRefSkeletonInterface { + connections: Record; + displayName: string; + nodeType: string; + x: number; + y: number; +} + +export interface InnerNodeRefSkeletonInterface { + _id: string; + displayName: string; + nodeType: string; +} + +export type NodeSkeleton = IdObjectSkeletonInterface & { + _type: AmServiceType; + nodes?: InnerNodeRefSkeletonInterface[]; + tree?: string; + identityResource?: string; +}; + +export type NodeTypeSkeleton = IdObjectSkeletonInterface & { + name: string; + collection: boolean; + tags: string[]; + metadata: { + tags: string[]; + [k: string]: string | number | boolean | string[]; + }; + help: string; +}; + /** * Get all node types - * @returns {Promise} a promise that resolves to an array of node type objects + * @returns {Promise>} a promise that resolves to an array of node type objects */ -export async function getNodeTypes({ state }: { state: State }) { +export async function getNodeTypes({ + state, +}: { + state: State; +}): Promise> { const urlString = util.format( queryAllNodeTypesURLTemplate, state.getHost(), @@ -49,7 +94,11 @@ export async function getNodeTypes({ state }: { state: State }) { * Get all nodes * @returns {Promise} a promise that resolves to an object containing an array of node objects */ -export async function getNodes({ state }: { state: State }) { +export async function getNodes({ + state, +}: { + state: State; +}): Promise> { const urlString = util.format( queryAllNodesURLTemplate, state.getHost(), @@ -72,7 +121,7 @@ export async function getNodes({ state }: { state: State }) { /** * Get all nodes by type * @param {string} nodeType node type - * @returns {Promise} a promise that resolves to an object containing an array of node objects of the requested type + * @returns {Promise>} a promise that resolves to an object containing an array of node objects of the requested type */ export async function getNodesByType({ nodeType, @@ -80,7 +129,7 @@ export async function getNodesByType({ }: { nodeType: string; state: State; -}) { +}): Promise> { const urlString = util.format( queryAllNodesByTypeURLTemplate, state.getHost(), @@ -98,9 +147,9 @@ export async function getNodesByType({ /** * Get node by uuid and type - * @param {String} nodeId node uuid - * @param {String} nodeType node type - * @returns {Promise} a promise that resolves to a node object + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @returns {Promise} a promise that resolves to a node object */ export async function getNode({ nodeId, @@ -127,6 +176,37 @@ export async function getNode({ return data; } +/** + * Create node by type + * @param {string} nodeType node type + * @param {object} nodeData node object + * @returns {Promise} a promise that resolves to a node object + */ +export async function createNode({ + nodeType, + nodeData, + state, +}: { + nodeType: string; + nodeData: NodeSkeleton; + state: State; +}): Promise { + const urlString = util.format( + createNodeURLTemplate, + state.getHost(), + getCurrentRealmPath(state), + nodeType + ); + const { data } = await generateAmApi({ + resource: getNodeApiConfig(), + state, + }).post(urlString, nodeData, { + withCredentials: true, + headers: { 'Accept-Encoding': 'gzip, deflate, br' }, + }); + return data; +} + /** * Put node by uuid and type * @param {string} nodeId node uuid diff --git a/src/api/OAuth2ClientApi.ts b/src/api/OAuth2ClientApi.ts index fed56f65c..b3b8459ac 100644 --- a/src/api/OAuth2ClientApi.ts +++ b/src/api/OAuth2ClientApi.ts @@ -1,13 +1,17 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { deleteDeepByKey } from '../utils/JsonUtils'; -import { State } from '../shared/State'; import { - NoIdObjectSkeletonInterface, - OAuth2ClientSkeleton, - PagedResult, + type IdObjectSkeletonInterface, + type NoIdObjectSkeletonInterface, + type PagedResult, + type ReadableStrings, + type WritableStrings, } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; +import { AmServiceType } from './ServiceApi'; const oauth2ClientURLTemplate = '%s/json%s/realm-config/agents/OAuth2Client/%s'; const oauth2ClientListURLTemplate = @@ -19,6 +23,47 @@ const getApiConfig = () => { }; }; +export type OAuth2ClientSkeleton = IdObjectSkeletonInterface & { + overrideOAuth2ClientConfig?: { + [k: string]: string | number | boolean | string[] | object | undefined; + }; + advancedOAuth2ClientConfig?: { + descriptions: { + inherited: boolean; + value: string[]; + }; + grantTypes?: ReadableStrings | WritableStrings; + [k: string]: string | number | boolean | string[] | object | undefined; + }; + signEncOAuth2ClientConfig?: { + [k: string]: string | number | boolean | string[] | object | undefined; + }; + coreOpenIDClientConfig?: { + [k: string]: string | number | boolean | string[] | object | undefined; + }; + coreOAuth2ClientConfig?: { + userpassword?: string; + clientName?: { + inherited: boolean; + value: string[]; + }; + accessTokenLifetime?: { + inherited: boolean; + value: number; + }; + scopes?: ReadableStrings | WritableStrings; + defaultScopes?: { + value: string[]; + [k: string]: string | number | boolean | string[] | object | undefined; + }; + [k: string]: string | number | boolean | string[] | object | undefined; + }; + coreUmaClientConfig?: { + [k: string]: string | number | boolean | string[] | object | undefined; + }; + _type: AmServiceType; +}; + /** * Get OAuth2 Clients * @returns {Promise} a promise that resolves to a PagedResults object containing an array of oauth2client objects diff --git a/src/api/OAuth2OIDCApi.ts b/src/api/OAuth2OIDCApi.ts index af8b287d8..fb7972fcd 100644 --- a/src/api/OAuth2OIDCApi.ts +++ b/src/api/OAuth2OIDCApi.ts @@ -1,10 +1,11 @@ -import util from 'util'; -import qs from 'qs'; -import { generateOauth2Api } from './BaseApi'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; -import { encode } from '../utils/Base64Utils'; import { AxiosRequestConfig } from 'axios'; +import qs from 'qs'; +import util from 'util'; + import { State } from '../shared/State'; +import { encode } from '../utils/Base64Utils'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { generateOauth2Api } from './BaseApi'; const authorizeUrlTemplate = '%s/oauth2%s/authorize'; const accessTokenUrlTemplate = '%s/oauth2%s/access_token'; diff --git a/src/api/OAuth2ProviderApi.ts b/src/api/OAuth2ProviderApi.ts index e6780b2f6..815c7a58b 100644 --- a/src/api/OAuth2ProviderApi.ts +++ b/src/api/OAuth2ProviderApi.ts @@ -1,9 +1,11 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; + import { State } from '../shared/State'; -import { AmServiceType, IdObjectSkeletonInterface } from './ApiTypes'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { cloneDeep } from '../utils/JsonUtils'; +import { type IdObjectSkeletonInterface } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; +import { type AmServiceType } from './ServiceApi'; const oAuth2ProviderServiceURLTemplate = '%s/json%s/realm-config/services/oauth-oidc'; diff --git a/src/api/PoliciesApi.ts b/src/api/PoliciesApi.ts index 3302dd843..f2bd1a4e7 100644 --- a/src/api/PoliciesApi.ts +++ b/src/api/PoliciesApi.ts @@ -1,8 +1,12 @@ import util from 'util'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { + type IdObjectSkeletonInterface, + type NoIdObjectSkeletonInterface, +} from './ApiTypes'; import { generateAmApi } from './BaseApi'; -import { State } from '../shared/State'; -import { PolicySkeleton } from './ApiTypes'; // const queryAllPoliciesByApplicationURLTemplate = // '%s/json%s/policies?_sortKeys=name&_queryFilter=applicationName+eq+%22%s%22'; @@ -18,6 +22,41 @@ const getApiConfig = () => { }; }; +export type PolicyConditionType = + | 'Script' + | 'AMIdentityMembership' + | 'IPv6' + | 'IPv4' + | 'SimpleTime' + | 'LEAuthLevel' + | 'LDAPFilter' + | 'AuthScheme' + | 'Session' + | 'AND' + | 'AuthenticateToRealm' + | 'ResourceEnvIP' + | 'Policy' + | 'OAuth2Scope' + | 'SessionProperty' + | 'OR' + | 'Transaction' + | 'NOT' + | 'AuthLevel' + | 'AuthenticateToService'; + +export type PolicyCondition = NoIdObjectSkeletonInterface & { + type: PolicyConditionType; + condition?: PolicyCondition; + conditions?: PolicyCondition[]; +}; + +export type PolicySkeleton = IdObjectSkeletonInterface & { + name: string; + applicationName: string; + condition?: PolicyCondition; + resourceTypeUuid: string; +}; + /** * Get all policies * @returns {Promise} a promise that resolves to an object containing an array of policy objects diff --git a/src/api/PolicySetApi.ts b/src/api/PolicySetApi.ts index 9bb9614d0..4a832d00d 100644 --- a/src/api/PolicySetApi.ts +++ b/src/api/PolicySetApi.ts @@ -1,9 +1,10 @@ import util from 'util'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; -import { generateAmApi } from './BaseApi'; + import { State } from '../shared/State'; -import { PolicySetSkeleton } from './ApiTypes'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { cloneDeep } from '../utils/JsonUtils'; +import { type NoIdObjectSkeletonInterface } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; const queryAllPolicySetURLTemplate = '%s/json%s/applications?_sortKeys=name&_queryFilter=name+eq+%22%5E(%3F!sunAMDelegationService%24).*%22'; @@ -17,6 +18,11 @@ const getApiConfig = () => { }; }; +export type PolicySetSkeleton = NoIdObjectSkeletonInterface & { + name: string; + resourceTypeUuids: string[]; +}; + /** * Get all policy sets * @returns {Promise} a promise that resolves to an object containing an array of policy set objects diff --git a/src/api/RealmApi.ts b/src/api/RealmApi.ts index 4897ed0b5..17c947264 100644 --- a/src/api/RealmApi.ts +++ b/src/api/RealmApi.ts @@ -1,8 +1,9 @@ import util from 'util'; -import { getHostBaseUrl } from '../utils/ForgeRockUtils'; -import { generateAmApi } from './BaseApi'; + import { State } from '../shared/State'; +import { getHostBaseUrl } from '../utils/ForgeRockUtils'; import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; const realmsListURLTemplate = '%s/json/global-config/realms/?_queryFilter=true'; const realmURLTemplate = '%s/json/global-config/realms/%s'; diff --git a/src/api/ResourceTypesApi.ts b/src/api/ResourceTypesApi.ts index 86488a1fa..17bf7bfc8 100644 --- a/src/api/ResourceTypesApi.ts +++ b/src/api/ResourceTypesApi.ts @@ -1,8 +1,9 @@ import util from 'util'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { type NoIdObjectSkeletonInterface } from './ApiTypes'; import { generateAmApi } from './BaseApi'; -import { State } from '../shared/State'; -import { ResourceTypeSkeleton } from './ApiTypes'; const queryAllResourceTypesURLTemplate = '%s/json%s/resourcetypes?_sortKeys=name&_queryFilter=name+eq+%22%5E(%3F!Delegation%20Service%24).*%22'; @@ -18,6 +19,11 @@ const getApiConfig = () => { }; }; +export type ResourceTypeSkeleton = NoIdObjectSkeletonInterface & { + uuid: string; + name: string; +}; + /** * Get all resource types * @returns {Promise} a promise that resolves to an object containing an array of resource type objects diff --git a/src/api/Saml2Api.ts b/src/api/Saml2Api.ts index c08fb5c04..5e237b212 100644 --- a/src/api/Saml2Api.ts +++ b/src/api/Saml2Api.ts @@ -1,9 +1,10 @@ import util from 'util'; + import { State } from '../shared/State'; -import { generateAmApi } from './BaseApi'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { cloneDeep } from '../utils/JsonUtils'; import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; const providerByLocationAndIdURLTemplate = '%s/json%s/realm-config/saml2/%s/%s'; const createHostedProviderURLTemplate = diff --git a/src/api/ScriptApi.ts b/src/api/ScriptApi.ts index 01038bfe7..cc380e5c7 100644 --- a/src/api/ScriptApi.ts +++ b/src/api/ScriptApi.ts @@ -1,8 +1,9 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; -import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; + import { State } from '../shared/State'; -import { PagedResult, ScriptSkeleton } from './ApiTypes'; +import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { type IdObjectSkeletonInterface, type PagedResult } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; const scriptURLTemplate = '%s/json%s/scripts/%s'; const scriptListURLTemplate = '%s/json%s/scripts?_queryFilter=true'; @@ -15,6 +16,37 @@ const getApiConfig = () => { }; }; +export type ScriptLanguage = 'GROOVY' | 'JAVASCRIPT'; + +export type ScriptContext = + | 'OAUTH2_ACCESS_TOKEN_MODIFICATION' + | 'AUTHENTICATION_CLIENT_SIDE' + | 'AUTHENTICATION_TREE_DECISION_NODE' + | 'AUTHENTICATION_SERVER_SIDE' + | 'SOCIAL_IDP_PROFILE_TRANSFORMATION' + | 'OAUTH2_VALIDATE_SCOPE' + | 'CONFIG_PROVIDER_NODE' + | 'OAUTH2_AUTHORIZE_ENDPOINT_DATA_PROVIDER' + | 'OAUTH2_EVALUATE_SCOPE' + | 'POLICY_CONDITION' + | 'OIDC_CLAIMS' + | 'SAML2_IDP_ADAPTER' + | 'SAML2_IDP_ATTRIBUTE_MAPPER' + | 'OAUTH2_MAY_ACT'; + +export type ScriptSkeleton = IdObjectSkeletonInterface & { + name: string; + description: string; + default: boolean; + script: string | string[]; + language: ScriptLanguage; + context: ScriptContext; + createdBy: string; + creationDate: number; + lastModifiedBy: string; + lastModifiedDate: number; +}; + /** * Get all scripts * @returns {Promise} a promise that resolves to an object containing an array of script objects diff --git a/src/api/ServerInfoApi.ts b/src/api/ServerInfoApi.ts index 8ae413e12..d694de764 100644 --- a/src/api/ServerInfoApi.ts +++ b/src/api/ServerInfoApi.ts @@ -1,6 +1,7 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; + import { State } from '../shared/State'; +import { generateAmApi } from './BaseApi'; const serverInfoUrlTemplate = '%s/json/serverinfo/%s'; diff --git a/src/api/ServiceApi.ts b/src/api/ServiceApi.ts index 618bd3ee6..cba8172bb 100644 --- a/src/api/ServiceApi.ts +++ b/src/api/ServiceApi.ts @@ -1,13 +1,9 @@ import util from 'util'; + import { State } from '../shared/State'; -import { - AmServiceSkeleton, - PagedResult, - ServiceNextDescendent, - ServiceNextDescendentResponse, -} from './ApiTypes'; -import { generateAmApi } from './BaseApi'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; const serviceURLTemplate = '%s/json%s/%s/services/%s'; const serviceURLNextDescendentsTemplate = @@ -37,16 +33,28 @@ export interface ServiceListItem { _rev: string; } -// export interface AmService { -// _id: ''; -// _rev: string; -// _type: { -// _id: string; -// name: string; -// collection: boolean; -// }; -// [key: string]: any; -// } +export type AmServiceType = IdObjectSkeletonInterface & { + name: string; +}; + +export type AmServiceSkeleton = IdObjectSkeletonInterface & { + _type: AmServiceType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +}; + +export interface ServiceNextDescendentResponse { + result: ServiceNextDescendent; +} + +export interface ServiceNextDescendent { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +export interface FullService extends AmServiceSkeleton { + nextDescendents?: ServiceNextDescendent[]; +} /** * Helper function to get the realm path required for the API call considering if the request diff --git a/src/api/SocialIdentityProvidersApi.ts b/src/api/SocialIdentityProvidersApi.ts index 32a15914e..66a1584bd 100644 --- a/src/api/SocialIdentityProvidersApi.ts +++ b/src/api/SocialIdentityProvidersApi.ts @@ -1,14 +1,15 @@ import util from 'util'; -import { generateAmApi } from './BaseApi'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; import { deleteDeepByKey } from '../utils/JsonUtils'; -import { State } from '../shared/State'; import { - AmServiceType, - IdObjectSkeletonInterface, - NoIdObjectSkeletonInterface, - PagedResult, + type IdObjectSkeletonInterface, + type NoIdObjectSkeletonInterface, + type PagedResult, } from './ApiTypes'; +import { generateAmApi } from './BaseApi'; +import { type AmServiceType } from './ServiceApi'; const getAllProviderTypesURLTemplate = '%s/json%s/realm-config/services/SocialIdentityProviders?_action=getAllTypes'; diff --git a/src/api/TreeApi.ts b/src/api/TreeApi.ts index 99a6e6551..fe5daee20 100644 --- a/src/api/TreeApi.ts +++ b/src/api/TreeApi.ts @@ -1,8 +1,10 @@ import util from 'util'; + +import { State } from '../shared/State'; import { getCurrentRealmPath } from '../utils/ForgeRockUtils'; +import type { IdObjectSkeletonInterface } from './ApiTypes'; import { generateAmApi } from './BaseApi'; -import { State } from '../shared/State'; -import { TreeSkeleton } from './ApiTypes'; +import { type NodeRefSkeletonInterface } from './NodeApi'; const treeByIdURLTemplate = '%s/json%s/realm-config/authentication/authenticationtrees/trees/%s'; @@ -16,6 +18,18 @@ const getTreeApiConfig = () => { }; }; +export interface UiConfigInterface { + categories: string; +} + +export type TreeSkeleton = IdObjectSkeletonInterface & { + entryNodeId: string; + nodes: Record; + identityResource?: string; + uiConfig?: UiConfigInterface; + enabled?: boolean; +}; + /** * Get all trees * @returns {Promise} a promise that resolves to an array of tree objects diff --git a/src/api/cloud/AdminFederationProvidersApi.ts b/src/api/cloud/AdminFederationProvidersApi.ts index f29e6caa6..e3deba4c3 100644 --- a/src/api/cloud/AdminFederationProvidersApi.ts +++ b/src/api/cloud/AdminFederationProvidersApi.ts @@ -1,10 +1,12 @@ import util from 'util'; -import { generateAmApi } from '../BaseApi'; + +import { State } from '../../shared/State'; import { getRealmPath } from '../../utils/ForgeRockUtils'; import { deleteDeepByKey } from '../../utils/JsonUtils'; -import { State } from '../../shared/State'; -import { AmServiceType, PagedResult } from '../ApiTypes'; -import { SocialIdpSkeleton } from '../SocialIdentityProvidersApi'; +import { type IdObjectSkeletonInterface, type PagedResult } from '../ApiTypes'; +import { generateAmApi } from '../BaseApi'; +import { type AmServiceType } from '../ServiceApi'; +import { type SocialIdpSkeleton } from '../SocialIdentityProvidersApi'; const getAllProviderTypesURLTemplate = '%s/json%s/realm-config/services/SocialIdentityProviders?_action=getAllTypes'; @@ -23,6 +25,16 @@ const getApiConfig = () => { }; }; +export type AdminFederationConfigSkeleton = IdObjectSkeletonInterface & { + groups: { + claim: string; + mappings: { + 'super-admins': string[]; + 'tenant-admins': string[]; + }; + }; +}; + /** * Get admin federation provider types * @returns {Promise} a promise that resolves to an object containing an array of admin federation provider types diff --git a/src/api/cloud/EnvInfoApi.ts b/src/api/cloud/EnvInfoApi.ts index c7be78c03..357bf011b 100644 --- a/src/api/cloud/EnvInfoApi.ts +++ b/src/api/cloud/EnvInfoApi.ts @@ -1,7 +1,8 @@ import util from 'util'; + +import { State } from '../../shared/State'; import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; import { generateAmApi } from '../BaseApi'; -import { State } from '../../shared/State'; const envInfoURLTemplate = '%s/environment/info'; diff --git a/src/api/cloud/FeatureApi.ts b/src/api/cloud/FeatureApi.ts index bb51f5707..ac545485e 100644 --- a/src/api/cloud/FeatureApi.ts +++ b/src/api/cloud/FeatureApi.ts @@ -1,8 +1,9 @@ import util from 'util'; + +import { IdObjectSkeletonInterface } from '../../api/ApiTypes'; +import { State } from '../../shared/State'; import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; import { generateAmApi } from '../BaseApi'; -import { State } from '../../shared/State'; -import { IdObjectSkeletonInterface } from '../../api/ApiTypes'; const envInfoURLTemplate = '%s/feature?_queryFilter=true'; diff --git a/src/api/cloud/LogApi.ts b/src/api/cloud/LogApi.ts index ea9ecd231..aa19d83db 100644 --- a/src/api/cloud/LogApi.ts +++ b/src/api/cloud/LogApi.ts @@ -1,8 +1,12 @@ import util from 'util'; -import { generateLogApi, generateLogKeysApi } from '../BaseApi'; -import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; + import { State } from '../../shared/State'; -import { LogApiKey, LogEventSkeleton, PagedResult } from '../ApiTypes'; +import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; +import { + type NoIdObjectSkeletonInterface, + type PagedResult, +} from '../ApiTypes'; +import { generateLogApi, generateLogKeysApi } from '../BaseApi'; const logsTailURLTemplate = '%s/monitoring/logs/tail?source=%s'; const logsFetchURLTemplate = @@ -12,6 +16,33 @@ const logsCreateAPIKeyAndSecretURLTemplate = '%s/keys?_action=create'; const logsGetAPIKeysURLTemplate = '%s/keys'; const logsAPIKeyURLTemplate = '%s/keys/%s'; +export type LogApiKey = { + name: string; + api_key_id: string; + api_key_secret?: string; + created_at: string; +}; + +export type LogEventPayloadSkeleton = NoIdObjectSkeletonInterface & { + context: string; + level: string; + logger: string; + mdc: { + transactionId: string; + }; + message: string; + thread: string; + timestamp: string; + transactionId: string; +}; + +export type LogEventSkeleton = NoIdObjectSkeletonInterface & { + payload: string | LogEventPayloadSkeleton; + timestamp: string; + type: string; + source: string; +}; + /** * Get log API key * @returns {Promise>} a promise resolving to a log api key object diff --git a/src/api/cloud/SecretsApi.ts b/src/api/cloud/SecretsApi.ts index c0fbd54d4..2d48f1a45 100644 --- a/src/api/cloud/SecretsApi.ts +++ b/src/api/cloud/SecretsApi.ts @@ -1,9 +1,10 @@ import util from 'util'; + +import { State } from '../../shared/State'; import { encode } from '../../utils/Base64Utils'; import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; -import { generateEnvApi } from '../BaseApi'; -import { State } from '../../shared/State'; import { IdObjectSkeletonInterface, PagedResult } from '../ApiTypes'; +import { generateEnvApi } from '../BaseApi'; const secretsListURLTemplate = '%s/environment/secrets'; const secretListVersionsURLTemplate = '%s/environment/secrets/%s/versions'; diff --git a/src/api/cloud/StartupApi.ts b/src/api/cloud/StartupApi.ts index c1babe7f4..c7c5e5a92 100644 --- a/src/api/cloud/StartupApi.ts +++ b/src/api/cloud/StartupApi.ts @@ -1,7 +1,8 @@ import util from 'util'; + +import { State } from '../../shared/State'; import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; import { generateEnvApi } from '../BaseApi'; -import { State } from '../../shared/State'; const startupURLTemplate = '%s/environment/startup'; const startupInitiateRestartURLTemplate = `${startupURLTemplate}?_action=restart`; diff --git a/src/api/cloud/VariablesApi.ts b/src/api/cloud/VariablesApi.ts index fcc051f21..138c3e450 100644 --- a/src/api/cloud/VariablesApi.ts +++ b/src/api/cloud/VariablesApi.ts @@ -1,9 +1,10 @@ import util from 'util'; + +import { State } from '../../shared/State'; import { encode } from '../../utils/Base64Utils'; import { getHostBaseUrl } from '../../utils/ForgeRockUtils'; -import { generateEnvApi } from '../BaseApi'; -import { State } from '../../shared/State'; import { IdObjectSkeletonInterface, PagedResult } from '../ApiTypes'; +import { generateEnvApi } from '../BaseApi'; const variablesListURLTemplate = '%s/environment/variables'; const variableURLTemplate = '%s/environment/variables/%s'; diff --git a/src/lib/FrodoLib.ts b/src/lib/FrodoLib.ts index 1beed530e..b373d90fa 100644 --- a/src/lib/FrodoLib.ts +++ b/src/lib/FrodoLib.ts @@ -1,25 +1,29 @@ // instantiable modules -import StateImpl, { State, StateInterface } from '../shared/State'; -import AdminFederationOps, { - AdminFederation, -} from '../ops/cloud/AdminFederationOps'; import AdminOps, { Admin } from '../ops/AdminOps'; import AgentOps, { Agent } from '../ops/AgentOps'; import ApplicationOps, { Application } from '../ops/ApplicationOps'; import AuthenticateOps, { Authenticate } from '../ops/AuthenticateOps'; import CirclesOfTrustOps, { CirclesOfTrust } from '../ops/CirclesOfTrustOps'; +import AdminFederationOps, { + AdminFederation, +} from '../ops/cloud/AdminFederationOps'; +import FeatureOps, { Feature } from '../ops/cloud/FeatureOps'; +import LogOps, { Log } from '../ops/cloud/LogOps'; +import SecretsOps, { Secret } from '../ops/cloud/SecretsOps'; +import ServiceAccountOps, { + ServiceAccount, +} from '../ops/cloud/ServiceAccountOps'; +import StartupOps, { Startup } from '../ops/cloud/StartupOps'; +import VariablesOps, { Variable } from '../ops/cloud/VariablesOps'; import ConnectionProfileOps, { ConnectionProfile, } from '../ops/ConnectionProfileOps'; import EmailTemplateOps, { EmailTemplate } from '../ops/EmailTemplateOps'; -import ExportImportUtils, { ExportImport } from '../utils/ExportImportUtils'; -import FeatureOps, { Feature } from '../ops/cloud/FeatureOps'; import IdmConfigOps, { IdmConfig } from '../ops/IdmConfigOps'; import IdpOps, { Idp } from '../ops/IdpOps'; import InfoOps, { Info } from '../ops/InfoOps'; import JoseOps, { Jose } from '../ops/JoseOps'; import JourneyOps, { Journey } from '../ops/JourneyOps'; -import LogOps, { Log } from '../ops/cloud/LogOps'; import ManagedObjectOps, { ManagedObject } from '../ops/ManagedObjectOps'; import NodeOps, { Node } from '../ops/NodeOps'; import OAuth2ClientOps, { OAuth2Client } from '../ops/OAuth2ClientOps'; @@ -32,24 +36,19 @@ import RealmOps, { Realm } from '../ops/RealmOps'; import ResourceTypeOps, { ResourceType } from '../ops/ResourceTypeOps'; import Saml2Ops, { Saml2 } from '../ops/Saml2Ops'; import ScriptOps, { Script } from '../ops/ScriptOps'; -import ScriptValidationUtils, { - ScriptValidation, -} from '../utils/ScriptValidationUtils'; import ServiceOps, { Service } from '../ops/ServiceOps'; -import SecretsOps, { Secret } from '../ops/cloud/SecretsOps'; -import ServiceAccountOps, { - ServiceAccount, -} from '../ops/cloud/ServiceAccountOps'; -import StartupOps, { Startup } from '../ops/cloud/StartupOps'; import ThemeOps, { Theme } from '../ops/ThemeOps'; -import VariablesOps, { Variable } from '../ops/cloud/VariablesOps'; import VersionUtils, { Version } from '../ops/VersionUtils'; - // non-instantiable modules import ConstantsImpl, { Constants } from '../shared/Constants'; -import ForgeRockUtils, { FRUtils } from '../utils/ForgeRockUtils'; +import StateImpl, { State, StateInterface } from '../shared/State'; import Base64Utils, { Base64 } from '../utils/Base64Utils'; +import ExportImportUtils, { ExportImport } from '../utils/ExportImportUtils'; +import ForgeRockUtils, { FRUtils } from '../utils/ForgeRockUtils'; import JsonUtils, { Json } from '../utils/JsonUtils'; +import ScriptValidationUtils, { + ScriptValidation, +} from '../utils/ScriptValidationUtils'; /** * Frodo Library @@ -423,6 +422,6 @@ const frodo = FrodoLib(); */ const state = frodo.state; -export { frodo, state, FrodoLib }; +export { frodo, FrodoLib, state }; export default FrodoLib; diff --git a/src/ops/AdminOps.ts b/src/ops/AdminOps.ts index 6343ec233..ef5ecfe5a 100644 --- a/src/ops/AdminOps.ts +++ b/src/ops/AdminOps.ts @@ -1,25 +1,23 @@ import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { type ReadableStrings, type WritableStrings } from '../api/ApiTypes'; +import { putSecret } from '../api/cloud/SecretsApi'; +import { getConfigEntity, putConfigEntity } from '../api/IdmConfigApi'; +import { type OAuth2ClientSkeleton } from '../api/OAuth2ClientApi'; +import { clientCredentialsGrant } from '../api/OAuth2OIDCApi'; import { - readOAuth2Clients, readOAuth2Client, + readOAuth2Clients, updateOAuth2Client, } from '../ops/OAuth2ClientOps'; -import { getConfigEntity, putConfigEntity } from '../api/IdmConfigApi'; -import { get, isEqualJson } from '../utils/JsonUtils'; -import { getCurrentRealmManagedUser } from '../utils/ForgeRockUtils'; -import { getRealmManagedOrganization } from './OrganizationOps'; import { readOAuth2Provider } from '../ops/OAuth2ProviderOps'; -import { putSecret } from '../api/cloud/SecretsApi'; -import { clientCredentialsGrant } from '../api/OAuth2OIDCApi'; -import { printMessage } from '../utils/Console'; -import path from 'path'; -import { fileURLToPath } from 'url'; import { State } from '../shared/State'; -import { - OAuth2ClientSkeleton, - ReadableStrings, - WritableStrings, -} from '../api/ApiTypes'; +import { printMessage } from '../utils/Console'; +import { getCurrentRealmManagedUser } from '../utils/ForgeRockUtils'; +import { get, isEqualJson } from '../utils/JsonUtils'; +import { getRealmManagedOrganization } from './OrganizationOps'; export type Admin = { listOAuth2CustomClients(): Promise; diff --git a/src/ops/AgentOps.ts b/src/ops/AgentOps.ts index 8561242fd..8c408dabd 100644 --- a/src/ops/AgentOps.ts +++ b/src/ops/AgentOps.ts @@ -1,16 +1,17 @@ -import { debugMessage, printMessage } from '../utils/Console'; import { - getAgentsByType, - getAgentByTypeAndId as _getAgentByTypeAndId, - putAgentByTypeAndId, - findAgentById, + type AgentSkeleton, + type AgentType, deleteAgentByTypeAndId, + findAgentById, findAgentByTypeAndId, + getAgentByTypeAndId as _getAgentByTypeAndId, + getAgentsByType, + putAgentByTypeAndId, } from '../api/AgentApi'; -import { AgentSkeleton, AgentType } from '../api/ApiTypes'; -import { AgentExportInterface } from './OpsTypes'; -import { validateImport } from '../utils/ExportImportUtils'; import { State } from '../shared/State'; +import { debugMessage, printMessage } from '../utils/Console'; +import { validateImport } from '../utils/ExportImportUtils'; +import { type ExportMetaData } from './OpsTypes'; export type Agent = { /** @@ -638,6 +639,11 @@ export default (state: State): Agent => { }; }; +export interface AgentExportInterface { + meta?: Record; + agents: Record; +} + /** * Create an empty agent export template * @returns {AgentExportInterface} an empty agent export template diff --git a/src/ops/ApplicationOps.ts b/src/ops/ApplicationOps.ts index 70b84795b..eba144678 100644 --- a/src/ops/ApplicationOps.ts +++ b/src/ops/ApplicationOps.ts @@ -1,26 +1,24 @@ -import { - IdObjectSkeletonInterface, - OAuth2ClientSkeleton, - ScriptSkeleton, -} from '../api/ApiTypes'; +import { type IdObjectSkeletonInterface } from '../api/ApiTypes'; +import { type OAuth2ClientSkeleton } from '../api/OAuth2ClientApi'; +import { type ScriptSkeleton } from '../api/ScriptApi'; +import constants from '../shared/Constants'; import { State } from '../shared/State'; +import { decode } from '../utils/Base64Utils'; import { debugMessage } from '../utils/Console'; import { getMetadata } from '../utils/ExportImportUtils'; import { get, mergeDeep } from '../utils/JsonUtils'; +import { exportCirclesOfTrust } from './CirclesOfTrustOps'; import { createManagedObject, + deleteManagedObject, + queryManagedObjects, readManagedObject, readManagedObjects, updateManagedObject, - deleteManagedObject, - queryManagedObjects, } from './ManagedObjectOps'; import { exportOAuth2Client } from './OAuth2ClientOps'; -import { ExportMetaData } from './OpsTypes'; +import { type ExportMetaData } from './OpsTypes'; import { exportSaml2Provider } from './Saml2Ops'; -import constants from '../shared/Constants'; -import { decode } from '../utils/Base64Utils'; -import { exportCirclesOfTrust } from './CirclesOfTrustOps'; const defaultFields = [ 'authoritative', diff --git a/src/ops/AuthenticateOps.ts b/src/ops/AuthenticateOps.ts index e39523e58..fa5ab5a5b 100644 --- a/src/ops/AuthenticateOps.ts +++ b/src/ops/AuthenticateOps.ts @@ -1,19 +1,19 @@ -import url from 'url'; import { createHash, randomBytes } from 'crypto'; import readlineSync from 'readline-sync'; -import { encodeBase64Url } from '../utils/Base64Utils'; -import { State } from '../shared/State'; -import Constants from '../shared/Constants'; -import { getServerInfo, getServerVersionInfo } from '../api/ServerInfoApi'; +import url from 'url'; +import { v4 } from 'uuid'; + import { step } from '../api/AuthenticateApi'; import { accessToken, authorize } from '../api/OAuth2OIDCApi'; -import { getConnectionProfile } from './ConnectionProfileOps'; -import { v4 } from 'uuid'; -import { parseUrl } from '../utils/MiscUtils'; -import { JwkRsa, createSignedJwtToken } from './JoseOps'; -import { getServiceAccount } from './cloud/ServiceAccountOps'; -import { isValidUrl } from '../utils/MiscUtils'; +import { getServerInfo, getServerVersionInfo } from '../api/ServerInfoApi'; +import Constants from '../shared/Constants'; +import { State } from '../shared/State'; +import { encodeBase64Url } from '../utils/Base64Utils'; import { debugMessage, printMessage, verboseMessage } from '../utils/Console'; +import { isValidUrl, parseUrl } from '../utils/MiscUtils'; +import { getServiceAccount } from './cloud/ServiceAccountOps'; +import { getConnectionProfile } from './ConnectionProfileOps'; +import { createSignedJwtToken, JwkRsa } from './JoseOps'; export type Authenticate = { /** diff --git a/src/ops/CirclesOfTrustOps.ts b/src/ops/CirclesOfTrustOps.ts index d4a75c14c..ad6d32825 100644 --- a/src/ops/CirclesOfTrustOps.ts +++ b/src/ops/CirclesOfTrustOps.ts @@ -1,15 +1,17 @@ -import { debugMessage } from '../utils/Console'; import { - getCirclesOfTrust as _getCirclesOfTrust, - getCircleOfTrust as _getCircleOfTrust, + type CircleOfTrustSkeleton, createCircleOfTrust as _createCircleOfTrust, - updateCircleOfTrust as _updateCircleOfTrust, deleteCircleOfTrust as _deleteCircleOfTrust, + getCircleOfTrust as _getCircleOfTrust, + getCirclesOfTrust as _getCirclesOfTrust, + updateCircleOfTrust as _updateCircleOfTrust, } from '../api/CirclesOfTrustApi'; -import { getMetadata } from '../utils/ExportImportUtils'; +import { type Saml2ProviderSkeleton } from '../api/Saml2Api'; +import { type ScriptSkeleton } from '../api/ScriptApi'; import { State } from '../shared/State'; -import { CirclesOfTrustExportInterface } from './OpsTypes'; -import { CircleOfTrustSkeleton } from '../api/ApiTypes'; +import { debugMessage } from '../utils/Console'; +import { getMetadata } from '../utils/ExportImportUtils'; +import { type ExportMetaData } from './OpsTypes'; export type CirclesOfTrust = { /** @@ -157,6 +159,17 @@ export default (state: State): CirclesOfTrust => { }; }; +export interface CirclesOfTrustExportInterface { + meta?: ExportMetaData; + script: Record; + saml: { + hosted: Record; + remote: Record; + metadata: Record; + cot: Record; + }; +} + /** * Create an empty agent export template * @returns {CirclesOfTrustExportInterface} an empty agent export template diff --git a/src/ops/ConnectionProfileOps.ts b/src/ops/ConnectionProfileOps.ts index 045dfa32a..f4a54cf55 100644 --- a/src/ops/ConnectionProfileOps.ts +++ b/src/ops/ConnectionProfileOps.ts @@ -1,18 +1,19 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; -import DataProtection from '../utils/DataProtection'; -import { debugMessage, printMessage, verboseMessage } from '../utils/Console'; + +import { IdObjectSkeletonInterface } from '../api/ApiTypes'; import Constants from '../shared/Constants'; -import { createJwkRsa, createJwks, getJwkRsaPublic, JwkRsa } from './JoseOps'; +import { State } from '../shared/State'; +import { debugMessage, printMessage, verboseMessage } from '../utils/Console'; +import DataProtection from '../utils/DataProtection'; +import { saveJsonToFile } from '../utils/ExportImportUtils'; +import { isValidUrl } from '../utils/MiscUtils'; import { createServiceAccount, getServiceAccount, } from './cloud/ServiceAccountOps'; -import { IdObjectSkeletonInterface } from '../api/ApiTypes'; -import { saveJsonToFile } from '../utils/ExportImportUtils'; -import { isValidUrl } from '../utils/MiscUtils'; -import { State } from '../shared/State'; +import { createJwkRsa, createJwks, getJwkRsaPublic, JwkRsa } from './JoseOps'; export type ConnectionProfile = { /** diff --git a/src/ops/IdmConfigOps.ts b/src/ops/IdmConfigOps.ts index b9fb04e37..365a3df76 100644 --- a/src/ops/IdmConfigOps.ts +++ b/src/ops/IdmConfigOps.ts @@ -3,18 +3,18 @@ import { NoIdObjectSkeletonInterface, } from '../api/ApiTypes'; import { - IdmConfigStub, - getConfigStubs as _getConfigEntityStubs, - getConfigEntity as _getConfigEntity, - putConfigEntity as _putConfigEntity, - getConfigEntitiesByType as _getConfigEntitiesByType, + deleteConfigEntity as _deleteConfigEntity, getConfigEntities as _getConfigEntities, + getConfigEntitiesByType as _getConfigEntitiesByType, + getConfigEntity as _getConfigEntity, getConfigEntity, - deleteConfigEntity as _deleteConfigEntity, + getConfigStubs as _getConfigEntityStubs, + IdmConfigStub, + putConfigEntity as _putConfigEntity, } from '../api/IdmConfigApi'; import { - testConnectorServers as _testConnectorServers, ConnectorServerStatusInterface, + testConnectorServers as _testConnectorServers, } from '../api/IdmSystemApi'; import { State } from '../shared/State'; import { debugMessage } from '../utils/Console'; diff --git a/src/ops/IdpOps.ts b/src/ops/IdpOps.ts index 771f88592..4eeebac1f 100644 --- a/src/ops/IdpOps.ts +++ b/src/ops/IdpOps.ts @@ -1,26 +1,26 @@ +import { type NoIdObjectSkeletonInterface } from '../api/ApiTypes'; +import { getScript, type ScriptSkeleton } from '../api/ScriptApi'; import { deleteProviderByTypeAndId, getSocialIdentityProviders as _getSocialIdentityProviders, putProviderByTypeAndId as _putProviderByTypeAndId, - SocialIdpSkeleton, + type SocialIdpSkeleton, } from '../api/SocialIdentityProvidersApi'; -import { getScript } from '../api/ScriptApi'; -import { updateScript } from './ScriptOps'; -import { - convertBase64TextToArray, - convertTextArrayToBase64, -} from '../utils/ExportImportUtils'; +import { State } from '../shared/State'; import { - printMessage, createProgressIndicator, - updateProgressIndicator, + debugMessage, + printMessage, stopProgressIndicator, + updateProgressIndicator, } from '../utils/Console'; -import { ExportMetaData } from './OpsTypes'; -import { NoIdObjectSkeletonInterface, ScriptSkeleton } from '../api/ApiTypes'; -import { getMetadata } from '../utils/ExportImportUtils'; -import { State } from '../shared/State'; -import { debugMessage } from '../utils/Console'; +import { + convertBase64TextToArray, + convertTextArrayToBase64, + getMetadata, +} from '../utils/ExportImportUtils'; +import { type ExportMetaData } from './OpsTypes'; +import { updateScript } from './ScriptOps'; export type Idp = { /** diff --git a/src/ops/InfoOps.ts b/src/ops/InfoOps.ts index f75d0b5f4..d16cdd0fe 100644 --- a/src/ops/InfoOps.ts +++ b/src/ops/InfoOps.ts @@ -1,8 +1,8 @@ -import { getEnvInfo, EnvInfoInterface } from '../api/cloud/EnvInfoApi'; -import Constants from '../shared/Constants'; +import { EnvInfoInterface, getEnvInfo } from '../api/cloud/EnvInfoApi'; import { getServerVersionInfo } from '../api/ServerInfoApi'; -import { getServiceAccount } from './cloud/ServiceAccountOps'; +import Constants from '../shared/Constants'; import { State } from '../shared/State'; +import { getServiceAccount } from './cloud/ServiceAccountOps'; export type Info = { /** diff --git a/src/ops/JoseOps.ts b/src/ops/JoseOps.ts index 5a1e9a6c1..525268456 100644 --- a/src/ops/JoseOps.ts +++ b/src/ops/JoseOps.ts @@ -1,4 +1,5 @@ import jose from 'node-jose'; + import { State } from '../shared/State'; export type Jose = { diff --git a/src/ops/JourneyOps.test.ts b/src/ops/JourneyOps.test.ts index 40b2224ca..7383c2231 100644 --- a/src/ops/JourneyOps.test.ts +++ b/src/ops/JourneyOps.test.ts @@ -59,7 +59,7 @@ state.setDeploymentType(Constants.CLOUD_DEPLOYMENT_TYPE_KEY); async function stageJourney(journey: { id: string }, create = true) { // delete if exists, then create try { - await JourneyOps.getJourney({ journeyId: journey.id, state }); + await JourneyOps.readJourney({ journeyId: journey.id, state }); await JourneyOps.deleteJourney({ journeyId: journey.id, options: { @@ -149,11 +149,11 @@ describe('JourneyOps', () => { ) { describe('getJourneys()', () => { test('0: Method is implemented', async () => { - expect(JourneyOps.getJourneys).toBeDefined(); + expect(JourneyOps.readJourneys).toBeDefined(); }); test('1: Get all journeys', async () => { - const journeys = await JourneyOps.getJourneys({ state }); + const journeys = await JourneyOps.readJourneys({ state }); expect(journeys).toMatchSnapshot(); }); }); @@ -165,7 +165,7 @@ describe('JourneyOps', () => { test(`1: Export journey '${journey3.id}' w/o dependencies`, async () => { const response = await JourneyOps.exportJourney({ - treeId: journey3.id, + journeyId: journey3.id, options: { useStringArrays: false, deps: false, @@ -179,7 +179,7 @@ describe('JourneyOps', () => { test(`2: Export journey '${journey3.id}' w/ dependencies`, async () => { const response = await JourneyOps.exportJourney({ - treeId: journey3.id, + journeyId: journey3.id, options: { useStringArrays: false, deps: true, diff --git a/src/ops/JourneyOps.ts b/src/ops/JourneyOps.ts index 0f298fe59..0a6429c45 100644 --- a/src/ops/JourneyOps.ts +++ b/src/ops/JourneyOps.ts @@ -1,47 +1,42 @@ -import { State } from '../shared/State'; import fs from 'fs'; import { v4 as uuidv4 } from 'uuid'; + import { - convertBase64TextToArray, - getTypedFilename, - convertTextArrayToBase64, - convertTextArrayToBase64Url, - findFilesByName, - getMetadata, -} from '../utils/ExportImportUtils'; -import { getCurrentRealmManagedUser } from '../utils/ForgeRockUtils'; + type CircleOfTrustSkeleton, + createCircleOfTrust, + updateCircleOfTrust, +} from '../api/CirclesOfTrustApi'; import { + deleteNode, getNode, + type InnerNodeRefSkeletonInterface, + type NodeRefSkeletonInterface, + type NodeSkeleton, putNode, - deleteNode, - getNodeTypes, - getNodesByType, } from '../api/NodeApi'; -import { isCloudOnlyNode, isCustomNode, isPremiumNode } from './NodeOps'; -import { getTrees, getTree, putTree, deleteTree } from '../api/TreeApi'; -import { readEmailTemplate, updateEmailTemplate } from './EmailTemplateOps'; -import { getScript } from '../api/ScriptApi'; -import Constants from '../shared/Constants'; -import { - printMessage, - createProgressIndicator, - updateProgressIndicator, - stopProgressIndicator, - debugMessage, -} from '../utils/Console'; import { + createProvider, getProvider, - getProviderStubs, getProviderMetadata, - createProvider, queryProviderStubs, + type Saml2ProviderSkeleton, updateProvider, } from '../api/Saml2Api'; +import { getScript, type ScriptSkeleton } from '../api/ScriptApi'; import { - createCircleOfTrust, - getCirclesOfTrust, - updateCircleOfTrust, -} from '../api/CirclesOfTrustApi'; + getSocialIdentityProviders, + putProviderByTypeAndId, + type SocialIdpSkeleton, +} from '../api/SocialIdentityProvidersApi'; +import { + deleteTree, + getTree, + getTrees, + putTree, + type TreeSkeleton, +} from '../api/TreeApi'; +import Constants from '../shared/Constants'; +import { State } from '../shared/State'; import { decode, encode, @@ -49,29 +44,39 @@ import { isBase64Encoded, } from '../utils/Base64Utils'; import { - getSocialIdentityProviders, - putProviderByTypeAndId, -} from '../api/SocialIdentityProvidersApi'; -import { readThemes, updateThemes } from './ThemeOps'; -import { updateScript } from './ScriptOps'; -import { - InnerNodeRefSkeletonInterface, - NodeRefSkeletonInterface, - NodeSkeleton, - ThemeSkeleton, - TreeSkeleton, -} from '../api/ApiTypes'; + createProgressIndicator, + debugMessage, + printMessage, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; import { - JourneyClassificationType, - TreeExportResolverInterface, - SingleTreeExportInterface, - MultiTreeExportInterface, - TreeDependencyMapInterface, - TreeExportOptions, - TreeImportOptions, - FrodoError, -} from './OpsTypes'; + convertBase64TextToArray, + convertTextArrayToBase64, + convertTextArrayToBase64Url, + findFilesByName, + getMetadata, + getTypedFilename, +} from '../utils/ExportImportUtils'; +import { getCurrentRealmManagedUser } from '../utils/ForgeRockUtils'; import { findInArray } from '../utils/JsonUtils'; +import { readCirclesOfTrust } from './CirclesOfTrustOps'; +import { + type EmailTemplateSkeleton, + readEmailTemplate, + updateEmailTemplate, +} from './EmailTemplateOps'; +import { + findOrphanedNodes as _findOrphanedNodes, + isCloudOnlyNode, + isCustomNode, + isPremiumNode, + removeOrphanedNodes as _removeOrphanedNodes, +} from './NodeOps'; +import { type ExportMetaData } from './OpsTypes'; +import { readSaml2ProviderStubs } from './Saml2Ops'; +import { updateScript } from './ScriptOps'; +import { readThemes, type ThemeSkeleton, updateThemes } from './ThemeOps'; export type Journey = { /** @@ -206,17 +211,6 @@ export type Journey = { resolveTreeExport: TreeExportResolverInterface, resolvedTreeIds?: string[] ): Promise; - /** - * Find all node configuration objects that are no longer referenced by any tree - * @returns {Promise} a promise that resolves to an array of orphaned nodes - */ - findOrphanedNodes(): Promise; - /** - * Remove orphaned nodes - * @param {NodeSkeleton[]} orphanedNodes Pass in an array of orphaned node configuration objects to remove - * @returns {Promise} a promise that resolves to an array nodes that encountered errors deleting - */ - removeOrphanedNodes(orphanedNodes: NodeSkeleton[]): Promise; /** * Analyze if a journey contains any custom nodes considering the detected or the overridden version. * @param {SingleTreeExportInterface} journey Journey/tree configuration object @@ -318,6 +312,21 @@ export type Journey = { importData: MultiTreeExportInterface, options: TreeImportOptions ): Promise; + /** + * Find all node configuration objects that are no longer referenced by any tree + * @returns {Promise} a promise that resolves to an array of orphaned nodes + * @deprecated since v2.0.0 use {@link Node.findOrphanedNodes | findOrphanedNodes} in the {@link Node} module instead + * @group Deprecated + */ + findOrphanedNodes(): Promise; + /** + * Remove orphaned nodes + * @param {NodeSkeleton[]} orphanedNodes Pass in an array of orphaned node configuration objects to remove + * @returns {Promise} a promise that resolves to an array nodes that encountered errors deleting + * @deprecated since v2.0.0 use {@link Node.removeOrphanedNodes | removeOrphanedNodes} in the {@link Node} module instead + * @group Deprecated + */ + removeOrphanedNodes(orphanedNodes: NodeSkeleton[]): Promise; }; export default (state: State): Journey => { @@ -335,13 +344,13 @@ export default (state: State): Journey => { deps: true, } ): Promise { - return exportJourney({ treeId, options, state }); + return exportJourney({ journeyId: treeId, options, state }); }, async readJourneys(): Promise { - return getJourneys({ state }); + return readJourneys({ state }); }, async readJourney(journeyId: string): Promise { - return getJourney({ journeyId, state }); + return readJourney({ journeyId, state }); }, async createJourney( journeyId: string, @@ -407,14 +416,6 @@ export default (state: State): Journey => { state, }); }, - async findOrphanedNodes(): Promise { - return findOrphanedNodes({ state }); - }, - async removeOrphanedNodes( - orphanedNodes: NodeSkeleton[] - ): Promise { - return removeOrphanedNodes({ orphanedNodes, state }); - }, isCustomJourney(journey: SingleTreeExportInterface) { return isCustomJourney({ journey, state }); }, @@ -448,10 +449,10 @@ export default (state: State): Journey => { // Deprecated async getJourneys(): Promise { - return getJourneys({ state }); + return readJourneys({ state }); }, async getJourney(journeyId: string): Promise { - return getJourney({ journeyId, state }); + return readJourney({ journeyId, state }); }, async importAllJourneys( treesMap: MultiTreeExportInterface, @@ -459,9 +460,70 @@ export default (state: State): Journey => { ): Promise { return importJourneys({ importData: treesMap, options, state }); }, + async findOrphanedNodes(): Promise { + return _findOrphanedNodes({ state }); + }, + async removeOrphanedNodes( + orphanedNodes: NodeSkeleton[] + ): Promise { + return _removeOrphanedNodes({ orphanedNodes, state }); + }, }; }; +/** + * Tree export options + */ +export interface TreeExportOptions { + /** + * Where applicable, use string arrays to store multi-line text (e.g. scripts). + */ + useStringArrays: boolean; + /** + * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes). + */ + deps: boolean; +} + +/** + * Tree import options + */ +export interface TreeImportOptions { + /** + * Generate new UUIDs for all nodes during import. + */ + reUuid: boolean; + /** + * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes). + */ + deps: boolean; +} + +export interface SingleTreeExportInterface { + meta?: ExportMetaData; + innerNodes?: Record; + innernodes?: Record; + nodes: Record; + scripts: Record; + emailTemplates: Record; + socialIdentityProviders: Record; + themes: ThemeSkeleton[]; + saml2Entities: Record; + circlesOfTrust: Record; + tree: TreeSkeleton; +} + +export interface MultiTreeExportInterface { + meta?: ExportMetaData; + trees: Record; +} + +export type JourneyClassificationType = + | 'standard' + | 'custom' + | 'cloud' + | 'premium'; + export enum JourneyClassification { STANDARD = 'standard', CUSTOM = 'custom', @@ -469,6 +531,14 @@ export enum JourneyClassification { PREMIUM = 'premium', } +export interface TreeDependencyMapInterface { + [k: string]: TreeDependencyMapInterface[]; +} + +export interface TreeExportResolverInterface { + (treeId: string, state: State): Promise; +} + const containerNodes = ['PageNode', 'CustomPageNode']; const scriptedNodes = [ @@ -536,6 +606,13 @@ async function getSaml2NodeDependencies( ) { const samlProperties = ['metaAlias', 'idpEntityId']; const saml2EntityPromises = []; + const saml2Entities = []; + let circlesOfTrust = []; + let saml2NodeDependencies = { + saml2Entities, + circlesOfTrust, + }; + const errors = []; for (const samlProperty of samlProperties) { // In the following line nodeObject[samlProperty] will look like '/alpha/iSPAzure'. const entityId = @@ -568,13 +645,15 @@ async function getSaml2NodeDependencies( } saml2EntityPromises.push(providerResponse); } catch (error) { - printMessage({ message: error.message, type: 'error', state }); + error.message = `Error reading saml2 dependencies: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } } } try { const saml2EntitiesPromisesResults = await Promise.all(saml2EntityPromises); - const saml2Entities = []; for (const saml2Entity of saml2EntitiesPromisesResults) { if (saml2Entity) { saml2Entities.push(saml2Entity); @@ -583,7 +662,7 @@ async function getSaml2NodeDependencies( const samlEntityIds = saml2Entities.map( (saml2EntityConfig) => `${saml2EntityConfig.entityId}|saml2` ); - const circlesOfTrust = allCirclesOfTrust.filter((circleOfTrust) => { + circlesOfTrust = allCirclesOfTrust.filter((circleOfTrust) => { let hasEntityId = false; for (const trustedProvider of circleOfTrust.trustedProviders) { if (!hasEntityId && samlEntityIds.includes(trustedProvider)) { @@ -592,42 +671,45 @@ async function getSaml2NodeDependencies( } return hasEntityId; }); - const saml2NodeDependencies = { + saml2NodeDependencies = { saml2Entities, circlesOfTrust, }; - return saml2NodeDependencies; } catch (error) { - printMessage({ message: error.message, type: 'error', state }); - const saml2NodeDependencies = { - saml2Entities: [], - circlesOfTrust: [], - }; - return saml2NodeDependencies; + error.message = `Error reading saml2 dependencies: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } + if (errors.length) { + const errorMessages = errors.map((error) => error.message).join('\n'); + throw new Error(`Saml2 dependencies error:\n${errorMessages}`); } + return saml2NodeDependencies; } /** * Create export data for a tree/journey with all its nodes and dependencies. The export data can be written to a file as is. - * @param {string} treeId tree id/name + * @param {string} journeyId journey id/name * @param {TreeExportOptions} options export options * @returns {Promise} a promise that resolves to an object containing the tree and all its nodes and dependencies */ export async function exportJourney({ - treeId, + journeyId, options = { useStringArrays: true, deps: true, }, state, }: { - treeId: string; + journeyId: string; options?: TreeExportOptions; state: State; }): Promise { const exportData = createSingleTreeExportTemplate({ state }); + const errors = []; try { - const treeObject = await getTree({ id: treeId, state }); + const treeObject = await getTree({ id: journeyId, state }); const { useStringArrays, deps } = options; const verbose = state.getDebug(); @@ -661,11 +743,7 @@ export async function exportJourney({ deps && state.getDeploymentType() !== Constants.CLASSIC_DEPLOYMENT_TYPE_KEY ) { - try { - themePromise = readThemes({ state }); - } catch (error) { - printMessage({ message: error, type: 'error', state }); - } + themePromise = readThemes({ state }); } let allSaml2Providers = null; @@ -719,15 +797,10 @@ export async function exportJourney({ }); emailTemplatePromises.push(emailTemplate); } catch (error) { - let message = `${error}`; - if (error.isAxiosError && error.response.status) { - message = error.response.statusText; - } - printMessage({ - message: `\n${message}: Email Template "${nodeObject.emailTemplateName}"`, - type: 'error', - state, - }); + error.message = `Error reading email template ${ + nodeObject.emailTemplateName + }: ${error.response?.data?.message || error.message}`; + errors.push(error); } } } @@ -735,12 +808,24 @@ export async function exportJourney({ // handle SAML2 node dependencies if (deps && nodeType === 'product-Saml2Node') { if (!allSaml2Providers) { - // eslint-disable-next-line no-await-in-loop - allSaml2Providers = (await getProviderStubs({ state })).result; + try { + allSaml2Providers = await readSaml2ProviderStubs({ state }); + } catch (error) { + error.message = `Error reading saml2 providers: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } } if (!allCirclesOfTrust) { - // eslint-disable-next-line no-await-in-loop - allCirclesOfTrust = (await getCirclesOfTrust({ state })).result; + try { + allCirclesOfTrust = await readCirclesOfTrust({ state }); + } catch (error) { + error.message = `Error reading circles of trust: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } } saml2ConfigPromises.push( getSaml2NodeDependencies( @@ -815,202 +900,244 @@ export async function exportJourney({ // Process inner nodes if (verbose && innerNodePromises.length > 0) printMessage({ message: ' - Inner nodes:', state }); - const innerNodeDataResults = await Promise.all(innerNodePromises); - for (const innerNodeObject of innerNodeDataResults) { - const innerNodeId = innerNodeObject._id; - const innerNodeType = innerNodeObject._type._id; - if (verbose) - printMessage({ - message: ` - ${innerNodeId} (${innerNodeType})`, - type: 'info', - newline: true, - state, - }); - exportData.innerNodes[innerNodeId] = innerNodeObject; + try { + const innerNodeDataResults = await Promise.all(innerNodePromises); + for (const innerNodeObject of innerNodeDataResults) { + const innerNodeId = innerNodeObject._id; + const innerNodeType = innerNodeObject._type._id; + if (verbose) + printMessage({ + message: ` - ${innerNodeId} (${innerNodeType})`, + type: 'info', + newline: true, + state, + }); + exportData.innerNodes[innerNodeId] = innerNodeObject; - // handle script node types - if (deps && scriptedNodes.includes(innerNodeType)) { - scriptPromises.push( - getScript({ scriptId: innerNodeObject.script, state }) - ); - } + // handle script node types + if (deps && scriptedNodes.includes(innerNodeType)) { + scriptPromises.push( + getScript({ scriptId: innerNodeObject.script, state }) + ); + } - // frodo supports email templates in platform deployments - if ( - (deps && - state.getDeploymentType() === Constants.CLOUD_DEPLOYMENT_TYPE_KEY) || - state.getDeploymentType() === Constants.FORGEOPS_DEPLOYMENT_TYPE_KEY - ) { - if (emailTemplateNodes.includes(innerNodeType)) { - try { - const emailTemplate = await readEmailTemplate({ - templateId: innerNodeObject.emailTemplateName, - state, - }); - emailTemplatePromises.push(emailTemplate); - } catch (error) { - let message = `${error}`; - if (error.isAxiosError && error.response.status) { - message = error.response.statusText; + // frodo supports email templates in platform deployments + if ( + (deps && + state.getDeploymentType() === + Constants.CLOUD_DEPLOYMENT_TYPE_KEY) || + state.getDeploymentType() === Constants.FORGEOPS_DEPLOYMENT_TYPE_KEY + ) { + if (emailTemplateNodes.includes(innerNodeType)) { + try { + const emailTemplate = await readEmailTemplate({ + templateId: innerNodeObject.emailTemplateName, + state, + }); + emailTemplatePromises.push(emailTemplate); + } catch (error) { + error.message = `Error reading email template ${ + innerNodeObject.emailTemplateName + }: ${error.response?.data?.message || error.message}`; + errors.push(error); } - printMessage({ - message: `\n${message}: Email Template "${innerNodeObject.emailTemplateName}"`, - type: 'error', - state, - }); } } - } - // handle SAML2 node dependencies - if (deps && innerNodeType === 'product-Saml2Node') { - printMessage({ message: 'SAML2 inner node', type: 'error', state }); - if (!allSaml2Providers) { - // eslint-disable-next-line no-await-in-loop - allSaml2Providers = (await getProviderStubs({ state })).result; - } - if (!allCirclesOfTrust) { - // eslint-disable-next-line no-await-in-loop - allCirclesOfTrust = (await getCirclesOfTrust({ state })).result; + // handle SAML2 node dependencies + if (deps && innerNodeType === 'product-Saml2Node') { + if (!allSaml2Providers) { + try { + allSaml2Providers = await readSaml2ProviderStubs({ state }); + } catch (error) { + error.message = `Error reading saml2 providers: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } + } + if (!allCirclesOfTrust) { + try { + allCirclesOfTrust = await readCirclesOfTrust({ state }); + } catch (error) { + error.message = `Error reading circles of trust: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } + } + saml2ConfigPromises.push( + getSaml2NodeDependencies( + innerNodeObject, + allSaml2Providers, + allCirclesOfTrust, + state + ) + ); } - saml2ConfigPromises.push( - getSaml2NodeDependencies( - innerNodeObject, - allSaml2Providers, - allCirclesOfTrust, - state - ) - ); - } - // If this is a SocialProviderHandlerNode get each enabled social identity provider. - if ( - deps && - !socialProviderPromise && - innerNodeType === 'SocialProviderHandlerNode' - ) { - socialProviderPromise = getSocialIdentityProviders({ state }); - } + // If this is a SocialProviderHandlerNode get each enabled social identity provider. + if ( + deps && + !socialProviderPromise && + innerNodeType === 'SocialProviderHandlerNode' + ) { + socialProviderPromise = getSocialIdentityProviders({ state }); + } - // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers. - if ( - deps && - !filteredSocialProviders && - innerNodeType === 'SelectIdPNode' && - innerNodeObject.filteredProviders - ) { - filteredSocialProviders = filteredSocialProviders || []; - for (const filteredProvider of innerNodeObject.filteredProviders) { - if (!filteredSocialProviders.includes(filteredProvider)) { - filteredSocialProviders.push(filteredProvider); + // If this is a SelectIdPNode and filteredProviters is not already set to empty array set filteredSocialProviers. + if ( + deps && + !filteredSocialProviders && + innerNodeType === 'SelectIdPNode' && + innerNodeObject.filteredProviders + ) { + filteredSocialProviders = filteredSocialProviders || []; + for (const filteredProvider of innerNodeObject.filteredProviders) { + if (!filteredSocialProviders.includes(filteredProvider)) { + filteredSocialProviders.push(filteredProvider); + } } } } + } catch (error) { + error.message = `Error reading inner nodes: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } // Process email templates if (verbose && emailTemplatePromises.length > 0) printMessage({ message: ' - Email templates:', state }); - const settledEmailTemplatePromises = await Promise.allSettled( - emailTemplatePromises - ); - for (const settledPromise of settledEmailTemplatePromises) { - if (settledPromise.status === 'fulfilled' && settledPromise.value) { - if (verbose) - printMessage({ - message: ` - ${settledPromise.value._id.split('/')[1]}${ - settledPromise.value.displayName - ? ` (${settledPromise.value.displayName})` - : '' - }`, - type: 'info', - newline: true, - state, - }); - exportData.emailTemplates[settledPromise.value._id.split('/')[1]] = - settledPromise.value; - } - } - - // Process SAML2 providers and circles of trust - const saml2NodeDependencies = await Promise.all(saml2ConfigPromises); - for (const saml2NodeDependency of saml2NodeDependencies) { - if (saml2NodeDependency) { - if (verbose) - printMessage({ message: ' - SAML2 entity providers:', state }); - for (const saml2Entity of saml2NodeDependency.saml2Entities) { + try { + const settledEmailTemplatePromises = await Promise.allSettled( + emailTemplatePromises + ); + for (const settledPromise of settledEmailTemplatePromises) { + if (settledPromise.status === 'fulfilled' && settledPromise.value) { if (verbose) printMessage({ - message: ` - ${saml2Entity.entityLocation} ${saml2Entity.entityId}`, + message: ` - ${settledPromise.value._id.split('/')[1]}${ + settledPromise.value.displayName + ? ` (${settledPromise.value.displayName})` + : '' + }`, type: 'info', + newline: true, state, }); - exportData.saml2Entities[saml2Entity._id] = saml2Entity; + exportData.emailTemplates[settledPromise.value._id.split('/')[1]] = + settledPromise.value; } - if (verbose) - printMessage({ message: ' - SAML2 circles of trust:', state }); - for (const circleOfTrust of saml2NodeDependency.circlesOfTrust) { + } + } catch (error) { + error.message = `Error reading email templates: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } + + // Process SAML2 providers and circles of trust + try { + const saml2NodeDependencies = await Promise.all(saml2ConfigPromises); + for (const saml2NodeDependency of saml2NodeDependencies) { + if (saml2NodeDependency) { if (verbose) - printMessage({ - message: ` - ${circleOfTrust._id}`, - type: 'info', - state, - }); - exportData.circlesOfTrust[circleOfTrust._id] = circleOfTrust; + printMessage({ message: ' - SAML2 entity providers:', state }); + for (const saml2Entity of saml2NodeDependency.saml2Entities) { + if (verbose) + printMessage({ + message: ` - ${saml2Entity.entityLocation} ${saml2Entity.entityId}`, + type: 'info', + state, + }); + exportData.saml2Entities[saml2Entity._id] = saml2Entity; + } + if (verbose) + printMessage({ message: ' - SAML2 circles of trust:', state }); + for (const circleOfTrust of saml2NodeDependency.circlesOfTrust) { + if (verbose) + printMessage({ + message: ` - ${circleOfTrust._id}`, + type: 'info', + state, + }); + exportData.circlesOfTrust[circleOfTrust._id] = circleOfTrust; + } } } + } catch (error) { + error.message = `Error reading saml2 dependencies: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } // Process socialIdentityProviders - const socialProviders = await Promise.resolve(socialProviderPromise); - if (socialProviders) { - if (verbose) - printMessage({ - message: ' - OAuth2/OIDC (social) identity providers:', - state, - }); - for (const socialProvider of socialProviders.result) { - // If the list of socialIdentityProviders needs to be filtered based on the - // filteredProviders property of a SelectIdPNode do it here. - if ( - socialProvider && - (!filteredSocialProviders || - filteredSocialProviders.length === 0 || - filteredSocialProviders.includes(socialProvider._id)) - ) { - if (verbose) - printMessage({ - message: ` - ${socialProvider._id}`, - type: 'info', - state, - }); - scriptPromises.push( - getScript({ scriptId: socialProvider.transform, state }) - ); - exportData.socialIdentityProviders[socialProvider._id] = - socialProvider; + try { + const socialProviders = await Promise.resolve(socialProviderPromise); + if (socialProviders) { + if (verbose) + printMessage({ + message: ' - OAuth2/OIDC (social) identity providers:', + state, + }); + for (const socialProvider of socialProviders.result) { + // If the list of socialIdentityProviders needs to be filtered based on the + // filteredProviders property of a SelectIdPNode do it here. + if ( + socialProvider && + (!filteredSocialProviders || + filteredSocialProviders.length === 0 || + filteredSocialProviders.includes(socialProvider._id)) + ) { + if (verbose) + printMessage({ + message: ` - ${socialProvider._id}`, + type: 'info', + state, + }); + scriptPromises.push( + getScript({ scriptId: socialProvider.transform, state }) + ); + exportData.socialIdentityProviders[socialProvider._id] = + socialProvider; + } } } + } catch (error) { + error.message = `Error reading social identity providers: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } // Process scripts if (verbose && scriptPromises.length > 0) printMessage({ message: ' - Scripts:', state }); - const scriptObjects = await Promise.all(scriptPromises); - for (const scriptObject of scriptObjects) { - if (scriptObject) { - if (verbose) - printMessage({ - message: ` - ${scriptObject._id} (${scriptObject.name})`, - type: 'info', - newline: true, - state, - }); - scriptObject.script = useStringArrays - ? convertBase64TextToArray(scriptObject.script) - : JSON.stringify(decode(scriptObject.script)); - exportData.scripts[scriptObject._id] = scriptObject; + try { + const scriptObjects = await Promise.all(scriptPromises); + for (const scriptObject of scriptObjects) { + if (scriptObject) { + if (verbose) + printMessage({ + message: ` - ${scriptObject._id} (${scriptObject.name})`, + type: 'info', + newline: true, + state, + }); + scriptObject.script = useStringArrays + ? convertBase64TextToArray(scriptObject.script) + : JSON.stringify(decode(scriptObject.script)); + exportData.scripts[scriptObject._id] = scriptObject; + } } + } catch (error) { + error.message = `Error reading scripts: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } // Process themes @@ -1037,23 +1164,22 @@ export async function exportJourney({ } } } catch (error) { - printMessage({ message: error.response.data, type: 'error', state }); - printMessage({ - message: 'Error handling themes: ' + error.message, - type: 'error', - state, - }); + error.message = `Error reading themes: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); } } } catch (error) { - printMessage({ message: error.response.data, type: 'error', state }); - printMessage({ - message: 'Error exporting tree: ' + treeId + ' - ' + error.message, - type: 'error', - state, - }); + error.message = `Error exporting journey ${journeyId}: ${ + error.response?.data?.message || error.message + }`; + errors.push(error); + } + if (errors.length) { + const errorMessages = errors.map((error) => error.message).join('\n'); + throw new Error(`Export error:\n${errorMessages}`); } - return exportData; } @@ -1061,7 +1187,7 @@ export async function exportJourney({ * Get all the journeys/trees without all their nodes and dependencies. * @returns {Promise} a promise that resolves to an array of journey objects */ -export async function getJourneys({ +export async function readJourneys({ state, }: { state: State; @@ -1076,7 +1202,7 @@ export async function getJourneys({ * @param {string} journeyId journey id/name * @returns {Promise} a promise that resolves to a journey object */ -export async function getJourney({ +export async function readJourney({ journeyId, state, }: { @@ -1103,7 +1229,7 @@ export async function createJourney({ }): Promise { debugMessage({ message: `JourneyOps.createJourney: start`, state }); try { - await getJourney({ journeyId, state }); + await readJourney({ journeyId, state }); } catch (error) { const result = await putTree({ treeId: journeyId, @@ -1196,7 +1322,11 @@ export async function importJourney({ try { await updateScript({ scriptId, scriptData: scriptObject, state }); } catch (error) { - error.message = `Error importing script ${scriptObject['name']} (${scriptId}) in journey ${treeId}: ${error.message}`; + error.message = `Error importing script ${ + scriptObject['name'] + } (${scriptId}) in journey ${treeId}: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } if (verbose) printMessage({ message: '', state }); @@ -1223,7 +1353,9 @@ export async function importJourney({ try { await updateEmailTemplate({ templateId, templateData, state }); } catch (error) { - error.message = `Error importing email templates: ${error.message}`; + error.message = `Error importing email templates: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } if (verbose) printMessage({ message: '', state }); @@ -1246,7 +1378,9 @@ export async function importJourney({ try { await updateThemes({ themeMap: themes, state }); } catch (error) { - error.message = `Error importing themes: ${error.message}`; + error.message = `Error importing themes: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } } @@ -1290,7 +1424,7 @@ export async function importJourney({ }); } catch (importError2) { printMessage({ - message: error.response?.data || error, + message: error.response?.data?.message || error, type: 'error', state, }); @@ -1299,7 +1433,9 @@ export async function importJourney({ ); } } else { - error.message = `\nError importing provider ${providerId} in journey ${treeId}: ${error}`; + error.message = `\nError importing provider ${providerId} in journey ${treeId}: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } } @@ -1354,7 +1490,9 @@ export async function importJourney({ state, }); } catch (error) { - error.message = `Error creating provider ${entityId}: ${error.message}`; + error.message = `Error creating provider ${entityId}: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } } else { @@ -1365,7 +1503,9 @@ export async function importJourney({ state, }); } catch (error) { - error.message = `Error updating provider ${entityId}: ${error.message}`; + error.message = `Error updating provider ${entityId}: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } } @@ -1392,19 +1532,21 @@ export async function importJourney({ await updateCircleOfTrust({ cotId, cotData, state }); } catch (updateCotErr) { printMessage({ - message: error.response?.data || error, + message: error.response?.data?.message || error, type: 'error', state, }); printMessage({ - message: updateCotErr.response?.data || updateCotErr, + message: updateCotErr.response?.data?.message || updateCotErr, type: 'error', state, }); throw new Error(`Error creating/updating circle of trust ${cotId}`); } } else { - error.message = `Error creating circle of trust ${cotId}: ${error.message}`; + error.message = `Error creating circle of trust ${cotId}: ${ + error.response?.data?.message || error.message + }`; errors.push(error); } } @@ -1530,13 +1672,18 @@ export async function importJourney({ }); nodeImportError2.message = `Error importing node ${innerNodeId}${ innerNodeId === newUuid ? '' : ` [${newUuid}]` - } in journey ${treeId}: ${nodeImportError2.message}`; + } in journey ${treeId}: ${ + nodeImportError2.response?.data?.message || + nodeImportError2.message + }`; errors.push(nodeImportError2); } } else { nodeImportError.message = `Error importing inner node ${innerNodeId}${ innerNodeId === newUuid ? '' : ` [${newUuid}]` - } in journey ${treeId}: ${nodeImportError.message}`; + } in journey ${treeId}: ${ + nodeImportError.response?.data?.message || nodeImportError.message + }`; errors.push(nodeImportError); } } @@ -1642,7 +1789,9 @@ export async function importJourney({ } else { nodeImportError.message = `Error importing node ${nodeId}${ nodeId === newUuid ? '' : ` [${newUuid}]` - } in journey ${treeId}: ${nodeImportError.message}`; + } in journey ${treeId}: ${ + nodeImportError.response?.data?.message || nodeImportError.message + }`; errors.push(nodeImportError); } } @@ -1731,11 +1880,15 @@ export async function importJourney({ state, }); } catch (importError2) { - importError2.message = `Error importing journey flow ${treeId}: ${importError2.message}`; + importError2.message = `Error importing journey flow ${treeId}: ${ + importError2.response?.data?.message || importError2.message + }`; errors.push(importError2); } } else { - importError.message = `\nError importing journey flow ${treeId}: ${importError.message}`; + importError.message = `\nError importing journey flow ${treeId}: ${ + importError.response?.data?.message || importError.message + }`; errors.push(importError); } } @@ -1750,18 +1903,95 @@ export async function importJourney({ } /** - * Resolve journey dependencies - * @param {Map} installedJorneys Map of installed journeys - * @param {Map} journeyMap Map of journeys to resolve dependencies for - * @param {[String]} unresolvedJourneys Map to hold the names of unresolved journeys and their dependencies - * @param {[String]} resolvedJourneys Array to hold the names of resolved journeys - * @param {int} index Depth of recursion + * Resolve inner tree dependencies + * @param {string[]} existingJorneys Array of existing journey names + * @param {MultiTreeExportInterface} candidateJourneys Map of journeys to resolve dependencies for + * @param {{ [k: string]: string[] }} unresolvedJourneys Map of unresolved journey names and their dependencies + * @param {string[]} resolvedJourneys Array of resolved journey names + * @param {number} index Depth of recursion + * @returns {Promise<{unresolvedJourneys: { [k: string]: string[] }; resolvedJourneys: string[];}>} a promise resolving to a dependency status object */ -export async function resolveDependencies( - installedJorneys, - journeyMap, +export async function resolveInnerTreeDependencies({ + existingJorneys, + candidateJourneys, unresolvedJourneys, resolvedJourneys, + index = -1, +}: { + existingJorneys: string[]; + candidateJourneys: MultiTreeExportInterface; + unresolvedJourneys?: { [k: string]: string[] }; + resolvedJourneys?: string[]; + index?: number; +}): Promise<{ + unresolvedJourneys: { [k: string]: string[] }; + resolvedJourneys: string[]; +}> { + let before = -1; + let after = index; + if (index !== -1) { + before = index; + } + + for (const tree in candidateJourneys) { + if ({}.hasOwnProperty.call(candidateJourneys, tree)) { + const dependencies = []; + for (const node in candidateJourneys[tree].nodes) { + if ( + candidateJourneys[tree].nodes[node]._type._id === + 'InnerTreeEvaluatorNode' + ) { + dependencies.push(candidateJourneys[tree].nodes[node].tree); + } + } + let allResolved = true; + for (const dependency of dependencies) { + if ( + !resolvedJourneys.includes(dependency) && + !existingJorneys.includes(dependency) + ) { + allResolved = false; + } + } + if (allResolved) { + if (resolvedJourneys.indexOf(tree) === -1) resolvedJourneys.push(tree); + delete unresolvedJourneys[tree]; + } else { + unresolvedJourneys[tree] = dependencies; + } + } + } + after = Object.keys(unresolvedJourneys).length; + if (index !== -1 && after === before) { + // This is the end, no progress was made since the last recursion + return { + unresolvedJourneys, + resolvedJourneys, + }; + } else if (after > 0) { + resolveInnerTreeDependencies({ + existingJorneys: existingJorneys, + candidateJourneys: candidateJourneys, + unresolvedJourneys, + resolvedJourneys, + index: after, + }); + } +} + +/** + * Resolve journey dependencies + * @param {string[]} installedJorneys Map of installed journeys + * @param {MultiTreeExportInterface} journeyMap Map of journeys to resolve dependencies for + * @param {string[]} unresolvedJourneys Map to hold the names of unresolved journeys and their dependencies + * @param {string[]} resolvedJourneys Array to hold the names of resolved journeys + * @param {number} index Depth of recursion + */ +export async function resolveDependencies( + installedJorneys: string[], + journeyMap: MultiTreeExportInterface, + unresolvedJourneys: { [k: string]: string[] }, + resolvedJourneys: string[], index = -1 ) { let before = -1; @@ -1824,17 +2054,8 @@ export async function resolveDependencies( } } -export class UnresolvedDependenciesError extends FrodoError { - unresolvedDependencies: any; - constructor(message: string, unresolvedDependencies: any) { - super(message); - this.name = 'UnresolvedDependenciesError'; - this.unresolvedDependencies = unresolvedDependencies; - } -} - /** - * Helper to import multiple trees from a tree map + * Import journeys * @param {MultiTreeExportInterface} importData map of trees object * @param {TreeImportOptions} options import options */ @@ -1850,9 +2071,7 @@ export async function importJourneys({ const response = []; const errors = []; const imported = []; - const installedJourneys = (await getTrees({ state })).result.map( - (x) => x._id - ); + const installedJourneys = (await readJourneys({ state })).map((x) => x._id); const unresolvedJourneys = {}; const resolvedJourneys = []; createProgressIndicator({ @@ -1868,12 +2087,20 @@ export async function importJourneys({ resolvedJourneys ); if (Object.keys(unresolvedJourneys).length === 0) { + // no unresolved journeys stopProgressIndicator({ message: `Resolved all dependencies.`, status: 'success', state, }); } else { + stopProgressIndicator({ + message: `${ + Object.keys(unresolvedJourneys).length + } journeys with unresolved dependencies:`, + status: 'fail', + state, + }); for (const journey of Object.keys(unresolvedJourneys)) { printMessage({ message: ` - ${journey} requires ${unresolvedJourneys[journey]}`, @@ -1882,23 +2109,33 @@ export async function importJourneys({ }); } } + createProgressIndicator({ + total: resolvedJourneys.length, + message: 'Importing', + state, + }); for (const tree of resolvedJourneys) { try { response.push( await importJourney({ importData: importData[tree], options, state }) ); imported.push(tree); + updateProgressIndicator({ message: `${tree}`, state }); } catch (error) { errors.push(error); } } if (errors.length) { - const errorMessages = errors.map((error) => error.message).join('\n'); + const errorMessages = errors + .map((error) => error.response?.data?.message || error.message) + .join('\n'); + stopProgressIndicator({ message: 'Error importing journeys!', state }); throw new Error(`Import error:\n${errorMessages}`); } if (0 === imported.length) { throw new Error(`Import error:\nNo clients found in import data!`); } + stopProgressIndicator({ message: 'Done', state }); return response; } @@ -1944,7 +2181,7 @@ export const onlineTreeExportResolver: TreeExportResolverInterface = async function (treeId: string, state: State) { debugMessage({ message: `onlineTreeExportResolver(${treeId})`, state }); return await exportJourney({ - treeId, + journeyId: treeId, options: { deps: false, useStringArrays: false, @@ -2047,207 +2284,51 @@ export async function getTreeDescendents({ resolvedTreeIds: string[]; state: State; }): Promise { + const treeId = treeExport.tree._id + ''; debugMessage({ - message: `getTreeDependencies(${ - treeExport.tree._id - }, [${resolvedTreeIds.join(', ')}])`, + message: `getTreeDependencies(${treeId}, [${resolvedTreeIds.join(', ')}])`, state, }); - if (!resolvedTreeIds.includes(treeExport.tree._id)) { - resolvedTreeIds.push(treeExport.tree._id); + if (!resolvedTreeIds.includes(treeId)) { + resolvedTreeIds.push(treeId); } const treeDependencyMap: TreeDependencyMapInterface = { - [treeExport.tree._id]: [], + [treeId]: [], }; const dependencies: TreeDependencyMapInterface[] = []; for (const [nodeId, node] of Object.entries(treeExport.tree.nodes)) { - const innerTreeId = treeExport.nodes[nodeId].tree; - if ( - node.nodeType === 'InnerTreeEvaluatorNode' && - !resolvedTreeIds.includes(innerTreeId) - ) { - const innerTreeExport = await resolveTreeExport(innerTreeId, state); - debugMessage({ - message: `resolved inner tree: ${innerTreeExport.tree._id}`, - state, - }); - // resolvedTreeIds.push(innerTreeId); - dependencies.push( - await getTreeDescendents({ - treeExport: innerTreeExport, - resolveTreeExport, - resolvedTreeIds, - state, - }) - ); - } - } - treeDependencyMap[treeExport.tree._id] = dependencies; - return treeDependencyMap; -} - -/** - * Find all node configuration objects that are no longer referenced by any tree - * @returns {Promise} a promise that resolves to an array of orphaned nodes - */ -export async function findOrphanedNodes({ - state, -}: { - state: State; -}): Promise { - const allNodes = []; - const orphanedNodes = []; - let types = []; - const allJourneys = (await getTrees({ state })).result; - let errorMessage = ''; - const errorTypes = []; - - createProgressIndicator({ - total: undefined, - message: `Counting total nodes...`, - type: 'indeterminate', - state, - }); - try { - types = (await getNodeTypes({ state })).result; - } catch (error) { - printMessage({ - message: 'Error retrieving all available node types:', - type: 'error', - state, - }); - printMessage({ message: error.response.data, type: 'error', state }); - return []; - } - for (const type of types) { + let innerTreeId: string; try { - // eslint-disable-next-line no-await-in-loop, no-loop-func - const nodes = (await getNodesByType({ nodeType: type._id, state })) - .result; - for (const node of nodes) { - allNodes.push(node); - updateProgressIndicator({ - message: `${allNodes.length} total nodes${errorMessage}`, - state, - }); - } - } catch (error) { - errorTypes.push(type._id); - errorMessage = ` (Skipped type(s): ${errorTypes})`['yellow']; - updateProgressIndicator({ - message: `${allNodes.length} total nodes${errorMessage}`, - state, - }); - } - } - if (errorTypes.length > 0) { - stopProgressIndicator({ - message: `${allNodes.length} total nodes${errorMessage}`, - state, - status: 'warn', - }); - } else { - stopProgressIndicator({ - message: `${allNodes.length} total nodes`, - status: 'success', - state, - }); - } - - createProgressIndicator({ - total: undefined, - message: 'Counting active nodes...', - type: 'indeterminate', - state, - }); - const activeNodes = []; - for (const journey of allJourneys) { - for (const nodeId in journey.nodes) { - if ({}.hasOwnProperty.call(journey.nodes, nodeId)) { - activeNodes.push(nodeId); - updateProgressIndicator({ - message: `${activeNodes.length} active nodes`, - state, - }); - const node = journey.nodes[nodeId]; - if (containerNodes.includes(node.nodeType)) { - const containerNode = await getNode({ - nodeId, - nodeType: node.nodeType, + if (node.nodeType === 'InnerTreeEvaluatorNode') { + innerTreeId = treeExport.nodes[nodeId].tree; + if (!resolvedTreeIds.includes(innerTreeId)) { + const innerTreeExport = await resolveTreeExport(innerTreeId, state); + debugMessage({ + message: `resolved inner tree: ${innerTreeExport.tree._id}`, state, }); - for (const innerNode of containerNode.nodes) { - activeNodes.push(innerNode._id); - updateProgressIndicator({ - message: `${activeNodes.length} active nodes`, + // resolvedTreeIds.push(innerTreeId); + dependencies.push( + await getTreeDescendents({ + treeExport: innerTreeExport, + resolveTreeExport, + resolvedTreeIds, state, - }); - } + }) + ); } } + } catch (error) { + if (innerTreeId) { + const unresolvableMap: TreeDependencyMapInterface = { + [innerTreeId]: [], + }; + dependencies.push(unresolvableMap); + } } } - stopProgressIndicator({ - message: `${activeNodes.length} active nodes`, - status: 'success', - state, - }); - - createProgressIndicator({ - total: undefined, - message: 'Calculating orphaned nodes...', - type: 'indeterminate', - state, - }); - const diff = allNodes.filter((x) => !activeNodes.includes(x._id)); - for (const orphanedNode of diff) { - orphanedNodes.push(orphanedNode); - } - stopProgressIndicator({ - message: `${orphanedNodes.length} orphaned nodes`, - status: 'success', - state, - }); - return orphanedNodes; -} - -/** - * Remove orphaned nodes - * @param {NodeSkeleton[]} orphanedNodes Pass in an array of orphaned node configuration objects to remove - * @returns {Promise} a promise that resolves to an array nodes that encountered errors deleting - */ -export async function removeOrphanedNodes({ - orphanedNodes, - state, -}: { - orphanedNodes: NodeSkeleton[]; - state: State; -}): Promise { - const errorNodes = []; - createProgressIndicator({ - total: orphanedNodes.length, - message: 'Removing orphaned nodes...', - state, - }); - for (const node of orphanedNodes) { - updateProgressIndicator({ message: `Removing ${node['_id']}...`, state }); - try { - // eslint-disable-next-line no-await-in-loop - await deleteNode({ - nodeId: node['_id'], - nodeType: node['_type']['_id'], - state, - }); - } catch (deleteError) { - errorNodes.push(node); - printMessage({ message: ` ${deleteError}`, type: 'error', state }); - } - } - stopProgressIndicator({ - message: `Removed ${orphanedNodes.length} orphaned nodes.`, - state, - }); - return errorNodes; + treeDependencyMap[treeId] = dependencies; + return treeDependencyMap; } /** diff --git a/src/ops/ManagedObjectOps.ts b/src/ops/ManagedObjectOps.ts index bb1767ba5..56e979a05 100644 --- a/src/ops/ManagedObjectOps.ts +++ b/src/ops/ManagedObjectOps.ts @@ -1,11 +1,11 @@ import { IdObjectSkeletonInterface } from '../api/ApiTypes'; import { - getManagedObject as _getManagedObject, createManagedObject as _createManagedObject, - putManagedObject as _putManagedObject, deleteManagedObject as _deleteManagedObject, - queryManagedObjects as _queryManagedObjects, + getManagedObject as _getManagedObject, + putManagedObject as _putManagedObject, queryAllManagedObjectsByType, + queryManagedObjects as _queryManagedObjects, } from '../api/ManagedObjectApi'; import { State } from '../shared/State'; @@ -74,8 +74,8 @@ export type ManagedObject = { */ queryManagedObjects( type: string, - filter: string, - fields: string[] + filter?: string, + fields?: string[] ): Promise; /** * Resolve a managed object's uuid to a human readable username @@ -130,8 +130,8 @@ export default (state: State): ManagedObject => { }, async queryManagedObjects( type: string, - filter: string, - fields: string[] + filter: string = undefined, + fields: string[] = [] ): Promise { return queryManagedObjects({ type, filter, fields, state }); }, diff --git a/src/ops/NodeOps.ts b/src/ops/NodeOps.ts index d3cd4afa5..13683c573 100644 --- a/src/ops/NodeOps.ts +++ b/src/ops/NodeOps.ts @@ -1,26 +1,78 @@ -import { State } from '../shared/State'; import { - getNode, - deleteNode, - getNodeTypes, - getNodesByType, + createNode as _createNode, + deleteNode as _deleteNode, + getNode as _getNode, + getNodes as _getNodes, + getNodesByType as _getNodesByType, + getNodeTypes as _getNodeTypes, + type NodeSkeleton, + type NodeTypeSkeleton, + putNode as _putNode, } from '../api/NodeApi'; import { getTrees } from '../api/TreeApi'; +import { State } from '../shared/State'; import { - printMessage, createProgressIndicator, - updateProgressIndicator, + printMessage, stopProgressIndicator, + updateProgressIndicator, } from '../utils/Console'; -import { NodeClassificationType } from './OpsTypes'; -import { NodeSkeleton } from '../api/ApiTypes'; export type Node = { + /** + * Read all node types + * @returns {Promise} a promise that resolves to an array of node type objects + */ + readNodeTypes(): Promise; + /** + * Read all nodes + * @returns {Promise} a promise that resolves to an object containing an array of node objects + */ + readNodes(): Promise; + /** + * Read all nodes by type + * @param {string} nodeType node type + * @returns {Promise} a promise that resolves to an object containing an array of node objects of the requested type + */ + readNodesByType(nodeType: string): Promise; + /** + * Read node by uuid and type + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @returns {Promise} a promise that resolves to a node object + */ + readNode(nodeId: string, nodeType: string): Promise; + /** + * Create node by type + * @param {string} nodeType node type + * @param {NodeSkeleton} nodeData node object + * @returns {Promise} a promise that resolves to an object containing a node object + */ + createNode(nodeType: string, nodeData: NodeSkeleton): Promise; + /** + * Update or create node by uuid and type + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @param {NodeSkeleton} nodeData node object + * @returns {Promise} a promise that resolves to an object containing a node object + */ + updateNode( + nodeId: string, + nodeType: string, + nodeData: NodeSkeleton + ): Promise; + /** + * Delete node by uuid and type + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @returns {Promise} a promise that resolves to an object containing a node object + */ + deleteNode(nodeId: string, nodeType: string): Promise; /** * Find all node configuration objects that are no longer referenced by any tree - * @returns {Promise} a promise that resolves to an array of orphaned nodes + * @returns {Promise} a promise that resolves to an array of orphaned nodes */ - findOrphanedNodes(): Promise; + findOrphanedNodes(): Promise; /** * Remove orphaned nodes * @param {NodeSkeleton[]} orphanedNodes Pass in an array of orphaned node configuration objects to remove @@ -58,66 +110,63 @@ export type Node = { export default (state: State): Node => { return { - /** - * Find all node configuration objects that are no longer referenced by any tree - * @returns {Promise} a promise that resolves to an array of orphaned nodes - */ - async findOrphanedNodes(): Promise { + readNodeTypes(): Promise { + return readNodeTypes({ state }); + }, + async readNodes(): Promise { + return readNodes({ state }); + }, + async readNodesByType(nodeType: string): Promise { + return readNodesByType({ nodeType, state }); + }, + async readNode(nodeId: string, nodeType: string): Promise { + return readNode({ nodeId, nodeType, state }); + }, + async createNode( + nodeType: string, + nodeData: NodeSkeleton + ): Promise { + return createNode({ nodeType, nodeData, state }); + }, + async updateNode( + nodeId: string, + nodeType: string, + nodeData: NodeSkeleton + ): Promise { + return updateNode({ nodeId, nodeType, nodeData, state }); + }, + async deleteNode(nodeId: string, nodeType: string): Promise { + return deleteNode({ nodeId, nodeType, state }); + }, + async findOrphanedNodes(): Promise { return findOrphanedNodes({ state }); }, - - /** - * Remove orphaned nodes - * @param {NodeSkeleton[]} orphanedNodes Pass in an array of orphaned node configuration objects to remove - * @returns {Promise} a promise that resolves to an array nodes that encountered errors deleting - */ async removeOrphanedNodes( orphanedNodes: NodeSkeleton[] ): Promise { return removeOrphanedNodes({ orphanedNodes, state }); }, - - /** - * Analyze if a node is a premium node. - * @param {string} nodeType Node type - * @returns {boolean} True if the node type is premium, false otherwise. - */ isPremiumNode(nodeType: string): boolean { return isPremiumNode(nodeType); }, - - /** - * Analyze if a node is a cloud-only node. - * @param {string} nodeType Node type - * @returns {boolean} True if the node type is cloud-only, false otherwise. - */ isCloudOnlyNode(nodeType: string): boolean { return isCloudOnlyNode(nodeType); }, - - /** - * Analyze if a node is custom. - * @param {string} nodeType Node type - * @returns {boolean} True if the node type is custom, false otherwise. - */ isCustomNode(nodeType: string): boolean { return isCustomNode({ nodeType, state }); }, - - /** - * Get a node's classifications, which can be one or multiple of: - * - standard: can run on any instance of a ForgeRock platform - * - cloud: utilize nodes, which are exclusively available in the ForgeRock Identity Cloud - * - premium: utilizes nodes, which come at a premium - * @param {string} nodeType Node type - * @returns {NodeClassificationType[]} an array of one or multiple classifications - */ getNodeClassification(nodeType: string): NodeClassificationType[] { return getNodeClassification({ nodeType, state }); }, }; }; +export type NodeClassificationType = + | 'standard' + | 'custom' + | 'cloud' + | 'premium'; + export enum NodeClassification { STANDARD = 'standard', CUSTOM = 'custom', @@ -127,6 +176,135 @@ export enum NodeClassification { const containerNodes = ['PageNode', 'CustomPageNode']; +/** + * Read all node types + * @returns {Promise} a promise that resolves to an array of node type objects + */ +export async function readNodeTypes({ + state, +}: { + state: State; +}): Promise { + const { result } = await _getNodeTypes({ state }); + return result; +} + +/** + * Get all nodes + * @returns {Promise} a promise that resolves to an object containing an array of node objects + */ +export async function readNodes({ + state, +}: { + state: State; +}): Promise { + const { result } = await _getNodes({ state }); + return result; +} + +/** + * Read all nodes by type + * @param {string} nodeType node type + * @returns {Promise} a promise that resolves to an object containing an array of node objects of the requested type + */ +export async function readNodesByType({ + nodeType, + state, +}: { + nodeType: string; + state: State; +}): Promise { + const { result } = await _getNodesByType({ nodeType, state }); + return result; +} + +/** + * Read node + * @param {String} nodeId node uuid + * @param {String} nodeType node type + * @returns {Promise} a promise that resolves to a node object + */ +export async function readNode({ + nodeId, + nodeType, + state, +}: { + nodeId: string; + nodeType: string; + state: State; +}): Promise { + return _getNode({ nodeId, nodeType, state }); +} + +/** + * Create node + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @param {NodeSkeleton} nodeData node object + * @returns {Promise} a promise that resolves to an object containing a node object + */ +export async function createNode({ + nodeId, + nodeType, + nodeData, + state, +}: { + nodeId?: string; + nodeType: string; + nodeData: NodeSkeleton; + state: State; +}): Promise { + if (nodeId) { + try { + await readNode({ nodeId, nodeType, state }); + } catch (error) { + const result = await updateNode({ nodeId, nodeType, nodeData, state }); + return result; + } + throw new Error(`Node ${nodeId} already exists!`); + } + return _createNode({ nodeType, nodeData, state }); +} + +/** + * Put node by uuid and type + * @param {string} nodeId node uuid + * @param {string} nodeType node type + * @param {object} nodeData node object + * @returns {Promise} a promise that resolves to an object containing a node object + */ +export async function updateNode({ + nodeId, + nodeType, + nodeData, + state, +}: { + nodeId: string; + nodeType: string; + nodeData: NodeSkeleton; + state: State; +}): Promise { + return _putNode({ nodeId, nodeType, nodeData, state }); +} + +/** + * Delete node by uuid and type + * @param {String} nodeId node uuid + * @param {String} nodeType node type + * @returns {Promise} a promise that resolves to an object containing a node object + */ +export async function deleteNode({ + nodeId, + nodeType, + state, +}: { + nodeId: string; + nodeType: string; + state: State; +}): Promise { + return _deleteNode({ nodeId, nodeType, state }); +} + /** * Find all node configuration objects that are no longer referenced by any tree * @returns {Promise} a promise that resolves to an array of orphaned nodes @@ -135,7 +313,7 @@ export async function findOrphanedNodes({ state, }: { state: State; -}): Promise { +}): Promise { const allNodes = []; const orphanedNodes = []; let types = []; @@ -150,20 +328,16 @@ export async function findOrphanedNodes({ state, }); try { - types = (await getNodeTypes({ state })).result; + types = (await _getNodeTypes({ state })).result; } catch (error) { - printMessage({ - message: 'Error retrieving all available node types:', - type: 'error', - state, - }); - printMessage({ message: error.response.data, type: 'error', state }); - return []; + error.message = `Error retrieving all available node types: ${ + error.response?.data?.message || error.message + }`; + throw error; } for (const type of types) { try { - // eslint-disable-next-line no-await-in-loop, no-loop-func - const nodes = (await getNodesByType({ nodeType: type._id, state })) + const nodes = (await _getNodesByType({ nodeType: type._id, state })) .result; for (const node of nodes) { allNodes.push(node); @@ -212,7 +386,7 @@ export async function findOrphanedNodes({ }); const node = journey.nodes[nodeId]; if (containerNodes.includes(node.nodeType)) { - const containerNode = await getNode({ + const containerNode = await _getNode({ nodeId, nodeType: node.nodeType, state, diff --git a/src/ops/OAuth2ClientOps.ts b/src/ops/OAuth2ClientOps.ts index 0b3cf0f07..eac3d5281 100644 --- a/src/ops/OAuth2ClientOps.ts +++ b/src/ops/OAuth2ClientOps.ts @@ -1,20 +1,20 @@ +import { type NoIdObjectSkeletonInterface } from '../api/ApiTypes'; import { + deleteOAuth2Client as _deleteOAuth2Client, getOAuth2Client as _getOAuth2Client, getOAuth2Clients as _getOAuth2Clients, + type OAuth2ClientSkeleton, putOAuth2Client as _putOAuth2Client, - deleteOAuth2Client as _deleteOAuth2Client, } from '../api/OAuth2ClientApi'; -import { ExportMetaData } from './OpsTypes'; -import { - NoIdObjectSkeletonInterface, - OAuth2ClientSkeleton, - ScriptSkeleton, -} from '../api/ApiTypes'; -import { getMetadata } from '../utils/ExportImportUtils'; +import { type ScriptSkeleton } from '../api/ScriptApi'; +import { State } from '../shared/State'; import { debugMessage, printMessage } from '../utils/Console'; -import { convertBase64TextToArray } from '../utils/ExportImportUtils'; +import { + convertBase64TextToArray, + getMetadata, +} from '../utils/ExportImportUtils'; import { readOAuth2Provider } from './OAuth2ProviderOps'; -import { State } from '../shared/State'; +import { ExportMetaData } from './OpsTypes'; import { readScript, updateScript } from './ScriptOps'; export type OAuth2Client = { diff --git a/src/ops/OAuth2OidcOps.ts b/src/ops/OAuth2OidcOps.ts index 77fbf605f..7747a7b59 100644 --- a/src/ops/OAuth2OidcOps.ts +++ b/src/ops/OAuth2OidcOps.ts @@ -1,11 +1,12 @@ -import { State } from '../shared/State'; +import { AxiosRequestConfig } from 'axios'; + import { accessToken, authorize, clientCredentialsGrant, getTokenInfo, } from '../api/OAuth2OIDCApi'; -import { AxiosRequestConfig } from 'axios'; +import { State } from '../shared/State'; export type OAuth2Oidc = { authorize( diff --git a/src/ops/OAuth2ProviderOps.ts b/src/ops/OAuth2ProviderOps.ts index 635f8a253..a8e05445d 100644 --- a/src/ops/OAuth2ProviderOps.ts +++ b/src/ops/OAuth2ProviderOps.ts @@ -1,9 +1,9 @@ import { - OAuth2ProviderSkeleton, - getOAuth2Provider as _getOAuth2Provider, createOAuth2Provider as _createOAuth2Provider, - putOAuth2Provider as _putOAuth2Provider, deleteOAuth2Provider as _deleteOAuth2Provider, + getOAuth2Provider as _getOAuth2Provider, + OAuth2ProviderSkeleton, + putOAuth2Provider as _putOAuth2Provider, } from '../api/OAuth2ProviderApi'; import { State } from '../shared/State'; diff --git a/src/ops/OpsTypes.ts b/src/ops/OpsTypes.ts index b459c910b..b1fd07ce6 100644 --- a/src/ops/OpsTypes.ts +++ b/src/ops/OpsTypes.ts @@ -1,45 +1,3 @@ -import { - AgentSkeleton, - AmServiceSkeleton, - CircleOfTrustSkeleton, - NodeSkeleton, - ScriptSkeleton, - ThemeSkeleton, - TreeSkeleton, -} from '../api/ApiTypes'; -import { Saml2ProviderSkeleton } from '../api/Saml2Api'; -import { SocialIdpSkeleton } from '../api/SocialIdentityProvidersApi'; -import { State } from '../shared/State'; -import { EmailTemplateSkeleton } from './EmailTemplateOps'; - -/** - * Tree export options - */ -export interface TreeExportOptions { - /** - * Where applicable, use string arrays to store multi-line text (e.g. scripts). - */ - useStringArrays: boolean; - /** - * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes). - */ - deps: boolean; -} - -/** - * Tree import options - */ -export interface TreeImportOptions { - /** - * Generate new UUIDs for all nodes during import. - */ - reUuid: boolean; - /** - * Include any dependencies (scripts, email templates, SAML entity providers and circles of trust, social identity providers, themes). - */ - deps: boolean; -} - export interface ExportMetaData { origin: string; originAmVersion: string; @@ -48,85 +6,3 @@ export interface ExportMetaData { exportTool: string; exportToolVersion: string; } - -export interface SingleTreeExportInterface { - meta?: ExportMetaData; - innerNodes?: Record; - innernodes?: Record; - nodes: Record; - scripts: Record; - emailTemplates: Record; - socialIdentityProviders: Record; - themes: ThemeSkeleton[]; - saml2Entities: Record; - circlesOfTrust: Record; - tree: TreeSkeleton; -} - -export interface MultiTreeExportInterface { - meta?: ExportMetaData; - trees: Record; -} - -export interface AgentExportInterface { - meta?: Record; - agents: Record; -} - -export interface CirclesOfTrustExportInterface { - meta?: ExportMetaData; - script: Record; - saml: { - hosted: Record; - remote: Record; - metadata: Record; - cot: Record; - }; -} - -export interface Saml2ExportInterface { - meta?: ExportMetaData; - script: Record; - saml: { - hosted: Record; - remote: Record; - metadata: Record; - }; -} - -export interface ServiceExportInterface { - meta?: Record; - service: Record; -} - -export interface TreeDependencyMapInterface { - [k: string]: TreeDependencyMapInterface[]; -} - -export interface TreeExportResolverInterface { - (treeId: string, state: State): Promise; -} - -export interface ScriptExportInterface { - meta?: ExportMetaData; - script: Record; -} - -export type NodeClassificationType = - | 'standard' - | 'custom' - | 'cloud' - | 'premium'; - -export type JourneyClassificationType = - | 'standard' - | 'custom' - | 'cloud' - | 'premium'; - -export class FrodoError extends Error { - constructor(message: string) { - super(message); - this.name = 'UnresolvedDependenciesError'; - } -} diff --git a/src/ops/OrganizationOps.ts b/src/ops/OrganizationOps.ts index bb7fc03d2..3ba9651f1 100644 --- a/src/ops/OrganizationOps.ts +++ b/src/ops/OrganizationOps.ts @@ -1,8 +1,8 @@ -import { State } from '../shared/State'; +import { IdObjectSkeletonInterface } from '../api/ApiTypes'; import { queryAllManagedObjectsByType } from '../api/ManagedObjectApi'; -import { printMessage } from '../utils/Console'; import Constants from '../shared/Constants'; -import { IdObjectSkeletonInterface } from '../api/ApiTypes'; +import { State } from '../shared/State'; +import { printMessage } from '../utils/Console'; export type Organization = { /** diff --git a/src/ops/PolicyOps.ts b/src/ops/PolicyOps.ts index a08dd93fe..33be57617 100644 --- a/src/ops/PolicyOps.ts +++ b/src/ops/PolicyOps.ts @@ -1,23 +1,25 @@ import { + deletePolicy as _deletePolicy, getPolicies as _getPolicies, getPoliciesByPolicySet as _getPoliciesByPolicySet, getPolicy as _getPolicy, - deletePolicy as _deletePolicy, + type PolicyCondition, + type PolicySkeleton, putPolicy as _putPolicy, } from '../api/PoliciesApi'; -import { readScript, updateScript } from './ScriptOps'; -import { convertBase64TextToArray } from '../utils/ExportImportUtils'; -import { ExportMetaData } from './OpsTypes'; +import { type PolicySetSkeleton } from '../api/PolicySetApi'; import { - PolicyCondition, - PolicySetSkeleton, - PolicySkeleton, - ResourceTypeSkeleton, - ScriptSkeleton, -} from '../api/ApiTypes'; -import { getMetadata } from '../utils/ExportImportUtils'; + getResourceType, + type ResourceTypeSkeleton, +} from '../api/ResourceTypesApi'; +import { type ScriptSkeleton } from '../api/ScriptApi'; +import { State } from '../shared/State'; import { debugMessage } from '../utils/Console'; -import { getResourceType } from '../api/ResourceTypesApi'; +import { + convertBase64TextToArray, + getMetadata, +} from '../utils/ExportImportUtils'; +import { type ExportMetaData } from './OpsTypes'; import { createPolicySet, readPolicySet, @@ -27,7 +29,7 @@ import { createResourceType as _createResourceType, updateResourceType, } from './ResourceTypeOps'; -import { State } from '../shared/State'; +import { readScript, updateScript } from './ScriptOps'; export type Policy = { /** diff --git a/src/ops/PolicySetOps.ts b/src/ops/PolicySetOps.ts index ad22c1658..a89d25308 100644 --- a/src/ops/PolicySetOps.ts +++ b/src/ops/PolicySetOps.ts @@ -1,29 +1,29 @@ +import { type PolicySkeleton } from '../api/PoliciesApi'; import { + createPolicySet as _createPolicySet, deletePolicySet as _deletePolicySet, - getPolicySets as _getPolicySets, getPolicySet as _getPolicySet, - createPolicySet as _createPolicySet, + getPolicySets as _getPolicySets, updatePolicySet as _updatePolicySet, } from '../api/PolicySetApi'; -import { updateScript } from './ScriptOps'; -import { convertBase64TextToArray } from '../utils/ExportImportUtils'; -import { ExportMetaData } from './OpsTypes'; -import { - PolicySetSkeleton, - PolicySkeleton, - ResourceTypeSkeleton, - ScriptSkeleton, -} from '../api/ApiTypes'; -import { getMetadata } from '../utils/ExportImportUtils'; -import { debugMessage } from '../utils/Console'; +import { type PolicySetSkeleton } from '../api/PolicySetApi'; import { getResourceType, putResourceType } from '../api/ResourceTypesApi'; +import { type ResourceTypeSkeleton } from '../api/ResourceTypesApi'; +import { type ScriptSkeleton } from '../api/ScriptApi'; +import { State } from '../shared/State'; +import { debugMessage } from '../utils/Console'; +import { + convertBase64TextToArray, + getMetadata, +} from '../utils/ExportImportUtils'; +import { ExportMetaData } from './OpsTypes'; import { findScriptUuids, - readPoliciesByPolicySet, getScripts, + readPoliciesByPolicySet, updatePolicy, } from './PolicyOps'; -import { State } from '../shared/State'; +import { updateScript } from './ScriptOps'; export type PolicySet = { /** diff --git a/src/ops/RealmOps.ts b/src/ops/RealmOps.ts index b08a91b05..845bec3e7 100644 --- a/src/ops/RealmOps.ts +++ b/src/ops/RealmOps.ts @@ -1,10 +1,10 @@ import { - RealmSkeleton, - getRealms as _getRealms, - getRealm as _getRealm, createRealm as _createRealm, - putRealm as _putRealm, deleteRealm as _deleteRealm, + getRealm as _getRealm, + getRealms as _getRealms, + putRealm as _putRealm, + RealmSkeleton, } from '../api/RealmApi'; import { State } from '../shared/State'; import { getRealmName } from '../utils/ForgeRockUtils'; diff --git a/src/ops/ResourceTypeOps.ts b/src/ops/ResourceTypeOps.ts index c40e49c93..114555e9b 100644 --- a/src/ops/ResourceTypeOps.ts +++ b/src/ops/ResourceTypeOps.ts @@ -1,16 +1,16 @@ import { + createResourceType as _createResourceType, deleteResourceType as _deleteResourceType, - getResourceTypes as _getResourceTypes, getResourceType as _getResourceType, getResourceTypeByName as _getResourceTypeByName, - createResourceType as _createResourceType, + getResourceTypes as _getResourceTypes, putResourceType as _putResourceType, + type ResourceTypeSkeleton, } from '../api/ResourceTypesApi'; -import { ExportMetaData } from './OpsTypes'; -import { ResourceTypeSkeleton } from '../api/ApiTypes'; -import { getMetadata } from '../utils/ExportImportUtils'; -import { debugMessage } from '../utils/Console'; import { State } from '../shared/State'; +import { debugMessage } from '../utils/Console'; +import { getMetadata } from '../utils/ExportImportUtils'; +import { ExportMetaData } from './OpsTypes'; export type ResourceType = { /** diff --git a/src/ops/Saml2Ops.ts b/src/ops/Saml2Ops.ts index 64398de56..c927cfa01 100644 --- a/src/ops/Saml2Ops.ts +++ b/src/ops/Saml2Ops.ts @@ -1,25 +1,24 @@ import { - Saml2ProiderLocation, - Saml2ProviderSkeleton, - Saml2ProviderStub, createProvider as _createProvider, - updateProvider as _updateProvider, - queryProviderStubs as _queryProviderStubs, + deleteProvider as _deleteProvider, getProvider as _getProviderByLocationAndId, getProviderMetadata as _getProviderMetadata, getProviderMetadataUrl as _getProviderMetadataUrl, getProviderStubs as _getProviderStubs, - deleteProvider as _deleteProvider, + queryProviderStubs as _queryProviderStubs, + type Saml2ProiderLocation, + type Saml2ProviderSkeleton, + type Saml2ProviderStub, + updateProvider as _updateProvider, } from '../api/Saml2Api'; -import { getScript } from '../api/ScriptApi'; +import { getScript, type ScriptSkeleton } from '../api/ScriptApi'; +import { State } from '../shared/State'; import { decode, decodeBase64Url, encode, encodeBase64Url, } from '../utils/Base64Utils'; -import { Saml2ExportInterface } from './OpsTypes'; -import { updateScript } from './ScriptOps'; import { debugMessage, printMessage } from '../utils/Console'; import { convertBase64TextToArray, @@ -28,8 +27,9 @@ import { convertTextArrayToBase64Url, getMetadata, } from '../utils/ExportImportUtils'; -import { State } from '../shared/State'; import { get } from '../utils/JsonUtils'; +import { type ExportMetaData } from './OpsTypes'; +import { updateScript } from './ScriptOps'; export type Saml2 = { /** @@ -259,6 +259,16 @@ export default (state: State): Saml2 => { }; }; +export interface Saml2ExportInterface { + meta?: ExportMetaData; + script: Record; + saml: { + hosted: Record; + remote: Record; + metadata: Record; + }; +} + // use a function vs a template variable to avoid problems in loops export function createSaml2ExportTemplate({ state, diff --git a/src/ops/ScriptOps.ts b/src/ops/ScriptOps.ts index 06ab9840d..f5e425da2 100644 --- a/src/ops/ScriptOps.ts +++ b/src/ops/ScriptOps.ts @@ -1,5 +1,15 @@ import { v4 as uuidv4 } from 'uuid'; -import { applyNameCollisionPolicy } from '../utils/ForgeRockUtils'; + +import { + deleteScript as _deleteScript, + getScript as _getScript, + getScriptByName as _getScriptByName, + getScripts as _getScripts, + putScript as _putScript, +} from '../api/ScriptApi'; +import { type ScriptSkeleton } from '../api/ScriptApi'; +import { type ExportMetaData } from '../ops/OpsTypes'; +import { State } from '../shared/State'; import { createProgressIndicator, debugMessage, @@ -8,22 +18,13 @@ import { updateProgressIndicator, verboseMessage, } from '../utils/Console'; -import { - getScript as _getScript, - getScriptByName as _getScriptByName, - getScripts as _getScripts, - putScript as _putScript, - deleteScript as _deleteScript, -} from '../api/ScriptApi'; import { convertBase64TextToArray, convertTextArrayToBase64, getMetadata, } from '../utils/ExportImportUtils'; -import { ScriptSkeleton } from '../api/ApiTypes'; -import { ExportMetaData } from '../ops/OpsTypes'; +import { applyNameCollisionPolicy } from '../utils/ForgeRockUtils'; import { validateScriptDecoded } from '../utils/ScriptValidationUtils'; -import { State } from '../shared/State'; export type Script = { /** diff --git a/src/ops/ServiceOps.ts b/src/ops/ServiceOps.ts index 522c8ce5f..2bdf1b99a 100644 --- a/src/ops/ServiceOps.ts +++ b/src/ops/ServiceOps.ts @@ -1,16 +1,17 @@ -import { AmServiceSkeleton, FullService } from '../api/ApiTypes'; import { + type AmServiceSkeleton, deleteService, deleteServiceNextDescendent, - getService, + type FullService, getListOfServices as _getListOfServices, + getService, getServiceDescendents, putService, putServiceNextDescendent, } from '../api/ServiceApi'; import { State } from '../shared/State'; -import { ServiceExportInterface } from './OpsTypes'; import { debugMessage, printMessage } from '../utils/Console'; +import { type ExportMetaData } from './OpsTypes'; export type Service = { createServiceExportTemplate(): ServiceExportInterface; @@ -187,6 +188,11 @@ export default (state: State): Service => { }; }; +export interface ServiceExportInterface { + meta?: Record; + service: Record; +} + /** * Create an empty service export template * @returns {SingleTreeExportInterface} an empty service export template diff --git a/src/ops/ThemeOps.ts b/src/ops/ThemeOps.ts index a66f1780d..e0a127d6c 100644 --- a/src/ops/ThemeOps.ts +++ b/src/ops/ThemeOps.ts @@ -1,10 +1,21 @@ -import { ThemeSkeleton, UiThemeRealmObject } from '../api/ApiTypes'; +import { type IdObjectSkeletonInterface } from '../api/ApiTypes'; import { getConfigEntity, putConfigEntity } from '../api/IdmConfigApi'; -import { getCurrentRealmName } from '../utils/ForgeRockUtils'; -import { debugMessage } from '../utils/Console'; import { State } from '../shared/State'; -import { ExportMetaData } from './OpsTypes'; +import { debugMessage } from '../utils/Console'; import { getMetadata } from '../utils/ExportImportUtils'; +import { getCurrentRealmName } from '../utils/ForgeRockUtils'; +import { ExportMetaData } from './OpsTypes'; + +export type ThemeSkeleton = IdObjectSkeletonInterface & { + name: string; + isDefault: boolean; + linkedTrees: string[]; +}; + +export type UiThemeRealmObject = IdObjectSkeletonInterface & { + name: string; + realm: Map; +}; export const THEMEREALM_ID = 'ui/themerealm'; diff --git a/src/ops/VersionUtils.ts b/src/ops/VersionUtils.ts index 94da1468d..8f0992147 100644 --- a/src/ops/VersionUtils.ts +++ b/src/ops/VersionUtils.ts @@ -1,8 +1,8 @@ import fs from 'fs'; import path from 'path'; -import { generateReleaseApi } from '../api/BaseApi'; - import { fileURLToPath } from 'url'; + +import { generateReleaseApi } from '../api/BaseApi'; import { State } from '../shared/State'; export type Version = { diff --git a/src/ops/cloud/AdminFederationOps.ts b/src/ops/cloud/AdminFederationOps.ts index 89cf76d56..623670ea7 100644 --- a/src/ops/cloud/AdminFederationOps.ts +++ b/src/ops/cloud/AdminFederationOps.ts @@ -1,15 +1,15 @@ import { - deleteProviderByTypeAndId, + type AdminFederationConfigSkeleton, + deleteProviderByTypeAndId as _deleteProviderByTypeAndId, getAdminFederationProviders as _getAdminFederationProviders, putProviderByTypeAndId as _putProviderByTypeAndId, } from '../../api/cloud/AdminFederationProvidersApi'; -import { ExportMetaData } from '../OpsTypes'; -import { AdminFederationConfigSkeleton } from '../../api/ApiTypes'; -import { getMetadata } from '../../utils/ExportImportUtils'; -import { debugMessage } from '../../utils/Console'; import { getConfigEntity, putConfigEntity } from '../../api/IdmConfigApi'; -import { State } from '../../shared/State'; import { SocialIdpSkeleton } from '../../api/SocialIdentityProvidersApi'; +import { State } from '../../shared/State'; +import { debugMessage } from '../../utils/Console'; +import { getMetadata } from '../../utils/ExportImportUtils'; +import { ExportMetaData } from '../OpsTypes'; export type AdminFederation = { /** @@ -413,7 +413,7 @@ export async function deleteAdminFederationProvider({ ); switch (foundProviders.length) { case 1: - return await deleteProviderByTypeAndId({ + return await _deleteProviderByTypeAndId({ providerType: foundProviders[0]._type._id, providerId: foundProviders[0]._id, state, diff --git a/src/ops/cloud/FeatureOps.ts b/src/ops/cloud/FeatureOps.ts index 513e42f34..bb05984ed 100644 --- a/src/ops/cloud/FeatureOps.ts +++ b/src/ops/cloud/FeatureOps.ts @@ -1,6 +1,6 @@ import { - getFeatures as _getFeatures, FeatureInterface, + getFeatures as _getFeatures, } from '../../api/cloud/FeatureApi'; import { State } from '../../shared/State'; import { debugMessage } from '../../utils/Console'; diff --git a/src/ops/cloud/LogOps.ts b/src/ops/cloud/LogOps.ts index 030fb8094..55775fa3b 100644 --- a/src/ops/cloud/LogOps.ts +++ b/src/ops/cloud/LogOps.ts @@ -1,17 +1,15 @@ -import { - LogApiKey, - LogEventPayloadSkeleton, - LogEventSkeleton, - PagedResult, -} from '../../api/ApiTypes'; +import { type PagedResult } from '../../api/ApiTypes'; import { createLogApiKey, deleteLogApiKey as _deleteLogApiKey, + fetch, + getLogApiKey, getLogApiKeys as _getLogApiKeys, getSources, + type LogApiKey, + type LogEventPayloadSkeleton, + type LogEventSkeleton, tail, - fetch, - getLogApiKey, } from '../../api/cloud/LogApi'; import { State } from '../../shared/State'; @@ -530,4 +528,4 @@ export async function deleteLogApiKeys({ return responses; } -export { tail, fetch, createLogApiKey }; +export { createLogApiKey, fetch, tail }; diff --git a/src/ops/cloud/SecretsOps.ts b/src/ops/cloud/SecretsOps.ts index 47b2d39de..dac69feae 100644 --- a/src/ops/cloud/SecretsOps.ts +++ b/src/ops/cloud/SecretsOps.ts @@ -7,9 +7,9 @@ import { getSecretVersions as _getSecretVersions, getVersionOfSecret as _getVersionOfSecret, putSecret as _putSecret, + SecretSkeleton, setSecretDescription as _setSecretDescription, setStatusOfVersionOfSecret as _setStatusOfVersionOfSecret, - SecretSkeleton, VersionOfSecretSkeleton, VersionOfSecretStatus, } from '../../api/cloud/SecretsApi'; @@ -382,12 +382,12 @@ export async function readSecrets({ } export { - _getSecret as readSecret, _putSecret as createSecret, - _setSecretDescription as updateSecretDescription, - _deleteSecret as deleteSecret, - _getSecretVersions as readVersionsOfSecret, _createNewVersionOfSecret as createVersionOfSecret, - _getVersionOfSecret as readVersionOfSecret, + _deleteSecret as deleteSecret, _deleteVersionOfSecret as deleteVersionOfSecret, + _getSecret as readSecret, + _getVersionOfSecret as readVersionOfSecret, + _getSecretVersions as readVersionsOfSecret, + _setSecretDescription as updateSecretDescription, }; diff --git a/src/ops/cloud/ServiceAccountOps.ts b/src/ops/cloud/ServiceAccountOps.ts index 90248a92b..69cabcc77 100644 --- a/src/ops/cloud/ServiceAccountOps.ts +++ b/src/ops/cloud/ServiceAccountOps.ts @@ -1,12 +1,12 @@ +import { IdObjectSkeletonInterface } from '../../api/ApiTypes'; import { createManagedObject, getManagedObject, } from '../../api/ManagedObjectApi'; -import { JwksInterface } from '../JoseOps'; -import { IdObjectSkeletonInterface } from '../../api/ApiTypes'; +import { State } from '../../shared/State'; import { debugMessage } from '../../utils/Console'; +import { JwksInterface } from '../JoseOps'; import { hasFeature } from './FeatureOps'; -import { State } from '../../shared/State'; export type ServiceAccount = { /** diff --git a/src/ops/cloud/StartupOps.ts b/src/ops/cloud/StartupOps.ts index ae02d1934..69b32d687 100644 --- a/src/ops/cloud/StartupOps.ts +++ b/src/ops/cloud/StartupOps.ts @@ -1,14 +1,14 @@ -import { - createProgressIndicator, - updateProgressIndicator, - stopProgressIndicator, -} from '../../utils/Console'; import { getStatus, initiateRestart, RestartStatus, } from '../../api/cloud/StartupApi'; import { State } from '../../shared/State'; +import { + createProgressIndicator, + stopProgressIndicator, + updateProgressIndicator, +} from '../../utils/Console'; import { readSecrets } from './SecretsOps'; import { readVariables } from './VariablesOps'; diff --git a/src/ops/cloud/VariablesOps.ts b/src/ops/cloud/VariablesOps.ts index 6131d1170..8d4df1584 100644 --- a/src/ops/cloud/VariablesOps.ts +++ b/src/ops/cloud/VariablesOps.ts @@ -1,11 +1,11 @@ import { - VariableExpressionType, - VariableSkeleton, deleteVariable as _deleteVariable, getVariable as _getVariable, getVariables as _getVariables, putVariable as _putVariable, setVariableDescription as _setVariableDescription, + VariableExpressionType, + VariableSkeleton, } from '../../api/cloud/VariablesApi'; import { State } from '../../shared/State'; import { debugMessage } from '../../utils/Console'; diff --git a/src/shared/State.ts b/src/shared/State.ts index f03067738..cb87b4a9b 100644 --- a/src/shared/State.ts +++ b/src/shared/State.ts @@ -1,8 +1,9 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; -import { JwkRsa } from '../ops/JoseOps'; + import { FeatureInterface } from '../api/cloud/FeatureApi'; +import { JwkRsa } from '../ops/JoseOps'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/src/test/mocks/ForgeRockApiMockEngine.ts b/src/test/mocks/ForgeRockApiMockEngine.ts index d06c12262..ce3499726 100644 --- a/src/test/mocks/ForgeRockApiMockEngine.ts +++ b/src/test/mocks/ForgeRockApiMockEngine.ts @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; + import { getTypedFilename } from '../../utils/ExportImportUtils'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/src/utils/AutoSetupPolly.ts b/src/utils/AutoSetupPolly.ts index 1a9372fc0..af8af1f83 100644 --- a/src/utils/AutoSetupPolly.ts +++ b/src/utils/AutoSetupPolly.ts @@ -1,13 +1,14 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import pollyJest from 'setup-polly-jest'; -import { Polly } from '@pollyjs/core'; -import { MODES } from '@pollyjs/utils'; import NodeHttpAdapter from '@pollyjs/adapter-node-http'; +import { Polly } from '@pollyjs/core'; import FSPersister from '@pollyjs/persister-fs'; -import { getTokens } from '../ops/AuthenticateOps'; -import { state } from '../index'; +import { MODES } from '@pollyjs/utils'; import { LogLevelDesc } from 'loglevel'; +import path from 'path'; +import pollyJest from 'setup-polly-jest'; +import { fileURLToPath } from 'url'; + +import { state } from '../index'; +import { getTokens } from '../ops/AuthenticateOps'; const { setupPolly } = pollyJest; Polly.register(NodeHttpAdapter); diff --git a/src/utils/DataProtection.ts b/src/utils/DataProtection.ts index 1d78ab225..53ec759ae 100644 --- a/src/utils/DataProtection.ts +++ b/src/utils/DataProtection.ts @@ -9,13 +9,14 @@ * +--------------------+-----------------------+----------------+----------------+ * This module doesn't take care of data persistence, it's assumed the consuming method/class/package will do so. */ -import fs, { promises as fsp } from 'fs'; import crypto from 'crypto'; +import fs, { promises as fsp } from 'fs'; import { homedir } from 'os'; import { promisify } from 'util'; -import { printMessage } from './Console'; + import Constants from '../shared/Constants'; import { State } from '../shared/State'; +import { printMessage } from './Console'; const scrypt = promisify(crypto.scrypt); // using WeakMaps for added security since it gets garbage collected diff --git a/src/utils/ExportImportUtils.test.ts b/src/utils/ExportImportUtils.test.ts index ebe3f76ac..7db494df0 100644 --- a/src/utils/ExportImportUtils.test.ts +++ b/src/utils/ExportImportUtils.test.ts @@ -47,77 +47,3 @@ test('convertTextArrayToBase64', () => { test('validateImport should always return true', () => { expect(validateImport(null)).not.toBe(false); }); - -// This function has no way to determine when its asnyc task is complete, suggest using callback or promises to allow for testing -describe.skip('file system based tests', () => { - afterAll(() => { - if (existsSync(PATH_TO_ARTIFACT)) { - rmSync(PATH_TO_ARTIFACT); - } - }); - - test('saveToFile should save a file to specified tmp directory with expected data format', async () => { - // Arrange - const id = `id-3021`; - const data = [ - { - id, - location: 'The Shire', - character: 'Gandalf', - words: 1064, - }, - ]; - - const expected = { - lotr: { - 'id-3021': { - id: 'id-3021', - location: 'The Shire', - character: 'Gandalf', - words: 1064, - }, - }, - }; - // Act - saveToFile({ - type: 'lotr', - data, - identifier: 'id', - filename: PATH_TO_ARTIFACT, - state, - }); - const resultingJSON = JSON.parse(readFileSync(PATH_TO_ARTIFACT, 'utf8')); - // Assert - expect(resultingJSON.lotr).toEqual(expected.lotr); - }); - - test('saveToFile should save a file with metadata', async () => { - // Arrange - const id = `id-3021`; - const data = [ - { - id, - location: 'The Shire', - character: 'Gandalf', - words: 1064, - }, - ]; - // Act - saveToFile({ - type: 'lotr', - data, - identifier: 'id', - filename: PATH_TO_ARTIFACT, - state, - }); - const resultingJSON = JSON.parse(readFileSync(PATH_TO_ARTIFACT, 'utf8')); - // Assert - expect(Object.keys(resultingJSON.meta)).toEqual([ - 'origin', - 'exportedBy', - 'exportDate', - 'exportTool', - 'exportToolVersion', - ]); - }); -}); diff --git a/src/utils/ExportImportUtils.ts b/src/utils/ExportImportUtils.ts index ede64fcd6..ac22abb65 100644 --- a/src/utils/ExportImportUtils.ts +++ b/src/utils/ExportImportUtils.ts @@ -1,19 +1,20 @@ import fs from 'fs'; import { lstat, readdir } from 'fs/promises'; import { join } from 'path'; +import { Reader } from 'properties-reader'; +import replaceall from 'replaceall'; import slugify from 'slugify'; + +import { ExportMetaData } from '../ops/OpsTypes'; +import Constants from '../shared/Constants'; +import { State } from '../shared/State'; import { decode, decodeBase64Url, encode, encodeBase64Url, } from './Base64Utils'; -import { State } from '../shared/State'; -import Constants from '../shared/Constants'; -import { ExportMetaData } from '../ops/OpsTypes'; import { debugMessage, printMessage } from './Console'; -import { Reader } from 'properties-reader'; -import replaceall from 'replaceall'; export type ExportImport = { getMetadata(): ExportMetaData; diff --git a/src/utils/ScriptValidationUtils.ts b/src/utils/ScriptValidationUtils.ts index 610c1e328..9e3d49d1c 100644 --- a/src/utils/ScriptValidationUtils.ts +++ b/src/utils/ScriptValidationUtils.ts @@ -1,8 +1,9 @@ import { parseScript } from 'esprima'; -import { ScriptSkeleton } from '../api/ApiTypes'; + +import { type ScriptSkeleton } from '../api/ScriptApi'; +import { State } from '../shared/State'; import { decode } from './Base64Utils'; import { printMessage } from './Console'; -import { State } from '../shared/State'; export type ScriptValidation = { validateScriptHooks(jsonData: object): boolean; diff --git a/src/utils/SetupPollyForFrodoLib.ts b/src/utils/SetupPollyForFrodoLib.ts index ab99e83d7..b1d890554 100644 --- a/src/utils/SetupPollyForFrodoLib.ts +++ b/src/utils/SetupPollyForFrodoLib.ts @@ -1,12 +1,13 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import { Polly } from '@pollyjs/core'; -import { MODES } from '@pollyjs/utils'; import NodeHttpAdapter from '@pollyjs/adapter-node-http'; +import { Polly } from '@pollyjs/core'; import FSPersister from '@pollyjs/persister-fs'; +import { MODES } from '@pollyjs/utils'; import { LogLevelDesc } from 'loglevel'; -import { debugMessage, printMessage } from './Console'; +import path from 'path'; +import { fileURLToPath } from 'url'; + import { State } from '../shared/State'; +import { debugMessage, printMessage } from './Console'; const __dirname = path.dirname(fileURLToPath(import.meta.url));