Pass scopes through to authentication providers

This commit is contained in:
Rachel Macfarlane 2021-02-11 12:28:17 -08:00
parent a7758e4328
commit 650906c369
14 changed files with 82 additions and 39 deletions

View file

@ -71,3 +71,25 @@ export async function promiseFromEvent<T, U>(
}
);
}
export function arrayEquals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArray<T> | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
if (one === other) {
return true;
}
if (!one || !other) {
return false;
}
if (one.length !== other.length) {
return false;
}
for (let i = 0, len = one.length; i < len; i++) {
if (!itemEquals(one[i], other[i])) {
return false;
}
}
return true;
}

View file

@ -24,7 +24,8 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('github', 'GitHub', {
onDidChangeSessions: onDidChangeSessions.event,
getSessions: () => Promise.resolve(loginService.sessions),
getAllSessions: () => Promise.resolve(loginService.sessions),
getSessions: (scopes: string[]) => loginService.getSessions(scopes),
login: async (scopeList: string[]) => {
try {
/* __GDPR__

View file

@ -8,6 +8,7 @@ import { v4 as uuid } from 'uuid';
import { Keychain } from './common/keychain';
import { GitHubServer, NETWORK_ERROR } from './githubServer';
import Logger from './common/logger';
import { arrayEquals } from './common/utils';
export const onDidChangeSessions = new vscode.EventEmitter<vscode.AuthenticationProviderAuthenticationSessionsChangeEvent>();
@ -43,6 +44,10 @@ export class GitHubAuthenticationProvider {
context.subscriptions.push(context.secrets.onDidChange(() => this.checkForUpdates()));
}
async getSessions(scopes: string[]): Promise<vscode.AuthenticationSession[]> {
return this._sessions.filter(session => arrayEquals(session.scopes, scopes));
}
private async verifySessions(): Promise<void> {
const verifiedSessions: vscode.AuthenticationSession[] = [];
const verificationPromises = this._sessions.map(async session => {

View file

@ -300,6 +300,12 @@ export class AzureActiveDirectoryService {
return Promise.all(this._tokens.map(token => this.convertToSession(token)));
}
async getSessions(scopes: string[]): Promise<vscode.AuthenticationSession[]> {
const orderedScopes = scopes.sort().join(' ');
const matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
return Promise.all(this._tokens.map(token => this.convertToSession(token)));
}
public async login(scope: string): Promise<vscode.AuthenticationSession> {
Logger.info('Logging in...');
if (!scope.includes('offline_access')) {

View file

@ -20,7 +20,8 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', {
onDidChangeSessions: onDidChangeSessions.event,
getSessions: () => Promise.resolve(loginService.sessions),
getAllSessions: () => Promise.resolve(loginService.sessions),
getSessions: (scopes: string[]) => loginService.getSessions(scopes),
login: async (scopes: string[]) => {
try {
/* __GDPR__

View file

@ -65,9 +65,18 @@ declare module 'vscode' {
/**
* Returns an array of current sessions.
*
* TODO @RMacfarlane finish deprecating this and remove it
*/
// eslint-disable-next-line vscode-dts-provider-naming
getSessions(): Thenable<ReadonlyArray<AuthenticationSession>>;
getAllSessions(): Thenable<ReadonlyArray<AuthenticationSession>>;
/**
* Returns an array of current sessions.
*/
// eslint-disable-next-line vscode-dts-provider-naming
getSessions(scopes: string[]): Thenable<ReadonlyArray<AuthenticationSession>>;
/**
* Prompts a user to login.

View file

@ -19,7 +19,6 @@ import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensi
export class MainThreadAuthenticationProvider extends Disposable {
private _accounts = new Map<string, string[]>(); // Map account name to session ids
private _sessions = new Map<string, string>(); // Map account id to name
private _hasInitializedSessions = false;
@ -35,10 +34,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
) {
super();
}
public hasSessions(): boolean {
return !!this._sessions.size;
}
public manageTrustedExtensions(accountName: string) {
const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName);
@ -81,8 +76,6 @@ export class MainThreadAuthenticationProvider extends Disposable {
}
private registerSession(session: modes.AuthenticationSession) {
this._sessions.set(session.id, session.account.label);
const existingSessionsForAccount = this._accounts.get(session.account.label);
if (existingSessionsForAccount) {
this._accounts.set(session.account.label, existingSessionsForAccount.concat(session.id));
@ -110,8 +103,12 @@ export class MainThreadAuthenticationProvider extends Disposable {
}
}
async getSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
const sessions = await this._proxy.$getSessions(this.id);
async getSessions(scopes: string[]) {
return this._proxy.$getSessions(this.id, scopes);
}
async getAllSessions(): Promise<ReadonlyArray<modes.AuthenticationSession>> {
const sessions = await this._proxy.$getAllSessions(this.id);
if (!this._hasInitializedSessions) {
sessions.forEach(session => this.registerSession(session));
this._hasInitializedSessions = true;
@ -125,12 +122,11 @@ export class MainThreadAuthenticationProvider extends Disposable {
removed.forEach(session => {
const sessionId = session.id;
const accountName = this._sessions.get(sessionId);
const accountName = session.account.label;
if (accountName) {
this._sessions.delete(sessionId);
let sessionsForAccount = this._accounts.get(accountName) || [];
const sessionIndex = sessionsForAccount.indexOf(sessionId);
sessionsForAccount.splice(sessionIndex);
sessionsForAccount.splice(sessionIndex, 1);
if (!sessionsForAccount.length) {
this._accounts.delete(accountName);
@ -230,7 +226,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
private async selectSession(providerId: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
private async selectSession(providerId: string, extensionId: string, extensionName: string, potentialSessions: readonly modes.AuthenticationSession[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
@ -259,8 +255,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.authenticationService.getSessions(providerId, true)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const sessions = await this.authenticationService.getSessions(providerId, scopes, true);
const silent = !options.createIfNone;
let session: modes.AuthenticationSession | undefined;

View file

@ -1126,8 +1126,8 @@ export interface ExtHostLabelServiceShape {
}
export interface ExtHostAuthenticationShape {
$getSessions(id: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
$getSessionAccessToken(id: string, sessionId: string): Promise<string>;
$getAllSessions(id: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
$getSessions(id: string, scopes: string[]): Promise<ReadonlyArray<modes.AuthenticationSession>>;
$login(id: string, scopes: string[]): Promise<modes.AuthenticationSession>;
$logout(id: string, sessionId: string): Promise<void>;
$onDidChangeAuthenticationSessions(id: string, label: string, event: modes.AuthenticationSessionsChangeEvent): Promise<void>;

View file

@ -147,25 +147,19 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
}
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
$getAllSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
const providerData = this._authenticationProviders.get(providerId);
if (providerData) {
return Promise.resolve(providerData.provider.getSessions());
return Promise.resolve(providerData.provider.getAllSessions());
}
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);
}
async $getSessionAccessToken(providerId: string, sessionId: string): Promise<string> {
$getSessions(providerId: string, scopes: string[]): Promise<ReadonlyArray<modes.AuthenticationSession>> {
const providerData = this._authenticationProviders.get(providerId);
if (providerData) {
const sessions = await providerData.provider.getSessions();
const session = sessions.find(session => session.id === sessionId);
if (session) {
return session.accessToken;
}
throw new Error(`Unable to find session with id: ${sessionId}`);
return Promise.resolve(providerData.provider.getSessions(scopes));
}
throw new Error(`Unable to find authentication provider with handle: ${providerId}`);

View file

@ -210,7 +210,7 @@ export class AccountsActivityActionViewItem extends MenuActivityActionViewItem {
const providers = this.authenticationService.getProviderIds();
const allSessions = providers.map(async providerId => {
try {
const sessions = await this.authenticationService.getSessions(providerId);
const sessions = await this.authenticationService.getAllSessions(providerId);
const groupedSessions: { [label: string]: AuthenticationSession[]; } = {};
sessions.forEach(session => {

View file

@ -54,7 +54,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
};
});
const experiments = await this.experimentService.getCurrentExperiments();
const githubSessions = await this.authenticationService.getSessions('github');
const githubSessions = await this.authenticationService.getAllSessions('github');
const potentialSessions = githubSessions.filter(session => session.scopes.includes('repo'));
const theme = this.themeService.getColorTheme();
const issueReporterData: IssueReporterData = Object.assign({

View file

@ -206,7 +206,7 @@ export async function readWorkspaceTrustedDomains(accessor: ServicesAccessor): P
export async function readAuthenticationTrustedDomains(accessor: ServicesAccessor): Promise<string[]> {
const authenticationService = accessor.get(IAuthenticationService);
return authenticationService.isAuthenticationProviderRegistered('github') && ((await authenticationService.getSessions('github')) ?? []).length > 0
return authenticationService.isAuthenticationProviderRegistered('github') && ((await authenticationService.getAllSessions('github')) ?? []).length > 0
? [`https://github.com`]
: [];
}

View file

@ -110,8 +110,8 @@ export interface IAuthenticationService {
unregisterAuthenticationProvider(id: string): void;
isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean;
showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<boolean>;
selectSession(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): Promise<AuthenticationSession>;
requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): void;
selectSession(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): Promise<AuthenticationSession>;
requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: readonly AuthenticationSession[]): void;
completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string): Promise<void>
requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void;
@ -124,7 +124,8 @@ export interface IAuthenticationService {
declaredProviders: AuthenticationProviderInformation[];
readonly onDidChangeDeclaredProviders: Event<AuthenticationProviderInformation[]>;
getSessions(providerId: string, activateImmediate?: boolean): Promise<ReadonlyArray<AuthenticationSession>>;
getSessions(id: string, scopes: string[], activateImmediate?: boolean): Promise<ReadonlyArray<AuthenticationSession>>;
getAllSessions(providerId: string, activateImmediate?: boolean): Promise<ReadonlyArray<AuthenticationSession>>;
getLabel(providerId: string): string;
supportsMultipleAccounts(providerId: string): boolean;
login(providerId: string, scopes: string[], activateImmediate?: boolean): Promise<AuthenticationSession>;
@ -694,10 +695,19 @@ export class AuthenticationService extends Disposable implements IAuthentication
return Promise.race([didRegister, didTimeout]);
}
async getSessions(id: string, activateImmediate: boolean = false): Promise<ReadonlyArray<AuthenticationSession>> {
async getAllSessions(id: string, activateImmediate: boolean = false): Promise<ReadonlyArray<AuthenticationSession>> {
try {
const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, activateImmediate);
return await authProvider.getSessions();
return await authProvider.getAllSessions();
} catch (_) {
throw new Error(`No authentication provider '${id}' is currently registered.`);
}
}
async getSessions(id: string, scopes: string[], activateImmediate: boolean = false): Promise<ReadonlyArray<AuthenticationSession>> {
try {
const authProvider = this._authenticationProviders.get(id) || await this.tryActivateProvider(id, activateImmediate);
return await authProvider.getSessions(scopes);
} catch (_) {
throw new Error(`No authentication provider '${id}' is currently registered.`);
}

View file

@ -202,7 +202,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
let accounts: Map<string, UserDataSyncAccount> = new Map<string, UserDataSyncAccount>();
let currentAccount: UserDataSyncAccount | null = null;
const sessions = await this.authenticationService.getSessions(authenticationProviderId) || [];
const sessions = await this.authenticationService.getAllSessions(authenticationProviderId) || [];
for (const session of sessions) {
const account: UserDataSyncAccount = new UserDataSyncAccount(authenticationProviderId, session);
accounts.set(account.accountName, account);