mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 00:28:52 +01:00
Adopt the MSAL broker to talk to the OS for Microsoft auth (#233739)
This adopts the `NativeBrokerPlugin` provided by `@azure/msal-node-extensions` to provide the ability to use auth state from the OS, and show native auth dialogs instead of going to the browser.
This has several pieces:
* The adoption of the broker in the microsoft-authentication extension:
* Adding `NativeBrokerPlugin` to our PCAs
* Using the proposed handle API to pass the native window handle down to MSAL calls (btw, this API will change in a follow up PR)
* Adopting an AccountAccess layer to handle:
* giving the user control of which accounts VS Code uses
* an eventing layer so that auth state can be updated across multiple windows
* Getting the extension to build properly and only build what it really needs. This required several package.json/webpack hacks:
* Use a fake keytar since we don't use the feature in `@azure/msal-node-extensions` that uses keytar
* Use a fake dpapi layer since we don't use the feature in `@azure/msal-node-extensions` that uses it
* Ensure the msal runtime `.node` and `.dll` files are included in the bundle
* Get the VS Code build to allow a native node module in an extension: by having a list of native extensions that will be built in the "ci" part of the build - in other words when VS Code is building on the target platform
There are a couple of followups:
* Refactor the `handle` API to handle (heh) Auxiliary Windows https://github.com/microsoft/vscode/issues/233106
* Separate the call to `acquireTokenSilent` and `acquireTokenInteractive` and all the usage of this native node module into a separate process or maybe in Core... we'll see. Something to experiment with after we have something working. NEEDS FOLLOW UP ISSUE
Fixes https://github.com/microsoft/vscode/issues/229431
This commit is contained in:
committed by
GitHub
parent
681164aaaa
commit
305134296c
106
extensions/microsoft-authentication/src/common/accountAccess.ts
Normal file
106
extensions/microsoft-authentication/src/common/accountAccess.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, Event, EventEmitter, SecretStorage } from 'vscode';
|
||||
import { AccountInfo } from '@azure/msal-node';
|
||||
|
||||
interface IAccountAccess {
|
||||
onDidAccountAccessChange: Event<void>;
|
||||
isAllowedAccess(account: AccountInfo): boolean;
|
||||
setAllowedAccess(account: AccountInfo, allowed: boolean): void;
|
||||
}
|
||||
|
||||
export class ScopedAccountAccess implements IAccountAccess {
|
||||
private readonly _onDidAccountAccessChangeEmitter = new EventEmitter<void>();
|
||||
readonly onDidAccountAccessChange = this._onDidAccountAccessChangeEmitter.event;
|
||||
|
||||
private readonly _accountAccessSecretStorage: AccountAccessSecretStorage;
|
||||
|
||||
private value = new Array<string>();
|
||||
|
||||
constructor(
|
||||
private readonly _secretStorage: SecretStorage,
|
||||
private readonly _cloudName: string,
|
||||
private readonly _clientId: string,
|
||||
private readonly _authority: string
|
||||
) {
|
||||
this._accountAccessSecretStorage = new AccountAccessSecretStorage(this._secretStorage, this._cloudName, this._clientId, this._authority);
|
||||
this._accountAccessSecretStorage.onDidChange(() => this.update());
|
||||
}
|
||||
|
||||
initialize() {
|
||||
return this.update();
|
||||
}
|
||||
|
||||
isAllowedAccess(account: AccountInfo): boolean {
|
||||
return this.value.includes(account.homeAccountId);
|
||||
}
|
||||
|
||||
async setAllowedAccess(account: AccountInfo, allowed: boolean): Promise<void> {
|
||||
if (allowed) {
|
||||
if (this.value.includes(account.homeAccountId)) {
|
||||
return;
|
||||
}
|
||||
await this._accountAccessSecretStorage.store([...this.value, account.homeAccountId]);
|
||||
return;
|
||||
}
|
||||
await this._accountAccessSecretStorage.store(this.value.filter(id => id !== account.homeAccountId));
|
||||
}
|
||||
|
||||
private async update() {
|
||||
const current = new Set(this.value);
|
||||
const value = await this._accountAccessSecretStorage.get();
|
||||
|
||||
this.value = value ?? [];
|
||||
if (current.size !== this.value.length || !this.value.every(id => current.has(id))) {
|
||||
this._onDidAccountAccessChangeEmitter.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountAccessSecretStorage {
|
||||
private _disposable: Disposable;
|
||||
|
||||
private readonly _onDidChangeEmitter = new EventEmitter<void>;
|
||||
readonly onDidChange: Event<void> = this._onDidChangeEmitter.event;
|
||||
|
||||
private readonly _key = `accounts-${this._cloudName}-${this._clientId}-${this._authority}`;
|
||||
|
||||
constructor(
|
||||
private readonly _secretStorage: SecretStorage,
|
||||
private readonly _cloudName: string,
|
||||
private readonly _clientId: string,
|
||||
private readonly _authority: string
|
||||
) {
|
||||
this._disposable = Disposable.from(
|
||||
this._onDidChangeEmitter,
|
||||
this._secretStorage.onDidChange(e => {
|
||||
if (e.key === this._key) {
|
||||
this._onDidChangeEmitter.fire();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async get(): Promise<string[] | undefined> {
|
||||
const value = await this._secretStorage.get(this._key);
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
store(value: string[]): Thenable<void> {
|
||||
return this._secretStorage.store(this._key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
delete(): Thenable<void> {
|
||||
return this._secretStorage.delete(this._key);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user