提交 650197b9 编写于 作者: R Rachel Macfarlane

Add authentication contribution point, #103507

上级 3bd7c698
......@@ -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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册