-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2372 from zowe/auth-order-spike
Auth order spike
- Loading branch information
Showing
8 changed files
with
1,368 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
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. | ||
|
||
**Tip:** Create a hard link from the file in the real file path to an identical file path under the **prototypes** directory. That way imports, compiles, and debugging will work in the real directory during experiments, while all changes are automatically recorded in the **prototypes** path. | ||
|
||
**Note:** Remember to delete a new file or revert an existing file in the real directory path. The remaining hard-linked file(s) in the **prototypes** directory path will still have all of the latest changes. |
Empty file.
47 changes: 47 additions & 0 deletions
47
prototypes/packages/cli/src/zosfiles/list/ds/DataSet.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
316
prototypes/packages/imperative/src/rest/src/session/AuthOrder.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
Oops, something went wrong.