Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth order spike #2372

Merged
merged 19 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8e6874d
Add design document.
gejohnston Oct 9, 2024
0719730
Committing local updates before merge from master
gejohnston Oct 15, 2024
4f85c56
Add Doc Impact
gejohnston Nov 12, 2024
3f617aa
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Nov 12, 2024
f953ab8
Complete section "configuration enhancement"
gejohnston Nov 12, 2024
d6417f4
Add ssh functions to list of functions to be modified
gejohnston Nov 13, 2024
f29132d
Clean up before draft PR
gejohnston Nov 19, 2024
7873e64
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Nov 21, 2024
aef982c
Add section of functions using rejectUnauthorized
gejohnston Nov 25, 2024
aa08d0b
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Nov 25, 2024
9ee18b9
Add AuthOrder class and update design doc
gejohnston Dec 4, 2024
e38dca7
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Dec 4, 2024
bcad1ab
Move AuthOrder prototype into 'prototypes' directory
gejohnston Dec 13, 2024
91d425e
No env var, cmd-line option, or schema. AuthOrder reserved word.
gejohnston Dec 13, 2024
f128a37
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Dec 13, 2024
60058ae
Remove AuthOrder.ts from our real packages directory
gejohnston Dec 13, 2024
c9c4ab8
Generalize hard-link advice.
gejohnston Dec 17, 2024
d108662
Add new 'Edge Case' section
gejohnston Dec 18, 2024
bef7195
Merge remote-tracking branch 'origin/master' into auth-order-spike
gejohnston Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
399 changes: 399 additions & 0 deletions docs/Design_for_selecting_auth_type.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions prototypes/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The purpose of this **prototypes** folder is to provide a home for prototype or proof-of-concept code. As an example, new, experimental code that implements a compelling feature may be good model for the future permanent implementation of that feature.

During experiments, you might place a new class into a particular package to confirm that an idea is feasible. However, that code may not be complete enough to be built and packaged into the product. Lint rules might fail on this new code. You may not have any tests yet, so code coverage verification may fail.

You do not want this new code to cause Zowe build pipelines to fail, but you do not want to lose the useful work that has been completed. Placing your source file(s) under the **prototypes** directory is a way to keep your valuable experiment and not break the Zowe build process.

Place your source file into a path under the **prototypes** directory that mirrors the real directory path . Personally, I like to create a hard link from the file in my real file path to an identical file path under the **prototypes** directory. That way I can import, compile, and debug in the real directory until I complete experiments. Before committing, I can just delete the file in the real directory path and the remaining hard-linked file in the **prototypes** directory path will have all of the latest changes.
gejohnston marked this conversation as resolved.
Show resolved Hide resolved
Empty file added prototypes/packages/.gitkeep
Empty file.
47 changes: 47 additions & 0 deletions prototypes/packages/cli/src/zosfiles/list/ds/DataSet.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.
*
*/

import { AbstractSession, IHandlerParameters, TextUtils } from "@zowe/imperative";
import { IZosFilesResponse, List } from "@zowe/zos-files-for-zowe-sdk";
import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler";

/**
* Handler to list a data sets
* @export
*/
export default class DataSetHandler extends ZosFilesBaseHandler {
public async processWithSession(commandParameters: IHandlerParameters, session: AbstractSession): Promise<IZosFilesResponse> {
/* todo: Remove diagnostic print statements
*/
return {
commandResponse: "____ DataSetHandler: Pretend that we ran the list command.",
success: true
};
// end Remove todo: */

const response = await List.dataSet(session, commandParameters.arguments.dataSetName, {
volume: commandParameters.arguments.volumeSerial,
attributes: commandParameters.arguments.attributes,
maxLength: commandParameters.arguments.maxLength,
responseTimeout: commandParameters.arguments.responseTimeout,
start: commandParameters.arguments.start
});

if (commandParameters.arguments.attributes && response.apiResponse.items.length > 0) {
commandParameters.response.console.log(TextUtils.prettyJson(response.apiResponse.items));
} else {
const dsnameList = response.apiResponse.items.map((mem: any) => mem.dsname);
commandParameters.response.console.log(dsnameList.join("\n"));
}

return response;
}
}
7 changes: 7 additions & 0 deletions prototypes/packages/cli/src/zosfiles/list/ds/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Files used to create a prototype of AuthOrder are:

