Skip to content

Commit 90985ce

Browse files
authored
Token auth check (#3855)
* fix Signed-off-by: jace-roell <[email protected]> * avoid duplicate entires Signed-off-by: jace-roell <[email protected]> * update profiles tests Signed-off-by: jace-roell <[email protected]> * changelog Signed-off-by: jace-roell <[email protected]> * unused import Signed-off-by: jace-roell <[email protected]> * fix case of original chlid path not being checked Signed-off-by: jace-roell <[email protected]> * optimize path building Signed-off-by: jace-roell <[email protected]> --------- Signed-off-by: jace-roell <[email protected]> Signed-off-by: Jace Roell <[email protected]>
1 parent 6e5a260 commit 90985ce

File tree

3 files changed

+233
-3
lines changed

3 files changed

+233
-3
lines changed

packages/zowe-explorer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
1111

1212
### Bug fixes
1313

14+
- Fixed regression where a profile was incorrectly detected as using basic authentication, specifically when `tokenValue` was set in the secure array of a parent profile or a default base profile instead of a service profile. [#3855](https://github.com/zowe/zowe-explorer-vscode/pull/3855)
1415
- Fixed an issue where secure credentials and headers were being logged to the Zowe logger and VSCode output channel. [#3848](https://github.com/zowe/zowe-explorer-vscode/pull/3848)
1516
- Updated Zowe SDKs to `8.27.0` for technical currency. [#3848](https://github.com/zowe/zowe-explorer-vscode/pull/3848)
1617

packages/zowe-explorer/__tests__/__unit__/configuration/Profiles.unit.test.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,199 @@ describe("Profiles Unit Tests - function deleteProfile", () => {
854854
});
855855
});
856856

857+
describe("Profiles Unit Tests - function profileHasSecureToken", () => {
858+
const globalMocks = createGlobalMocks();
859+
860+
const environmentSetup = (globalMocks): void => {
861+
globalMocks.testProfile.profile.password = null;
862+
globalMocks.testProfile.profile.tokenType = "";
863+
Object.defineProperty(Profiles.getInstance(), "profilesForValidation", {
864+
value: [
865+
{
866+
name: "sestest",
867+
message: "",
868+
type: "",
869+
status: "active",
870+
failNotFound: false,
871+
},
872+
],
873+
configurable: true,
874+
});
875+
Object.defineProperty(Profiles.getInstance(), "profilesValidationSetting", {
876+
value: [
877+
{
878+
name: "otherSestest",
879+
setting: false,
880+
},
881+
],
882+
configurable: true,
883+
});
884+
};
885+
886+
beforeEach(() => {
887+
jest.clearAllMocks();
888+
environmentSetup(globalMocks);
889+
});
890+
891+
it("should extract parent profiles", async () => {
892+
Object.defineProperty(Constants, "PROFILES_CACHE", { value: "test4" });
893+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
894+
getTeamConfig: () => ({
895+
api: {
896+
profiles: {
897+
getProfilePathFromName: () => "profiles.test1.profiles.test2.profiles.test3",
898+
},
899+
secure: {
900+
secureFields: () => ["test1.test2.test3"],
901+
},
902+
},
903+
}),
904+
isSecured: () => true,
905+
} as any);
906+
jest.spyOn(Profiles.getInstance(), "getDefaultProfile").mockReturnValue({} as any);
907+
expect((Profiles.getInstance() as any).profileHasSecureToken("test1.test2.test3")).toBeTruthy();
908+
});
909+
910+
it("should return false when no secure fields match any profile paths", async () => {
911+
Object.defineProperty(Constants, "PROFILES_CACHE", { value: { getDefaultProfile: () => null } });
912+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
913+
getTeamConfig: () => ({
914+
api: {
915+
profiles: {
916+
getProfilePathFromName: () => "profiles.test1.profiles.test2",
917+
},
918+
secure: {
919+
secureFields: () => ["someother.field.tokenValue"],
920+
},
921+
},
922+
}),
923+
isSecured: () => true,
924+
} as any);
925+
926+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "test2" });
927+
expect(result).toBeFalsy();
928+
});
929+
930+
it("should return true when base profile has secure token", async () => {
931+
const mockBaseProfile = { name: "base" };
932+
Object.defineProperty(Constants, "PROFILES_CACHE", {
933+
value: { getDefaultProfile: () => mockBaseProfile },
934+
});
935+
936+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
937+
getTeamConfig: () => ({
938+
api: {
939+
profiles: {
940+
getProfilePathFromName: (name) => {
941+
if (name === "base") return "profiles.base";
942+
return "profiles.test1.profiles.test2";
943+
},
944+
},
945+
secure: {
946+
secureFields: () => ["profiles.base.properties.tokenValue"],
947+
},
948+
},
949+
}),
950+
isSecured: () => true,
951+
} as any);
952+
953+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "test2" });
954+
expect(result).toBeTruthy();
955+
});
956+
957+
it("should handle empty secure fields array", async () => {
958+
Object.defineProperty(Constants, "PROFILES_CACHE", { value: { getDefaultProfile: () => null } });
959+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
960+
getTeamConfig: () => ({
961+
api: {
962+
profiles: {
963+
getProfilePathFromName: () => "profiles.test1.profiles.test2",
964+
},
965+
secure: {
966+
secureFields: () => [],
967+
},
968+
},
969+
}),
970+
isSecured: () => true,
971+
} as any);
972+
973+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "test2" });
974+
expect(result).toBeFalsy();
975+
});
976+
977+
it("should handle complex nested profile hierarchy", async () => {
978+
Object.defineProperty(Constants, "PROFILES_CACHE", { value: { getDefaultProfile: () => null } });
979+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
980+
getTeamConfig: () => ({
981+
api: {
982+
profiles: {
983+
getProfilePathFromName: () => "profiles.level1.profiles.level2.profiles.level3.profiles.level4",
984+
},
985+
secure: {
986+
secureFields: () => ["profiles.level1.profiles.level2.properties.tokenValue", "other.field"],
987+
},
988+
},
989+
}),
990+
isSecured: () => true,
991+
} as any);
992+
993+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "level4" });
994+
expect(result).toBeTruthy();
995+
});
996+
997+
it("should not include duplicate paths in allPaths array", async () => {
998+
Object.defineProperty(Constants, "PROFILES_CACHE", { value: { getDefaultProfile: () => null } });
999+
const secureFieldsSpy = jest.fn().mockReturnValue(["profiles.test1.properties.tokenValue"]);
1000+
1001+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
1002+
getTeamConfig: () => ({
1003+
api: {
1004+
profiles: {
1005+
getProfilePathFromName: () => "profiles.test1.profiles.test1", // duplicate segment
1006+
},
1007+
secure: {
1008+
secureFields: secureFieldsSpy,
1009+
},
1010+
},
1011+
}),
1012+
isSecured: () => true,
1013+
} as any);
1014+
1015+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "test1" });
1016+
1017+
// Should still work correctly even with duplicate segments
1018+
expect(result).toBeTruthy();
1019+
expect(secureFieldsSpy).toHaveBeenCalledTimes(1);
1020+
});
1021+
1022+
it("should handle base profile when it matches an existing path", async () => {
1023+
const mockBaseProfile = { name: "test1" };
1024+
Object.defineProperty(Constants, "PROFILES_CACHE", {
1025+
value: { getDefaultProfile: () => mockBaseProfile },
1026+
});
1027+
1028+
jest.spyOn(Profiles.getInstance(), "getProfileInfo").mockResolvedValue({
1029+
getTeamConfig: () => ({
1030+
api: {
1031+
profiles: {
1032+
getProfilePathFromName: (name) => {
1033+
if (name === "test1") return "profiles.test1";
1034+
return "profiles.test1.profiles.test2";
1035+
},
1036+
},
1037+
secure: {
1038+
secureFields: () => ["profiles.test1.properties.tokenValue"],
1039+
},
1040+
},
1041+
}),
1042+
isSecured: () => true,
1043+
} as any);
1044+
1045+
const result = await (Profiles.getInstance() as any).profileHasSecureToken({ name: "test2" });
1046+
expect(result).toBeTruthy();
1047+
});
1048+
});
1049+
8571050
describe("Profiles Unit Tests - function checkCurrentProfile", () => {
8581051
const environmentSetup = (globalMocks): void => {
8591052
globalMocks.testProfile.profile.password = null;

packages/zowe-explorer/src/configuration/Profiles.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,49 @@ export class Profiles extends ProfilesCache {
102102
* Checks if the profile has a secure token.
103103
* Note: This is a workaround to maintain backward compatibility.
104104
* @param theProfile - The profile to check.
105-
* @returns True if the profile has a secure token, false otherwise.
105+
* @returns True if the profile, a parent on the profile, or the default base profile has a secure token, false otherwise.
106106
*/
107107
private async profileHasSecureToken(theProfile: imperative.IProfileLoaded): Promise<boolean> {
108108
const teamConfig = (await this.getProfileInfo()).getTeamConfig();
109109
const profName = teamConfig.api.profiles.getProfilePathFromName(theProfile.name);
110-
const tokenValue = profName + ".properties.tokenValue";
111-
return teamConfig.api.secure.secureFields().includes(tokenValue);
110+
111+
const getCumulativePaths = (profName: string): string[] => {
112+
const parts = profName.split(".profiles.");
113+
return parts.slice(1).reduce(
114+
(acc, part) => {
115+
const previousPath = acc.length > 0 ? acc[acc.length - 1] : parts[0];
116+
const currentPath = `${previousPath}.profiles.${part}`;
117+
acc.push(currentPath);
118+
return acc;
119+
},
120+
[parts[0]]
121+
);
122+
};
123+
124+
const paths = getCumulativePaths(profName);
125+
126+
// Add all intermediate paths by working backwards
127+
const allPaths: string[] = [];
128+
for (const path of paths) {
129+
const segments = path.split(".profiles.");
130+
for (let j = 1; j <= segments.length; j++) {
131+
const subPath = segments.slice(0, j).join(".profiles.");
132+
if (!allPaths.includes(subPath)) {
133+
allPaths.push(subPath);
134+
}
135+
}
136+
}
137+
138+
const defaultBase = Constants.PROFILES_CACHE.getDefaultProfile?.("base");
139+
const profilePath = defaultBase && teamConfig.api.profiles.getProfilePathFromName(defaultBase.name);
140+
if (profilePath && !allPaths.includes(profilePath)) {
141+
allPaths.push(profilePath);
142+
}
143+
if (!allPaths.includes(profName)) {
144+
allPaths.push(profName);
145+
}
146+
147+
return allPaths.some((path) => teamConfig.api.secure.secureFields().includes(path + ".properties.tokenValue"));
112148
}
113149

114150
public async checkCurrentProfile(theProfile: imperative.IProfileLoaded, node?: Types.IZoweNodeType): Promise<Validation.IValidationProfile> {

0 commit comments

Comments
 (0)