提交 4c0811c9 编写于 作者: R Rachel Macfarlane

Removing polling from auth extensions, fixes #107480

上级 c055d391
...@@ -84,6 +84,12 @@ export class Keychain { ...@@ -84,6 +84,12 @@ export class Keychain {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
} }
onDidChangePassword(listener: () => void) {
vscode.authentication.onDidChangePassword(_ => {
listener();
});
}
} }
export const keychain = new Keychain(); export const keychain = new Keychain();
...@@ -33,52 +33,48 @@ export class GitHubAuthenticationProvider { ...@@ -33,52 +33,48 @@ export class GitHubAuthenticationProvider {
// Ignore, network request failed // Ignore, network request failed
} }
this.pollForChange(); keychain.onDidChangePassword(() => this.checkForUpdates());
} }
private pollForChange() { private async checkForUpdates() {
setTimeout(async () => { let storedSessions: vscode.AuthenticationSession[];
let storedSessions: vscode.AuthenticationSession[]; try {
try { storedSessions = await this.readSessions();
storedSessions = await this.readSessions(); } catch (e) {
} catch (e) { // Ignore, network request failed
// Ignore, network request failed return;
return; }
}
const added: string[] = [];
const removed: string[] = [];
storedSessions.forEach(session => { const added: string[] = [];
const matchesExisting = this._sessions.some(s => s.id === session.id); const removed: string[] = [];
// Another window added a session to the keychain, add it to our state as well
if (!matchesExisting) {
Logger.info('Adding session found in keychain');
this._sessions.push(session);
added.push(session.id);
}
});
this._sessions.map(session => {
const matchesExisting = storedSessions.some(s => s.id === session.id);
// Another window has logged out, remove from our state
if (!matchesExisting) {
Logger.info('Removing session no longer found in keychain');
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
if (sessionIndex > -1) {
this._sessions.splice(sessionIndex, 1);
}
removed.push(session.id); storedSessions.forEach(session => {
const matchesExisting = this._sessions.some(s => s.id === session.id);
// Another window added a session to the keychain, add it to our state as well
if (!matchesExisting) {
Logger.info('Adding session found in keychain');
this._sessions.push(session);
added.push(session.id);
}
});
this._sessions.map(session => {
const matchesExisting = storedSessions.some(s => s.id === session.id);
// Another window has logged out, remove from our state
if (!matchesExisting) {
Logger.info('Removing session no longer found in keychain');
const sessionIndex = this._sessions.findIndex(s => s.id === session.id);
if (sessionIndex > -1) {
this._sessions.splice(sessionIndex, 1);
} }
});
if (added.length || removed.length) { removed.push(session.id);
onDidChangeSessions.fire({ added, removed, changed: [] });
} }
});
this.pollForChange(); if (added.length || removed.length) {
}, 1000 * 30); onDidChangeSessions.fire({ added, removed, changed: [] });
}
} }
private async readSessions(): Promise<vscode.AuthenticationSession[]> { private async readSessions(): Promise<vscode.AuthenticationSession[]> {
......
...@@ -140,7 +140,7 @@ export class AzureActiveDirectoryService { ...@@ -140,7 +140,7 @@ export class AzureActiveDirectoryService {
} }
} }
this.pollForChange(); keychain.onDidChangePassword(() => this.checkForUpdates);
} }
private parseStoredData(data: string): IStoredSession[] { private parseStoredData(data: string): IStoredSession[] {
...@@ -160,67 +160,63 @@ export class AzureActiveDirectoryService { ...@@ -160,67 +160,63 @@ export class AzureActiveDirectoryService {
await keychain.setToken(JSON.stringify(serializedData)); await keychain.setToken(JSON.stringify(serializedData));
} }
private pollForChange() { private async checkForUpdates(): Promise<void> {
setTimeout(async () => { const addedIds: string[] = [];
const addedIds: string[] = []; let removedIds: string[] = [];
let removedIds: string[] = []; const storedData = await keychain.getToken();
const storedData = await keychain.getToken(); if (storedData) {
if (storedData) { try {
try { const sessions = this.parseStoredData(storedData);
const sessions = this.parseStoredData(storedData); let promises = sessions.map(async session => {
let promises = sessions.map(async session => { const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id);
const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); if (!matchesExisting && session.refreshToken) {
if (!matchesExisting && session.refreshToken) { try {
try { await this.refreshToken(session.refreshToken, session.scope, session.id);
await this.refreshToken(session.refreshToken, session.scope, session.id); addedIds.push(session.id);
addedIds.push(session.id); } catch (e) {
} catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) {
if (e.message === REFRESH_NETWORK_FAILURE) { // Ignore, will automatically retry on next poll.
// Ignore, will automatically retry on next poll. } else {
} else { await this.logout(session.id);
await this.logout(session.id);
}
} }
} }
}); }
});
promises = promises.concat(this._tokens.map(async token => {
const matchesExisting = sessions.some(session => token.scope === session.scope && token.sessionId === session.id);
if (!matchesExisting) {
await this.logout(token.sessionId);
removedIds.push(token.sessionId);
}
}));
await Promise.all(promises); promises = promises.concat(this._tokens.map(async token => {
} catch (e) { const matchesExisting = sessions.some(session => token.scope === session.scope && token.sessionId === session.id);
Logger.error(e.message); if (!matchesExisting) {
// if data is improperly formatted, remove all of it and send change event await this.logout(token.sessionId);
removedIds = this._tokens.map(token => token.sessionId); removedIds.push(token.sessionId);
this.clearSessions(); }
} }));
} else {
if (this._tokens.length) {
// Log out all, remove all local data
removedIds = this._tokens.map(token => token.sessionId);
Logger.info('No stored keychain data, clearing local data');
this._tokens = []; await Promise.all(promises);
} catch (e) {
Logger.error(e.message);
// if data is improperly formatted, remove all of it and send change event
removedIds = this._tokens.map(token => token.sessionId);
this.clearSessions();
}
} else {
if (this._tokens.length) {
// Log out all, remove all local data
removedIds = this._tokens.map(token => token.sessionId);
Logger.info('No stored keychain data, clearing local data');
this._refreshTimeouts.forEach(timeout => { this._tokens = [];
clearTimeout(timeout);
});
this._refreshTimeouts.clear(); this._refreshTimeouts.forEach(timeout => {
} clearTimeout(timeout);
} });
if (addedIds.length || removedIds.length) { this._refreshTimeouts.clear();
onDidChangeSessions.fire({ added: addedIds, removed: removedIds, changed: [] });
} }
}
this.pollForChange(); if (addedIds.length || removedIds.length) {
}, 1000 * 30); onDidChangeSessions.fire({ added: addedIds, removed: removedIds, changed: [] });
}
} }
private async convertToSession(token: IToken): Promise<vscode.AuthenticationSession> { private async convertToSession(token: IToken): Promise<vscode.AuthenticationSession> {
......
...@@ -101,6 +101,12 @@ export class Keychain { ...@@ -101,6 +101,12 @@ export class Keychain {
return Promise.resolve(null); return Promise.resolve(null);
} }
} }
onDidChangePassword(listener: () => void) {
vscode.authentication.onDidChangePassword(_ => {
listener();
});
}
} }
export const keychain = new Keychain(); export const keychain = new Keychain();
...@@ -149,4 +149,5 @@ export interface ICommonNativeHostService { ...@@ -149,4 +149,5 @@ export interface ICommonNativeHostService {
deletePassword(service: string, account: string): Promise<boolean>; deletePassword(service: string, account: string): Promise<boolean>;
findPassword(service: string): Promise<string | null>; findPassword(service: string): Promise<string | null>;
findCredentials(service: string): Promise<Array<{ account: string, password: string }>> findCredentials(service: string): Promise<Array<{ account: string, password: string }>>
readonly onDidChangePassword: Event<void>;
} }
...@@ -83,6 +83,9 @@ export class NativeHostMainService implements INativeHostMainService { ...@@ -83,6 +83,9 @@ export class NativeHostMainService implements INativeHostMainService {
private readonly _onColorSchemeChange = new Emitter<IColorScheme>(); private readonly _onColorSchemeChange = new Emitter<IColorScheme>();
readonly onColorSchemeChange = this._onColorSchemeChange.event; readonly onColorSchemeChange = this._onColorSchemeChange.event;
private readonly _onDidChangePassword = new Emitter<void>();
readonly onDidChangePassword = this._onDidChangePassword.event;
//#endregion //#endregion
//#region Window //#region Window
...@@ -632,14 +635,18 @@ export class NativeHostMainService implements INativeHostMainService { ...@@ -632,14 +635,18 @@ export class NativeHostMainService implements INativeHostMainService {
async setPassword(windowId: number | undefined, service: string, account: string, password: string): Promise<void> { async setPassword(windowId: number | undefined, service: string, account: string, password: string): Promise<void> {
const keytar = await import('keytar'); const keytar = await import('keytar');
await keytar.setPassword(service, account, password);
return keytar.setPassword(service, account, password); this._onDidChangePassword.fire();
} }
async deletePassword(windowId: number | undefined, service: string, account: string): Promise<boolean> { async deletePassword(windowId: number | undefined, service: string, account: string): Promise<boolean> {
const keytar = await import('keytar'); const keytar = await import('keytar');
const didDelete = await keytar.deletePassword(service, account);
if (didDelete) {
return keytar.deletePassword(service, account); this._onDidChangePassword.fire();
}
return didDelete;
} }
async findPassword(windowId: number | undefined, service: string): Promise<string | null> { async findPassword(windowId: number | undefined, service: string): Promise<string | null> {
......
...@@ -163,6 +163,11 @@ declare module 'vscode' { ...@@ -163,6 +163,11 @@ declare module 'vscode' {
* @param key The key the password was stored under. * @param key The key the password was stored under.
*/ */
export function deletePassword(key: string): Thenable<void>; export function deletePassword(key: string): Thenable<void>;
/**
* Fires when a password is set or deleted.
*/
export const onDidChangePassword: Event<void>;
} }
//#endregion //#endregion
......
...@@ -248,6 +248,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu ...@@ -248,6 +248,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this._register(this.authenticationService.onDidChangeDeclaredProviders(e => { this._register(this.authenticationService.onDidChangeDeclaredProviders(e => {
this._proxy.$setProviders(e); this._proxy.$setProviders(e);
})); }));
this._register(this.credentialsService.onDidChangePassword(_ => {
this._proxy.$onDidChangePassword();
}));
} }
$getProviderIds(): Promise<string[]> { $getProviderIds(): Promise<string[]> {
......
...@@ -233,6 +233,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ...@@ -233,6 +233,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}, },
deletePassword(key: string): Thenable<void> { deletePassword(key: string): Thenable<void> {
return extHostAuthentication.deletePassword(extension, key); return extHostAuthentication.deletePassword(extension, key);
},
get onDidChangePassword(): Event<void> {
return extHostAuthentication.onDidChangePassword;
} }
}; };
......
...@@ -1082,6 +1082,7 @@ export interface ExtHostAuthenticationShape { ...@@ -1082,6 +1082,7 @@ export interface ExtHostAuthenticationShape {
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>; $onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;
$onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>; $onDidChangeAuthenticationProviders(added: modes.AuthenticationProviderInformation[], removed: modes.AuthenticationProviderInformation[]): Promise<void>;
$setProviders(providers: modes.AuthenticationProviderInformation[]): Promise<void>; $setProviders(providers: modes.AuthenticationProviderInformation[]): Promise<void>;
$onDidChangePassword(): Promise<void>;
} }
export interface ExtHostSearchShape { export interface ExtHostSearchShape {
......
...@@ -24,6 +24,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { ...@@ -24,6 +24,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>(); private _onDidChangeSessions = new Emitter<vscode.AuthenticationSessionsChangeEvent>();
readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event; readonly onDidChangeSessions: Event<vscode.AuthenticationSessionsChangeEvent> = this._onDidChangeSessions.event;
private _onDidChangePassword = new Emitter<void>();
readonly onDidChangePassword: Event<void> = this._onDidChangePassword.event;
constructor(mainContext: IMainContext) { constructor(mainContext: IMainContext) {
this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication); this._proxy = mainContext.getProxy(MainContext.MainThreadAuthentication);
} }
...@@ -204,6 +208,11 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { ...@@ -204,6 +208,11 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
return Promise.resolve(); return Promise.resolve();
} }
$onDidChangePassword(): Promise<void> {
this._onDidChangePassword.fire();
return Promise.resolve();
}
getPassword(requestingExtension: IExtensionDescription, key: string): Promise<string | undefined> { getPassword(requestingExtension: IExtensionDescription, key: string): Promise<string | undefined> {
const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier); const extensionId = ExtensionIdentifier.toKey(requestingExtension.identifier);
return this._proxy.$getPassword(extensionId, key); return this._proxy.$getPassword(extensionId, key);
......
...@@ -6,11 +6,15 @@ ...@@ -6,11 +6,15 @@
import { ICredentialsService, ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials'; import { ICredentialsService, ICredentialsProvider } from 'vs/workbench/services/credentials/common/credentials';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Emitter } from 'vs/base/common/event';
export class BrowserCredentialsService implements ICredentialsService { export class BrowserCredentialsService implements ICredentialsService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
private _onDidChangePassword: Emitter<void> = new Emitter();
onDidChangePassword = this._onDidChangePassword.event;
private credentialsProvider: ICredentialsProvider; private credentialsProvider: ICredentialsProvider;
constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) {
...@@ -25,12 +29,18 @@ export class BrowserCredentialsService implements ICredentialsService { ...@@ -25,12 +29,18 @@ export class BrowserCredentialsService implements ICredentialsService {
return this.credentialsProvider.getPassword(service, account); return this.credentialsProvider.getPassword(service, account);
} }
setPassword(service: string, account: string, password: string): Promise<void> { async setPassword(service: string, account: string, password: string): Promise<void> {
return this.credentialsProvider.setPassword(service, account, password); await this.credentialsProvider.setPassword(service, account, password);
this._onDidChangePassword.fire();
} }
deletePassword(service: string, account: string): Promise<boolean> { deletePassword(service: string, account: string): Promise<boolean> {
return this.credentialsProvider.deletePassword(service, account); const didDelete = this.credentialsProvider.deletePassword(service, account);
if (didDelete) {
this._onDidChangePassword.fire();
}
return didDelete;
} }
findPassword(service: string): Promise<string | null> { findPassword(service: string): Promise<string | null> {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ICredentialsService = createDecorator<ICredentialsService>('credentialsService'); export const ICredentialsService = createDecorator<ICredentialsService>('credentialsService');
...@@ -17,4 +18,5 @@ export interface ICredentialsProvider { ...@@ -17,4 +18,5 @@ export interface ICredentialsProvider {
export interface ICredentialsService extends ICredentialsProvider { export interface ICredentialsService extends ICredentialsProvider {
readonly _serviceBrand: undefined; readonly _serviceBrand: undefined;
onDidChangePassword: Event<void>;
} }
...@@ -6,12 +6,18 @@ ...@@ -6,12 +6,18 @@
import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Emitter } from 'vs/base/common/event';
export class KeytarCredentialsService implements ICredentialsService { export class KeytarCredentialsService implements ICredentialsService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
constructor(@INativeHostService private readonly nativeHostService: INativeHostService) { } private _onDidChangePassword: Emitter<void> = new Emitter();
onDidChangePassword = this._onDidChangePassword.event;
constructor(@INativeHostService private readonly nativeHostService: INativeHostService) {
this.nativeHostService.onDidChangePassword(event => this._onDidChangePassword.fire(event));
}
getPassword(service: string, account: string): Promise<string | null> { getPassword(service: string, account: string): Promise<string | null> {
return this.nativeHostService.getPassword(service, account); return this.nativeHostService.getPassword(service, account);
......
...@@ -167,6 +167,7 @@ export class TestNativeHostService implements INativeHostService { ...@@ -167,6 +167,7 @@ export class TestNativeHostService implements INativeHostService {
onWindowBlur: Event<number> = Event.None; onWindowBlur: Event<number> = Event.None;
onOSResume: Event<unknown> = Event.None; onOSResume: Event<unknown> = Event.None;
onColorSchemeChange = Event.None; onColorSchemeChange = Event.None;
onDidChangePassword = Event.None;
windowCount = Promise.resolve(1); windowCount = Promise.resolve(1);
getWindowCount(): Promise<number> { return this.windowCount; } getWindowCount(): Promise<number> { return this.windowCount; }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册