UNPKG

@zowe/imperative

Version:
446 lines 23.4 kB
"use strict"; /* * This program and the accompanying materials are made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v20.html * * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. * */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectionPropsForSessCfg = void 0; const utilities_1 = require("../../../utilities"); const error_1 = require("../../../error"); const logger_1 = require("../../../logger"); const SessConstants = require("./SessConstants"); const ConfigAutoStore_1 = require("../../../config/src/ConfigAutoStore"); const ConfigUtils_1 = require("../../../config/src/ConfigUtils"); const AuthOrder_1 = require("./AuthOrder"); const Censor_1 = require("../../../censor/src/Censor"); /** * This class adds connection information to an existing session configuration * object for making REST API calls with the Imperative RestClient. */ class ConnectionPropsForSessCfg { // *********************************************************************** /** * Create a REST session configuration object starting with the supplied * initialSessCfg and retrieving connection properties from the command * line arguments (or environment, or profile). If required connection * properties are missing we interactively prompt the user for the values. * for any of the following properties: * host * port * user name * password * * Any prompt will timeout after 30 seconds so that this function can * be run from an automated script, and will not indefinitely hang that * script. * * In addition to properties that we prompt for, we will also add the following * properties to the session configuration if they are available. * type * tokenType * tokenValue * * @param initialSessCfg * An initial session configuration (like ISession, or other * specially defined configuration) that contains your desired * session configuration properties. * * @param cmdArgs * The arguments specified by the user on the command line * (or in environment, or in profile). The contents of the * supplied cmdArgs will be modified. * * @param connOpts * Options that alter our connection actions. See IOptionsForAddConnProps. * The connOpts parameter need not be supplied. * * @example * // Within the process() function of a command handler, * // do steps similar to the following: * const sessCfg: ISession = { * rejectUnauthorized: commandParameters.arguments.rejectUnauthorized, * basePath: commandParameters.arguments.basePath * }; * const connectableSessCfg = await ConnectionPropsForSessCfg.addPropsOrPrompt<ISession>( * sessCfg, commandParameters.arguments * ); * mySession = new Session(connectableSessCfg); * * @returns A session configuration object with connection information * added to the initialSessCfg. Its intended use is for our * caller to create a session for a REST Client. */ static addPropsOrPrompt(initialSessCfg_1, cmdArgs_1) { return __awaiter(this, arguments, void 0, function* (initialSessCfg, cmdArgs, connOpts = {}) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; /* Create copies of our initialSessCfg and connOpts so that * we can modify them without changing the caller's copy. */ const sessCfgToUse = Object.assign({}, initialSessCfg); const connOptsToUse = Object.assign({}, connOpts); // resolve all values between sessCfg and cmdArgs using option choices ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfgToUse, cmdArgs, connOptsToUse); // This function will provide all the needed properties in one array let promptForValues = []; const doNotPromptForValues = []; /* Add the override properties to the session object. */ if (((_a = connOpts.propertyOverrides) === null || _a === void 0 ? void 0 : _a.length) > 0) { for (const override of connOpts.propertyOverrides) { const argName = (_b = override.argumentName) !== null && _b !== void 0 ? _b : override.propertyName; // If the override is found on the session or command arguments, start setting things and do not prompt for overridden properties if (sessCfgToUse[override.propertyName] != null || cmdArgs[argName] != null) { // Set the session config to use the command line argument if it exists. if (cmdArgs[argName] != null) { sessCfgToUse[override.propertyName] = cmdArgs[argName]; } for (const prop of override.propertiesOverridden) { // Make sure we do not prompt for the overridden property. if (!doNotPromptForValues.includes(prop)) { doNotPromptForValues.push(prop); } // remove the property from the session if (prop in sessCfgToUse) { sessCfgToUse[prop] = undefined; } // remove the property from command arguments if (prop in cmdArgs) { cmdArgs[prop] = undefined; } // remove the property from the cached creds if ((_c = sessCfgToUse._authCache) === null || _c === void 0 ? void 0 : _c.availableCreds) { if (prop in sessCfgToUse._authCache.availableCreds) { sessCfgToUse._authCache.availableCreds[prop] = undefined; } } } } } } // resolveSessCfgProps previously added creds to our session, but // our caller's overrides may have changed the available creds, // so again add the creds that are currently available. AuthOrder_1.AuthOrder.addCredsToSession(sessCfgToUse, cmdArgs); // Set default values on propsToPromptFor if (((_d = connOpts.propsToPromptFor) === null || _d === void 0 ? void 0 : _d.length) > 0) { connOpts.propsToPromptFor.forEach(obj => { if (obj.secure == null) obj.secure = true; if (obj.secure) this.secureSessCfgProps.add(obj.name.toString()); promptForValues.push(obj.name); this.promptTextForValues[obj.name.toString()] = obj.description; }); } // check what properties are needed to be prompted if (ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.hostname) === false && !doNotPromptForValues.includes("hostname")) { promptForValues.push("hostname"); } if ((ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.port) === false || sessCfgToUse.port === 0) && !doNotPromptForValues.includes("port")) { promptForValues.push("port"); } // when no creds were found, we must ask for user and password if (sessCfgToUse.type === SessConstants.AUTH_TYPE_NONE || sessCfgToUse.type === SessConstants.AUTH_TYPE_BASIC && !sessCfgToUse.base64EncodedAuth) { if (!((_f = (_e = sessCfgToUse._authCache) === null || _e === void 0 ? void 0 : _e.availableCreds) === null || _f === void 0 ? void 0 : _f.user) && !doNotPromptForValues.includes("user")) { promptForValues.push("user"); } if (!((_h = (_g = sessCfgToUse._authCache) === null || _g === void 0 ? void 0 : _g.availableCreds) === null || _h === void 0 ? void 0 : _h.password) && !doNotPromptForValues.includes("password")) { promptForValues.push("password"); } } this.loadSecureSessCfgProps(connOptsToUse.parms, promptForValues); if (connOptsToUse.getValuesBack == null && connOptsToUse.doPrompting) { connOptsToUse.getValuesBack = this.getValuesBack(connOptsToUse); } if (connOptsToUse.getValuesBack != null) { // put all the needed properties in an array and call the external function const answers = yield connOptsToUse.getValuesBack(promptForValues); if (((_j = connOpts.propsToPromptFor) === null || _j === void 0 ? void 0 : _j.length) > 0) { connOpts.propsToPromptFor.forEach(obj => { if (obj.isGivenValueValid != null) { if (!obj.isGivenValueValid(answers[obj.name])) promptForValues = promptForValues.filter(item => obj.name !== item); } }); } // validate what values are given back and move it to sessCfgToUse for (const value of promptForValues) { if (ConnectionPropsForSessCfg.propHasValue(answers[value])) { sessCfgToUse[value] = answers[value]; } } // if (connOptsToUse.autoStore !== false && connOptsToUse.parms != null) { yield ConfigAutoStore_1.ConfigAutoStore.storeSessCfgProps(connOptsToUse.parms, sessCfgToUse, promptForValues); } } // We previously added creds, but this function may have added more creds // after prompting. So, we add available creds again. AuthOrder_1.AuthOrder.addCredsToSession(sessCfgToUse, cmdArgs); return sessCfgToUse; }); } // *********************************************************************** /** * Resolve the overlapping or mutually exclusive properties that can * occur. Ensure that the resulting session configuration object contains * only the applicable properties. The contents of the supplied sessCfg, * cmdArgs, and connOpts will be modified. * * @param sessCfg * An initial session configuration that contains your desired * session configuration properties. * * @param cmdArgs * The arguments specified by the user on the command line * (or in environment, or in profile) * * @param connOpts * Options that alter our actions. See IOptionsForAddConnProps. * The connOpts parameter need not be supplied. * The only option values used by this function are: * connOpts.requestToken * connOpts.defaultTokenType * * @example * let sessCfg = YouCollectAllProfilePropertiesRelatedToSession(); * let cmdArgs = YouSetPropertiesRequiredInCmdArgs(); * ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, cmdArgs); * sessionToUse = new Session(sessCfg); */ static resolveSessCfgProps(sessCfg, cmdArgs = { $0: "", _: [] }, connOpts = {}) { // use defaults if caller has not specified these properties. if (!Object.prototype.hasOwnProperty.call(connOpts, "requestToken")) { connOpts.requestToken = false; } if (!Object.prototype.hasOwnProperty.call(connOpts, "doPrompting")) { connOpts.doPrompting = true; } if (!Object.prototype.hasOwnProperty.call(connOpts, "defaultTokenType")) { connOpts.defaultTokenType = SessConstants.TOKEN_TYPE_JWT; } if (connOpts.requestToken) { // record in the session that we want to request a token. AuthOrder_1.AuthOrder.makingRequestForToken(sessCfg); // When no token type is specified in the command args or in the session, // store our defaultTokenType in the session. if (!cmdArgs.tokenType && !sessCfg.tokenType && connOpts.defaultTokenType) { sessCfg.tokenType = connOpts.defaultTokenType; } } /* Override properties from our caller's sessCfg * with any values from the command line. */ if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.host)) { sessCfg.hostname = cmdArgs.host; } if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.port)) { sessCfg.port = cmdArgs.port; } if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.user)) { sessCfg.user = cmdArgs.user; } if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.password)) { sessCfg.password = cmdArgs.password; } // record all of the currently available credential information into the session AuthOrder_1.AuthOrder.addCredsToSession(sessCfg, cmdArgs); // When our caller only supports limited authTypes, limit the authTypes in the session if (connOpts.supportedAuthTypes) { logger_1.Logger.getImperativeLogger().warn(`Overriding existing authOrder = ${sessCfg.authTypeOrder} ` + `because a service only supports these limited authTypes = ${connOpts.supportedAuthTypes}`); sessCfg.authTypeOrder = Array.from(connOpts.supportedAuthTypes); // ensure that our newly set authOrder will not be overridden in the future sessCfg._authCache.didUserSetAuthOrder = true; // now that we changed the criteria, ensure that the top creds are recorded in the session AuthOrder_1.AuthOrder.putTopAuthInSession(sessCfg); } } // *********************************************************************** /** * Confirm whether the given session has a credentials. * * @param sessToTest * the session to be confirmed. * * @returns true is the session has credentials. false otherwise. */ static sessHasCreds(sessToTest) { if (sessToTest == null) { return false; } const hasToken = sessToTest.tokenType != null && sessToTest.tokenValue != null; const hasCert = sessToTest.certKey != null && sessToTest.cert; const hasBasicAuth = sessToTest.base64EncodedAuth != null; const hasCreds = sessToTest.user != null && sessToTest.password; return hasToken || hasCert || hasBasicAuth || hasCreds; } /** * Prompts the user to input session config values in a CLI environment. * This is the default implementation of the `getValuesBack` callback when * `connOpts.doPrompting` is true. * @param connOpts Options for adding connection properties * @returns Name-value pairs of connection properties */ static getValuesBack(connOpts) { return (promptForValues) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; /* The check for console.log in the following 'if' statement is only needed for tests * which do not create a mock for the connOpts.parms.response.console.log property. * In the real world, that property always exists for this CLI-only path of logic. */ if (promptForValues.length > 0 && ((_a = connOpts.parms) === null || _a === void 0 ? void 0 : _a.response.console.log)) { // We need to prompt for some values. Determine why we need to prompt. let reasonForPrompts = ""; if ((_b = utilities_1.ImperativeConfig.instance.config) === null || _b === void 0 ? void 0 : _b.exists) { reasonForPrompts += "Some required connection properties have not been specified " + "in your Zowe client configuration. "; } else if (ConfigUtils_1.ConfigUtils.onlyV1ProfilesExist) { reasonForPrompts += "Only V1 profiles exist. V1 profiles are no longer supported. " + "You should convert your V1 profiles to a newer Zowe client configuration. "; } else { reasonForPrompts += "No Zowe client configuration exists. "; } reasonForPrompts += "Therefore, you will be asked for the connection properties " + "that are required to complete your command.\n"; connOpts.parms.response.console.log(utilities_1.TextUtils.wordWrap(utilities_1.TextUtils.chalk.yellowBright(reasonForPrompts))); } const answers = {}; const profileSchema = this.loadSchemaForSessCfgProps(connOpts.parms, promptForValues); const serviceDescription = connOpts.serviceDescription || "your service"; for (const value of promptForValues) { let answer; while (answer === undefined) { const hideText = ((_c = profileSchema[value]) === null || _c === void 0 ? void 0 : _c.secure) || this.secureSessCfgProps.has(value); const valuePrompt = (_d = this.promptTextForValues[value]) !== null && _d !== void 0 ? _d : `Enter your ${value} for`; let promptText = `${valuePrompt} ${serviceDescription}`; if (hideText) { promptText += " (will be hidden)"; } answer = yield this.clientPrompt(`${promptText}: `, { hideText, parms: connOpts.parms }); if (answer === null) { throw new error_1.ImperativeError({ msg: `Timed out waiting for ${value}.` }); } } if (((_e = profileSchema[value]) === null || _e === void 0 ? void 0 : _e.type) === "number") { answer = Number(answer); if (isNaN(answer)) { throw new error_1.ImperativeError({ msg: `Specified ${value} was not a number.` }); } } answers[value] = answer; } return answers; }); } /** * Handle prompting for clients. If in a CLI environment, use the IHandlerParameters.response * object prompt method. * @private * @static * @param {string} promptText * @param {IHandlePromptOptions} opts * @returns {Promise<string>} * @memberof ConnectionPropsForSessCfg */ static clientPrompt(promptText, opts) { return __awaiter(this, void 0, void 0, function* () { if (opts.parms) { return opts.parms.response.console.prompt(promptText, opts); } else { return utilities_1.CliUtils.readPrompt(promptText, opts); } }); } // *********************************************************************** /** * Confirm whether the specified property has a value. * * @param propToTest * the property key to be confirmed. * * @returns true is the property exists and has a value. false otherwise. */ static propHasValue(propToTest) { return propToTest != null && propToTest !== ""; } /** * Load base profile property schema for connection properties. * @param params CLI handler parameters object * @param promptForValues List of ISessCfg properties to prompt for * @returns Key-value pairs of ISessCfg property name and profile property schema */ static loadSchemaForSessCfgProps(params, promptForValues) { var _a; if (params == null || ((_a = utilities_1.ImperativeConfig.instance.loadedConfig) === null || _a === void 0 ? void 0 : _a.baseProfile) == null) { return {}; } const schemas = {}; for (const propName of promptForValues) { const profilePropName = propName === "hostname" ? "host" : propName; schemas[propName] = utilities_1.ImperativeConfig.instance.loadedConfig.baseProfile.schema.properties[profilePropName]; } return schemas; } /** * Load list of secure property names defined in team config. * @param params CLI handler parameters object * @param promptForValues List of ISessCfg properties to prompt for */ static loadSecureSessCfgProps(params, promptForValues) { var _a; if (params == null || !((_a = utilities_1.ImperativeConfig.instance.config) === null || _a === void 0 ? void 0 : _a.exists)) { return; } // Find profile that includes all the properties being prompted for const profileProps = promptForValues.map(propName => propName === "hostname" ? "host" : propName); const profileData = ConfigAutoStore_1.ConfigAutoStore.findActiveProfile(params, profileProps); if (profileData == null) { return; } // Load secure property names that are defined for active profiles const config = utilities_1.ImperativeConfig.instance.config; const baseProfileName = ConfigUtils_1.ConfigUtils.getActiveProfileName("base", params.arguments); for (const secureProp of [...config.api.secure.securePropsForProfile(profileData[1]), ...config.api.secure.securePropsForProfile(baseProfileName)]) { this.secureSessCfgProps.add(secureProp === "host" ? "hostname" : secureProp); } } } exports.ConnectionPropsForSessCfg = ConnectionPropsForSessCfg; /** * List of properties on `sessCfg` object that should be kept secret and * may not appear in Imperative log files. */ ConnectionPropsForSessCfg.secureSessCfgProps = new Set(Censor_1.Censor.SECURE_PROMPT_OPTIONS); /** * List of prompt messages that is used when the CLI prompts for session * config values. */ ConnectionPropsForSessCfg.promptTextForValues = { hostname: "Enter the host name of", port: "Enter the port number of", user: "Enter the user name for", password: "Enter the password for" }; //# sourceMappingURL=ConnectionPropsForSessCfg.js.map