diff --git a/extensions/kusto/package.nls.json b/extensions/kusto/package.nls.json index f139a8b3bd..3f961de959 100644 --- a/extensions/kusto/package.nls.json +++ b/extensions/kusto/package.nls.json @@ -1,20 +1,15 @@ { "notebook.command.new": "New Notebook", "notebook.command.open": "Open Notebook", - "cloud.databaseProperties.name": "Database Name", "cloud.databaseProperties.size": "Size (MB)", - "cloud.serverProperties.summary": "Status", "cloud.serverProperties.machinesTotal": "Total Machines in the cluster", "cloud.serverProperties.diskCacheCapacity": "% of Cluster data capacity used", - "databasesListProperties.name": "Name", "databasesListProperties.size": "Size (MB)", - "objectsListProperties.name": "Name", "objectsListProperties.metadataTypeName": "Type", - "kusto.configuration.title": "KUSTO configuration", "kusto.query.displayBitAsNumber": "Should BIT columns be displayed as numbers (1 or 0)? If false, BIT columns will be displayed as 'true' or 'false'", "kusto.format.alignColumnDefinitionsInColumns": "Should column definitions be aligned?", @@ -22,7 +17,6 @@ "kusto.format.keywordCasing": "Should keywords be formatted as UPPERCASE, lowercase, or none (not formatted)", "kusto.logDebugInfo": "[Optional] Log debug output to the console (View -> Output) and then select appropriate output channel from the dropdown", "kusto.tracingLevel": "[Optional] Log level for backend services. Azure Data Studio generates a file name every time it starts and if the file already exists the logs entries are appended to that file. For cleanup of old log files see logRetentionMinutes and logFilesRemovalLimit settings. The default tracingLevel does not log much. Changing verbosity could lead to extensive logging and disk space requirements for the logs. Error includes Critical, Warning includes Error, Information includes Warning and Verbose includes Information", - "kusto.provider.displayName": "Azure Data Explorer (Kusto)", "kusto.connectionOptions.connectionName.displayName": "Name (optional)", "kusto.connectionOptions.connectionName.description": "Custom name of the connection", diff --git a/src/sql/platform/connection/common/constants.ts b/src/sql/platform/connection/common/constants.ts index ee35e120f8..f44fbc373c 100644 --- a/src/sql/platform/connection/common/constants.ts +++ b/src/sql/platform/connection/common/constants.ts @@ -25,6 +25,7 @@ export const sqlLogin = 'SqlLogin'; export const integrated = 'Integrated'; export const azureMFA = 'AzureMFA'; export const azureMFAAndUser = 'AzureMFAAndUser'; +export const dstsAuth = 'dstsAuth'; /* CMS constants */ export const cmsProviderName = 'MSSQL-CMS'; diff --git a/src/sql/workbench/services/connection/browser/connectionController.ts b/src/sql/workbench/services/connection/browser/connectionController.ts index 659f671050..ac36cf91f8 100644 --- a/src/sql/workbench/services/connection/browser/connectionController.ts +++ b/src/sql/workbench/services/connection/browser/connectionController.ts @@ -47,8 +47,8 @@ export class ConnectionController implements IConnectionComponentController { onCreateNewServerGroup: () => this.onCreateNewServerGroup(), onAdvancedProperties: () => this.handleOnAdvancedProperties(), onSetAzureTimeOut: () => this.handleonSetAzureTimeOut(), - onFetchDatabases: (serverName: string, authenticationType: string, userName?: string, password?: string, azureAccount?: string) => this.onFetchDatabases( - serverName, authenticationType, userName, password, azureAccount).then(result => { + onFetchDatabases: (serverName: string, authenticationType: string, userName?: string, password?: string, authToken?: string) => this.onFetchDatabases( + serverName, authenticationType, userName, password, authToken).then(result => { return result; }), onAzureTenantSelection: (azureTenantId?: string) => this.onAzureTenantSelection(azureTenantId), @@ -56,7 +56,7 @@ export class ConnectionController implements IConnectionComponentController { this._providerName = providerName; } - protected async onFetchDatabases(serverName: string, authenticationType: string, userName?: string, password?: string, azureAccount?: string): Promise { + protected async onFetchDatabases(serverName: string, authenticationType: string, userName?: string, password?: string, authToken?: string): Promise { let tempProfile = this._model; tempProfile.serverName = serverName; tempProfile.authenticationType = authenticationType; @@ -64,7 +64,7 @@ export class ConnectionController implements IConnectionComponentController { tempProfile.password = password; tempProfile.groupFullName = ''; tempProfile.saveProfile = false; - tempProfile.azureAccount = azureAccount; + tempProfile.azureAccount = authToken; let uri = this._connectionManagementService.getConnectionUri(tempProfile); if (this._databaseCache.has(uri)) { let cachedDatabases: string[] = this._databaseCache.get(uri); diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index f3feddd44f..25be32ec55 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -42,7 +42,7 @@ export interface IConnectionComponentCallbacks { onCreateNewServerGroup?: () => void; onAdvancedProperties?: () => void; onSetAzureTimeOut?: () => void; - onFetchDatabases?: (serverName: string, authenticationType: string, userName?: string, password?: string, azureAccount?: string) => Promise; + onFetchDatabases?: (serverName: string, authenticationType: string, userName?: string, password?: string, token?: string) => Promise; onAzureTenantSelection?: (azureTenantId?: string) => void; } diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index ffeb40596a..341cf1bbc5 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -251,7 +251,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti * @param connectionProfile Connection Profile */ public async addSavedPassword(connectionProfile: interfaces.IConnectionProfile): Promise { - await this.fillInOrClearAzureToken(connectionProfile); + await this.fillInOrClearToken(connectionProfile); return this._connectionStore.addSavedPassword(connectionProfile).then(result => result.profile); } @@ -310,7 +310,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti } // Fill in the Azure account token if needed and open the connection dialog if it fails - let tokenFillSuccess = await this.fillInOrClearAzureToken(newConnection); + let tokenFillSuccess = await this.fillInOrClearToken(newConnection); // If the password is required and still not loaded show the dialog if ((!foundPassword && this._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) || !tokenFillSuccess) { @@ -468,7 +468,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti if (callbacks.onConnectStart) { callbacks.onConnectStart(); } - let tokenFillSuccess = await this.fillInOrClearAzureToken(connection); + let tokenFillSuccess = await this.fillInOrClearToken(connection); if (!tokenFillSuccess) { throw new Error(nls.localize('connection.noAzureAccount', "Failed to get Azure account token for connection")); } @@ -803,17 +803,38 @@ export class ConnectionManagementService extends Disposable implements IConnecti } /** - * Fills in the Azure account token if it's needed for this connection and doesn't already have one + * Fills in the account token if it's needed for this connection and doesn't already have one * and clears it if it isn't. * @param connection The connection to fill in or update */ - private async fillInOrClearAzureToken(connection: interfaces.IConnectionProfile): Promise { - if (connection.authenticationType !== Constants.azureMFA && connection.authenticationType !== Constants.azureMFAAndUser) { + private async fillInOrClearToken(connection: interfaces.IConnectionProfile): Promise { + if (connection.authenticationType !== Constants.azureMFA + && connection.authenticationType !== Constants.azureMFAAndUser + && connection.authenticationType !== Constants.dstsAuth) { connection.options['azureAccountToken'] = undefined; return true; } + let azureResource = this.getAzureResourceForConnection(connection); const accounts = await this._accountManagementService.getAccounts(); + + if (connection.authenticationType === Constants.dstsAuth) { + let dstsAccounts = accounts.filter(a => a.key.providerId.startsWith('dstsAuth')); + if (dstsAccounts.length <= 0) { + connection.options['azureAccountToken'] = undefined; + return false; + } + + dstsAccounts[0].key.providerArgs = { + serverName: connection.serverName, + databaseName: connection.databaseName + }; + + let tokenPromise = await this._accountManagementService.getAccountSecurityToken(dstsAccounts[0], undefined, undefined); + connection.options['azureAccountToken'] = tokenPromise.token; + return true; + } + const azureAccounts = accounts.filter(a => a.key.providerId.startsWith('azure')); if (azureAccounts && azureAccounts.length > 0) { let accountId = (connection.authenticationType === Constants.azureMFA || connection.authenticationType === Constants.azureMFAAndUser) ? connection.azureAccount : connection.userName; diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index d28ae54097..a3f6ece013 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -40,7 +40,8 @@ export enum AuthenticationType { SqlLogin = 'SqlLogin', Integrated = 'Integrated', AzureMFA = 'AzureMFA', - AzureMFAAndUser = 'AzureMFAAndUser' + AzureMFAAndUser = 'AzureMFAAndUser', + dSTSAuth = 'dstsAuth' } export class ConnectionWidget extends lifecycle.Disposable { @@ -65,6 +66,7 @@ export class ConnectionWidget extends lifecycle.Disposable { private _defaultDatabaseName: string = localize('defaultDatabaseOption', ""); private _loadingDatabaseName: string = localize('loadingDatabaseOption', "Loading..."); private _serverGroupDisplayString: string = localize('serverGroup', "Server group"); + private _token: string; protected _container: HTMLElement; protected _serverGroupSelectBox: SelectBox; protected _authTypeSelectBox: SelectBox; @@ -75,7 +77,7 @@ export class ConnectionWidget extends lifecycle.Disposable { protected _databaseNameInputBox: Dropdown; protected _advancedButton: Button; private static readonly _authTypes: AuthenticationType[] = - [AuthenticationType.AzureMFA, AuthenticationType.AzureMFAAndUser, AuthenticationType.Integrated, AuthenticationType.SqlLogin]; + [AuthenticationType.AzureMFA, AuthenticationType.AzureMFAAndUser, AuthenticationType.Integrated, AuthenticationType.SqlLogin, AuthenticationType.dSTSAuth]; private static readonly _osByName = { Windows: OperatingSystem.Windows, Macintosh: OperatingSystem.Macintosh, @@ -364,7 +366,7 @@ export class ConnectionWidget extends lifecycle.Disposable { this._databaseDropdownExpanded = true; if (this.serverName) { this._databaseNameInputBox.values = [this._loadingDatabaseName]; - this._callbacks.onFetchDatabases(this.serverName, this.authenticationType, this.userName, this._password, this.azureAccount).then(databases => { + this._callbacks.onFetchDatabases(this.serverName, this.authenticationType, this.userName, this._password, this.authToken).then(databases => { if (databases) { this._databaseNameInputBox.values = databases.sort((a, b) => a.localeCompare(b)); } else { @@ -504,6 +506,22 @@ export class ConnectionWidget extends lifecycle.Disposable { this._tableContainer.classList.remove('hide-username'); this._tableContainer.classList.add('hide-password'); this._tableContainer.classList.remove('hide-azure-accounts'); + } else if (currentAuthType === AuthenticationType.dSTSAuth) { + this._accountManagementService.getAccountsForProvider('dstsAuth').then(accounts => { + if (accounts && accounts.length > 0) { + accounts[0].key.providerArgs = { + serverName: this.serverName, + databaseName: this.databaseName + }; + + this._accountManagementService.getAccountSecurityToken(accounts[0], undefined, undefined).then(securityToken => { + this._token = securityToken.token; + }); + } + }); + this._tableContainer.classList.add('hide-username'); + this._tableContainer.classList.add('hide-password'); + this._tableContainer.classList.add('hide-azure-accounts'); } else { this._azureAccountDropdown.disable(); this._azureAccountDropdown.hideMessage(); @@ -826,8 +844,14 @@ export class ConnectionWidget extends lifecycle.Disposable { return this._authTypeSelectBox ? this.getAuthTypeName(this._authTypeSelectBox.value) : undefined; } - public get azureAccount(): string { - return this.authenticationType === AuthenticationType.AzureMFAAndUser || this.authenticationType === AuthenticationType.AzureMFA ? this._azureAccountDropdown.value : undefined; + public get authToken(): string | undefined { + if (this.authenticationType === AuthenticationType.AzureMFAAndUser || this.authenticationType === AuthenticationType.AzureMFA) { + return this._azureAccountDropdown.value; + } + if (this.authenticationType === AuthenticationType.dSTSAuth) { + return this._token; + } + return undefined; } private validateAzureAccountSelection(showMessage: boolean = true): boolean { @@ -883,7 +907,7 @@ export class ConnectionWidget extends lifecycle.Disposable { model.userName = this.userName; model.password = this.password; model.authenticationType = this.authenticationType; - model.azureAccount = this.azureAccount; + model.azureAccount = this.authToken; model.savePassword = this._rememberPasswordCheckBox.checked; model.connectionName = this.connectionName; model.databaseName = this.databaseName;