From 35aa8d42de2eec0a8c81827dc79042155ee5da62 Mon Sep 17 00:00:00 2001 From: Hai Cao Date: Fri, 28 Apr 2023 07:56:15 -0700 Subject: [PATCH] [User Management] Implement handlers for app role, db role and server role (#2030) * contracts * more contracts * wire up * add server role and rename data classes * finish app role * finish db role * build * finish server role * register * docs and dispose * build * fix issues * fix null ref issue * fix schema not being set issue * fix password for app role --------- Co-authored-by: Alan Ren --- .../ObjectManagementService.cs | 5 +- .../ObjectTypes/AppRole/AppRoleHandler.cs | 208 ++++ .../ObjectTypes/AppRole/AppRoleInfo.cs | 18 + .../ObjectTypes/AppRole/AppRoleViewContext.cs | 31 + .../ObjectTypes/AppRole/AppRoleViewInfo.cs | 15 + .../DatabaseRole/DatabaseRoleHandler.cs | 207 ++++ .../DatabaseRole/DatabaseRoleInfo.cs | 18 + .../DatabaseRole/DatabaseRoleViewContext.cs | 30 + .../DatabaseRole/DatabaseRoleViewInfo.cs | 15 + .../ObjectTypes/Security/AppRoleActions.cs | 38 + .../ObjectTypes/Security/AppRoleData.cs | 636 +++++++++++++ .../ObjectTypes/Security/AppRoleGeneral.cs | 653 ------------- .../Security/DatabaseRoleActions.cs | 38 + .../ObjectTypes/Security/DatabaseRoleData.cs | 662 +++++++++++++ .../Security/DatabaseRoleGeneral.cs | 890 ------------------ .../Security/ExtenedPropertyInfo.cs | 16 + .../ObjectTypes/Security/ServerRoleActions.cs | 38 + .../ObjectTypes/Security/ServerRoleData.cs | 499 ++++++++++ .../ServerRoleManageTaskFormComponent.cs | 327 ------- .../ServerRole/ServerRoleHandler.cs | 157 +++ .../ObjectTypes/ServerRole/ServerRoleInfo.cs | 17 + .../ServerRole/ServerRoleViewContext.cs | 18 + .../ServerRole/ServerRoleViewInfo.cs | 16 + .../ObjectManagement/SqlObjectType.cs | 6 + 24 files changed, 2687 insertions(+), 1871 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleHandler.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleHandler.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleActions.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleData.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleActions.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleData.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ExtenedPropertyInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleActions.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleData.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleHandler.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewInfo.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs index 3aca8955..c4ed6e20 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs @@ -33,6 +33,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement this.objectTypeHandlers.Add(new LoginHandler(ConnectionService.Instance)); this.objectTypeHandlers.Add(new UserHandler(ConnectionService.Instance)); this.objectTypeHandlers.Add(new CredentialHandler(ConnectionService.Instance)); + this.objectTypeHandlers.Add(new AppRoleHandler(ConnectionService.Instance)); + this.objectTypeHandlers.Add(new DatabaseRoleHandler(ConnectionService.Instance)); + this.objectTypeHandlers.Add(new ServerRoleHandler(ConnectionService.Instance)); } /// @@ -121,7 +124,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement return handler; } } - throw new NotSupportedException(objectType.ToString()); + throw new NotSupportedException($"No handler found for object type '{objectType.ToString()}'"); } private SqlObjectViewContext GetContext(string contextId) diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleHandler.cs new file mode 100644 index 00000000..e7d4f39b --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleHandler.cs @@ -0,0 +1,208 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.Management; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// AppRole object type handler + /// + public class AppRoleHandler : ObjectTypeHandler + { + public AppRoleHandler(ConnectionService connectionService) : base(connectionService) { } + + public override bool CanHandleType(SqlObjectType objectType) + { + return objectType == SqlObjectType.ApplicationRole; + } + + public override async Task InitializeObjectView(InitializeViewRequestParams parameters) + { + // check input parameters + if (string.IsNullOrWhiteSpace(parameters.Database)) + { + throw new ArgumentNullException("parameters.Database"); + } + + // open a connection for running the AppRole associated tasks + ConnectionInfo originalConnInfo; + this.ConnectionService.TryFindConnection(parameters.ConnectionUri, out originalConnInfo); + if (originalConnInfo == null) + { + throw new ArgumentException("Invalid connection URI '{0}'", parameters.ConnectionUri); + } + string originalDatabaseName = originalConnInfo.ConnectionDetails.DatabaseName; + try + { + originalConnInfo.ConnectionDetails.DatabaseName = parameters.Database; + ConnectParams connectParams = new ConnectParams + { + OwnerUri = parameters.ContextId, + Connection = originalConnInfo.ConnectionDetails, + Type = Connection.ConnectionType.Default + }; + await this.ConnectionService.Connect(connectParams); + } + finally + { + originalConnInfo.ConnectionDetails.DatabaseName = originalDatabaseName; + } + + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(parameters.ContextId, out connInfo); + + CDataContainer dataContainer = CreateAppRoleDataContainer(connInfo, null, ConfigAction.Create, parameters.Database); + + AppRolePrototype prototype = parameters.IsNewObject + ? new AppRolePrototype(dataContainer, parameters.Database) + : new AppRolePrototype(dataContainer, parameters.Database, dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as ApplicationRole); + + AppRoleInfo appRoleInfo = new AppRoleInfo() + { + Name = prototype.Name, + DefaultSchema = prototype.DefaultSchema, + Password = prototype.Password, + ExtendedProperties = prototype.ExtendedProperties.Select( + item => new ExtendedPropertyInfo() + { + Name = item.Key, + Value = item.Value + }).ToArray(), + OwnedSchemas = prototype.SchemasOwned, + }; + + var viewInfo = new AppRoleViewInfo() + { + ObjectInfo = appRoleInfo, + Schemas = prototype.Schemas + }; + + var context = new AppRoleViewContext(parameters, dataContainer.ServerConnection); + return new InitializeViewResult() + { + ViewInfo = viewInfo, + Context = context + }; + } + + public override Task Save(AppRoleViewContext context, AppRoleInfo obj) + { + if (context.Parameters.IsNewObject) + { + this.DoHandleCreateAppRoleRequest(context, obj, RunType.RunNow); + } + else + { + this.DoHandleUpdateAppRoleRequest(context, obj, RunType.RunNow); + } + return Task.CompletedTask; + } + + public override Task Script(AppRoleViewContext context, AppRoleInfo obj) + { + string script; + if (context.Parameters.IsNewObject) + { + script = this.DoHandleCreateAppRoleRequest(context, obj, RunType.ScriptToWindow); + } + else + { + script = this.DoHandleUpdateAppRoleRequest(context, obj, RunType.ScriptToWindow); + } + return Task.FromResult(script); + } + + private string ConfigureAppRole(CDataContainer dataContainer, ConfigAction configAction, RunType runType, AppRolePrototype prototype) + { + string sqlScript = string.Empty; + using (var actions = new AppRoleActions(dataContainer, configAction, prototype)) + { + var executionHandler = new ExecutonHandler(actions); + executionHandler.RunNow(runType, this); + if (executionHandler.ExecutionResult == ExecutionMode.Failure) + { + throw executionHandler.ExecutionFailureException; + } + + if (runType == RunType.ScriptToWindow) + { + sqlScript = executionHandler.ScriptTextFromLastRun; + } + } + + return sqlScript; + } + + private string DoHandleUpdateAppRoleRequest(AppRoleViewContext context, AppRoleInfo appRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CreateAppRoleDataContainer(connInfo, null, ConfigAction.Create, context.Parameters.Database); + AppRolePrototype prototype = new AppRolePrototype(dataContainer, context.Parameters.Database, dataContainer.Server.Databases[context.Parameters.Database].ApplicationRoles[appRoleInfo.Name]); + prototype.ApplyInfoToPrototype(appRoleInfo); + return ConfigureAppRole(dataContainer, ConfigAction.Update, runType, prototype); + } + + private string DoHandleCreateAppRoleRequest(AppRoleViewContext context, AppRoleInfo appRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CreateAppRoleDataContainer(connInfo, null, ConfigAction.Create, context.Parameters.Database); + + AppRolePrototype prototype = new AppRolePrototype(dataContainer, context.Parameters.Database, appRoleInfo); + return ConfigureAppRole(dataContainer, ConfigAction.Create, runType, prototype); + } + + internal CDataContainer CreateAppRoleDataContainer(ConnectionInfo connInfo, AppRoleInfo role, ConfigAction configAction, string databaseName) + { + var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer"); + var connectionInfoWithConnection = new SqlConnectionInfoWithConnection(); + connectionInfoWithConnection.ServerConnection = serverConnection; + + string urn = (configAction == ConfigAction.Update && role != null) + ? string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Server/Database[@Name='{0}']/ApplicationRole[@Name='{1}']", + Urn.EscapeString(databaseName), + Urn.EscapeString(role.Name)) + : string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Server/Database[@Name='{0}']", + Urn.EscapeString(databaseName)); + + ActionContext context = new ActionContext(serverConnection, "ApplicationRole", urn); + DataContainerXmlGenerator containerXml = new DataContainerXmlGenerator(context); + + if (configAction == ConfigAction.Create) + { + containerXml.AddProperty("itemtype", "ApplicationRole"); + } + + XmlDocument xmlDoc = containerXml.GenerateXmlDocument(); + return CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleInfo.cs new file mode 100644 index 00000000..c60882d0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleInfo.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various application role properties + /// + public class AppRoleInfo : SqlObject + { + public string? DefaultSchema { get; set; } + public string? Password { get; set; } + public string[]? OwnedSchemas { get; set; } + public ExtendedPropertyInfo[]? ExtendedProperties { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewContext.cs new file mode 100644 index 00000000..1703f052 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewContext.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + public class AppRoleViewContext : SqlObjectViewContext + { + public ServerConnection Connection { get; } + + public AppRoleViewContext(Contracts.InitializeViewRequestParams parameters, ServerConnection connection) : base(parameters) + { + this.Connection = connection; + } + + public override void Dispose() + { + try + { + this.Connection.Disconnect(); + } + catch + { + // ignore + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewInfo.cs new file mode 100644 index 00000000..42456615 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/AppRole/AppRoleViewInfo.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various application role view properties + /// + public class AppRoleViewInfo : SqlObjectViewInfo + { + public string[]? Schemas { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleHandler.cs new file mode 100644 index 00000000..0214757a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleHandler.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.Management; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// DatabaseRole object type handler + /// + public class DatabaseRoleHandler : ObjectTypeHandler + { + public DatabaseRoleHandler(ConnectionService connectionService) : base(connectionService) { } + + public override bool CanHandleType(SqlObjectType objectType) + { + return objectType == SqlObjectType.DatabaseRole; + } + + public override async Task InitializeObjectView(InitializeViewRequestParams parameters) + { + // check input parameters + if (string.IsNullOrWhiteSpace(parameters.Database)) + { + throw new ArgumentNullException("parameters.Database"); + } + + // open a connection for running the DatabaseRole associated tasks + ConnectionInfo originalConnInfo; + this.ConnectionService.TryFindConnection(parameters.ConnectionUri, out originalConnInfo); + if (originalConnInfo == null) + { + throw new ArgumentException("Invalid connection URI '{0}'", parameters.ConnectionUri); + } + string originalDatabaseName = originalConnInfo.ConnectionDetails.DatabaseName; + try + { + originalConnInfo.ConnectionDetails.DatabaseName = parameters.Database; + ConnectParams connectParams = new ConnectParams + { + OwnerUri = parameters.ContextId, + Connection = originalConnInfo.ConnectionDetails, + Type = Connection.ConnectionType.Default + }; + await this.ConnectionService.Connect(connectParams); + } + finally + { + originalConnInfo.ConnectionDetails.DatabaseName = originalDatabaseName; + } + + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(parameters.ContextId, out connInfo); + + CDataContainer dataContainer = CreateDatabaseRoleDataContainer(connInfo, null, ConfigAction.Create, parameters.Database); + + DatabaseRolePrototype prototype = parameters.IsNewObject + ? new DatabaseRolePrototype(dataContainer, parameters.Database) + : new DatabaseRolePrototype(dataContainer, parameters.Database, dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as DatabaseRole); + + DatabaseRoleInfo DatabaseRoleInfo = new DatabaseRoleInfo() + { + Name = prototype.Name, + Owner = prototype.Owner, + ExtendedProperties = prototype.ExtendedProperties.Select(item => new ExtendedPropertyInfo() + { + Name = item.Key, + Value = item.Value + }).ToArray(), + Members = prototype.Members.ToArray(), + OwnedSchemas = prototype.SchemasOwned.ToArray(), + }; + + var viewInfo = new DatabaseRoleViewInfo() + { + ObjectInfo = DatabaseRoleInfo, + Schemas = prototype.Schemas + }; + + var context = new DatabaseRoleViewContext(parameters, dataContainer.ServerConnection); + return new InitializeViewResult() + { + ViewInfo = viewInfo, + Context = context + }; + } + + public override Task Save(DatabaseRoleViewContext context, DatabaseRoleInfo obj) + { + if (context.Parameters.IsNewObject) + { + this.DoHandleCreateDatabaseRoleRequest(context, obj, RunType.RunNow); + } + else + { + this.DoHandleUpdateDatabaseRoleRequest(context, obj, RunType.RunNow); + } + return Task.CompletedTask; + } + + public override Task Script(DatabaseRoleViewContext context, DatabaseRoleInfo obj) + { + string script; + if (context.Parameters.IsNewObject) + { + script = this.DoHandleCreateDatabaseRoleRequest(context, obj, RunType.ScriptToWindow); + } + else + { + script = this.DoHandleUpdateDatabaseRoleRequest(context, obj, RunType.ScriptToWindow); + } + return Task.FromResult(script); + } + + private string ConfigureDatabaseRole(CDataContainer dataContainer, ConfigAction configAction, RunType runType, DatabaseRolePrototype prototype) + { + string sqlScript = string.Empty; + using (var actions = new DatabaseRoleActions(dataContainer, configAction, prototype)) + { + var executionHandler = new ExecutonHandler(actions); + executionHandler.RunNow(runType, this); + if (executionHandler.ExecutionResult == ExecutionMode.Failure) + { + throw executionHandler.ExecutionFailureException; + } + + if (runType == RunType.ScriptToWindow) + { + sqlScript = executionHandler.ScriptTextFromLastRun; + } + } + + return sqlScript; + } + + private string DoHandleUpdateDatabaseRoleRequest(DatabaseRoleViewContext context, DatabaseRoleInfo DatabaseRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CreateDatabaseRoleDataContainer(connInfo, null, ConfigAction.Create, context.Parameters.Database); + DatabaseRolePrototype prototype = new DatabaseRolePrototype(dataContainer, context.Parameters.Database, dataContainer.Server.Databases[context.Parameters.Database].Roles[DatabaseRoleInfo.Name]); + prototype.ApplyInfoToPrototype(DatabaseRoleInfo); + return ConfigureDatabaseRole(dataContainer, ConfigAction.Update, runType, prototype); + } + + private string DoHandleCreateDatabaseRoleRequest(DatabaseRoleViewContext context, DatabaseRoleInfo DatabaseRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CreateDatabaseRoleDataContainer(connInfo, null, ConfigAction.Create, context.Parameters.Database); + + DatabaseRolePrototype prototype = new DatabaseRolePrototype(dataContainer, context.Parameters.Database, DatabaseRoleInfo); + return ConfigureDatabaseRole(dataContainer, ConfigAction.Create, runType, prototype); + } + + internal CDataContainer CreateDatabaseRoleDataContainer(ConnectionInfo connInfo, DatabaseRoleInfo role, ConfigAction configAction, string databaseName) + { + var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer"); + var connectionInfoWithConnection = new SqlConnectionInfoWithConnection(); + connectionInfoWithConnection.ServerConnection = serverConnection; + + string urn = (configAction == ConfigAction.Update && role != null) + ? string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Server/Database[@Name='{0}']/Role[@Name='{1}']", + Urn.EscapeString(databaseName), + Urn.EscapeString(role.Name)) + : string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Server/Database[@Name='{0}']", + Urn.EscapeString(databaseName)); + + ActionContext context = new ActionContext(serverConnection, "Role", urn); + DataContainerXmlGenerator containerXml = new DataContainerXmlGenerator(context); + + if (configAction == ConfigAction.Create) + { + containerXml.AddProperty("itemtype", "Role"); + } + + XmlDocument xmlDoc = containerXml.GenerateXmlDocument(); + return CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleInfo.cs new file mode 100644 index 00000000..9f733367 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleInfo.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various database role properties + /// + public class DatabaseRoleInfo : SqlObject + { + public string? Owner { get; set; } + public string[]? OwnedSchemas { get; set; } + public ExtendedPropertyInfo[]? ExtendedProperties { get; set; } + public string[]? Members { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewContext.cs new file mode 100644 index 00000000..3d889fe5 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewContext.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + public class DatabaseRoleViewContext : SqlObjectViewContext + { + public ServerConnection Connection { get; } + public DatabaseRoleViewContext(Contracts.InitializeViewRequestParams parameters, ServerConnection connection) : base(parameters) + { + this.Connection = connection; + } + + public override void Dispose() + { + try + { + this.Connection.Disconnect(); + } + catch + { + // ignore + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewInfo.cs new file mode 100644 index 00000000..7ac14dad --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/DatabaseRole/DatabaseRoleViewInfo.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various database role view properties + /// + public class DatabaseRoleViewInfo : SqlObjectViewInfo + { + public string[]? Schemas { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleActions.cs new file mode 100644 index 00000000..8c84163d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleActions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Management; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + internal class AppRoleActions : ManagementActionBase + { + private ConfigAction configAction; + + private AppRolePrototype prototype; + + /// + /// Handle login create and update actions + /// + public AppRoleActions(CDataContainer dataContainer, ConfigAction configAction, AppRolePrototype prototype) + { + this.DataContainer = dataContainer; + this.configAction = configAction; + this.prototype = prototype; + } + + /// + /// called by the management actions framework to execute the action + /// + /// + public override void OnRunNow(object sender) + { + if (this.configAction != ConfigAction.Drop) + { + this.prototype.SendDataToServer(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleData.cs new file mode 100644 index 00000000..c089d7d8 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleData.cs @@ -0,0 +1,636 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable + +using Microsoft.SqlServer.Management.Sdk.Sfc; +using System; +using System.Data; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Management; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// AppRoleGeneral - main app role page + /// + internal class AppRolePrototype + { + #region Members + + /// + /// data container member that contains data specific information like + /// connection infor, SMO server object or an AMO server object as well + /// as a hash table where one can manipulate custom data + /// + private CDataContainer dataContainer = null; + + private bool exists; + private AppRolePrototypeData currentState; + private AppRolePrototypeData originalState; + + #endregion + + #region Trace support + private const string componentName = "AppRoleGeneral"; + + public string ComponentName + { + get + { + return componentName; + } + } + #endregion + + #region Constants - urn fields, etc... + private const string ownerField = "Owner"; + private const string defaultSchemaField = "DefaultSchema"; + private const string schemaNameField = "Name"; + private const string schemaOwnerField = "Owner"; + private const string memberNameField = "Name"; + private const string memberUrnField = "Urn"; + #endregion + + #region Non-UI variables + + // info extracted from context + private string databaseName; + #endregion + + + #region Properties: CreateNew/Properties mode + public string Name + { + get + { + return this.currentState.AppRoleName; + } + set + { + this.currentState.AppRoleName = value; + } + } + + public string DefaultSchema + { + get + { + return this.currentState.DefaultSchema; + } + set + { + this.currentState.DefaultSchema = value; + } + } + + public string[] Schemas + { + get + { + return this.currentState.Schemas; + } + } + + public string[] SchemasOwned + { + get + { + return this.currentState.SchemasOwned; + } + set + { + this.currentState.SchemasOwned = value; + } + } + + public string Password + { + get + { + return this.currentState.Password.ToString(); + } + set + { + this.currentState.Password = value; + } + } + + public bool IsYukonOrLater + { + get + { + return this.currentState.IsYukonOrLater; + } + } + + public Dictionary ExtendedProperties + { + get + { + return this.currentState.ExtendedProperties; + } + set + { + this.currentState.ExtendedProperties = value; + } + } + #endregion + + #region Constructors / Dispose + public AppRolePrototype(CDataContainer context, string database) + { + this.exists = false; + this.databaseName = database; + this.dataContainer = context; + this.currentState = new AppRolePrototypeData(context, database); + this.originalState = (AppRolePrototypeData)this.currentState.Clone(); + } + + /// + /// AppRoleData for creating a new app role + /// + public AppRolePrototype(CDataContainer context, string database, AppRoleInfo roleInfo) + { + this.exists = false; + this.dataContainer = context; + this.databaseName = database; + this.currentState = new AppRolePrototypeData(context, database); + this.originalState = (AppRolePrototypeData)this.currentState.Clone(); + + this.ApplyInfoToPrototype(roleInfo); + } + + /// + /// AppRoleData for editing an existing app role + /// + public AppRolePrototype(CDataContainer context, string database, ApplicationRole role) + { + this.exists = true; + this.dataContainer = context; + this.databaseName = database; + this.currentState = new AppRolePrototypeData(context, database, role); + this.originalState = (AppRolePrototypeData)this.currentState.Clone(); + } + + #endregion + + #region Implementation: SendDataToServer() + /// + /// SendDataToServer + /// + /// here we talk with server via smo and do the actual data changing + /// + public void SendDataToServer() + { + System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.databaseName), "databaseName is empty"); + + Microsoft.SqlServer.Management.Smo.Server srv = this.dataContainer.Server; + System.Diagnostics.Debug.Assert(srv != null, "server object is null"); + + Database db = srv.Databases[this.databaseName]; + System.Diagnostics.Debug.Assert(db != null, "database object is null"); + + if (this.exists) // in properties mode -> alter role + { + System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.Name), "approleName is empty"); + + ApplicationRole approle = db.ApplicationRoles[this.Name]; + System.Diagnostics.Debug.Assert(approle != null, "approle object is null"); + + bool alterRequired = false; + + if (this.IsYukonOrLater && this.originalState.DefaultSchema != this.currentState.DefaultSchema) + { + approle.DefaultSchema = this.currentState.DefaultSchema; + alterRequired = true; + } + + if (this.originalState.Password != this.currentState.Password) + { + approle.ChangePassword(this.Password); + alterRequired = true; + } + + if (alterRequired == true) + { + approle.Alter(); + } + + SendToServerSchemaOwnershipChanges(db, approle); + SendToServerExtendedPropertiesChange(); + } + else // not in properties mode -> create role + { + ApplicationRole approle = new ApplicationRole(db, this.Name); + if (this.IsYukonOrLater && this.currentState.DefaultSchema.Length > 0) + { + approle.DefaultSchema = this.currentState.DefaultSchema; + } + + approle.Create(this.Password); + + SendToServerSchemaOwnershipChanges(db, approle); + SendToServerExtendedPropertiesChange(); + } + + } + #endregion + + #region Schemas - general operations with ... + /// + /// sends to server changes related to schema ownership + /// + private void SendToServerSchemaOwnershipChanges(Database db, ApplicationRole approle) + { + if (this.IsYukonOrLater) + { + foreach (string schemaName in this.Schemas) + { + bool currentlyOwned = this.currentState.SchemasOwned.Contains(schemaName); + + if (this.exists) + { + bool wasOwned = this.originalState.SchemasOwned.Contains(schemaName); + + if (currentlyOwned != wasOwned) + { + if (currentlyOwned == true) + { + Schema schema = db.Schemas[schemaName]; + schema.Owner = approle.Name; + schema.Alter(); + } + else + { + /* we cannot not renounce ownership + Schema schema = db.Schemas[name]; + schema.Owner = null; + schema.Alter(); + */ + } + } + } + else + { + if (currentlyOwned == true) + { + Schema schema = db.Schemas[schemaName]; + schema.Owner = approle.Name; + schema.Alter(); + } + } + } + } + } + #endregion + + private void SendToServerExtendedPropertiesChange() + { + // add or alter the extended properties + foreach (var item in this.ExtendedProperties) + { + if (this.originalState.ExtendedProperties.ContainsKey(item.Key)) + { + // alter the existing extended property + ExtendedProperty ep = this.originalState.ApplicationRole.ExtendedProperties[item.Key]; + ep.Value = item.Value; + ep.Alter(); + } + else + { + // create the extended property + ExtendedProperty ep = new ExtendedProperty(this.originalState.ApplicationRole, item.Key, item.Value); + ep.Create(); + } + } + + // remove the extended properties that are not in the current list + foreach (var item in this.originalState.ExtendedProperties) + { + if (!this.ExtendedProperties.ContainsKey(item.Key)) + { + ExtendedProperty ep = this.originalState.ApplicationRole.ExtendedProperties[item.Key]; + ep.Drop(); + } + } + } + + public void ApplyInfoToPrototype(AppRoleInfo roleInfo) + { + this.Name = roleInfo.Name; + this.DefaultSchema = roleInfo.DefaultSchema; + this.SchemasOwned = roleInfo.OwnedSchemas.ToArray(); + this.Password = roleInfo.Password; + this.ExtendedProperties = roleInfo.ExtendedProperties.Select(ep => new KeyValuePair(ep.Name, ep.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private class AppRolePrototypeData : ICloneable + { + #region data members + private string appRoleName = string.Empty; + private string defaultSchema = String.Empty; + private string password = String.Empty; + private bool initialized = false; + private List schemaNames = new List(); + private Dictionary dictSchemas = new Dictionary(); + private Dictionary dictExtendedProperties = new Dictionary(); + private ApplicationRole role = null; + private Server server = null; + private string database = string.Empty; + private CDataContainer context = null; + private bool isYukonOrLater = false; + #endregion + + #region Properties + + // General properties + + + public string AppRoleName + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.appRoleName; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.appRoleName = value; + } + } + + public ApplicationRole ApplicationRole + { + get + { + return this.role; + } + } + + public string DefaultSchema + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.defaultSchema; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.defaultSchema = value; + } + } + public string[] SchemasOwned + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.dictSchemas.Keys.Where(s => dictSchemas[s] == this.appRoleName).ToArray(); + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + foreach (string schema in value) + { + // cannot renounce ownership + this.dictSchemas[schema] = this.AppRoleName; + } + } + } + + public string[] Schemas + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.schemaNames.ToArray(); + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.schemaNames = value.ToList(); + } + } + + public Dictionary ExtendedProperties + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.dictExtendedProperties; + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.dictExtendedProperties = value; + } + } + + public bool Exists + { + get + { + return (this.role != null); + } + } + + public bool IsYukonOrLater + { + get + { + return this.isYukonOrLater; + } + } + + public Microsoft.SqlServer.Management.Smo.Server Server + { + get + { + return this.server; + } + } + + public ApplicationRole AppRole + { + get + { + return this.role; + } + } + + public string Password + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.password; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.password = value; + } + } + + #endregion + + /// + /// private default constructor - used by Clone() + /// + private AppRolePrototypeData() + { + } + + /// + /// constructor + /// + /// The context in which we are creating a new appRole + /// The database in which we are creating a new appRole + public AppRolePrototypeData(CDataContainer context, string databaseName) + { + this.server = context.Server; + this.database = databaseName; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + LoadData(); + } + + /// + /// constructor + /// + /// The context in which we are modifying a appRole + /// The database in which we are modifying a appRole + /// The appRole we are modifying + public AppRolePrototypeData(CDataContainer context, string databaseName, ApplicationRole appRole) + { + this.server = context.Server; + this.database = databaseName; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + this.role = appRole; + LoadData(); + } + + /// + /// Create a clone of this AppRolePrototypeData object + /// + /// The clone AppRolePrototypeData object + public object Clone() + { + AppRolePrototypeData result = new AppRolePrototypeData(); + result.appRoleName = this.appRoleName; + result.defaultSchema = this.defaultSchema; + result.password = this.password; + result.initialized = this.initialized; + result.schemaNames = new List(this.schemaNames); + result.dictSchemas = new Dictionary(this.dictSchemas); + result.dictExtendedProperties = new Dictionary(this.dictExtendedProperties); + result.role = this.role; + result.server = this.server; + return result; + } + + private void LoadData() + { + this.initialized = true; + + if (this.Exists) + { + LoadExisting(); + } + else + { + LoadNew(); + } + } + + private void LoadExisting() + { + System.Diagnostics.Debug.Assert(server != null, "server is null"); + System.Diagnostics.Debug.Assert(role != null, "app role is null"); + this.appRoleName = role.Name; + this.password = LoginPrototype.fakePassword; + this.defaultSchema = role.DefaultSchema; + LoadSchemas(); + LoadExtendProperties(); + } + + private void LoadNew() + { + LoadSchemas(); + } + + /// + /// loads initial schemas from server together with information about the schema owner + /// + private void LoadSchemas() + { + if (this.isYukonOrLater) + { + this.dictSchemas = new Dictionary(); + this.schemaNames = new List(); + + Enumerator en = new Enumerator(); + Request req = new Request(); + req.Fields = new String[] { AppRolePrototype.schemaNameField, AppRolePrototype.schemaOwnerField }; + req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.database) + "']/Schema"; + req.OrderByList = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc) }; + + DataTable dt = en.Process(server.ConnectionContext, req); + System.Diagnostics.Debug.Assert((dt != null) && (dt.Rows.Count > 0), "No rows returned from schema enumerator"); + + foreach (DataRow dr in dt.Rows) + { + string name = Convert.ToString(dr[AppRolePrototype.schemaNameField], System.Globalization.CultureInfo.InvariantCulture); + string owner = Convert.ToString(dr[AppRolePrototype.schemaOwnerField], System.Globalization.CultureInfo.InvariantCulture); + + dictSchemas.Add(name, owner); + schemaNames.Add(name); + } + } + } + + private void LoadExtendProperties() + { + if (this.isYukonOrLater) + { + foreach (ExtendedProperty ep in this.role.ExtendedProperties) + { + this.dictExtendedProperties.Add(ep.Name, (string)ep.Value); + } + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs deleted file mode 100644 index f770fd26..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs +++ /dev/null @@ -1,653 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using Microsoft.SqlServer.Management.Sdk.Sfc; -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Xml; -using System.Data; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlTools.ServiceLayer.Management; - -namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement -{ - /// - /// AppRoleGeneral - main app role page - /// - internal class AppRoleGeneral - { - #region Members - - /// - /// data container member that contains data specific information like - /// connection infor, SMO server object or an AMO server object as well - /// as a hash table where one can manipulate custom data - /// - private CDataContainer dataContainer = null; - - //SMO Server connection that MUST be used for all enumerator calls - //We'll get this object out of CDataContainer, that must be initialized - //property by the initialization code - private ServerConnection serverConnection; - - #endregion - - - #region Trace support - private const string componentName = "AppRoleGeneral"; - - public string ComponentName - { - get - { - return componentName; - } - } - #endregion - - #region Constants - urn fields, etc... - private const string ownerField = "Owner"; - private const string defaultSchemaField = "DefaultSchema"; - private const string schemaNameField = "Name"; - private const string schemaOwnerField = "Owner"; - private const string memberNameField = "Name"; - private const string memberUrnField = "Urn"; - #endregion - - #region Constants - grid columns positions, etc... - private const int colSchemasChecked = 0; - private const int colSchemasOwnedSchemas = 1; - - private const int colMembershipBitmap = 0; - private const int colMembershipRoleMembers = 1; - - private const int sizeCheckboxColumn = 20; - private const int sizeBitmapColumn = 20; - #endregion - - #region Non-UI variables - private System.Xml.XmlDocument document = null; - - // info extracted from context - private string serverName; - private string databaseName; - private string approleName; - private bool passwordChanged = false; - - - // initial values loaded from server - private string initialDefaultSchema; - - - private bool isYukonOrLater; - #endregion - - - #region Properties: CreateNew/Properties mode - private bool IsPropertiesMode - { - get - { - return (approleName != null) && (approleName.Trim().Length != 0); - } - } - #endregion - - #region Constructors / Dispose - public AppRoleGeneral() - { - // This call is required by the Windows.Forms Form Designer. - // InitializeComponent(); - } - - public AppRoleGeneral(CDataContainer context) - { - dataContainer = context; - - if (dataContainer != null) - { - document = dataContainer.Document; - } - else - { - document = null; - } - - this.isYukonOrLater = (9 <= context.Server.ConnectionContext.ServerVersion.Major); - } - - /// - /// Clean up any resources being used. - /// - // protected override void Dispose( bool disposing ) - // { - // if ( disposing ) - // { - // if (components != null) - // { - // components.Dispose(); - // } - // } - // base.Dispose( disposing ); - // } - - #endregion - - #region Implementation: LoadData(), InitProp(), SendDataToServer() - - - /// - /// LoadData - /// - /// loads connection parameters from an xml - /// - /// - private void LoadData(XmlDocument doc) - { - // STrace.Params(ComponentName, "LoadData", "XmlDocument doc=\"{0}\"", doc.OuterXml); - - STParameters param; - bool bStatus; - - param = new STParameters(); - - param.SetDocument(doc); - - bStatus = param.GetParam("servername", ref this.serverName); - bStatus = param.GetParam("database", ref this.databaseName); - bStatus = param.GetParam("applicationrole", ref this.approleName); - } - - /// - /// InitProp - /// - /// talks with enumerator an retrieves info - /// - private void InitProp() - { - // STrace.Params(ComponentName, "InitProp", "", null); - - System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.serverName), "serverName is empty"); - System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.databaseName), "databaseName is empty"); - - passwordChanged = false; - - // InitializeBitmapAndIcons(); // bitmapMember - - if (this.IsPropertiesMode == true) - { - // initialize from enumerator in properties mode - // STrace.Assert(!string.IsNullOrWhiteSpace(this.approleName), "approleName is empty"); - - // this.textBoxRoleName.Text = this.approleName; - - if (this.isYukonOrLater) - { - // get the default schema - - // STrace.Assert(this.DataContainer.ObjectUrn.Length != 0, "object urn is empty"); - - Enumerator enumerator = new Enumerator(); - Request request = new Request(); - request.Urn = this.dataContainer.ObjectUrn; - request.Fields = new String[] { AppRoleGeneral.defaultSchemaField }; - - DataTable dataTable = enumerator.Process(serverConnection, request); - // STrace.Assert(dataTable != null, "dataTable is null"); - // STrace.Assert(dataTable.Rows.Count == 1, "unexpected number of rows in dataTable"); - - if (dataTable.Rows.Count == 0) - { - throw new Exception("AppRoleSR.ErrorAppRoleNotFound"); - } - - DataRow dataRow = dataTable.Rows[0]; - this.initialDefaultSchema = Convert.ToString(dataRow[AppRoleGeneral.defaultSchemaField], System.Globalization.CultureInfo.InvariantCulture); - - // this.textBoxDefaultSchema.Text = this.initialDefaultSchema; - } - } - else - { - // initialize with empty values in create new mode - // this.textBoxRoleName.Text = String.Empty; - // this.textBoxDefaultSchema.Text = this.initialDefaultSchema; - - // this.textBoxPasword.Text = String.Empty; - // this.textBoxConfirmPassword.Text = String.Empty; - } - - LoadSchemas(); - // InitializeSchemasGridColumns(); - // FillSchemasGrid(); - - LoadMembership(); - // InitializeMembershipGridColumns(); - // FillMembershipGrid(); - - // dont display the membership controls - app roles dont support members - // HideMembership(); - - // update UI enable/disable controls - // EnableDisableControls(); - } - - // private void HideMembership() - // { - // try - // { - // this.SuspendLayout(); - // this.panelSchema.SuspendLayout(); - - // this.panelSchema.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right | AnchorStyles.Left; - // this.panelSchema.Size = new Size - // ( - // this.panelSchema.Size.Width - // , - // this.panelMembership.Location.Y + this.panelMembership.Size.Height - - // this.panelSchema.Location.Y - // ); - - // this.panelMembership.Visible = false; - // } - // finally - // { - // this.panelSchema.ResumeLayout(); - // this.ResumeLayout(); - - // this.gridSchemasOwned.Refresh(); - // } - // } - - - private string _selectedDefaultSchema; - // private SqlSecureString _textBoxPaswordText; - // private SqlSecureString _textBoxConfirmPasswordText; - private string _textBoxRoleNameText; - - /// - /// Called to validate all date in controls and save them in - /// temproary storage to be used when OnRunNow is called - /// - // public override void OnGatherUiInformation(RunType runType) - // { - // base.OnGatherUiInformation(runType); - - // try - // { - // base.ExecutionMode = ExecutionMode.Success; - - // _selectedDefaultSchema = this.textBoxDefaultSchema.Text; - // _textBoxPaswordText = textBoxPasword.Text; - // _textBoxConfirmPasswordText = textBoxConfirmPassword.Text; - // _textBoxRoleNameText = textBoxRoleName.Text; - // } - // catch (Exception exception) - // { - // DisplayExceptionMessage(exception); - // base.ExecutionMode = ExecutionMode.Failure; - // } - // } - - /// - /// SendDataToServer - /// - /// here we talk with server via smo and do the actual data changing - /// - private void SendDataToServer() - { - // STrace.Params(ComponentName, "SendDataToServer", "", null); - - System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.databaseName), "databaseName is empty"); - - Microsoft.SqlServer.Management.Smo.Server srv = this.dataContainer.Server; - System.Diagnostics.Debug.Assert(srv != null, "server object is null"); - - Database db = srv.Databases[this.databaseName]; - System.Diagnostics.Debug.Assert(db != null, "database object is null"); - - if (this.IsPropertiesMode == true) // in properties mode -> alter role - { - System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.approleName), "approleName is empty"); - - ApplicationRole approle = db.ApplicationRoles[this.approleName]; - System.Diagnostics.Debug.Assert(approle != null, "approle object is null"); - - bool alterRequired = false; - - if (this.isYukonOrLater && _selectedDefaultSchema != this.initialDefaultSchema) - { - approle.DefaultSchema = _selectedDefaultSchema; - alterRequired = true; - } - - if (passwordChanged == true) - { - approle.ChangePassword((string)"_textBoxPaswordText"); - } - - if (alterRequired == true) - { - approle.Alter(); - } - - SendToServerSchemaOwnershipChanges(db, approle); - SendToServerMembershipChanges(db, approle); - } - else // not in properties mode -> create role - { - ApplicationRole approle = new ApplicationRole(db, _textBoxRoleNameText); - if (this.isYukonOrLater && _selectedDefaultSchema.Length > 0) - { - approle.DefaultSchema = _selectedDefaultSchema; - } - - approle.Create((string)"_textBoxPaswordText"); - - SendToServerSchemaOwnershipChanges(db, approle); - SendToServerMembershipChanges(db, approle); - - this.dataContainer.SqlDialogSubject = approle; // needed by extended properties page - } - - } - #endregion - - #region Schemas - general operations with ... - HybridDictionary dictSchemas = null; - StringCollection schemaNames = null; - /// - /// loads initial schemas from server together with information about the schema owner - /// - private void LoadSchemas() - { - if (this.isYukonOrLater) - { - this.dictSchemas = new HybridDictionary(); - this.schemaNames = new StringCollection(); - - Enumerator en = new Enumerator(); - Request req = new Request(); - req.Fields = new String[] { AppRoleGeneral.schemaNameField, AppRoleGeneral.schemaOwnerField }; - req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.databaseName) + "']/Schema"; - req.OrderByList = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc) }; - - DataTable dt = en.Process(serverConnection, req); - // STrace.Assert((dt != null) && (dt.Rows.Count > 0), "No rows returned from schema enumerator"); - - foreach (DataRow dr in dt.Rows) - { - string name = Convert.ToString(dr[AppRoleGeneral.schemaNameField], System.Globalization.CultureInfo.InvariantCulture); - string owner = Convert.ToString(dr[AppRoleGeneral.schemaOwnerField], System.Globalization.CultureInfo.InvariantCulture); - - dictSchemas.Add(name, owner); - schemaNames.Add(name); - } - } - } - - /// - /// sends to server changes related to schema ownership - /// - private void SendToServerSchemaOwnershipChanges(Database db, ApplicationRole approle) - { - if (this.isYukonOrLater) - { - // DlgGridControl grid = this.gridSchemasOwned; - - for (int i = 0; i < 1; ++i) - { - string name = "grid.GetCellInfo(i, colSchemasOwnedSchemas).CellData.ToString()"; - object o = dictSchemas[name]; - - System.Diagnostics.Debug.Assert(o != null, "schema object is null"); - - // bool currentlyOwned = IsEmbeededCheckboxChecked(grid, i, colSchemasChecked); - bool currentlyOwned = false; - - if (IsPropertiesMode == true) - { - bool wasOwned = (o.ToString() == approleName); - - if (currentlyOwned != wasOwned) - { - if (currentlyOwned == true) - { - Schema schema = db.Schemas[name]; - schema.Owner = approle.Name; - schema.Alter(); - } - else - { - /* we cannot not renounce ownership - Schema schema = db.Schemas[name]; - schema.Owner = null; - schema.Alter(); - */ - - - - - } - } - } - else - { - if (currentlyOwned == true) - { - Schema schema = db.Schemas[name]; - schema.Owner = approle.Name; - schema.Alter(); - } - } - } - } - } - - // private void gridSchemasOwned_MouseButtonClicked(object sender, Microsoft.SqlServer.Management.UI.Grid.MouseButtonClickedEventArgs args) - // { - // if (args.Button != MouseButtons.Left) - // { - // return; - // } - - // int rowno = Convert.ToInt32(args.RowIndex); - // int colno = Convert.ToInt32(args.ColumnIndex); - - // switch (colno) - // { - // case colSchemasChecked: - // FlipCheckbox(gridSchemasOwned, rowno, colno); - // break; - // default: // else do default action: e.g. edit - open combo - etc ... - // break; - // } - // } - - #endregion - - #region Membership - general operations with ... - System.Collections.Specialized.HybridDictionary dictMembership = null; - - /// - /// loads from server initial membership information - /// - private void LoadMembership() - { - dictMembership = new System.Collections.Specialized.HybridDictionary(); - if (IsPropertiesMode == false) - { - return; - } - - Enumerator en = new Enumerator(); - Request req = new Request(); - req.Fields = new String[] { AppRoleGeneral.memberNameField, AppRoleGeneral.memberUrnField }; - req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.databaseName) + "']/ApplicationRole[@Name='" + Urn.EscapeString(this.approleName) + "']/Member"; - - try - { - DataTable dt = en.Process(serverConnection, req); - System.Diagnostics.Debug.Assert(dt != null, "No results returned from membership query"); - - foreach (DataRow dr in dt.Rows) - { - string name = Convert.ToString(dr[AppRoleGeneral.memberNameField], System.Globalization.CultureInfo.InvariantCulture); - string urn = Convert.ToString(dr[AppRoleGeneral.memberUrnField], System.Globalization.CultureInfo.InvariantCulture); - - dictMembership.Add(name, urn); - } - } - catch (Exception e) - { - System.Diagnostics.Debug.WriteLine(e.Message); - } - } - - /// - /// initialize grid column headers, but not the content - /// - // private void InitializeMembershipGridColumns() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridRoleMembership; - - // if (grid.RowsNumber != 0) - // { - // grid.DeleteAllRows(); - // } - - // while (grid.ColumnsNumber != 0) - // { - // grid.DeleteColumn(0); - // } - - // GridColumnInfo colInfo = null; - - // // bitmap member type - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = sizeBitmapColumn; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // colInfo.ColumnType = GridColumnType.Bitmap; - // grid.AddColumn(colInfo); - - // // member name - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = grid.Width - sizeBitmapColumn - 2; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // grid.AddColumn(colInfo); - - // grid.SetHeaderInfo(colMembershipRoleMembers, AppRoleSR.HeaderRoleMembers, null); - - // grid.SelectionType = GridSelectionType.SingleRow; - // grid.UpdateGrid(); - // } - - /// - /// fills the membership grid with data (bitmaps, names, etc) - /// - // private void FillMembershipGrid() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridRoleMembership; - - // grid.DeleteAllRows(); - // foreach (DictionaryEntry de in dictMembership) - // { - // GridCellCollection row = new GridCellCollection(); - // GridCell cell = null; - - // string name = de.Key.ToString(); - - // cell = new GridCell(bitmapMember); row.Add(cell); // compute type based on urn - // cell = new GridCell(name); row.Add(cell); - - // // row.Tag = urn == de.Value.ToString(); - - // grid.AddRow(row); - // } - - // if (grid.RowsNumber > 0) - // { - // grid.SelectedRow = 0; - // } - // } - - /// - /// sends to server user changes related to membership - /// - private void SendToServerMembershipChanges(Database db, ApplicationRole approle) - { - // DlgGridControl grid = this.gridRoleMembership; - - if (IsPropertiesMode == true) - { - // members to add - for (int i = 0; i < 1; ++i) - { - string name = "grid.GetCellInfo(i, colMembershipRoleMembers).CellData.ToString()"; - bool nameExistedInitially = dictMembership.Contains(name); - - if (nameExistedInitially == false) - { - // need SMO for: role.Members.Add(); - } - } - // members to drop - foreach (DictionaryEntry de in dictMembership) - { - if (FoundInMembershipGrid(de.Key.ToString(), de.Value.ToString()) == false) - { - // need SMO for: role.Members.Remove(); - } - } - } - else - { - // add only - for (int i = 0; i < 1; ++i) - { - string name = "grid.GetCellInfo(i, colMembershipRoleMembers).CellData.ToString()"; - // need SMO for: role.Members.Add(); - } - } - } - - /// - /// lookup in membership grid to see if user added a name - /// - /// - /// - /// - private bool FoundInMembershipGrid(string name, string urn) - { - // DlgGridControl grid = this.gridRoleMembership; - - // for (int i = 0; i < grid.RowsNumber; ++i) - // { - // string currentName = grid.GetCellInfo(i, colMembershipRoleMembers).CellData.ToString(); - // if (name == currentName) - // { - // return true; - // } - // } - - return false; - } - #endregion - } - -} - - - - - - - - - diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleActions.cs new file mode 100644 index 00000000..042db1c0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleActions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Management; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + internal class DatabaseRoleActions : ManagementActionBase + { + private ConfigAction configAction; + + private DatabaseRolePrototype prototype; + + /// + /// Handle database role create and update actions + /// + public DatabaseRoleActions(CDataContainer dataContainer, ConfigAction configAction, DatabaseRolePrototype prototype) + { + this.DataContainer = dataContainer; + this.configAction = configAction; + this.prototype = prototype; + } + + /// + /// called by the management actions framework to execute the action + /// + /// + public override void OnRunNow(object sender) + { + if (this.configAction != ConfigAction.Drop) + { + this.prototype.SendDataToServer(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleData.cs new file mode 100644 index 00000000..1c4f51e5 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleData.cs @@ -0,0 +1,662 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable + +using Microsoft.SqlServer.Management.Sdk.Sfc; +using System; +using System.Data; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Management; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// DatabaseRoleGeneral - main app role page + /// + internal class DatabaseRolePrototype + { + #region Members + + /// + /// data container member that contains data specific information like + /// connection infor, SMO server object or an AMO server object as well + /// as a hash table where one can manipulate custom data + /// + private CDataContainer dataContainer = null; + + private bool exists; + private DatabaseRolePrototypeData currentState; + private DatabaseRolePrototypeData originalState; + + #endregion + + #region Trace support + private const string componentName = "DatabaseRoleGeneral"; + + public string ComponentName + { + get + { + return componentName; + } + } + #endregion + + #region Constants - urn fields, etc... + private const string ownerField = "Owner"; + private const string schemaOwnerField = "Owner"; + private const string schemaNameField = "Name"; + private const string memberNameField = "Name"; + private const string memberUrnField = "Urn"; + #endregion + + #region Non-UI variables + + // info extracted from context + private string databaseName; + #endregion + + + #region Properties: CreateNew/Properties mode + public string Name + { + get + { + return this.currentState.DatabaseRoleName; + } + set + { + this.currentState.DatabaseRoleName = value; + } + } + + public string Owner + { + get + { + return this.currentState.Owner; + } + set + { + this.currentState.Owner = value; + } + } + + public string[] Schemas + { + get + { + return this.currentState.Schemas; + } + } + + public string[] SchemasOwned + { + get + { + return this.currentState.SchemasOwned; + } + set + { + this.currentState.SchemasOwned = value; + } + } + + public Dictionary ExtendedProperties + { + get + { + return this.currentState.ExtendedProperties; + } + set + { + this.currentState.ExtendedProperties = value; + } + } + + public List Members + { + get + { + return this.currentState.Members; + } + set + { + this.currentState.Members = value; + } + } + + public bool IsYukonOrLater + { + get + { + return this.dataContainer.Server.VersionMajor >= 9; + } + } + #endregion + + #region Constructors / Dispose + public DatabaseRolePrototype(CDataContainer context, string database) + { + this.exists = false; + this.databaseName = database; + this.dataContainer = context; + this.currentState = new DatabaseRolePrototypeData(context, database); + this.originalState = (DatabaseRolePrototypeData)this.currentState.Clone(); + } + + /// + /// DatabaseRoleData for creating a new app role + /// + public DatabaseRolePrototype(CDataContainer context, string database, DatabaseRoleInfo roleInfo) + { + this.exists = false; + this.databaseName = database; + this.dataContainer = context; + this.currentState = new DatabaseRolePrototypeData(context, database); + this.originalState = (DatabaseRolePrototypeData)this.currentState.Clone(); + + this.ApplyInfoToPrototype(roleInfo); + } + + /// + /// DatabaseRoleData for editing an existing app role + /// + public DatabaseRolePrototype(CDataContainer context, string database, DatabaseRole role) + { + this.exists = true; + this.databaseName = database; + this.dataContainer = context; + this.currentState = new DatabaseRolePrototypeData(context, database, role); + this.originalState = (DatabaseRolePrototypeData)this.currentState.Clone(); + } + + #endregion + + #region Implementation: SendDataToServer() + /// + /// SendDataToServer + /// + /// here we talk with server via smo and do the actual data changing + /// + public void SendDataToServer() + { + System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.databaseName), "databaseName is empty"); + + Microsoft.SqlServer.Management.Smo.Server srv = this.dataContainer.Server; + System.Diagnostics.Debug.Assert(srv != null, "server object is null"); + + Database db = srv.Databases[this.databaseName]; + System.Diagnostics.Debug.Assert(db != null, "database object is null"); + + DatabaseRole databaseRole = null; + if (this.exists) // in properties mode -> alter role + { + System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.Name), "databaseRoleName is empty"); + + databaseRole = db.Roles[this.Name]; + System.Diagnostics.Debug.Assert(databaseRole != null, "databaseRole object is null"); + + if (0 != String.Compare(this.currentState.Owner, this.originalState.Owner, StringComparison.Ordinal)) + { + databaseRole.Owner = this.Owner; + databaseRole.Alter(); + } + } + else // not in properties mode -> create role + { + databaseRole = new DatabaseRole(db, this.Name); + if (this.Owner.Length != 0) + { + databaseRole.Owner = this.Owner; + } + databaseRole.Create(); + } + SendToServerSchemaOwnershipChanges(db, databaseRole); + SendToServerExtendedPropertiesChange(); + } + #endregion + + #region Schemas - general operations with ... + /// + /// sends to server changes related to schema ownership + /// + private void SendToServerSchemaOwnershipChanges(Database db, DatabaseRole databaseRole) + { + if (this.IsYukonOrLater) + { + foreach (string schemaName in this.Schemas) + { + bool currentlyOwned = this.currentState.SchemasOwned.Contains(schemaName); + + if (this.exists) + { + bool wasOwned = this.originalState.SchemasOwned.Contains(schemaName); + + if (currentlyOwned != wasOwned) + { + if (currentlyOwned == true) + { + Schema schema = db.Schemas[schemaName]; + schema.Owner = databaseRole.Name; + schema.Alter(); + } + else + { + /* we cannot not renounce ownership + Schema schema = db.Schemas[name]; + schema.Owner = null; + schema.Alter(); + */ + } + } + } + else + { + if (currentlyOwned == true) + { + Schema schema = db.Schemas[schemaName]; + schema.Owner = databaseRole.Name; + schema.Alter(); + } + } + } + } + } + #endregion + + private void SendToServerExtendedPropertiesChange() + { + // add or alter the extended properties + foreach (var item in this.ExtendedProperties) + { + if (this.originalState.ExtendedProperties.ContainsKey(item.Key)) + { + // alter the existing extended property + ExtendedProperty ep = this.originalState.DatabaseRole.ExtendedProperties[item.Key]; + ep.Value = item.Value; + ep.Alter(); + } + else + { + // create the extended property + ExtendedProperty ep = new ExtendedProperty(this.originalState.DatabaseRole, item.Key, item.Value); + ep.Create(); + } + } + + // remove the extended properties that are not in the current list + foreach (var item in this.originalState.ExtendedProperties) + { + if (!this.ExtendedProperties.ContainsKey(item.Key)) + { + ExtendedProperty ep = this.originalState.DatabaseRole.ExtendedProperties[item.Key]; + ep.Drop(); + } + } + } + + /// + /// sends to server user changes related to membership + /// + private void SendToServerMembershipChanges(Database db, DatabaseRole dbrole) + { + if (!this.exists) + { + foreach (string member in this.Members) + { + dbrole.AddMember(member); + } + } + else + { + foreach (string member in this.Members) + { + if (!this.originalState.Members.Contains(member)) + { + dbrole.AddMember(member); + } + } + + foreach (string member in this.originalState.Members) + { + if (!this.Members.Contains(member)) + { + dbrole.DropMember(member); + } + } + } + } + + + public void ApplyInfoToPrototype(DatabaseRoleInfo roleInfo) + { + this.Name = roleInfo.Name; + this.Owner = roleInfo.Owner; + this.Members = roleInfo.Members.ToList(); + this.SchemasOwned = roleInfo.OwnedSchemas.ToArray(); + this.ExtendedProperties = roleInfo.ExtendedProperties.Select(ep => new KeyValuePair(ep.Name, ep.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + private class DatabaseRolePrototypeData : ICloneable + { + #region data members + private string databaseRoleName = string.Empty; + private string owner = String.Empty; + private bool initialized = false; + private List schemaNames = null; + private Dictionary dictSchemas = new Dictionary(); + private Dictionary dictExtendedProperties = new Dictionary(); + private List members = new List(); + private DatabaseRole role = null; + private Server server = null; + private string database = string.Empty; + private CDataContainer context = null; + private bool isYukonOrLater = false; + #endregion + + #region Properties + + // General properties + + + public string DatabaseRoleName + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.databaseRoleName; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.databaseRoleName = value; + } + } + + public DatabaseRole DatabaseRole + { + get + { + return this.role; + } + } + + public string Owner + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.owner; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.owner = value; + } + } + public string[] SchemasOwned + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.dictSchemas.Keys.Where(s => dictSchemas[s] == this.databaseRoleName).ToArray(); + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + foreach (string schema in value) + { + // cannot renounce ownership + this.dictSchemas[schema] = this.DatabaseRoleName; + } + } + } + + public string[] Schemas + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.schemaNames.ToArray(); + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.schemaNames = value.ToList(); + } + } + + public Dictionary ExtendedProperties + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.dictExtendedProperties; + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.dictExtendedProperties = value; + } + } + + public List Members + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.members; + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.members = value; + } + } + + public bool Exists + { + get + { + return (this.role != null); + } + } + + public Microsoft.SqlServer.Management.Smo.Server Server + { + get + { + return this.server; + } + } + + public bool IsYukonOrLater + { + get + { + return this.isYukonOrLater; + } + } + + #endregion + + /// + /// private default constructor - used by Clone() + /// + private DatabaseRolePrototypeData() + { + } + + /// + /// constructor + /// + /// The server on which we are creating a new databaseRole + /// The database on which we are creating a new databaseRole + public DatabaseRolePrototypeData(CDataContainer context, string databaseName) + { + this.server = context.Server; + this.database = databaseName; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + LoadData(); + } + + /// + /// constructor + /// + /// The server on which we are modifying a databaseRole + /// The database on which we are modifying a databaseRole + /// The databaseRole we are modifying + public DatabaseRolePrototypeData(CDataContainer context, string databaseName, DatabaseRole databaseRole) + { + this.server = context.Server; + this.database = databaseName; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + this.role = databaseRole; + LoadData(); + } + + /// + /// Create a clone of this DatabaseRolePrototypeData object + /// + /// The clone DatabaseRolePrototypeData object + public object Clone() + { + DatabaseRolePrototypeData result = new DatabaseRolePrototypeData(); + result.databaseRoleName = this.databaseRoleName; + result.initialized = this.initialized; + result.schemaNames = new List(this.schemaNames); + result.dictSchemas = new Dictionary(this.dictSchemas); + result.dictExtendedProperties = new Dictionary(this.dictExtendedProperties); + result.members = new List(this.members); + result.owner = this.owner; + result.role = this.role; + result.server = this.server; + return result; + } + + private void LoadData() + { + this.initialized = true; + + if (this.Exists) + { + LoadExisting(); + } + else + { + LoadNew(); + } + } + + private void LoadExisting() + { + System.Diagnostics.Debug.Assert(server != null, "server is null"); + System.Diagnostics.Debug.Assert(role != null, "app role is null"); + this.databaseRoleName = role.Name; + this.owner = role.Owner; + LoadSchemas(); + LoadMembership(); + LoadExtendProperties(); + } + + private void LoadNew() + { + LoadSchemas(); + } + + /// + /// loads initial schemas from server together with information about the schema owner + /// + private void LoadSchemas() + { + if (this.isYukonOrLater) + { + this.dictSchemas = new Dictionary(); + this.schemaNames = new List(); + + Enumerator en = new Enumerator(); + Request req = new Request(); + req.Fields = new String[] { DatabaseRolePrototype.schemaNameField, DatabaseRolePrototype.schemaOwnerField }; + req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.database) + "']/Schema"; + req.OrderByList = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc) }; + + DataTable dt = en.Process(server.ConnectionContext, req); + System.Diagnostics.Debug.Assert((dt != null) && (dt.Rows.Count > 0), "No rows returned from schema enumerator"); + + foreach (DataRow dr in dt.Rows) + { + string name = Convert.ToString(dr[DatabaseRolePrototype.schemaNameField], System.Globalization.CultureInfo.InvariantCulture); + string owner = Convert.ToString(dr[DatabaseRolePrototype.schemaOwnerField], System.Globalization.CultureInfo.InvariantCulture); + + dictSchemas.Add(name, owner); + schemaNames.Add(name); + } + } + } + + private void LoadMembership() + { + if (this.Exists) + { + this.members = new List(); + Enumerator enumerator = new Enumerator(); + Urn urn = String.Format(System.Globalization.CultureInfo.InvariantCulture, + "Server/Database[@Name='{0}']/Role[@Name='{1}']/Member", + Urn.EscapeString(this.database), + Urn.EscapeString(this.databaseRoleName)); + string[] fields = new string[] { DatabaseRolePrototype.memberNameField }; + OrderBy[] orderBy = new OrderBy[] { new OrderBy(DatabaseRolePrototype.memberNameField, OrderBy.Direction.Asc) }; + Request request = new Request(urn, fields, orderBy); + DataTable dt = enumerator.Process(this.server.ConnectionContext, request); + foreach (DataRow dr in dt.Rows) + { + string memberName = dr[DatabaseRolePrototype.memberNameField].ToString(); + this.members.Add(memberName); + } + } + } + + private void LoadExtendProperties() + { + if (this.isYukonOrLater) + { + foreach (ExtendedProperty ep in this.role.ExtendedProperties) + { + this.dictExtendedProperties.Add(ep.Name, (string)ep.Value); + } + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs deleted file mode 100644 index bd362cda..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs +++ /dev/null @@ -1,890 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using Microsoft.SqlServer.Management.Sdk.Sfc; -using System; -using System.Collections; -using System.Collections.Specialized; -using System.Xml; -using System.Data; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlTools.ServiceLayer.Management; - -namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement -{ - /// - /// DatabaseRoleGeneral - main panel for database role - /// - internal class DatabaseRoleGeneral - { - #region Members - /// - /// data container member that contains data specific information like - /// connection infor, SMO server object or an AMO server object as well - /// as a hash table where one can manipulate custom data - /// - private CDataContainer dataContainer = null; - - //SMO Server connection that MUST be used for all enumerator calls - //We'll get this object out of CDataContainer, that must be initialized - //property by the initialization code - private ServerConnection serverConnection; - - #endregion - - #region Trace support - private const string componentName = "DatabaseRoleGeneral"; - - public string ComponentName - { - get - { - return componentName; - } - } - #endregion - - private class SchemaOwnership - { - public bool initiallyOwned; - public bool currentlyOwned; - - public SchemaOwnership(bool initiallyOwned) - { - this.initiallyOwned = initiallyOwned; - this.currentlyOwned = initiallyOwned; - } - } - - private class RoleMembership - { - public bool initiallyAMember; - public bool currentlyAMember; - - public RoleMembership(bool initiallyAMember) - { - this.initiallyAMember = initiallyAMember; - this.currentlyAMember = initiallyAMember; - } - - public RoleMembership(bool initiallyAMember, bool currentlyAMember) - { - this.initiallyAMember = initiallyAMember; - this.currentlyAMember = currentlyAMember; - } - } - - - #region Constants - urn fields, etc... - private const string ownerField = "Owner"; - private const string schemaOwnerField = "Owner"; - private const string schemaNameField = "Name"; - private const string memberNameField = "Name"; - private const string memberUrnField = "Urn"; - #endregion - - #region Constants - grid columns positions, etc... - private const int colSchemasChecked = 0; - private const int colSchemasOwnedSchemas = 1; - - private const int colMembershipBitmap = 0; - private const int colMembershipRoleMembers = 1; - - private const int sizeCheckboxColumn = 20; - private const int sizeBitmapColumn = 20; - #endregion - - #region Non-UI variables - - private System.Xml.XmlDocument document = null; - - // info extracted from context - private string serverName; - private string databaseName; - private string dbroleName; - private string dbroleUrn; - - // initial values loaded from server - private string initialOwner; - - private string ownerName = String.Empty; - private string roleName = String.Empty; - private HybridDictionary schemaOwnership = null; - private HybridDictionary roleMembers = null; - - #endregion - - #region Properties: CreateNew/Properties mode - private bool IsPropertiesMode - { - get - { - return (dbroleName != null) && (dbroleName.Trim().Length != 0); - } - } - #endregion - - #region Constructors / Dispose - public DatabaseRoleGeneral() - { - // This call is required by the Windows.Forms Form Designer. - // InitializeComponent(); - } - - public DatabaseRoleGeneral(CDataContainer context) - { - // InitializeComponent(); - dataContainer = context; - - if (dataContainer != null) - { - document = dataContainer.Document; - } - else - { - document = null; - } - } - - #endregion - - #region Implementation: LoadData(), InitProp(), SendDataToServer() - - - /// - /// LoadData - /// - /// loads connection parameters from an xml - /// - /// - private void LoadData(XmlDocument doc) - { - // STrace.Params(ComponentName, "LoadData", "XmlDocument doc=\"{0}\"", doc.OuterXml); - - STParameters param; - bool bStatus; - - param = new STParameters(); - - param.SetDocument(doc); - - bStatus = param.GetParam("servername", ref this.serverName); - bStatus = param.GetParam("database", ref this.databaseName); - - bStatus = param.GetParam("role", ref this.dbroleName); - bStatus = param.GetParam("urn", ref this.dbroleUrn); - } - - - /// - /// InitProp - /// - /// talks with enumerator an retrievives info - /// - private void InitProp() - { - // STrace.Params(ComponentName, "InitProp", "", null); - - System.Diagnostics.Debug.Assert(this.serverName != null); - System.Diagnostics.Debug.Assert((this.databaseName != null) && (this.databaseName.Trim().Length != 0)); - - - // InitializeSchemasGridColumns(); - if (this.dataContainer.Server.Information.Version.Major >= 9) - { - LoadSchemas(); - // FillSchemasGrid(); - } - else - { - // panelSchema.Enabled = false; - } - - LoadMembership(); - // InitializeMembershipGridColumns(); - // FillMembershipGrid(); - - if (this.IsPropertiesMode == true) - { - // initialize from enumerator in properties mode - System.Diagnostics.Debug.Assert(this.dbroleName != null); - System.Diagnostics.Debug.Assert(this.dbroleName.Trim().Length != 0); - System.Diagnostics.Debug.Assert(this.dbroleUrn != null); - System.Diagnostics.Debug.Assert(this.dbroleUrn.Trim().Length != 0); - - // this.textBoxDbRoleName.Text = this.dbroleName; - - Enumerator en = new Enumerator(); - Request req = new Request(); - req.Fields = new String[] { DatabaseRoleGeneral.ownerField }; - - if ((this.dbroleUrn != null) && (this.dbroleUrn.Trim().Length != 0)) - { - req.Urn = this.dbroleUrn; - } - else - { - req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.databaseName) + "']/Role[@Name='" + Urn.EscapeString(this.dbroleName) + "]"; - } - - DataTable dt = en.Process(serverConnection, req); - System.Diagnostics.Debug.Assert(dt != null); - System.Diagnostics.Debug.Assert(dt.Rows.Count == 1); - - if (dt.Rows.Count == 0) - { - throw new Exception("DatabaseRoleSR.ErrorDbRoleNotFound"); - } - - DataRow dr = dt.Rows[0]; - this.initialOwner = Convert.ToString(dr[DatabaseRoleGeneral.ownerField], System.Globalization.CultureInfo.InvariantCulture); - // this.textBoxOwner.Text = this.initialOwner; - } - else - { - // initialize with empty values in create new mode - // this.textBoxDbRoleName.Text = String.Empty; - // this.textBoxOwner.Text = String.Empty; - } - - // update UI enable/disable controls - // EnableDisableControls(); - } - - - // public override void OnGatherUiInformation(RunType runType) - // { - // base.OnGatherUiInformation(runType); - - // this.ownerName = this.textBoxOwner.Text; - // this.roleName = this.textBoxDbRoleName.Text; - // } - - /// - /// SendDataToServer - /// - /// here we talk with server via smo and do the actual data changing - /// - private void SendDataToServer() - { - // STrace.Params(ComponentName, "SendDataToServer", "", null); - - // STrace.Assert(this.databaseName != null && this.databaseName.Trim().Length != 0, "database name is empty"); - // STrace.Assert(this.DataContainer.Server != null, "server is null"); - - Database database = this.dataContainer.Server.Databases[this.databaseName]; - // STrace.Assert(database!= null, "database is null"); - - DatabaseRole role = null; - - if (this.IsPropertiesMode == true) // in properties mode -> alter role - { - // STrace.Assert(this.dbroleName != null && this.dbroleName.Trim().Length != 0, "role name is empty"); - - role = database.Roles[this.dbroleName]; - // STrace.Assert(role != null, "role is null"); - - if (0 != String.Compare(this.ownerName, this.initialOwner, StringComparison.Ordinal)) - { - role.Owner = this.ownerName; - role.Alter(); - } - } - else // not in properties mode -> create role - { - role = new DatabaseRole(database, this.roleName); - if (this.ownerName.Length != 0) - { - role.Owner = this.ownerName; - } - - role.Create(); - } - - SendToServerSchemaOwnershipChanges(database, role); - SendToServerMembershipChanges(database, role); - - this.dataContainer.ObjectName = role.Name; - this.dataContainer.SqlDialogSubject = role; // needed by extended properties page - } - - #endregion - - // #region Update UI enable/disable controls - // private void EnableDisableControls() - // { - // if (this.DataContainer.Server.Information.Version.Major<9) - // { - // panelSchema.Enabled = false; - // } - - // if (this.IsPropertiesMode == true) - // { - // this.textBoxDbRoleName.Enabled = false; - - // this.AllUIEnabled = true; - // } - // else - // { - // this.textBoxDbRoleName.Enabled = true; - - // this.AllUIEnabled = (this.textBoxDbRoleName.Text.Trim().Length!=0); - // } - - // buttonRemove.Enabled = (gridRoleMembership.SelectedRow>=0); - // } - // #endregion - - // #region ISupportValidation Members - - // bool ISupportValidation.Validate() - // { - // if (IsPropertiesMode == false) - // { - // if (this.textBoxDbRoleName.Text.Trim().Length==0) - // { - // System.Exception e = new System.Exception(DatabaseRoleSR.Error_SpecifyAName); - // this.DisplayExceptionMessage(e); - - // return false; - // } - // } - - // return true; - // } - - // #endregion - - // #region Component Designer generated code - // /// - // /// Required method for Designer support - do not modify - // /// the contents of this method with the code editor. - // /// - // private void InitializeComponent() - // { - // System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DatabaseRoleGeneral)); - // this.panelEntireUserControl = new System.Windows.Forms.Panel(); - // this.panelSchema = new System.Windows.Forms.Panel(); - // this.gridSchemasOwned = new Microsoft.SqlServer.Management.SqlManagerUI.SqlManagerUIDlgGrid(); - // this.labelSchemasOwnedByDbRole = new System.Windows.Forms.Label(); - // this.panelMembership = new System.Windows.Forms.Panel(); - // this.buttonRemove = new System.Windows.Forms.Button(); - // this.buttonAdd = new System.Windows.Forms.Button(); - // this.gridRoleMembership = new Microsoft.SqlServer.Management.SqlManagerUI.SqlManagerUIDlgGrid(); - // this.labelMembersOfDbRole = new System.Windows.Forms.Label(); - // this.panelDbRoleGeneralInfo = new System.Windows.Forms.Panel(); - // this.buttonSearchOwner = new System.Windows.Forms.Button(); - // this.textBoxOwner = new System.Windows.Forms.TextBox(); - // this.labelDbRoleOwner = new System.Windows.Forms.Label(); - // this.textBoxDbRoleName = new System.Windows.Forms.TextBox(); - // this.labelDbRoleName = new System.Windows.Forms.Label(); - // this.panelEntireUserControl.SuspendLayout(); - // this.panelSchema.SuspendLayout(); - // ((System.ComponentModel.ISupportInitialize)(this.gridSchemasOwned)).BeginInit(); - // this.panelMembership.SuspendLayout(); - // ((System.ComponentModel.ISupportInitialize)(this.gridRoleMembership)).BeginInit(); - // this.panelDbRoleGeneralInfo.SuspendLayout(); - // this.SuspendLayout(); - // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - // // - // // panelEntireUserControl - // // - // this.panelEntireUserControl.Controls.Add(this.panelSchema); - // this.panelEntireUserControl.Controls.Add(this.panelMembership); - // this.panelEntireUserControl.Controls.Add(this.panelDbRoleGeneralInfo); - // resources.ApplyResources(this.panelEntireUserControl, "panelEntireUserControl"); - // this.panelEntireUserControl.Name = "panelEntireUserControl"; - // // - // // panelSchema - // // - // resources.ApplyResources(this.panelSchema, "panelSchema"); - // this.panelSchema.Controls.Add(this.gridSchemasOwned); - // this.panelSchema.Controls.Add(this.labelSchemasOwnedByDbRole); - // this.panelSchema.Name = "panelSchema"; - // // - // // gridSchemasOwned - // // - // resources.ApplyResources(this.gridSchemasOwned, "gridSchemasOwned"); - // this.gridSchemasOwned.BackColor = System.Drawing.SystemColors.Window; - // this.gridSchemasOwned.ForceEnabled = false; - // this.gridSchemasOwned.Name = "gridSchemasOwned"; - // this.gridSchemasOwned.MouseButtonClicked += new Microsoft.SqlServer.Management.UI.Grid.MouseButtonClickedEventHandler(this.gridSchemasOwned_MouseButtonClicked); - // // - // // labelSchemasOwnedByDbRole - // // - // resources.ApplyResources(this.labelSchemasOwnedByDbRole, "labelSchemasOwnedByDbRole"); - // this.labelSchemasOwnedByDbRole.Name = "labelSchemasOwnedByDbRole"; - // // - // // panelMembership - // // - // resources.ApplyResources(this.panelMembership, "panelMembership"); - // this.panelMembership.Controls.Add(this.buttonRemove); - // this.panelMembership.Controls.Add(this.buttonAdd); - // this.panelMembership.Controls.Add(this.gridRoleMembership); - // this.panelMembership.Controls.Add(this.labelMembersOfDbRole); - // this.panelMembership.Name = "panelMembership"; - // // - // // buttonRemove - // // - // resources.ApplyResources(this.buttonRemove, "buttonRemove"); - // this.buttonRemove.Name = "buttonRemove"; - // this.buttonRemove.Click += new System.EventHandler(this.buttonRemove_Click); - // // - // // buttonAdd - // // - // resources.ApplyResources(this.buttonAdd, "buttonAdd"); - // this.buttonAdd.Name = "buttonAdd"; - // this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click); - // // - // // gridRoleMembership - // // - // resources.ApplyResources(this.gridRoleMembership, "gridRoleMembership"); - // this.gridRoleMembership.BackColor = System.Drawing.SystemColors.Window; - // this.gridRoleMembership.ForceEnabled = false; - // this.gridRoleMembership.Name = "gridRoleMembership"; - // this.gridRoleMembership.SelectionChanged += new Microsoft.SqlServer.Management.UI.Grid.SelectionChangedEventHandler(this.gridRoleMembership_SelectionChanged); - // // - // // labelMembersOfDbRole - // // - // resources.ApplyResources(this.labelMembersOfDbRole, "labelMembersOfDbRole"); - // this.labelMembersOfDbRole.Name = "labelMembersOfDbRole"; - // // - // // panelDbRoleGeneralInfo - // // - // resources.ApplyResources(this.panelDbRoleGeneralInfo, "panelDbRoleGeneralInfo"); - // this.panelDbRoleGeneralInfo.Controls.Add(this.buttonSearchOwner); - // this.panelDbRoleGeneralInfo.Controls.Add(this.textBoxOwner); - // this.panelDbRoleGeneralInfo.Controls.Add(this.labelDbRoleOwner); - // this.panelDbRoleGeneralInfo.Controls.Add(this.textBoxDbRoleName); - // this.panelDbRoleGeneralInfo.Controls.Add(this.labelDbRoleName); - // this.panelDbRoleGeneralInfo.Name = "panelDbRoleGeneralInfo"; - // // - // // buttonSearchOwner - // // - // resources.ApplyResources(this.buttonSearchOwner, "buttonSearchOwner"); - // this.buttonSearchOwner.Name = "buttonSearchOwner"; - // this.buttonSearchOwner.Click += new System.EventHandler(this.buttonSearchOwner_Click); - // // - // // textBoxOwner - // // - // resources.ApplyResources(this.textBoxOwner, "textBoxOwner"); - // this.textBoxOwner.Name = "textBoxOwner"; - // // - // // labelDbRoleOwner - // // - // resources.ApplyResources(this.labelDbRoleOwner, "labelDbRoleOwner"); - // this.labelDbRoleOwner.Name = "labelDbRoleOwner"; - // // - // // textBoxDbRoleName - // // - // resources.ApplyResources(this.textBoxDbRoleName, "textBoxDbRoleName"); - // this.textBoxDbRoleName.Name = "textBoxDbRoleName"; - // // - // // labelDbRoleName - // // - // resources.ApplyResources(this.labelDbRoleName, "labelDbRoleName"); - // this.labelDbRoleName.Name = "labelDbRoleName"; - // // - // // DatabaseRoleGeneral - // // - // this.Controls.Add(this.panelEntireUserControl); - // this.Name = "DatabaseRoleGeneral"; - // resources.ApplyResources(this, "$this"); - // this.panelEntireUserControl.ResumeLayout(false); - // this.panelSchema.ResumeLayout(false); - // ((System.ComponentModel.ISupportInitialize)(this.gridSchemasOwned)).EndInit(); - // this.panelMembership.ResumeLayout(false); - // ((System.ComponentModel.ISupportInitialize)(this.gridRoleMembership)).EndInit(); - // this.panelDbRoleGeneralInfo.ResumeLayout(false); - // this.panelDbRoleGeneralInfo.PerformLayout(); - // this.ResumeLayout(false); - - // } - // #endregion - - #region Schemas - general operations with ... - /// - /// loads initial schemas from server together with information about the schema owner - /// - private void LoadSchemas() - { - this.schemaOwnership = new HybridDictionary(); - - Enumerator en = new Enumerator(); - Request req = new Request(); - req.Fields = new String[] { DatabaseRoleGeneral.schemaNameField, DatabaseRoleGeneral.schemaOwnerField }; - req.Urn = "Server/Database[@Name='" + Urn.EscapeString(this.databaseName) + "']/Schema"; - - DataTable dt = en.Process(serverConnection, req); - // STrace.Assert((dt != null) && (0 < dt.Rows.Count), "enumerator did not return schemas"); - // STrace.Assert(!this.IsPropertiesMode || (this.dbroleName.Length != 0), "role name is not known"); - - foreach (DataRow dr in dt.Rows) - { - string schemaName = Convert.ToString(dr[DatabaseRoleGeneral.schemaNameField], System.Globalization.CultureInfo.InvariantCulture); - string schemaOwner = Convert.ToString(dr[DatabaseRoleGeneral.schemaOwnerField], System.Globalization.CultureInfo.InvariantCulture); - bool roleOwnsSchema = - this.IsPropertiesMode && - (0 == String.Compare(this.dbroleName, schemaOwner, StringComparison.Ordinal)); - - this.schemaOwnership[schemaName] = new SchemaOwnership(roleOwnsSchema); - } - } - - - /// - /// initializes the columns and headers of schema grid - but doesnt populate grid with any data - /// - // private void InitializeSchemasGridColumns() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridSchemasOwned; - - // if (grid.RowsNumber != 0) - // { - // grid.DeleteAllRows(); - // } - - // while (grid.ColumnsNumber != 0) - // { - // grid.DeleteColumn(0); - // } - - // GridColumnInfo colInfo = null; - - // // checkbox owned/not-owned - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = sizeCheckboxColumn; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // colInfo.ColumnType = GridColumnType.Checkbox; - // grid.AddColumn(colInfo); - - // // schema name - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = grid.Width - sizeCheckboxColumn - 2; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // grid.AddColumn(colInfo); - - // grid.SetHeaderInfo(colSchemasOwnedSchemas, DatabaseRoleSR.HeaderOwnedSchemas, null); - - // grid.SelectionType = GridSelectionType.SingleRow; - // grid.UpdateGrid(); - // } - - // private void FillSchemasGrid() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridSchemasOwned; - - // grid.BeginInit(); - // grid.DeleteAllRows(); - - // IDictionaryEnumerator enumerator = this.schemaOwnership.GetEnumerator(); - // enumerator.Reset(); - // while (enumerator.MoveNext()) - // { - // DictionaryEntry entry = enumerator.Entry; - // GridCellCollection row = new GridCellCollection(); - // GridCell cell = null; - - // string schemaName = entry.Key.ToString(); - // bool roleCurrentlyOwnsSchema = ((SchemaOwnership)entry.Value).currentlyOwned; - - // // grid is filled either - // // a) disabled-checked checkboxes: Indeterminate - if already owning schema - we cannot renounce ownership - // // b) enabled-unchecked checkboxes: Unchecked - user can check / uncheck them and we read final state - // cell = new GridCell(roleCurrentlyOwnsSchema ? GridCheckBoxState.Indeterminate : GridCheckBoxState.Unchecked); - // row.Add(cell); - - // cell = new GridCell(schemaName); - // row.Add(cell); - - // grid.AddRow(row); - // } - - // grid.EndInit(); - - // if (grid.RowsNumber > 0) - // { - // grid.SelectedRow = 0; - // } - - // } - - /// - /// sends to server changes related to schema ownership - /// - private void SendToServerSchemaOwnershipChanges(Database db, DatabaseRole dbrole) - { - if (9 <= this.dataContainer.Server.Information.Version.Major) - { - IDictionaryEnumerator enumerator = this.schemaOwnership.GetEnumerator(); - enumerator.Reset(); - while (enumerator.MoveNext()) - { - DictionaryEntry de = enumerator.Entry; - string schemaName = de.Key.ToString(); - SchemaOwnership ownership = (SchemaOwnership)de.Value; - - // If we are creating a new role, then no schema will have been initially owned by this role. - // If we are modifying an existing role, we can only take ownership of roles. (Ownership can't - // be renounced, it can only be positively assigned to a principal.) - if (ownership.currentlyOwned && !ownership.initiallyOwned) - { - Schema schema = db.Schemas[schemaName]; - schema.Owner = dbrole.Name; - schema.Alter(); - } - } - } - } - - // private void gridSchemasOwned_MouseButtonClicked(object sender, Microsoft.SqlServer.Management.UI.Grid.MouseButtonClickedEventArgs args) - // { - // if ((args.Button == MouseButtons.Left) && - // (colSchemasChecked == args.ColumnIndex)) - // { - // int row = (int) args.RowIndex; - // string schemaName = this.gridSchemasOwned.GetCellInfo(row, colSchemasOwnedSchemas).CellData.ToString(); - // GridCheckBoxState newState = this.FlipCheckbox(this.gridSchemasOwned, row, colSchemasChecked); - // bool nowOwned = ((GridCheckBoxState.Checked == newState) || (GridCheckBoxState.Indeterminate == newState)); - - // ((SchemaOwnership) this.schemaOwnership[schemaName]).currentlyOwned = nowOwned; - // } - // } - - #endregion - - #region Membership - general operations with ... - - /// - /// loads from server initial membership information - /// - private void LoadMembership() - { - this.roleMembers = new HybridDictionary(); - - if (this.IsPropertiesMode) - { - Enumerator enumerator = new Enumerator(); - Urn urn = String.Format(System.Globalization.CultureInfo.InvariantCulture, - "Server/Database[@Name='{0}']/Role[@Name='{1}']/Member", - Urn.EscapeString(this.databaseName), - Urn.EscapeString(this.dbroleName)); - string[] fields = new string[] { DatabaseRoleGeneral.memberNameField }; - OrderBy[] orderBy = new OrderBy[] { new OrderBy(DatabaseRoleGeneral.memberNameField, OrderBy.Direction.Asc) }; - Request request = new Request(urn, fields, orderBy); - DataTable dt = enumerator.Process(this.serverConnection, request); - - foreach (DataRow dr in dt.Rows) - { - string memberName = dr[DatabaseRoleGeneral.memberNameField].ToString(); - this.roleMembers[memberName] = new RoleMembership(true); - } - } - } - - /// - /// initialize grid column headers, but not the content - /// - // private void InitializeMembershipGridColumns() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridRoleMembership; - - // if (grid.RowsNumber != 0) - // { - // grid.DeleteAllRows(); - // } - - // while (grid.ColumnsNumber != 0) - // { - // grid.DeleteColumn(0); - // } - - // GridColumnInfo colInfo = null; - - // // bitmap member type - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = sizeBitmapColumn; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // colInfo.ColumnType = GridColumnType.Bitmap; - // grid.AddColumn(colInfo); - - // // member name - // colInfo = new GridColumnInfo(); - // colInfo.ColumnWidth = grid.Width - sizeBitmapColumn - 2; - // colInfo.WidthType = GridColumnWidthType.InPixels; - // grid.AddColumn(colInfo); - - // grid.SetHeaderInfo(colMembershipRoleMembers, DatabaseRoleSR.HeaderRoleMembers, null); - - // grid.SelectionType = GridSelectionType.SingleRow; - // grid.UpdateGrid(); - // } - - /// - /// fills the membership grid with data (bitmaps, names, etc) - /// - // private void FillMembershipGrid() - // { - // Microsoft.SqlServer.Management.UI.Grid.DlgGridControl grid = this.gridRoleMembership; - - // grid.BeginInit(); - // grid.DeleteAllRows(); - - // IDictionaryEnumerator enumerator = this.roleMembers.GetEnumerator(); - // enumerator.Reset(); - // while (enumerator.MoveNext()) - // { - // DictionaryEntry entry = enumerator.Entry; - // string memberName = entry.Key.ToString(); - // RoleMembership membership = (RoleMembership) entry.Value; - - // if (membership.currentlyAMember) - // { - // GridCellCollection row = new GridCellCollection(); - // GridCell cell = null; - - // cell = new GridCell(bitmapMember); - // row.Add(cell); - - // cell = new GridCell(memberName); - // row.Add(cell); - - // grid.AddRow(row); - // } - // } - - // grid.EndInit(); - - // if (grid.RowsNumber > 0) - // { - // grid.SelectedRow = 0; - // } - // } - - /// - /// sends to server user changes related to membership - /// - private void SendToServerMembershipChanges(Database db, DatabaseRole dbrole) - { - IDictionaryEnumerator enumerator = this.roleMembers.GetEnumerator(); - enumerator.Reset(); - - while (enumerator.MoveNext()) - { - DictionaryEntry entry = enumerator.Entry; - string memberName = entry.Key.ToString(); - RoleMembership membership = (RoleMembership)entry.Value; - - if (!membership.initiallyAMember && membership.currentlyAMember) - { - dbrole.AddMember(memberName); - } - else if (membership.initiallyAMember && !membership.currentlyAMember) - { - dbrole.DropMember(memberName); - } - } - } - - // private void gridRoleMembership_SelectionChanged(object sender, Microsoft.SqlServer.Management.UI.Grid.SelectionChangedEventArgs args) - // { - // EnableDisableControls(); - // } - - // private void buttonAdd_Click(object sender, System.EventArgs e) - // { - - // using (SqlObjectSearch dlg = new SqlObjectSearch( - // this.Font, - // iconSearchRolesAndUsers, - // this.HelpProvider, - // DatabaseRoleSR.Add_DialogTitle, - // this.DataContainer.ConnectionInfo, - // this.databaseName, - // new SearchableObjectTypeCollection(SearchableObjectType.User, SearchableObjectType.DatabaseRole), - // new SearchableObjectTypeCollection(SearchableObjectType.User, SearchableObjectType.DatabaseRole), - // false)) - // { - // if (DialogResult.OK == dlg.ShowDialog(this.FindForm())) - // { - // bool memberAdded = false; - - // this.gridRoleMembership.BeginInit(); - - // foreach (SearchableObject principal in dlg.SearchResults) - // { - // if (!this.roleMembers.Contains(principal.Name)) - // { - // this.roleMembers[principal.Name] = new RoleMembership(false, true); - // memberAdded = true; - // } - // else - // { - // RoleMembership membership = (RoleMembership) this.roleMembers[principal.Name]; - - // if (!membership.currentlyAMember) - // { - // membership.currentlyAMember = true; - // memberAdded = true; - // } - // } - - // if (memberAdded) - // { - // GridCellCollection row = new GridCellCollection(); - // GridCell cell = null; - - // cell = new GridCell(bitmapMember); - // row.Add(cell); - - // cell = new GridCell(principal.Name); - // row.Add(cell); - - // this.gridRoleMembership.AddRow(row); - // } - // } - - // this.gridRoleMembership.EndInit(); - - // if (memberAdded) - // { - // this.gridRoleMembership.SelectedRow = this.gridRoleMembership.RowsNumber - 1; - // } - // } - // } - // } - - // private void buttonRemove_Click(object sender, System.EventArgs e) - // { - // DlgGridControl grid = this.gridRoleMembership; - - // int row = this.gridRoleMembership.SelectedRow; - // STrace.Assert(0 <= row, "unexpected row number"); - - // if (0 <= row) - // { - // string memberName = this.gridRoleMembership.GetCellInfo(row, colMembershipRoleMembers).CellData.ToString(); - // RoleMembership membership = (RoleMembership) this.roleMembers[memberName]; - - // if (membership.initiallyAMember) - // { - // membership.currentlyAMember = false; - // } - // else - // { - // this.roleMembers.Remove(memberName); - // } - - // this.gridRoleMembership.DeleteRow(row); - // } - // } - #endregion - - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ExtenedPropertyInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ExtenedPropertyInfo.cs new file mode 100644 index 00000000..dc978c24 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ExtenedPropertyInfo.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various application role properties + /// + public class ExtendedPropertyInfo + { + public string? Name { get; set; } + public string? Value { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleActions.cs new file mode 100644 index 00000000..aa6bf7ff --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleActions.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.ServiceLayer.Management; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + internal class ServerRoleActions : ManagementActionBase + { + private ConfigAction configAction; + + private ServerRolePrototype prototype; + + /// + /// Handle login create and update actions + /// + public ServerRoleActions(CDataContainer dataContainer, ConfigAction configAction, ServerRolePrototype prototype) + { + this.DataContainer = dataContainer; + this.configAction = configAction; + this.prototype = prototype; + } + + /// + /// called by the management actions framework to execute the action + /// + /// + public override void OnRunNow(object sender) + { + if (this.configAction != ConfigAction.Drop) + { + prototype.SendDataToServer(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleData.cs new file mode 100644 index 00000000..8ea8f364 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleData.cs @@ -0,0 +1,499 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable + +using System; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Management; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// ServerRoleGeneral - main app role page + /// + internal class ServerRolePrototype + { + #region Members + + /// + /// data container member that contains data specific information like + /// connection infor, SMO server object or an AMO server object as well + /// as a hash table where one can manipulate custom data + /// + private CDataContainer dataContainer = null; + + private bool exists; + private ServerRolePrototypeData currentState; + private ServerRolePrototypeData originalState; + + #endregion + + #region Trace support + private const string componentName = "ServerRoleGeneral"; + + public string ComponentName + { + get + { + return componentName; + } + } + #endregion + + #region Properties: CreateNew/Properties mode + public string Name + { + get + { + return this.currentState.ServerRoleName; + } + set + { + this.currentState.ServerRoleName = value; + } + } + + public string Owner + { + get + { + return this.currentState.Owner; + } + set + { + this.currentState.Owner = value; + } + } + + public List Members + { + get + { + return this.currentState.Members; + } + set + { + this.currentState.Members = value; + } + } + + public List Memberships + { + get + { + return this.currentState.Memberships; + } + set + { + this.currentState.Memberships = value; + } + } + + public bool IsYukonOrLater + { + get + { + return this.dataContainer.Server.VersionMajor >= 9; + } + } + + public bool IsFixedRole + { + get + { + return this.currentState.IsFixedRole; + } + } + #endregion + + #region Constructors / Dispose + public ServerRolePrototype(CDataContainer context) + { + this.exists = false; + this.dataContainer = context; + this.currentState = new ServerRolePrototypeData(context); + this.originalState = (ServerRolePrototypeData)this.currentState.Clone(); + } + + /// + /// ServerRoleData for creating a new app role + /// + public ServerRolePrototype(CDataContainer context, ServerRoleInfo roleInfo) + { + this.exists = false; + this.dataContainer = context; + this.currentState = new ServerRolePrototypeData(context); + this.originalState = (ServerRolePrototypeData)this.currentState.Clone(); + + this.ApplyInfoToPrototype(roleInfo); + } + + /// + /// ServerRoleData for editing an existing app role + /// + public ServerRolePrototype(CDataContainer context, ServerRole role) + { + this.exists = true; + this.dataContainer = context; + this.currentState = new ServerRolePrototypeData(context, role); + this.originalState = (ServerRolePrototypeData)this.currentState.Clone(); + } + + #endregion + + #region Implementation: SendDataToServer() + /// + /// SendDataToServer + /// + /// here we talk with server via smo and do the actual data changing + /// + public void SendDataToServer() + { + Microsoft.SqlServer.Management.Smo.Server srv = this.dataContainer.Server; + System.Diagnostics.Debug.Assert(srv != null, "server object is null"); + + ServerRole serverRole = null; + if (this.exists) // in properties mode -> alter role + { + System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(this.Name), "serverRoleName is empty"); + + serverRole = srv.Roles[this.Name]; + System.Diagnostics.Debug.Assert(serverRole != null, "serverRole object is null"); + + if (0 != String.Compare(this.currentState.Owner, this.originalState.Owner, StringComparison.Ordinal)) + { + serverRole.Owner = this.Owner; + serverRole.Alter(); + } + } + else // not in properties mode -> create role + { + serverRole = new ServerRole(srv, this.Name); + if (this.Owner.Length != 0) + { + serverRole.Owner = this.Owner; + } + serverRole.Create(); + } + + SendToServerMemberChanges(serverRole); + SendToServerMembershipChanges(serverRole); + } + #endregion + + /// + /// sends to server user changes related to members + /// + private void SendToServerMemberChanges(ServerRole serverRole) + { + if (!this.exists) + { + foreach (string member in this.Members) + { + serverRole.AddMember(member); + } + } + else + { + foreach (string member in this.Members) + { + if (!this.originalState.Members.Contains(member)) + { + serverRole.AddMember(member); + } + } + + foreach (string member in this.originalState.Members) + { + if (!this.Members.Contains(member)) + { + serverRole.DropMember(member); + } + } + } + } + + /// + /// sends to server user changes related to memberships + /// + private void SendToServerMembershipChanges(ServerRole serverRole) + { + if (!this.exists) + { + foreach (string role in this.Memberships) + { + serverRole.AddMembershipToRole(this.dataContainer.Server.Roles[role].Name); + } + } + else + { + foreach (string role in this.Memberships) + { + if (!this.originalState.Memberships.Contains(role)) + { + serverRole.AddMembershipToRole(this.dataContainer.Server.Roles[role].Name); + } + } + + foreach (string role in this.originalState.Memberships) + { + if (!this.Memberships.Contains(role)) + { + serverRole.DropMembershipFromRole(this.dataContainer.Server.Roles[role].Name); + } + } + } + } + + + public void ApplyInfoToPrototype(ServerRoleInfo roleInfo) + { + this.Name = roleInfo.Name; + this.Owner = roleInfo.Owner; + this.Members = roleInfo.Members.ToList(); + this.Memberships = roleInfo.Memberships.ToList(); + } + + private class ServerRolePrototypeData : ICloneable + { + #region data members + private string serverRoleName = string.Empty; + private string owner = String.Empty; + private bool initialized = false; + private List members = new List(); + private List memberships = new List(); + private ServerRole role = null; + private ServerRoleExtender extender = null; + private Server server = null; + private CDataContainer context = null; + private bool isYukonOrLater = false; + #endregion + + #region Properties + + // General properties + + + public string ServerRoleName + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.serverRoleName; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.serverRoleName = value; + } + } + + public ServerRole ServerRole + { + get + { + return this.role; + } + } + + public string Owner + { + get + { + if (!this.initialized) + { + LoadData(); + } + + return this.owner; + } + + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.owner = value; + } + } + + public List Members + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.members; + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.members = value; + } + } + + public List Memberships + { + get + { + if (!this.initialized) + { + LoadData(); + } + return this.memberships; + } + set + { + System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization"); + this.memberships = value; + } + } + + public bool Exists + { + get + { + return (this.role != null); + } + } + + public Microsoft.SqlServer.Management.Smo.Server Server + { + get + { + return this.server; + } + } + + public bool IsYukonOrLater + { + get + { + return this.isYukonOrLater; + } + } + + public bool IsFixedRole + { + get + { + return this.role != null && this.role.IsFixedRole; + } + } + + #endregion + + /// + /// private default constructor - used by Clone() + /// + private ServerRolePrototypeData() + { + } + + /// + /// constructor + /// + /// The context in which we are creating a new serverRole + public ServerRolePrototypeData(CDataContainer context) + { + this.server = context.Server; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + LoadData(); + } + + /// + /// constructor + /// + /// The context in which we are modifying an existing serverRole + /// The serverRole we are modifying + public ServerRolePrototypeData(CDataContainer context, ServerRole serverRole) + { + this.server = context.Server; + this.context = context; + this.isYukonOrLater = (this.server.Information.Version.Major >= 9); + this.role = serverRole; + this.extender = new ServerRoleExtender(this.role); + LoadData(); + } + + /// + /// Create a clone of this ServerRolePrototypeData object + /// + /// The clone ServerRolePrototypeData object + public object Clone() + { + ServerRolePrototypeData result = new ServerRolePrototypeData(); + result.serverRoleName = this.serverRoleName; + result.initialized = this.initialized; + result.members = new List(this.members); + result.memberships = new List(this.memberships); + result.role = this.role; + result.extender = this.extender; + result.owner = this.owner; + result.server = this.server; + return result; + } + + private void LoadData() + { + this.initialized = true; + + if (this.Exists) + { + LoadExisting(); + } + else + { + LoadNew(); + } + } + + private void LoadExisting() + { + System.Diagnostics.Debug.Assert(server != null, "server is null"); + System.Diagnostics.Debug.Assert(role != null, "app role is null"); + this.serverRoleName = role.Name; + this.owner = role.Owner; + LoadMembers(); + LoadMemberships(); + } + + private void LoadNew() + { + } + + private void LoadMembers() + { + if (this.Exists) + { + foreach (string memberName in this.role.EnumMemberNames()) + { + this.members.Add(memberName); + } + } + } + + private void LoadMemberships() + { + foreach (ServerRole srvRole in this.server.Roles) + { + if (srvRole.EnumMemberNames().Contains(this.role.Name)) + { + this.memberships.Add(srvRole.Name); + } + } + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs deleted file mode 100644 index 4b1da0be..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs +++ /dev/null @@ -1,327 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using System; -using System.Collections.Generic; -using Microsoft.SqlServer.Management.Sdk.Sfc; -using Microsoft.SqlServer.Management.Smo; -using System.ComponentModel; -using System.Collections.Specialized; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlTools.ServiceLayer.Management; - -namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement -{ - public class ServerRoleManageTaskFormComponent - { - protected ServerRole serverRole; - ServerRoleExtender extender; - - ServerRole instance; - Server server; - - private void InitializeComponent() - { - components = new System.ComponentModel.Container(); - } - - public ServerRoleManageTaskFormComponent() - { - InitializeComponent(); - } - - public ServerRoleManageTaskFormComponent(IContainer container) - { - // container.Add(this); - InitializeComponent(); - } - - protected string MethodName - { - get - { - return "Alter"; - } - } - - protected ServerRole Instance - { - get - { -#pragma warning disable IDE0074 // Use compound assignment - if (this.instance == null) - { - this.instance = CreateSmoObjectInstance(); - } -#pragma warning restore IDE0074 // Use compound assignment - return this.instance; - } - } - - protected Server Server - { - get - { - return this.server; - } - } - - protected Microsoft.SqlServer.Management.Sdk.Sfc.ISfcPropertyProvider CreatePropertyProvider() - { -#pragma warning disable IDE0074 // Use compound assignment - if (extender == null) - { - extender = new ServerRoleExtender(this.Instance); - } -#pragma warning restore IDE0074 // Use compound assignment - return extender; - } - - protected ServerRole CreateSmoObjectInstance() - { - if (this.serverRole == null) - { - Urn urn; - // if (!SmoTaskHelper.TryGetUrn(this.TaskManager.Context, out urn)) - if (true) // TODO new server role - { - this.serverRole = new ServerRole(this.Server, GetServerRoleName()); - } - else - { - this.serverRole = this.Server.GetSmoObject(urn) as ServerRole; - } - } - return this.serverRole; - } - - protected void PerformTask() - { - this.CreateOrManage(); - - //General Page Permissions Related actions - EventHandler tempEventHandler = (EventHandler)this.extender.GeneralPageOnRunNow; - if (tempEventHandler != null) - { - tempEventHandler(this, new EventArgs()); - } - - //Disposing DataContainer object as it has a dedicated server connection. - if (this.extender.GeneralPageDataContainer != null) - { - ((CDataContainer)this.extender.GeneralPageDataContainer).Dispose(); - } - } - - private void CreateOrManage() - { - //General Page Actions - if (Utils.IsSql11OrLater(this.Instance.ServerVersion.Major)) - { - if (this.Instance.State == SqlSmoState.Creating) - { - if (this.extender.OwnerForUI == string.Empty) //In order to avoid scripting Authorization part of ddl. - { - this.extender.OwnerForUI = null; - } - this.Instance.Create(); - } - else - { - this.Instance.Alter(); - } - } - - //Members Page Related actions - this.extender.RefreshRoleMembersHash(); - Dictionary memberNameIsMemberHash = this.extender.MemberNameIsMemberHash; - - StringCollection membersToBeDropped = new StringCollection(); - StringCollection membersToBeAdded = new StringCollection(); - - StringCollection membershipsToBeDropped = new StringCollection(); - StringCollection membershipsToBeAdded = new StringCollection(); - - foreach (string memberName in memberNameIsMemberHash.Keys) - { - if (memberNameIsMemberHash[memberName]) //if added as member - { - membersToBeAdded.Add(memberName); - } - else //if dropped from members - { - membersToBeDropped.Add(memberName); - } - } - - //Membership page Related actions - this.extender.RefreshServerRoleNameHasMembershipHash(); - Dictionary membershipInfoHash = this.extender.ServerRoleNameHasMembershipHash; - - foreach (string serverRoleName in membershipInfoHash.Keys) - { - if (membershipInfoHash[serverRoleName]) //If new membership added - { - membershipsToBeAdded.Add(serverRoleName); - } - else //If now not a member of - { - membershipsToBeDropped.Add(serverRoleName); - } - } - - //First dropping members and memberships - foreach (string member in membersToBeDropped) - { - this.serverRole.DropMember(member); - } - - foreach (string containingRole in membershipsToBeDropped) - { - this.serverRole.DropMembershipFromRole(containingRole); - } - - //Now adding members and memberships. - foreach (string member in membersToBeAdded) - { - this.serverRole.AddMember(member); - } - - foreach (string containingRole in membershipsToBeAdded) - { - this.serverRole.AddMembershipToRole(containingRole); - } - } - - // protected System.Collections.Specialized.StringCollection GetScriptStrings(ITaskExecutionContext context) - // { - // StringCollection script = this.GetScriptForCreateOrManage(); - - // StringCollection permissionScript = this.GetPermissionRelatedScripts(); - - // foreach (string str in permissionScript) - // { - // script.Add(str); - // } - - // if (script.Count == 0) - // { - // //When the user tries to script and no changes have been made. - // // throw new SsmsException(SR.NoActionToBeScripted); - // } - // return script; - // } - - private StringCollection GetPermissionRelatedScripts() - { - StringCollection script = new StringCollection(); - - if (this.extender.GeneralPageDataContainer != null) //Permission controls have been initialized. - { - //For General Page permissions - ServerConnection permControlConn = ((CDataContainer)this.extender.GeneralPageDataContainer).ServerConnection; - SqlExecutionModes em = permControlConn.SqlExecutionModes; - - //PermissionUI has a cloned connection. - permControlConn.CapturedSql.Clear(); - permControlConn.SqlExecutionModes = SqlExecutionModes.CaptureSql; - - //This will run General page's permission related actions. - EventHandler tempEventHandler = (EventHandler)this.extender.GeneralPageOnRunNow; - if (tempEventHandler != null) - { - tempEventHandler(this, new EventArgs()); - } - - script = permControlConn.CapturedSql.Text; - permControlConn.SqlExecutionModes = em; - } - - return script; - } - - /// - /// Generates script for Create and Properties. - /// - /// - private StringCollection GetScriptForCreateOrManage() - { - StringCollection script = new StringCollection(); - - Server svr = this.Instance.Parent; - svr.ConnectionContext.CapturedSql.Clear(); - SqlExecutionModes em = svr.ConnectionContext.SqlExecutionModes; - - svr.ConnectionContext.SqlExecutionModes = SqlExecutionModes.CaptureSql; - - //T-SQL Capturing starts. - - this.CreateOrManage(); - - //T-SQL capturing ends - script = svr.ConnectionContext.CapturedSql.Text; - svr.ConnectionContext.SqlExecutionModes = em; - - return script; - } - - protected string GetServerRoleName() - { - return "ServerRole-" + DateTime.Now.ToString("yyyyMMdd-HHmmss",SmoApplication.DefaultCulture); - } - - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected void Dispose(bool disposing) - { - // if (disposing && (components != null)) - // { - // components.Dispose(); - // } - // base.Dispose(disposing); - } - } - - public class ServerRoleCreateTaskFormComponent : ServerRoleManageTaskFormComponent - { - public ServerRoleCreateTaskFormComponent() - : base() - { - } - - public ServerRoleCreateTaskFormComponent(IContainer container) - : base(container) - { - } - - protected ServerRole CreateSmoObjectInstance() - { -#pragma warning disable IDE0074 // Use compound assignment - if (this.serverRole == null) - { - this.serverRole = new ServerRole(this.Server, GetServerRoleName()); - } -#pragma warning restore IDE0074 // Use compound assignment - return this.serverRole; - } - - protected string MethodName - { - get - { - return "Create"; - } - } - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleHandler.cs new file mode 100644 index 00000000..c483559d --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleHandler.cs @@ -0,0 +1,157 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#nullable disable +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Management; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// ServerRole object type handler + /// + public class ServerRoleHandler : ObjectTypeHandler + { + public ServerRoleHandler(ConnectionService connectionService) : base(connectionService) { } + + public override bool CanHandleType(SqlObjectType objectType) + { + return objectType == SqlObjectType.ServerRole; + } + + public override Task InitializeObjectView(InitializeViewRequestParams parameters) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(parameters.ConnectionUri, out connInfo); + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + + ServerRolePrototype prototype = parameters.IsNewObject + ? new ServerRolePrototype(dataContainer) + : new ServerRolePrototype(dataContainer, dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as ServerRole); + + List serverRoles = new List(); + for (int i = 0; i < dataContainer.Server.Roles.Count; i++) + { + var role = dataContainer.Server.Roles[i].Name; + // Cannot add member to public, sysadmin and self + if (role != "public" && role != "sysadmin" && role != prototype.Name) + { + serverRoles.Add(role); + } + } + + ServerRoleInfo ServerRoleInfo = new ServerRoleInfo() + { + Name = prototype.Name, + Owner = prototype.Owner, + Members = prototype.Members.ToArray(), + Memberships = prototype.Memberships.ToArray() + }; + + var viewInfo = new ServerRoleViewInfo() + { + ObjectInfo = ServerRoleInfo, + IsFixedRole = prototype.IsFixedRole, + ServerRoles = serverRoles.ToArray() + }; + + var context = new ServerRoleViewContext(parameters); + return Task.FromResult(new InitializeViewResult() + { + ViewInfo = viewInfo, + Context = context + }); + } + + public override Task Save(ServerRoleViewContext context, ServerRoleInfo obj) + { + if (context.Parameters.IsNewObject) + { + this.DoHandleCreateServerRoleRequest(context, obj, RunType.RunNow); + } + else + { + this.DoHandleUpdateServerRoleRequest(context, obj, RunType.RunNow); + } + return Task.CompletedTask; + } + + public override Task Script(ServerRoleViewContext context, ServerRoleInfo obj) + { + string script; + if (context.Parameters.IsNewObject) + { + script = this.DoHandleCreateServerRoleRequest(context, obj, RunType.ScriptToWindow); + } + else + { + script = this.DoHandleUpdateServerRoleRequest(context, obj, RunType.ScriptToWindow); + } + return Task.FromResult(script); + } + + private string ConfigureServerRole(CDataContainer dataContainer, ConfigAction configAction, RunType runType, ServerRolePrototype prototype) + { + string sqlScript = string.Empty; + using (var actions = new ServerRoleActions(dataContainer, configAction, prototype)) + { + var executionHandler = new ExecutonHandler(actions); + executionHandler.RunNow(runType, this); + if (executionHandler.ExecutionResult == ExecutionMode.Failure) + { + throw executionHandler.ExecutionFailureException; + } + + if (runType == RunType.ScriptToWindow) + { + sqlScript = executionHandler.ScriptTextFromLastRun; + } + } + + return sqlScript; + } + + private string DoHandleUpdateServerRoleRequest(ServerRoleViewContext context, ServerRoleInfo serverRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + ServerRolePrototype prototype = new ServerRolePrototype(dataContainer, dataContainer.Server.Roles[serverRoleInfo.Name]); + prototype.ApplyInfoToPrototype(serverRoleInfo); + return ConfigureServerRole(dataContainer, ConfigAction.Update, runType, prototype); + + } + + private string DoHandleCreateServerRoleRequest(ServerRoleViewContext context, ServerRoleInfo serverRoleInfo, RunType runType) + { + ConnectionInfo connInfo; + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + + if (connInfo == null) + { + throw new ArgumentException("Invalid ConnectionUri"); + } + + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + + ServerRolePrototype prototype = new ServerRolePrototype(dataContainer, serverRoleInfo); + return ConfigureServerRole(dataContainer, ConfigAction.Create, runType, prototype); + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleInfo.cs new file mode 100644 index 00000000..e4b63c3a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleInfo.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various server role properties + /// + public class ServerRoleInfo : SqlObject + { + public string? Owner { get; set; } + public string[]? Members { get; set; } + public string[]? Memberships { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewContext.cs new file mode 100644 index 00000000..4eb5eba0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewContext.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + public class ServerRoleViewContext : SqlObjectViewContext + { + public ServerRoleViewContext(Contracts.InitializeViewRequestParams parameters) : base(parameters) + { + } + + public override void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewInfo.cs new file mode 100644 index 00000000..e62407d4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/ServerRole/ServerRoleViewInfo.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + /// + /// a class for storing various server role view properties + /// + public class ServerRoleViewInfo : SqlObjectViewInfo + { + public bool IsFixedRole { get; set; } + public string[]? ServerRoles { get; set; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs index 360d3379..2f4417a5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs @@ -12,12 +12,18 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement [JsonConverter(typeof(StringEnumConverter))] public enum SqlObjectType { + [EnumMember(Value = "ApplicationRole")] + ApplicationRole, [EnumMember(Value = "Column")] Column, [EnumMember(Value = "Credential")] Credential, + [EnumMember(Value = "DatabaseRole")] + DatabaseRole, [EnumMember(Value = "ServerLevelLogin")] ServerLevelLogin, + [EnumMember(Value = "ServerLevelServerRole")] + ServerRole, [EnumMember(Value = "Table")] Table, [EnumMember(Value = "User")]