add securable settings (#22936)

* wip

* Update typings

* nullable

* update test service

* support securables

* updata test data

* fix issues

* fix build failure

* update test mocks

* fix typo

* fix reference

* fix findobjectdialog issue

* update SearchResultItem type

* fix table component perf issue

* hide effective permission for server role

* hide effective permission for app role and db role

* vbump sts and fix a couple issues

* STS update and UI update

* fix user login display issue

* vbump sts
This commit is contained in:
Alan Ren
2023-05-15 15:01:57 -07:00
committed by GitHub
parent 25318a648e
commit b56f2ccb60
21 changed files with 693 additions and 178 deletions

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { AlterApplicationRoleDocUrl, CreateApplicationRoleDocUrl } from '../constants';
import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase';
import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectManagement.ApplicationRoleInfo, ObjectManagement.ApplicationRoleViewInfo> {
export class ApplicationRoleDialog extends PrincipalDialogBase<ObjectManagement.ApplicationRoleInfo, ObjectManagement.ApplicationRoleViewInfo> {
// Sections
private generalSection: azdata.GroupContainer;
private ownedSchemasSection: azdata.GroupContainer;
@@ -25,7 +26,7 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
private ownedSchemaTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
super(objectManagementService, options, true, false);
}
protected override postInitializeData(): void {
@@ -51,10 +52,11 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
return errors;
}
protected async initializeUI(): Promise<void> {
protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection();
this.initializeOwnedSchemasSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection]);
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.securableSection], this.getSectionItemLayout());
}
private initializeGeneralSection(): void {
@@ -84,7 +86,7 @@ export class ApplicationRoleDialog extends ObjectManagementDialogBase<ObjectMana
[localizedConstants.SchemaText],
this.viewInfo.schemas,
this.objectInfo.ownedSchemas,
DefaultMaxTableHeight,
DefaultMaxTableRowCount,
(item) => {
// It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1;

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { AlterDatabaseRoleDocUrl, CreateDatabaseRoleDocUrl } from '../constants';
import { FindObjectDialog } from './findObjectDialog';
import { DefaultMaxTableHeight } from '../../ui/dialogBase';
import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagement.DatabaseRoleInfo, ObjectManagement.DatabaseRoleViewInfo> {
export class DatabaseRoleDialog extends PrincipalDialogBase<ObjectManagement.DatabaseRoleInfo, ObjectManagement.DatabaseRoleViewInfo> {
// Sections
private generalSection: azdata.GroupContainer;
private ownedSchemasSection: azdata.GroupContainer;
@@ -27,18 +28,19 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
private memberTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
super(objectManagementService, options, true, false);
}
protected override get helpUrl(): string {
return this.options.isNewObject ? CreateDatabaseRoleDocUrl : AlterDatabaseRoleDocUrl;
}
protected async initializeUI(): Promise<void> {
protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection();
this.initializeOwnedSchemasSection();
this.initializeMemberSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.memberSection]);
this.formContainer.addItems([this.generalSection, this.ownedSchemasSection, this.memberSection, this.securableSection], this.getSectionItemLayout());
}
private initializeGeneralSection(): void {
@@ -53,9 +55,11 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ApplicationRole, ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User],
selectAllObjectTypes: true,
multiSelect: false,
contextId: this.contextId,
title: localizedConstants.SelectDatabaseRoleOwnerDialogTitle
title: localizedConstants.SelectDatabaseRoleOwnerDialogTitle,
showSchemaColumn: false
});
await dialog.open();
const result = await dialog.waitForClose();
@@ -70,48 +74,45 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
}
private initializeMemberSection(): void {
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [
{
type: azdata.ColumnType.text,
value: localizedConstants.NameText
}
], this.objectInfo.members.map(m => [m]));
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
async () => {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.DatabaseRole, ObjectManagement.NodeType.User],
selectAllObjectTypes: true,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectDatabaseRoleMemberDialogTitle
title: localizedConstants.SelectDatabaseRoleMemberDialogTitle,
showSchemaColumn: false
});
await dialog.open();
const result = await dialog.waitForClose();
this.addMembers(result.selectedObjects.map(r => r.name));
await this.addMembers(result.selectedObjects.map(r => r.name));
},
async () => {
if (this.memberTable.selectedRows.length === 1) {
this.removeMember(this.memberTable.selectedRows[0]);
await this.removeMember(this.memberTable.selectedRows[0]);
}
});
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
}
private addMembers(names: string[]): void {
private async addMembers(names: string[]): Promise<void> {
names.forEach(n => {
if (this.objectInfo.members.indexOf(n) === -1) {
this.objectInfo.members.push(n);
}
});
this.updateMembersTable();
await this.updateMembersTable();
}
private removeMember(idx: number): void {
private async removeMember(idx: number): Promise<void> {
this.objectInfo.members.splice(idx, 1);
this.updateMembersTable();
await this.updateMembersTable();
}
private updateMembersTable(): void {
this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
private async updateMembersTable(): Promise<void> {
await this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
this.onFormFieldChange();
}
@@ -120,7 +121,7 @@ export class DatabaseRoleDialog extends ObjectManagementDialogBase<ObjectManagem
[localizedConstants.SchemaText],
this.viewInfo.schemas,
this.objectInfo.ownedSchemas,
DefaultMaxTableHeight,
DefaultMaxTableRowCount,
(item) => {
// It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1;

View File

@@ -5,15 +5,19 @@
import * as azdata from 'azdata';
import * as mssql from 'mssql';
import { DefaultTableListItemEnabledStateGetter, DefaultMaxTableHeight, DialogBase, TableListItemComparer, TableListItemValueGetter } from '../../ui/dialogBase';
import { DefaultTableListItemEnabledStateGetter, DefaultMaxTableRowCount, DialogBase, TableListItemComparer } from '../../ui/dialogBase';
import * as localizedConstants from '../localizedConstants';
import { getErrorMessage } from '../../utils';
type ObjectType = string | { name: string, displayName: string };
export interface FindObjectDialogOptions {
objectTypes: mssql.ObjectManagement.NodeType[];
objectTypes: ObjectType[];
selectAllObjectTypes: boolean;
multiSelect: boolean;
contextId: string;
title: string;
showSchemaColumn?: boolean;
}
export interface FindObjectDialogResult {
@@ -22,15 +26,10 @@ export interface FindObjectDialogResult {
const ObjectComparer: TableListItemComparer<mssql.ObjectManagement.SearchResultItem> =
(item1, item2) => {
return item1.name === item2.name && item1.type === item2.type;
return item1.name === item2.name && item1.type === item2.type && item1.schema === item2.schema;
};
const ObjectRowValueGetter: TableListItemValueGetter<mssql.ObjectManagement.SearchResultItem> =
(item) => {
return [item.name, localizedConstants.getNodeTypeDisplayName(item.type, true)];
};
const ObjectsTableMaxHeight = 700;
const ObjectsTableMaxRowCount = 20;
export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
private objectTypesTable: azdata.TableComponent;
@@ -38,7 +37,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
private objectsTable: azdata.TableComponent;
private objectsLoadingComponent: azdata.LoadingComponent;
private result: FindObjectDialogResult;
private selectedObjectTypes: string[] = [];
private selectedObjectTypes: ObjectType[] = [];
private allObjects: mssql.ObjectManagement.SearchResultItem[] = [];
constructor(private readonly objectManagementService: mssql.IObjectManagementService, private readonly options: FindObjectDialogOptions) {
@@ -47,40 +46,52 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.result = {
selectedObjects: []
};
this.selectedObjectTypes = [...options.objectTypes];
this.selectedObjectTypes = options.selectAllObjectTypes ? [...options.objectTypes] : [];
}
private getObjectTypeName(objectType: ObjectType): string {
return typeof objectType === 'string' ? objectType : objectType.name;
}
private getObjectTypeDisplayName(objectType: ObjectType): string {
return typeof objectType === 'string' ? localizedConstants.getNodeTypeDisplayName(objectType, true) : objectType.displayName;
}
protected override async initialize(): Promise<void> {
this.dialogObject.okButton.enabled = false;
this.objectTypesTable = this.createTableList<string>(localizedConstants.ObjectTypeText,
this.objectTypesTable = this.createTableList<ObjectType>(localizedConstants.ObjectTypeText,
[localizedConstants.ObjectTypeText],
this.options.objectTypes,
this.selectedObjectTypes,
DefaultMaxTableHeight,
DefaultMaxTableRowCount,
DefaultTableListItemEnabledStateGetter, (item) => {
return [localizedConstants.getNodeTypeDisplayName(item, true)];
return [this.getObjectTypeDisplayName(item)];
}, (item1, item2) => {
return this.getObjectTypeName(item1) === this.getObjectTypeName(item2);
});
this.findButton = this.createButton(localizedConstants.FindText, localizedConstants.FindText, async () => {
await this.onFindObjectButtonClick();
});
}, this.options.selectAllObjectTypes);
const buttonContainer = this.createButtonContainer([this.findButton]);
const objectTypeSection = this.createGroup(localizedConstants.ObjectTypeText, [this.objectTypesTable, buttonContainer]);
const columns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.options.showSchemaColumn) {
columns.splice(1, 0, localizedConstants.SchemaText);
}
if (this.options.multiSelect) {
this.objectsTable = this.createTableList<mssql.ObjectManagement.SearchResultItem>(localizedConstants.ObjectsText,
[localizedConstants.NameText, localizedConstants.ObjectTypeText],
columns,
this.allObjects,
this.result.selectedObjects,
ObjectsTableMaxHeight,
ObjectsTableMaxRowCount,
DefaultTableListItemEnabledStateGetter,
ObjectRowValueGetter,
(item) => {
return this.getObjectRowValue(item);
},
ObjectComparer);
} else {
this.objectsTable = this.createTable(localizedConstants.ObjectsText, [{
value: localizedConstants.NameText,
}, {
value: localizedConstants.ObjectTypeText
}], []);
this.objectsTable = this.createTable(localizedConstants.ObjectsText, columns, [], ObjectsTableMaxRowCount);
this.disposables.push(this.objectsTable.onRowSelected(async () => {
if (this.objectsTable.selectedRows.length > 0) {
this.result.selectedObjects = [this.allObjects[this.objectsTable.selectedRows[0]]];
@@ -95,7 +106,7 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
}).component();
const objectsSection = this.createGroup(localizedConstants.ObjectsText, [this.objectsLoadingComponent]);
this.formContainer.addItems([objectTypeSection, objectsSection]);
this.formContainer.addItems([objectTypeSection, objectsSection], this.getSectionItemLayout());
}
protected override get dialogResult(): FindObjectDialogResult | undefined {
@@ -107,16 +118,18 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.objectsLoadingComponent.loading = true;
this.findButton.enabled = false;
try {
const results = await this.objectManagementService.search(this.options.contextId, <mssql.ObjectManagement.NodeType[]>this.selectedObjectTypes);
const results = await this.objectManagementService.search(this.options.contextId, this.selectedObjectTypes.map(item => this.getObjectTypeName(item)));
this.allObjects.splice(0, this.allObjects.length, ...results);
let data;
if (this.options.multiSelect) {
data = this.getDataForTableList(this.allObjects, this.result.selectedObjects, DefaultTableListItemEnabledStateGetter, ObjectRowValueGetter, ObjectComparer);
data = this.getDataForTableList(this.allObjects, this.result.selectedObjects, DefaultTableListItemEnabledStateGetter, (item) => {
return this.getObjectRowValue(item);
}, ObjectComparer);
}
else {
data = this.allObjects.map(item => ObjectRowValueGetter(item));
data = this.allObjects.map(item => { return this.getObjectRowValue(item); });
}
this.setTableData(this.objectsTable, data, ObjectsTableMaxHeight);
await this.setTableData(this.objectsTable, data, ObjectsTableMaxRowCount);
this.objectsLoadingComponent.loadingCompletedText = localizedConstants.LoadingObjectsCompletedText(results.length);
} catch (err) {
this.dialogObject.message = {
@@ -132,4 +145,12 @@ export class FindObjectDialog extends DialogBase<FindObjectDialogResult> {
this.findButton.enabled = this.selectedObjectTypes.length > 0;
this.dialogObject.okButton.enabled = this.result.selectedObjects.length > 0;
}
private getObjectRowValue(item: mssql.ObjectManagement.SearchResultItem): string[] {
const row = [item.name, this.getObjectTypeName(item.type)];
if (this.options.showSchemaColumn) {
row.splice(1, 0, item.schema);
}
return row;
}
}

View File

@@ -4,15 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as objectManagementLoc from '../localizedConstants';
import * as uiLoc from '../../ui/localizedConstants';
import { AlterLoginDocUrl, CreateLoginDocUrl, PublicServerRoleName } from '../constants';
import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase';
import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Login, ObjectManagement.LoginViewInfo> {
export class LoginDialog extends PrincipalDialogBase<ObjectManagement.Login, ObjectManagement.LoginViewInfo> {
private generalSection: azdata.GroupContainer;
private sqlAuthSection: azdata.GroupContainer;
private serverRoleSection: azdata.GroupContainer;
@@ -34,7 +35,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
private lockedOutCheckbox: azdata.CheckBoxComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
super(objectManagementService, options, false);
}
protected override get helpUrl(): string {
@@ -82,7 +83,8 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
this.objectInfo.password = this.objectInfo.password ?? '';
}
protected async initializeUI(): Promise<void> {
protected override async initializeUI(): Promise<void> {
await super.initializeUI();
const sections: azdata.Component[] = [];
this.initializeGeneralSection();
sections.push(this.generalSection);
@@ -94,12 +96,13 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
this.initializeServerRolesSection();
sections.push(this.serverRoleSection);
sections.push(this.securableSection);
if (this.viewInfo.supportAdvancedOptions) {
this.initializeAdvancedSection();
sections.push(this.advancedSection);
}
this.formContainer.addItems(sections);
this.formContainer.addItems(sections, this.getSectionItemLayout());
}
private initializeGeneralSection(): void {
@@ -203,7 +206,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
items.push(defaultDatabaseContainer, defaultLanguageContainer, this.connectPermissionCheckbox);
}
this.advancedSection = this.createGroup(objectManagementLoc.AdvancedSectionHeader, items);
this.advancedSection = this.createGroup(objectManagementLoc.AdvancedSectionHeader, items, true, true);
}
private initializeServerRolesSection(): void {
@@ -211,7 +214,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
[objectManagementLoc.ServerRoleTypeDisplayNameInTitle],
this.viewInfo.serverRoles,
this.objectInfo.serverRoles,
DefaultMaxTableHeight,
DefaultMaxTableRowCount,
(item) => {
return item !== PublicServerRoleName
});
@@ -220,7 +223,7 @@ export class LoginDialog extends ObjectManagementDialogBase<ObjectManagement.Log
private setViewByAuthenticationType(): void {
if (this.authTypeDropdown.value === objectManagementLoc.SQLAuthenticationTypeDisplayText) {
this.addItem(this.formContainer, this.sqlAuthSection, 1);
this.addItem(this.formContainer, this.sqlAuthSection, this.getSectionItemLayout(), 1);
} else if (this.authTypeDropdown.value !== objectManagementLoc.SQLAuthenticationTypeDisplayText) {
this.removeItem(this.formContainer, this.sqlAuthSection);
}

View File

@@ -0,0 +1,228 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as mssql from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { FindObjectDialog } from './findObjectDialog';
import { deepClone } from '../../util/objects';
import { DefaultTableWidth, getTableHeight } from '../../ui/dialogBase';
const GrantColumnIndex = 2;
const WithGrantColumnIndex = 3;
const DenyColumnIndex = 4;
/**
* Base class for security principal dialogs such as user, role, etc.
*/
export abstract class PrincipalDialogBase<ObjectInfoType extends mssql.ObjectManagement.SecurityPrincipalObject, ViewInfoType extends mssql.ObjectManagement.SecurityPrincipalViewInfo<ObjectInfoType>> extends ObjectManagementDialogBase<ObjectInfoType, ViewInfoType> {
protected securableTable: azdata.TableComponent;
protected permissionTable: azdata.TableComponent;
protected effectivePermissionTable: azdata.TableComponent;
protected securableSection: azdata.GroupContainer;
protected explicitPermissionTableLabel: azdata.TextComponent;
protected effectivePermissionTableLabel: azdata.TextComponent;
private securablePermissions: mssql.ObjectManagement.SecurablePermissions[] = [];
constructor(objectManagementService: mssql.IObjectManagementService, options: ObjectManagementDialogOptions, private readonly showSchemaColumn: boolean, private readonly supportEffectivePermissions: boolean = true) {
super(objectManagementService, options);
}
protected override async initializeUI(): Promise<void> {
this.securablePermissions = deepClone(this.objectInfo.securablePermissions);
this.initializeSecurableSection();
}
private initializeSecurableSection(): void {
const items: azdata.Component[] = [];
const securableTableColumns = [localizedConstants.NameText, localizedConstants.ObjectTypeText];
if (this.showSchemaColumn) {
securableTableColumns.splice(1, 0, localizedConstants.SchemaText);
}
this.securableTable = this.createTable(localizedConstants.SecurablesText, securableTableColumns, this.getSecurableTableData());
const buttonContainer = this.addButtonsForTable(this.securableTable, localizedConstants.AddSecurableAriaLabel, localizedConstants.RemoveSecurableAriaLabel,
() => this.onAddSecurableButtonClicked(), () => this.onRemoveSecurableButtonClicked());
this.disposables.push(this.securableTable.onRowSelected(async () => {
await this.updatePermissionsTable();
}));
this.explicitPermissionTableLabel = this.modelView.modelBuilder.text().withProps({ value: localizedConstants.ExplicitPermissionsTableLabel }).component();
this.permissionTable = this.modelView.modelBuilder.table().withProps({
ariaLabel: localizedConstants.ExplicitPermissionsTableLabel,
columns:
[{
type: azdata.ColumnType.text,
value: localizedConstants.PermissionColumnHeader
}, {
type: azdata.ColumnType.text,
value: localizedConstants.GrantorColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.GrantColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.WithGrantColumnHeader
}, {
type: azdata.ColumnType.checkBox,
value: localizedConstants.DenyColumnHeader
}],
data: [],
height: getTableHeight(0),
width: DefaultTableWidth
}).component();
this.disposables.push(this.permissionTable.onCellAction(async (arg: azdata.ICheckboxCellActionEventArgs) => {
const permissionName = this.permissionTable.data[arg.row][0];
const securable = this.securablePermissions[this.securableTable.selectedRows[0]];
let permission: mssql.ObjectManagement.SecurablePermissionItem = securable.permissions.find(securablePermission => securablePermission.permission === permissionName);
if (!permission) {
permission = {
permission: permissionName,
grantor: ''
};
securable.permissions.push(permission);
}
if (arg.column === GrantColumnIndex) {
permission.grant = arg.checked ? true : undefined;
if (!arg.checked) {
permission.withGrant = undefined;
}
} else if (arg.column === WithGrantColumnIndex) {
permission.withGrant = arg.checked ? true : undefined;
if (arg.checked) {
permission.grant = true;
}
} else if (arg.column === DenyColumnIndex) {
permission.grant = arg.checked ? false : undefined;
if (arg.checked) {
permission.withGrant = undefined;
}
}
await this.updatePermissionsTable();
this.updateSecurablePermissions();
// Restore the focus to previously selected cell.
this.permissionTable.setActiveCell(arg.row, arg.column);
}));
items.push(this.securableTable, buttonContainer, this.explicitPermissionTableLabel, this.permissionTable);
if (this.showEffectivePermissions) {
this.effectivePermissionTableLabel = this.modelView.modelBuilder.text().withProps({ value: localizedConstants.EffectivePermissionsTableLabel }).component();
this.effectivePermissionTable = this.createTable(localizedConstants.EffectivePermissionsTableLabel, [localizedConstants.PermissionColumnHeader], []);
items.push(this.effectivePermissionTableLabel, this.effectivePermissionTable);
}
this.securableSection = this.createGroup(localizedConstants.SecurablesText, items, true, true);
}
private async onAddSecurableButtonClicked(): Promise<void> {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: this.viewInfo.supportedSecurableTypes,
selectAllObjectTypes: false,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectSecurablesDialogTitle,
showSchemaColumn: this.showSchemaColumn
});
await dialog.open();
const result = await dialog.waitForClose();
if (result && result.selectedObjects.length > 0) {
result.selectedObjects.forEach(obj => {
if (this.securablePermissions.find(securable => securable.type === obj.type && securable.name === obj.name && securable.schema === obj.schema)) {
return;
}
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableType => securableType.name === obj.type);
this.securablePermissions.push({
name: obj.name,
schema: obj.schema,
type: obj.type,
permissions: securableTypeMetadata.permissions.map(permission => {
return {
permission: permission.name,
grantor: '',
grant: undefined,
withGrant: undefined
};
}),
effectivePermissions: []
});
});
const data = this.getSecurableTableData();
await this.setTableData(this.securableTable, data);
}
}
private async onRemoveSecurableButtonClicked(): Promise<void> {
if (this.securableTable.selectedRows.length === 1) {
this.securablePermissions.splice(this.securableTable.selectedRows[0], 1);
const data = this.getSecurableTableData();
await this.setTableData(this.securableTable, data);
this.updateSecurablePermissions();
}
}
private getSecurableTableData(): string[][] {
return this.securablePermissions.map(securable => {
const row = [securable.name, this.getSecurableTypeDisplayName(securable.type)];
if (this.showSchemaColumn) {
row.splice(1, 0, securable.schema);
}
return row;
});
}
private async updatePermissionsTable(): Promise<void> {
let permissionsTableData: any[][] = [];
let effectivePermissionsTableData: any[][] = [];
let explicitPermissionsLabel = localizedConstants.ExplicitPermissionsTableLabel;
let effectivePermissionsLabel = localizedConstants.EffectivePermissionsTableLabel;
if (this.securableTable.selectedRows.length === 1) {
const securable = this.securablePermissions[this.securableTable.selectedRows[0]];
if (securable) {
const securableDisplayName = securable.schema ? `${securable.schema}.${securable.name}` : securable.name;
explicitPermissionsLabel = localizedConstants.ExplicitPermissionsTableLabelSelected(securableDisplayName);
effectivePermissionsLabel = localizedConstants.EffectivePermissionsTableLabelSelected(securableDisplayName);
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableType => securableType.name === securable.type);
permissionsTableData = securable.permissions.map(permission => {
return [permission.permission, permission.grantor, { checked: permission.grant === true }, { checked: permission.withGrant === true }, { checked: permission.grant === false }];
});
permissionsTableData = securableTypeMetadata.permissions.map(permissionMetadata => {
const permission = securable.permissions.find(securablePermission => securablePermission.permission === permissionMetadata.name);
return [
permissionMetadata.name,
permission?.grantor ?? '',
{ checked: permission?.grant === true },
{ checked: permission?.withGrant === true },
{ checked: permission?.grant === false }];
});
effectivePermissionsTableData = securable.effectivePermissions.map(permission => [permission]);
}
}
this.explicitPermissionTableLabel.value = explicitPermissionsLabel;
await this.setTableData(this.permissionTable, permissionsTableData);
if (this.showEffectivePermissions) {
this.effectivePermissionTableLabel.value = effectivePermissionsLabel;
await this.setTableData(this.effectivePermissionTable, effectivePermissionsTableData);
}
}
private updateSecurablePermissions(): void {
// Only save securable permissions that have grant or deny value.
this.objectInfo.securablePermissions = deepClone(this.securablePermissions.filter((securablePermissions) => {
return securablePermissions.permissions.some(permission => permission.grant !== undefined);
}));
this.objectInfo.securablePermissions.forEach(securablePermissions => {
securablePermissions.permissions = securablePermissions.permissions.filter(permission => permission.grant !== undefined);
});
this.onFormFieldChange();
}
private getSecurableTypeDisplayName(securableType: string): string {
const securableTypeMetadata = this.viewInfo.supportedSecurableTypes.find(securableTypeMetadata => securableTypeMetadata.name === securableType);
return securableTypeMetadata ? securableTypeMetadata.displayName : securableType;
}
private get showEffectivePermissions(): boolean {
return !this.options.isNewObject && this.supportEffectivePermissions;
}
}

View File

@@ -3,13 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { AlterServerRoleDocUrl, CreateServerRoleDocUrl } from '../constants';
import { FindObjectDialog } from './findObjectDialog';
import { PrincipalDialogBase } from './principalDialogBase';
export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagement.ServerRoleInfo, ObjectManagement.ServerRoleViewInfo> {
export class ServerRoleDialog extends PrincipalDialogBase<ObjectManagement.ServerRoleInfo, ObjectManagement.ServerRoleViewInfo> {
// Sections
private generalSection: azdata.GroupContainer;
private membershipSection: azdata.GroupContainer;
@@ -27,22 +28,24 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
super(objectManagementService, options, false, false);
}
protected override get helpUrl(): string {
return this.options.isNewObject ? CreateServerRoleDocUrl : AlterServerRoleDocUrl;
}
protected async initializeUI(): Promise<void> {
protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection();
this.initializeMemberSection();
const sections: azdata.Component[] = [this.generalSection, this.memberSection];
if (!this.viewInfo.isFixedRole) {
this.initializeMembershipSection();
sections.push(this.membershipSection);
sections.push(this.securableSection);
}
this.formContainer.addItems(sections);
this.formContainer.addItems(sections, this.getSectionItemLayout());
}
private initializeGeneralSection(): void {
@@ -57,6 +60,7 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
const browseOwnerButton = this.createButton(localizedConstants.BrowseText, localizedConstants.BrowseOwnerButtonAriaLabel, async () => {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole],
selectAllObjectTypes: true,
multiSelect: false,
contextId: this.contextId,
title: localizedConstants.SelectServerRoleOwnerDialogTitle
@@ -76,48 +80,44 @@ export class ServerRoleDialog extends ObjectManagementDialogBase<ObjectManagemen
}
private initializeMemberSection(): void {
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [
{
type: azdata.ColumnType.text,
value: localizedConstants.NameText
}
], this.objectInfo.members.map(m => [m]));
this.memberTable = this.createTable(localizedConstants.MemberSectionHeader, [localizedConstants.NameText], this.objectInfo.members.map(m => [m]));
const buttonContainer = this.addButtonsForTable(this.memberTable, localizedConstants.AddMemberAriaLabel, localizedConstants.RemoveMemberAriaLabel,
async () => {
const dialog = new FindObjectDialog(this.objectManagementService, {
objectTypes: [ObjectManagement.NodeType.ServerLevelLogin, ObjectManagement.NodeType.ServerLevelServerRole],
selectAllObjectTypes: true,
multiSelect: true,
contextId: this.contextId,
title: localizedConstants.SelectServerRoleMemberDialogTitle
});
await dialog.open();
const result = await dialog.waitForClose();
this.addMembers(result.selectedObjects.map(r => r.name));
await this.addMembers(result.selectedObjects.map(r => r.name));
},
async () => {
if (this.memberTable.selectedRows.length === 1) {
this.removeMember(this.memberTable.selectedRows[0]);
await this.removeMember(this.memberTable.selectedRows[0]);
}
});
this.memberSection = this.createGroup(localizedConstants.MemberSectionHeader, [this.memberTable, buttonContainer]);
}
private addMembers(names: string[]): void {
private async addMembers(names: string[]): Promise<void> {
names.forEach(n => {
if (this.objectInfo.members.indexOf(n) === -1) {
this.objectInfo.members.push(n);
}
});
this.updateMembersTable();
await this.updateMembersTable();
}
private removeMember(idx: number): void {
private async removeMember(idx: number): Promise<void> {
this.objectInfo.members.splice(idx, 1);
this.updateMembersTable();
await this.updateMembersTable();
}
private updateMembersTable(): void {
this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
private async updateMembersTable(): Promise<void> {
await this.setTableData(this.memberTable, this.objectInfo.members.map(m => [m]));
this.onFormFieldChange();
}

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { ObjectManagementDialogBase, ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { ObjectManagementDialogOptions } from './objectManagementDialogBase';
import { IObjectManagementService, ObjectManagement } from 'mssql';
import * as localizedConstants from '../localizedConstants';
import { AlterUserDocUrl, CreateUserDocUrl } from '../constants';
import { isValidSQLPassword } from '../utils';
import { DefaultMaxTableHeight } from '../../ui/dialogBase';
import { DefaultMaxTableRowCount } from '../../ui/dialogBase';
import { PrincipalDialogBase } from './principalDialogBase';
export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User, ObjectManagement.UserViewInfo> {
export class UserDialog extends PrincipalDialogBase<ObjectManagement.User, ObjectManagement.UserViewInfo> {
private generalSection: azdata.GroupContainer;
private ownedSchemaSection: azdata.GroupContainer;
private membershipSection: azdata.GroupContainer;
@@ -31,7 +32,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
private membershipTable: azdata.TableComponent;
constructor(objectManagementService: IObjectManagementService, options: ObjectManagementDialogOptions) {
super(objectManagementService, options);
super(objectManagementService, options, true);
}
protected override get helpUrl(): string {
@@ -61,12 +62,13 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
return errors;
}
protected async initializeUI(): Promise<void> {
protected override async initializeUI(): Promise<void> {
await super.initializeUI();
this.initializeGeneralSection();
this.initializeOwnedSchemaSection();
this.initializeMembershipSection();
this.initializeAdvancedSection();
this.formContainer.addItems([this.generalSection, this.ownedSchemaSection, this.membershipSection, this.advancedSection]);
this.formContainer.addItems([this.generalSection, this.ownedSchemaSection, this.membershipSection, this.securableSection, this.advancedSection], this.getSectionItemLayout());
setTimeout(() => {
this.setViewByUserType();
}, 100);
@@ -94,7 +96,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
this.loginDropdown = this.createDropdown(localizedConstants.LoginText, async (newValue) => {
this.objectInfo.loginName = newValue;
}, this.viewInfo.logins, this.objectInfo.loginName, this.options.isNewObject);
}, this.options.isNewObject ? this.viewInfo.logins : [this.objectInfo.loginName], this.objectInfo.loginName, this.options.isNewObject);
this.loginContainer = this.createLabelInputContainer(localizedConstants.LoginText, this.loginDropdown);
this.passwordInput = this.createPasswordInputBox(localizedConstants.PasswordText, async (newValue) => {
@@ -119,7 +121,7 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
[localizedConstants.SchemaText],
this.viewInfo.schemas,
this.objectInfo.ownedSchemas,
DefaultMaxTableHeight,
DefaultMaxTableRowCount,
(item) => {
// It is not allowed to have unassigned schema.
return this.objectInfo.ownedSchemas.indexOf(item) === -1;
@@ -157,6 +159,11 @@ export class UserDialog extends ObjectManagementDialogBase<ObjectManagement.User
this.addItem(this.generalSection, this.confirmPasswordContainer);
this.addItem(this.formContainer, this.advancedSection);
break;
case ObjectManagement.UserType.WindowsUser:
if (this.objectInfo.loginName) {
this.addItem(this.generalSection, this.loginContainer);
}
break;
default:
break;
}