diff --git a/.vscode/launch.json b/.vscode/launch.json index d31b387d..3f7850bd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "request": "launch", "name": "Run Frodo CLI", "program": "${workspaceFolder}/src/app.ts", - "args": ["config-manager", "push", "terms-and-conditions", "dev"], + "args": ["config-manager", "push", "password-policy", "dev"], "outFiles": [ "${workspaceFolder}/dist/**/*.(m|c|)js", "${workspaceFolder}/node_modules/@rockcarver/frodo-lib/dist/**/*.(m|c|)js" diff --git a/src/cli/config-manager/config-manager-push/config-manager-push-password-policy.ts b/src/cli/config-manager/config-manager-push/config-manager-push-password-policy.ts new file mode 100644 index 00000000..52be8fcf --- /dev/null +++ b/src/cli/config-manager/config-manager-push/config-manager-push-password-policy.ts @@ -0,0 +1,54 @@ +import { Option } from 'commander'; + +import { configManagerImportPasswordPolicy } from '../../../configManagerOps/FrConfigPasswordPolicyOps'; +import { getTokens } from '../../../ops/AuthenticateOps'; +import { printMessage, verboseMessage } from '../../../utils/Console'; +import { FrodoCommand } from '../../FrodoCommand'; + +const deploymentTypes = ['cloud', 'forgeops']; + +export default function setup() { + const program = new FrodoCommand( + 'frodo config-manager push password-policy', + [], + deploymentTypes + ); + + program + .description('Import password-policy objects.') + .addOption( + new Option( + '-r, --realm ', + 'Specifies the realm to Import from. Only the entity object from this realm will be imported.' + ) + ) + .action(async (host, realm, user, password, options, command) => { + command.handleDefaultArgsAndOpts( + host, + realm, + user, + password, + options, + command + ); + if (options.realm) { + realm = options.realm; + } + if (await getTokens(false, true, deploymentTypes)) { + verboseMessage('Importing config entity password-policy'); + const outcome = await configManagerImportPasswordPolicy(realm); + if (!outcome) process.exitCode = 1; + } + // unrecognized combination of options or no options + else { + printMessage( + 'Unrecognized combination of options or no options...', + 'error' + ); + program.help(); + process.exitCode = 1; + } + }); + + return program; +} diff --git a/src/cli/config-manager/config-manager-push/config-manager-push-uiConfig.ts b/src/cli/config-manager/config-manager-push/config-manager-push-uiConfig.ts index 5752d5f8..30f9565e 100644 --- a/src/cli/config-manager/config-manager-push/config-manager-push-uiConfig.ts +++ b/src/cli/config-manager/config-manager-push/config-manager-push-uiConfig.ts @@ -33,7 +33,7 @@ export default function setup() { ); if (await getTokens(false, true, deploymentTypes)) { - verboseMessage('Exporting config entity ui-configuration'); + verboseMessage('Importing config entity ui-configuration'); const outcome = await configManagerImportUiConfig(options.file); if (!outcome) process.exitCode = 1; } diff --git a/src/cli/config-manager/config-manager-push/config-manager-push.ts b/src/cli/config-manager/config-manager-push/config-manager-push.ts index a797d849..57c82b18 100644 --- a/src/cli/config-manager/config-manager-push/config-manager-push.ts +++ b/src/cli/config-manager/config-manager-push/config-manager-push.ts @@ -2,6 +2,7 @@ import { FrodoStubCommand } from '../../FrodoCommand'; import TermsAndConditions from './config-manager-push-terms-and-conditions'; import Themes from './config-manager-push-themes'; import UiConfig from './config-manager-push-uiConfig'; +import PasswordPolicy from './config-manager-push-password-policy' export default function setup() { const program = new FrodoStubCommand('push').description( @@ -11,6 +12,7 @@ export default function setup() { program.addCommand(Themes().name('themes')); program.addCommand(UiConfig().name('ui-config')); program.addCommand(TermsAndConditions().name('terms-and-conditions')); + program.addCommand(PasswordPolicy().name('password-policy')) return program; } diff --git a/src/configManagerOps/FrConfigPasswordPolicyOps.ts b/src/configManagerOps/FrConfigPasswordPolicyOps.ts index 9e5a9ab5..341e0b6b 100644 --- a/src/configManagerOps/FrConfigPasswordPolicyOps.ts +++ b/src/configManagerOps/FrConfigPasswordPolicyOps.ts @@ -1,11 +1,12 @@ import { frodo } from '@rockcarver/frodo-lib'; +import fs from 'fs'; import { getIdmImportExportOptions } from '../ops/IdmOps'; import { printError } from '../utils/Console'; import { realmList } from '../utils/FrConfig'; const { getFilePath, saveJsonToFile } = frodo.utils; -const { exportConfigEntity } = frodo.idm.config; +const { exportConfigEntity, importConfigEntities } = frodo.idm.config; /** * Export an IDM configuration object in the fr-config-manager format. @@ -29,6 +30,8 @@ export async function configManagerExportPasswordPolicy( saveJsonToFile(realmData, getFilePath(fileName, true), false, true); } else { for (const realmName of await realmList()) { + // bypassing root realm + if (realmName === '/') continue; const realmData = ( await exportConfigEntity(`fieldPolicy/${realmName}_user`, { envReplaceParams: options.envReplaceParams, @@ -46,3 +49,39 @@ export async function configManagerExportPasswordPolicy( } return false; } + +export async function configManagerImportPasswordPolicy( realm?: string, ): Promise { + try { + + if (realm && realm !== '__default__realm__') { + + const filePath = getFilePath(`realms/${realm}/password-policy/${realm}_user-password-policy.json`); + const mainFile = fs.readFileSync(filePath, 'utf8') + let importData = JSON.parse(mainFile) + const id = importData._id + importData = { idm: { [id]: importData } }; + + + await importConfigEntities(importData) + // saveJsonToFile(fileContent, 'indvidual-password-export') + } + + else { + for (const realmName of await realmList()) { + if (realmName === '/') continue; + const filePath = getFilePath(`realms/${realmName}/password-policy/${realmName}_user-password-policy.json`); + const mainFile = fs.readFileSync(filePath, 'utf8') + let importData = JSON.parse(mainFile) + const id = importData._id + importData = { idm: { [id]: importData } }; + await importConfigEntities(importData) + // saveJsonToFile(fileContent, 'password-export') + } } + + return true; + } catch (error) { + printError(error, `Error importing config entity ui-configuration`); + } + return false; +} + diff --git a/test/client_cli/en/__snapshots__/config-manager.test.js.snap b/test/client_cli/en/__snapshots__/config-manager.test.js.snap index 2bb8bcb2..52131f5b 100644 --- a/test/client_cli/en/__snapshots__/config-manager.test.js.snap +++ b/test/client_cli/en/__snapshots__/config-manager.test.js.snap @@ -11,5 +11,6 @@ Options: Commands: help display help for command pull Export cloud configuration using fr-config-manager. + push Export cloud configuration using fr-config-manager. " `; diff --git a/test/e2e/fr-config-manager-push/__snapshots__/config-manager-push-password-policy.e2e.test.js.snap b/test/e2e/fr-config-manager-push/__snapshots__/config-manager-push-password-policy.e2e.test.js.snap new file mode 100644 index 00000000..99287598 --- /dev/null +++ b/test/e2e/fr-config-manager-push/__snapshots__/config-manager-push-password-policy.e2e.test.js.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`"frodo config-manager push password-policy -D test/e2e/exports/fr-config-manager -m forgeops": should import the password policy into forgeops" 1`] = ` +"Usage: frodo config-manager push password-policy [options] [host] [realm] [username] [password] + +Import password-policy objects. + +Arguments: + host AM base URL, e.g.: + https://cdk.iam.example.com/am. To use a + connection profile, just specify a + unique substring. + realm Realm. Specify realm as '/' for the root + realm or 'realm' or '/parent/child' + otherwise. (default: "alpha" for + Identity Cloud tenants, "/" otherwise.) + username Username to login with. Must be an admin + user with appropriate rights to manage + authentication journeys/trees. + password Password. + +Options: + --curlirize Output all network calls in curl format. + -D, --directory Set the working directory. + --debug Debug output during command execution. + If specified, may or may not produce + additional output helpful for + troubleshooting. + --flush-cache Flush token cache. + -h, --help Help + --idm-host IDM base URL, e.g.: + https://cdk.idm.example.com/myidm. Use + only if your IDM installation resides in + a different domain and/or if the base + path differs from the default + "/openidm". + -k, --insecure Allow insecure connections when using + SSL/TLS. Has no effect when using a + network proxy for https + (HTTPS_PROXY=http://:), in + that case the proxy must provide this + capability. (default: Don't allow + insecure connections) + --login-client-id Specify a custom OAuth2 client id to use + a your own oauth2 client for IDM API + calls in deployments of type "cloud" or + "forgeops". Your custom client must be + configured as a public client and allow + the authorization code grant using the + "openid fr:idm:*" scope. Use the + "--redirect-uri" parameter if you have + configured a custom redirect uri + (default: + "/platform/appAuthHelperRedirect.html"). + --login-redirect-uri Specify a custom redirect URI to use + with your custom OAuth2 client (efault: + "/platform/appAuthHelperRedirect.html"). + -m, --type Override auto-detected deployment type. + Valid values for type: + classic: A classic Access + Management-only deployment with custom + layout and configuration. + cloud: A ForgeRock Identity Cloud + environment. + forgeops: A ForgeOps CDK or CDM + deployment. + The detected or provided deployment type + controls certain behavior like obtaining + an Identity Management admin token or + not and whether to export/import + referenced email templates or how to + walk through the tenant admin login flow + of Identity Cloud and handle MFA + (choices: "classic", "cloud", + "forgeops") + --no-cache Disable token cache for this operation. + --passphrase The passphrase for the Amster private + key if it is encrypted. + --private-key File containing the private key for + authenticating with Amster. Supported + formats include PEM (both PKCS#1 and + PKCS#8 variants), OpenSSH, DNSSEC, and + JWK. + -r, --realm Specifies the realm to Import from. Only + the entity object from this realm will + be imported. + --retry Retry failed operations. Valid values + for strategy: + everything: Retry all failed operations. + + network: Retry only network-related + failed operations. + nothing: Do not retry failed + operations. + The selected retry strategy controls how + the CLI handles failures. (choices: + "nothing", "everything", "network", + default: Do not retry failed + operations.) + --sa-id Service account id. + --sa-jwk-file File containing the JSON Web Key (JWK) + associated with the the service account. + --verbose Verbose output during command execution. + If specified, may or may not produce + additional output. + +Environment Variables: + FRODO_HOST: AM base URL. Overridden by 'host' argument. + FRODO_IDM_HOST: IDM base URL. Overridden by '--idm-host' option. + FRODO_REALM: Realm. Overridden by 'realm' argument. + FRODO_USERNAME: Username. Overridden by 'username' argument. + FRODO_PASSWORD: Password. Overridden by 'password' argument. + FRODO_LOGIN_CLIENT_ID: OAuth2 client id for IDM API calls. Overridden by '--login-client-id' option. + FRODO_LOGIN_REDIRECT_URI: Redirect Uri for custom OAuth2 client id. Overridden by '--login-redirect-uri' option. + FRODO_SA_ID: Service account uuid. Overridden by '--sa-id' option. + FRODO_SA_JWK: Service account JWK. Overridden by '--sa-jwk-file' option but takes the actual JWK as a value, not a file name. + FRODO_AMSTER_PASSPHRASE: Passphrase for the Amster private key if it is encrypted. Overridden by '--passphrase' option. + FRODO_AMSTER_PRIVATE_KEY: Amster private key. Overridden by '--private-key' option but takes the actual private key as a value (i.e. the file contents), not a file name. Supported formats include PEM (both PKCS#1 and PKCS#8 variants), OpenSSH, DNSSEC, and JWK. + FRODO_NO_CACHE: Disable token cache. Same as '--no-cache' option. + FRODO_TOKEN_CACHE_PATH: Use this token cache file instead of '~/.frodo/TokenCache.json'. + FRODO_CONNECTION_PROFILES_PATH: Use this connection profiles file instead of '~/.frodo/Connections.json'. + FRODO_AUTHENTICATION_SERVICE: Name of a login journey to use. When using an Amster private key, specifies which journey to use for Amster authentication as opposed to the default 'amsterService' journey. + FRODO_DEBUG: Set to any value to enable debug output. Same as '--debug'. + FRODO_MASTER_KEY_PATH: Use this master key file instead of '~/.frodo/masterkey.key' file. + FRODO_MASTER_KEY: Use this master key instead of what's in '~/.frodo/masterkey.key'. Takes precedence over FRODO_MASTER_KEY_PATH. + +" +`; diff --git a/test/e2e/fr-config-manager-push/config-manager-push-password-policy.e2e.test.js b/test/e2e/fr-config-manager-push/config-manager-push-password-policy.e2e.test.js new file mode 100644 index 00000000..901fb3c3 --- /dev/null +++ b/test/e2e/fr-config-manager-push/config-manager-push-password-policy.e2e.test.js @@ -0,0 +1,72 @@ +/** + * Follow this process to write e2e tests for the CLI project: + * + * 1. Test if all the necessary mocks for your tests already exist. + * In mock mode, run the command you want to test with the same arguments + * and parameters exactly as you want to test it, for example: + * + * $ FRODO_MOCK=1 frodo conn save https://openam-frodo-dev.forgeblocks.com/am volker.scheuber@forgerock.com Sup3rS3cr3t! + * + * If your command completes without errors and with the expected results, + * all the required mocks already exist and you are good to write your + * test and skip to step #4. + * + * If, however, your command fails and you see errors like the one below, + * you know you need to record the mock responses first: + * + * [Polly] [adapter:node-http] Recording for the following request is not found and `recordIfMissing` is `false`. + * + * 2. Record mock responses for your exact command. + * In mock record mode, run the command you want to test with the same arguments + * and parameters exactly as you want to test it, for example: + * + * $ FRODO_MOCK=record frodo conn save https://openam-frodo-dev.forgeblocks.com/am volker.scheuber@forgerock.com Sup3rS3cr3t! + * + * Wait until you see all the Polly instances (mock recording adapters) have + * shutdown before you try to run step #1 again. + * Messages like these indicate mock recording adapters shutting down: + * + * Polly instance 'conn/4' stopping in 3s... + * Polly instance 'conn/4' stopping in 2s... + * Polly instance 'conn/save/3' stopping in 3s... + * Polly instance 'conn/4' stopping in 1s... + * Polly instance 'conn/save/3' stopping in 2s... + * Polly instance 'conn/4' stopped. + * Polly instance 'conn/save/3' stopping in 1s... + * Polly instance 'conn/save/3' stopped. + * + * 3. Validate your freshly recorded mock responses are complete and working. + * Re-run the exact command you want to test in mock mode (see step #1). + * + * 4. Write your test. + * Make sure to use the exact command including number of arguments and params. + * + * 5. Commit both your test and your new recordings to the repository. + * Your tests are likely going to reside outside the frodo-lib project but + * the recordings must be committed to the frodo-lib project. + */ + +/* +// ForgeOps +FRODO_MOCK=record FRODO_NO_CACHE=1 FRODO_HOST=https://nightly.gcp.forgeops.com/am frodo config-manager push password-policy -D test/e2e/exports/fr-config-manager -m forgeops +*/ + + +import cp from 'child_process'; +import { promisify } from 'util'; +import { getEnv, removeAnsiEscapeCodes } from '../utils/TestUtils'; +import { forgeops_connection as fc } from '../utils/TestConfig'; + + +const exec = promisify(cp.exec); + +process.env['FRODO_MOCK'] = '1'; +const forgeopsEnv = getEnv(fc); + +const allDirectory = "test/e2e/exports/fr-config-manager"; + +test(`"frodo config-manager push password-policy -D ${allDirectory} -m forgeops": should import the password policy into forgeops"`, async () => { + const CMD = `frodo config-manager push password-policy -D ${allDirectory} -m forgeops`; + const { stdout } = await exec(CMD, forgeopsEnv); + expect(removeAnsiEscapeCodes(stdout)).toMatchSnapshot(); +}); \ No newline at end of file