prototypes\packages\imperative\src\rest\src\session\AuthOrder.ts

prototypes\packages\imperative\src\rest\src\session\ConnectionPropsForSessCfg.ts

prototypes\packages\cli\src\zosfiles\list\ds\DataSet.handler.ts
316 changes: 316 additions & 0 deletions prototypes/packages/imperative/src/rest/src/session/AuthOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
/*
* 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.
*
*/

import { ICommandArguments } from "../../../cmd";
import { ImperativeError } from "../../../error";
import { ISession } from "./doc/ISession";
import { Logger } from "../../../logger";
import * as SessConstants from "./SessConstants";

/**
* @internal - Cannot be used outside of the imperative package
*
* The purpose of this class is to detect an authentication order property
* supplied by a user in a profile, command line, or environment variable.
* That authOrder is then used to place the correct set of credentials into
* a session for authentication.
*/
export class AuthOrder {

/**
* When a user does not supply an auth order, Zowe clients will use a
* hard-coded default order. This property records whether AUTH_TYPE_BASIC
* or AUTH_TYPE_TOKEN is the top auth choice in the order.
*/
private static m_topDefaultAuth: string = SessConstants.AUTH_TYPE_BASIC;

/**
* This array of authentication types specifies the order of preferred
* authentication. It contains the user-specified order, or a default order
* if the user does not specify an order. m_authOrder[0] is the highest
* preferred authentication.
*/
private static m_authOrder: SessConstants.AUTH_TYPE_CHOICES[] = null;

// ***********************************************************************
/**
* Set the top auth type when a default authOrder is used. Previously,
* two different hard-coded orders were present in the Zowe clients.
* Both hard-coded orders are now provided by this class. Zowe code,
* that still sets a specific order for backward compatibility, now
* calls this function to ensure that that the original behavior remains
* the same and avoids a breaking change when a user has not specified
* an authOrder property.
*
* @param topDefaultAuth - Input.
* The top authentication type that will be used when forming a
* default authOrder.
*/
public static setTopDefaultAuth(
topDefaultAuth: typeof SessConstants.AUTH_TYPE_BASIC | typeof SessConstants.AUTH_TYPE_TOKEN
): void {
AuthOrder.m_topDefaultAuth = topDefaultAuth;
}

// ***********************************************************************
/**
* Cache the authOrder property from the supplied cmdArgs. If no authOrder exists
* in cmdArgs, a default authOrder is created and cached.
*
* @param cmdArgs - Input.
* The set of arguments that the calling function is using.
*/
private static cacheAuthOrder(cmdArgs: ICommandArguments): void {
// have we already cached the authOrder?
if (AuthOrder.m_authOrder !== null) {
// start over with an empty order.
AuthOrder.m_authOrder = null;
}

if (cmdArgs.authOrder) {
// validate each user-supplied type of authentication
for (const nextUserAuth of cmdArgs.authOrder) {
switch (nextUserAuth) {
case SessConstants.AUTH_TYPE_BASIC:
case SessConstants.AUTH_TYPE_TOKEN:
case SessConstants.AUTH_TYPE_BEARER:
case SessConstants.AUTH_TYPE_CERT_PEM:
case SessConstants.AUTH_TYPE_NONE:
if (AuthOrder.m_authOrder === null) {
AuthOrder.m_authOrder = [];
}
AuthOrder.m_authOrder.push(nextUserAuth);
break;
default:
Logger.getImperativeLogger().error(
`The authentication = '${nextUserAuth}' is not valid and will be ignored.`
);
// todo: Remove diagnostic print statements
console.log("____ cacheAuthOrder: nextUserAuth = '" + nextUserAuth + "' is not valid and will be ignored.");
// todo: end Remove
}
}
}

// the user supplied an authOrder
if (AuthOrder.m_authOrder !== null) {
return;
}

// No authOrder was supplied by the user. Create a default order.
AuthOrder.m_authOrder = [];
if (AuthOrder.m_topDefaultAuth === SessConstants.AUTH_TYPE_BASIC) {
// we want user & password auth as the top choice
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BASIC);
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_TOKEN);
} else {
// we want token auth as the top choice
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_TOKEN);
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BASIC);
}
// add remaining auth types. We do not include 'none' in our defaults.
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BEARER);
AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_CERT_PEM);
}

