Add support for Encrypt=Strict for TDS 8.0 connections with SQL Server 2022 (#21256)

This commit is contained in:
Cheena Malhotra
2023-02-10 10:34:36 -08:00
committed by GitHub
parent c75628639c
commit 3fb8d57d25
13 changed files with 256 additions and 24 deletions

View File

@@ -556,6 +556,38 @@ declare module 'azdata' {
* and not the Advanced Options window.
*/
showOnConnectionDialog?: boolean;
/**
* Used to define list of values based on which another option is rendered visible/hidden.
*/
onSelectionChange?: SelectionChangeEvent[];
}
/**
* This change event defines actions
*/
export interface SelectionChangeEvent {
/**
* Values that affect actions defined in this event.
*/
values: string[];
/**
* Action to be taken on another option when selected value matches to the list of values provided.
*/
dependentOptionActions: DependentOptionAction[];
}
export interface DependentOptionAction {
/**
* Name of option affected by defined action.
*/
optionName: string,
/**
* Action to be taken, Supported values: 'show', 'hide'.
*/
action: string;
}
// Object Explorer interfaces --------------------------------

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Widget } from 'vs/base/browser/ui/widget';
``
/**
* This interface is implemented by SelectBox and InputBox to provide a common API surface area in connectionWidget.ts
* If more widgets must be used in Connection Dialog, they should implement this interface.
*/
export interface AdsWidget extends Widget {
get value(): string;
get id(): string;
getAriaLabel(): string;
enable(): void;
disable(): void;
hideMessage(): void;
}

View File

