Unexpected behaviour when retrieving username/password from zowe config with autoStore: true #1777
-
Hello, We're working on a custom extension that uses the CLI and imperative packages. We recently reworked our authentication handler to make it more robust and minimize the amount of times a user has to sign in. In general, the flow for authentication and creating a Session is as follows:
If we try to create a new session and the username/password are not stored in the import { ProfileInfo, IProfAttrs, Session } from '@zowe/imperative';
import * as zowe from '@zowe/cli';
...
private async doLogin(username: string, password: string) {
...
// Load base profile
const profile = this.profileInfo.getDefaultProfile('base');
if (profile === undefined || profile === null) {
throw new ProfileLoadingError(`Retrieved base profile is undefined`, MessageLocation.CORE);
}
const profileToUpdate = { profileName: profile.profName, profileType: profile.profType };
const updSession = new zowe.imperative.Session({
hostname: MAINFRAME_HOSTNAME,
port: MAINFRAME_PORT,
user: username, // --> Input has been validated before
password: password, // --> Input has been validated before
rejectUnauthorized: MAINFRAME_REJECT_UNAUTHORIZED, // false
tokenType: MAINFRAME_TOKEN_TYPE, // apimlAuthenticationToken
type: zowe.imperative.SessConstants.AUTH_TYPE_TOKEN
});
// Generate new token
const token = await zowe.Login.apimlLogin(updSession);
await this.profileInfo.updateProperty({
...profileToUpdate,
property: 'tokenType',
value: updSession.ISession.tokenType!
});
await this.profileInfo.updateProperty({
...profileToUpdate,
property: 'tokenValue',
value: token,
setSecure: this.profileInfo.isSecured()
});
// Reload profile info
await this._loadProfileInfo();
}
// Perhaps this needs to be reworked because we store our zowe profiles in `%USERPROFILE%/.zowe`
public async _loadProfileInfo() {
if (vscode.workspace.workspaceFolders !== undefined && vscode.workspace.workspaceFolders.length > 0) {
const workspaceRoot: string = vscode.workspace.workspaceFolders[0].uri.fsPath;
await this.profileInfo.readProfilesFromDisk({
homeDir: path.join(os.homedir(), '.zowe'),
projectDir: getFullPath(workspaceRoot)
});
} else {
await this.profileInfo.readProfilesFromDisk({
homeDir: path.join(os.homedir(), '.zowe'),
projectDir: false
});
}
const profiles = await this.profileInfo.getAllProfiles();
if (profiles.length === 0) {
throw new ProfileLoadingError('Unable to load profiles from disk', MessageLocation.CORE);
}
} When we make a call to APIML, for example retrieving a source file, and the session object is not defined, the following code will be executed to retrieved the needed values from private async createSession(): Promise<void> {
try {
const zosmfProfAttr = this.profileInfo.getDefaultProfile('zosmf');
if (zosmfProfAttr === null) {
throw new ProfileLoadingError('Unable to load default ZOSMF profile', MessageLocation.CORE);
}
let zosmfMergedArgs = this.profileInfo.mergeArgsForProfile(zosmfProfAttr, { getSecureVals: true});
// Validate if we have a filled and valid tokenValue in the profile
const tokenValueProfAttr = zosmfMergedArgs.knownArgs.find(v => v.argName === 'tokenValue');
if (!tokenValueProfAttr) {
// No TokenValue available in profile
// Check if we have username and password and login
const usernameProfAttr = zosmfMergedArgs.knownArgs.find(v => v.argName === 'user');
const passwordProfAttr = zosmfMergedArgs.knownArgs.find(v => v.argName === 'password');
if (!usernameProfAttr || !passwordProfAttr) {
// Do the login via the regular login command
await vscode.commands.executeCommand('daf.commands.authhandler.update-credentials');
// After login, reload the zosmfMergedArgs so that we have the new tokenValue
zosmfMergedArgs = this.profileInfo.mergeArgsForProfile(zosmfProfAttr, { getSecureVals: true});
} else {
// Login
if ((typeof usernameProfAttr.argValue !== 'string' || usernameProfAttr.argValue === '') || (typeof passwordProfAttr.argValue !== 'string' || passwordProfAttr.argValue === '')) {
// Do the login via the regular login command since we're missing either the username or the password
await vscode.commands.executeCommand('daf.commands.authhandler.update-credentials');
} else {
await this.doLogin(usernameProfAttr.argValue, passwordProfAttr.argValue);
}
// After login, reload the zosmfMergedArgs so that we have the new tokenValue
zosmfMergedArgs = this.profileInfo.mergeArgsForProfile(zosmfProfAttr!, { getSecureVals: true });
}
} else {
// We have a token in the profile
// Verify if the token is still valid...
if (typeof tokenValueProfAttr.argValue !== 'string') {
throw new ProfileLoadingError(`Expected token to be of type 'string', but received '${typeof tokenValueProfAttr.argValue}' instead`, MessageLocation.CORE);
}
let decodedToken = JSON.parse(Buffer.from(tokenValueProfAttr.argValue.split('.')[1], 'base64').toString());
if (decodedToken === null || decodedToken === undefined || decodedToken === '') {
throw new ProfileLoadingError(`Unable to decoded token`, MessageLocation.CORE);
}
if(!decodedToken.hasOwnProperty('exp')) {
throw new ProfileLoadingError(`Unable to retrieve expiration from token`, MessageLocation.CORE);
}
// Check if token is valid and ask user to login again if not
const currentEpoch = Math.floor(+new Date() / 1000);
if (decodedToken.exp <= currentEpoch) {
// token has expired, login again
// Check if we have a username and password
const usernameProfAttr = zosmfMergedArgs.knownArgs.find(v => v.argName === 'user');
const passwordProfAttr = zosmfMergedArgs.knownArgs.find(v => v.argName === 'password');
if (!usernameProfAttr || !passwordProfAttr) {
// Do the login via the regular login command
await vscode.commands.executeCommand('daf.commands.authhandler.update-credentials');
// After login, reload the zosmfMergedArgs so that we have the new tokenValue
zosmfMergedArgs = this.profileInfo.mergeArgsForProfile(zosmfProfAttr, { getSecureVals: true});
} else {
// Login
if ((typeof usernameProfAttr.argValue !== 'string' || usernameProfAttr.argValue === '') || (typeof passwordProfAttr.argValue !== 'string' || passwordProfAttr.argValue === '')) {
// Do the login via the regular login command since we're missing either the username or the password
await vscode.commands.executeCommand('daf.commands.authhandler.update-credentials');
} else {
await this.doLogin(usernameProfAttr.argValue, passwordProfAttr.argValue);
}
// After login, reload the zosmfMergedArgs so that we have the new tokenValue
zosmfMergedArgs = this.profileInfo.mergeArgsForProfile(zosmfProfAttr!, { getSecureVals: true });
}
}
}
this.session = ProfileInfo.createSession(zosmfMergedArgs.knownArgs);
} catch (err) {
this.logger.logError(err, MessageSeverity.ERROR);
}
} As you can see in the In the {
"$schema": "./zowe.schema.json",
"profiles": {
"DAF-mainframe": {
"type": "zosmf",
"properties": {
"basePath": "ibmzosmf/api/v1"
},
"secure": [
"user",
"password"
]
},
...
"DAF-base": {
"type": "base",
"properties": {
"host": "xxxx",
"port": 1234,
"rejectUnauthorized": true,
"account": "xxxx",
"characterSet": "697",
"codePage": "1047",
"tokenType": "apimlAuthenticationToken"
},
"secure": [
"tokenValue"
]
}
},
"defaults": {
"zosmf": "DAF-mainframe",
"tso": "DAF-tso3",
"base": "DAF-base"
},
"autoStore": true
} So far, we tested our reworked code with 3 users. For 2 of them, it works as expected: When the token is expired, the username and password are retrieved from the For one user however, it always fails when retrieving the username and password from the
I would expect that for this user, the extension would also be able to find the username and password in the At this point I'm stuck in finding the actual cause for this issue and I'm actually looking for some guidance in how to debug this. Any suggestions (and also improvents for the code above) are welcome! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
Hey @juleskreutzer, The indentation was a bit confusing towards the end of the file (starting with One more thing, I'm assuming that I do have a few questions to help me understand a bit better what could be causing the issue:
|
Beta Was this translation helpful? Give feedback.
To get back on this, we now explicitly store the
user
andpassword
properties in thezosmf
profile and this works as expected. I analyzed the log of a colleague this morning and see that when the token is expired, theuser
andpassword
are retrieved from thezosmf
profile and used to create a new session/login to APIML.For clarity, the
doLogin
method has been extended with the following code: