Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,453 changes: 788 additions & 665 deletions package-lock.json

Large diffs are not rendered by default.

331 changes: 1 addition & 330 deletions src/api/AmConfigApi.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { FrodoError } from '../ops/FrodoError';
import { ResultCallback } from '../ops/OpsTypes';
import Constants from '../shared/Constants';
import { State } from '../shared/State';
import { getResult } from '../utils/ExportImportUtils';
import {
getRealmPathGlobal,
getRealmsForExport,
getRealmUsingExportFormat,
} from '../utils/ForgeRockUtils';
import { getRealmPathGlobal } from '../utils/ForgeRockUtils';
import { AmConfigEntityInterface, PagedResult } from './ApiTypes';
import { generateAmApi } from './BaseApi';

Expand All @@ -27,135 +20,8 @@ export interface AmConfigEntitiesInterface {
wsEntity: AmConfigEntityInterface;
}

const ALL_DEPLOYMENTS = [
Constants.CLASSIC_DEPLOYMENT_TYPE_KEY,
Constants.CLOUD_DEPLOYMENT_TYPE_KEY,
Constants.FORGEOPS_DEPLOYMENT_TYPE_KEY,
];
const CLASSIC_DEPLOYMENT = [Constants.CLASSIC_DEPLOYMENT_TYPE_KEY];

const NEXT_DESCENDENTS_ACTION = 'nextdescendents';
const TRUE_QUERY_FILTER = 'true';

const DEFAULT_PROTOCOL = '2.1';

/**
* Consists of all AM entities that are not currently being exported elsewhere in Frodo.
* Endpoints and resource versions were scraped directly from the Amster entity reference documentation: https://backstage.forgerock.com/docs/amster/7.5/entity-reference/preface.html
*/
const AM_ENTITIES: Record<string, EntityInfo> = {
applicationTypes: {
realm: { path: '/applicationtypes', version: '1.0' },
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
readonly: true,
},
authenticationChains: {
realm: {
path: '/realm-config/authentication/chains',
version: '2.0',
importWithId: true,
queryFilter: TRUE_QUERY_FILTER,
},
global: {
path: '/global-config/authentication/chains',
version: '1.0',
deployments: CLASSIC_DEPLOYMENT,
},
deployments: ALL_DEPLOYMENTS,
},
authenticationModules: {
realm: { path: '/realm-config/authentication/modules', version: '2.0' },
global: {
path: '/global-config/authentication/modules',
version: '1.0',
deployments: CLASSIC_DEPLOYMENT,
},
deployments: ALL_DEPLOYMENTS,
action: NEXT_DESCENDENTS_ACTION,
readonly: true,
},
authenticationTreesConfiguration: {
global: {
path: '/global-config/authentication/authenticationtrees',
version: '1.0',
},
deployments: CLASSIC_DEPLOYMENT,
},
conditionTypes: {
realm: { path: '/conditiontypes', version: '1.0' },
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
readonly: true,
},
decisionCombiners: {
realm: { path: '/decisioncombiners', version: '1.0' },
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
readonly: true,
},
secrets: {
realm: { path: '/realm-config/secrets', version: '2.0' },
global: {
path: '/global-config/secrets',
version: '1.0',
deployments: CLASSIC_DEPLOYMENT,
},
deployments: ALL_DEPLOYMENTS,
action: NEXT_DESCENDENTS_ACTION,
readonly: true,
},
serverInformation: {
// Note: Amster documentation says to do this by realm, but it really should be global (the API explorer does it this way and it makes more sense)
global: { path: '/serverinfo/*', version: '2.0' },
deployments: ALL_DEPLOYMENTS,
readonly: true,
},
serverVersion: {
// Note: Amster documentation says to do this by realm, but it really should be global (the API explorer does it this way and it makes more sense)
global: { path: '/serverinfo/version', version: '1.0' },
deployments: ALL_DEPLOYMENTS,
readonly: true,
},
subjectAttributes: {
// Due to a bug with this endpoint, protocol 1.0 is the only way for it to work as of version 7.5.0.
realm: { path: '/subjectattributes', version: '1.0', protocol: '1.0' },
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
readonly: true,
},
subjectTypes: {
realm: { path: '/subjecttypes', version: '1.0' },
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
readonly: true,
},
webhookService: {
realm: {
path: '/realm-config/webhooks',
version: '2.0',
importWithId: true,
queryFilter: TRUE_QUERY_FILTER,
},
global: {
path: '/global-config/webhooks',
version: '1.0',
ifMatch: '*',
deployments: CLASSIC_DEPLOYMENT,
},
deployments: ALL_DEPLOYMENTS,
},
wsEntity: {
realm: {
path: '/realm-config/federation/entityproviders/ws',
version: '2.0',
importWithId: true,
},
deployments: ALL_DEPLOYMENTS,
queryFilter: TRUE_QUERY_FILTER,
},
};

