@zowe/imperative
Version:
framework for building configurable CLIs
446 lines • 23.4 kB
JavaScript
/*
* 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
;