Misc fixes for Sovereign Clouds (#228591)

* Misc fixes for Sovereign Clouds

* For now, use the URL handler since the main flow doesn't work right now because the localhost redirect url needs to be in those environments
* Includes the name of the cloud in the PCAs so that we have separation between the auth providers
* extra logging for the URL Handler

* fix tests
This commit is contained in:
Tyler James Leonhardt
2024-09-13 12:57:12 -07:00
committed by GitHub
parent 3fd5eb3429
commit 6bd8e90fb7
4 changed files with 70 additions and 46 deletions

View File

@@ -50,7 +50,12 @@ export class MsalAuthProvider implements AuthenticationProvider {
private readonly _env: Environment = Environment.AzureCloud
) {
this._disposables = context.subscriptions;
this._publicClientManager = new CachedPublicClientApplicationManager(context.globalState, context.secrets, this._logger);
this._publicClientManager = new CachedPublicClientApplicationManager(
context.globalState,
context.secrets,
this._logger,
this._env.name
);
const accountChangeEvent = this._eventBufferer.wrapEvent(
this._publicClientManager.onDidAccountsChange,
(last, newEvent) => {
@@ -148,40 +153,52 @@ export class MsalAuthProvider implements AuthenticationProvider {
this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting');
const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
let result: AuthenticationResult;
try {
result = await cachedPca.acquireTokenInteractive({
openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
scopes: scopeData.scopesToSend,
// The logic for rendering one or the other of these templates is in the
// template itself, so we pass the same one for both.
successTemplate: loopbackTemplate,
errorTemplate: loopbackTemplate
});
} catch (e) {
if (e instanceof CancellationError) {
const yes = l10n.t('Yes');
const result = await window.showErrorMessage(
l10n.t('Having trouble logging in?'),
{
modal: true,
detail: l10n.t('Would you like to try a different way to sign in to your Microsoft account? ({0})', 'protocol handler')
},
yes
);
if (!result) {
let result: AuthenticationResult | undefined;
// Currently, the http://localhost redirect URI is only in the AzureCloud environment... even though I did make the change in the SovereignCloud environments...
// TODO: Remove this check when the change is in all environments.
let useLoopBack = this._env !== Environment.AzureCloud && scopeData.clientId === 'aebc6443-996d-45c2-90f0-388ff96faa56';
if (!useLoopBack) {
try {
result = await cachedPca.acquireTokenInteractive({
openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); },
scopes: scopeData.scopesToSend,
// The logic for rendering one or the other of these templates is in the
// template itself, so we pass the same one for both.
successTemplate: loopbackTemplate,
errorTemplate: loopbackTemplate
});
} catch (e) {
if (e instanceof CancellationError) {
const yes = l10n.t('Yes');
const result = await window.showErrorMessage(
l10n.t('Having trouble logging in?'),
{
modal: true,
detail: l10n.t('Would you like to try a different way to sign in to your Microsoft account? ({0})', 'protocol handler')
},
yes
);
if (!result) {
this._telemetryReporter.sendLoginFailedEvent();
throw e;
}
}
// This error comes from the backend and is likely not due to the user's machine
// failing to open a port or something local that would require us to try the
// URL handler loopback client.
if (e instanceof ServerError) {
this._telemetryReporter.sendLoginFailedEvent();
throw e;
}
// The user wants to try the loopback client or we got an error likely due to spinning up the server
useLoopBack = true;
}
// This error comes from the backend and is likely not due to the user's machine
// failing to open a port or something local that would require us to try the
// URL handler loopback client.
if (e instanceof ServerError) {
this._telemetryReporter.sendLoginFailedEvent();
throw e;
}
const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri);
}
if (useLoopBack) {
const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger);
try {
result = await cachedPca.acquireTokenInteractive({
openBrowser: (url: string) => loopbackClient.openBrowser(url),
@@ -194,6 +211,11 @@ export class MsalAuthProvider implements AuthenticationProvider {
}
}
if (!result) {
this._telemetryReporter.sendLoginFailedEvent();
throw new Error('No result returned from MSAL');
}
const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes);
this._telemetryReporter.sendLoginEvent(session.scopes);
this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'returned session');

View File

@@ -27,9 +27,10 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
constructor(
private readonly _globalMemento: Memento,
private readonly _secretStorage: SecretStorage,
private readonly _logger: LogOutputChannel
private readonly _logger: LogOutputChannel,
cloudName: string
) {
this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage);
this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage, cloudName);
this._disposable = Disposable.from(
this._pcasSecretStorage,
this._registerSecretStorageHandler(),
@@ -190,18 +191,18 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
}
class PublicClientApplicationsSecretStorage {
private static key = 'publicClientApplications';
private _disposable: Disposable;
private readonly _onDidChangeEmitter = new EventEmitter<void>;
readonly onDidChange: Event<void> = this._onDidChangeEmitter.event;
constructor(private readonly _secretStorage: SecretStorage) {
private readonly _key = `publicClientApplications-${this._cloudName}`;
constructor(private readonly _secretStorage: SecretStorage, private readonly _cloudName: string) {
this._disposable = Disposable.from(
this._onDidChangeEmitter,
this._secretStorage.onDidChange(e => {
if (e.key === PublicClientApplicationsSecretStorage.key) {
if (e.key === this._key) {
this._onDidChangeEmitter.fire();
}
})
@@ -209,7 +210,7 @@ class PublicClientApplicationsSecretStorage {
}
async get(): Promise<string[] | undefined> {
const value = await this._secretStorage.get(PublicClientApplicationsSecretStorage.key);
const value = await this._secretStorage.get(this._key);
if (!value) {
return undefined;
}
@@ -217,11 +218,11 @@ class PublicClientApplicationsSecretStorage {
}
store(value: string[]): Thenable<void> {
return this._secretStorage.store(PublicClientApplicationsSecretStorage.key, JSON.stringify(value));
return this._secretStorage.store(this._key, JSON.stringify(value));
}
delete(): Thenable<void> {
return this._secretStorage.delete(PublicClientApplicationsSecretStorage.key);
return this._secretStorage.delete(this._key);
}
dispose() {