Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d97c8a8
Add extensions.gallery.authProvider policy, marketplace scope, and co…
mcumming Jul 7, 2026
6f54fb9
Add Entra ID eligibility check to the gallery manifest service
mcumming Jul 7, 2026
ab9e525
Add provider-aware marketplace sign-in and access-denied UX
mcumming Jul 7, 2026
3481985
Add microsoft to trustedExtensionAuthAccess
mcumming Jul 7, 2026
94a70a7
Add unit tests for marketplace provider routing and eligibility
mcumming Jul 7, 2026
bd44656
Harden Entra marketplace access: cache scoping, race guards, error ha…
mcumming Jul 10, 2026
eec93cb
Address Copilot PR review: policy export, cross-account leak, layerin…
mcumming Jul 10, 2026
6434607
Avoid `any` casts in extensionGalleryManifestService test
mcumming Jul 14, 2026
ca0d56f
Refactor Private Marketplace access validation into a provider strategy
mcumming Jul 29, 2026
5510595
Surface AccessDenied instead of re-prompting sign-in on 401 for signe…
mcumming Jul 29, 2026
2f7a8f9
Remove policy data from contributor PR
joshspicer Aug 4, 2026
6ec632f
Dissolve access validator into account + service-index services
mcumming Aug 7, 2026
3aa3035
marketplace: thread CancellationToken guards, materialize index in ca…
mcumming Aug 10, 2026
988ffeb
marketplace: make getEffectiveAuthProvider dependency-free, cache res…
mcumming Aug 10, 2026
c0306bc
marketplace: collapse duplicate access-denied welcome content into on…
mcumming Aug 10, 2026
5ec451b
marketplace: fix telemetry provider scoping
mcumming Aug 10, 2026
0b48532
test: add gallery access unit and telemetry coverage
mcumming Aug 10, 2026
b5aaf89
Remove CONTEXT_MARKETPLACE_AUTH_PROVIDER re-export
mcumming Aug 12, 2026
3789654
Use Event.signal for onDidChangeAccount instead of an emitter relay
mcumming Aug 12, 2026
0630e80
Default extensions gallery auth provider to a valid enum member
mcumming Aug 12, 2026
322b2e3
Revert 'Add microsoft to trustedExtensionAuthAccess'
mcumming Aug 12, 2026
67955a3
Register account resolver as a Delayed singleton (Tyler #3, #4)
mcumming Aug 12, 2026
84ff6e0
Ground Private Marketplace account selection in a persisted slot
mcumming Aug 12, 2026
fc4e4a0
Show account quick pick on Microsoft marketplace sign-in
mcumming Aug 13, 2026
117928b
Clarify getAccount vs resolveCurrentAccount intent
mcumming Aug 13, 2026
d2626c5
Move Microsoft (Entra) marketplace eligibility check client-side
mcumming Aug 13, 2026
af73002
marketplace: break account->auth DI cycle via orchestrator wiring
mcumming Aug 13, 2026
c67ffce
marketplace: rename ExtensionGalleryServiceIndexService to ...Fetcher
mcumming Aug 13, 2026
cec950d
marketplace: drop obsolete microtask deferral in gallery manifest ser…
mcumming Aug 14, 2026
bbfd837
marketplace: move access resolution into the gallery account service
mcumming Aug 17, 2026
7c18968
marketplace: include the service index error body in the failure
mcumming Aug 17, 2026
72ec505
marketplace: pin that a signed-out user is never told to check the ne…
mcumming Aug 17, 2026
8da36d1
marketplace: report a rejected client as denied, not unreachable
mcumming Aug 17, 2026
082b6c7
marketplace: give the account service one job, and the manifest servi…
mcumming Aug 17, 2026
61ac56e
marketplace: cut comment volume across the PR
mcumming Aug 17, 2026
7250434
Fold the service index fetch back in and drop the access cache
mcumming Aug 19, 2026
fa1f726
Publish the catalog on every successful resolve
mcumming Aug 19, 2026
c41e451
refactor gallery manifest service to have minimal changes
sandy081 Aug 20, 2026
e61ce45
Adopt the reviewer's gallery manifest service
mcumming Aug 20, 2026
bdf2d21
Remove the Entra auth product flag
mcumming Aug 20, 2026
a65242b
Read marketplace auth scopes from product.json
mcumming Aug 21, 2026
3973ede
Route marketplace sign-in through the account service
mcumming Aug 21, 2026
cdf706a
Prompt for restart when the marketplace auth provider changes
mcumming Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/vs/base/common/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,9 @@ export interface IAuthorizationJWTClaims {
*/
roles?: string[];

/** Entra ID tenant id; absent on non-Entra tokens. Distinguishes work/school from personal accounts. */
tid?: string;

/**
* OPTIONAL. Handles optional claims that are not explicitly defined in the standard.
*/
Expand Down
1 change: 1 addition & 0 deletions src/vs/base/common/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export interface IProductConfiguration {
readonly resourceUrlTemplate: string;
readonly nlsBaseUrl: string;
readonly accessSKUs?: string[];
readonly accessScopes?: string[];
};

readonly agentSdks?: { readonly [packageId: string]: IAgentSdkProductConfig };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,5 @@ export function getExtensionGalleryManifestResourceUri(manifest: IExtensionGalle
}

export const ExtensionGalleryServiceUrlConfigKey = 'extensions.gallery.serviceUrl';

export const ExtensionGalleryAuthProviderConfigKey = 'extensions.gallery.authProvider';
1 change: 1 addition & 0 deletions src/vs/sessions/sessions.desktop.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import '../workbench/services/localization/electron-browser/localeService.js';
import '../workbench/services/extensions/electron-browser/extensionsScannerService.js';
import '../workbench/services/extensionManagement/electron-browser/extensionManagementServerService.js';
import '../workbench/services/extensionManagement/electron-browser/extensionGalleryManifestService.js';
import '../workbench/services/extensionManagement/electron-browser/extensionGalleryAccountService.js';
import '../workbench/services/extensionManagement/electron-browser/extensionTipsService.js';
import '../workbench/services/userDataSync/electron-browser/userDataSyncService.js';
import '../workbench/services/userDataSync/electron-browser/userDataAutoSyncService.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { CommandsRegistry, ICommandService } from '../../../../platform/commands
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
import { IDialogService, IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { ExtensionGalleryManifestStatus, ExtensionGalleryResourceType, ExtensionGalleryServiceUrlConfigKey, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
import { ExtensionGalleryManifestStatus, ExtensionGalleryResourceType, ExtensionGalleryAuthProviderConfigKey, ExtensionGalleryServiceUrlConfigKey, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
import { IExtensionGalleryAccountService } from '../../../services/extensionManagement/common/extensionGalleryAccount.js';
import { EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, ExtensionRequestsTimeoutConfigKey, ExtensionsLocalizedLabel, FilterType, IExtensionGalleryService, IExtensionManagementService, PreferencesLocalizedLabel, SortBy, VerifyExtensionSignatureConfigKey } from '../../../../platform/extensionManagement/common/extensionManagement.js';
import { areSameExtensions, getIdAndVersion } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
import { ExtensionStorageService } from '../../../../platform/extensionManagement/common/extensionStorage.js';
Expand All @@ -50,7 +51,6 @@ import { IsSessionsWindowContext, ResourceContextKey, WorkbenchStateContext } fr
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, registerWorkbenchContribution2, Extensions as WorkbenchExtensions, WorkbenchPhase } from '../../../common/contributions.js';
import { EditorExtensions } from '../../../common/editor.js';
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from '../../../common/views.js';
import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/browser/defaultAccount.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { EnablementState, IExtensionManagementServerService, IPublisherInfo, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../services/extensionManagement/common/extensionManagement.js';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js';
Expand Down Expand Up @@ -360,6 +360,18 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
},
[ExtensionGalleryAuthProviderConfigKey]: {
type: 'string',
enum: ['github', 'microsoft'],
enumDescriptions: [
localize('extensions.gallery.authProvider.github', "Authenticate to the Extensions Marketplace using GitHub."),
localize('extensions.gallery.authProvider.microsoft', "Authenticate to the Extensions Marketplace using a Microsoft (Entra ID) account."),
],
description: localize('extensions.gallery.authProvider', "Configure the authentication provider for the Extensions Marketplace"),
default: 'github',
scope: ConfigurationScope.APPLICATION,
included: false,
},
'extensions.supportNodeGlobalNavigator': {
type: 'boolean',
description: localize('extensionsSupportNodeGlobalNavigator', "When enabled, Node.js navigator object is exposed on the global scope."),
Expand Down Expand Up @@ -2118,12 +2130,15 @@ registerAction2(class ExtensionsGallerySignInAction extends Action2 {
title: localize2('signInToMarketplace', 'Sign in to access Extensions Marketplace'),
menu: {
id: MenuId.AccountsContext,
when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn)
when: ContextKeyExpr.or(
CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn),
CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied),
)
},
});
}
run(accessor: ServicesAccessor): Promise<void> {
return accessor.get(ICommandService).executeCommand(DEFAULT_ACCOUNT_SIGN_IN_COMMAND);
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(IExtensionGalleryAccountService).signIn();
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js
import { KeyCode } from '../../../../base/common/keyCodes.js';
import { IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from '../../../../platform/extensionManagement/common/extensionGalleryManifest.js';
import { URI } from '../../../../base/common/uri.js';
import { DEFAULT_ACCOUNT_SIGN_IN_COMMAND } from '../../../services/accounts/browser/defaultAccount.js';

export const ExtensionsSortByContext = new RawContextKey<string>('extensionsSortByValue', '');
export const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
Expand Down Expand Up @@ -146,7 +145,10 @@ export class ExtensionsViewletViewsContribution extends Disposable implements IW
ContextKeyExpr.or(
ContextKeyExpr.has('searchMarketplaceExtensions'), ContextKeyExpr.and(DefaultViewsContext)
),
ContextKeyExpr.or(CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn), CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied))
ContextKeyExpr.or(
CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn),
CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied)
)
),
order: -1,
});
Expand All @@ -155,10 +157,11 @@ export class ExtensionsViewletViewsContribution extends Disposable implements IW
viewRegistry.registerViews(viewDescriptors, this.container);

viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {
content: localize('sign in', "[Sign in to access Extensions Marketplace]({0})", `command:${DEFAULT_ACCOUNT_SIGN_IN_COMMAND}`),
content: localize('sign in', "[Sign in to access Extensions Marketplace]({0})", `command:workbench.extensions.actions.gallery.signIn`),
when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.RequiresSignIn)
});

// Access denied applies to every provider (microsoft/github/default), so gate on status alone.
viewRegistry.registerViewWelcomeContent('workbench.views.extensions.marketplaceAccess', {
content: localize('access denied', "Your account does not have access to the Extensions Marketplace. Please contact your administrator."),
when: CONTEXT_EXTENSIONS_GALLERY_STATUS.isEqualTo(ExtensionGalleryManifestStatus.AccessDenied)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from '../../../../base/common/event.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';

/** `accessToken` is only carried when the provider authenticates with a bearer. */
export interface IExtensionGalleryAccount {
readonly accessToken?: string;
}

export const enum ExtensionGalleryAccountStatus {
/** None signed in, or several with no choice made. */
SignedOut = 'signedOut',
Ineligible = 'ineligible',
Eligible = 'eligible',
/** Could not be resolved — a transient auth failure, not a sign-out. */
Unknown = 'unknown'
}

/**
* The authentication half of marketplace access. Implementations live in the Electron layer and
* are supplied through {@link IExtensionGalleryAccountService.setAccountProvider}, so the service
* itself never depends on authentication.
*/
export interface IExtensionGalleryAccountProvider {
readonly accountStatus: ExtensionGalleryAccountStatus;
readonly onDidChangeAccountStatus: Event<ExtensionGalleryAccountStatus>;
readonly onDidChangeAccount: Event<void>;

/** Never prompts. Check {@link accountStatus} for whether the account may actually be used. */
getAccount(): Promise<IExtensionGalleryAccount | undefined>;

/** Interactive. The provider owns account selection and how the session is obtained. */
signIn(): Promise<void>;
}

export const IExtensionGalleryAccountService = createDecorator<IExtensionGalleryAccountService>('extensionGalleryAccountService');

/** Identity and entitlement for the Private Marketplace. Knows nothing about URLs or HTTP. */
export interface IExtensionGalleryAccountService {
readonly _serviceBrand: undefined;

readonly accountStatus: ExtensionGalleryAccountStatus;
readonly onDidChangeAccountStatus: Event<ExtensionGalleryAccountStatus>;
readonly onDidChangeAccount: Event<void>;

/** Never prompts. Check {@link accountStatus} for whether the account may actually be used. */
getAccount(): Promise<IExtensionGalleryAccount | undefined>;

/** Interactive sign-in for whichever provider the deployment configured. */
signIn(): Promise<void>;

setAccountProvider(provider: IExtensionGalleryAccountProvider): void;
}
Loading
Loading