function getApiConfig(protocol: string, version: string) {
return {
apiVersion: `protocol=${protocol},resource=${version}`,
Expand Down Expand Up @@ -268,105 +134,6 @@ export async function getConfigEntity({
}
}

/**
* Get all other AM config entities
* @param {boolean} includeReadOnly Include read only config in the export
* @param {boolean} onlyRealm Get config only from the active realm. If onlyGlobal is also active, then it will also get the global config.
* @param {boolean} onlyGlobal Get global config only. If onlyRealm is also active, then it will also get the active realm config.
* @param {ResultCallback} resultCallback Optional callback to process individual results
* @returns {Promise<ConfigSkeleton>} a promise that resolves to a config object containing global and realm config entities
*/
export async function getConfigEntities({
includeReadOnly = false,
onlyRealm = false,
onlyGlobal = false,
resultCallback = void 0,
state,
}: {
includeReadOnly: boolean;
onlyRealm: boolean;
onlyGlobal: boolean;
resultCallback: ResultCallback<ConfigEntitySkeleton>;
state: State;
}): Promise<ConfigSkeleton> {
const realms = await getRealmsForExport({ state });
const stateRealms = realms.map(getRealmUsingExportFormat);
const entities = {
global: {},
realm: Object.fromEntries(realms.map((r) => [r, {}])),
} as ConfigSkeleton;
for (const [key, entityInfo] of Object.entries(AM_ENTITIES)) {
if (!includeReadOnly && entityInfo.readonly) {
continue;
}
const deploymentAllowed =
entityInfo.deployments &&
entityInfo.deployments.includes(state.getDeploymentType());
if (
(onlyGlobal || !onlyRealm) &&
entityInfo.global &&
((entityInfo.global.deployments &&
entityInfo.global.deployments.includes(state.getDeploymentType())) ||
(entityInfo.global.deployments == undefined && deploymentAllowed))
) {
entities.global[key] = await getResult(
resultCallback,
`Error getting '${key}' from resource path '${entityInfo.global.path}'`,
getConfigEntity,
{
state,
path: entityInfo.global.path,
version: entityInfo.global.version,
protocol: entityInfo.global.protocol,
queryFilter: entityInfo.global.queryFilter
? entityInfo.global.queryFilter
: entityInfo.queryFilter,
action: entityInfo.global.action
? entityInfo.global.action
: entityInfo.action,
}
);
}
if (
(!onlyGlobal || onlyRealm) &&
entityInfo.realm &&
((entityInfo.realm.deployments &&
entityInfo.realm.deployments.includes(state.getDeploymentType())) ||
(entityInfo.realm.deployments == undefined && deploymentAllowed))
) {
const activeRealm = state.getRealm();
for (let i = 0; i < realms.length; i++) {
if (
onlyRealm &&
(activeRealm.startsWith('/') ? activeRealm : '/' + activeRealm) !==
stateRealms[i]
) {
continue;
}
entities.realm[realms[i]][key] = await getResult(
resultCallback,
`Error getting '${key}' from resource path '${entityInfo.realm.path}'`,
getConfigEntity,
{
state,
path: entityInfo.realm.path,
version: entityInfo.realm.version,
protocol: entityInfo.realm.protocol,
realm: stateRealms[i],
queryFilter: entityInfo.realm.queryFilter
? entityInfo.realm.queryFilter
: entityInfo.queryFilter,
action: entityInfo.realm.action
? entityInfo.realm.action
: entityInfo.action,
}
);
}
}
}
return entities;
}

/**
* Puts a single am config entity at the given realm and path
* @param {ConfigEntitySkeleton} entityData config entity object. If it's not provided, no import is performed.
Expand Down Expand Up @@ -422,99 +189,3 @@ export async function putConfigEntity({
);
}
}

/**
* Put all other AM config entities
* @param {ConfigSkeleton} config the config object containing global and realm config entities
* @param {ResultCallback} resultCallback Optional callback to process individual results
* @returns {Promise<ConfigSkeleton>} a promise that resolves to a config object containing global and realm config entities
*/
export async function putConfigEntities({
config,
resultCallback = void 0,
state,
}: {
config: ConfigSkeleton;
resultCallback: ResultCallback<ConfigEntitySkeleton>;
state: State;
}): Promise<ConfigSkeleton> {
const realms = config.realm ? Object.keys(config.realm) : [];
const stateRealms = realms.map(getRealmUsingExportFormat);
const entities = {
global: {},
realm: Object.fromEntries(realms.map((r) => [r, {}])),
} as ConfigSkeleton;
for (const [key, entityInfo] of Object.entries(AM_ENTITIES)) {
if (entityInfo.readonly) {
continue;
}
const deploymentAllowed =
entityInfo.deployments &&
entityInfo.deployments.includes(state.getDeploymentType());
if (
entityInfo.global &&
((entityInfo.global.deployments &&
entityInfo.global.deployments.includes(state.getDeploymentType())) ||
(entityInfo.global.deployments == undefined && deploymentAllowed)) &&
config.global &&
config.global[key]
) {
for (const [id, entityData] of Object.entries(config.global[key])) {
if (!entities.global[key]) {
entities.global[key] = {};
}
entities.global[key][id] = await getResult(
resultCallback,
`Error putting entity '${id}' of type '${key}' to global resource path '${entityInfo.global.path}'`,
putConfigEntity,
{
state,
entityData: entityData as ConfigEntitySkeleton,
path:
entityInfo.global.path +
(entityInfo.global.importWithId ? `/${id}` : ''),
version: entityInfo.global.version,
protocol: entityInfo.global.protocol,
ifMatch: entityInfo.global.ifMatch,
}
);
}
}
if (
entityInfo.realm &&
((entityInfo.realm.deployments &&
entityInfo.realm.deployments.includes(state.getDeploymentType())) ||
(entityInfo.realm.deployments == undefined && deploymentAllowed))
) {
for (let i = 0; i < realms.length; i++) {
if (!config.realm[realms[i]][key]) {
continue;
}
for (const [id, entityData] of Object.entries(
config.realm[realms[i]][key]
)) {
if (!entities.realm[realms[i]][key]) {
entities.realm[realms[i]][key] = {};
}
entities.realm[realms[i]][key][id] = await getResult(
resultCallback,
`Error putting entity '${id}' of type '${key}' to realm resource path '${entityInfo.realm.path}'`,
putConfigEntity,
{
state,
entityData: entityData as ConfigEntitySkeleton,
path:
entityInfo.realm.path +
(entityInfo.realm.importWithId ? `/${id}` : ''),
version: entityInfo.realm.version,
protocol: entityInfo.realm.protocol,
ifMatch: entityInfo.realm.ifMatch,
realm: stateRealms[i],
}
);
}
}
}
}
return entities;
}
Loading