diff --git a/src/api/ApiTypes.ts b/src/api/ApiTypes.ts index d20782534..1a701b7c3 100644 --- a/src/api/ApiTypes.ts +++ b/src/api/ApiTypes.ts @@ -42,3 +42,20 @@ export type EntityType = IdObjectSkeletonInterface & { name: string; collection: boolean; }; + +/** + * See {@link https://backstage.forgerock.com/docs/idm/7.5/crest/crest-patch.html}. + */ +export interface PatchOperationInterface { + operation: + | 'add' + | 'copy' + | 'increment' + | 'move' + | 'remove' + | 'replace' + | 'transform'; + field: string; + value?: any; + from?: string; +} diff --git a/src/api/InternalObjectApi.ts b/src/api/InternalObjectApi.ts new file mode 100644 index 000000000..44bc76c78 --- /dev/null +++ b/src/api/InternalObjectApi.ts @@ -0,0 +1,256 @@ +import util from 'util'; + +import { State } from '../shared/State'; +import { getIdmBaseUrl } from '../utils/ForgeRockUtils'; +import { + IdObjectSkeletonInterface, + PagedResult, + PatchOperationInterface, +} from './ApiTypes'; +import { generateIdmApi } from './BaseApi'; + +const createInternalObjectURLTemplate = '%s/internal/%s?_action=create'; +const internalObjectByIdURLTemplate = '%s/internal/%s/%s'; +const queryAllInternalObjectURLTemplate = `%s/internal/%s?_queryFilter=true&_pageSize=%s`; +const queryInternalObjectURLTemplate = `%s/internal/%s?_queryFilter=%s&_pageSize=%s`; + +export const DEFAULT_PAGE_SIZE: number = 1000; + +/** + * Get internal object + * @param {string} type internal object type, e.g. alpha_user or user + * @param {string} id internal object id + * @param {string[]} id array of fields to include + * @param {State} state library state + * @returns {Promise} a promise that resolves to an ObjectSkeletonInterface + */ +export async function getInternalObject({ + type, + id, + fields = ['*'], + state, +}: { + type: string; + id: string; + fields: string[]; + state: State; +}): Promise { + const fieldsParam = `_fields=${fields.join(',')}`; + const urlString = util.format( + `${internalObjectByIdURLTemplate}?${fieldsParam}`, + getIdmBaseUrl(state), + type, + id + ); + const { data } = await generateIdmApi({ requestOverride: {}, state }).get( + urlString + ); + return data as IdObjectSkeletonInterface; +} + +/** + * Create internal object with server-generated id + * @param {string} ioType internal object type + * @param {IdObjectSkeletonInterface} ioData internal object data + * @param {State} state library state + * @returns {Promise} a promise that resolves to an object containing a internal object + */ +export async function createInternalObject({ + ioType, + ioData, + state, +}: { + ioType: string; + ioData: IdObjectSkeletonInterface; + state: State; +}): Promise { + const urlString = util.format( + createInternalObjectURLTemplate, + getIdmBaseUrl(state), + ioType + ); + const { data } = await generateIdmApi({ requestOverride: {}, state }).post( + urlString, + ioData + ); + return data; +} + +/** + * Create or update internal object + * @param {string} id internal object id + * @param {IdObjectSkeletonInterface} ioData internal object + * @param {boolean} failIfExists fail if exists + * @param {State} state library state + * @returns {Promise} a promise that resolves to an object containing a internal object + */ +export async function putInternalObject({ + type, + id, + ioData, + failIfExists = false, + state, +}: { + type: string; + id: string; + ioData: IdObjectSkeletonInterface; + failIfExists?: boolean; + state: State; +}): Promise { + const urlString = util.format( + internalObjectByIdURLTemplate, + getIdmBaseUrl(state), + type, + id + ); + const requestOverride = failIfExists + ? { headers: { 'If-None-Match': '*' } } + : {}; + const { data } = await generateIdmApi({ requestOverride, state }).put( + urlString, + ioData + ); + return data; +} + +/** + * Partially update an internal object, with an array of operations. + * @param {string} type internal object type + * @param {string} id internal object id + * @param {PatchOperationInterface[]} operations array of operations + * @param {string} rev revision + * @param {State} state library state + * @returns {Promise} a promise that resolves to an object containing an internal object + */ +export async function patchInternalObject({ + type, + id, + operations: operations, + rev = null, + state, +}: { + type: string; + id: string; + operations: PatchOperationInterface[]; + rev?: string; + state: State; +}): Promise { + const urlString = util.format( + internalObjectByIdURLTemplate, + getIdmBaseUrl(state), + type, + id + ); + const requestOverride = rev ? { headers: { 'If-Match': rev } } : {}; + const { data } = await generateIdmApi({ requestOverride, state }).patch( + urlString, + operations + ); + return data; +} + +/** + * Query internal object + * @param {string} type internal object type, e.g. alpha_user or user + * @param {string} filter CREST search filter + * @param {string[]} id array of fields to include + * @param {string} pageCookie paged results cookie + * @param {State} state library state + * @returns {Promise} a promise that resolves to an ObjectSkeletonInterface + */ +export async function queryInternalObjects({ + type, + filter, + fields = ['*'], + pageSize = DEFAULT_PAGE_SIZE, + pageCookie, + state, +}: { + type: string; + filter: string; + fields?: string[]; + pageSize?: number; + pageCookie?: string; + state: State; +}): Promise> { + const fieldsParam = `_fields=${fields.join(',')}`; + const urlString = util.format( + pageCookie + ? `${queryInternalObjectURLTemplate}&${fieldsParam}&_pagedResultsCookie=${encodeURIComponent( + pageCookie + )}` + : `${queryInternalObjectURLTemplate}&${fieldsParam}`, + getIdmBaseUrl(state), + type, + encodeURIComponent(filter), + pageSize + ); + const { data } = await generateIdmApi({ requestOverride: {}, state }).get( + urlString + ); + return data as PagedResult; +} + +/** + * Query internal objects + * @param {string} type internal object type + * @param {string[]} fields fields to retrieve + * @param {string} pageCookie paged results cookie + * @returns {Promise<{result: any[]; resultCount: number; pagedResultsCookie: any; totalPagedResultsPolicy: string; totalPagedResults: number; remainingPagedResults: number;}>} a promise that resolves to internal objects of the desired type + */ +export async function queryAllInternalObjectsByType({ + type, + fields = [], + pageSize = DEFAULT_PAGE_SIZE, + pageCookie = undefined, + state, +}: { + type: string; + fields?: string[]; + pageSize?: number; + pageCookie?: string; + state: State; +}): Promise> { + const fieldsParam = + fields.length > 0 ? `&_fields=${fields.join(',')}` : '&_fields=_id'; + const urlTemplate = pageCookie + ? `${queryAllInternalObjectURLTemplate}${fieldsParam}&_pagedResultsCookie=${encodeURIComponent( + pageCookie + )}` + : `${queryAllInternalObjectURLTemplate}${fieldsParam}`; + const urlString = util.format( + urlTemplate, + getIdmBaseUrl(state), + type, + pageSize + ); + const { data } = await generateIdmApi({ state }).get(urlString); + return data; +} + +/** + * Delete internal object + * @param {string} id internal object id + * @param {State} state library state + * @returns {Promise} a promise that resolves to an object containing a internal object + */ +export async function deleteInternalObject({ + type, + id, + state, +}: { + type: string; + id: string; + state: State; +}): Promise { + const urlString = util.format( + internalObjectByIdURLTemplate, + getIdmBaseUrl(state), + type, + id + ); + const { data } = await generateIdmApi({ requestOverride: {}, state }).delete( + urlString + ); + return data; +} diff --git a/src/api/ManagedObjectApi.ts b/src/api/ManagedObjectApi.ts index 36197aec6..e47bce510 100644 --- a/src/api/ManagedObjectApi.ts +++ b/src/api/ManagedObjectApi.ts @@ -2,7 +2,11 @@ import util from 'util'; import { State } from '../shared/State'; import { getIdmBaseUrl } from '../utils/ForgeRockUtils'; -import { IdObjectSkeletonInterface, PagedResult } from './ApiTypes'; +import { + IdObjectSkeletonInterface, + PagedResult, + PatchOperationInterface, +} from './ApiTypes'; import { generateIdmApi } from './BaseApi'; const createManagedObjectURLTemplate = '%s/managed/%s?_action=create'; @@ -12,23 +16,6 @@ const queryManagedObjectURLTemplate = `%s/managed/%s?_queryFilter=%s&_pageSize=% export const DEFAULT_PAGE_SIZE: number = 1000; -/** - * See {@link https://backstage.forgerock.com/docs/idm/7/rest-api-reference/sec-about-crest.html#about-crest-patch}. - */ -export interface ManagedObjectPatchOperationInterface { - operation: - | 'add' - | 'copy' - | 'increment' - | 'move' - | 'remove' - | 'replace' - | 'transform'; - field: string; - value?: any; - from?: string; -} - /** * Get managed object * @param {string} type managed object type, e.g. alpha_user or user @@ -130,7 +117,7 @@ export async function putManagedObject({ * Partially update a managed object, with an array of operations. * @param {string} type managed object type * @param {string} id managed object id - * @param {ManagedObjectPatchOperationInterface[]} operations array of operations + * @param {PatchOperationInterface[]} operations array of operations * @param {string} rev revision * @param {State} state library state * @returns {Promise} a promise that resolves to an object containing a managed object @@ -144,7 +131,7 @@ export async function patchManagedObject({ }: { type: string; id: string; - operations: ManagedObjectPatchOperationInterface[]; + operations: PatchOperationInterface[]; rev?: string; state: State; }): Promise { diff --git a/src/lib/FrodoLib.ts b/src/lib/FrodoLib.ts index e7eca135a..449c3b7dc 100644 --- a/src/lib/FrodoLib.ts +++ b/src/lib/FrodoLib.ts @@ -53,6 +53,7 @@ import IdmScriptOps, { IdmScript } from '../ops/IdmScriptOps'; import IdmSystemOps, { IdmSystem } from '../ops/IdmSystemOps'; import IdpOps, { Idp } from '../ops/IdpOps'; import InfoOps, { Info } from '../ops/InfoOps'; +import InternalRoleOps, { InternalRole } from '../ops/InternalRoleOps'; import JoseOps, { Jose } from '../ops/JoseOps'; import JourneyOps, { Journey } from '../ops/JourneyOps'; import ManagedObjectOps, { ManagedObject } from '../ops/ManagedObjectOps'; @@ -168,6 +169,8 @@ export type Frodo = { realm: Realm; + role: InternalRole; + saml2: { circlesOfTrust: CirclesOfTrust; entityProvider: Saml2; @@ -326,6 +329,8 @@ const FrodoLib = (config: StateInterface = {}): Frodo => { realm: RealmOps(state), + role: InternalRoleOps(state), + saml2: { circlesOfTrust: CirclesOfTrustOps(state), entityProvider: Saml2Ops(state), diff --git a/src/ops/InternalRoleOps.test.ts b/src/ops/InternalRoleOps.test.ts new file mode 100644 index 000000000..e8b1f13c4 --- /dev/null +++ b/src/ops/InternalRoleOps.test.ts @@ -0,0 +1,176 @@ +/** + * To record and update snapshots, you must perform 3 steps in order: + * + * 1. Record API responses + * + * To record API responses, you must call the test:record script and + * override all the connection state required to connect to the + * env to record from: + * + * ATTENTION: For the recording to succeed, you MUST make sure to use a + * user account, not a service account. + * + * FRODO_DEBUG=1 FRODO_HOST=frodo-dev npm run test:record InternalRoleOps + * + * The above command assumes that you have a connection profile for + * 'frodo-dev' on your development machine. + * + * 2. Update snapshots + * + * After recording API responses, you must manually update/create snapshots + * by running: + * + * FRODO_DEBUG=1 npm run test:update InternalRoleOps + * + * 3. Test your changes + * + * If 1 and 2 didn't produce any errors, you are ready to run the tests in + * replay mode and make sure they all succeed as well: + * + * FRODO_DEBUG=1 npm run test:only InternalRoleOps + * + * Note: FRODO_DEBUG=1 is optional and enables debug logging for some output + * in case things don't function as expected + */ +import { autoSetupPolly } from "../utils/AutoSetupPolly"; +import { filterRecording } from "../utils/PollyUtils"; +import * as InternalRoleOps from "./InternalRoleOps"; +import { state } from "../lib/FrodoLib"; + +const ctx = autoSetupPolly(); + +describe('InternalRoleOps', () => { + beforeEach(async () => { + if (process.env.FRODO_POLLY_MODE === 'record') { + ctx.polly.server.any().on('beforePersist', (_req, recording) => { + filterRecording(recording); + }); + } + }); + + describe('createInternalRoleExportTemplate()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.createInternalRoleExportTemplate).toBeDefined(); + }); + + test('1: Create InternalRole Export Template', async () => { + const response = InternalRoleOps.createInternalRoleExportTemplate({ state }); + expect(response).toMatchSnapshot({ + meta: expect.any(Object), + }); + }); + }); + + describe('createInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.createInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('readInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.readInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('readInternalRoleByName()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.readInternalRoleByName).toBeDefined(); + }); + //TODO: create tests + }); + + describe('readInternalRoles()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.readInternalRoles).toBeDefined(); + }); + test('1: Read internal roles', async () => { + const response = await InternalRoleOps.readInternalRoles({ state }); + expect(response).toMatchSnapshot(); + }); + }); + + describe('updateInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.updateInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('deleteInternalRoleByName()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.deleteInternalRoleByName).toBeDefined(); + }); + //TODO: create tests + }); + + describe('deleteInternalRoles()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.deleteInternalRoles).toBeDefined(); + }); + //TODO: create tests + }); + + describe('queryInternalRoles()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.queryInternalRoles).toBeDefined(); + }); + //TODO: create tests + }); + + describe('exportInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.exportInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('exportInternalRoleByName()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.exportInternalRoleByName).toBeDefined(); + }); + //TODO: create tests + }); + + describe('exportInternalRoles()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.exportInternalRoles).toBeDefined(); + }); + test('1: Export internal roles', async () => { + const response = await InternalRoleOps.exportInternalRoles({ state }); + expect(response).toMatchSnapshot({ + meta: expect.any(Object), + }); + }); + }); + + describe('importInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.importInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('importInternalRoleByName()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.importInternalRoleByName).toBeDefined(); + }); + //TODO: create tests + }); + + describe('importFirstInternalRole()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.importFirstInternalRole).toBeDefined(); + }); + //TODO: create tests + }); + + describe('importInternalRoles()', () => { + test('0: Method is implemented', async () => { + expect(InternalRoleOps.importInternalRoles).toBeDefined(); + }); + //TODO: create tests + }); +}); diff --git a/src/ops/InternalRoleOps.ts b/src/ops/InternalRoleOps.ts new file mode 100644 index 000000000..a5db0a42f --- /dev/null +++ b/src/ops/InternalRoleOps.ts @@ -0,0 +1,866 @@ +import { type IdObjectSkeletonInterface } from '../api/ApiTypes'; +import { + createInternalObject, + deleteInternalObject, + getInternalObject, + putInternalObject, + queryAllInternalObjectsByType, + queryInternalObjects, +} from '../api/InternalObjectApi'; +import { State } from '../shared/State'; +import { + createProgressIndicator, + debugMessage, + stopProgressIndicator, + updateProgressIndicator, +} from '../utils/Console'; +import { getMetadata } from '../utils/ExportImportUtils'; +import { FrodoError } from './FrodoError'; +import { type ExportMetaData } from './OpsTypes'; + +const defaultFields = [ + 'condition', + 'description', + 'name', + 'privileges', + 'temporalConstraints', +]; + +const INTERNAL_ROLE_TYPE = 'role'; + +export type InternalRole = { + /** + * Create an empty internal role export template + * @returns {InternalRoleExportInterface} an empty internal role export template + */ + createInternalRoleExportTemplate(): InternalRoleExportInterface; + /** + * Create internal role + * @param {InternalRoleSkeleton} roleData internal role data + * @returns {Promise} a promise that resolves to an internal role object + */ + createInternalRole( + roleData: InternalRoleSkeleton + ): Promise; + /** + * Read internal role + * @param {string} roleId internal role uuid + * @returns {Promise} a promise that resolves to an internal role object + */ + readInternalRole(roleId: string): Promise; + /** + * Read internal role by name + * @param {string} roleName internal role name + * @returns {Promise} a promise that resolves to an internal role object + */ + readInternalRoleByName(roleName: string): Promise; + /** + * Read all internal roles. Results are sorted aphabetically. + * @returns {Promise} a promise that resolves to an array of internal role objects + */ + readInternalRoles(): Promise; + /** + * Update internal role + * @param {string} roleId internal role uuid + * @param {InternalRoleSkeleton} roleData internal role data + * @returns {Promise} a promise that resolves to an internal role object + */ + updateInternalRole( + roleId: string, + roleData: InternalRoleSkeleton + ): Promise; + /** + * Delete internal role + * @param {string} roleId internal role uuid + * @param {boolean} deep deep delete (remove dependencies) + * @returns {Promise} a promise that resolves to an internal role object + */ + deleteInternalRole( + roleId: string, + deep?: boolean + ): Promise; + /** + * Delete internal role by name + * @param {string} roleName internal role name + * @param {boolean} deep deep delete (remove dependencies) + * @returns {Promise} a promise that resolves to an internal role object + */ + deleteInternalRoleByName( + roleName: string, + deep?: boolean + ): Promise; + /** + * Delete all internal roles + * @param {boolean} deep deep delete (remove dependencies) + * @returns {Promise} a promise that resolves to an array of internal role objects + */ + deleteInternalRoles(deep?: boolean): Promise; + /** + * Query internal roles + * @param filter CREST search filter + * @param fields array of fields to return + */ + queryInternalRoles( + filter: string, + fields?: string[] + ): Promise; + /** + * Export internal role. The response can be saved to file as is. + * @param {string} roleId internal role uuid + * @returns {Promise; + /** + * Export internal role by name. The response can be saved to file as is. + * @param {string} roleName internal role name + * @returns {Promise; + /** + * Export all internal roles. The response can be saved to file as is. + * @returns {Promise} Promise resolving to an InternalRoleExportInterface object. + */ + exportInternalRoles(): Promise; + /** + * Import internal role. The import data is usually read from an internal role export file. + * @param {string} roleId internal role uuid + * @param {InternalRoleExportInterface} importData internal role import data. + * @returns {Promise} Promise resolving to an internal role object. + */ + importInternalRole( + roleId: string, + importData: InternalRoleExportInterface + ): Promise; + /** + * Import internal role by name. The import data is usually read from an internal role export file. + * @param {string} roleName internal role name + * @param {InternalRoleExportInterface} importData internal role import data. + * @returns {Promise} Promise resolving to an internal role object. + */ + importInternalRoleByName( + roleName: string, + importData: InternalRoleExportInterface + ): Promise; + /** + * Import first internal role. The import data is usually read from an internal role export file. + * @param {InternalRoleExportInterface} importData internal role import data. + */ + importFirstInternalRole( + importData: InternalRoleExportInterface + ): Promise; + /** + * Import internal roles. The import data is usually read from an internal role export file. + * @param {InternalRoleExportInterface} importData internal role import data. + */ + importInternalRoles( + importData: InternalRoleExportInterface + ): Promise; +}; + +export default (state: State): InternalRole => { + return { + createInternalRoleExportTemplate(): InternalRoleExportInterface { + return createInternalRoleExportTemplate({ state }); + }, + async createInternalRole( + roleData: InternalRoleSkeleton + ): Promise { + return createInternalRole({ + roleData, + state, + }); + }, + async readInternalRole( + roleId: string, + fields = defaultFields + ): Promise { + return readInternalRole({ roleId, fields, state }); + }, + async readInternalRoleByName( + roleName: string, + fields = defaultFields + ): Promise { + return readInternalRoleByName({ roleName, fields, state }); + }, + async readInternalRoles(): Promise { + return readInternalRoles({ state }); + }, + async updateInternalRole( + roleId: string, + ioData: InternalRoleSkeleton + ): Promise { + return updateInternalRole({ + roleId, + roleData: ioData, + state, + }); + }, + async deleteInternalRole(roleId: string): Promise { + return deleteInternalRole({ roleId, state }); + }, + async deleteInternalRoleByName( + roleName: string + ): Promise { + return deleteInternalRoleByName({ + roleName, + state, + }); + }, + async deleteInternalRoles(): Promise { + return deleteInternalRoles({ state }); + }, + async queryInternalRoles( + filter: string, + fields: string[] = defaultFields + ): Promise { + return queryInternalRoles({ filter, fields, state }); + }, + async exportInternalRole( + roleId: string + ): Promise { + return exportInternalRole({ roleId, state }); + }, + async exportInternalRoleByName( + roleName: string + ): Promise { + return exportInternalRoleByName({ roleName, state }); + }, + async exportInternalRoles(): Promise { + return exportInternalRoles({ state }); + }, + async importInternalRole( + roleId: string, + importData: InternalRoleExportInterface + ): Promise { + return importInternalRole({ roleId, importData, state }); + }, + async importInternalRoleByName( + roleName: string, + importData: InternalRoleExportInterface + ): Promise { + return importInternalRoleByName({ + roleName, + importData, + state, + }); + }, + async importFirstInternalRole( + importData: InternalRoleExportInterface + ): Promise { + return importInternalRoles({ importData, state }); + }, + async importInternalRoles( + importData: InternalRoleExportInterface + ): Promise { + return importInternalRoles({ importData, state }); + }, + }; +}; + +export type InternalRoleSkeleton = IdObjectSkeletonInterface & { + condition: string; + description: string; + name: string; + privileges: { + accessFlags: { + attribute: string; + readOnly: boolean; + }[]; + actions: string[]; + filter: string; + name: string; + path: string; + permissions: string[]; + }[]; + temporalConstraints: { duration: string }[]; +}; + +/** + * Export format for internal roles + */ +export interface InternalRoleExportInterface { + /** + * Metadata + */ + meta?: ExportMetaData; + /** + * Internal roles + */ + internalRole: Record; +} + +export function createInternalRoleExportTemplate({ + state, +}: { + state: State; +}): InternalRoleExportInterface { + return { + meta: getMetadata({ state }), + internalRole: {}, + } as InternalRoleExportInterface; +} + +export async function createInternalRole({ + roleData, + state, +}: { + roleData: InternalRoleSkeleton; + state: State; +}): Promise { + try { + // Due to a bug with the temporal constraint policy, you cannot use empty arrays for temporalConstraints. However, deleting the array entirely before creating achieves the same effect. + if ( + roleData.temporalConstraints && + roleData.temporalConstraints.length === 0 + ) { + delete roleData.temporalConstraints; + } + const role = await createInternalObject({ + ioType: INTERNAL_ROLE_TYPE, + ioData: roleData, + state, + }); + return role as InternalRoleSkeleton; + } catch (error) { + throw new FrodoError(`Error creating internal role ${roleData._id}`, error); + } +} + +export async function readInternalRole({ + roleId, + fields = defaultFields, + state, +}: { + roleId: string; + fields?: string[]; + state: State; +}): Promise { + try { + const role = await getInternalObject({ + type: INTERNAL_ROLE_TYPE, + id: roleId, + fields, + state, + }); + return role as InternalRoleSkeleton; + } catch (error) { + throw new FrodoError(`Error reading internal role ${roleId}`, error); + } +} + +export async function readInternalRoleByName({ + roleName, + fields = defaultFields, + state, +}: { + roleName: string; + fields?: string[]; + state: State; +}): Promise { + try { + const roles = await queryInternalRoles({ + filter: `name eq '${roleName}'`, + fields, + state, + }); + switch (roles.length) { + case 1: + return roles[0]; + case 0: + throw new Error(`InternalRole '${roleName}' not found`); + default: + throw new Error(`${roles.length} internal roles '${roleName}' found`); + } + } catch (error) { + throw new FrodoError(`Error reading internal role ${roleName}`, error); + } +} + +export async function readInternalRoles({ + fields = defaultFields, + state, +}: { + fields?: string[]; + state: State; +}): Promise { + try { + const { result } = await queryAllInternalObjectsByType({ + type: INTERNAL_ROLE_TYPE, + fields, + state, + }); + return result as InternalRoleSkeleton[]; + } catch (error) { + throw new FrodoError(`Error reading internal roles`, error); + } +} + +export async function updateInternalRole({ + roleId, + roleData, + state, +}: { + roleId: string; + roleData: InternalRoleSkeleton; + state: State; +}): Promise { + try { + // Due to a bug with the temporal constraint policy, you cannot use empty arrays for temporalConstraints. However, deleting the array entirely before updating achieves the same effect. + if ( + roleData.temporalConstraints && + roleData.temporalConstraints.length === 0 + ) { + delete roleData.temporalConstraints; + } + const role = await putInternalObject({ + type: INTERNAL_ROLE_TYPE, + id: roleId, + ioData: roleData, + failIfExists: false, + state, + }); + return role as InternalRoleSkeleton; + } catch (error) { + throw new FrodoError(`Error updating internal role ${roleId}`, error); + } +} + +export async function deleteInternalRole({ + roleId, + state, +}: { + roleId: string; + state: State; +}): Promise { + try { + debugMessage({ + message: `InternalRoleOps.deleteInternalRole: start`, + state, + }); + const roleData: InternalRoleSkeleton = (await deleteInternalObject({ + type: INTERNAL_ROLE_TYPE, + id: roleId, + state, + })) as InternalRoleSkeleton; + debugMessage({ message: `InternalRoleOps.deleteInternalRole: end`, state }); + return roleData as InternalRoleSkeleton; + } catch (error) { + throw new FrodoError(`Error deleting internal role ${roleId}`, error); + } +} + +export async function deleteInternalRoleByName({ + roleName, + state, +}: { + roleName: string; + state: State; +}): Promise { + let roles: InternalRoleSkeleton[] = []; + try { + roles = await queryInternalRoles({ + filter: `name eq '${roleName}'`, + fields: ['_id'], + state, + }); + if (roles.length == 1) { + return deleteInternalRole({ + roleId: roles[0]._id, + state, + }); + } + } catch (error) { + throw new FrodoError(`Error deleting internal role ${roleName}`, error); + } + if (roles.length == 0) { + throw new FrodoError(`InternalRole '${roleName}' not found`); + } + if (roles.length > 1) { + throw new FrodoError(`${roles.length} internal roles '${roleName}' found`); + } +} + +export async function deleteInternalRoles({ + state, +}: { + state: State; +}): Promise { + const errors: Error[] = []; + try { + debugMessage({ + message: `InternalRoleOps.deleteInternalRoles: start`, + state, + }); + const roles = await readInternalRoles({ + state, + }); + const deleted: InternalRoleSkeleton[] = []; + for (const role of roles) { + debugMessage({ + message: `InternalRoleOps.deleteInternalRoles: '${role['_id']}'`, + state, + }); + try { + deleted.push( + await deleteInternalRole({ + roleId: role['_id'], + state, + }) + ); + } catch (error) { + errors.push(error); + } + } + if (errors.length) { + throw new FrodoError(`Error deleting internal roles`, errors); + } + debugMessage({ + message: `InternalRoleOps.deleteInternalRoles: end`, + state, + }); + return deleted; + } catch (error) { + // just re-throw previously caught errors + if (errors.length > 0) { + throw error; + } + throw new FrodoError(`Error deleting internal roles`, error); + } +} + +export async function queryInternalRoles({ + filter, + fields = defaultFields, + state, +}: { + filter: string; + fields?: string[]; + state: State; +}): Promise { + try { + const { result } = await queryInternalObjects({ + type: INTERNAL_ROLE_TYPE, + filter, + fields, + state, + }); + return result as InternalRoleSkeleton[]; + } catch (error) { + throw new FrodoError( + `Error querying internal roles with filter ${filter}`, + error + ); + } +} + +export async function exportInternalRole({ + roleId, + state, +}: { + roleId: string; + state: State; +}): Promise { + try { + debugMessage({ + message: `InternalRoleOps.exportInternalRole: start`, + state, + }); + const roleData = await readInternalRole({ roleId, state }); + const exportData = createInternalRoleExportTemplate({ state }); + exportData.internalRole[roleData._id] = roleData; + debugMessage({ message: `InternalRoleOps.exportInternalRole: end`, state }); + return exportData; + } catch (error) { + throw new FrodoError(`Error exporting internal role ${roleId}`, error); + } +} + +export async function exportInternalRoleByName({ + roleName, + state, +}: { + roleName: string; + state: State; +}): Promise { + try { + debugMessage({ + message: `InternalRoleOps.exportInternalRoleByName: start`, + state, + }); + const roleData = await readInternalRoleByName({ + roleName, + state, + }); + const exportData = createInternalRoleExportTemplate({ state }); + exportData.internalRole[roleData._id] = roleData; + debugMessage({ + message: `InternalRoleOps.exportInternalRoleByName: end`, + state, + }); + return exportData; + } catch (error) { + throw new FrodoError(`Error exporting internal role ${roleName}`, error); + } +} + +export async function exportInternalRoles({ + state, +}: { + state: State; +}): Promise { + const errors: Error[] = []; + let indicatorId: string; + try { + debugMessage({ + message: `InternalRoleOps.exportInternalRole: start`, + state, + }); + const exportData = createInternalRoleExportTemplate({ state }); + const roles = await readInternalRoles({ state }); + indicatorId = createProgressIndicator({ + total: roles.length, + message: 'Exporting internal roles...', + state, + }); + for (const roleData of roles) { + updateProgressIndicator({ + id: indicatorId, + message: `Exporting internal role ${roleData.name}`, + state, + }); + exportData.internalRole[roleData._id] = roleData; + } + if (errors.length > 0) { + stopProgressIndicator({ + id: indicatorId, + message: `Error exporting internal roles`, + status: 'fail', + state, + }); + throw new FrodoError(`Error exporting internal roles`, errors); + } + stopProgressIndicator({ + id: indicatorId, + message: `Exported ${roles.length} internal roles`, + state, + }); + debugMessage({ message: `InternalRoleOps.exportInternalRole: end`, state }); + return exportData; + } catch (error) { + stopProgressIndicator({ + id: indicatorId, + message: `Error exporting internal roles`, + status: 'fail', + state, + }); + // just re-throw previously caught errors + if (errors.length > 0) { + throw error; + } + throw new FrodoError(`Error exporting internal roles`, error); + } +} + +/** + * Import internal role + * @param {string} clientId client id + * @param {InternalRoleExportInterface} importData import data + * @returns {Promise} a promise resolving to an oauth2 client + */ +export async function importInternalRole({ + roleId, + importData, + state, +}: { + roleId: string; + importData: InternalRoleExportInterface; + state: State; +}): Promise { + let response = null; + const errors = []; + const imported = []; + try { + for (const id of Object.keys(importData.internalRole)) { + if (id === roleId) { + try { + const roleData = importData.internalRole[id]; + delete roleData._provider; + delete roleData._rev; + response = await updateInternalRole({ + roleId, + roleData, + state, + }); + imported.push(id); + } catch (error) { + errors.push(error); + } + } + } + if (errors.length > 0) { + throw new FrodoError(`Error importing internal role ${roleId}`, errors); + } + if (0 === imported.length) { + throw new FrodoError( + `Import error:\n${roleId} not found in import data!` + ); + } + return response; + } catch (error) { + // just re-throw previously caught errors + if (errors.length > 0 || imported.length == 0) { + throw error; + } + throw new FrodoError(`Error importing internal role ${roleId}`, error); + } +} + +/** + * Import internal role + * @param {string} clientId client id + * @param {InternalRoleExportInterface} importData import data + * @returns {Promise} a promise resolving to an oauth2 client + */ +export async function importInternalRoleByName({ + roleName: roleName, + importData, + state, +}: { + roleName: string; + importData: InternalRoleExportInterface; + state: State; +}): Promise { + let response = null; + const errors = []; + const imported = []; + try { + for (const roleId of Object.keys(importData.internalRole)) { + if (importData.internalRole[roleId].name === roleName) { + try { + const roleData = importData.internalRole[roleId]; + delete roleData._provider; + delete roleData._rev; + response = await updateInternalRole({ + roleId, + roleData, + state, + }); + imported.push(roleId); + } catch (error) { + errors.push(error); + } + } + } + if (errors.length > 0) { + throw new FrodoError(`Error importing internal role ${roleName}`, errors); + } + if (0 === imported.length) { + throw new FrodoError( + `Import error:\n${roleName} not found in import data!` + ); + } + return response; + } catch (error) { + // just re-throw previously caught errors + if (errors.length > 0 || imported.length == 0) { + throw error; + } + throw new FrodoError(`Error importing internal role ${roleName}`, error); + } +} + +/** + * Import first internal role + * @param {InternalRoleExportInterface} importData import data + * @returns {Promise} a promise resolving to an array of oauth2 clients + */ +export async function importFirstInternalRole({ + importData, + state, +}: { + importData: InternalRoleExportInterface; + state: State; +}): Promise { + let response = null; + const errors = []; + const imported = []; + try { + for (const roleId of Object.keys(importData.internalRole)) { + try { + const roleData = importData.internalRole[roleId]; + delete roleData._provider; + delete roleData._rev; + response = await updateInternalRole({ + roleId, + roleData, + state, + }); + imported.push(roleId); + } catch (error) { + errors.push(error); + } + break; + } + if (errors.length > 0) { + throw new FrodoError(`Error importing first internal role`, errors); + } + if (0 === imported.length) { + throw new FrodoError( + `Import error:\nNo internal roles found in import data!` + ); + } + return response; + } catch (error) { + // just re-throw previously caught errors + if (errors.length > 0 || imported.length == 0) { + throw error; + } + throw new FrodoError(`Error importing first internal role`, error); + } +} + +/** + * Import internal roles + * @param {InternalRoleExportInterface} importData import data + * @returns {Promise} a promise resolving to an array of oauth2 clients + */ +export async function importInternalRoles({ + importData, + state, +}: { + importData: InternalRoleExportInterface; + state: State; +}): Promise { + const response = []; + const errors = []; + try { + for (const roleId of Object.keys(importData.internalRole)) { + const roleData = importData.internalRole[roleId]; + delete roleData._provider; + delete roleData._rev; + try { + response.push( + await updateInternalRole({ + roleId, + roleData, + state, + }) + ); + } catch (error) { + errors.push(error); + } + } + if (errors.length) { + throw new FrodoError(`Error importing internal roles`, errors); + } + return response; + } catch (error) { + // just re-throw previously caught errors + if (errors.length > 0) { + throw error; + } + throw new FrodoError(`Error importing internal roles`, error); + } +} diff --git a/src/ops/ManagedObjectOps.ts b/src/ops/ManagedObjectOps.ts index 8aeaea42a..1623072da 100644 --- a/src/ops/ManagedObjectOps.ts +++ b/src/ops/ManagedObjectOps.ts @@ -1,10 +1,13 @@ -import { IdObjectSkeletonInterface, PagedResult } from '../api/ApiTypes'; +import { + IdObjectSkeletonInterface, + PagedResult, + PatchOperationInterface, +} from '../api/ApiTypes'; import { createManagedObject as _createManagedObject, DEFAULT_PAGE_SIZE, deleteManagedObject as _deleteManagedObject, getManagedObject as _getManagedObject, - type ManagedObjectPatchOperationInterface, patchManagedObject as _patchManagedObject, putManagedObject as _putManagedObject, queryAllManagedObjectsByType, @@ -64,21 +67,21 @@ export type ManagedObject = { * Partially update managed object through a collection of patch operations. * @param {string} type managed object type, e.g. alpha_user or user * @param {string} id managed object id - * @param {ManagedObjectPatchOperationInterface[]} operations collection of patch operations to perform on the object + * @param {PatchOperationInterface[]} operations collection of patch operations to perform on the object * @param {string} rev managed object revision * @returns {Promise} a promise that resolves to an IdObjectSkeletonInterface */ updateManagedObjectProperties( type: string, id: string, - operations: ManagedObjectPatchOperationInterface[], + operations: PatchOperationInterface[], rev?: string ): Promise; /** * Partially update multiple managed object through a collection of patch operations. * @param {string} type managed object type, e.g. alpha_user or user * @param {string} filter CREST search filter - * @param {ManagedObjectPatchOperationInterface[]} operations collection of patch operations to perform on the object + * @param {PatchOperationInterface[]} operations collection of patch operations to perform on the object * @param {string} rev managed object revision * @param {number} pageSize page size * @returns {Promise} a promise that resolves to an IdObjectSkeletonInterface @@ -86,7 +89,7 @@ export type ManagedObject = { updateManagedObjectsProperties( type: string, filter: string, - operations: ManagedObjectPatchOperationInterface[], + operations: PatchOperationInterface[], rev?: string, pageSize?: number ): Promise; @@ -174,7 +177,7 @@ export default (state: State): ManagedObject => { async updateManagedObjectProperties( type: string, id: string, - operations: ManagedObjectPatchOperationInterface[], + operations: PatchOperationInterface[], rev?: string ): Promise { return updateManagedObjectProperties({ @@ -188,7 +191,7 @@ export default (state: State): ManagedObject => { async updateManagedObjectsProperties( type: string, filter: string, - operations: ManagedObjectPatchOperationInterface[], + operations: PatchOperationInterface[], rev?: string, pageSize: number = DEFAULT_PAGE_SIZE ): Promise { @@ -335,7 +338,7 @@ export async function updateManagedObjectProperties({ }: { type: string; id: string; - operations: ManagedObjectPatchOperationInterface[]; + operations: PatchOperationInterface[]; rev?: string; state: State; }): Promise { @@ -359,7 +362,7 @@ export async function updateManagedObjectsProperties({ }: { type: string; filter: string; - operations: ManagedObjectPatchOperationInterface[]; + operations: PatchOperationInterface[]; rev?: string; pageSize?: number; state: State; diff --git a/src/test/mock-recordings/InternalRoleOps_2094519708/exportInternalRoles_3818454568/1-Export-internal-roles_2198934816/recording.har b/src/test/mock-recordings/InternalRoleOps_2094519708/exportInternalRoles_3818454568/1-Export-internal-roles_2198934816/recording.har new file mode 100644 index 000000000..3c6ab0f87 --- /dev/null +++ b/src/test/mock-recordings/InternalRoleOps_2094519708/exportInternalRoles_3818454568/1-Export-internal-roles_2198934816/recording.har @@ -0,0 +1,162 @@ +{ + "log": { + "_recordingName": "InternalRoleOps/exportInternalRoles()/1: Export internal roles", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "9d5a767d3e7afd74ccd5fc8d823ed08c", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 0, + "cookies": [], + "headers": [ + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "user-agent", + "value": "@rockcarver/frodo-lib/3.0.0" + }, + { + "name": "x-forgerock-transactionid", + "value": "frodo-cfb60d11-4224-477c-8c4e-401424c886c2" + }, + { + "name": "authorization", + "value": "Bearer " + }, + { + "name": "accept-encoding", + "value": "gzip, compress, deflate, br" + }, + { + "name": "host", + "value": "openam-frodo-dev.forgeblocks.com" + } + ], + "headersSize": 2002, + "httpVersion": "HTTP/1.1", + "method": "GET", + "queryString": [ + { + "name": "_queryFilter", + "value": "true" + }, + { + "name": "_pageSize", + "value": "1000" + }, + { + "name": "_fields", + "value": "condition,description,name,privileges,temporalConstraints" + } + ], + "url": "https://openam-frodo-dev.forgeblocks.com/openidm/internal/role?_queryFilter=true&_pageSize=1000&_fields=condition%2Cdescription%2Cname%2Cprivileges%2CtemporalConstraints" + }, + "response": { + "bodySize": 5404, + "content": { + "mimeType": "application/json;charset=utf-8", + "size": 5404, + "text": "{\"result\":[{\"_id\":\"openidm-admin\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7867\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-admin\",\"description\":\"Administrative access\",\"temporalConstraints\":[]},{\"_id\":\"openidm-authorized\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7868\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-authorized\",\"description\":\"Basic minimum user\",\"temporalConstraints\":[]},{\"_id\":\"openidm-reg\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7871\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-reg\",\"description\":\"Anonymous access\",\"temporalConstraints\":[]},{\"_id\":\"openidm-cert\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7870\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-cert\",\"description\":\"Authenticated via certificate\",\"temporalConstraints\":[]},{\"_id\":\"openidm-tasks-manager\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7872\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-tasks-manager\",\"description\":\"Allowed to reassign workflow tasks\",\"temporalConstraints\":[]},{\"_id\":\"platform-provisioning\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7873\",\"privileges\":[],\"condition\":null,\"name\":\"platform-provisioning\",\"description\":\"Platform provisioning access\",\"temporalConstraints\":[]},{\"_id\":\"ccb11ba1-333b-4197-95db-89bb08a2ab56\",\"_rev\":\"e9f91cb0-4a02-4c2e-8612-5c13a5375282-2799\",\"privileges\":[{\"accessFlags\":[{\"attribute\":\"userName\",\"readOnly\":false},{\"attribute\":\"givenName\",\"readOnly\":false},{\"attribute\":\"cn\",\"readOnly\":false},{\"attribute\":\"sn\",\"readOnly\":false},{\"attribute\":\"mail\",\"readOnly\":false},{\"attribute\":\"profileImage\",\"readOnly\":true},{\"attribute\":\"description\",\"readOnly\":false},{\"attribute\":\"accountStatus\",\"readOnly\":true},{\"attribute\":\"telephoneNumber\",\"readOnly\":true},{\"attribute\":\"postalAddress\",\"readOnly\":true},{\"attribute\":\"city\",\"readOnly\":true},{\"attribute\":\"postalCode\",\"readOnly\":true},{\"attribute\":\"country\",\"readOnly\":true},{\"attribute\":\"stateProvince\",\"readOnly\":true},{\"attribute\":\"roles\",\"readOnly\":true},{\"attribute\":\"assignments\",\"readOnly\":true},{\"attribute\":\"groups\",\"readOnly\":true},{\"attribute\":\"applications\",\"readOnly\":true},{\"attribute\":\"manager\",\"readOnly\":true},{\"attribute\":\"authzRoles\",\"readOnly\":true},{\"attribute\":\"reports\",\"readOnly\":true},{\"attribute\":\"effectiveRoles\",\"readOnly\":true},{\"attribute\":\"effectiveAssignments\",\"readOnly\":true},{\"attribute\":\"effectiveGroups\",\"readOnly\":true},{\"attribute\":\"effectiveApplications\",\"readOnly\":true},{\"attribute\":\"lastSync\",\"readOnly\":true},{\"attribute\":\"kbaInfo\",\"readOnly\":true},{\"attribute\":\"preferences\",\"readOnly\":true},{\"attribute\":\"consentedMappings\",\"readOnly\":true},{\"attribute\":\"ownerOfOrg\",\"readOnly\":true},{\"attribute\":\"adminOfOrg\",\"readOnly\":true},{\"attribute\":\"memberOfOrg\",\"readOnly\":true},{\"attribute\":\"memberOfOrgIDs\",\"readOnly\":true},{\"attribute\":\"ownerOfApp\",\"readOnly\":true},{\"attribute\":\"frIndexedString1\",\"readOnly\":true},{\"attribute\":\"frIndexedString2\",\"readOnly\":true},{\"attribute\":\"frIndexedString3\",\"readOnly\":true},{\"attribute\":\"frIndexedString4\",\"readOnly\":true},{\"attribute\":\"frIndexedString5\",\"readOnly\":true},{\"attribute\":\"frUnindexedString1\",\"readOnly\":true},{\"attribute\":\"frUnindexedString2\",\"readOnly\":true},{\"attribute\":\"frUnindexedString3\",\"readOnly\":true},{\"attribute\":\"frUnindexedString4\",\"readOnly\":true},{\"attribute\":\"frUnindexedString5\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued1\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued2\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued3\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued4\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued5\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued1\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued2\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued3\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued4\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued5\",\"readOnly\":true},{\"attribute\":\"frIndexedDate1\",\"readOnly\":true},{\"attribute\":\"frIndexedDate2\",\"readOnly\":true},{\"attribute\":\"frIndexedDate3\",\"readOnly\":true},{\"attribute\":\"frIndexedDate4\",\"readOnly\":true},{\"attribute\":\"frIndexedDate5\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate1\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate2\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate3\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate4\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate5\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger1\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger2\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger3\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger4\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger5\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger1\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger2\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger3\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger4\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger5\",\"readOnly\":true},{\"attribute\":\"assignedDashboard\",\"readOnly\":true}],\"actions\":[],\"filter\":\"/userName co \\\"test\\\"\",\"name\":\"Alpha realm - Users\",\"path\":\"managed/alpha_user\",\"permissions\":[\"VIEW\",\"UPDATE\",\"CREATE\"]}],\"condition\":\"/description co \\\"somerandomstring\\\"\",\"name\":\"test-internal-role\",\"description\":\"A test internal role\",\"temporalConstraints\":[{\"duration\":\"2024-11-04T12:45:00.000Z/2100-12-01T12:45:00.000Z\"}]}],\"resultCount\":7,\"pagedResultsCookie\":null,\"totalPagedResultsPolicy\":\"NONE\",\"totalPagedResults\":-1,\"remainingPagedResults\":-1}" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Tue, 12 Nov 2024 22:43:16 GMT" + }, + { + "name": "cache-control", + "value": "no-store" + }, + { + "name": "content-security-policy", + "value": "default-src 'none';frame-ancestors 'none';sandbox" + }, + { + "name": "content-type", + "value": "application/json;charset=utf-8" + }, + { + "name": "cross-origin-opener-policy", + "value": "same-origin" + }, + { + "name": "cross-origin-resource-policy", + "value": "same-origin" + }, + { + "name": "expires", + "value": "0" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "x-frame-options", + "value": "DENY" + }, + { + "name": "content-length", + "value": "5404" + }, + { + "name": "x-forgerock-transactionid", + "value": "frodo-cfb60d11-4224-477c-8c4e-401424c886c2" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubDomains; preload;" + }, + { + "name": "x-robots-tag", + "value": "none" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" + } + ], + "headersSize": 617, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-11-12T22:43:16.698Z", + "time": 62, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 62 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/src/test/mock-recordings/InternalRoleOps_2094519708/readInternalRoles_1491997246/1-Read-internal-roles_2521945934/recording.har b/src/test/mock-recordings/InternalRoleOps_2094519708/readInternalRoles_1491997246/1-Read-internal-roles_2521945934/recording.har new file mode 100644 index 000000000..8a46ef54a --- /dev/null +++ b/src/test/mock-recordings/InternalRoleOps_2094519708/readInternalRoles_1491997246/1-Read-internal-roles_2521945934/recording.har @@ -0,0 +1,162 @@ +{ + "log": { + "_recordingName": "InternalRoleOps/readInternalRoles()/1: Read internal roles", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "9d5a767d3e7afd74ccd5fc8d823ed08c", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 0, + "cookies": [], + "headers": [ + { + "name": "accept", + "value": "application/json, text/plain, */*" + }, + { + "name": "content-type", + "value": "application/json" + }, + { + "name": "user-agent", + "value": "@rockcarver/frodo-lib/3.0.0" + }, + { + "name": "x-forgerock-transactionid", + "value": "frodo-cfb60d11-4224-477c-8c4e-401424c886c2" + }, + { + "name": "authorization", + "value": "Bearer " + }, + { + "name": "accept-encoding", + "value": "gzip, compress, deflate, br" + }, + { + "name": "host", + "value": "openam-frodo-dev.forgeblocks.com" + } + ], + "headersSize": 2002, + "httpVersion": "HTTP/1.1", + "method": "GET", + "queryString": [ + { + "name": "_queryFilter", + "value": "true" + }, + { + "name": "_pageSize", + "value": "1000" + }, + { + "name": "_fields", + "value": "condition,description,name,privileges,temporalConstraints" + } + ], + "url": "https://openam-frodo-dev.forgeblocks.com/openidm/internal/role?_queryFilter=true&_pageSize=1000&_fields=condition%2Cdescription%2Cname%2Cprivileges%2CtemporalConstraints" + }, + "response": { + "bodySize": 5404, + "content": { + "mimeType": "application/json;charset=utf-8", + "size": 5404, + "text": "{\"result\":[{\"_id\":\"openidm-admin\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7867\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-admin\",\"description\":\"Administrative access\",\"temporalConstraints\":[]},{\"_id\":\"openidm-authorized\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7868\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-authorized\",\"description\":\"Basic minimum user\",\"temporalConstraints\":[]},{\"_id\":\"openidm-reg\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7871\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-reg\",\"description\":\"Anonymous access\",\"temporalConstraints\":[]},{\"_id\":\"openidm-cert\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7870\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-cert\",\"description\":\"Authenticated via certificate\",\"temporalConstraints\":[]},{\"_id\":\"openidm-tasks-manager\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7872\",\"privileges\":[],\"condition\":null,\"name\":\"openidm-tasks-manager\",\"description\":\"Allowed to reassign workflow tasks\",\"temporalConstraints\":[]},{\"_id\":\"platform-provisioning\",\"_rev\":\"82b3fa07-ef32-4716-92fc-ce4402dd8944-7873\",\"privileges\":[],\"condition\":null,\"name\":\"platform-provisioning\",\"description\":\"Platform provisioning access\",\"temporalConstraints\":[]},{\"_id\":\"ccb11ba1-333b-4197-95db-89bb08a2ab56\",\"_rev\":\"e9f91cb0-4a02-4c2e-8612-5c13a5375282-2799\",\"privileges\":[{\"accessFlags\":[{\"attribute\":\"userName\",\"readOnly\":false},{\"attribute\":\"givenName\",\"readOnly\":false},{\"attribute\":\"cn\",\"readOnly\":false},{\"attribute\":\"sn\",\"readOnly\":false},{\"attribute\":\"mail\",\"readOnly\":false},{\"attribute\":\"profileImage\",\"readOnly\":true},{\"attribute\":\"description\",\"readOnly\":false},{\"attribute\":\"accountStatus\",\"readOnly\":true},{\"attribute\":\"telephoneNumber\",\"readOnly\":true},{\"attribute\":\"postalAddress\",\"readOnly\":true},{\"attribute\":\"city\",\"readOnly\":true},{\"attribute\":\"postalCode\",\"readOnly\":true},{\"attribute\":\"country\",\"readOnly\":true},{\"attribute\":\"stateProvince\",\"readOnly\":true},{\"attribute\":\"roles\",\"readOnly\":true},{\"attribute\":\"assignments\",\"readOnly\":true},{\"attribute\":\"groups\",\"readOnly\":true},{\"attribute\":\"applications\",\"readOnly\":true},{\"attribute\":\"manager\",\"readOnly\":true},{\"attribute\":\"authzRoles\",\"readOnly\":true},{\"attribute\":\"reports\",\"readOnly\":true},{\"attribute\":\"effectiveRoles\",\"readOnly\":true},{\"attribute\":\"effectiveAssignments\",\"readOnly\":true},{\"attribute\":\"effectiveGroups\",\"readOnly\":true},{\"attribute\":\"effectiveApplications\",\"readOnly\":true},{\"attribute\":\"lastSync\",\"readOnly\":true},{\"attribute\":\"kbaInfo\",\"readOnly\":true},{\"attribute\":\"preferences\",\"readOnly\":true},{\"attribute\":\"consentedMappings\",\"readOnly\":true},{\"attribute\":\"ownerOfOrg\",\"readOnly\":true},{\"attribute\":\"adminOfOrg\",\"readOnly\":true},{\"attribute\":\"memberOfOrg\",\"readOnly\":true},{\"attribute\":\"memberOfOrgIDs\",\"readOnly\":true},{\"attribute\":\"ownerOfApp\",\"readOnly\":true},{\"attribute\":\"frIndexedString1\",\"readOnly\":true},{\"attribute\":\"frIndexedString2\",\"readOnly\":true},{\"attribute\":\"frIndexedString3\",\"readOnly\":true},{\"attribute\":\"frIndexedString4\",\"readOnly\":true},{\"attribute\":\"frIndexedString5\",\"readOnly\":true},{\"attribute\":\"frUnindexedString1\",\"readOnly\":true},{\"attribute\":\"frUnindexedString2\",\"readOnly\":true},{\"attribute\":\"frUnindexedString3\",\"readOnly\":true},{\"attribute\":\"frUnindexedString4\",\"readOnly\":true},{\"attribute\":\"frUnindexedString5\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued1\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued2\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued3\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued4\",\"readOnly\":true},{\"attribute\":\"frIndexedMultivalued5\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued1\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued2\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued3\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued4\",\"readOnly\":true},{\"attribute\":\"frUnindexedMultivalued5\",\"readOnly\":true},{\"attribute\":\"frIndexedDate1\",\"readOnly\":true},{\"attribute\":\"frIndexedDate2\",\"readOnly\":true},{\"attribute\":\"frIndexedDate3\",\"readOnly\":true},{\"attribute\":\"frIndexedDate4\",\"readOnly\":true},{\"attribute\":\"frIndexedDate5\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate1\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate2\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate3\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate4\",\"readOnly\":true},{\"attribute\":\"frUnindexedDate5\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger1\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger2\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger3\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger4\",\"readOnly\":true},{\"attribute\":\"frIndexedInteger5\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger1\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger2\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger3\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger4\",\"readOnly\":true},{\"attribute\":\"frUnindexedInteger5\",\"readOnly\":true},{\"attribute\":\"assignedDashboard\",\"readOnly\":true}],\"actions\":[],\"filter\":\"/userName co \\\"test\\\"\",\"name\":\"Alpha realm - Users\",\"path\":\"managed/alpha_user\",\"permissions\":[\"VIEW\",\"UPDATE\",\"CREATE\"]}],\"condition\":\"/description co \\\"somerandomstring\\\"\",\"name\":\"test-internal-role\",\"description\":\"A test internal role\",\"temporalConstraints\":[{\"duration\":\"2024-11-04T12:45:00.000Z/2100-12-01T12:45:00.000Z\"}]}],\"resultCount\":7,\"pagedResultsCookie\":null,\"totalPagedResultsPolicy\":\"NONE\",\"totalPagedResults\":-1,\"remainingPagedResults\":-1}" + }, + "cookies": [], + "headers": [ + { + "name": "date", + "value": "Tue, 12 Nov 2024 22:43:16 GMT" + }, + { + "name": "cache-control", + "value": "no-store" + }, + { + "name": "content-security-policy", + "value": "default-src 'none';frame-ancestors 'none';sandbox" + }, + { + "name": "content-type", + "value": "application/json;charset=utf-8" + }, + { + "name": "cross-origin-opener-policy", + "value": "same-origin" + }, + { + "name": "cross-origin-resource-policy", + "value": "same-origin" + }, + { + "name": "expires", + "value": "0" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "x-frame-options", + "value": "DENY" + }, + { + "name": "content-length", + "value": "5404" + }, + { + "name": "x-forgerock-transactionid", + "value": "frodo-cfb60d11-4224-477c-8c4e-401424c886c2" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubDomains; preload;" + }, + { + "name": "x-robots-tag", + "value": "none" + }, + { + "name": "via", + "value": "1.1 google" + }, + { + "name": "alt-svc", + "value": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000" + } + ], + "headersSize": 617, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-11-12T22:43:16.582Z", + "time": 99, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 99 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/src/test/snapshots/ops/InternalRoleOps.test.js.snap b/src/test/snapshots/ops/InternalRoleOps.test.js.snap new file mode 100644 index 000000000..8784d5322 --- /dev/null +++ b/src/test/snapshots/ops/InternalRoleOps.test.js.snap @@ -0,0 +1,783 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InternalRoleOps createInternalRoleExportTemplate() 1: Create InternalRole Export Template 1`] = ` +{ + "internalRole": {}, + "meta": Any, +} +`; + +exports[`InternalRoleOps exportInternalRoles() 1: Export internal roles 1`] = ` +{ + "internalRole": { + "ccb11ba1-333b-4197-95db-89bb08a2ab56": { + "_id": "ccb11ba1-333b-4197-95db-89bb08a2ab56", + "_rev": "e9f91cb0-4a02-4c2e-8612-5c13a5375282-2799", + "condition": "/description co "somerandomstring"", + "description": "A test internal role", + "name": "test-internal-role", + "privileges": [ + { + "accessFlags": [ + { + "attribute": "userName", + "readOnly": false, + }, + { + "attribute": "givenName", + "readOnly": false, + }, + { + "attribute": "cn", + "readOnly": false, + }, + { + "attribute": "sn", + "readOnly": false, + }, + { + "attribute": "mail", + "readOnly": false, + }, + { + "attribute": "profileImage", + "readOnly": true, + }, + { + "attribute": "description", + "readOnly": false, + }, + { + "attribute": "accountStatus", + "readOnly": true, + }, + { + "attribute": "telephoneNumber", + "readOnly": true, + }, + { + "attribute": "postalAddress", + "readOnly": true, + }, + { + "attribute": "city", + "readOnly": true, + }, + { + "attribute": "postalCode", + "readOnly": true, + }, + { + "attribute": "country", + "readOnly": true, + }, + { + "attribute": "stateProvince", + "readOnly": true, + }, + { + "attribute": "roles", + "readOnly": true, + }, + { + "attribute": "assignments", + "readOnly": true, + }, + { + "attribute": "groups", + "readOnly": true, + }, + { + "attribute": "applications", + "readOnly": true, + }, + { + "attribute": "manager", + "readOnly": true, + }, + { + "attribute": "authzRoles", + "readOnly": true, + }, + { + "attribute": "reports", + "readOnly": true, + }, + { + "attribute": "effectiveRoles", + "readOnly": true, + }, + { + "attribute": "effectiveAssignments", + "readOnly": true, + }, + { + "attribute": "effectiveGroups", + "readOnly": true, + }, + { + "attribute": "effectiveApplications", + "readOnly": true, + }, + { + "attribute": "lastSync", + "readOnly": true, + }, + { + "attribute": "kbaInfo", + "readOnly": true, + }, + { + "attribute": "preferences", + "readOnly": true, + }, + { + "attribute": "consentedMappings", + "readOnly": true, + }, + { + "attribute": "ownerOfOrg", + "readOnly": true, + }, + { + "attribute": "adminOfOrg", + "readOnly": true, + }, + { + "attribute": "memberOfOrg", + "readOnly": true, + }, + { + "attribute": "memberOfOrgIDs", + "readOnly": true, + }, + { + "attribute": "ownerOfApp", + "readOnly": true, + }, + { + "attribute": "frIndexedString1", + "readOnly": true, + }, + { + "attribute": "frIndexedString2", + "readOnly": true, + }, + { + "attribute": "frIndexedString3", + "readOnly": true, + }, + { + "attribute": "frIndexedString4", + "readOnly": true, + }, + { + "attribute": "frIndexedString5", + "readOnly": true, + }, + { + "attribute": "frUnindexedString1", + "readOnly": true, + }, + { + "attribute": "frUnindexedString2", + "readOnly": true, + }, + { + "attribute": "frUnindexedString3", + "readOnly": true, + }, + { + "attribute": "frUnindexedString4", + "readOnly": true, + }, + { + "attribute": "frUnindexedString5", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued1", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued2", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued3", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued4", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued5", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued1", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued2", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued3", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued4", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued5", + "readOnly": true, + }, + { + "attribute": "frIndexedDate1", + "readOnly": true, + }, + { + "attribute": "frIndexedDate2", + "readOnly": true, + }, + { + "attribute": "frIndexedDate3", + "readOnly": true, + }, + { + "attribute": "frIndexedDate4", + "readOnly": true, + }, + { + "attribute": "frIndexedDate5", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate1", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate2", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate3", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate4", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate5", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger1", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger2", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger3", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger4", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger5", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger1", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger2", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger3", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger4", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger5", + "readOnly": true, + }, + { + "attribute": "assignedDashboard", + "readOnly": true, + }, + ], + "actions": [], + "filter": "/userName co "test"", + "name": "Alpha realm - Users", + "path": "managed/alpha_user", + "permissions": [ + "VIEW", + "UPDATE", + "CREATE", + ], + }, + ], + "temporalConstraints": [ + { + "duration": "2024-11-04T12:45:00.000Z/2100-12-01T12:45:00.000Z", + }, + ], + }, + "openidm-admin": { + "_id": "openidm-admin", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7867", + "condition": null, + "description": "Administrative access", + "name": "openidm-admin", + "privileges": [], + "temporalConstraints": [], + }, + "openidm-authorized": { + "_id": "openidm-authorized", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7868", + "condition": null, + "description": "Basic minimum user", + "name": "openidm-authorized", + "privileges": [], + "temporalConstraints": [], + }, + "openidm-cert": { + "_id": "openidm-cert", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7870", + "condition": null, + "description": "Authenticated via certificate", + "name": "openidm-cert", + "privileges": [], + "temporalConstraints": [], + }, + "openidm-reg": { + "_id": "openidm-reg", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7871", + "condition": null, + "description": "Anonymous access", + "name": "openidm-reg", + "privileges": [], + "temporalConstraints": [], + }, + "openidm-tasks-manager": { + "_id": "openidm-tasks-manager", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7872", + "condition": null, + "description": "Allowed to reassign workflow tasks", + "name": "openidm-tasks-manager", + "privileges": [], + "temporalConstraints": [], + }, + "platform-provisioning": { + "_id": "platform-provisioning", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7873", + "condition": null, + "description": "Platform provisioning access", + "name": "platform-provisioning", + "privileges": [], + "temporalConstraints": [], + }, + }, + "meta": Any, +} +`; + +exports[`InternalRoleOps readInternalRoles() 1: Read internal roles 1`] = ` +[ + { + "_id": "openidm-admin", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7867", + "condition": null, + "description": "Administrative access", + "name": "openidm-admin", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "openidm-authorized", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7868", + "condition": null, + "description": "Basic minimum user", + "name": "openidm-authorized", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "openidm-reg", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7871", + "condition": null, + "description": "Anonymous access", + "name": "openidm-reg", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "openidm-cert", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7870", + "condition": null, + "description": "Authenticated via certificate", + "name": "openidm-cert", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "openidm-tasks-manager", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7872", + "condition": null, + "description": "Allowed to reassign workflow tasks", + "name": "openidm-tasks-manager", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "platform-provisioning", + "_rev": "82b3fa07-ef32-4716-92fc-ce4402dd8944-7873", + "condition": null, + "description": "Platform provisioning access", + "name": "platform-provisioning", + "privileges": [], + "temporalConstraints": [], + }, + { + "_id": "ccb11ba1-333b-4197-95db-89bb08a2ab56", + "_rev": "e9f91cb0-4a02-4c2e-8612-5c13a5375282-2799", + "condition": "/description co "somerandomstring"", + "description": "A test internal role", + "name": "test-internal-role", + "privileges": [ + { + "accessFlags": [ + { + "attribute": "userName", + "readOnly": false, + }, + { + "attribute": "givenName", + "readOnly": false, + }, + { + "attribute": "cn", + "readOnly": false, + }, + { + "attribute": "sn", + "readOnly": false, + }, + { + "attribute": "mail", + "readOnly": false, + }, + { + "attribute": "profileImage", + "readOnly": true, + }, + { + "attribute": "description", + "readOnly": false, + }, + { + "attribute": "accountStatus", + "readOnly": true, + }, + { + "attribute": "telephoneNumber", + "readOnly": true, + }, + { + "attribute": "postalAddress", + "readOnly": true, + }, + { + "attribute": "city", + "readOnly": true, + }, + { + "attribute": "postalCode", + "readOnly": true, + }, + { + "attribute": "country", + "readOnly": true, + }, + { + "attribute": "stateProvince", + "readOnly": true, + }, + { + "attribute": "roles", + "readOnly": true, + }, + { + "attribute": "assignments", + "readOnly": true, + }, + { + "attribute": "groups", + "readOnly": true, + }, + { + "attribute": "applications", + "readOnly": true, + }, + { + "attribute": "manager", + "readOnly": true, + }, + { + "attribute": "authzRoles", + "readOnly": true, + }, + { + "attribute": "reports", + "readOnly": true, + }, + { + "attribute": "effectiveRoles", + "readOnly": true, + }, + { + "attribute": "effectiveAssignments", + "readOnly": true, + }, + { + "attribute": "effectiveGroups", + "readOnly": true, + }, + { + "attribute": "effectiveApplications", + "readOnly": true, + }, + { + "attribute": "lastSync", + "readOnly": true, + }, + { + "attribute": "kbaInfo", + "readOnly": true, + }, + { + "attribute": "preferences", + "readOnly": true, + }, + { + "attribute": "consentedMappings", + "readOnly": true, + }, + { + "attribute": "ownerOfOrg", + "readOnly": true, + }, + { + "attribute": "adminOfOrg", + "readOnly": true, + }, + { + "attribute": "memberOfOrg", + "readOnly": true, + }, + { + "attribute": "memberOfOrgIDs", + "readOnly": true, + }, + { + "attribute": "ownerOfApp", + "readOnly": true, + }, + { + "attribute": "frIndexedString1", + "readOnly": true, + }, + { + "attribute": "frIndexedString2", + "readOnly": true, + }, + { + "attribute": "frIndexedString3", + "readOnly": true, + }, + { + "attribute": "frIndexedString4", + "readOnly": true, + }, + { + "attribute": "frIndexedString5", + "readOnly": true, + }, + { + "attribute": "frUnindexedString1", + "readOnly": true, + }, + { + "attribute": "frUnindexedString2", + "readOnly": true, + }, + { + "attribute": "frUnindexedString3", + "readOnly": true, + }, + { + "attribute": "frUnindexedString4", + "readOnly": true, + }, + { + "attribute": "frUnindexedString5", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued1", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued2", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued3", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued4", + "readOnly": true, + }, + { + "attribute": "frIndexedMultivalued5", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued1", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued2", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued3", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued4", + "readOnly": true, + }, + { + "attribute": "frUnindexedMultivalued5", + "readOnly": true, + }, + { + "attribute": "frIndexedDate1", + "readOnly": true, + }, + { + "attribute": "frIndexedDate2", + "readOnly": true, + }, + { + "attribute": "frIndexedDate3", + "readOnly": true, + }, + { + "attribute": "frIndexedDate4", + "readOnly": true, + }, + { + "attribute": "frIndexedDate5", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate1", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate2", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate3", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate4", + "readOnly": true, + }, + { + "attribute": "frUnindexedDate5", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger1", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger2", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger3", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger4", + "readOnly": true, + }, + { + "attribute": "frIndexedInteger5", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger1", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger2", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger3", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger4", + "readOnly": true, + }, + { + "attribute": "frUnindexedInteger5", + "readOnly": true, + }, + { + "attribute": "assignedDashboard", + "readOnly": true, + }, + ], + "actions": [], + "filter": "/userName co "test"", + "name": "Alpha realm - Users", + "path": "managed/alpha_user", + "permissions": [ + "VIEW", + "UPDATE", + "CREATE", + ], + }, + ], + "temporalConstraints": [ + { + "duration": "2024-11-04T12:45:00.000Z/2100-12-01T12:45:00.000Z", + }, + ], + }, +] +`;