mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 10:38:31 -05:00
Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)
* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 * Bump distro * Upgrade GCC to 4.9 due to yarn install errors * Update build image * Fix bootstrap base url * Bump distro * Fix build errors * Update source map file * Disable checkbox for blocking migration issues (#15131) * disable checkbox for blocking issues * wip * disable checkbox fixes * fix strings * Remove duplicate tsec command * Default to off for tab color if settings not present * re-skip failing tests * Fix mocha error * Bump sqlite version & fix notebooks search view * Turn off esbuild warnings * Update esbuild log level * Fix overflowactionbar tests * Fix ts-ignore in dropdown tests * cleanup/fixes * Fix hygiene * Bundle in entire zone.js module * Remove extra constructor param * bump distro for web compile break * bump distro for web compile break v2 * Undo log level change * New distro * Fix integration test scripts * remove the "no yarn.lock changes" workflow * fix scripts v2 * Update unit test scripts * Ensure ads-kerberos2 updates in .vscodeignore * Try fix unit tests * Upload crash reports * remove nogpu * always upload crashes * Use bash script * Consolidate data/ext dir names * Create in tmp directory Co-authored-by: chlafreniere <hichise@gmail.com> Co-authored-by: Christopher Suh <chsuh@microsoft.com> Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
@@ -23,9 +23,64 @@ 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';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
|
||||
export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; }
|
||||
|
||||
export interface IAccountUsage {
|
||||
extensionId: string;
|
||||
extensionName: string;
|
||||
lastUsed: number;
|
||||
}
|
||||
|
||||
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
|
||||
|
||||
export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
const storedUsages = storageService.get(accountKey, StorageScope.GLOBAL);
|
||||
let usages: IAccountUsage[] = [];
|
||||
if (storedUsages) {
|
||||
try {
|
||||
usages = JSON.parse(storedUsages);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return usages;
|
||||
}
|
||||
|
||||
export function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
storageService.remove(accountKey, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
export function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
|
||||
const accountKey = `${providerId}-${accountName}-usages`;
|
||||
const usages = readAccountUsages(storageService, providerId, accountName);
|
||||
|
||||
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
|
||||
if (existingUsageIndex > -1) {
|
||||
usages.splice(existingUsageIndex, 1, {
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
} else {
|
||||
usages.push({
|
||||
extensionId,
|
||||
extensionName,
|
||||
lastUsed: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
export type AuthenticationSessionInfo = { readonly id: string, readonly accessToken: string, readonly providerId: string, readonly canSignOut?: boolean };
|
||||
export async function getCurrentAuthenticationSessionInfo(environmentService: IWorkbenchEnvironmentService, productService: IProductService): Promise<AuthenticationSessionInfo | undefined> {
|
||||
if (environmentService.options?.credentialsProvider) {
|
||||
@@ -53,7 +108,11 @@ export interface IAuthenticationService {
|
||||
getProviderIds(): string[];
|
||||
registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void;
|
||||
unregisterAuthenticationProvider(id: string): void;
|
||||
requestNewSession(id: string, scopes: string[], extensionId: string, extensionName: string): void;
|
||||
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;
|
||||
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;
|
||||
|
||||
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
|
||||
@@ -134,6 +193,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
private _placeholderMenuItem: IDisposable | undefined;
|
||||
private _noAccountsMenuItem: IDisposable | undefined;
|
||||
private _signInRequestItems = new Map<string, SessionRequestInfo>();
|
||||
private _sessionAccessRequestItems = new Map<string, { [extensionId: string]: { disposables: IDisposable[], possibleSessions: AuthenticationSession[] } }>();
|
||||
private _accountBadgeDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
|
||||
@@ -157,7 +217,11 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
|
||||
constructor(
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
||||
) {
|
||||
super();
|
||||
this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
|
||||
@@ -255,6 +319,13 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
this._authenticationProviders.delete(id);
|
||||
this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label });
|
||||
this.updateAccountsMenuItem();
|
||||
|
||||
const accessRequests = this._sessionAccessRequestItems.get(id) || {};
|
||||
Object.keys(accessRequests).forEach(extensionId => {
|
||||
this.removeAccessRequest(id, extensionId);
|
||||
});
|
||||
|
||||
this.updateBadgeCount();
|
||||
}
|
||||
|
||||
if (!this._authenticationProviders.size) {
|
||||
@@ -278,6 +349,12 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
if (event.added) {
|
||||
await this.updateNewSessionRequests(provider);
|
||||
}
|
||||
|
||||
if (event.removed) {
|
||||
await this.updateAccessRequests(id, event.removed);
|
||||
}
|
||||
|
||||
this.updateBadgeCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,12 +365,9 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
}
|
||||
|
||||
const sessions = await provider.getSessions();
|
||||
let changed = false;
|
||||
|
||||
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
|
||||
if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
|
||||
// Request has been completed
|
||||
changed = true;
|
||||
const sessionRequest = existingRequestsForProvider[requestedScopes];
|
||||
sessionRequest?.disposables.forEach(item => item.dispose());
|
||||
|
||||
@@ -305,22 +379,214 @@ export class AuthenticationService extends Disposable implements IAuthentication
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this._accountBadgeDisposable.clear();
|
||||
|
||||
if (this._signInRequestItems.size > 0) {
|
||||
let numberOfRequests = 0;
|
||||
this._signInRequestItems.forEach(providerRequests => {
|
||||
Object.keys(providerRequests).forEach(request => {
|
||||
numberOfRequests += providerRequests[request].requestingExtensionIds.length;
|
||||
});
|
||||
private async updateAccessRequests(providerId: string, removedSessionIds: readonly string[]) {
|
||||
const providerRequests = this._sessionAccessRequestItems.get(providerId);
|
||||
if (providerRequests) {
|
||||
Object.keys(providerRequests).forEach(extensionId => {
|
||||
removedSessionIds.forEach(removedId => {
|
||||
const indexOfSession = providerRequests[extensionId].possibleSessions.findIndex(session => session.id === removedId);
|
||||
if (indexOfSession) {
|
||||
providerRequests[extensionId].possibleSessions.splice(indexOfSession, 1);
|
||||
}
|
||||
});
|
||||
|
||||
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
|
||||
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
|
||||
if (!providerRequests[extensionId].possibleSessions.length) {
|
||||
this.removeAccessRequest(providerId, extensionId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateBadgeCount(): void {
|
||||
this._accountBadgeDisposable.clear();
|
||||
|
||||
let numberOfRequests = 0;
|
||||
this._signInRequestItems.forEach(providerRequests => {
|
||||
Object.keys(providerRequests).forEach(request => {
|
||||
numberOfRequests += providerRequests[request].requestingExtensionIds.length;
|
||||
});
|
||||
});
|
||||
|
||||
this._sessionAccessRequestItems.forEach(accessRequest => {
|
||||
numberOfRequests += Object.keys(accessRequest).length;
|
||||
});
|
||||
|
||||
if (numberOfRequests > 0) {
|
||||
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
|
||||
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
|
||||
}
|
||||
}
|
||||
|
||||
private removeAccessRequest(providerId: string, extensionId: string): void {
|
||||
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
|
||||
if (providerRequests[extensionId]) {
|
||||
providerRequests[extensionId].disposables.forEach(d => d.dispose());
|
||||
delete providerRequests[extensionId];
|
||||
}
|
||||
}
|
||||
|
||||
async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<boolean> {
|
||||
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
|
||||
const extensionData = allowList.find(extension => extension.id === extensionId);
|
||||
if (extensionData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const remoteConnection = this.remoteAgentService.getConnection();
|
||||
const isVSO = remoteConnection !== null
|
||||
? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces')
|
||||
: isWeb;
|
||||
|
||||
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const providerName = this.getLabel(providerId);
|
||||
const { choice } = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName),
|
||||
[nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")],
|
||||
{
|
||||
cancelId: 1
|
||||
}
|
||||
);
|
||||
|
||||
const allow = choice === 0;
|
||||
if (allow) {
|
||||
allowList.push({ id: extensionId, name: extensionName });
|
||||
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
this.removeAccessRequest(providerId, extensionId);
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
async selectSession(providerId: string, extensionId: string, extensionName: string, availableSessions: AuthenticationSession[]): Promise<AuthenticationSession> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// This function should be used only when there are sessions to disambiguate.
|
||||
if (!availableSessions.length) {
|
||||
reject('No available sessions');
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: AuthenticationSession }>();
|
||||
quickPick.ignoreFocusOut = true;
|
||||
const items: { label: string, session?: AuthenticationSession }[] = availableSessions.map(session => {
|
||||
return {
|
||||
label: session.account.label,
|
||||
session: session
|
||||
};
|
||||
});
|
||||
|
||||
items.push({
|
||||
label: nls.localize('useOtherAccount', "Sign in to another account")
|
||||
});
|
||||
|
||||
const providerName = this.getLabel(providerId);
|
||||
|
||||
quickPick.items = items;
|
||||
|
||||
quickPick.title = nls.localize(
|
||||
{
|
||||
key: 'selectAccount',
|
||||
comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.']
|
||||
},
|
||||
"The extension '{0}' wants to access a {1} account",
|
||||
extensionName,
|
||||
providerName);
|
||||
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
|
||||
|
||||
quickPick.onDidAccept(async _ => {
|
||||
const session = quickPick.selectedItems[0].session ?? await this.login(providerId, availableSessions[0].scopes as string[]);
|
||||
const accountName = session.account.label;
|
||||
|
||||
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
|
||||
if (!allowList.find(allowed => allowed.id === extensionId)) {
|
||||
allowList.push({ id: extensionId, name: extensionName });
|
||||
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
this.removeAccessRequest(providerId, extensionId);
|
||||
}
|
||||
|
||||
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
|
||||
quickPick.dispose();
|
||||
resolve(session);
|
||||
});
|
||||
|
||||
quickPick.onDidHide(_ => {
|
||||
if (!quickPick.selectedItems[0]) {
|
||||
reject('User did not consent to account access');
|
||||
}
|
||||
|
||||
quickPick.dispose();
|
||||
});
|
||||
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
async completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string): Promise<void> {
|
||||
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
|
||||
const existingRequest = providerRequests[extensionId];
|
||||
if (!existingRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const possibleSessions = existingRequest.possibleSessions;
|
||||
const supportsMultipleAccounts = this.supportsMultipleAccounts(providerId);
|
||||
|
||||
let session: AuthenticationSession | undefined;
|
||||
if (supportsMultipleAccounts) {
|
||||
try {
|
||||
session = await this.selectSession(providerId, extensionId, extensionName, possibleSessions);
|
||||
} catch (_) {
|
||||
// ignore cancel
|
||||
}
|
||||
} else {
|
||||
const approved = await this.showGetSessionPrompt(providerId, possibleSessions[0].account.label, extensionId, extensionName);
|
||||
if (approved) {
|
||||
session = possibleSessions[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (session) {
|
||||
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
|
||||
const providerName = this.getLabel(providerId);
|
||||
this._onDidChangeSessions.fire({ providerId, label: providerName, event: { added: [], removed: [], changed: [session.id] } });
|
||||
}
|
||||
}
|
||||
|
||||
requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): void {
|
||||
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
|
||||
const hasExistingRequest = providerRequests[extensionId];
|
||||
if (hasExistingRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
|
||||
group: '3_accessRequests',
|
||||
command: {
|
||||
id: `${providerId}${extensionId}Access`,
|
||||
title: nls.localize({
|
||||
key: 'accessRequest',
|
||||
comment: ['The placeholder {0} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count']
|
||||
},
|
||||
"Grant access to {0}... (1)", extensionName)
|
||||
}
|
||||
});
|
||||
|
||||
const accessCommand = CommandsRegistry.registerCommand({
|
||||
id: `${providerId}${extensionId}Access`,
|
||||
handler: async (accessor) => {
|
||||
const authenticationService = accessor.get(IAuthenticationService);
|
||||
authenticationService.completeSessionAccessRequest(providerId, extensionId, extensionName);
|
||||
}
|
||||
});
|
||||
|
||||
providerRequests[extensionId] = { possibleSessions, disposables: [menuItem, accessCommand] };
|
||||
this._sessionAccessRequestItems.set(providerId, providerRequests);
|
||||
this.updateBadgeCount();
|
||||
}
|
||||
|
||||
async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user