mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
[Azure Arc] Update MIAA model to include encryption properties (#21702)
This commit is contained in:
@@ -190,3 +190,7 @@ export namespace cssStyles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const iconSize = '20px';
|
export const iconSize = '20px';
|
||||||
|
|
||||||
|
export const encryptOption = 'encrypt';
|
||||||
|
export const trustServerCertificateOption = 'trustServerCertificate';
|
||||||
|
export const encryptReadMoreLink = 'https://learn.microsoft.com/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine';
|
||||||
|
|||||||
@@ -142,12 +142,21 @@ export const controllerPassword = localize('arc.controllerPassword', "Controller
|
|||||||
export const username = localize('arc.username', "Username");
|
export const username = localize('arc.username', "Username");
|
||||||
export const password = localize('arc.password', "Password");
|
export const password = localize('arc.password', "Password");
|
||||||
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
||||||
|
export const encrypt = localize('arc.encrypt', "Encrypt");
|
||||||
|
export const encryptDescription = localize('arc.encryptDescription', "When true, SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed.");
|
||||||
|
export const trustServerCertificate = localize('arc.trustServerCertificate', "Trust Server Certificate");
|
||||||
|
export const trustServerCertDescription = localize('arc.trustServerCertDescription', "When true (and encrypt=true), SQL Server uses SSL encryption for all data sent between the client and server without validating the server certificate.");
|
||||||
|
export const enableTrustServerCert = localize('arc.enableTrustServerCert', "Enable Trust Server Certificate");
|
||||||
|
export const msgPromptSSLCertificateValidationFailed = localize('arc.msgPromptSSLCertificateValidationFailed', 'Encryption was enabled on this connection, review your SSL and certificate configuration for the target SQL Server, or set \'Trust server certificate\' to \'true\' in the settings file. Note: A self-signed certificate offers only limited protection and is not a recommended practice for production environments. Do you want to enable \'Trust server certificate\' on this connection and retry?');
|
||||||
export const connect = localize('arc.connect', "Connect");
|
export const connect = localize('arc.connect', "Connect");
|
||||||
|
export const readMore = localize('arc.readMore', "Read more");
|
||||||
export const cancel = localize('arc.cancel', "Cancel");
|
export const cancel = localize('arc.cancel', "Cancel");
|
||||||
export const apply = localize('arc.apply', "Apply");
|
export const apply = localize('arc.apply', "Apply");
|
||||||
export const ok = localize('arc.ok', "Ok");
|
export const ok = localize('arc.ok', "Ok");
|
||||||
export const on = localize('arc.on', "On");
|
export const on = localize('arc.on', "On");
|
||||||
export const off = localize('arc.off', "Off");
|
export const off = localize('arc.off', "Off");
|
||||||
|
export const booleantrue = localize('arc.booleantrue', "True");
|
||||||
|
export const booleanfalse = localize('arc.booleanfalse', "False");
|
||||||
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
||||||
|
|
||||||
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
||||||
|
|||||||
@@ -219,7 +219,10 @@ export class MiaaModel extends ResourceModel {
|
|||||||
saveProfile: true,
|
saveProfile: true,
|
||||||
id: '',
|
id: '',
|
||||||
groupId: undefined,
|
groupId: undefined,
|
||||||
options: {}
|
options: {
|
||||||
|
encrypt: this._miaaInfo.encrypt || true,
|
||||||
|
trustServerCertificate: this._miaaInfo.trustServerCertificate || false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +243,8 @@ export class MiaaModel extends ResourceModel {
|
|||||||
this._activeConnectionId = connectionProfile.id;
|
this._activeConnectionId = connectionProfile.id;
|
||||||
this.info.connectionId = connectionProfile.id;
|
this.info.connectionId = connectionProfile.id;
|
||||||
this._miaaInfo.userName = connectionProfile.userName;
|
this._miaaInfo.userName = connectionProfile.userName;
|
||||||
|
this._miaaInfo.encrypt = connectionProfile.options.encrypt;
|
||||||
|
this._miaaInfo.trustServerCertificate = connectionProfile.options.trustServerCertificate;
|
||||||
await this._treeDataProvider.saveControllers();
|
await this._treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +275,5 @@ export class MiaaModel extends ResourceModel {
|
|||||||
this._databaseTimeWindow.set(dbName, ['', '']);
|
this._databaseTimeWindow.set(dbName, ['', '']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
extensions/arc/src/typings/arc.d.ts
vendored
4
extensions/arc/src/typings/arc.d.ts
vendored
@@ -20,7 +20,9 @@ declare module 'arc' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type MiaaResourceInfo = ResourceInfo & {
|
export type MiaaResourceInfo = ResourceInfo & {
|
||||||
userName?: string
|
userName?: string,
|
||||||
|
encrypt?: string,
|
||||||
|
trustServerCertificate?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PGResourceInfo = ResourceInfo & {
|
export type PGResourceInfo = ResourceInfo & {
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ import * as vscode from 'vscode';
|
|||||||
import { Deferred } from '../../common/promise';
|
import { Deferred } from '../../common/promise';
|
||||||
import * as loc from '../../localizedConstants';
|
import * as loc from '../../localizedConstants';
|
||||||
import { createCredentialId } from '../../common/utils';
|
import { createCredentialId } from '../../common/utils';
|
||||||
import { credentialNamespace } from '../../constants';
|
import * as constants from '../../constants';
|
||||||
import { InitializingComponent } from '../components/initializingComponent';
|
import { InitializingComponent } from '../components/initializingComponent';
|
||||||
import { ResourceModel } from '../../models/resourceModel';
|
import { ResourceModel } from '../../models/resourceModel';
|
||||||
import { ControllerModel } from '../../models/controllerModel';
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
|
|
||||||
|
export interface IReconnectAction {
|
||||||
|
(profile: azdata.IConnectionProfile): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class ConnectToSqlDialog extends InitializingComponent {
|
export abstract class ConnectToSqlDialog extends InitializingComponent {
|
||||||
protected modelBuilder!: azdata.ModelBuilder;
|
protected modelBuilder!: azdata.ModelBuilder;
|
||||||
|
|
||||||
@@ -20,6 +24,9 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
protected usernameInputBox!: azdata.InputBoxComponent;
|
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||||
protected passwordInputBox!: azdata.InputBoxComponent;
|
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||||
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
protected encryptSelectBox!: azdata.DropDownComponent;
|
||||||
|
protected trustServerCertificateSelectBox!: azdata.DropDownComponent;
|
||||||
|
|
||||||
private options: { [name: string]: any } = {};
|
private options: { [name: string]: any } = {};
|
||||||
|
|
||||||
protected _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
|
protected _completionPromise = new Deferred<azdata.IConnectionProfile | undefined>();
|
||||||
@@ -29,7 +36,11 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public showDialog(dialogTitle: string, connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
|
public showDialog(dialogTitle: string, connectionProfile?: azdata.IConnectionProfile): azdata.window.Dialog {
|
||||||
const dialog = azdata.window.createModelViewDialog(dialogTitle);
|
const dialog = azdata.window.createModelViewDialog(dialogTitle, undefined, 'narrow');
|
||||||
|
const trueCategory: azdata.CategoryValue = { displayName: loc.booleantrue, name: 'true' }
|
||||||
|
const falseCategory: azdata.CategoryValue = { displayName: loc.booleanfalse, name: 'false' }
|
||||||
|
const booleanCategoryValues: azdata.CategoryValue[] = [trueCategory, falseCategory];
|
||||||
|
|
||||||
dialog.cancelButton.onClick(() => this.handleCancel());
|
dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
dialog.registerContent(async view => {
|
dialog.registerContent(async view => {
|
||||||
this.modelBuilder = view.modelBuilder;
|
this.modelBuilder = view.modelBuilder;
|
||||||
@@ -47,13 +58,22 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
.withProps({
|
.withProps({
|
||||||
inputType: 'password',
|
inputType: 'password',
|
||||||
value: connectionProfile?.password
|
value: connectionProfile?.password
|
||||||
})
|
}).component();
|
||||||
.component();
|
|
||||||
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||||
.withProps({
|
.withProps({
|
||||||
label: loc.rememberPassword,
|
label: loc.rememberPassword,
|
||||||
checked: connectionProfile?.savePassword
|
checked: connectionProfile?.savePassword
|
||||||
}).component();
|
}).component();
|
||||||
|
this.encryptSelectBox = this.modelBuilder.dropDown()
|
||||||
|
.withProps({
|
||||||
|
values: booleanCategoryValues,
|
||||||
|
value: connectionProfile?.options[constants.encryptOption] ? trueCategory : falseCategory
|
||||||
|
}).component();
|
||||||
|
this.trustServerCertificateSelectBox = this.modelBuilder.dropDown()
|
||||||
|
.withProps({
|
||||||
|
values: booleanCategoryValues,
|
||||||
|
value: connectionProfile?.options[constants.trustServerCertificateOption] ? trueCategory : falseCategory
|
||||||
|
}).component();
|
||||||
|
|
||||||
let formModel = this.modelBuilder.formContainer()
|
let formModel = this.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
@@ -73,6 +93,18 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
}, {
|
}, {
|
||||||
component: this.rememberPwCheckBox,
|
component: this.rememberPwCheckBox,
|
||||||
title: ''
|
title: ''
|
||||||
|
}, {
|
||||||
|
component: this.encryptSelectBox,
|
||||||
|
title: loc.encrypt,
|
||||||
|
layout: {
|
||||||
|
info: loc.encryptDescription,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
component: this.trustServerCertificateSelectBox,
|
||||||
|
title: loc.trustServerCertificate,
|
||||||
|
layout: {
|
||||||
|
info: loc.trustServerCertDescription,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
title: ''
|
title: ''
|
||||||
@@ -94,6 +126,10 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
if (!this.serverNameInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
if (!this.serverNameInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.options.encrypt = this.encryptSelectBox.value;
|
||||||
|
this.options.trustServerCertificate = this.trustServerCertificateSelectBox.value;
|
||||||
|
|
||||||
const connectionProfile: azdata.IConnectionProfile = {
|
const connectionProfile: azdata.IConnectionProfile = {
|
||||||
serverName: this.serverNameInputBox.value,
|
serverName: this.serverNameInputBox.value,
|
||||||
databaseName: '',
|
databaseName: '',
|
||||||
@@ -109,10 +145,15 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
groupId: undefined,
|
groupId: undefined,
|
||||||
options: this.options
|
options: this.options
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return await this.connect(connectionProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async connect(connectionProfile: azdata.IConnectionProfile): Promise<boolean> {
|
||||||
const result = await azdata.connection.connect(connectionProfile, false, false);
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
if (result.connected) {
|
if (result.connected) {
|
||||||
connectionProfile.id = result.connectionId!;
|
connectionProfile.id = result.connectionId!;
|
||||||
const credentialProvider = await azdata.credentials.getProvider(credentialNamespace);
|
const credentialProvider = await azdata.credentials.getProvider(constants.credentialNamespace);
|
||||||
if (connectionProfile.savePassword) {
|
if (connectionProfile.savePassword) {
|
||||||
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._model.info.resourceType, this._model.info.name), connectionProfile.password);
|
await credentialProvider.saveCredential(createCredentialId(this._controllerModel.info.id, this._model.info.resourceType, this._model.info.name), connectionProfile.password);
|
||||||
} else {
|
} else {
|
||||||
@@ -123,10 +164,40 @@ export abstract class ConnectToSqlDialog extends InitializingComponent {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
vscode.window.showErrorMessage(this.connectionFailedMessage(result.errorMessage));
|
vscode.window.showErrorMessage(this.connectionFailedMessage(result.errorMessage));
|
||||||
|
// Show error with instructions for MSSQL Provider Encryption error code -2146893019 thrown by SqlClient when certificate validation fails.
|
||||||
|
if (result.errorCode === -2146893019) {
|
||||||
|
return this.showInstructionTextAsWarning(connectionProfile, async updatedConnection => {
|
||||||
|
return await this.connect(updatedConnection);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async showInstructionTextAsWarning(profile: azdata.IConnectionProfile, reconnectAction: IReconnectAction): Promise<boolean> {
|
||||||
|
while (true) {
|
||||||
|
const selection = await vscode.window.showWarningMessage(
|
||||||
|
loc.msgPromptSSLCertificateValidationFailed,
|
||||||
|
{ modal: false },
|
||||||
|
...[
|
||||||
|
loc.enableTrustServerCert,
|
||||||
|
loc.readMore,
|
||||||
|
loc.cancel
|
||||||
|
]);
|
||||||
|
if (selection === loc.enableTrustServerCert) {
|
||||||
|
profile.options.encrypt = true;
|
||||||
|
profile.options.trustServerCertificate = true;
|
||||||
|
return await reconnectAction(profile);
|
||||||
|
} else if (selection === loc.readMore) {
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse(constants.encryptReadMoreLink));
|
||||||
|
// Show the dialog again so the user can still pick yes or no after they've read the docs
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
protected abstract get providerName(): string;
|
protected abstract get providerName(): string;
|
||||||
|
|
||||||
protected abstract connectionFailedMessage(error: any): string;
|
protected abstract connectionFailedMessage(error: any): string;
|
||||||
|
|||||||
@@ -110,10 +110,15 @@ export class ControllerTreeNode extends TreeNode {
|
|||||||
node = new PostgresTreeNode(postgresModel, this.model);
|
node = new PostgresTreeNode(postgresModel, this.model);
|
||||||
break;
|
break;
|
||||||
case ResourceType.sqlManagedInstances:
|
case ResourceType.sqlManagedInstances:
|
||||||
// Fill in the username too if we already have it
|
// Fill in the username and connection properties too if we already have them
|
||||||
(resourceInfo as MiaaResourceInfo).userName = (this.model.info.resources.find(info =>
|
let miaaResourceInfo = this.model.info.resources.find(info =>
|
||||||
info.name === resourceInfo.name &&
|
info.name === resourceInfo.name &&
|
||||||
info.resourceType === resourceInfo.resourceType) as MiaaResourceInfo)?.userName;
|
info.resourceType === resourceInfo.resourceType) as MiaaResourceInfo;
|
||||||
|
if (miaaResourceInfo) {
|
||||||
|
(resourceInfo as MiaaResourceInfo).userName = miaaResourceInfo.userName;
|
||||||
|
(resourceInfo as MiaaResourceInfo).encrypt = miaaResourceInfo.encrypt;
|
||||||
|
(resourceInfo as MiaaResourceInfo).trustServerCertificate = miaaResourceInfo.trustServerCertificate;
|
||||||
|
}
|
||||||
const miaaModel = new MiaaModel(this.model, resourceInfo, registration, this._treeDataProvider);
|
const miaaModel = new MiaaModel(this.model, resourceInfo, registration, this._treeDataProvider);
|
||||||
node = new MiaaTreeNode(miaaModel, this.model);
|
node = new MiaaTreeNode(miaaModel, this.model);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user