Skip to content

Commit

Permalink
wip: lock in other FS providers; fix update credentials logic
Browse files Browse the repository at this point in the history
Signed-off-by: Trae Yelovich <[email protected]>
  • Loading branch information
traeok committed Dec 19, 2024
1 parent cea4f85 commit 1216b03
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 29 deletions.
53 changes: 38 additions & 15 deletions packages/zowe-explorer-api/src/profiles/AuthHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
*/

import { Gui } from "../globals";
import { CorrelatedError, reloadWorkspacesForProfile } from "../utils";
import { CorrelatedError, DeferredPromise, reloadWorkspacesForProfile } from "../utils";
import * as imperative from "@zowe/imperative";
import { DeferredPromise } from "../utils";
import { IZoweTreeNode } from "../tree";
import { IZoweDatasetTreeNode, IZoweJobTreeNode, IZoweTree, IZoweTreeNode, IZoweUSSTreeNode } from "../tree";
import { commands } from "vscode";

export interface IAuthMethods {
ssoLogin: (node?: IZoweTreeNode, profileName?: string) => PromiseLike<boolean>;
Expand All @@ -25,6 +25,10 @@ export interface AuthPromptOpts extends IAuthMethods {
errorCorrelation?: CorrelatedError;
}

export interface LockProfileOpts extends AuthPromptOpts {
waitAfterLock?: boolean;
}

type ProfileLike = string | imperative.IProfileLoaded;
export class AuthHandler {
private static lockedProfiles: Map<string, DeferredPromise<void>> = new Map();
Expand All @@ -35,21 +39,22 @@ export class AuthHandler {
* @param {string[]} baseSecureProfileProps Base profile's secure properties (optional)
* @returns {Promise<boolean>} a boolean representing whether token based auth is being used or not
*/
public static async isUsingTokenAuth(secureProfileProps: string[], baseSecureProfileProps?: string[]): Promise<boolean> {
public static isUsingTokenAuth(secureProfileProps: string[], baseSecureProfileProps?: string[]): boolean {
const profileUsesBasicAuth = secureProfileProps.includes("user") && secureProfileProps.includes("password");
if (secureProfileProps.includes("tokenValue")) {
return secureProfileProps.includes("tokenValue") && !profileUsesBasicAuth;
}
return baseSecureProfileProps?.includes("tokenValue") && !profileUsesBasicAuth;
}

public static async unlockProfile(profileName: string) {
public static unlockProfile(profile: ProfileLike): void {
const profileName = typeof profile === "string" ? profile : profile.name;
const deferred = this.lockedProfiles.get(profileName);
if (deferred) {
deferred.resolve();
this.lockedProfiles.delete(profileName);
// reload virtual workspaces for the profile now that its usable
await reloadWorkspacesForProfile(profileName);
reloadWorkspacesForProfile(profileName);
}
}

Expand Down Expand Up @@ -89,14 +94,28 @@ export class AuthHandler {
});

if (creds != null) {
// New creds were provided, unlock profile
// New creds were provided
// Propagate new creds to other profiles
const treeProviders = (await commands.executeCommand("zowe.getTreeProviders")) as any;
const dsNode = (await (treeProviders.ds as IZoweTree<IZoweDatasetTreeNode>).getChildren()).find((n) => n.label === profileName);
if (dsNode && typeof profile !== "string") {
dsNode.setProfileToChoice(profile);
}
const ussNode = (await (treeProviders.uss as IZoweTree<IZoweUSSTreeNode>).getChildren()).find((n) => n.label === profileName);
if (ussNode && typeof profile !== "string") {
ussNode.setProfileToChoice(profile);
}
const jobsNode = (await (treeProviders.job as IZoweTree<IZoweJobTreeNode>).getChildren()).find((n) => n.label === profileName);
if (jobsNode && typeof profile !== "string") {
jobsNode.setProfileToChoice(profile);
}
AuthHandler.unlockProfile(profileName);
return true;
}
return false;
}

public static async lockProfile(profile: ProfileLike, imperativeError: imperative.ImperativeError, opts: AuthPromptOpts): Promise<void> {
public static async lockProfile(profile: ProfileLike, imperativeError?: imperative.ImperativeError, opts?: LockProfileOpts): Promise<void> {
const profileName = typeof profile === "string" ? profile : profile.name;
if (this.lockedProfiles.has(profileName)) {
return this.lockedProfiles.get(profileName)!.promise;
Expand All @@ -105,18 +124,22 @@ export class AuthHandler {
this.lockedProfiles.set(profileName, deferred);

// Prompt the user to re-authenticate
const credsEntered = await AuthHandler.promptForAuthentication(imperativeError, profile, opts);
if (imperativeError && opts) {
const credsEntered = await AuthHandler.promptForAuthentication(imperativeError, profile, opts);

// If the user failed to re-authenticate, reject the promise
// TODO: more manual testing
if (!credsEntered) {
deferred.reject();
// If the user failed to re-authenticate, reject the promise
// TODO: more manual testing
if (!credsEntered) {
deferred.reject();
}
}

return deferred.promise;
if (opts?.waitAfterLock) {
return deferred.promise;
}
}

public static async isLocked(profile: ProfileLike) {
public static isLocked(profile: ProfileLike): boolean {
return this.lockedProfiles.has(typeof profile === "string" ? profile : profile.name);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer-api/src/profiles/ProfilesCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import * as imperative from "@zowe/imperative";
import type { IRegisterClient } from "../extend/IRegisterClient";
import { FileManagement } from "../utils";
import { FileManagement } from "../utils/FileManagement";
import { Validation } from "./Validation";
import { ZosmfProfile } from "@zowe/zosmf-for-zowe-sdk";
import { ZosTsoProfile } from "@zowe/zos-tso-for-zowe-sdk";
Expand Down
2 changes: 1 addition & 1 deletion packages/zowe-explorer/src/configuration/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { imperative, PersistenceSchemaEnum } from "@zowe/zowe-explorer-api";
import type { Profiles } from "./Profiles";

export class Constants {
public static readonly COMMAND_COUNT = 105;
public static readonly COMMAND_COUNT = 106;
public static readonly MAX_SEARCH_HISTORY = 5;
public static readonly MAX_FILE_HISTORY = 10;
public static readonly MS_PER_SEC = 1000;
Expand Down
14 changes: 8 additions & 6 deletions packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
UriFsInfo,
FileEntry,
ZoweExplorerApiType,
AuthHandler,
} from "@zowe/zowe-explorer-api";
import { IZosFilesResponse } from "@zowe/zos-files-for-zowe-sdk";
import { Profiles } from "../../configuration/Profiles";
Expand Down Expand Up @@ -388,13 +389,16 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem
const metadata = dsEntry?.metadata ?? this._getInfoFromUri(uri);
const profileEncoding = dsEntry?.encoding ? null : dsEntry?.metadata.profile.profile?.encoding;
try {
await AuthHandler.waitIfLocked(metadata.profile);
await AuthHandler.lockProfile(metadata.profile);
const resp = await ZoweExplorerApiRegister.getMvsApi(metadata.profile).getContents(metadata.dsName, {
binary: dsEntry?.encoding?.kind === "binary",
encoding: dsEntry?.encoding?.kind === "other" ? dsEntry?.encoding.codepage : profileEncoding,
responseTimeout: metadata.profile.profile?.responseTimeout,
returnEtag: true,
stream: bufBuilder,
});
AuthHandler.unlockProfile(metadata.profile);
const data: Uint8Array = bufBuilder.read() ?? new Uint8Array();
//if an entry does not exist for the dataset, create it
if (!dsEntry) {
Expand Down Expand Up @@ -470,6 +474,10 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem
}
}

if (ds && ds.metadata?.profile == null) {
throw vscode.FileSystemError.FileNotFound(vscode.l10n.t("Profile does not exist for this file."));
}

// we need to fetch the contents from the mainframe if the file hasn't been accessed yet
if (!ds || (!ds.wasAccessed && !urlQuery.has("inDiff")) || isConflict) {
//try and fetch its contents from remote
Expand All @@ -488,12 +496,6 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem
throw vscode.FileSystemError.FileNotFound(uri);
}

const profInfo = this._getInfoFromUri(uri);

if (profInfo.profile == null) {
throw vscode.FileSystemError.FileNotFound(vscode.l10n.t("Profile does not exist for this file."));
}

return isConflict ? ds.conflictData.contents : ds.data;
}

Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer/src/trees/shared/SharedInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export class SharedInit {
(error: CorrelatedError, stackTrace?: string) => new TroubleshootError(context, { error, stackTrace })
)
);
context.subscriptions.push(vscode.commands.registerCommand("zowe.getTreeProviders", () => SharedTreeProviders));
context.subscriptions.push(vscode.window.registerUriHandler(ZoweUriHandler.getInstance()));
context.subscriptions.push(
vscode.commands.registerCommand("zowe.placeholderCommand", () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/zowe-explorer/src/trees/uss/UssFSProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ZoweScheme,
UriFsInfo,
ZoweExplorerApiType,
AuthHandler,
} from "@zowe/zowe-explorer-api";
import { IZosFilesResponse } from "@zowe/zos-files-for-zowe-sdk";
import { USSFileStructure } from "./USSFileStructure";
Expand Down Expand Up @@ -279,13 +280,16 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv
try {
await this.autoDetectEncoding(file as UssFile);
const profileEncoding = file.encoding ? null : file.metadata.profile.profile?.encoding;
await AuthHandler.waitIfLocked(metadata.profile);
await AuthHandler.lockProfile(metadata.profile);
resp = await ZoweExplorerApiRegister.getUssApi(metadata.profile).getContents(filePath, {
binary: file.encoding?.kind === "binary",
encoding: file.encoding?.kind === "other" ? file.encoding.codepage : profileEncoding,
responseTimeout: metadata.profile.profile?.responseTimeout,
returnEtag: true,
stream: bufBuilder,
});
AuthHandler.unlockProfile(metadata.profile);
} catch (err) {
if (err instanceof Error) {
ZoweLogger.error(err.message);
Expand Down Expand Up @@ -322,6 +326,8 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv
return;
}

await AuthHandler.waitIfLocked(entry.metadata.profile);
await AuthHandler.lockProfile(entry.metadata.profile);
const ussApi = ZoweExplorerApiRegister.getUssApi(entry.metadata.profile);
if (ussApi.getTag != null) {
const taggedEncoding = await ussApi.getTag(entry.metadata.path);
Expand All @@ -334,6 +340,7 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv
const isBinary = await ussApi.isFileTagBinOrAscii(entry.metadata.path);
entry.encoding = isBinary ? { kind: "binary" } : undefined;
}
AuthHandler.unlockProfile(entry.metadata.profile);
}

public async fetchEncodingForUri(uri: vscode.Uri): Promise<ZosEncoding> {
Expand Down
23 changes: 17 additions & 6 deletions packages/zowe-explorer/src/utils/AuthUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,23 @@ export class AuthUtils {
profileName: profile.name,
},
});
await AuthHandler.lockProfile(profile, err, {
ssoLogin: Constants.PROFILES_CACHE.ssoLogin.bind(Constants.PROFILES_CACHE),
promptCredentials: Constants.PROFILES_CACHE.promptCredentials.bind(Constants.PROFILES_CACHE),
isUsingTokenAuth: await AuthUtils.isUsingTokenAuth(profile.name),
errorCorrelation,
});
if (AuthHandler.isLocked(profile)) {
await AuthHandler.promptForAuthentication(err, profile, {
ssoLogin: Constants.PROFILES_CACHE.ssoLogin.bind(Constants.PROFILES_CACHE),
promptCredentials: Constants.PROFILES_CACHE.promptCredentials.bind(Constants.PROFILES_CACHE),
isUsingTokenAuth: await AuthUtils.isUsingTokenAuth(profile.name),
errorCorrelation,
});
} else {
await AuthHandler.lockProfile(profile, err, {
ssoLogin: Constants.PROFILES_CACHE.ssoLogin.bind(Constants.PROFILES_CACHE),
promptCredentials: Constants.PROFILES_CACHE.promptCredentials.bind(Constants.PROFILES_CACHE),
isUsingTokenAuth: await AuthUtils.isUsingTokenAuth(profile.name),
errorCorrelation,
});
}
} else {
AuthHandler.unlockProfile(profile);
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/zowe-explorer/src/utils/ProfilesUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ export class ProfilesUtils {
args: [typeof profile === "string" ? profile : profile.name],
comment: ["Profile name"],
});
if (typeof profile !== "string") {
await Constants.PROFILES_CACHE.updateCachedProfile(profile, node);
}
ZoweLogger.info(successMsg);
Gui.showMessage(successMsg);
}
Expand Down

0 comments on commit 1216b03

Please sign in to comment.