Migrate old accounts to MSAL (#234147)

Bascally, we reach into the old location in secret storage and if we find sessions (with a refresh token) we seed that in the MSAL world.

We do this one time... unless they switch back to the old world and then switch to the new world.

This has two different behaviors depending on if the Broker is used:
* If the broker is not used, this does what you might expect. It makes it seem totally transparent to the user that something has changed. All sessions get migrated over and the user is still logged in to what they were previously.
* If the broker is used... you don't get automatically logged in _unless_ you have already logged in to that account at the OS level. So this helps skip the "VS Code access layer" outlined in `accountAccess.ts`. Not as good as the previous bullet, but this is the best we can do in the broker world.

In time, we can remove this migration along with the old way of doing things.
This commit is contained in:
Tyler James Leonhardt
2024-11-18 15:56:53 -08:00
committed by GitHub
parent 98fafd6072
commit e5079d8a05
4 changed files with 87 additions and 13 deletions

View File

@@ -13,6 +13,8 @@ import { MicrosoftAccountType, MicrosoftAuthenticationTelemetryReporter } from '
import { loopbackTemplate } from './loopbackTemplate';
import { ScopeData } from '../common/scopeData';
import { EventBufferer } from '../common/event';
import { BetterTokenStorage } from '../betterSecretStorage';
import { IStoredSession } from '../AADHelper';
const redirectUri = 'https://vscode.dev/redirect';
const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad';
@@ -43,16 +45,16 @@ export class MsalAuthProvider implements AuthenticationProvider {
onDidChangeSessions = this._onDidChangeSessionsEmitter.event;
constructor(
context: ExtensionContext,
private readonly _context: ExtensionContext,
private readonly _telemetryReporter: MicrosoftAuthenticationTelemetryReporter,
private readonly _logger: LogOutputChannel,
private readonly _uriHandler: UriEventHandler,
private readonly _env: Environment = Environment.AzureCloud
) {
this._disposables = context.subscriptions;
this._disposables = _context.subscriptions;
this._publicClientManager = new CachedPublicClientApplicationManager(
context.globalState,
context.secrets,
_context.globalState,
_context.secrets,
this._logger,
this._env.name
);
@@ -85,9 +87,41 @@ export class MsalAuthProvider implements AuthenticationProvider {
);
}
/**
* Migrate sessions from the old secret storage to MSAL.
* TODO: MSAL Migration. Remove this when we remove the old flow.
*/
private async _migrateSessions() {
const betterSecretStorage = new BetterTokenStorage<IStoredSession>('microsoft.login.keylist', this._context);
const sessions = await betterSecretStorage.getAll(item => {
item.endpoint ||= Environment.AzureCloud.activeDirectoryEndpointUrl;
return item.endpoint === this._env.activeDirectoryEndpointUrl;
});
this._context.globalState.update('msalMigration', true);
const clientTenantMap = new Map<string, { clientId: string; tenant: string; refreshTokens: string[] }>();
for (const session of sessions) {
const scopeData = new ScopeData(session.scope.split(' '));
const key = `${scopeData.clientId}:${scopeData.tenant}`;
if (!clientTenantMap.has(key)) {
clientTenantMap.set(key, { clientId: scopeData.clientId, tenant: scopeData.tenant, refreshTokens: [] });
}
clientTenantMap.get(key)!.refreshTokens.push(session.refreshToken);
}
for (const { clientId, tenant, refreshTokens } of clientTenantMap.values()) {
await this.getOrCreatePublicClientApplication(clientId, tenant, refreshTokens);
}
}
async initialize(): Promise<void> {
await this._eventBufferer.bufferEventsAsync(() => this._publicClientManager.initialize());
if (!this._context.globalState.get('msalMigration', false)) {
await this._migrateSessions();
}
// Send telemetry for existing accounts
for (const cachedPca of this._publicClientManager.getAll()) {
for (const account of cachedPca.accounts) {
@@ -259,9 +293,9 @@ export class MsalAuthProvider implements AuthenticationProvider {
//#endregion
private async getOrCreatePublicClientApplication(clientId: string, tenant: string): Promise<ICachedPublicClientApplication> {
private async getOrCreatePublicClientApplication(clientId: string, tenant: string, refreshTokensToMigrate?: string[]): Promise<ICachedPublicClientApplication> {
const authority = new URL(tenant, this._env.activeDirectoryEndpointUrl).toString();
return await this._publicClientManager.getOrCreate(clientId, authority);
return await this._publicClientManager.getOrCreate(clientId, authority, refreshTokensToMigrate);
}
private async getAllSessionsForPca(