// ***********************************************************************
/**
* Find the highest auth type (according to the authOrder) which exists
* in either the supplied session config or command line arguments.
* Then place the credentials associated with that auth type into the
* supplied session config. Credentials for all other auth types are
* removed from the session config.
*
* @param sessCfg - Modified.
* Authentication properties are added to and removed from this
* session configuration, which can already have properties in
* this object when passed to this function.
*
* @param cmdArgs - Input.
* The set of arguments with which the calling function is operating.
* For CLI, the cmdArgs come from the command line, profile, or
* environment. Other apps can place relevant arguments into this
* object to be processed by this function.
*/
public static putTopAuthInSession<SessCfgType extends ISession>(
sessCfg: SessCfgType,
cmdArgs: ICommandArguments
): void {
let sessTypeToUse: SessConstants.AUTH_TYPE_CHOICES = null;

// todo: Remove diagnostic print statements
console.log("____ putTopAuthInSession: cmdArgs = " + JSON.stringify(cmdArgs, null, 2));
sessCfg.tokenType = "apimlAuthenticationToken";
sessCfg.tokenValue = "SomeTokenValue";
sessCfg.cert = "./certFile.txt";
sessCfg.certKey = "./certKeyFile.txt";
sessCfg.authTypeOrder = [];
sessCfg.authTypeOrder.push("bogusAuthType1");
sessCfg.authTypeOrder.push("bogusAuthType2");
sessCfg.authTypeOrder.push("bogusAuthType3");
sessCfg.authTypeOrder.push("bogusAuthType4");
console.log("____ putTopAuthInSession: sessCfg before processing = " + JSON.stringify(sessCfg, null, 2));
// todo: end Remove

// cache the correct authOrder to use
AuthOrder.cacheAuthOrder(cmdArgs);

// Detect the first auth type (from our auth order) provided in the session config or in command args.
// Ensure that the auth properties are placed in the session config.
// Record the detected auth type for use as the session type.
let errMsg: string;
for (const nextAuth of AuthOrder.m_authOrder) {
switch (nextAuth) {
case SessConstants.AUTH_TYPE_BASIC:
// todo: do we have to check for sessCfg.base64EncodedAuth ?
if (cmdArgs.user?.length > 0) {
sessCfg.user = cmdArgs.user;
}
if (cmdArgs.password?.length > 0) {
sessCfg.password = cmdArgs.password;
}
if (sessCfg.user?.length > 0 && sessCfg.password?.length > 0) {
// TODO: Confirm in ConnectionPropsForSessCfg that requestToken will work ok with sessTypeToUse
sessTypeToUse = SessConstants.AUTH_TYPE_BASIC;
}
break;
case SessConstants.AUTH_TYPE_TOKEN:
if (cmdArgs.tokenType?.length > 0) {
sessCfg.tokenType = cmdArgs.tokenType;
}
if (cmdArgs.tokenValue?.length > 0) {
sessCfg.tokenValue = cmdArgs.tokenValue;
}
if (sessCfg.tokenType?.length > 0 && sessCfg.tokenValue?.length > 0) {
sessTypeToUse = SessConstants.AUTH_TYPE_TOKEN;
}
break;
case SessConstants.AUTH_TYPE_BEARER:
if (cmdArgs.tokenType?.length > 0) {
sessCfg.tokenType = cmdArgs.tokenType;
}
if (cmdArgs.tokenValue?.length > 0) {
sessCfg.tokenValue = cmdArgs.tokenValue;
}
// a tokenValue with no tokenType implies a bearer token
if (!(sessCfg.tokenType?.length > 0) && sessCfg.tokenValue?.length > 0) {
sessTypeToUse = SessConstants.AUTH_TYPE_BEARER;
}
break;
case SessConstants.AUTH_TYPE_CERT_PEM:
if (cmdArgs.certFile?.length > 0) {
sessCfg.cert = cmdArgs.certFile;
}
if (cmdArgs.certKeyFile?.length > 0) {
sessCfg.certKey = cmdArgs.certKeyFile;
}
if (sessCfg.cert?.length > 0 && sessCfg.certKey?.length > 0) {
sessTypeToUse = SessConstants.AUTH_TYPE_CERT_PEM;
}
break;
case SessConstants.AUTH_TYPE_NONE:
sessTypeToUse = SessConstants.AUTH_TYPE_NONE;
break;
default:
// authOrder was validated. A wrong value now is our programming error.
errMsg = `authOrder contains an invalid authentication = ${nextAuth}.`;
Logger.getImperativeLogger().error(errMsg);
throw new ImperativeError({ msg: errMsg });
}
if (sessTypeToUse !== null) {
// stop looking for auth types after we find the first one
break;
}
}

// When no creds are in the session and AUTH_TYPE_NONE is not in the user's authOrder,
// remove the session type from the session. Otherwise set the type that we found.
if (sessTypeToUse === null) {
delete sessCfg.type;
} else {
sessCfg.type = sessTypeToUse;
}

// remove all extra auth creds from the session
AuthOrder.removeExtraCredsFromSess(sessCfg);

// copy our authOrder into the session object
sessCfg.authTypeOrder = [...AuthOrder.m_authOrder];

// todo: Should we throw error if no creds are in the session, or let other functions throw the error?

// todo: Remove diagnostic print statements
console.log("____ putTopAuthInSession:\nsessCfg after processing = " + JSON.stringify(sessCfg, null, 2));
// todo: end Remove
}

