diff --git a/.vscode/launch.json b/.vscode/launch.json index 098a5ca927..772a2e5584 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,6 +37,15 @@ "console": "integratedTerminal", "sourceMaps": true }, + { + "type": "node", + "name": "Current Unit Tests (Jest)", + "request": "launch", + "runtimeArgs": ["--inspect-brk", "${workspaceFolder}/node_modules/jest/bin/jest", "-i", "${fileBasenameNoExtension}"], + "cwd": "${workspaceFolder}/packages/zowe-explorer", + "console": "integratedTerminal", + "sourceMaps": true + }, { "type": "node", "name": "API Unit Tests (Jest)", diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 805231b081..ea5edee79d 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -24,6 +24,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - Fixed issue where Zowe Explorer would present the "No configs detected" notification when initialized in a workspace without a Zowe team configuration. [#3280](https://github.com/zowe/zowe-explorer-vscode/issues/3280) - Reduced the number of MVS API calls performed by `vscode.workspace.fs.readFile` when fetching the contents of a data set entry. [#3278](https://github.com/zowe/zowe-explorer-vscode/issues/3278) - Fixed an issue to review inconsistent capitalization across translation strings. [#2935](https://github.com/zowe/zowe-explorer-vscode/issues/2935) +- Updated the test for the default credential manager for better compatibility with Cloud-based platforms such as Eclipse Che and Red Hat OpenShift Dev Spaces. [#3297](https://github.com/zowe/zowe-explorer-vscode/pull/3297) - Fixed issue where persistent settings defined at the workspace level were migrated into global storage rather than workspace-specific storage. [#3180](https://github.com/zowe/zowe-explorer-vscode/issues/3180) ## `3.0.2` diff --git a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts index c7e8b9af01..41fd8274df 100644 --- a/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/utils/ProfilesUtils.unit.test.ts @@ -65,6 +65,7 @@ describe("ProfilesUtils unit tests", () => { Object.defineProperty(ZoweLogger, "info", { value: jest.fn(), configurable: true }); Object.defineProperty(SettingsConfig, "getDirectValue", { value: newMocks.mockGetDirectValue, configurable: true }); Object.defineProperty(ProfilesUtils, "PROFILE_SECURITY", { value: Constants.ZOWE_CLI_SCM, configurable: true }); + Object.defineProperty(ProfilesUtils, "checkDefaultCredentialManager", { value: jest.fn(), configurable: true }); return newMocks; } @@ -457,6 +458,7 @@ describe("ProfilesUtils unit tests", () => { describe("initializeZoweFolder", () => { it("should create directories and files that do not exist", async () => { const blockMocks = createBlockMocks(); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); blockMocks.mockGetDirectValue.mockReturnValue(true); blockMocks.mockExistsSync.mockReturnValue(false); jest.spyOn(fs, "readFileSync").mockReturnValue(Buffer.from(JSON.stringify({ overrides: { credentialManager: "@zowe/cli" } }), "utf-8")); @@ -469,6 +471,7 @@ describe("ProfilesUtils unit tests", () => { it("should skip creating directories and files that already exist", async () => { const blockMocks = createBlockMocks(); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); jest.spyOn(ProfilesUtils, "getCredentialManagerOverride").mockReturnValue("@zowe/cli"); blockMocks.mockGetDirectValue.mockReturnValue("@zowe/cli"); blockMocks.mockExistsSync.mockReturnValue(true); @@ -566,7 +569,7 @@ describe("ProfilesUtils unit tests", () => { it("should handle Imperative error thrown on read config from disk", async () => { const testError = new imperative.ImperativeError({ msg: "readConfigFromDisk failed" }); - const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockReturnValueOnce(); + const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockResolvedValueOnce(); const readConfigFromDiskSpy = jest.spyOn(ProfilesUtils, "readConfigFromDisk").mockRejectedValueOnce(testError); await ProfilesUtils.initializeZoweProfiles((msg) => ZoweExplorerExtender.showZoweConfigError(msg)); expect(initZoweFolderSpy).toHaveBeenCalledTimes(1); @@ -576,7 +579,7 @@ describe("ProfilesUtils unit tests", () => { it("should handle JSON parse error thrown on read config from disk", async () => { const testError = new Error("readConfigFromDisk failed"); - const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockReturnValueOnce(); + const initZoweFolderSpy = jest.spyOn(ProfilesUtils, "initializeZoweFolder").mockResolvedValueOnce(); const readConfigFromDiskSpy = jest.spyOn(ProfilesUtils, "readConfigFromDisk").mockRejectedValueOnce(testError); const showZoweConfigErrorSpy = jest.spyOn(ZoweExplorerExtender, "showZoweConfigError").mockReturnValueOnce(); await ProfilesUtils.initializeZoweProfiles((msg) => ZoweExplorerExtender.showZoweConfigError(msg)); @@ -649,6 +652,7 @@ describe("ProfilesUtils unit tests", () => { it("should update the credential manager setting if secure value is true", () => { jest.spyOn(SettingsConfig, "isConfigSettingSetByUser").mockReturnValue(false); jest.spyOn(SettingsConfig, "getDirectValue").mockReturnValueOnce(true); + jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager").mockReturnValue(true); const loggerInfoSpy = jest.spyOn(ZoweLogger, "info"); const recordCredMgrInConfigSpy = jest.spyOn(imperative.CredentialManagerOverride, "recordCredMgrInConfig"); ProfilesUtils.updateCredentialManagerSetting(); @@ -679,7 +683,8 @@ describe("ProfilesUtils unit tests", () => { let getCredentialManagerMapSpy: jest.SpyInstance; let setupCustomCredentialManagerSpy: jest.SpyInstance; let readProfilesFromDiskSpy: jest.SpyInstance; - let promptAndDisableCredentialManagementSpy: jest.SpyInstance; + let disableCredentialManagementSpy: jest.SpyInstance; + let checkDefaultCredentialManagerSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); @@ -692,7 +697,8 @@ describe("ProfilesUtils unit tests", () => { getCredentialManagerMapSpy = jest.spyOn(ProfilesUtils, "getCredentialManagerMap"); setupCustomCredentialManagerSpy = jest.spyOn(ProfilesUtils, "setupCustomCredentialManager"); readProfilesFromDiskSpy = jest.spyOn(imperative.ProfileInfo.prototype, "readProfilesFromDisk"); - promptAndDisableCredentialManagementSpy = jest.spyOn(ProfilesUtils, "promptAndDisableCredentialManagement"); + disableCredentialManagementSpy = jest.spyOn(ProfilesUtils, "disableCredentialManagement"); + checkDefaultCredentialManagerSpy = jest.spyOn(ProfilesUtils, "checkDefaultCredentialManager"); }); it("should retrieve the custom credential manager", async () => { @@ -720,10 +726,11 @@ describe("ProfilesUtils unit tests", () => { await expect(ProfilesUtils.getProfileInfo()).resolves.toEqual({}); }); - it("should retrieve the default credential manager and prompt to disable credential management if environment not supported", async () => { + it("should throw exception of readProfilesFromDiskSpy fails", async () => { const expectedErrMsg = // eslint-disable-next-line max-len "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment."; + checkDefaultCredentialManagerSpy.mockReturnValue(false); getDirectValueSpy.mockReturnValueOnce(false); getCredentialManagerOverrideSpy.mockReturnValue("@zowe/cli"); isVSCodeCredentialPluginInstalledSpy.mockReturnValueOnce(false); @@ -741,11 +748,11 @@ describe("ProfilesUtils unit tests", () => { throw err; }); await expect(ProfilesUtils.getProfileInfo()).rejects.toThrow(expectedErrMsg); - expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(1); }); it("should ignore error if it is not an instance of ProfInfoErr", async () => { const expectedErrorMsg = "Another error unrelated to credential management"; + checkDefaultCredentialManagerSpy.mockReturnValue(true); getDirectValueSpy.mockReturnValueOnce(false); getCredentialManagerOverrideSpy.mockReturnValue("@zowe/cli"); isVSCodeCredentialPluginInstalledSpy.mockReturnValueOnce(false); @@ -756,7 +763,7 @@ describe("ProfilesUtils unit tests", () => { throw new Error(expectedErrorMsg); }); await expect(ProfilesUtils.getProfileInfo()).resolves.not.toThrow(); - expect(promptAndDisableCredentialManagementSpy).toHaveBeenCalledTimes(0); + expect(disableCredentialManagementSpy).toHaveBeenCalledTimes(0); }); }); @@ -960,10 +967,11 @@ describe("ProfilesUtils unit tests", () => { }); }); - describe("promptAndDisableCredentialManagement", () => { + describe("disableCredentialManagement", () => { let setDirectValueSpy: jest.SpyInstance; let warningMessageSpy: jest.SpyInstance; let executeCommandSpy: jest.SpyInstance; + let getDirectValueSpy: jest.SpyInstance; beforeEach(() => { jest.clearAllMocks(); @@ -972,33 +980,16 @@ describe("ProfilesUtils unit tests", () => { setDirectValueSpy = jest.spyOn(SettingsConfig, "setDirectValue"); warningMessageSpy = jest.spyOn(Gui, "warningMessage"); executeCommandSpy = jest.spyOn(vscode.commands, "executeCommand"); + getDirectValueSpy = jest.spyOn(SettingsConfig, "getDirectValue"); }); - it("should prompt whether to disable credential management, and disable globally if 'Yes, globally' selected", async () => { + it("should show warning that credential management was disabled", async () => { warningMessageSpy.mockResolvedValue("Yes, globally"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).resolves.not.toThrow(); + getDirectValueSpy.mockReturnValueOnce(true); + await expect(ProfilesUtils.disableCredentialManagement()).resolves.not.toThrow(); expect(setDirectValueSpy).toHaveBeenCalledWith(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); expect(executeCommandSpy).toHaveBeenCalledWith("workbench.action.reloadWindow"); }); - - it("should prompt whether to disable credential management, and disable on workspace if 'Only for this workspace' selected", async () => { - warningMessageSpy.mockResolvedValue("Only for this workspace"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).resolves.not.toThrow(); - expect(setDirectValueSpy).toHaveBeenCalledWith( - Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, - false, - vscode.ConfigurationTarget.Workspace - ); - expect(executeCommandSpy).toHaveBeenCalledWith("workbench.action.reloadWindow"); - }); - - it("should prompt whether to disable credential management, and throw error if 'No'", async () => { - warningMessageSpy.mockResolvedValue("No"); - await expect(ProfilesUtils.promptAndDisableCredentialManagement()).rejects.toThrow( - // eslint-disable-next-line max-len - "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment." - ); - }); }); describe("v1ProfileOptions", () => { diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index ea30ecb0ae..7ebc5cb55a 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -57,10 +57,9 @@ "Credential manager display name" ] }, - "Yes, globally": "Yes, globally", - "Only for this workspace": "Only for this workspace", - "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.": "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.", - "Do you wish to disable credential management? (VS Code window reload will be triggered)": "Do you wish to disable credential management? (VS Code window reload will be triggered)", + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.": "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.", + "Reload window": "Reload window", + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.": "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.", "No custom credential managers found, using the default instead.": "No custom credential managers found, using the default instead.", "Custom credential manager {0} found/Credential manager display name": { "message": "Custom credential manager {0} found", diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index bee96d5256..b38dcae06b 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -469,10 +469,9 @@ "Zowe Explorer profiles are being set as secured.": "", "Custom credential manager failed to activate": "", "Custom credential manager {0} found, attempting to activate.": "", - "Yes, globally": "", - "Only for this workspace": "", - "Zowe Explorer failed to activate since the default credential manager is not supported in your environment.": "", - "Do you wish to disable credential management? (VS Code window reload will be triggered)": "", + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager.": "", + "Reload window": "", + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions.": "", "No custom credential managers found, using the default instead.": "", "Custom credential manager {0} found": "", "Do you wish to use this credential manager instead?": "", diff --git a/packages/zowe-explorer/src/utils/ProfilesUtils.ts b/packages/zowe-explorer/src/utils/ProfilesUtils.ts index a54adae25a..5abc97cfe5 100644 --- a/packages/zowe-explorer/src/utils/ProfilesUtils.ts +++ b/packages/zowe-explorer/src/utils/ProfilesUtils.ts @@ -28,7 +28,7 @@ export enum ProfilesConvertStatus { export class ProfilesUtils { public static PROFILE_SECURITY: string | boolean = Constants.ZOWE_CLI_SCM; - private static noConfigDialogShown: boolean = false; + private static noConfigDialogShown = false; /** * Check if the credential manager's vsix is installed for use @@ -82,18 +82,22 @@ export class ProfilesUtils { */ public static updateCredentialManagerSetting(credentialManager?: string | false): void { ZoweLogger.trace("ProfilesUtils.updateCredentialManagerSetting called."); + const currentProfileSecurity = this.PROFILE_SECURITY; const settingEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, true); + const defaultCredentialManagerFound = this.checkDefaultCredentialManager(); if (settingEnabled && credentialManager) { this.PROFILE_SECURITY = credentialManager; return; - } else if (!settingEnabled) { + } else if (!settingEnabled || !defaultCredentialManagerFound) { this.PROFILE_SECURITY = false; ZoweLogger.info(vscode.l10n.t(`Zowe Explorer profiles are being set as unsecured.`)); } else { this.PROFILE_SECURITY = Constants.ZOWE_CLI_SCM; ZoweLogger.info(vscode.l10n.t(`Zowe Explorer profiles are being set as secured.`)); } - imperative.CredentialManagerOverride.recordCredMgrInConfig(this.PROFILE_SECURITY); + if (currentProfileSecurity !== this.PROFILE_SECURITY) { + imperative.CredentialManagerOverride.recordCredMgrInConfig(this.PROFILE_SECURITY); + } } /** @@ -149,35 +153,38 @@ export class ProfilesUtils { * Prompt whether to disable credential management setting * * This will disable credential management on all settings - * scopes since order presedence can be hard to predict based on the user's setup + * scopes since order precedence can be hard to predict based on the user's setup */ - public static async promptAndDisableCredentialManagement(): Promise { - ZoweLogger.trace("ProfilesUtils.promptAndDisableCredentialManagement called."); - const noButton = vscode.l10n.t("No"); - const yesGloballyButton = vscode.l10n.t("Yes, globally"); - const yesWorkspaceButton = vscode.l10n.t("Only for this workspace"); - const response = await Gui.warningMessage( - vscode.l10n.t("Zowe Explorer failed to activate since the default credential manager is not supported in your environment."), - { - items: [noButton, yesGloballyButton, yesWorkspaceButton], - vsCodeOpts: { - modal: true, - detail: vscode.l10n.t("Do you wish to disable credential management? (VS Code window reload will be triggered)"), - }, - } - ); - if (response === yesGloballyButton || response === yesWorkspaceButton) { - const scope = response === yesGloballyButton ? vscode.ConfigurationTarget.Global : vscode.ConfigurationTarget.Workspace; - await SettingsConfig.setDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, scope); - await vscode.commands.executeCommand("workbench.action.reloadWindow"); - } else { - throw new imperative.ImperativeError({ - msg: vscode.l10n.t( - // eslint-disable-next-line max-len - "Failed to load credential manager. This may be related to Zowe Explorer being unable to use the default credential manager in a browser based environment." + public static async disableCredentialManagement(): Promise { + ZoweLogger.trace("ProfilesUtils.disableCredentialManagement called."); + const settingEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, true); + if (settingEnabled) { + this.PROFILE_SECURITY = false; + await SettingsConfig.setDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED, false, vscode.ConfigurationTarget.Global); + await Gui.infoMessage( + vscode.l10n.t( + "Zowe Explorer's default credential manager is not supported in your environment. Consider installing a custom solution for your platform. Click Reload to start Zowe Explorer without a credential manager." ), - }); + { + items: [vscode.l10n.t("Reload window")], + } + ); + await vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + } + + public static checkDefaultCredentialManager(): boolean { + try { + ProfilesCache.requireKeyring(); + } catch (_error) { + ZoweLogger.info( + vscode.l10n.t( + "Default Zowe credentials manager not found on current platform. This is typically the case when running in container-based environments or Linux systems that miss required security libraries or user permissions." + ) + ); + return false; } + return true; } /** @@ -204,7 +211,7 @@ export class ProfilesUtils { return profileInfo; } catch (err) { if (err instanceof imperative.ProfInfoErr && err.errorCode === imperative.ProfInfoErr.LOAD_CRED_MGR_FAILED) { - await ProfilesUtils.promptAndDisableCredentialManagement(); + await ProfilesUtils.disableCredentialManagement(); } if (err instanceof Error) { ZoweLogger.error(err.message); @@ -306,8 +313,8 @@ export class ProfilesUtils { */ public static async getProfileInfo(): Promise { ZoweLogger.trace("ProfilesUtils.getProfileInfo called."); - const hasSecureCredentialManagerEnabled: boolean = SettingsConfig.getDirectValue(Constants.SETTINGS_SECURE_CREDENTIALS_ENABLED); + const hasSecureCredentialManagerEnabled: boolean = this.checkDefaultCredentialManager(); if (hasSecureCredentialManagerEnabled) { const shouldCheckForCustomCredentialManagers = SettingsConfig.getDirectValue( Constants.SETTINGS_CHECK_FOR_CUSTOM_CREDENTIAL_MANAGERS @@ -327,9 +334,17 @@ export class ProfilesUtils { if (credentialManagerMap && isVSCodeCredentialPluginInstalled) { return this.setupCustomCredentialManager(credentialManagerMap); } + return this.setupDefaultCredentialManager(); } - return this.setupDefaultCredentialManager(); + const profileInfo = new imperative.ProfileInfo("zowe", {}); + const workspacePath = ZoweVsCodeExtension.workspaceRoot?.uri.fsPath; + // Trigger initialize() function of credential manager to throw an error early if failed to load + await profileInfo.readProfilesFromDisk({ + homeDir: FileManagement.getZoweDir(), + projectDir: workspacePath ? FileManagement.getFullPath(workspacePath) : undefined, + }); + return profileInfo; } public static async readConfigFromDisk(warnForMissingSchema?: boolean): Promise { @@ -466,7 +481,7 @@ export class ProfilesUtils { } } - public static initializeZoweFolder(): void { + public static async initializeZoweFolder(): Promise { ZoweLogger.trace("ProfilesUtils.initializeZoweFolder called."); // Ensure that ~/.zowe folder exists const zoweDir = FileManagement.getZoweDir(); @@ -477,6 +492,11 @@ export class ProfilesUtils { if (!fs.existsSync(settingsPath)) { fs.mkdirSync(settingsPath); } + + if (!this.checkDefaultCredentialManager()) { + await this.disableCredentialManagement(); + } + ProfilesUtils.writeOverridesFile(); // set global variable of security value to existing override // this will later get reverted to default in getProfilesInfo.ts if user chooses to @@ -553,7 +573,7 @@ export class ProfilesUtils { public static async initializeZoweProfiles(errorCallback: (msg: string) => unknown): Promise { ZoweLogger.trace("ProfilesUtils.initializeZoweProfiles called."); try { - ProfilesUtils.initializeZoweFolder(); + await ProfilesUtils.initializeZoweFolder(); } catch (err) { ZoweLogger.error(err); Gui.errorMessage(