Path: blob/main/src/vs/workbench/services/authentication/test/browser/authenticationAccessService.test.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 assert from 'assert';6import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';7import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';8import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';9import { IProductService } from '../../../../../platform/product/common/productService.js';10import { TestStorageService, TestProductService } from '../../../../test/common/workbenchTestServices.js';11import { AuthenticationAccessService, IAuthenticationAccessService } from '../../browser/authenticationAccessService.js';12import { AllowedExtension } from '../../common/authentication.js';1314suite('AuthenticationAccessService', () => {15const disposables = ensureNoDisposablesAreLeakedInTestSuite();1617let instantiationService: TestInstantiationService;18let storageService: TestStorageService;19let productService: IProductService & { trustedExtensionAuthAccess?: string[] | Record<string, string[]> };20let authenticationAccessService: IAuthenticationAccessService;2122setup(() => {23instantiationService = disposables.add(new TestInstantiationService());2425// Set up storage service26storageService = disposables.add(new TestStorageService());27instantiationService.stub(IStorageService, storageService);2829// Set up product service with no trusted extensions by default30productService = { ...TestProductService, trustedExtensionAuthAccess: undefined };31instantiationService.stub(IProductService, productService);3233// Create the service instance34authenticationAccessService = disposables.add(instantiationService.createInstance(AuthenticationAccessService));35});3637teardown(() => {38// Reset product service configuration to prevent test interference39if (productService) {40productService.trustedExtensionAuthAccess = undefined;41}42});4344suite('isAccessAllowed', () => {45test('returns undefined for unknown extension with no product configuration', () => {46const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'unknown-extension');47assert.strictEqual(result, undefined);48});4950test('returns true for trusted extension from product.json (array format)', () => {51productService.trustedExtensionAuthAccess = ['trusted-extension-1', 'trusted-extension-2'];5253const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'trusted-extension-1');54assert.strictEqual(result, true);55});5657test('returns true for trusted extension from product.json (object format)', () => {58productService.trustedExtensionAuthAccess = {59'github': ['github-extension'],60'microsoft': ['microsoft-extension']61};6263const result1 = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'github-extension');64assert.strictEqual(result1, true);6566const result2 = authenticationAccessService.isAccessAllowed('microsoft', '[email protected]', 'microsoft-extension');67assert.strictEqual(result2, true);68});6970test('returns undefined for extension not in trusted list', () => {71productService.trustedExtensionAuthAccess = ['trusted-extension'];7273const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'untrusted-extension');74assert.strictEqual(result, undefined);75});7677test('returns stored allowed state when extension is in storage', () => {78// Add extension to storage79authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [{80id: 'stored-extension',81name: 'Stored Extension',82allowed: false83}]);8485const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'stored-extension');86assert.strictEqual(result, false);87});8889test('returns true for extension in storage with allowed=true', () => {90authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [{91id: 'allowed-extension',92name: 'Allowed Extension',93allowed: true94}]);9596const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'allowed-extension');97assert.strictEqual(result, true);98});99100test('returns true for extension in storage with undefined allowed property (legacy behavior)', () => {101// Simulate legacy data where allowed property didn't exist102const legacyExtension: AllowedExtension = {103id: 'legacy-extension',104name: 'Legacy Extension'105// allowed property is undefined106};107108authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [legacyExtension]);109110const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'legacy-extension');111assert.strictEqual(result, true);112});113114test('product.json trusted extensions take precedence over storage', () => {115productService.trustedExtensionAuthAccess = ['product-trusted-extension'];116117// Try to store the same extension as not allowed118authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [{119id: 'product-trusted-extension',120name: 'Product Trusted Extension',121allowed: false122}]);123124// Product.json should take precedence125const result = authenticationAccessService.isAccessAllowed('github', '[email protected]', 'product-trusted-extension');126assert.strictEqual(result, true);127});128});129130suite('readAllowedExtensions', () => {131test('returns empty array when no data exists', () => {132const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');133assert.strictEqual(result.length, 0);134});135136test('returns stored extensions', () => {137const extensions: AllowedExtension[] = [138{ id: 'extension1', name: 'Extension 1', allowed: true },139{ id: 'extension2', name: 'Extension 2', allowed: false }140];141142authenticationAccessService.updateAllowedExtensions('github', '[email protected]', extensions);143144const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');145assert.strictEqual(result.length, 2);146assert.strictEqual(result[0].id, 'extension1');147assert.strictEqual(result[0].allowed, true);148assert.strictEqual(result[1].id, 'extension2');149assert.strictEqual(result[1].allowed, false);150});151152test('includes trusted extensions from product.json (array format)', () => {153productService.trustedExtensionAuthAccess = ['trusted-extension-1', 'trusted-extension-2'];154155const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');156assert.strictEqual(result.length, 2);157158const trustedExtension1 = result.find(e => e.id === 'trusted-extension-1');159assert.ok(trustedExtension1);160assert.strictEqual(trustedExtension1.allowed, true);161assert.strictEqual(trustedExtension1.trusted, true);162assert.strictEqual(trustedExtension1.name, 'trusted-extension-1'); // Should default to ID163164const trustedExtension2 = result.find(e => e.id === 'trusted-extension-2');165assert.ok(trustedExtension2);166assert.strictEqual(trustedExtension2.allowed, true);167assert.strictEqual(trustedExtension2.trusted, true);168});169170test('includes trusted extensions from product.json (object format)', () => {171productService.trustedExtensionAuthAccess = {172'github': ['github-extension'],173'microsoft': ['microsoft-extension']174};175176const githubResult = authenticationAccessService.readAllowedExtensions('github', '[email protected]');177assert.strictEqual(githubResult.length, 1);178assert.strictEqual(githubResult[0].id, 'github-extension');179assert.strictEqual(githubResult[0].trusted, true);180181const microsoftResult = authenticationAccessService.readAllowedExtensions('microsoft', '[email protected]');182assert.strictEqual(microsoftResult.length, 1);183assert.strictEqual(microsoftResult[0].id, 'microsoft-extension');184assert.strictEqual(microsoftResult[0].trusted, true);185186// Provider not in trusted list should return empty (no stored extensions)187const unknownResult = authenticationAccessService.readAllowedExtensions('unknown', '[email protected]');188assert.strictEqual(unknownResult.length, 0);189});190191test('merges stored extensions with trusted extensions from product.json', () => {192productService.trustedExtensionAuthAccess = ['trusted-extension'];193194// Add some stored extensions195authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [196{ id: 'stored-extension', name: 'Stored Extension', allowed: false }197]);198199const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');200assert.strictEqual(result.length, 2);201202const trustedExtension = result.find(e => e.id === 'trusted-extension');203assert.ok(trustedExtension);204assert.strictEqual(trustedExtension.trusted, true);205assert.strictEqual(trustedExtension.allowed, true);206207const storedExtension = result.find(e => e.id === 'stored-extension');208assert.ok(storedExtension);209assert.strictEqual(storedExtension.trusted, undefined);210assert.strictEqual(storedExtension.allowed, false);211});212213test('updates existing stored extension to trusted when found in product.json', () => {214// First add an extension to storage215authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [216{ id: 'extension1', name: 'Extension 1', allowed: false }217]);218219// Then add it to trusted list220productService.trustedExtensionAuthAccess = ['extension1'];221222const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');223assert.strictEqual(result.length, 1);224assert.strictEqual(result[0].id, 'extension1');225assert.strictEqual(result[0].trusted, true);226assert.strictEqual(result[0].allowed, true); // Should be marked as allowed due to being trusted227});228229test('handles malformed storage data gracefully', () => {230// Directly store malformed data in storage231storageService.store('[email protected]', 'invalid-json', StorageScope.APPLICATION, StorageTarget.USER);232233const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');234assert.strictEqual(result.length, 0); // Should return empty array instead of throwing235});236});237238suite('updateAllowedExtensions', () => {239test('adds new extensions to storage', () => {240const extensions: AllowedExtension[] = [241{ id: 'extension1', name: 'Extension 1', allowed: true },242{ id: 'extension2', name: 'Extension 2', allowed: false }243];244245authenticationAccessService.updateAllowedExtensions('github', '[email protected]', extensions);246247const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');248assert.strictEqual(result.length, 2);249assert.strictEqual(result[0].id, 'extension1');250assert.strictEqual(result[1].id, 'extension2');251});252253test('updates existing extension allowed status', () => {254// First add an extension255authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [256{ id: 'extension1', name: 'Extension 1', allowed: true }257]);258259// Then update its allowed status260authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [261{ id: 'extension1', name: 'Extension 1', allowed: false }262]);263264const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');265assert.strictEqual(result.length, 1);266assert.strictEqual(result[0].allowed, false);267});268269test('updates existing extension name when new name is provided', () => {270// First add an extension with default name271authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [272{ id: 'extension1', name: 'extension1', allowed: true }273]);274275// Then update with a proper name276authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [277{ id: 'extension1', name: 'My Extension', allowed: true }278]);279280const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');281assert.strictEqual(result.length, 1);282assert.strictEqual(result[0].name, 'My Extension');283});284285test('does not update name when new name is same as ID', () => {286// First add an extension with a proper name287authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [288{ id: 'extension1', name: 'My Extension', allowed: true }289]);290291// Then try to update with ID as name (should keep existing name)292authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [293{ id: 'extension1', name: 'extension1', allowed: false }294]);295296const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');297assert.strictEqual(result.length, 1);298assert.strictEqual(result[0].name, 'My Extension'); // Should keep the original name299assert.strictEqual(result[0].allowed, false); // But update the allowed status300});301302test('does not store trusted extensions - they should only come from product.json', () => {303productService.trustedExtensionAuthAccess = ['trusted-extension'];304305// Try to store a trusted extension along with regular extensions306authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [307{ id: 'regular-extension', name: 'Regular Extension', allowed: true },308{ id: 'trusted-extension', name: 'Trusted Extension', allowed: false }309]);310311// Check what's actually stored in storage (should only be the regular extension)312const storedData = storageService.get('[email protected]', StorageScope.APPLICATION);313assert.ok(storedData);314const parsedData = JSON.parse(storedData);315assert.strictEqual(parsedData.length, 1);316assert.strictEqual(parsedData[0].id, 'regular-extension');317318// But when we read, we should get both (trusted from product.json + stored)319const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');320assert.strictEqual(result.length, 2);321322const trustedExt = result.find(e => e.id === 'trusted-extension');323assert.ok(trustedExt);324assert.strictEqual(trustedExt.trusted, true);325assert.strictEqual(trustedExt.allowed, true); // Should be true from product.json, not false from storage326327const regularExt = result.find(e => e.id === 'regular-extension');328assert.ok(regularExt);329assert.strictEqual(regularExt.trusted, undefined);330assert.strictEqual(regularExt.allowed, true);331});332333test('filters out trusted extensions before storing', () => {334productService.trustedExtensionAuthAccess = ['trusted-ext-1', 'trusted-ext-2'];335336// Add both trusted and regular extensions337const extensions: AllowedExtension[] = [338{ id: 'regular-ext', name: 'Regular Extension', allowed: true },339{ id: 'trusted-ext-1', name: 'Trusted Extension 1', allowed: false },340{ id: 'another-regular-ext', name: 'Another Regular Extension', allowed: false },341{ id: 'trusted-ext-2', name: 'Trusted Extension 2', allowed: true }342];343344authenticationAccessService.updateAllowedExtensions('github', '[email protected]', extensions);345346// Check storage - should only contain regular extensions347const storedData = storageService.get('[email protected]', StorageScope.APPLICATION);348assert.ok(storedData);349const parsedData = JSON.parse(storedData);350assert.strictEqual(parsedData.length, 2);351assert.ok(parsedData.find((e: AllowedExtension) => e.id === 'regular-ext'));352assert.ok(parsedData.find((e: AllowedExtension) => e.id === 'another-regular-ext'));353assert.ok(!parsedData.find((e: AllowedExtension) => e.id === 'trusted-ext-1'));354assert.ok(!parsedData.find((e: AllowedExtension) => e.id === 'trusted-ext-2'));355});356357test('fires onDidChangeExtensionSessionAccess event', () => {358let eventFired = false;359let eventData: { providerId: string; accountName: string } | undefined;360361const subscription = authenticationAccessService.onDidChangeExtensionSessionAccess(e => {362eventFired = true;363eventData = e;364});365disposables.add(subscription);366367authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [368{ id: 'extension1', name: 'Extension 1', allowed: true }369]);370371assert.strictEqual(eventFired, true);372assert.ok(eventData);373assert.strictEqual(eventData.providerId, 'github');374assert.strictEqual(eventData.accountName, '[email protected]');375});376});377378suite('removeAllowedExtensions', () => {379test('removes all extensions from storage', () => {380// First add some extensions381authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [382{ id: 'extension1', name: 'Extension 1', allowed: true },383{ id: 'extension2', name: 'Extension 2', allowed: false }384]);385386// Verify they exist387const result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');388assert.ok(result.length > 0);389390// Remove them391authenticationAccessService.removeAllowedExtensions('github', '[email protected]');392393// Verify storage is empty (but trusted extensions from product.json might still be there)394const storedData = storageService.get('[email protected]', StorageScope.APPLICATION);395assert.strictEqual(storedData, undefined);396});397398test('fires onDidChangeExtensionSessionAccess event', () => {399let eventFired = false;400let eventData: { providerId: string; accountName: string } | undefined;401402// First add an extension403authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [404{ id: 'extension1', name: 'Extension 1', allowed: true }405]);406407// Then listen for the remove event408const subscription = authenticationAccessService.onDidChangeExtensionSessionAccess(e => {409eventFired = true;410eventData = e;411});412disposables.add(subscription);413414authenticationAccessService.removeAllowedExtensions('github', '[email protected]');415416assert.strictEqual(eventFired, true);417assert.ok(eventData);418assert.strictEqual(eventData.providerId, 'github');419assert.strictEqual(eventData.accountName, '[email protected]');420});421422test('does not affect trusted extensions from product.json', () => {423productService.trustedExtensionAuthAccess = ['trusted-extension'];424425// Add some regular extensions and verify both trusted and regular exist426authenticationAccessService.updateAllowedExtensions('github', '[email protected]', [427{ id: 'regular-extension', name: 'Regular Extension', allowed: true }428]);429430let result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');431assert.strictEqual(result.length, 2); // 1 trusted + 1 regular432433// Remove stored extensions434authenticationAccessService.removeAllowedExtensions('github', '[email protected]');435436// Trusted extension should still be there437result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');438assert.strictEqual(result.length, 1);439assert.strictEqual(result[0].id, 'trusted-extension');440assert.strictEqual(result[0].trusted, true);441});442});443444suite('integration with product.json configurations', () => {445test('handles switching between array and object format', () => {446// Start with array format447productService.trustedExtensionAuthAccess = ['ext1', 'ext2'];448let result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');449assert.strictEqual(result.length, 2);450451// Switch to object format452productService.trustedExtensionAuthAccess = {453'github': ['ext1', 'ext3'],454'microsoft': ['ext4']455};456result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');457assert.strictEqual(result.length, 2); // ext1 and ext3 for github458assert.ok(result.find(e => e.id === 'ext1'));459assert.ok(result.find(e => e.id === 'ext3'));460assert.ok(!result.find(e => e.id === 'ext2')); // Should not be there anymore461});462463test('handles empty trusted extension configurations', () => {464// Test undefined465productService.trustedExtensionAuthAccess = undefined;466let result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');467assert.strictEqual(result.length, 0);468469// Test empty array470productService.trustedExtensionAuthAccess = [];471result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');472assert.strictEqual(result.length, 0);473474// Test empty object475productService.trustedExtensionAuthAccess = {};476result = authenticationAccessService.readAllowedExtensions('github', '[email protected]');477assert.strictEqual(result.length, 0);478});479});480});481482483