@@ -7,6 +7,7 @@ import { InputBox as vsInputBox, IInputOptions as vsIInputBoxOptions, IInputBoxS
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Color } from 'vs/base/common/color';
import { Event, Emitter } from 'vs/base/common/event';
import { AdsWidget } from 'sql/base/browser/ui/adsWidget';
export interface OnLoseFocusParams {
value: string;
@@ -29,7 +30,7 @@ export interface IInputOptions extends vsIInputBoxOptions {
ariaDescription?: string;
}
export class InputBox extends vsInputBox {
export class InputBox extends vsInputBox implements AdsWidget {
private enabledInputBackground?: Color;
private enabledInputForeground?: Color;
private enabledInputBorder?: Color;
@@ -48,7 +49,7 @@ export class InputBox extends vsInputBox {
private _isTextAreaInput = false;
private _hideErrors = false;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, private _sqlOptions?: IInputOptions) {
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, private _sqlOptions?: IInputOptions, id?: string) {
super(container, contextViewProvider, _sqlOptions);
this.enabledInputBackground = this.inputBackground;
this.enabledInputForeground = this.inputForeground;
@@ -74,6 +75,7 @@ export class InputBox extends vsInputBox {
if (this._sqlOptions.ariaDescription) {
this.inputElement.setAttribute('aria-description', this._sqlOptions.ariaDescription);
}
this.inputElement.id = id;
}
public override style(styles: IInputBoxStyles): void {
@@ -197,4 +199,8 @@ export class InputBox extends vsInputBox {
this._lastLoseFocusValue = newValue;
super.value = newValue;
}
public get id(): string {
return this.input.id;
}
}

View File

@@ -17,6 +17,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom';
import { Event, Emitter } from 'vs/base/common/event';
import { AdsWidget } from 'sql/base/browser/ui/adsWidget';
const $ = dom.$;
@@ -39,7 +40,7 @@ export interface ISelectBoxStyles extends vsISelectBoxStyles {
inputValidationErrorForeground?: Color;
}
export class SelectBox extends vsSelectBox {
export class SelectBox extends vsSelectBox implements AdsWidget {
private _optionsDictionary: Map<string, number>;
private _dialogOptions: SelectOptionItemSQL[];
private _selectedOption: string;
@@ -67,7 +68,7 @@ export class SelectBox extends vsSelectBox {
private element?: HTMLElement;
constructor(options: SelectOptionItemSQL[] | string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions) {
constructor(options: SelectOptionItemSQL[] | string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions, id?: string) {
let optionItems: SelectOptionItemSQL[] = SelectBox.createOptions(options);
super(optionItems, 0, contextViewProvider, undefined, selectBoxOptions);
@@ -98,6 +99,7 @@ export class SelectBox extends vsSelectBox {
this.element = dom.append(container, $('.monaco-selectbox.idle'));
}
this.selectElement.id = id;
this._selectBoxOptions = selectBoxOptions;
let focusTracker = dom.trackFocus(this.selectElement);
this._register(focusTracker);
@@ -220,7 +222,6 @@ export class SelectBox extends vsSelectBox {
});
}
public override setOptions(options: string[] | SelectOptionItemSQL[] | ISelectOptionItem[], selected?: number): void {
let selectOptions: SelectOptionItemSQL[] = SelectBox.createOptions(options);
this.populateOptionsDictionary(selectOptions);
@@ -255,6 +256,14 @@ export class SelectBox extends vsSelectBox {
this.applyStyles();
}
public getAriaLabel(): string {
return this.selectElem.ariaLabel;
}
public get id(): string {
return this.selectElem.id;
}
public hasFocus(): boolean {
return document.activeElement === this.selectElement;
}

View File

@@ -57,6 +57,20 @@ export enum AuthenticationType {
None = 'None'
}
/*
* Actions for the connection dialog to show/hide connection options.
*/
export enum Actions {
/**
* Shows a connection option
*/
Show = 'show',
/**
* Hides a connection option
*/
Hide = 'hide'
}
/* CMS constants */
export const cmsProviderName = 'MSSQL-CMS';

View File

@@ -214,6 +214,21 @@ suite('SQL ProviderConnectionInfo tests', () => {
assert.strictEqual(conn.options['encrypt'], 'true');
});
test('constructor should initialize the options with encrypt strict', () => {
let options: { [key: string]: string } = {};
options['encrypt'] = 'strict';
let conn2 = Object.assign({}, connectionProfile, { options: options });
let conn = new ProviderConnectionInfo(capabilitiesService, conn2);
assert.strictEqual(conn.connectionName, conn2.connectionName);
assert.strictEqual(conn.serverName, conn2.serverName);
assert.strictEqual(conn.databaseName, conn2.databaseName);
assert.strictEqual(conn.authenticationType, conn2.authenticationType);
assert.strictEqual(conn.password, conn2.password);
assert.strictEqual(conn.userName, conn2.userName);
assert.strictEqual(conn.options['encrypt'], 'strict');
});
test('getOptionsKey should create a valid unique id', () => {
let conn = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
// **IMPORTANT** This should NEVER change without thorough review and consideration of side effects. This key controls

View File

@@ -35,9 +35,11 @@ import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMess
import Severity from 'vs/base/common/severity';
import { ConnectionStringOptions } from 'sql/platform/capabilities/common/capabilitiesService';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { AuthenticationType } from 'sql/platform/connection/common/constants';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { AuthLibrary, filterAccounts } from 'sql/workbench/services/accountManagement/browser/accountDialog';
import { AuthenticationType, Actions } from 'sql/platform/connection/common/constants';
import { AdsWidget } from 'sql/base/browser/ui/adsWidget';
import { createCSSRule } from 'vs/base/browser/dom';
const ConnectionStringText = localize('connectionWidget.connectionString', "Connection string");
@@ -78,7 +80,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
protected _providerName: string;
protected _connectionNameInputBox: InputBox;
protected _databaseNameInputBox: Dropdown;
protected _customOptionWidgets: (InputBox | SelectBox)[];
protected _customOptionWidgets: AdsWidget[];
protected _advancedButton: Button;
private static readonly _authTypes: AuthenticationType[] =
[AuthenticationType.AzureMFA, AuthenticationType.AzureMFAAndUser, AuthenticationType.Integrated, AuthenticationType.SqlLogin, AuthenticationType.DSTSAuth, AuthenticationType.None];
@@ -192,6 +194,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
this.addConnectionNameOptions();
this.addAdvancedOptions();
this.updateRequiredStateForOptions();
this.registerOnSelectionChangeEvents();
if (this._connectionStringOptions.isEnabled) {
// update the UI based on connection string setting after initialization
this.handleConnectionStringOptionChange();
@@ -255,7 +258,8 @@ export class ConnectionWidget extends lifecycle.Disposable {
protected addAuthenticationTypeOption(authTypeChanged: boolean = false): void {
if (this._optionsMaps[ConnectionOptionSpecialType.authType]) {
let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName, 'connection-label', 'connection-input', 'auth-type-row');
let authType = DialogHelper.appendRow(this._tableContainer, this._optionsMaps[ConnectionOptionSpecialType.authType].displayName,
'connection-label', 'connection-input', 'auth-type-row');
DialogHelper.appendInputSelectBox(authType, this._authTypeSelectBox);
}
}
@@ -264,10 +268,12 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (this._customOptions.length > 0) {
this._customOptionWidgets = [];
this._customOptions.forEach((option, i) => {
let customOptionsContainer = DialogHelper.appendRow(this._tableContainer, option.displayName, 'connection-label', 'connection-input', 'custom-connection-options', false, option.description, 100);
let customOptionsContainer = DialogHelper.appendRow(this._tableContainer, option.displayName, 'connection-label', 'connection-input',
['custom-connection-options', `option-${option.name}`], false, option.description, 100);
switch (option.valueType) {
case ServiceOptionType.boolean:
case ServiceOptionType.category:
let selectedValue = option.defaultValue;
let options = option.valueType === ServiceOptionType.category
@@ -282,7 +288,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
return { text: v.displayName, value: v.value } as SelectOptionItemSQL;
});
this._customOptionWidgets[i] = new SelectBox(options, selectedValue, this._contextViewService, customOptionsContainer, { ariaLabel: option.displayName });
this._customOptionWidgets[i] = new SelectBox(options, selectedValue, this._contextViewService, customOptionsContainer, { ariaLabel: option.displayName }, option.name);
DialogHelper.appendInputSelectBox(customOptionsContainer, this._customOptionWidgets[i] as SelectBox);
this._register(styler.attachSelectBoxStyler(this._customOptionWidgets[i] as SelectBox, this._themeService));
break;
@@ -296,6 +302,70 @@ export class ConnectionWidget extends lifecycle.Disposable {
}
}
/**
* Registers on selection change event for connection options configured with 'onSelectionChange' property.
* TODO extend this to include collection of other main and advanced option widgets here.
*/
protected registerOnSelectionChangeEvents(): void {
//Register on selection change event for custom options
this._customOptionWidgets.forEach((widget, i) => {
if (widget instanceof SelectBox) {
this._registerSelectionChangeEvents([this._customOptionWidgets], this._customOptions[i], widget);
}
});
}
private _registerSelectionChangeEvents(collections: AdsWidget[][], option: azdata.ConnectionOption, widget: SelectBox) {
if (option.onSelectionChange) {
option.onSelectionChange.forEach((event) => {
this._register(widget.onDidSelect(value => {
let selectedValue = value.selected;
event?.dependentOptionActions?.forEach((optionAction) => {
let defaultValue: string | undefined = this._customOptions.find(o => o.name === optionAction.optionName)?.defaultValue;
let widget: AdsWidget | undefined = this._findWidget(collections, optionAction.optionName);
if (widget) {
createCSSRule(`.hide-${widget.id} .option-${widget.id}`, `display: none;`);
this._onValueChangeEvent(selectedValue, event.values, widget, defaultValue, optionAction.action);
}
});
}));
});
}
}
/**
* Finds Widget from provided collection of widgets using option name.
* @param collections collections of widgets to search for the widget with the widget Id
* @param id Widget Id
* @returns Widget if found, undefined otherwise
*/
private _findWidget(collections: AdsWidget[][], id: string): AdsWidget | undefined {
let foundWidget: AdsWidget | undefined;
collections.forEach((collection) => {
if (!foundWidget) {
foundWidget = collection.find(widget => widget.id === id);
}
});
return foundWidget;
}
private _onValueChangeEvent(selectedValue: string, acceptedValues: string[],
widget: AdsWidget, defaultValue: string, action: string): void {
if ((acceptedValues.includes(selectedValue.toLocaleLowerCase()) && action === Actions.Show)
|| (!acceptedValues.includes(selectedValue.toLocaleLowerCase()) && action === Actions.Hide)) {
this._tableContainer.classList.remove(`hide-${widget.id}`);
} else {
// Support more Widget classes here as needed.
if (widget instanceof SelectBox) {
widget.select(widget.values.indexOf(defaultValue));
} else if (widget instanceof InputBox) {
widget.value = defaultValue;
}
this._tableContainer.classList.add(`hide-${widget.id}`);
widget.hideMessage();
}
}
protected addServerNameOption(): void {
// Server name
let serverNameOption = this._optionsMaps[ConnectionOptionSpecialType.serverName];
@@ -832,7 +902,7 @@ export class ConnectionWidget extends lifecycle.Disposable {
if (value !== '') {
if (widget instanceof SelectBox) {
widget.selectWithOptionName(value);
} else {
} else if (widget instanceof InputBox) {
widget.value = value;
}
}