// ***********************************************************************
/**
* Remove all credential properties from the supplied session except for the
* creds related to the session type specified within the sessCfg argument.
*
* @param sessCfg - Modified.
* Authentication credentials are removed from this session configuration.
*/
private static removeExtraCredsFromSess<SessCfgType extends ISession>(
sessCfg: SessCfgType
): void {
if (AuthOrder.cacheAuthOrder == null) {
const errMsg = "AuthOrder.cacheAuthOrder must be called before AuthOrder.removeExtraCredsFromSess";
Logger.getImperativeLogger().error(errMsg);
throw new ImperativeError({ msg: errMsg });
}
if (!sessCfg?.type) {
const errMsg = "Session type must exist in the supplied session.";
Logger.getImperativeLogger().error(errMsg);
throw new ImperativeError({ msg: errMsg });
}

// initially set all creds to be removed from the session. Later we delete the desired creds from this set
const credsToRemove = new Set(["user", "password", "base64EncodedAuth", "tokenType", "tokenValue", "cert", "certKey"]);

// Delete the selected creds from the set of creds that will be removed from our session config.
let errMsg: string;
switch (sessCfg.type) {
case SessConstants.AUTH_TYPE_BASIC:
credsToRemove.delete("user");
credsToRemove.delete("password");
credsToRemove.delete("base64EncodedAuth");
break;
case SessConstants.AUTH_TYPE_TOKEN:
credsToRemove.delete("tokenType");
credsToRemove.delete("tokenValue");
break;
case SessConstants.AUTH_TYPE_BEARER:
credsToRemove.delete("tokenValue");
break;
case SessConstants.AUTH_TYPE_CERT_PEM:
credsToRemove.delete("cert");
credsToRemove.delete("certKey");
break;
case SessConstants.AUTH_TYPE_NONE:
break;
default:
// authOrder was validated. A wrong value now is our programming error.
errMsg = `Session an invalid type = ${sessCfg.type}.`;
Logger.getImperativeLogger().error(errMsg);
throw new ImperativeError({ msg: errMsg });
}

// remove all auth creds from the session, except the creds for the auth type that we chose to keep
const credIter = credsToRemove.values();
let nextCredToRemove = credIter.next();
while (!nextCredToRemove.done) {
delete (sessCfg as any)[nextCredToRemove.value];
nextCredToRemove = credIter.next();
}
}
}
Loading
Loading