mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Initial commit for dSTS Auth (#13802)
* Initial commit for dSTS Auth * Removed getDstsToken from accountManagementService. Renamed azureAccount to token. * Renamed dstsToken to _token in connectionWidget * Code Review Feedback. Renamed token to authToken in onFetchDatabases. * Removed dsts options from Kusto package.json
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<string[]> {
|
||||
protected async onFetchDatabases(serverName: string, authenticationType: string, userName?: string, password?: string, authToken?: string): Promise<string[]> {
|
||||
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);
|
||||
|
||||
@@ -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<string[]>;
|
||||
onFetchDatabases?: (serverName: string, authenticationType: string, userName?: string, password?: string, token?: string) => Promise<string[]>;
|
||||
onAzureTenantSelection?: (azureTenantId?: string) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
* @param connectionProfile Connection Profile
|
||||
*/
|
||||
public async addSavedPassword(connectionProfile: interfaces.IConnectionProfile): Promise<interfaces.IConnectionProfile> {
|
||||
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<boolean> {
|
||||
if (connection.authenticationType !== Constants.azureMFA && connection.authenticationType !== Constants.azureMFAAndUser) {
|
||||
private async fillInOrClearToken(connection: interfaces.IConnectionProfile): Promise<boolean> {
|
||||
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;
|
||||
|
||||
@@ -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', "<Default>");
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user