Skip to content

Commit

Permalink
Merge pull request #3151 from zowe/fix/switch-token-to-basic
Browse files Browse the repository at this point in the history
fix: resolve error when changing token to basic auth
  • Loading branch information
JillieBeanSim authored Sep 30, 2024
2 parents 838f34d + 6108792 commit d993a78
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Fixed issue where obsolete credentials persisted for PDS member nodes in Data Sets tree. [#3112](https://github.com/zowe/zowe-explorer-vscode/issues/3112)
- Fixed issue where Search operation did not prompt for credentials if profile contains expired token. [#2259](https://github.com/zowe/zowe-explorer-vscode/issues/2259)
- Fixed issue where inactive status was not displayed for profiles loaded from Global Config. [#3134](https://github.com/zowe/zowe-explorer-vscode/issues/3134)
- Fixed issue where switching from token-based authentication to user/password would cause an error for nested profiles. [#3142](https://github.com/zowe/zowe-explorer-vscode/issues/3142)

## `3.0.0-next.202409132122`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ import * as os from "os";
import * as path from "path";
const log4js = require("log4js");

export enum ProfLocType {
OLD_PROFILE = 0, // an old-school profile
TEAM_CONFIG = 1, // a team configuration
ENV = 2, // an environment variable
DEFAULT = 3,
}

/**
* Constants used for REST client, etc.
* @export
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2143,3 +2143,141 @@ describe("Profiles Unit Tests - function promptChangeForAllTrees", () => {
expect(hideSpy).toHaveBeenCalledTimes(1);
});
});

describe("Profiles Unit Tests - function basicAuthClearSecureArray", () => {
it("calls Config APIs when profLoc.jsonLoc is valid, no loginTokenType provided", async () => {
const teamCfgMock = {
delete: jest.fn(),
save: jest.fn(),
set: jest.fn(),
};
const profAttrsMock = {
isDefaultProfile: false,
profName: "example_profile",
profType: "zosmf",
profLoc: {
jsonLoc: "/user/path/to/zowe.config.json",
locType: imperative.ProfLocType.TEAM_CONFIG,
},
};
const mergeArgsMock = {
knownArgs: [
{
argName: "user",
argLoc: {
jsonLoc: "profiles.example_profile.properties.user",
},
},
{
argName: "password",
argLoc: {
jsonLoc: "profiles.example_profile.properties.password",
},
},
],
};
const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
getTeamConfig: jest.fn().mockReturnValue(teamCfgMock),
mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock),
} as any);
const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock);

await Profiles.getInstance().basicAuthClearSecureArray("example_profile");
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[0].argLoc.jsonLoc);
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[1].argLoc.jsonLoc);
expect(teamCfgMock.set).toHaveBeenCalledWith(`${profAttrsMock.profLoc.jsonLoc}.secure`, ["tokenValue"]);
expect(teamCfgMock.save).toHaveBeenCalled();
getProfileInfoMock.mockRestore();
getProfileFromConfigMock.mockRestore();
});
it("calls Config APIs when profLoc.jsonLoc is valid, loginTokenType provided", async () => {
const teamCfgMock = {
delete: jest.fn(),
save: jest.fn(),
set: jest.fn(),
};
const profAttrsMock = {
isDefaultProfile: false,
profName: "example_profile",
profType: "zosmf",
profLoc: {
jsonLoc: "/user/path/to/zowe.config.json",
locType: imperative.ProfLocType.TEAM_CONFIG,
},
};
const mergeArgsMock = {
knownArgs: [
{
argName: "user",
argLoc: {
jsonLoc: "profiles.example_profile.properties.user",
},
},
{
argName: "password",
argLoc: {
jsonLoc: "profiles.example_profile.properties.password",
},
},
],
};
const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
getTeamConfig: jest.fn().mockReturnValue(teamCfgMock),
mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock),
} as any);
const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock);

await Profiles.getInstance().basicAuthClearSecureArray("example_profile", "apimlAuthenticationToken");
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[0].argLoc.jsonLoc);
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[1].argLoc.jsonLoc);
expect(teamCfgMock.set).toHaveBeenCalledWith(`${profAttrsMock.profLoc.jsonLoc}.secure`, []);
expect(teamCfgMock.save).toHaveBeenCalled();
getProfileInfoMock.mockRestore();
getProfileFromConfigMock.mockRestore();
});

it("does not call Config.set when profLoc.jsonLoc is invalid", async () => {
const teamCfgMock = {
delete: jest.fn(),
save: jest.fn(),
set: jest.fn(),
};
const profAttrsMock = {
isDefaultProfile: false,
profName: "example_profile",
profType: "zosmf",
profLoc: {
jsonLoc: undefined,
},
};
const mergeArgsMock = {
knownArgs: [
{
argName: "user",
argLoc: {
jsonLoc: "profiles.example_profile.properties.user",
},
},
{
argName: "password",
argLoc: {
jsonLoc: "profiles.example_profile.properties.password",
},
},
],
};
const getProfileInfoMock = jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
getTeamConfig: jest.fn().mockReturnValue(teamCfgMock),
mergeArgsForProfile: jest.fn().mockReturnValue(mergeArgsMock),
} as any);
const getProfileFromConfigMock = jest.spyOn(Profiles.getInstance(), "getProfileFromConfig").mockResolvedValue(profAttrsMock);

await Profiles.getInstance().basicAuthClearSecureArray("example_profile");
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[0].argLoc.jsonLoc);
expect(teamCfgMock.delete).toHaveBeenCalledWith(mergeArgsMock.knownArgs[1].argLoc.jsonLoc);
expect(teamCfgMock.set).not.toHaveBeenCalled();
expect(teamCfgMock.save).toHaveBeenCalled();
getProfileInfoMock.mockRestore();
getProfileFromConfigMock.mockRestore();
});
});
26 changes: 12 additions & 14 deletions packages/zowe-explorer/src/configuration/Profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,10 +821,11 @@ export class Profiles extends ProfilesCache {
const profInfo = await this.getProfileInfo();
const configApi = profInfo.getTeamConfig();
const profAttrs = await this.getProfileFromConfig(profileName);
if (loginTokenType && loginTokenType.startsWith("apimlAuthenticationToken")) {
configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, []);
} else {
configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, ["tokenValue"]);
if (profAttrs.profLoc.jsonLoc) {
configApi.set(
`${profAttrs.profLoc.jsonLoc}.secure`,
loginTokenType?.startsWith("apimlAuthenticationToken") ? [] : ["tokenValue"]
);
}
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "user")?.argLoc.jsonLoc);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "password")?.argLoc.jsonLoc);
Expand All @@ -834,20 +835,17 @@ export class Profiles extends ProfilesCache {
public async tokenAuthClearSecureArray(profileName?: string, loginTokenType?: string): Promise<void> {
const profInfo = await this.getProfileInfo();
const configApi = profInfo.getTeamConfig();
if (loginTokenType && loginTokenType.startsWith("apimlAuthenticationToken")) {
const profAttrs = await this.getProfileFromConfig("base");
configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, []);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenType")?.argLoc.jsonLoc);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenValue")?.argLoc.jsonLoc);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenExpiration")?.argLoc.jsonLoc);
} else {
const profAttrs = await this.getProfileFromConfig(profileName);
configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, ["user", "password"]);
const usingApimlToken = loginTokenType?.startsWith("apimlAuthenticationToken");
const profAttrs = await this.getProfileFromConfig(usingApimlToken ? "base" : profileName);
// For users with nested profiles, we should only update the secure array if a base profile or a regular profile matching profileName exists.
// Otherwise, we want to keep `tokenValue` in the secure array of the parent profile to avoid disconnecting child profiles
if (profAttrs?.profLoc.jsonLoc) {
configApi.set(`${profAttrs.profLoc.jsonLoc}.secure`, usingApimlToken ? [] : ["user", "password"]);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenType")?.argLoc.jsonLoc);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenValue")?.argLoc.jsonLoc);
configApi.delete(profInfo.mergeArgsForProfile(profAttrs).knownArgs.find((arg) => arg.argName === "tokenExpiration")?.argLoc.jsonLoc);
await configApi.save();
}
await configApi.save();
}

public async handleSwitchAuthentication(node: Types.IZoweNodeType): Promise<void> {
Expand Down

0 comments on commit d993a78

Please sign in to comment.