Path: blob/main/src/vs/workbench/services/authentication/browser/authenticationUsageService.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 { Queue } from '../../../../base/common/async.js';6import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';7import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';8import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';9import { ILogService } from '../../../../platform/log/common/log.js';10import { IProductService } from '../../../../platform/product/common/productService.js';11import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';12import { IAuthenticationService } from '../common/authentication.js';1314export interface IAccountUsage {15extensionId: string;16extensionName: string;17lastUsed: number;18scopes?: string[];19}2021export const IAuthenticationUsageService = createDecorator<IAuthenticationUsageService>('IAuthenticationUsageService');22export interface IAuthenticationUsageService {23readonly _serviceBrand: undefined;24/**25* Initializes the cache of extensions that use authentication. Ideally used in a contribution that can be run eventually after the workspace is loaded.26*/27initializeExtensionUsageCache(): Promise<void>;28/**29* Checks if an extension uses authentication30* @param extensionId The id of the extension to check31*/32extensionUsesAuth(extensionId: string): Promise<boolean>;33/**34* Reads the usages for an account35* @param providerId The id of the authentication provider to get usages for36* @param accountName The name of the account to get usages for37*/38readAccountUsages(providerId: string, accountName: string,): IAccountUsage[];39/**40*41* @param providerId The id of the authentication provider to get usages for42* @param accountName The name of the account to get usages for43*/44removeAccountUsage(providerId: string, accountName: string): void;45/**46* Adds a usage for an account47* @param providerId The id of the authentication provider to get usages for48* @param accountName The name of the account to get usages for49* @param extensionId The id of the extension to add a usage for50* @param extensionName The name of the extension to add a usage for51*/52addAccountUsage(providerId: string, accountName: string, scopes: ReadonlyArray<string> | undefined, extensionId: string, extensionName: string): void;53}5455export class AuthenticationUsageService extends Disposable implements IAuthenticationUsageService {56_serviceBrand: undefined;5758private _queue = this._register(new Queue());59private _extensionsUsingAuth = new Set<string>();6061private _disposed = false;6263constructor(64@IStorageService private readonly _storageService: IStorageService,65@IAuthenticationService private readonly _authenticationService: IAuthenticationService,66@ILogService private readonly _logService: ILogService,67@IProductService productService: IProductService,68) {69super();70this._register(toDisposable(() => this._disposed = true));71// If an extension is listed in `trustedExtensionAuthAccess` we should consider it as using auth72const trustedExtensionAuthAccess = productService.trustedExtensionAuthAccess;73if (Array.isArray(trustedExtensionAuthAccess)) {74for (const extensionId of trustedExtensionAuthAccess) {75this._extensionsUsingAuth.add(extensionId);76}77} else if (trustedExtensionAuthAccess) {78for (const extensions of Object.values(trustedExtensionAuthAccess)) {79for (const extensionId of extensions) {80this._extensionsUsingAuth.add(extensionId);81}82}83}8485this._register(this._authenticationService.onDidRegisterAuthenticationProvider(86provider => this._queue.queue(87() => this._addExtensionsToCache(provider.id)88)89));90}9192async initializeExtensionUsageCache(): Promise<void> {93await this._queue.queue(() => Promise.all(this._authenticationService.getProviderIds().map(providerId => this._addExtensionsToCache(providerId))));94}9596async extensionUsesAuth(extensionId: string): Promise<boolean> {97await this._queue.whenIdle();98return this._extensionsUsingAuth.has(extensionId);99}100101readAccountUsages(providerId: string, accountName: string): IAccountUsage[] {102const accountKey = `${providerId}-${accountName}-usages`;103const storedUsages = this._storageService.get(accountKey, StorageScope.APPLICATION);104let usages: IAccountUsage[] = [];105if (storedUsages) {106try {107usages = JSON.parse(storedUsages);108} catch (e) {109// ignore110}111}112113return usages;114}115116removeAccountUsage(providerId: string, accountName: string): void {117const accountKey = `${providerId}-${accountName}-usages`;118this._storageService.remove(accountKey, StorageScope.APPLICATION);119}120121addAccountUsage(providerId: string, accountName: string, scopes: string[] | undefined, extensionId: string, extensionName: string): void {122const accountKey = `${providerId}-${accountName}-usages`;123const usages = this.readAccountUsages(providerId, accountName);124125const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);126if (existingUsageIndex > -1) {127usages.splice(existingUsageIndex, 1, {128extensionId,129extensionName,130scopes,131lastUsed: Date.now()132});133} else {134usages.push({135extensionId,136extensionName,137scopes,138lastUsed: Date.now()139});140}141142this._storageService.store(accountKey, JSON.stringify(usages), StorageScope.APPLICATION, StorageTarget.MACHINE);143this._extensionsUsingAuth.add(extensionId);144}145146private async _addExtensionsToCache(providerId: string) {147if (this._disposed) {148return;149}150try {151const accounts = await this._authenticationService.getAccounts(providerId);152for (const account of accounts) {153const usage = this.readAccountUsages(providerId, account.label);154for (const u of usage) {155this._extensionsUsingAuth.add(u.extensionId);156}157}158} catch (e) {159this._logService.error(e);160}161}162}163164registerSingleton(IAuthenticationUsageService, AuthenticationUsageService, InstantiationType.Delayed);165166167