diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 3ffee262651..4dd3b8f4001 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -53,7 +53,7 @@ export class MainThreadAuthenticationProvider extends Disposable { }); quickPick.items = items; - quickPick.selectedItems = items; + quickPick.selectedItems = items.filter(item => item.extension.allowed === undefined || item.extension.allowed); quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account"); @@ -172,12 +172,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } - + this.authenticationService.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE); } @@ -225,9 +220,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu if (!didAcceptPrompt) { throw new Error('User did not consent to login.'); } - } else { + } else if (allowed !== false) { this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, [session]); return undefined; + } else { + return undefined; } } } else { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index 6c1efc704be..dfd68dd6ff9 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -108,7 +108,8 @@ export interface IAuthenticationService { getProviderIds(): string[]; registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void; unregisterAuthenticationProvider(id: string): void; - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean; + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined; + updatedAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): Promise; showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise; selectSession(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): Promise; requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): void; @@ -137,6 +138,7 @@ export interface IAuthenticationService { export interface AllowedExtension { id: string; name: string; + allowed?: boolean; } export function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { @@ -396,11 +398,22 @@ export class AuthenticationService extends Disposable implements IAuthentication } } - isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean { + /** + * Check extension access to an account + * @param providerId The id of the authentication provider + * @param accountName The account name that access is checked for + * @param extensionId The id of the extension requesting access + * @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined + * if they haven't made a choice yet + */ + isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined { const allowList = readAllowedExtensions(this.storageService, providerId, accountName); const extensionData = allowList.find(extension => extension.id === extensionId); if (extensionData) { - return true; + // This property didn't exist on this data previously, inclusion in the list at all indicates allowance + return extensionData.allowed !== undefined + ? extensionData.allowed + : true; } const remoteConnection = this.remoteAgentService.getConnection(); @@ -412,7 +425,19 @@ export class AuthenticationService extends Disposable implements IAuthentication return true; } - return false; + return undefined; + } + + async updatedAllowedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string, isAllowed: boolean): Promise { + const allowList = readAllowedExtensions(this.storageService, providerId, accountName); + const index = allowList.findIndex(extension => extension.id === extensionId); + if (index === -1) { + allowList.push({ id: extensionId, name: extensionName, allowed: isAllowed }); + } else { + allowList[index].allowed = isAllowed; + } + + await this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); } async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { @@ -420,21 +445,20 @@ export class AuthenticationService extends Disposable implements IAuthentication const { choice } = await this.dialogService.show( Severity.Info, nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName), - [nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")], + [nls.localize('allow', "Allow"), nls.localize('deny', "Deny"), nls.localize('cancel', "Cancel")], { - cancelId: 1 + cancelId: 2 } ); - const allow = choice === 0; - if (allow) { - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); + const cancelled = choice === 2; + const allowed = choice === 0; + if (!cancelled) { + this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, allowed); } this.removeAccessRequest(providerId, extensionId); - return allow; + return allowed; } async selectSession(providerId: string, extensionId: string, extensionName: string, availableSessions: AuthenticationSession[]): Promise { @@ -475,11 +499,7 @@ export class AuthenticationService extends Disposable implements IAuthentication const session = quickPick.selectedItems[0].session ?? await this.createSession(providerId, availableSessions[0].scopes as string[]); const accountName = session.account.label; - const allowList = readAllowedExtensions(this.storageService, providerId, accountName); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } + this.updatedAllowedExtension(providerId, accountName, extensionId, extensionName, true); this.removeAccessRequest(providerId, extensionId); this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE); @@ -612,11 +632,7 @@ export class AuthenticationService extends Disposable implements IAuthentication const session = await authenticationService.createSession(providerId, scopes); // Add extension to allow list since user explicitly signed in on behalf of it - const allowList = readAllowedExtensions(storageService, providerId, session.account.label); - if (!allowList.find(allowed => allowed.id === extensionId)) { - allowList.push({ id: extensionId, name: extensionName }); - storageService.store(`${providerId}-${session.account.label}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER); - } + this.updatedAllowedExtension(providerId, session.account.label, extensionId, extensionName, true); // And also set it as the preferred account for the extension storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);