mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Add authentication contribution point, #103507
This commit is contained in:
parent
3bd7c6981e
commit
650197b991
|
@ -17,7 +17,6 @@
|
|||
"web"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*",
|
||||
"onAuthenticationRequest:github"
|
||||
],
|
||||
"contributes": {
|
||||
|
@ -34,7 +33,13 @@
|
|||
"when": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"authentication": [
|
||||
{
|
||||
"label": "GitHub",
|
||||
"id": "github"
|
||||
}
|
||||
]
|
||||
},
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"main": "./out/extension.js",
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
],
|
||||
"enableProposedApi": true,
|
||||
"activationEvents": [
|
||||
"*",
|
||||
"onAuthenticationRequest:microsoft"
|
||||
],
|
||||
"extensionKind": [
|
||||
|
@ -20,6 +19,14 @@
|
|||
"workspace",
|
||||
"web"
|
||||
],
|
||||
"contributes": {
|
||||
"authentication": [
|
||||
{
|
||||
"label": "Microsoft",
|
||||
"id": "microsoft"
|
||||
}
|
||||
]
|
||||
},
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"main": "./out/extension.js",
|
||||
"browser": "./dist/browser/extension.js",
|
||||
|
|
|
@ -108,6 +108,11 @@ export interface ICodeActionContribution {
|
|||
readonly actions: readonly ICodeActionContributionAction[];
|
||||
}
|
||||
|
||||
export interface IAuthenticationContribution {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface IExtensionContributions {
|
||||
commands?: ICommand[];
|
||||
configuration?: IConfiguration | IConfiguration[];
|
||||
|
@ -126,6 +131,7 @@ export interface IExtensionContributions {
|
|||
localizations?: ILocalization[];
|
||||
readonly customEditors?: readonly IWebviewEditor[];
|
||||
readonly codeActions?: readonly ICodeActionContribution[];
|
||||
authentication?: IAuthenticationContribution[];
|
||||
}
|
||||
|
||||
export type ExtensionKind = 'ui' | 'workspace' | 'web';
|
||||
|
|
|
@ -232,6 +232,12 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
|
|||
this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(info => {
|
||||
this._proxy.$onDidChangeAuthenticationProviders([], [info]);
|
||||
}));
|
||||
|
||||
this._proxy.$setProviders(this.authenticationService.declaredProviders);
|
||||
|
||||
this._register(this.authenticationService.onDidChangeDeclaredProviders(e => {
|
||||
this._proxy.$setProviders(e);
|
||||
}));
|
||||
}
|
||||
|
||||
$getProviderIds(): Promise<string[]> {
|
||||
|
|
|
@ -1055,6 +1055,7 @@ export interface ExtHostAuthenticationShape {
|
|||
$logout(id: string, sessionId: string): Promise<void>;
|
||||
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
|
||||
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
$setProviders(providers: modes.AuthenticationProviderInformation[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
|
|
|
@ -28,6 +28,11 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
|
|||
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
|
||||
}
|
||||
|
||||
$setProviders(providers: vscode.AuthenticationProviderInformation[]): Promise<void> {
|
||||
this._providers = providers;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getProviderIds(): Promise<ReadonlyArray<string>> {
|
||||
return this._proxy.$getProviderIds();
|
||||
}
|
||||
|
|
|
@ -904,6 +904,7 @@ export class ExtensionEditor extends EditorPane {
|
|||
this.renderViews(content, manifest, layout),
|
||||
this.renderLocalizations(content, manifest, layout),
|
||||
this.renderCustomEditors(content, manifest, layout),
|
||||
this.renderAuthentication(content, manifest, layout),
|
||||
];
|
||||
|
||||
scrollableContent.scanDomNode();
|
||||
|
@ -1151,6 +1152,32 @@ export class ExtensionEditor extends EditorPane {
|
|||
return true;
|
||||
}
|
||||
|
||||
private renderAuthentication(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
|
||||
const authentication = manifest.contributes?.authentication || [];
|
||||
if (!authentication.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const details = $('details', { open: true, ontoggle: onDetailsToggle },
|
||||
$('summary', { tabindex: '0' }, localize('authentication', "Authentication ({0})", authentication.length)),
|
||||
$('table', undefined,
|
||||
$('tr', undefined,
|
||||
$('th', undefined, localize('authentication.label', "Label")),
|
||||
$('th', undefined, localize('authentication.id', "Id"))
|
||||
),
|
||||
...authentication.map(action =>
|
||||
$('tr', undefined,
|
||||
$('td', undefined, action.label),
|
||||
$('td', undefined, action.id)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
append(container, details);
|
||||
return true;
|
||||
}
|
||||
|
||||
private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
|
||||
const contrib = manifest.contributes?.themes || [];
|
||||
if (!contrib.length) {
|
||||
|
|
|
@ -18,6 +18,11 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
|||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; }
|
||||
|
||||
|
@ -55,6 +60,10 @@ export interface IAuthenticationService {
|
|||
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
|
||||
|
||||
readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>;
|
||||
|
||||
declaredProviders: AuthenticationProviderInformation[];
|
||||
readonly onDidChangeDeclaredProviders: Event<AuthenticationProviderInformation[]>;
|
||||
|
||||
getSessions(providerId: string): Promise<ReadonlyArray<AuthenticationSession>>;
|
||||
getLabel(providerId: string): string;
|
||||
supportsMultipleAccounts(providerId: string): boolean;
|
||||
|
@ -96,6 +105,30 @@ CommandsRegistry.registerCommand('workbench.getCodeExchangeProxyEndpoints', func
|
|||
return environmentService.options?.codeExchangeProxyEndpoints;
|
||||
});
|
||||
|
||||
const authenticationDefinitionSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: nls.localize('authentication.id', 'The id of the authentication provider.')
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: nls.localize('authentication.label', 'The human readable name of the authentication provider.'),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint<AuthenticationProviderInformation[]>({
|
||||
extensionPoint: 'authentication',
|
||||
jsonSchema: {
|
||||
description: nls.localize('authenticationExtensionPoint', 'Contributes authentication'),
|
||||
type: 'array',
|
||||
items: authenticationDefinitionSchema
|
||||
}
|
||||
});
|
||||
|
||||
export class AuthenticationService extends Disposable implements IAuthenticationService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private _placeholderMenuItem: IDisposable | undefined;
|
||||
|
@ -105,6 +138,11 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
|
||||
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
|
||||
|
||||
/**
|
||||
* All providers that have been statically declared by extensions. These may not be registered.
|
||||
*/
|
||||
declaredProviders: AuthenticationProviderInformation[] = [];
|
||||
|
||||
private _onDidRegisterAuthenticationProvider: Emitter<AuthenticationProviderInformation> = this._register(new Emitter<AuthenticationProviderInformation>());
|
||||
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this._onDidRegisterAuthenticationProvider.event;
|
||||
|
||||
|
@ -114,7 +152,13 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
private _onDidChangeSessions: Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>());
|
||||
readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event;
|
||||
|
||||
constructor(@IActivityService private readonly activityService: IActivityService) {
|
||||
private _onDidChangeDeclaredProviders: Emitter<AuthenticationProviderInformation[]> = this._register(new Emitter<AuthenticationProviderInformation[]>());
|
||||
readonly onDidChangeDeclaredProviders: Event<AuthenticationProviderInformation[]> = this._onDidChangeDeclaredProviders.event;
|
||||
|
||||
constructor(
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
) {
|
||||
super();
|
||||
this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
|
||||
command: {
|
||||
|
@ -123,6 +167,38 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
precondition: ContextKeyExpr.false()
|
||||
},
|
||||
});
|
||||
|
||||
authenticationExtPoint.setHandler((extensions, { added, removed }) => {
|
||||
added.forEach(point => {
|
||||
for (const provider of point.value) {
|
||||
if (isFalsyOrWhitespace(provider.id)) {
|
||||
point.collector.error(nls.localize('authentication.missingId', 'An authentication contribution must specify an id.'));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFalsyOrWhitespace(provider.label)) {
|
||||
point.collector.error(nls.localize('authentication.missingLabel', 'An authentication contribution must specify a label.'));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.declaredProviders.some(p => p.id === provider.id)) {
|
||||
this.declaredProviders.push(provider);
|
||||
} else {
|
||||
point.collector.error(nls.localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removedExtPoints = flatten(removed.map(r => r.value));
|
||||
removedExtPoints.forEach(point => {
|
||||
const index = this.declaredProviders.findIndex(provider => provider.id === point.id);
|
||||
if (index > -1) {
|
||||
this.declaredProviders.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
this._onDidChangeDeclaredProviders.fire(this.declaredProviders);
|
||||
});
|
||||
}
|
||||
|
||||
getProviderIds(): string[] {
|
||||
|
@ -339,11 +415,11 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
}
|
||||
}
|
||||
getLabel(id: string): string {
|
||||
const authProvider = this._authenticationProviders.get(id);
|
||||
const authProvider = this.declaredProviders.find(provider => provider.id === id);
|
||||
if (authProvider) {
|
||||
return authProvider.label;
|
||||
} else {
|
||||
throw new Error(`No authentication provider '${id}' is currently registered.`);
|
||||
throw new Error(`No authentication provider '${id}' has been declared.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,6 +433,8 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
}
|
||||
|
||||
async getSessions(id: string): Promise<ReadonlyArray<AuthenticationSession>> {
|
||||
await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id));
|
||||
|
||||
const authProvider = this._authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return await authProvider.getSessions();
|
||||
|
@ -366,6 +444,8 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
|||
}
|
||||
|
||||
async login(id: string, scopes: string[]): Promise<AuthenticationSession> {
|
||||
await this.extensionService.activateByEvent(getAuthenticationProviderActivationEvent(id));
|
||||
|
||||
const authProvider = this._authenticationProviders.get(id);
|
||||
if (authProvider) {
|
||||
return authProvider.login(scopes);
|
||||
|
|
Loading…
Reference in a new issue