try to use an existing refresh token to get a token with the specified scopes. Fixes #114003

This commit is contained in:
Tyler Leonhardt 2022-03-03 14:54:48 -08:00
parent 49a5c195e6
commit 739dfea8e6
No known key found for this signature in database
GPG key ID: 1BC2B6244363E77E

View file

@ -164,6 +164,7 @@ export class AzureActiveDirectoryService {
sessionId: session.id
});
} else {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
Logger.error(e);
await this.removeSession(session.id);
}
@ -189,7 +190,7 @@ export class AzureActiveDirectoryService {
return sessions;
}
const modifiedScopes = [...scopes];
let modifiedScopes = [...scopes];
if (!modifiedScopes.includes('openid')) {
modifiedScopes.push('openid');
}
@ -199,9 +200,10 @@ export class AzureActiveDirectoryService {
if (!modifiedScopes.includes('profile')) {
modifiedScopes.push('profile');
}
modifiedScopes = modifiedScopes.sort();
let orderedScopes = modifiedScopes.sort().join(' ');
Logger.info(`Getting sessions for the following scopes: ${orderedScopes}`);
let modifiedScopesStr = modifiedScopes.join(' ');
Logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`);
if (this._refreshingPromise) {
Logger.info('Refreshing in progress. Waiting for completion before continuing.');
@ -212,18 +214,51 @@ export class AzureActiveDirectoryService {
}
}
let matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
let matchingTokens = this._tokens.filter(token => token.scope === modifiedScopesStr);
// The user may still have a token that doesn't have the openid & email scopes so check for that as well.
// Eventually, we should remove this and force the user to re-log in so that we don't have any sessions
// without an idtoken.
if (!matchingTokens.length) {
orderedScopes = scopes.sort().join(' ');
Logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${orderedScopes}`);
matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
const fallbackOrderedScopes = scopes.sort().join(' ');
Logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`);
matchingTokens = this._tokens.filter(token => token.scope === fallbackOrderedScopes);
if (matchingTokens.length) {
modifiedScopesStr = fallbackOrderedScopes;
}
}
Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${orderedScopes}`);
// If we still don't have a matching token try to get a new token from an existing token by using
// the refreshToken. This is documented here:
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
// "Refresh tokens are valid for all permissions that your client has already received consent for."
if (!matchingTokens.length) {
const clientId = this.getClientId(modifiedScopes);
// Get a token with the correct client id.
const token = clientId === DEFAULT_CLIENT_ID
? this._tokens.find(t => t.refreshToken && !t.scope.includes('VSCODE_CLIENT_ID'))
: this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${clientId}`));
if (token) {
const scopeData: IScopeData = {
clientId,
scopes: modifiedScopes,
scopeStr: modifiedScopesStr,
// filter our special scopes
scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '),
tenant: this.getTenantId(modifiedScopes),
};
try {
const itoken = await this.refreshToken(token.refreshToken, scopeData);
matchingTokens.push(itoken);
} catch (err) {
Logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`);
}
}
}
Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`);
return Promise.all(matchingTokens.map(token => this.convertToSession(token)));
}
@ -402,6 +437,7 @@ export class AzureActiveDirectoryService {
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
} catch (e) {
if (e.message !== REFRESH_NETWORK_FAILURE) {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
await this.removeSession(sessionId);
}
}
@ -517,7 +553,7 @@ export class AzureActiveDirectoryService {
//#region refresh logic
private async refreshToken(refreshToken: string, scopeData: IScopeData, sessionId: string): Promise<IToken> {
private async refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise<IToken> {
this._refreshingPromise = this.doRefreshToken(refreshToken, scopeData, sessionId);
try {
const result = await this._refreshingPromise;
@ -527,7 +563,7 @@ export class AzureActiveDirectoryService {
}
}
private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId: string): Promise<IToken> {
private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise<IToken> {
Logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`);
const postData = querystring.stringify({
refresh_token: refreshToken,
@ -552,13 +588,14 @@ export class AzureActiveDirectoryService {
} catch (e) {
if (e.message === REFRESH_NETWORK_FAILURE) {
// We were unable to refresh because of a network failure (i.e. the user lost internet access).
// so set up a timeout to try again later.
this.setSessionTimeout(sessionId, refreshToken, scopeData, AzureActiveDirectoryService.POLLING_CONSTANT);
// so set up a timeout to try again later. We only do this if we have a session id to reference later.
if (sessionId) {
this.setSessionTimeout(sessionId, refreshToken, scopeData, AzureActiveDirectoryService.POLLING_CONSTANT);
}
throw e;
}
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
Logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`);
throw new Error('Refreshing token failed');
throw e;
}
}
@ -743,6 +780,7 @@ export class AzureActiveDirectoryService {
} catch (e) {
// Network failures will automatically retry on next poll.
if (e.message !== REFRESH_NETWORK_FAILURE) {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
await this.removeSession(session.id);
}
return;