From 3102be37494ab80c00e091df7d6c4e03eae6a423 Mon Sep 17 00:00:00 2001 From: Volker Scheuber Date: Wed, 18 Oct 2023 22:55:33 -0500 Subject: [PATCH 1/3] fixes #331 --- src/ops/CirclesOfTrustOps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ops/CirclesOfTrustOps.ts b/src/ops/CirclesOfTrustOps.ts index 5d9e243a8..475febd79 100644 --- a/src/ops/CirclesOfTrustOps.ts +++ b/src/ops/CirclesOfTrustOps.ts @@ -546,7 +546,7 @@ export async function importCirclesOfTrust({ hasEntityId = true; } } - if (entityProviders.length || hasEntityId) { + if (entityProviders.length === 0 || hasEntityId) { try { response.push(await createCircleOfTrust({ cotId, cotData, state })); } catch (createError) { From 429c2a2936f74883047c00920b308ea1e86ee72e Mon Sep 17 00:00:00 2001 From: Volker Scheuber Date: Wed, 18 Oct 2023 22:57:19 -0500 Subject: [PATCH 2/3] update package-lock.json --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3b4405fa..204ea63a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rockcarver/frodo-lib", - "version": "2.0.0-40", + "version": "2.0.0-42", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rockcarver/frodo-lib", - "version": "2.0.0-40", + "version": "2.0.0-42", "license": "MIT", "dependencies": { "@pollyjs/adapter-node-http": "^6.0.5", From a55c335d87a3acb9ae9082b317e53d6950a7c60c Mon Sep 17 00:00:00 2001 From: Volker Scheuber Date: Wed, 18 Oct 2023 23:29:34 -0500 Subject: [PATCH 3/3] resolves #328 --- src/api/BaseApi.ts | 2 +- src/api/ReconApi.ts | 244 ++++++++++++++++++++++++++++++++++++++++++++ src/lib/FrodoLib.ts | 3 + src/ops/ReconOps.ts | 119 +++++++++++++++++++++ 4 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 src/api/ReconApi.ts create mode 100644 src/ops/ReconOps.ts diff --git a/src/api/BaseApi.ts b/src/api/BaseApi.ts index eaa499d69..a53c78565 100644 --- a/src/api/BaseApi.ts +++ b/src/api/BaseApi.ts @@ -194,7 +194,7 @@ export function generateAmApi({ /** * Generates an OAuth2 Axios API instance - * @param {object} resource Takes an object takes a resource object. example: + * @param {object} resource Takes a resource object. example: * @param {object} requestOverride Takes an object of AXIOS parameters that can be used to either * add on extra information or override default properties https://github.com/axios/axios#request-config * diff --git a/src/api/ReconApi.ts b/src/api/ReconApi.ts new file mode 100644 index 000000000..c1f929766 --- /dev/null +++ b/src/api/ReconApi.ts @@ -0,0 +1,244 @@ +import util from 'util'; + +import { State } from '../shared/State'; +import { getHostBaseUrl } from '../utils/ForgeRockUtils'; +import { IdObjectSkeletonInterface } from './ApiTypes'; +import { generateIdmApi } from './BaseApi'; + +const apiVersion = 'resource=1.0'; +const apiConfig = { headers: { 'Accept-API-Version': apiVersion } }; + +const reconUrlTemplate = '%s/openidm/recon'; +const reconByIdUrlTemplate = '%s/openidm/recon/%s'; +const startReconUrlTemplate = '%s/openidm/recon?_action=recon&mapping=%s'; +const startReconByIdUrlTemplate = + '%s/openidm/recon?_action=reconById&mapping=%s&id=%s'; +const cancelReconUrlTemplate = '%s/openidm/recon/%s?_action=cancel'; + +export type ReconType = IdObjectSkeletonInterface & { + mapping: string; + state: 'SUCCESS' | string; + stage: 'COMPLETED_SUCCESS' | string; + stageDescription: string; + progress: { + source: { existing: { processed: number; total: string } }; + target: { + existing: { processed: number; total: string }; + created: number; + unchanged: number; + updated: number; + deleted: number; + }; + links: { existing: { processed: number; total: string }; created: number }; + }; + situationSummary: { + SOURCE_IGNORED: number; + TARGET_CHANGED: number; + SOURCE_TARGET_CONFLICT: number; + FOUND_ALREADY_LINKED: number; + UNQUALIFIED: number; + ABSENT: number; + TARGET_IGNORED: number; + MISSING: number; + ALL_GONE: number; + UNASSIGNED: number; + AMBIGUOUS: number; + CONFIRMED: number; + LINK_ONLY: number; + SOURCE_MISSING: number; + FOUND: number; + }; + statusSummary: { SUCCESS: number; FAILURE: number }; + durationSummary: { + sourceQuery: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + auditLog: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + defaultPropertyMapping: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + sourceLinkQuery: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + updateTargetObject: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + propertyMappingScript: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + updateLastSync: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + targetObjectQuery: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + sourcePhase: { + min: number; + max: number; + mean: number; + count: number; + sum: number; + stdDev: number; + }; + }; + parameters: { + sourceIds: [string]; + sourceQuery: { + resourceName: string; + _queryFilter: string; + _fields: string; + }; + targetQuery: { + resourceName: string; + queryFilter: string; + _fields: string; + }; + }; + started: string; + ended: string; + duration: number; + sourceProcessedByNode: object; +}; + +export type ReconStatusType = IdObjectSkeletonInterface & { + state: 'ACTIVE' | string; + action: 'cancel' | string; + status: 'INITIATED' | string; +}; + +export async function getRecons({ + state, +}: { + state: State; +}): Promise { + const urlString = util.format( + reconUrlTemplate, + getHostBaseUrl(state.getHost()) + ); + const { data } = await generateIdmApi({ + requestOverride: apiConfig, + state, + }).get(urlString); + return data; +} + +export async function getRecon({ + reconId, + state, +}: { + reconId: string; + state: State; +}): Promise { + const urlString = util.format( + reconByIdUrlTemplate, + getHostBaseUrl(state.getHost()), + reconId + ); + const { data } = await generateIdmApi({ + requestOverride: apiConfig, + state, + }).get(urlString); + return data; +} + +export async function startRecon({ + mappingName, + state, +}: { + mappingName: string; + state: State; +}): Promise { + const urlString = util.format( + startReconUrlTemplate, + getHostBaseUrl(state.getHost()), + mappingName + ); + const { data } = await generateIdmApi({ + requestOverride: apiConfig, + state, + }).post(urlString); + return data; +} + +export async function startReconById({ + mappingName, + objectId, + state, +}: { + mappingName: string; + objectId: string; + state: State; +}): Promise { + const urlString = util.format( + startReconByIdUrlTemplate, + getHostBaseUrl(state.getHost()), + mappingName, + objectId + ); + const { data } = await generateIdmApi({ + requestOverride: apiConfig, + state, + }).post(urlString); + return data; +} + +export async function cancelRecon({ + reconId, + state, +}: { + reconId: string; + state: State; +}): Promise { + const urlString = util.format( + cancelReconUrlTemplate, + getHostBaseUrl(state.getHost()), + reconId + ); + const { data } = await generateIdmApi({ + requestOverride: apiConfig, + state, + }).post(urlString); + return data; +} diff --git a/src/lib/FrodoLib.ts b/src/lib/FrodoLib.ts index 4bb04ba26..7d857ff60 100644 --- a/src/lib/FrodoLib.ts +++ b/src/lib/FrodoLib.ts @@ -36,6 +36,7 @@ import OrganizationOps, { Organization } from '../ops/OrganizationOps'; import PolicyOps, { Policy } from '../ops/PolicyOps'; import PolicySetOps, { PolicySet } from '../ops/PolicySetOps'; import RealmOps, { Realm } from '../ops/RealmOps'; +import ReconOps, { Recon } from '../ops/ReconOps'; import ResourceTypeOps, { ResourceType } from '../ops/ResourceTypeOps'; import Saml2Ops, { Saml2 } from '../ops/Saml2Ops'; import ScriptOps, { Script } from '../ops/ScriptOps'; @@ -95,6 +96,7 @@ export type Frodo = { managed: ManagedObject; mapping: Mapping; organization: Organization; + recon: Recon; system: IdmSystem; }; @@ -230,6 +232,7 @@ const FrodoLib = (config: StateInterface = {}): Frodo => { managed: ManagedObjectOps(state), mapping: MappingOps(state), organization: OrganizationOps(state), + recon: ReconOps(state), system: IdmSystemOps(state), }, diff --git a/src/ops/ReconOps.ts b/src/ops/ReconOps.ts new file mode 100644 index 000000000..3027b7e1e --- /dev/null +++ b/src/ops/ReconOps.ts @@ -0,0 +1,119 @@ +import { + cancelRecon as _cancelRecon, + getRecon as _getRecon, + getRecons as _getRecons, + ReconStatusType, + ReconType, + startRecon as _startRecon, + startReconById as _startReconById, +} from '../api/ReconApi'; +import { State } from '../shared/State'; + +export type Recon = { + /** + * Read all reconciliation runs + * @returns {Promise} a promise resolving to an array of recon objects + */ + readRecons(): Promise; + /** + * Read recon + * @param {string} reconId id of the recon + * @returns {Promise} a promise resolving to a recon object + */ + readRecon(reconId: string): Promise; + /** + * Start a reconciliation + * @param {string} mappingName mapping to reconcile + * @returns {Promise} a promise resolving to a recon status object + */ + startRecon(mappingName: string): Promise; + /** + * Start a reconciliation by Id + * @param {string} mappingName mapping to reconcile + * @param {string} objectId id of object to reconcile + * @returns {Promise} a promise resolving to a recon status object + */ + startReconById( + mappingName: string, + objectId: string + ): Promise; + /** + * Cancel a reconciliation + * @param {string} reconId id of the recon to cancel + * @returns {Promise} a promise resolving to a recon status object + */ + cancelRecon(reconId: string): Promise; +}; + +export default (state: State): Recon => { + return { + async readRecons(): Promise { + return readRecons({ state }); + }, + async readRecon(reconId: string): Promise { + return readRecon({ reconId, state }); + }, + async startRecon(mappingName: string): Promise { + return startRecon({ mappingName, state }); + }, + async startReconById( + mappingName: string, + objectId: string + ): Promise { + return startReconById({ mappingName, objectId, state }); + }, + async cancelRecon(reconId: string): Promise { + return cancelRecon({ reconId, state }); + }, + }; +}; + +export async function readRecons({ + state, +}: { + state: State; +}): Promise { + return _getRecons({ state }); +} + +export async function readRecon({ + reconId, + state, +}: { + reconId: string; + state: State; +}): Promise { + return _getRecon({ reconId, state }); +} + +export async function startRecon({ + mappingName, + state, +}: { + mappingName: string; + state: State; +}): Promise { + return _startRecon({ mappingName, state }); +} + +export async function startReconById({ + mappingName, + objectId, + state, +}: { + mappingName: string; + objectId: string; + state: State; +}): Promise { + return _startReconById({ mappingName, objectId, state }); +} + +export async function cancelRecon({ + reconId, + state, +}: { + reconId: string; + state: State; +}): Promise { + return _cancelRecon({ reconId, state }); +}