diff --git a/packages/zowe-explorer-api/src/profiles/AuthHandler.ts b/packages/zowe-explorer-api/src/profiles/AuthHandler.ts index 6622b044c7..f8dcf089f5 100644 --- a/packages/zowe-explorer-api/src/profiles/AuthHandler.ts +++ b/packages/zowe-explorer-api/src/profiles/AuthHandler.ts @@ -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; @@ -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> = new Map(); @@ -35,7 +39,7 @@ export class AuthHandler { * @param {string[]} baseSecureProfileProps Base profile's secure properties (optional) * @returns {Promise} a boolean representing whether token based auth is being used or not */ - public static async isUsingTokenAuth(secureProfileProps: string[], baseSecureProfileProps?: string[]): Promise { + 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; @@ -43,13 +47,14 @@ export class AuthHandler { 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); } } @@ -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).getChildren()).find((n) => n.label === profileName); + if (dsNode && typeof profile !== "string") { + dsNode.setProfileToChoice(profile); + } + const ussNode = (await (treeProviders.uss as IZoweTree).getChildren()).find((n) => n.label === profileName); + if (ussNode && typeof profile !== "string") { + ussNode.setProfileToChoice(profile); + } + const jobsNode = (await (treeProviders.job as IZoweTree).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 { + public static async lockProfile(profile: ProfileLike, imperativeError?: imperative.ImperativeError, opts?: LockProfileOpts): Promise { const profileName = typeof profile === "string" ? profile : profile.name; if (this.lockedProfiles.has(profileName)) { return this.lockedProfiles.get(profileName)!.promise; @@ -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); } diff --git a/packages/zowe-explorer-api/src/profiles/ProfilesCache.ts b/packages/zowe-explorer-api/src/profiles/ProfilesCache.ts index e57067336a..275e434942 100644 --- a/packages/zowe-explorer-api/src/profiles/ProfilesCache.ts +++ b/packages/zowe-explorer-api/src/profiles/ProfilesCache.ts @@ -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"; diff --git a/packages/zowe-explorer/src/configuration/Constants.ts b/packages/zowe-explorer/src/configuration/Constants.ts index e55309adfb..b2edd1c351 100644 --- a/packages/zowe-explorer/src/configuration/Constants.ts +++ b/packages/zowe-explorer/src/configuration/Constants.ts @@ -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; diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index 2a84437e99..d0cd5c884f 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -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"; @@ -388,6 +389,8 @@ 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, @@ -395,6 +398,7 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem 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) { @@ -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 @@ -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; } diff --git a/packages/zowe-explorer/src/trees/shared/SharedInit.ts b/packages/zowe-explorer/src/trees/shared/SharedInit.ts index 539aae7800..9733254159 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedInit.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedInit.ts @@ -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", () => { diff --git a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts index d8dc4374e1..9367b2af8b 100644 --- a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts +++ b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts @@ -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"; @@ -279,6 +280,8 @@ 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, @@ -286,6 +289,7 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv returnEtag: true, stream: bufBuilder, }); + AuthHandler.unlockProfile(metadata.profile); } catch (err) { if (err instanceof Error) { ZoweLogger.error(err.message); @@ -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); @@ -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 { diff --git a/packages/zowe-explorer/src/utils/AuthUtils.ts b/packages/zowe-explorer/src/utils/AuthUtils.ts index fd80bddbd3..5d36daa890 100644 --- a/packages/zowe-explorer/src/utils/AuthUtils.ts +++ b/packages/zowe-explorer/src/utils/AuthUtils.ts @@ -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); } } diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index c0d2ece423..1b212a9dd7 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -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); }