Skip to content

Commit

Permalink
Add 'manage trusted extensions' option to account quickpick, #90385
Browse files Browse the repository at this point in the history
  • Loading branch information
Rachel Macfarlane committed Mar 27, 2020
1 parent ce90579 commit ab91532
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 52 deletions.
4 changes: 3 additions & 1 deletion extensions/github-authentication/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export async function activate(context: vscode.ExtensionContext) {
try {
const session = await loginService.login(scopeList.join(' '));
Logger.info('Login success!');
onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] });
return session;
} catch (e) {
vscode.window.showErrorMessage(`Sign in failed: ${e}`);
Expand All @@ -32,7 +33,8 @@ export async function activate(context: vscode.ExtensionContext) {
}
},
logout: async (id: string) => {
return loginService.logout(id);
await loginService.logout(id);
onDidChangeSessions.fire({ added: [], removed: [id], changed: [] });
}
});

Expand Down
104 changes: 57 additions & 47 deletions src/vs/workbench/api/browser/mainThreadAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [
}
];

interface AllowedExtension {
id: string;
name: string;
}

function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] {
let trustedExtensions: AllowedExtension[] = [];
try {
const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.GLOBAL);
if (trustedExtensionSrc) {
trustedExtensions = JSON.parse(trustedExtensionSrc);
}
} catch (err) { }

return trustedExtensions;
}

export class MainThreadAuthenticationProvider extends Disposable {
private _sessionMenuItems = new Map<string, IDisposable[]>();
private _accounts = new Map<string, string[]>(); // Map account name to session ids
Expand All @@ -48,30 +65,25 @@ export class MainThreadAuthenticationProvider extends Disposable {
this.registerCommandsAndContextMenuItems();
}

private setPermissionsForAccount(quickInputService: IQuickInputService, doLogin?: boolean) {
const quickPick = quickInputService.createQuickPick();
private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) {
const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>();
quickPick.canSelectMany = true;
const items = this.dependents.map(dependent => {
const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName);
const items = allowedExtensions.map(extension => {
return {
label: dependent.label,
description: dependent.scopeDescriptions,
picked: true,
scopes: dependent.scopes
label: extension.name,
extension
};
});

quickPick.items = items;
// TODO read from storage and filter is not doLogin
quickPick.selectedItems = items;
quickPick.title = nls.localize('signInTo', "Sign in to {0}", this.displayName);
quickPick.placeholder = nls.localize('accountPermissions', "Choose what features and extensions to authorize to use this account");
quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account");

quickPick.onDidAccept(() => {
const scopes = quickPick.selectedItems.reduce((previous, current) => previous.concat((current as any).scopes), []);
if (scopes.length && doLogin) {
this.login(scopes);
}

const updatedAllowedList = quickPick.selectedItems.map(item => item.extension);
storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL);
quickPick.dispose();
});

Expand All @@ -87,7 +99,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
this._register(CommandsRegistry.registerCommand({
id: `signIn${this.id}`,
handler: (accessor, args) => {
this.setPermissionsForAccount(accessor.get(IQuickInputService), true);
this.login(this.dependents.reduce((previous: string[], current) => previous.concat(current.scopes), []));
},
}));

Expand Down Expand Up @@ -130,19 +142,26 @@ export class MainThreadAuthenticationProvider extends Disposable {
id: `configureSessions${session.id}`,
handler: (accessor, args) => {
const quickInputService = accessor.get(IQuickInputService);
const storageService = accessor.get(IStorageService);

const quickPick = quickInputService.createQuickPick();
const items = [{ label: 'Sign Out' }];
const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions");
const signOut = nls.localize('signOut', "Sign Out");
const items = ([{ label: manage }, { label: signOut }]);

quickPick.items = items;

quickPick.onDidAccept(e => {
const selected = quickPick.selectedItems[0];
if (selected.label === 'Sign Out') {
if (selected.label === signOut) {
const sessionsForAccount = this._accounts.get(session.accountName);
sessionsForAccount?.forEach(sessionId => this.logout(sessionId));
}

if (selected.label === manage) {
this.manageTrustedExtensions(quickInputService, storageService, session.accountName);
}

quickPick.dispose();
});

Expand Down Expand Up @@ -185,6 +204,7 @@ export class MainThreadAuthenticationProvider extends Disposable {
disposeables.forEach(disposeable => disposeable.dispose());
this._sessionMenuItems.delete(accountName);
}
this._accounts.delete(accountName);
}
}
});
Expand Down Expand Up @@ -242,55 +262,45 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this.authenticationService.sessionsUpdate(id, event);
}

async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL);
if (alwaysAllow) {
return alwaysAllow === 'true';
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
let allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (allowList.some(extension => extension.id === extensionId)) {
return true;
}

const { choice, checkboxChecked } = await this.dialogService.show(
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName),
nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information for the {1} account '{2}'.", extensionName, providerName, accountName),
[nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")],
{
cancelId: 0,
checkbox: {
label: nls.localize('neverAgain', "Don't Show Again")
}
cancelId: 0
}
);

const allow = choice === 1;
if (checkboxChecked) {
this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL);
if (allow) {
allowList = allowList.concat({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}

return allow;
}

async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL);
if (alwaysAllow) {
return alwaysAllow === 'true';
}

const { choice, checkboxChecked } = await this.dialogService.show(
async $loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName),
[nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue")],
[nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")],
{
cancelId: 0,
checkbox: {
label: nls.localize('neverAgain', "Don't Show Again")
}
cancelId: 0
}
);

const allow = choice === 1;
if (checkboxChecked) {
this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL);
}
return choice === 1;
}

return allow;
async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName).concat({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}
}
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$registerAuthenticationProvider(id: string, displayName: string): void;
$unregisterAuthenticationProvider(id: string): void;
$onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<void>;
}

export interface MainThreadConfigurationShape extends IDisposable {
Expand Down
8 changes: 6 additions & 2 deletions src/vs/workbench/api/common/extHostAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
getAccessToken: async () => {
const isAllowed = await this._proxy.$getSessionsPrompt(
provider.id,
session.accountName,
provider.displayName,
ExtensionIdentifier.toKey(requestingExtension.identifier),
requestingExtension.displayName || requestingExtension.name);
Expand All @@ -70,12 +71,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
throw new Error(`No authentication provider with id '${providerId}' is currently registered.`);
}

const isAllowed = await this._proxy.$loginPrompt(provider.id, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name);
const extensionName = requestingExtension.displayName || requestingExtension.name;
const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}

return provider.login(scopes);
const newSession = await provider.login(scopes);
await this._proxy.$setTrustedExtension(provider.id, newSession.accountName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName);
return newSession;
}

registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
Expand Down
10 changes: 10 additions & 0 deletions src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return;
case 2: return;
}
} else if (skipAccountPick) {
const result = await this.dialogService.confirm({
type: 'info',
message: localize('turn on sync confirmation', "Do you want to turn on preferences sync?"),
primaryButton: localize('turn on', "Turn On")
});
if (!result.confirmed) {
return;
}
}

return new Promise((c, e) => {
const disposables: DisposableStore = new DisposableStore();
const quickPick = this.quickInputService.createQuickPick<ConfigureSyncQuickPickItem>();
Expand Down

0 comments on commit ab91532

Please sign in to comment.