`issuer` -> `authorizationServer` refactor (#250359)

* `issuer` -> `authorizationServer` refactor

Also:
* adds `authorizationServerGlobs` to the authentication contribution schema
* removes ugly MCP issuer hack and instead plumbs the authorizationServer down to the new auth providers
This commit is contained in:
Tyler James Leonhardt 2025-06-02 17:38:21 -07:00 committed by GitHub
parent a2ebac10a3
commit c235626145
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 195 additions and 177 deletions

View File

@ -35,14 +35,14 @@
{
"label": "GitHub",
"id": "github",
"issuerGlobs": [
"authorizationServerGlobs": [
"https://github.com/login/oauth"
]
},
{
"label": "GitHub Enterprise Server",
"id": "github-enterprise",
"issuerGlobs": [
"authorizationServerGlobs": [
"*"
]
}

View File

@ -143,7 +143,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
this,
{
supportsMultipleAccounts: true,
supportedIssuers: [
supportedAuthorizationServers: [
ghesUri ?? vscode.Uri.parse('https://github.com/login/oauth')
]
}

View File

@ -33,7 +33,7 @@
{
"label": "Microsoft",
"id": "microsoft",
"issuerGlobs": [
"authorizationServerGlobs": [
"https://login.microsoftonline.com/*/v2.0"
]
},

View File

@ -203,7 +203,7 @@ export class AzureActiveDirectoryService {
return this._sessionChangeEmitter.event;
}
public getSessions(scopes: string[] | undefined, { account, issuer }: vscode.AuthenticationProviderSessionOptions = {}): Promise<vscode.AuthenticationSession[]> {
public getSessions(scopes: string[] | undefined, { account, authorizationServer }: vscode.AuthenticationProviderSessionOptions = {}): Promise<vscode.AuthenticationSession[]> {
if (!scopes) {
this._logger.info('Getting sessions for all scopes...');
const sessions = this._tokens
@ -226,8 +226,8 @@ export class AzureActiveDirectoryService {
if (!modifiedScopes.includes('offline_access')) {
modifiedScopes.push('offline_access');
}
if (issuer) {
const tenant = issuer.path.split('/')[1];
if (authorizationServer) {
const tenant = authorizationServer.path.split('/')[1];
if (tenant) {
modifiedScopes.push(`VSCODE_TENANT:${tenant}`);
}
@ -303,7 +303,7 @@ export class AzureActiveDirectoryService {
.map(result => (result as PromiseFulfilledResult<vscode.AuthenticationSession>).value);
}
public createSession(scopes: string[], { account, issuer }: vscode.AuthenticationProviderSessionOptions = {}): Promise<vscode.AuthenticationSession> {
public createSession(scopes: string[], { account, authorizationServer }: vscode.AuthenticationProviderSessionOptions = {}): Promise<vscode.AuthenticationSession> {
let modifiedScopes = [...scopes];
if (!modifiedScopes.includes('openid')) {
modifiedScopes.push('openid');
@ -317,8 +317,8 @@ export class AzureActiveDirectoryService {
if (!modifiedScopes.includes('offline_access')) {
modifiedScopes.push('offline_access');
}
if (issuer) {
const tenant = issuer.path.split('/')[1];
if (authorizationServer) {
const tenant = authorizationServer.path.split('/')[1];
if (tenant) {
modifiedScopes.push(`VSCODE_TENANT:${tenant}`);
}

View File

@ -45,14 +45,14 @@ export class ScopeData {
*/
readonly tenantId: string | undefined;
constructor(readonly originalScopes: readonly string[] = [], issuer?: Uri) {
constructor(readonly originalScopes: readonly string[] = [], authorizationServer?: Uri) {
const modifiedScopes = [...originalScopes];
modifiedScopes.sort();
this.allScopes = modifiedScopes;
this.scopeStr = modifiedScopes.join(' ');
this.scopesToSend = this.getScopesToSend(modifiedScopes);
this.clientId = this.getClientId(this.allScopes);
this.tenant = this.getTenant(this.allScopes, issuer);
this.tenant = this.getTenant(this.allScopes, authorizationServer);
this.tenantId = this.getTenantId(this.tenant);
}
@ -65,10 +65,10 @@ export class ScopeData {
}, undefined) ?? DEFAULT_CLIENT_ID;
}
private getTenant(scopes: string[], issuer?: Uri): string {
if (issuer?.path) {
private getTenant(scopes: string[], authorizationServer?: Uri): string {
if (authorizationServer?.path) {
// Get tenant portion of URL
const tenant = issuer.path.split('/')[1];
const tenant = authorizationServer.path.split('/')[1];
if (tenant) {
return tenant;
}

View File

@ -75,21 +75,21 @@ suite('ScopeData', () => {
assert.strictEqual(scopeData.tenantId, 'some_guid');
});
test('should extract tenant from issuer URL path', () => {
const issuer = Uri.parse('https://login.microsoftonline.com/tenant123/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope'], issuer);
test('should extract tenant from authorization server URL path', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com/tenant123/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope'], authorizationServer);
assert.strictEqual(scopeData.tenant, 'tenant123');
});
test('should fallback to default tenant if issuer URL has no path segments', () => {
const issuer = Uri.parse('https://login.microsoftonline.com');
const scopeData = new ScopeData(['custom_scope'], issuer);
test('should fallback to default tenant if authorization server URL has no path segments', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com');
const scopeData = new ScopeData(['custom_scope'], authorizationServer);
assert.strictEqual(scopeData.tenant, 'organizations');
});
test('should prioritize issuer URL over VSCODE_TENANT scope', () => {
const issuer = Uri.parse('https://login.microsoftonline.com/url_tenant/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], issuer);
test('should prioritize authorization server URL over VSCODE_TENANT scope', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com/url_tenant/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], authorizationServer);
assert.strictEqual(scopeData.tenant, 'url_tenant');
});
});

View File

@ -170,7 +170,7 @@ export async function activate(context: vscode.ExtensionContext, telemetryReport
},
{
supportsMultipleAccounts: true,
supportedIssuers: [
supportedAuthorizationServers: [
vscode.Uri.parse('https://login.microsoftonline.com/*/v2.0')
]
}

View File

@ -81,7 +81,7 @@ export async function activate(context: ExtensionContext, mainTelemetryReporter:
authProvider,
{
supportsMultipleAccounts: true,
supportedIssuers: [
supportedAuthorizationServers: [
Uri.parse('https://login.microsoftonline.com/*/v2.0')
]
}

View File

@ -156,7 +156,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
async getSessions(scopes: string[] | undefined, options: AuthenticationGetSessionOptions = {}): Promise<AuthenticationSession[]> {
const askingForAll = scopes === undefined;
const scopeData = new ScopeData(scopes, options?.issuer);
const scopeData = new ScopeData(scopes, options?.authorizationServer);
// Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
this._logger.info('[getSessions]', askingForAll ? '[all]' : `[${scopeData.scopeStr}]`, 'starting');
@ -186,7 +186,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
}
async createSession(scopes: readonly string[], options: AuthenticationProviderSessionOptions): Promise<AuthenticationSession> {
const scopeData = new ScopeData(scopes, options.issuer);
const scopeData = new ScopeData(scopes, options.authorizationServer);
// Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting');

View File

@ -26,7 +26,7 @@ export interface IAuthorizationProtectedResourceMetadata {
resource_name?: string;
/**
* OPTIONAL. JSON array containing a list of OAuth authorization server issuer identifiers.
* OPTIONAL. JSON array containing a list of OAuth authorization server identifiers.
*/
authorization_servers?: string[];
@ -624,12 +624,12 @@ export function isAuthorizationDeviceTokenErrorResponse(obj: unknown): obj is IA
//#endregion
export function getDefaultMetadataForUrl(issuer: URL): IRequiredAuthorizationServerMetadata & IRequiredAuthorizationServerMetadata {
export function getDefaultMetadataForUrl(authorizationServer: URL): IRequiredAuthorizationServerMetadata & IRequiredAuthorizationServerMetadata {
return {
issuer: issuer.toString(),
authorization_endpoint: new URL('/authorize', issuer).toString(),
token_endpoint: new URL('/token', issuer).toString(),
registration_endpoint: new URL('/register', issuer).toString(),
issuer: authorizationServer.toString(),
authorization_endpoint: new URL('/authorize', authorizationServer).toString(),
token_endpoint: new URL('/token', authorizationServer).toString(),
registration_endpoint: new URL('/register', authorizationServer).toString(),
// Default values for Dynamic OpenID Providers
// https://openid.net/specs/openid-connect-discovery-1_0.html
response_types_supported: ['code', 'id_token', 'id_token token'],

View File

@ -197,8 +197,8 @@ suite('OAuth', () => {
suite('Utility Functions', () => {
test('getDefaultMetadataForUrl should return correct default endpoints', () => {
const issuer = new URL('https://auth.example.com');
const metadata = getDefaultMetadataForUrl(issuer);
const authorizationServer = new URL('https://auth.example.com');
const metadata = getDefaultMetadataForUrl(authorizationServer);
assert.strictEqual(metadata.issuer, 'https://auth.example.com/');
assert.strictEqual(metadata.authorization_endpoint, 'https://auth.example.com/authorize');

View File

@ -104,7 +104,7 @@ export interface ICodeActionContribution {
export interface IAuthenticationContribution {
readonly id: string;
readonly label: string;
readonly issuerGlobs?: string[];
readonly authorizationServerGlobs?: string[];
}
export interface IWalkthroughStep {

View File

@ -40,7 +40,7 @@ export interface AuthenticationGetSessionOptions {
forceNewSession?: boolean | AuthenticationInteractiveOptions;
silent?: boolean;
account?: AuthenticationSessionAccount;
issuer?: UriComponents;
authorizationServer?: UriComponents;
}
export class MainThreadAuthenticationProvider extends Disposable implements IAuthenticationProvider {
@ -52,7 +52,7 @@ export class MainThreadAuthenticationProvider extends Disposable implements IAut
public readonly id: string,
public readonly label: string,
public readonly supportsMultipleAccounts: boolean,
public readonly issuers: ReadonlyArray<URI>,
public readonly authorizationServers: ReadonlyArray<URI>,
private readonly notificationService: INotificationService,
onDidChangeSessionsEmitter: Emitter<AuthenticationSessionsChangeEvent>,
) {
@ -115,13 +115,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this._register(authenticationService.registerAuthenticationProviderHostDelegate({
// Prefer Node.js extension hosts when they're available. No CORS issues etc.
priority: extHostContext.extensionHostKind === ExtensionHostKind.LocalWebWorker ? 0 : 1,
create: async (serverMetadata, resource) => {
const clientId = this.dynamicAuthProviderStorageService.getClientId(serverMetadata.issuer);
create: async (authorizationServer, serverMetadata, resource) => {
const authProviderId = authorizationServer.toString(true);
const clientId = this.dynamicAuthProviderStorageService.getClientId(authProviderId);
let initialTokens: (IAuthorizationTokenResponse & { created_at: number })[] | undefined = undefined;
if (clientId) {
initialTokens = await this.dynamicAuthProviderStorageService.getSessionsForDynamicAuthProvider(serverMetadata.issuer, clientId);
initialTokens = await this.dynamicAuthProviderStorageService.getSessionsForDynamicAuthProvider(authProviderId, clientId);
}
return await this._proxy.$registerDynamicAuthProvider(
authorizationServer,
serverMetadata,
resource,
clientId,
@ -131,7 +133,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}));
}
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean, supportedIssuers: UriComponents[] = []): Promise<void> {
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean, supportedAuthorizationServer: UriComponents[] = []): Promise<void> {
if (!this.authenticationService.declaredProviders.find(p => p.id === id)) {
// If telemetry shows that this is not happening much, we can instead throw an error here.
this.logService.warn(`Authentication provider ${id} was not declared in the Extension Manifest.`);
@ -144,8 +146,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
const emitter = new Emitter<AuthenticationSessionsChangeEvent>();
this._registrations.set(id, emitter);
const supportedIssuerUris = supportedIssuers.map(i => URI.revive(i));
const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, supportedIssuerUris, this.notificationService, emitter);
const supportedAuthorizationServerUris = supportedAuthorizationServer.map(i => URI.revive(i));
const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, supportedAuthorizationServerUris, this.notificationService, emitter);
this.authenticationService.registerAuthenticationProvider(id, provider);
}
@ -212,9 +214,9 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return deferredPromise.p;
}
async $registerDynamicAuthenticationProvider(id: string, label: string, issuer: UriComponents, clientId: string): Promise<void> {
await this.$registerAuthenticationProvider(id, label, false, [issuer]);
this.dynamicAuthProviderStorageService.storeClientId(id, clientId, label, URI.revive(issuer).toString());
async $registerDynamicAuthenticationProvider(id: string, label: string, authorizationServer: UriComponents, clientId: string): Promise<void> {
await this.$registerAuthenticationProvider(id, label, false, [authorizationServer]);
this.dynamicAuthProviderStorageService.storeClientId(id, URI.revive(authorizationServer).toString(true), clientId, label);
}
async $setSessionsForDynamicAuthProvider(authProviderId: string, clientId: string, sessions: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<void> {
@ -288,8 +290,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
private async doGetSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: AuthenticationGetSessionOptions): Promise<AuthenticationSession | undefined> {
const issuer = URI.revive(options.issuer);
const sessions = await this.authenticationService.getSessions(providerId, scopes, { account: options.account, issuer }, true);
const authorizationServer = URI.revive(options.authorizationServer);
const sessions = await this.authenticationService.getSessions(providerId, scopes, { account: options.account, authorizationServer }, true);
const provider = this.authenticationService.getProvider(providerId);
// Error cases
@ -360,7 +362,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
{
activateImmediate: true,
account: accountToCreate,
issuer
authorizationServer
});
} while (
accountToCreate

View File

@ -9,7 +9,7 @@ import { Emitter } from '../../../base/common/event.js';
import { Disposable, DisposableMap } from '../../../base/common/lifecycle.js';
import { ISettableObservable, observableValue } from '../../../base/common/observable.js';
import Severity from '../../../base/common/severity.js';
import { URI } from '../../../base/common/uri.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { IDialogService, IPromptButton } from '../../../platform/dialogs/common/dialogs.js';
import { LogLevel } from '../../../platform/log/common/log.js';
import { IMcpMessageTransport, IMcpRegistry } from '../../contrib/mcp/common/mcpRegistryTypes.js';
@ -138,24 +138,23 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
this._servers.get(id)?.pushMessage(message);
}
async $getTokenFromServerMetadata(id: number, metadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<string | undefined> {
async $getTokenFromServerMetadata(id: number, authServerComponents: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined): Promise<string | undefined> {
const server = this._serverDefinitions.get(id);
if (!server) {
return undefined;
}
const issuer = URI.parse(metadata.issuer);
// Some better default?
const scopesSupported = metadata.scopes_supported || [];
let providerId = await this._authenticationService.getOrActivateProviderIdForIssuer(issuer);
const authorizationServer = URI.revive(authServerComponents);
const scopesSupported = resourceMetadata?.scopes_supported || serverMetadata.scopes_supported || [];
let providerId = await this._authenticationService.getOrActivateProviderIdForServer(authorizationServer);
if (!providerId) {
const provider = await this._authenticationService.createDynamicAuthenticationProvider(metadata, resource);
const provider = await this._authenticationService.createDynamicAuthenticationProvider(authorizationServer, serverMetadata, resourceMetadata);
if (!provider) {
return undefined;
}
providerId = provider.id;
}
const sessions = await this._authenticationService.getSessions(providerId, scopesSupported, { issuer }, true);
const sessions = await this._authenticationService.getSessions(providerId, scopesSupported, { authorizationServer: authorizationServer }, true);
const accountNamePreference = this.authenticationMcpServersService.getAccountPreference(server.id, providerId);
let matchingAccountPreferenceSession: AuthenticationSession | undefined;
if (accountNamePreference) {
@ -193,7 +192,7 @@ export class MainThreadMcp extends Disposable implements MainThreadMcpShape {
{
activateImmediate: true,
account: accountToCreate,
issuer
authorizationServer
});
} while (
accountToCreate

View File

@ -300,7 +300,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
) {
checkProposedApiEnabled(extension, 'authLearnMore');
}
if (options?.issuer) {
if (options?.authorizationServer) {
checkProposedApiEnabled(extension, 'authIssuers');
}
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
@ -317,7 +317,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return _asExtensionEvent(extHostAuthentication.getExtensionScopedSessionsEvent(extension.identifier.value));
},
registerAuthenticationProvider(id: string, label: string, provider: vscode.AuthenticationProvider, options?: vscode.AuthenticationProviderOptions): vscode.Disposable {
if (options?.supportedIssuers) {
if (options?.supportedAuthorizationServers) {
checkProposedApiEnabled(extension, 'authIssuers');
}
return extHostAuthentication.registerAuthenticationProvider(id, label, provider, options);

View File

@ -182,7 +182,7 @@ export interface AuthenticationGetSessionOptions {
}
export interface MainThreadAuthenticationShape extends IDisposable {
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean, supportedIssuers?: UriComponents[]): Promise<void>;
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean, supportedAuthorizationServers?: UriComponents[]): Promise<void>;
$unregisterAuthenticationProvider(id: string): Promise<void>;
$ensureProvider(id: string): Promise<void>;
$sendDidChangeSessions(providerId: string, event: AuthenticationSessionsChangeEvent): Promise<void>;
@ -192,7 +192,7 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$waitForUriHandler(expectedUri: UriComponents): Promise<UriComponents>;
$showContinueNotification(message: string): Promise<boolean>;
$showDeviceCodeModal(userCode: string, verificationUri: string): Promise<boolean>;
$registerDynamicAuthenticationProvider(id: string, label: string, issuer: UriComponents, clientId: string): Promise<void>;
$registerDynamicAuthenticationProvider(id: string, label: string, authorizationServer: UriComponents, clientId: string): Promise<void>;
$setSessionsForDynamicAuthProvider(authProviderId: string, clientId: string, sessions: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<void>;
}
@ -1990,7 +1990,7 @@ export interface ExtHostAuthenticationShape {
$removeSession(id: string, sessionId: string): Promise<void>;
$onDidChangeAuthenticationSessions(id: string, label: string, extensionIdFilter?: string[]): Promise<void>;
$onDidUnregisterAuthenticationProvider(id: string): Promise<void>;
$registerDynamicAuthProvider(serverMetadata: IAuthorizationServerMetadata, resource?: IAuthorizationProtectedResourceMetadata, clientId?: string, initialTokens?: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<string>;
$registerDynamicAuthProvider(authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resource?: IAuthorizationProtectedResourceMetadata, clientId?: string, initialTokens?: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<string>;
$onDidChangeDynamicAuthProviderTokens(authProviderId: string, clientId: string, tokens?: (IAuthorizationTokenResponse & { created_at: number })[]): Promise<void>;
}
@ -3019,7 +3019,7 @@ export interface MainThreadMcpShape {
$onDidReceiveMessage(id: number, message: string): void;
$upsertMcpCollection(collection: McpCollectionDefinition.FromExtHost, servers: McpServerDefinition.Serialized[]): void;
$deleteMcpCollection(collectionId: string): void;
$getTokenFromServerMetadata(id: number, metadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<string | undefined>;
$getTokenFromServerMetadata(id: number, authorizationServer: UriComponents, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined): Promise<string | undefined>;
}
export interface ExtHostLocalizationShape {

View File

@ -12,7 +12,7 @@ import { IExtensionDescription, ExtensionIdentifier } from '../../../platform/ex
import { INTERNAL_AUTH_PROVIDER_PREFIX } from '../../services/authentication/common/authentication.js';
import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
import { IExtHostRpcService } from './extHostRpcService.js';
import { URI } from '../../../base/common/uri.js';
import { URI, UriComponents } from '../../../base/common/uri.js';
import { fetchDynamicRegistration, getClaimsFromJWT, IAuthorizationJWTClaims, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata, IAuthorizationTokenResponse, isAuthorizationTokenResponse } from '../../../base/common/oauth.js';
import { IExtHostWindow } from './extHostWindow.js';
import { IExtHostInitDataService } from './extHostInitDataService.js';
@ -114,7 +114,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
this._authenticationProviders.set(id, { label, provider, options: options ?? { supportsMultipleAccounts: false } });
const listener = provider.onDidChangeSessions(e => this._proxy.$sendDidChangeSessions(id, e));
this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false, options?.supportedIssuers);
this._proxy.$registerAuthenticationProvider(id, label, options?.supportsMultipleAccounts ?? false, options?.supportedAuthorizationServers);
return new Disposable(() => {
listener.dispose();
@ -126,7 +126,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
async $createSession(providerId: string, scopes: string[], options: vscode.AuthenticationProviderSessionOptions): Promise<vscode.AuthenticationSession> {
const providerData = this._authenticationProviders.get(providerId);
if (providerData) {
options.issuer = URI.revive(options.issuer);
options.authorizationServer = URI.revive(options.authorizationServer);
return await providerData.provider.createSession(scopes, options);
}
@ -145,7 +145,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
async $getSessions(providerId: string, scopes: ReadonlyArray<string> | undefined, options: vscode.AuthenticationProviderSessionOptions): Promise<ReadonlyArray<vscode.AuthenticationSession>> {
const providerData = this._authenticationProviders.get(providerId);
if (providerData) {
options.issuer = URI.revive(options.issuer);
options.authorizationServer = URI.revive(options.authorizationServer);
return await providerData.provider.getSessions(scopes, options);
}
@ -170,6 +170,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
}
async $registerDynamicAuthProvider(
authorizationServerComponents: UriComponents,
serverMetadata: IAuthorizationServerMetadata,
resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined,
clientId: string | undefined,
@ -193,6 +194,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
this._extHostProgress,
this._extHostLoggerService,
this._proxy,
URI.revive(authorizationServerComponents),
serverMetadata,
resourceMetadata,
clientId,
@ -209,7 +211,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
options: { supportsMultipleAccounts: false }
}
);
await this._proxy.$registerDynamicAuthenticationProvider(provider.id, provider.label, provider.issuer, provider.clientId);
await this._proxy.$registerDynamicAuthenticationProvider(provider.id, provider.label, provider.authorizationServer, provider.clientId);
return provider.id;
}
@ -236,7 +238,6 @@ class TaskSingler<T> {
export class DynamicAuthProvider implements vscode.AuthenticationProvider {
readonly id: string;
readonly label: string;
readonly issuer: URI;
private _onDidChangeSessions = new Emitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
readonly onDidChangeSessions = this._onDidChangeSessions.event;
@ -258,19 +259,20 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider {
@IExtHostProgress private readonly _extHostProgress: IExtHostProgress,
@ILoggerService loggerService: ILoggerService,
protected readonly _proxy: MainThreadAuthenticationShape,
readonly authorizationServer: URI,
protected readonly _serverMetadata: IAuthorizationServerMetadata,
protected readonly _resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined,
readonly clientId: string,
onDidDynamicAuthProviderTokensChange: Emitter<{ authProviderId: string; clientId: string; tokens: IAuthorizationToken[] }>,
initialTokens: IAuthorizationToken[],
) {
this.issuer = URI.parse(_serverMetadata.issuer);
const stringifiedServer = authorizationServer.toString(true);
this.id = _resourceMetadata?.resource
? _serverMetadata.issuer + ' ' + _resourceMetadata?.resource
: _serverMetadata.issuer;
this.label = _resourceMetadata?.resource_name ?? this.issuer.authority;
? stringifiedServer + ' ' + _resourceMetadata?.resource
: stringifiedServer;
this.label = _resourceMetadata?.resource_name ?? this.authorizationServer.authority;
this._logger = loggerService.createLogger(_serverMetadata.issuer, { name: this.label });
this._logger = loggerService.createLogger(stringifiedServer, { name: this.label });
this._disposable = new DisposableStore();
this._disposable.add(this._onDidChangeSessions);
const scopedEvent = Event.chain(onDidDynamicAuthProviderTokensChange.event, $ => $
@ -280,7 +282,7 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider {
this._tokenStore = this._disposable.add(new TokenStore(
{
onDidChange: scopedEvent,
set: (tokens) => _proxy.$setSessionsForDynamicAuthProvider(this._serverMetadata.issuer, this.clientId, tokens),
set: (tokens) => _proxy.$setSessionsForDynamicAuthProvider(stringifiedServer, this.clientId, tokens),
},
initialTokens,
this._logger
@ -412,8 +414,7 @@ export class DynamicAuthProvider implements vscode.AuthenticationProvider {
// Generate a random state value to prevent CSRF
const nonce = this.generateRandomString(32);
const issuer = URI.parse(this._serverMetadata.issuer);
const callbackUri = URI.parse(`${this._initData.environment.appUriScheme}://dynamicauthprovider/${issuer.authority}/authorize?nonce=${nonce}`);
const callbackUri = URI.parse(`${this._initData.environment.appUriScheme}://dynamicauthprovider/${this.authorizationServer.authority}/authorize?nonce=${nonce}`);
let state: URI;
try {
state = await this._extHostUrls.createAppUri(callbackUri);

View File

@ -189,8 +189,9 @@ class McpHTTPHandle extends Disposable {
private readonly _cts = new CancellationTokenSource();
private readonly _abortCtrl = new AbortController();
private _authMetadata?: {
server: IAuthorizationServerMetadata;
resource?: IAuthorizationProtectedResourceMetadata;
authorizationServer: URI;
serverMetadata: IAuthorizationServerMetadata;
resourceMetadata?: IAuthorizationProtectedResourceMetadata;
};
constructor(
@ -354,17 +355,9 @@ class McpHTTPHandle extends Disposable {
const serverMetadataResponse = await this._getAuthorizationServerMetadata(serverMetadataUrl, addtionalHeaders);
const serverMetadataWithDefaults = getMetadataWithDefaultValues(serverMetadataResponse);
this._authMetadata = {
server: {
...serverMetadataWithDefaults,
// HACK: For now, just use the serverMetadataUrl as the issuer. I found an example, Entra,
// that uses a placeholder for the tenant... https://login.microsoftonline.com/{tenant}/v2.0
// literally... it contains `{tenant}`... instead of `organizations`. This may change our
// API a bit to instead pass in these other endpoints, but for now, just user the serverMetadataUrl
// as the isser.
issuer: serverMetadataUrl,
scopes_supported: scopesSupported ?? serverMetadataWithDefaults.scopes_supported
},
resource
authorizationServer: URI.parse(serverMetadataUrl),
serverMetadata: serverMetadataWithDefaults,
resourceMetadata: resource
};
return;
} catch (e) {
@ -375,8 +368,9 @@ class McpHTTPHandle extends Disposable {
const defaultMetadata = getDefaultMetadataForUrl(new URL(baseUrl));
defaultMetadata.scopes_supported = scopesSupported ?? defaultMetadata.scopes_supported ?? [];
this._authMetadata = {
server: defaultMetadata,
resource
authorizationServer: URI.parse(serverMetadataUrl),
serverMetadata: defaultMetadata,
resourceMetadata: resource
};
}
@ -414,8 +408,8 @@ class McpHTTPHandle extends Disposable {
// For the oauth server metadata discovery path, we _INSERT_
// the well known path after the origin and before the path.
// https://datatracker.ietf.org/doc/html/rfc8414#section-3
const issuer = new URL(authorizationServer);
const extraPath = issuer.pathname === '/' ? '' : issuer.pathname;
const authorizationServerUrl = new URL(authorizationServer);
const extraPath = authorizationServerUrl.pathname === '/' ? '' : authorizationServerUrl.pathname;
const pathToFetch = new URL(AUTH_SERVER_METADATA_DISCOVERY_PATH, authorizationServer).toString() + extraPath;
let authServerMetadataResponse = await fetch(pathToFetch, {
method: 'GET',
@ -648,7 +642,7 @@ class McpHTTPHandle extends Disposable {
private async _addAuthHeader(headers: Record<string, string>) {
if (this._authMetadata) {
try {
const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.server, this._authMetadata.resource);
const token = await this._proxy.$getTokenFromServerMetadata(this._id, this._authMetadata.authorizationServer, this._authMetadata.serverMetadata, this._authMetadata.resourceMetadata);
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

View File

@ -21,6 +21,7 @@ import { DeferredPromise, raceCancellationError } from '../../../base/common/asy
import { IExtHostProgress } from '../common/extHostProgress.js';
import { IProgressStep } from '../../../platform/progress/common/progress.js';
import { CancellationError, isCancellationError } from '../../../base/common/errors.js';
import { URI } from '../../../base/common/uri.js';
interface IOAuthResult {
code: string;
@ -200,6 +201,7 @@ export class NodeDynamicAuthProvider extends DynamicAuthProvider {
extHostProgress: IExtHostProgress,
loggerService: ILoggerService,
proxy: MainThreadAuthenticationShape,
authorizationServer: URI,
serverMetadata: IAuthorizationServerMetadata,
resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined,
clientId: string,
@ -213,6 +215,7 @@ export class NodeDynamicAuthProvider extends DynamicAuthProvider {
extHostProgress,
loggerService,
proxy,
authorizationServer,
serverMetadata,
resourceMetadata,
clientId,

View File

@ -54,7 +54,7 @@ class AuthenticationDataRenderer extends Disposable implements IExtensionFeature
return [
auth.label,
auth.id,
(auth.issuerGlobs ?? []).join(',\n')
(auth.authorizationServerGlobs ?? []).join(',\n')
];
});

View File

@ -60,6 +60,14 @@ const authenticationDefinitionSchema: IJSONSchema = {
label: {
type: 'string',
description: localize('authentication.label', 'The human readable name of the authentication provider.'),
},
authorizationServerGlobs: {
type: 'array',
items: {
type: 'string',
description: localize('authentication.authorizationServerGlobs', 'A list of globs that match the authorization servers that this provider supports.'),
},
description: localize('authentication.authorizationServerGlobsDescription', 'A list of globs that match the authorization servers that this provider supports.')
}
}
};
@ -258,15 +266,15 @@ export class AuthenticationService extends Disposable implements IAuthentication
async getSessions(id: string, scopes?: string[], options?: IAuthenticationGetSessionsOptions, activateImmediate: boolean = false): Promise<ReadonlyArray<AuthenticationSession>> {
const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, activateImmediate);
if (authProvider) {
// Check if the issuer is in the list of supported issuers
if (options?.issuer) {
const issuerStr = options.issuer.toString(true);
// Check if the authorization server is in the list of supported authorization servers
if (options?.authorizationServer) {
const authServerStr = options.authorizationServer.toString(true);
// TODO: something is off here...
if (!authProvider.issuers?.some(i => i.toString(true) === issuerStr || match(i.toString(true), issuerStr))) {
throw new Error(`The issuer '${issuerStr}' is not supported by the authentication provider '${id}'.`);
if (!authProvider.authorizationServers?.some(i => i.toString(true) === authServerStr || match(i.toString(true), authServerStr))) {
throw new Error(`The authorization server '${authServerStr}' is not supported by the authentication provider '${id}'.`);
}
}
return await authProvider.getSessions(scopes, { account: options?.account, issuer: options?.issuer });
return await authProvider.getSessions(scopes, { account: options?.account, authorizationServer: options?.authorizationServer });
} else {
throw new Error(`No authentication provider '${id}' is currently registered.`);
}
@ -277,7 +285,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
if (authProvider) {
return await authProvider.createSession(scopes, {
account: options?.account,
issuer: options?.issuer
authorizationServer: options?.authorizationServer
});
} else {
throw new Error(`No authentication provider '${id}' is currently registered.`);
@ -293,36 +301,36 @@ export class AuthenticationService extends Disposable implements IAuthentication
}
}
async getOrActivateProviderIdForIssuer(issuer: URI): Promise<string | undefined> {
async getOrActivateProviderIdForServer(authorizationServer: URI): Promise<string | undefined> {
for (const provider of this._authenticationProviders.values()) {
if (provider.issuers?.some(i => i.toString(true) === issuer.toString(true) || match(i.toString(true), issuer.toString(true)))) {
if (provider.authorizationServers?.some(i => i.toString(true) === authorizationServer.toString(true) || match(i.toString(true), authorizationServer.toString(true)))) {
return provider.id;
}
}
const issuerStr = issuer.toString(true);
const authServerStr = authorizationServer.toString(true);
const providers = this._declaredProviders
// Only consider providers that are not already registered since we already checked them
.filter(p => !this._authenticationProviders.has(p.id))
.filter(p => !!p.issuerGlobs?.some(i => match(i, issuerStr)));
.filter(p => !!p.authorizationServerGlobs?.some(i => match(i, authServerStr)));
// TODO:@TylerLeonhardt fan out?
for (const provider of providers) {
const activeProvider = await this.tryActivateProvider(provider.id, true);
// Check the resolved issuers
if (activeProvider.issuers?.some(i => match(i.toString(true), issuerStr))) {
// Check the resolved authorization servers
if (activeProvider.authorizationServers?.some(i => match(i.toString(true), authServerStr))) {
return activeProvider.id;
}
}
return undefined;
}
async createDynamicAuthenticationProvider(serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<IAuthenticationProvider | undefined> {
async createDynamicAuthenticationProvider(authorizationServer: URI, serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<IAuthenticationProvider | undefined> {
const delegate = this._delegates[0];
if (!delegate) {
this._logService.error('No authentication provider host delegate found');
return undefined;
}
const providerId = await delegate.create(serverMetadata, resource);
const providerId = await delegate.create(authorizationServer, serverMetadata, resource);
const provider = this._authenticationProviders.get(providerId);
if (provider) {
this._logService.debug(`Created dynamic authentication provider: ${providerId}`);

View File

@ -56,12 +56,12 @@ export class DynamicAuthenticationProviderStorageService extends Disposable impl
return provider?.clientId;
}
storeClientId(providerId: string, clientId: string, label?: string, issuer?: string): void {
storeClientId(providerId: string, authorizationServer: string, clientId: string, label?: string): void {
// Store provider information in single location
this._trackProvider(providerId, clientId, label, issuer);
this._trackProvider(providerId, authorizationServer, clientId, label);
}
private _trackProvider(providerId: string, clientId: string, label?: string, issuer?: string): void {
private _trackProvider(providerId: string, authorizationServer: string, clientId: string, label?: string): void {
const providers = this._getStoredProviders();
// Check if provider already exists
@ -71,7 +71,7 @@ export class DynamicAuthenticationProviderStorageService extends Disposable impl
const newProvider: DynamicAuthenticationProviderInfo = {
providerId,
label: label || providerId, // Use provided label or providerId as default
issuer: issuer || providerId, // Use provided issuer or providerId as default
authorizationServer,
clientId
};
providers.push(newProvider);
@ -82,7 +82,7 @@ export class DynamicAuthenticationProviderStorageService extends Disposable impl
const updatedProvider: DynamicAuthenticationProviderInfo = {
providerId,
label: label || existingProvider.label,
issuer: issuer || existingProvider.issuer,
authorizationServer,
clientId
};
providers[existingProviderIndex] = updatedProvider;
@ -93,7 +93,14 @@ export class DynamicAuthenticationProviderStorageService extends Disposable impl
private _getStoredProviders(): DynamicAuthenticationProviderInfo[] {
const stored = this.storageService.get(DynamicAuthenticationProviderStorageService.PROVIDERS_STORAGE_KEY, StorageScope.APPLICATION, '[]');
try {
return JSON.parse(stored);
const providerInfos = JSON.parse(stored);
// MIGRATION: remove after an iteration or 2
for (const providerInfo of providerInfos) {
if (!providerInfo.authorizationServer) {
providerInfo.authorizationServer = providerInfo.issuer;
}
}
return providerInfos;
} catch {
return [];
}

View File

@ -35,7 +35,7 @@ export interface AuthenticationSessionsChangeEvent {
export interface AuthenticationProviderInformation {
id: string;
label: string;
issuerGlobs?: ReadonlyArray<string>;
authorizationServerGlobs?: ReadonlyArray<string>;
}
export interface IAuthenticationCreateSessionOptions {
@ -46,10 +46,10 @@ export interface IAuthenticationCreateSessionOptions {
*/
account?: AuthenticationSessionAccount;
/**
* The issuer URI to use for this creation request. If passed in, first we validate that
* the provider can use this issuer, then it is passed down to the auth provider.
* The authorization server URI to use for this creation request. If passed in, first we validate that
* the provider can use this authorization server, then it is passed down to the auth provider.
*/
issuer?: URI;
authorizationServer?: URI;
}
export interface IAuthenticationGetSessionsOptions {
@ -59,10 +59,10 @@ export interface IAuthenticationGetSessionsOptions {
*/
account?: AuthenticationSessionAccount;
/**
* The issuer URI to use for this request. If passed in, first we validate that
* the provider can use this issuer, then it is passed down to the auth provider.
* The authorization server URI to use for this request. If passed in, first we validate that
* the provider can use this authorization server, then it is passed down to the auth provider.
*/
issuer?: URI;
authorizationServer?: URI;
}
export interface AllowedExtension {
@ -82,7 +82,7 @@ export interface AllowedExtension {
export interface IAuthenticationProviderHostDelegate {
/** Priority for this delegate, delegates are tested in descending priority order */
readonly priority: number;
create(serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<string>;
create(authorizationServer: URI, serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<string>;
}
export const IAuthenticationService = createDecorator<IAuthenticationService>('IAuthenticationService');
@ -189,10 +189,10 @@ export interface IAuthenticationService {
removeSession(providerId: string, sessionId: string): Promise<void>;
/**
* Gets a provider id for a specified issuer
* @param issuer The issuer url that this provider is responsible for
* Gets a provider id for a specified authorization server
* @param authorizationServer The authorization server url that this provider is responsible for
*/
getOrActivateProviderIdForIssuer(issuer: URI): Promise<string | undefined>;
getOrActivateProviderIdForServer(authorizationServer: URI): Promise<string | undefined>;
/**
* Allows the ability register a delegate that will be used to start authentication providers
@ -204,7 +204,7 @@ export interface IAuthenticationService {
* Creates a dynamic authentication provider for the given server metadata
* @param serverMetadata The metadata for the server that is being authenticated against
*/
createDynamicAuthenticationProvider(serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined): Promise<IAuthenticationProvider | undefined>;
createDynamicAuthenticationProvider(authorizationServer: URI, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined): Promise<IAuthenticationProvider | undefined>;
}
export function isAuthenticationSession(thing: unknown): thing is AuthenticationSession {
@ -301,10 +301,10 @@ export interface IAuthenticationProviderSessionOptions {
*/
account?: AuthenticationSessionAccount;
/**
* The issuer that is being asked about. If this is passed in, the provider should
* attempt to return sessions that are only related to this issuer.
* The authorization server that is being asked about. If this is passed in, the provider should
* attempt to return sessions that are only related to this authorization server.
*/
issuer?: URI;
authorizationServer?: URI;
}
/**
@ -322,9 +322,9 @@ export interface IAuthenticationProvider {
readonly label: string;
/**
* The resolved issuers. These can still contain globs, but should be concrete URIs
* The resolved authorization servers. These can still contain globs, but should be concrete URIs
*/
readonly issuers?: ReadonlyArray<URI>;
readonly authorizationServers?: ReadonlyArray<URI>;
/**
* Indicates whether the authentication provider supports multiple accounts.

View File

@ -12,7 +12,11 @@ export const IDynamicAuthenticationProviderStorageService = createDecorator<IDyn
export interface DynamicAuthenticationProviderInfo {
readonly providerId: string;
readonly label: string;
readonly issuer: string;
/**
* @deprecated in favor of authorizationServer
*/
readonly issuer?: string;
readonly authorizationServer: string;
readonly clientId: string;
}
@ -35,19 +39,19 @@ export interface IDynamicAuthenticationProviderStorageService {
/**
* Get the client ID for a dynamic authentication provider.
* @param providerId The provider ID or issuer URL.
* @param providerId The provider ID or authorization server URL.
* @returns The client ID if it exists, undefined otherwise.
*/
getClientId(providerId: string): string | undefined;
/**
* Store the client ID for a dynamic authentication provider.
* @param providerId The provider ID or issuer URL.
* @param providerId The provider ID or authorization server URL.
* @param clientId The client ID to store.
* @param label Optional label for the provider.
* @param issuer Optional issuer URL for the provider.
* @param authorizationServer Optional authorization server URL for the provider.
*/
storeClientId(providerId: string, clientId: string, label?: string, issuer?: string): void;
storeClientId(providerId: string, authorizationServer: string, clientId: string, label?: string): void;
/**
* Get all dynamic authentication providers that have been interacted with.

View File

@ -129,73 +129,73 @@ suite('AuthenticationService', () => {
assert.deepEqual(retrievedProvider, provider);
});
test('getOrActivateProviderIdForIssuer - should return undefined when no provider matches the issuer', async () => {
const issuer = URI.parse('https://example.com');
const result = await authenticationService.getOrActivateProviderIdForIssuer(issuer);
test('getOrActivateProviderIdForServer - should return undefined when no provider matches the authorization server', async () => {
const authorizationServer = URI.parse('https://example.com');
const result = await authenticationService.getOrActivateProviderIdForServer(authorizationServer);
assert.strictEqual(result, undefined);
});
test('getOrActivateProviderIdForIssuer - should return provider id if issuerGlobs matches and issuers match', async () => {
// Register a declared provider with an issuer glob
test('getOrActivateProviderIdForServer - should return provider id if authorizationServerGlobs matches and authorizationServers match', async () => {
// Register a declared provider with an authorization server glob
const provider: AuthenticationProviderInformation = {
id: 'github',
label: 'GitHub',
issuerGlobs: ['https://github.com/*']
authorizationServerGlobs: ['https://github.com/*']
};
authenticationService.registerDeclaredAuthenticationProvider(provider);
// Register an authentication provider with matching issuers
// Register an authentication provider with matching authorization servers
const authProvider = createProvider({
id: 'github',
label: 'GitHub',
issuers: [URI.parse('https://github.com/login')]
authorizationServers: [URI.parse('https://github.com/login')]
});
authenticationService.registerAuthenticationProvider('github', authProvider);
// Test with a matching URI
const issuer = URI.parse('https://github.com/login');
const result = await authenticationService.getOrActivateProviderIdForIssuer(issuer);
const authorizationServer = URI.parse('https://github.com/login');
const result = await authenticationService.getOrActivateProviderIdForServer(authorizationServer);
// Verify the result
assert.strictEqual(result, 'github');
});
test('getOrActivateProviderIdForIssuer - should return undefined if issuerGlobs match but issuers do not match', async () => {
// Register a declared provider with an issuer glob
test('getOrActivateProviderIdForServer - should return undefined if authorizationServerGlobs match but authorizationServers do not match', async () => {
// Register a declared provider with an authorization server glob
const provider: AuthenticationProviderInformation = {
id: 'github',
label: 'GitHub',
issuerGlobs: ['https://github.com/*']
authorizationServerGlobs: ['https://github.com/*']
};
authenticationService.registerDeclaredAuthenticationProvider(provider);
// Register an authentication provider with non-matching issuers
// Register an authentication provider with non-matching authorization servers
const authProvider = createProvider({
id: 'github',
label: 'GitHub',
issuers: [URI.parse('https://github.com/different')]
authorizationServers: [URI.parse('https://github.com/different')]
});
authenticationService.registerAuthenticationProvider('github', authProvider);
// Test with a non-matching URI
const issuer = URI.parse('https://github.com/login');
const result = await authenticationService.getOrActivateProviderIdForIssuer(issuer);
const authorizationServer = URI.parse('https://github.com/login');
const result = await authenticationService.getOrActivateProviderIdForServer(authorizationServer);
// Verify the result
assert.strictEqual(result, undefined);
});
test('getOrActivateProviderIdForIssuer - should check multiple providers and return the first match', async () => {
// Register two declared providers with issuer globs
test('getOrActivateProviderIdForAuthorizationServer - should check multiple providers and return the first match', async () => {
// Register two declared providers with authorization server globs
const provider1: AuthenticationProviderInformation = {
id: 'github',
label: 'GitHub',
issuerGlobs: ['https://github.com/*']
authorizationServerGlobs: ['https://github.com/*']
};
const provider2: AuthenticationProviderInformation = {
id: 'microsoft',
label: 'Microsoft',
issuerGlobs: ['https://login.microsoftonline.com/*']
authorizationServerGlobs: ['https://login.microsoftonline.com/*']
};
authenticationService.registerDeclaredAuthenticationProvider(provider1);
authenticationService.registerDeclaredAuthenticationProvider(provider2);
@ -204,20 +204,20 @@ suite('AuthenticationService', () => {
const githubProvider = createProvider({
id: 'github',
label: 'GitHub',
issuers: [URI.parse('https://github.com/different')]
authorizationServers: [URI.parse('https://github.com/different')]
});
authenticationService.registerAuthenticationProvider('github', githubProvider);
const microsoftProvider = createProvider({
id: 'microsoft',
label: 'Microsoft',
issuers: [URI.parse('https://login.microsoftonline.com/common')]
authorizationServers: [URI.parse('https://login.microsoftonline.com/common')]
});
authenticationService.registerAuthenticationProvider('microsoft', microsoftProvider);
// Test with a URI that should match the second provider
const issuer = URI.parse('https://login.microsoftonline.com/common');
const result = await authenticationService.getOrActivateProviderIdForIssuer(issuer);
const authorizationServer = URI.parse('https://login.microsoftonline.com/common');
const result = await authenticationService.getOrActivateProviderIdForServer(authorizationServer);
// Verify the result
assert.strictEqual(result, 'microsoft');
@ -240,7 +240,7 @@ suite('AuthenticationService', () => {
assert.ok(isCalled);
});
test('getSessions - issuer is not registered', async () => {
test('getSessions - authorization server is not registered', async () => {
let isCalled = false;
const provider = createProvider({
getSessions: async () => {
@ -249,7 +249,7 @@ suite('AuthenticationService', () => {
},
});
authenticationService.registerAuthenticationProvider(provider.id, provider);
assert.rejects(() => authenticationService.getSessions(provider.id, [], { issuer: URI.parse('https://example.com') }));
assert.rejects(() => authenticationService.getSessions(provider.id, [], { authorizationServer: URI.parse('https://example.com') }));
assert.ok(!isCalled);
});

View File

@ -6,25 +6,25 @@
declare module 'vscode' {
export interface AuthenticationProviderOptions {
/**
* When specified, this provider will be associated with these issuers. They can still contain globs
* When specified, this provider will be associated with these authorization servers. They can still contain globs
* just like their extension contribution counterparts.
*/
readonly supportedIssuers?: Uri[];
readonly supportedAuthorizationServers?: Uri[];
}
export interface AuthenticationProviderSessionOptions {
/**
* When specified, the authentication provider will use the provided issuer URL to
* authenticate the user. This is only used when a provider `supportsIssuerOverride` is set to true
* When specified, the authentication provider will use the provided authorization server URL to
* authenticate the user. This is only used when a provider has `supportsAuthorizationServers` set
*/
issuer?: Uri;
authorizationServer?: Uri;
}
export interface AuthenticationGetSessionOptions {
/**
* When specified, the authentication provider will use the provided issuer URL to
* authenticate the user. This is only used when a provider `supportsIssuerOverride` is set to true
* When specified, the authentication provider will use the provided authorization server URL to
* authenticate the user. This is only used when a provider has `supportsAuthorizationServers` set
*/
issuer?: Uri;
authorizationServer?: Uri;
}
}