Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationAccessService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../../base/common/event.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';8import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';9import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';10import { IProductService } from '../../../../platform/product/common/productService.js';11import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';12import { AllowedExtension } from '../common/authentication.js';1314export const IAuthenticationAccessService = createDecorator<IAuthenticationAccessService>('IAuthenticationAccessService');15export interface IAuthenticationAccessService {16readonly _serviceBrand: undefined;1718readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }>;1920/**21* Check extension access to an account22* @param providerId The id of the authentication provider23* @param accountName The account name that access is checked for24* @param extensionId The id of the extension requesting access25* @returns Returns true or false if the user has opted to permanently grant or disallow access, and undefined26* if they haven't made a choice yet27*/28isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined;29readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[];30updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void;31removeAllowedExtensions(providerId: string, accountName: string): void;32}3334// TODO@TylerLeonhardt: Move this class to MainThreadAuthentication35// TODO@TylerLeonhardt: Should this class only keep track of allowed things and throw away disallowed ones?36export class AuthenticationAccessService extends Disposable implements IAuthenticationAccessService {37_serviceBrand: undefined;3839private _onDidChangeExtensionSessionAccess: Emitter<{ providerId: string; accountName: string }> = this._register(new Emitter<{ providerId: string; accountName: string }>());40readonly onDidChangeExtensionSessionAccess: Event<{ providerId: string; accountName: string }> = this._onDidChangeExtensionSessionAccess.event;4142constructor(43@IStorageService private readonly _storageService: IStorageService,44@IProductService private readonly _productService: IProductService45) {46super();47}4849isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean | undefined {50const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess;51const extensionKey = ExtensionIdentifier.toKey(extensionId);52if (Array.isArray(trustedExtensionAuthAccess)) {53if (trustedExtensionAuthAccess.includes(extensionKey)) {54return true;55}56} else if (trustedExtensionAuthAccess?.[providerId]?.includes(extensionKey)) {57return true;58}5960const allowList = this.readAllowedExtensions(providerId, accountName);61const extensionData = allowList.find(extension => extension.id === extensionKey);62if (!extensionData) {63return undefined;64}65// This property didn't exist on this data previously, inclusion in the list at all indicates allowance66return extensionData.allowed !== undefined67? extensionData.allowed68: true;69}7071readAllowedExtensions(providerId: string, accountName: string): AllowedExtension[] {72let trustedExtensions: AllowedExtension[] = [];73try {74const trustedExtensionSrc = this._storageService.get(`${providerId}-${accountName}`, StorageScope.APPLICATION);75if (trustedExtensionSrc) {76trustedExtensions = JSON.parse(trustedExtensionSrc);77}78} catch (err) { }7980// Add trusted extensions from product.json if they're not already in the list81const trustedExtensionAuthAccess = this._productService.trustedExtensionAuthAccess;82const trustedExtensionIds =83// Case 1: trustedExtensionAuthAccess is an array84Array.isArray(trustedExtensionAuthAccess)85? trustedExtensionAuthAccess86// Case 2: trustedExtensionAuthAccess is an object87: typeof trustedExtensionAuthAccess === 'object'88? trustedExtensionAuthAccess[providerId] ?? []89: [];9091for (const extensionId of trustedExtensionIds) {92const extensionKey = ExtensionIdentifier.toKey(extensionId);93const existingExtension = trustedExtensions.find(extension => extension.id === extensionKey);94if (!existingExtension) {95// Add new trusted extension (name will be set by caller if they have extension info)96trustedExtensions.push({97id: extensionKey,98name: extensionId, // Use original casing for display name99allowed: true,100trusted: true101});102} else {103// Update existing extension to be trusted104existingExtension.allowed = true;105existingExtension.trusted = true;106}107}108109return trustedExtensions;110}111112updateAllowedExtensions(providerId: string, accountName: string, extensions: AllowedExtension[]): void {113const allowList = this.readAllowedExtensions(providerId, accountName);114for (const extension of extensions) {115const extensionKey = ExtensionIdentifier.toKey(extension.id);116const index = allowList.findIndex(e => e.id === extensionKey);117if (index === -1) {118allowList.push({119...extension,120id: extensionKey121});122} else {123allowList[index].allowed = extension.allowed;124// Update name if provided and not already set to a proper name125if (extension.name && extension.name !== extensionKey && allowList[index].name !== extension.name) {126allowList[index].name = extension.name;127}128}129}130131// Filter out trusted extensions before storing - they should only come from product.json, not user storage132const userManagedExtensions = allowList.filter(extension => !extension.trusted);133this._storageService.store(`${providerId}-${accountName}`, JSON.stringify(userManagedExtensions), StorageScope.APPLICATION, StorageTarget.USER);134this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName });135}136137removeAllowedExtensions(providerId: string, accountName: string): void {138this._storageService.remove(`${providerId}-${accountName}`, StorageScope.APPLICATION);139this._onDidChangeExtensionSessionAccess.fire({ providerId, accountName });140}141}142143registerSingleton(IAuthenticationAccessService, AuthenticationAccessService, InstantiationType.Delayed);144145146