Simplify Object Management APIs (#2015)

* unify requests-wip

* wip

* unify api

* fix test

* add credential handler

* fix credential handler issue.

* generic type update

* fix scripting for user
This commit is contained in:
Alan Ren
2023-04-19 15:43:01 -07:00
committed by GitHub
parent 98ad0197e4
commit e314f839d8
57 changed files with 1802 additions and 2234 deletions

View File

@@ -0,0 +1,23 @@
//
// 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.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class DisposeViewRequestParams : GeneralRequestDetails
{
public string ContextId { get; set; }
}
public class DisposeViewRequestResponse { }
public class DisposeViewRequest
{
public static readonly RequestType<DisposeViewRequestParams, DisposeViewRequestResponse> Type = RequestType<DisposeViewRequestParams, DisposeViewRequestResponse>.Create("objectManagement/disposeView");
}
}

View File

@@ -11,6 +11,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class DropRequestParams : GeneralRequestDetails
{
/// <summary>
/// The object type.
/// </summary>
public SqlObjectType ObjectType { get; set; }
/// <summary>
/// SFC (SMO) URN identifying the object
/// </summary>
@@ -25,8 +29,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
public bool ThrowIfNotExist { get; set; } = false;
}
public class DropRequestResponse { }
public class DropRequest
{
public static readonly RequestType<DropRequestParams, bool> Type = RequestType<DropRequestParams, bool>.Create("objectManagement/drop");
public static readonly RequestType<DropRequestParams, DropRequestResponse> Type = RequestType<DropRequestParams, DropRequestResponse>.Create("objectManagement/drop");
}
}

View File

@@ -0,0 +1,48 @@
//
// 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.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class InitializeViewRequestParams : GeneralRequestDetails
{
/// <summary>
/// The connection uri.
/// </summary>
public string ConnectionUri { get; set; }
/// <summary>
/// The target database name.
/// </summary>
public string Database { get; set; }
/// <summary>
/// The object type.
/// </summary>
public SqlObjectType ObjectType { get; set; }
/// <summary>
/// Whether the view is for a new object.
/// </summary>
public bool IsNewObject { get; set; }
/// <summary>
/// The object view context id.
/// </summary>
public string ContextId { get; set; }
/// <summary>
/// Urn of the parent object.
/// </summary>
public string ParentUrn { get; set; }
/// <summary>
/// Urn of the object. Only set when the view is for an existing object.
/// </summary>
public string ObjectUrn { get; set; }
}
public class InitializeViewRequest
{
public static readonly RequestType<InitializeViewRequestParams, SqlObjectViewInfo> Type = RequestType<InitializeViewRequestParams, SqlObjectViewInfo>.Create("objectManagement/initializeView");
}
}

View File

@@ -11,6 +11,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class RenameRequestParams : GeneralRequestDetails
{
/// <summary>
/// The object type.
/// </summary>
public SqlObjectType ObjectType { get; set; }
/// <summary>
/// SFC (SMO) URN identifying the object
/// </summary>
@@ -24,8 +28,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
/// </summary>
public string ConnectionUri { get; set; }
}
public class RenameRequestResponse { }
public class RenameRequest
{
public static readonly RequestType<RenameRequestParams, bool> Type = RequestType<RenameRequestParams, bool>.Create("objectManagement/rename");
public static readonly RequestType<RenameRequestParams, RenameRequestResponse> Type = RequestType<RenameRequestParams, RenameRequestResponse>.Create("objectManagement/rename");
}
}

View File

@@ -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.
//
#nullable disable
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.Utility;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class SaveObjectRequestParams : GeneralRequestDetails
{
/// <summary>
/// The context id.
/// </summary>
public string ContextId { get; set; }
/// <summary>
/// The object information.
/// </summary>
public JToken Object { get; set; }
}
public class SaveObjectRequestResponse { }
public class SaveObjectRequest
{
public static readonly RequestType<SaveObjectRequestParams, SaveObjectRequestResponse> Type = RequestType<SaveObjectRequestParams, SaveObjectRequestResponse>.Create("objectManagement/save");
}
}

View File

@@ -0,0 +1,29 @@
//
// 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.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.Utility;
using Newtonsoft.Json.Linq;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts
{
public class ScriptObjectRequestParams : GeneralRequestDetails
{
/// <summary>
/// The context id.
/// </summary>
public string ContextId { get; set; }
/// <summary>
/// The object information.
/// </summary>
public JToken Object { get; set; }
}
public class ScriptObjectRequest
{
public static readonly RequestType<ScriptObjectRequestParams, string> Type = RequestType<ScriptObjectRequestParams, string>.Create("objectManagement/script");
}
}

View File

@@ -6,15 +6,11 @@
#nullable disable
using System;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
@@ -23,12 +19,21 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
/// </summary>
public class ObjectManagementService
{
private const string ObjectManagementServiceApplicationName = "azdata-object-management";
public const string ApplicationName = "azdata-object-management";
private static Lazy<ObjectManagementService> objectManagementServiceInstance = new Lazy<ObjectManagementService>(() => new ObjectManagementService());
public static ObjectManagementService Instance => objectManagementServiceInstance.Value;
public static ConnectionService connectionService;
private IProtocolEndpoint serviceHost;
public ObjectManagementService() { }
private List<IObjectTypeHandler> objectTypeHandlers = new List<IObjectTypeHandler>();
private ConcurrentDictionary<string, SqlObjectViewContext> contextMap = new ConcurrentDictionary<string, SqlObjectViewContext>();
public ObjectManagementService()
{
this.objectTypeHandlers.Add(new CommonObjectTypeHandler(ConnectionService.Instance));
this.objectTypeHandlers.Add(new LoginHandler(ConnectionService.Instance));
this.objectTypeHandlers.Add(new UserHandler(ConnectionService.Instance));
this.objectTypeHandlers.Add(new CredentialHandler(ConnectionService.Instance));
}
/// <summary>
/// Internal for testing purposes only
@@ -51,84 +56,81 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
this.serviceHost = serviceHost;
this.serviceHost.SetRequestHandler(RenameRequest.Type, HandleRenameRequest, true);
this.serviceHost.SetRequestHandler(DropRequest.Type, HandleDropRequest, true);
this.serviceHost.SetRequestHandler(InitializeViewRequest.Type, HandleInitializeViewRequest, true);
this.serviceHost.SetRequestHandler(SaveObjectRequest.Type, HandleSaveObjectRequest, true);
this.serviceHost.SetRequestHandler(ScriptObjectRequest.Type, HandleScriptObjectRequest, true);
this.serviceHost.SetRequestHandler(DisposeViewRequest.Type, HandleDisposeViewRequest, true);
}
/// <summary>
/// Method to handle the renaming operation
/// </summary>
/// <param name="requestParams">parameters which are needed to execute renaming operation</param>
/// <param name="requestContext">Request Context</param>
/// <returns></returns>
internal async Task HandleRenameRequest(RenameRequestParams requestParams, RequestContext<bool> requestContext)
internal async Task HandleRenameRequest(RenameRequestParams requestParams, RequestContext<RenameRequestResponse> requestContext)
{
Logger.Verbose("Handle Request in HandleRenameRequest()");
ExecuteActionOnObject(requestParams.ConnectionUri, requestParams.ObjectUrn, (dbObject) =>
{
var renamable = dbObject as IRenamable;
if (renamable != null)
{
renamable.Rename(requestParams.NewName);
}
else
{
throw new Exception(SR.ObjectNotRenamable(requestParams.ObjectUrn));
}
});
await requestContext.SendResult(true);
var handler = this.GetObjectTypeHandler(requestParams.ObjectType);
await handler.Rename(requestParams.ConnectionUri, requestParams.ObjectUrn, requestParams.NewName);
await requestContext.SendResult(new RenameRequestResponse());
}
/// <summary>
/// Method to handle the delete object request
/// </summary>
/// <param name="requestParams">parameters which are needed to execute deletion operation</param>
/// <param name="requestContext">Request Context</param>
/// <returns></returns>
internal async Task HandleDropRequest(DropRequestParams requestParams, RequestContext<bool> requestContext)
internal async Task HandleDropRequest(DropRequestParams requestParams, RequestContext<DropRequestResponse> requestContext)
{
Logger.Verbose("Handle Request in HandleDeleteRequest()");
ConnectionInfo connectionInfo = this.GetConnectionInfo(requestParams.ConnectionUri);
using (CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfo, databaseExists: true))
{
try
{
dataContainer.SqlDialogSubject = dataContainer.Server?.GetSmoObject(requestParams.ObjectUrn);
DatabaseUtils.DoDropObject(dataContainer);
}
catch (FailedOperationException ex)
{
if (!(ex.InnerException is MissingObjectException) || (ex.InnerException is MissingObjectException && requestParams.ThrowIfNotExist))
{
throw;
}
}
}
await requestContext.SendResult(true);
var handler = this.GetObjectTypeHandler(requestParams.ObjectType);
await handler.Drop(requestParams.ConnectionUri, requestParams.ObjectUrn, requestParams.ThrowIfNotExist);
await requestContext.SendResult(new DropRequestResponse());
}
private ConnectionInfo GetConnectionInfo(string connectionUri)
internal async Task HandleInitializeViewRequest(InitializeViewRequestParams requestParams, RequestContext<SqlObjectViewInfo> requestContext)
{
ConnectionInfo connInfo;
if (ConnectionServiceInstance.TryFindConnection(connectionUri, out connInfo))
{
return connInfo;
}
else
{
Logger.Error($"The connection with URI '{connectionUri}' could not be found.");
throw new Exception(SR.ErrorConnectionNotFound);
}
var handler = this.GetObjectTypeHandler(requestParams.ObjectType);
var result = await handler.InitializeObjectView(requestParams);
contextMap[requestParams.ContextId] = result.Context;
await requestContext.SendResult(result.ViewInfo);
}
private void ExecuteActionOnObject(string connectionUri, string objectUrn, Action<SqlSmoObject> action)
internal async Task HandleSaveObjectRequest(SaveObjectRequestParams requestParams, RequestContext<SaveObjectRequestResponse> requestContext)
{
ConnectionInfo connInfo = this.GetConnectionInfo(connectionUri);
ServerConnection serverConnection = ConnectionService.OpenServerConnection(connInfo, ObjectManagementServiceApplicationName);
using (serverConnection.SqlConnectionObject)
var context = this.GetContext(requestParams.ContextId);
var handler = this.GetObjectTypeHandler(context.Parameters.ObjectType);
var obj = requestParams.Object.ToObject(handler.GetObjectType());
await handler.Save(context, obj as SqlObject);
await requestContext.SendResult(new SaveObjectRequestResponse());
}
internal async Task HandleScriptObjectRequest(ScriptObjectRequestParams requestParams, RequestContext<string> requestContext)
{
var context = this.GetContext(requestParams.ContextId);
var handler = this.GetObjectTypeHandler(context.Parameters.ObjectType);
var obj = requestParams.Object.ToObject(handler.GetObjectType());
var script = await handler.Script(context, obj as SqlObject);
await requestContext.SendResult(script);
}
internal async Task HandleDisposeViewRequest(DisposeViewRequestParams requestParams, RequestContext<DisposeViewRequestResponse> requestContext)
{
SqlObjectViewContext context;
if (contextMap.Remove(requestParams.ContextId, out context))
{
Server server = new Server(serverConnection);
SqlSmoObject dbObject = server.GetSmoObject(new Urn(objectUrn));
action(dbObject);
context.Dispose();
}
await requestContext.SendResult(new DisposeViewRequestResponse());
}
private IObjectTypeHandler GetObjectTypeHandler(SqlObjectType objectType)
{
foreach (var handler in objectTypeHandlers)
{
if (handler.CanHandleType(objectType))
{
return handler;
}
}
throw new NotSupportedException(objectType.ToString());
}
private SqlObjectViewContext GetContext(string contextId)
{
if (contextMap.TryGetValue(contextId, out SqlObjectViewContext context))
{
return context;
}
throw new ArgumentException($"Context '{contextId}' not found");
}
}
}

View File

@@ -0,0 +1,115 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading.Tasks;
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.Management;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public interface IObjectTypeHandler
{
bool CanHandleType(SqlObjectType objectType);
Task<InitializeViewResult> InitializeObjectView(Contracts.InitializeViewRequestParams requestParams);
Task Save(SqlObjectViewContext context, SqlObject obj);
Task<string> Script(SqlObjectViewContext context, SqlObject obj);
Type GetObjectType();
Task Rename(string connectionUri, string objectUrn, string newName);
Task Drop(string connectionUri, string objectUrn, bool throwIfNotExist);
}
public abstract class ObjectTypeHandler<ObjectType, ContextType> : IObjectTypeHandler
where ObjectType : SqlObject
where ContextType : SqlObjectViewContext
{
protected ConnectionService ConnectionService { get; }
public ObjectTypeHandler(ConnectionService connectionService)
{
this.ConnectionService = connectionService;
}
public abstract bool CanHandleType(SqlObjectType objectType);
public abstract Task<InitializeViewResult> InitializeObjectView(Contracts.InitializeViewRequestParams requestParams);
public abstract Task Save(ContextType context, ObjectType obj);
public abstract Task<string> Script(ContextType context, ObjectType obj);
public Task Save(SqlObjectViewContext context, SqlObject obj)
{
return this.Save((ContextType)context, (ObjectType)obj);
}
public Task<string> Script(SqlObjectViewContext context, SqlObject obj)
{
return this.Script((ContextType)context, (ObjectType)obj);
}
public Type GetObjectType()
{
return typeof(ObjectType);
}
public virtual Task Rename(string connectionUri, string objectUrn, string newName)
{
ConnectionInfo connInfo = this.GetConnectionInfo(connectionUri);
ServerConnection serverConnection = ConnectionService.OpenServerConnection(connInfo, ObjectManagementService.ApplicationName);
using (serverConnection.SqlConnectionObject)
{
Server server = new Server(serverConnection);
SqlSmoObject dbObject = server.GetSmoObject(new Urn(objectUrn));
var renamable = dbObject as IRenamable;
if (renamable != null)
{
renamable.Rename(newName);
}
else
{
throw new Exception(SR.ObjectNotRenamable(objectUrn));
}
}
return Task.CompletedTask;
}
public virtual Task Drop(string connectionUri, string objectUrn, bool throwIfNotExist)
{
ConnectionInfo connectionInfo = this.GetConnectionInfo(connectionUri);
using (CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfo, databaseExists: true))
{
try
{
dataContainer.SqlDialogSubject = dataContainer.Server?.GetSmoObject(objectUrn);
DatabaseUtils.DoDropObject(dataContainer);
}
catch (FailedOperationException ex)
{
if (!(ex.InnerException is MissingObjectException) || (ex.InnerException is MissingObjectException && throwIfNotExist))
{
throw;
}
}
}
return Task.CompletedTask;
}
protected ConnectionInfo GetConnectionInfo(string connectionUri)
{
ConnectionInfo connInfo;
if (this.ConnectionService.TryFindConnection(connectionUri, out connInfo))
{
return connInfo;
}
else
{
Logger.Error($"The connection with URI '{connectionUri}' could not be found.");
throw new Exception(SR.ErrorConnectionNotFound);
}
}
}
}

View File

@@ -0,0 +1,54 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public class CommonObjectType : SqlObject { }
public class CommonObjectTypeViewContext : SqlObjectViewContext
{
public CommonObjectTypeViewContext(InitializeViewRequestParams parameters) : base(parameters) { }
public override void Dispose() { }
}
/// <summary>
/// A handler for the object types that only has rename/drop support
/// </summary>
public class CommonObjectTypeHandler : ObjectTypeHandler<CommonObjectType, CommonObjectTypeViewContext>
{
// The message is only used in developing time, no need to be localized.
private const string NotSupportedException = "This operation is not supported for this object type";
public CommonObjectTypeHandler(ConnectionService connectionService) : base(connectionService) { }
public override bool CanHandleType(SqlObjectType objectType)
{
return objectType == SqlObjectType.Column ||
objectType == SqlObjectType.Table ||
objectType == SqlObjectType.View;
}
public override Task Save(CommonObjectTypeViewContext context, CommonObjectType obj)
{
throw new NotSupportedException(NotSupportedException);
}
public override Task<InitializeViewResult> InitializeObjectView(Contracts.InitializeViewRequestParams requestParams)
{
throw new NotSupportedException(NotSupportedException);
}
public override Task<string> Script(CommonObjectTypeViewContext context, CommonObjectType obj)
{
throw new NotSupportedException(NotSupportedException);
}
}
}

View File

@@ -0,0 +1,88 @@
//
// 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.Threading.Tasks;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// Credential object type handler
/// </summary>
public class CredentialHandler : ObjectTypeHandler<CredentialInfo, CredentialViewContext>
{
public CredentialHandler(ConnectionService connectionService) : base(connectionService)
{
}
public override bool CanHandleType(SqlObjectType objectType)
{
return objectType == SqlObjectType.Credential;
}
public override Task<InitializeViewResult> InitializeObjectView(Contracts.InitializeViewRequestParams parameters)
{
// TODO: this is partially implemented only.
ConnectionInfo connInfo;
this.ConnectionService.TryFindConnection(parameters.ConnectionUri, out connInfo);
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
var credentialInfo = new CredentialInfo();
if (!parameters.IsNewObject)
{
var credential = dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as Credential;
credentialInfo.Name = credential.Name;
credentialInfo.Identity = credential.Identity;
credentialInfo.Id = credential.ID;
credentialInfo.DateLastModified = credential.DateLastModified;
credentialInfo.CreateDate = credential.CreateDate;
credentialInfo.ProviderName = credential.ProviderName;
}
var viewInfo = new CredentialViewInfo() { ObjectInfo = credentialInfo };
var context = new CredentialViewContext(parameters);
var result = new InitializeViewResult { ViewInfo = viewInfo, Context = context };
return Task.FromResult(result);
}
public override async Task Save(CredentialViewContext context, CredentialInfo obj)
{
await ConfigureCredential(context.Parameters.ConnectionUri, obj, ConfigAction.Update, RunType.RunNow);
}
public override Task<string> Script(CredentialViewContext context, CredentialInfo obj)
{
throw new NotImplementedException();
}
private Task<Tuple<bool, string>> ConfigureCredential(string ownerUri, CredentialInfo credential, ConfigAction configAction, RunType runType)
{
return Task<Tuple<bool, string>>.Run(() =>
{
try
{
ConnectionInfo connInfo;
this.ConnectionService.TryFindConnection(ownerUri, out connInfo);
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
using (CredentialActions actions = new CredentialActions(dataContainer, credential, configAction))
{
var executionHandler = new ExecutonHandler(actions);
executionHandler.RunNow(runType, this);
}
return new Tuple<bool, string>(true, string.Empty);
}
catch (Exception ex)
{
return new Tuple<bool, string>(false, ex.ToString());
}
});
}
}
}

View File

@@ -0,0 +1,23 @@
//
// 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;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// a class for storing various credential properties
/// </summary>
public class CredentialInfo : SqlObject
{
public int Id { get; set; }
public string Identity { get; set; }
public DateTime DateLastModified { get; set; }
public DateTime CreateDate { get; set; }
public string ProviderName { get; set; }
}
}

View File

@@ -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.
//
using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public class CredentialViewContext : SqlObjectViewContext
{
public CredentialViewContext(InitializeViewRequestParams parameters) : base(parameters) { }
public override void Dispose() { }
}
}

View File

@@ -0,0 +1,14 @@
//
// 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
{
/// <summary>
/// The information required to render the credential view.
/// </summary>
public class CredentialViewInfo : SqlObjectViewInfo
{
}
}

View File

@@ -0,0 +1,282 @@
//
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// Login object type handler
/// </summary>
public class LoginHandler : ObjectTypeHandler<LoginInfo, LoginViewContext>
{
public LoginHandler(ConnectionService connectionService) : base(connectionService) { }
public override bool CanHandleType(SqlObjectType objectType)
{
return objectType == SqlObjectType.ServerLevelLogin;
}
public override Task<InitializeViewResult> 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);
LoginViewInfo loginViewInfo = new LoginViewInfo();
// TODO cache databases and languages
string[] databases = new string[dataContainer.Server.Databases.Count];
for (int i = 0; i < dataContainer.Server.Databases.Count; i++)
{
databases[i] = dataContainer.Server.Databases[i].Name;
}
var languageOptions = LanguageUtils.GetDefaultLanguageOptions(dataContainer);
var languageOptionsList = languageOptions.Select(LanguageUtils.FormatLanguageDisplay).ToList();
if (parameters.IsNewObject)
{
languageOptionsList.Insert(0, SR.DefaultLanguagePlaceholder);
}
string[] languages = languageOptionsList.ToArray();
LoginPrototype prototype = parameters.IsNewObject
? new LoginPrototype(dataContainer.Server)
: new LoginPrototype(dataContainer.Server, dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as Login);
List<string> loginServerRoles = new List<string>();
foreach (string role in prototype.ServerRoles.ServerRoleNames)
{
if (prototype.ServerRoles.IsMember(role))
{
loginServerRoles.Add(role);
}
}
LoginInfo loginInfo = new LoginInfo()
{
Name = prototype.LoginName,
Password = prototype.SqlPassword,
OldPassword = prototype.OldPassword,
AuthenticationType = LoginTypeToAuthenticationType(prototype.LoginType),
EnforcePasswordExpiration = prototype.EnforceExpiration,
EnforcePasswordPolicy = prototype.EnforcePolicy,
MustChangePassword = prototype.MustChange,
DefaultDatabase = prototype.DefaultDatabase,
DefaultLanguage = parameters.IsNewObject ? SR.DefaultLanguagePlaceholder : LanguageUtils.FormatLanguageDisplay(languageOptions.FirstOrDefault(o => o?.Language.Name == prototype.DefaultLanguage || o?.Language.Alias == prototype.DefaultLanguage, null)),
ServerRoles = loginServerRoles.ToArray(),
ConnectPermission = prototype.WindowsGrantAccess,
IsEnabled = !prototype.IsDisabled,
IsLockedOut = prototype.IsLockedOut,
UserMapping = new ServerLoginDatabaseUserMapping[0]
};
var viewInfo = new LoginViewInfo()
{
ObjectInfo = loginInfo,
SupportWindowsAuthentication = prototype.WindowsAuthSupported,
SupportAADAuthentication = prototype.AADAuthSupported,
SupportSQLAuthentication = true, // SQL Auth support for login, not necessarily mean SQL Auth support for CONNECT etc.
CanEditLockedOutState = !parameters.IsNewObject && prototype.IsLockedOut,
Databases = databases,
Languages = languages,
ServerRoles = prototype.ServerRoles.ServerRoleNames,
SupportAdvancedPasswordOptions = dataContainer.Server.DatabaseEngineType == DatabaseEngineType.Standalone || dataContainer.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse,
SupportAdvancedOptions = dataContainer.Server.DatabaseEngineType == DatabaseEngineType.Standalone || dataContainer.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance
};
var context = new LoginViewContext(parameters);
return Task.FromResult(new InitializeViewResult()
{
ViewInfo = viewInfo,
Context = context
});
}
public override Task Save(LoginViewContext context, LoginInfo obj)
{
if (context.Parameters.IsNewObject)
{
this.DoHandleCreateLoginRequest(context, obj, RunType.RunNow);
}
else
{
this.DoHandleUpdateLoginRequest(context, obj, RunType.RunNow);
}
return Task.CompletedTask;
}
public override Task<string> Script(LoginViewContext context, LoginInfo obj)
{
string script;
if (context.Parameters.IsNewObject)
{
script = this.DoHandleCreateLoginRequest(context, obj, RunType.ScriptToWindow);
}
else
{
script = this.DoHandleUpdateLoginRequest(context, obj, RunType.ScriptToWindow);
}
return Task.FromResult(script);
}
private LoginAuthenticationType LoginTypeToAuthenticationType(LoginType loginType)
{
switch (loginType)
{
case LoginType.WindowsUser:
case LoginType.WindowsGroup:
return LoginAuthenticationType.Windows;
case LoginType.SqlLogin:
return LoginAuthenticationType.Sql;
case LoginType.ExternalUser:
case LoginType.ExternalGroup:
return LoginAuthenticationType.AAD;
default:
return LoginAuthenticationType.Others;
}
}
private string ConfigureLogin(CDataContainer dataContainer, ConfigAction configAction, RunType runType, LoginPrototype prototype)
{
string sqlScript = string.Empty;
using (var actions = new LoginActions(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 DoHandleUpdateLoginRequest(LoginViewContext context, LoginInfo login, 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);
LoginPrototype prototype = new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[login.Name]);
prototype.SqlPassword = login.Password;
if (0 != string.Compare(login.DefaultLanguage, SR.DefaultLanguagePlaceholder, StringComparison.Ordinal))
{
string[] arr = login.DefaultLanguage?.Split(" - ");
if (arr != null && arr.Length > 1)
{
prototype.DefaultLanguage = arr[1];
}
}
prototype.DefaultDatabase = login.DefaultDatabase;
prototype.EnforcePolicy = login.EnforcePasswordPolicy;
prototype.EnforceExpiration = login.EnforcePasswordPolicy ? login.EnforcePasswordExpiration : false;
prototype.IsLockedOut = login.IsLockedOut;
prototype.IsDisabled = !login.IsEnabled;
prototype.MustChange = login.EnforcePasswordPolicy ? login.MustChangePassword : false;
prototype.WindowsGrantAccess = login.ConnectPermission;
if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
{
// check that there is a password
// this check is made if policy enforcement is off
// with policy turned on we do not display this message, instead we let server
// return the error associated with null password (coming from policy) - see bug 124377
if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false)
{
// raise error here
}
// check that password and confirm password controls' text matches
if (0 != string.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal))
{
// raise error here
}
}
var _ = prototype.ServerRoles.ServerRoleNames;
foreach (string role in prototype.ServerRoles.ServerRoleNames)
{
prototype.ServerRoles.SetMember(role, false);
}
foreach (string role in login.ServerRoles)
{
prototype.ServerRoles.SetMember(role, true);
}
return ConfigureLogin(
dataContainer,
ConfigAction.Update,
runType,
prototype);
}
private string DoHandleCreateLoginRequest(LoginViewContext context, LoginInfo login, 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);
LoginPrototype prototype = new LoginPrototype(dataContainer.Server, login);
if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
{
// check that there is a password
// this check is made if policy enforcement is off
// with policy turned on we do not display this message, instead we let server
// return the error associated with null password (coming from policy) - see bug 124377
if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false)
{
// raise error here
}
// check that password and confirm password controls' text matches
if (0 != string.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal))
{
// raise error here
}
}
// TODO move this to LoginData
// TODO support role assignment for Azure
foreach (string role in login.ServerRoles ?? Enumerable.Empty<string>())
{
prototype.ServerRoles.SetMember(role, true);
}
return ConfigureLogin(dataContainer, ConfigAction.Create, runType, prototype);
}
}
}

View File

@@ -0,0 +1,67 @@
//
// 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.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
[JsonConverter(typeof(StringEnumConverter))]
public enum LoginAuthenticationType
{
[EnumMember(Value = "Windows")]
Windows,
[EnumMember(Value = "Sql")]
Sql,
[EnumMember(Value = "AAD")]
AAD,
[EnumMember(Value = "Others")]
Others
}
public class ServerLoginDatabaseUserMapping
{
public string Database { get; set; }
public string User { get; set; }
public string DefaultSchema { get; set; }
public string[] DatabaseRoles { get; set; }
}
/// <summary>
/// a class for storing various login properties
/// </summary>
public class LoginInfo : SqlObject
{
public LoginAuthenticationType AuthenticationType { get; set; }
public bool WindowsGrantAccess { get; set; }
public bool MustChangePassword { get; set; }
public bool IsEnabled { get; set; }
public bool ConnectPermission { get; set; }
public bool IsLockedOut { get; set; }
public bool EnforcePasswordPolicy { get; set; }
public bool EnforcePasswordExpiration { get; set; }
public string Password { get; set; }
public string OldPassword { get; set; }
public string DefaultLanguage { get; set; }
public string DefaultDatabase { get; set; }
public string[] ServerRoles { get; set; }
public ServerLoginDatabaseUserMapping[] UserMapping;
}
}

View File

@@ -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 LoginViewContext : SqlObjectViewContext
{
public LoginViewContext(Contracts.InitializeViewRequestParams parameters) : base(parameters)
{
}
public override void Dispose()
{
}
}
}

View File

@@ -0,0 +1,21 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public class LoginViewInfo : SqlObjectViewInfo
{
public bool SupportWindowsAuthentication { get; set; }
public bool SupportAADAuthentication { get; set; }
public bool SupportSQLAuthentication { get; set; }
public bool CanEditLockedOutState { get; set; }
public string[] Databases;
public string[] Languages;
public string[] ServerRoles;
public bool SupportAdvancedPasswordOptions;
public bool SupportAdvancedOptions;
}
}

View File

@@ -0,0 +1,653 @@
//
// 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
{
/// <summary>
/// AppRoleGeneral - main app role page
/// </summary>
internal class AppRoleGeneral
{
#region Members
/// <summary>
/// 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
/// </summary>
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);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
// protected override void Dispose( bool disposing )
// {
// if ( disposing )
// {
// if (components != null)
// {
// components.Dispose();
// }
// }
// base.Dispose( disposing );
// }
#endregion
#region Implementation: LoadData(), InitProp(), SendDataToServer()
/// <summary>
/// LoadData
///
/// loads connection parameters from an xml
/// </summary>
/// <param name="doc"></param>
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);
}
/// <summary>
/// InitProp
///
/// talks with enumerator an retrieves info
/// </summary>
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;
/// <summary>
/// Called to validate all date in controls and save them in
/// temproary storage to be used when OnRunNow is called
/// </summary>
// 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;
// }
// }
/// <summary>
/// SendDataToServer
///
/// here we talk with server via smo and do the actual data changing
/// </summary>
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;
/// <summary>
/// loads initial schemas from server together with information about the schema owner
/// </summary>
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);
}
}
}
/// <summary>
/// sends to server changes related to schema ownership
/// </summary>
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;
/// <summary>
/// loads from server initial membership information
/// </summary>
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);
}
}
/// <summary>
/// initialize grid column headers, but not the content
/// </summary>
// 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();
// }
/// <summary>
/// fills the membership grid with data (bitmaps, names, etc)
/// </summary>
// 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;
// }
// }
/// <summary>
/// sends to server user changes related to membership
/// </summary>
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();
}
}
}
/// <summary>
/// lookup in membership grid to see if user added a name
/// </summary>
/// <param name="name"></param>
/// <param name="urn"></param>
/// <returns></returns>
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
}
}

View File

@@ -0,0 +1,78 @@
//
// 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.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
internal class CredentialActions : ManagementActionBase
{
#region Constants
private const int MAX_SQL_SYS_NAME_LENGTH = 128; // max sql sys name length
#endregion
#region Variables
private CredentialData credentialData = null;
private CredentialInfo credential;
private ConfigAction configAction;
#endregion
#region Constructors / Dispose
/// <summary>
/// required when loading from Object Explorer context
/// </summary>
/// <param name="context"></param>
public CredentialActions(
CDataContainer context,
CredentialInfo credential,
ConfigAction configAction)
{
this.DataContainer = context;
this.credential = credential;
this.configAction = configAction;
this.credentialData = new CredentialData(context, credential);
this.credentialData.Initialize();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing == true)
{
if (this.credentialData != null)
{
this.credentialData.Dispose();
}
}
}
#endregion
/// <summary>
/// called on background thread by the framework to execute the action
/// </summary>
/// <param name="node"></param>
public override void OnRunNow(object sender)
{
if (this.configAction == ConfigAction.Drop)
{
if (this.credentialData.Credential != null)
{
this.credentialData.Credential.DropIfExists();
}
}
else
{
this.credentialData.SendDataToServer();
}
}
}
}

View File

@@ -0,0 +1,258 @@
//
// 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.Security;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
internal class CredentialData : IDisposable
{
#region Properties
private string credentialName = string.Empty;
public string CredentialName
{
get { return credentialName; }
set { credentialName = value; }
}
private string credentialIdentity = string.Empty;
public string CredentialIdentity
{
get { return credentialIdentity; }
set { credentialIdentity = value; }
}
private SecureString securePassword;
public SecureString SecurePassword
{
get { return securePassword; }
set
{
securePassword = value;
PasswordWasChanged = true;
}
}
private bool isPropertiesMode = false;
public bool IsPropertiesMode
{
get
{
return isPropertiesMode;
}
}
private bool passwordWasChanged = false;
public bool PasswordWasChanged
{
get { return passwordWasChanged; }
set { passwordWasChanged = value; }
}
private bool isEncryptionByProvider = false;
public bool IsEncryptionByProvider
{
get { return isEncryptionByProvider; }
set { isEncryptionByProvider = value; }
}
private string providerName = string.Empty;
public string ProviderName
{
get { return providerName; }
set { providerName = value; }
}
public Microsoft.SqlServer.Management.Smo.Credential Credential
{
get
{
return !string.IsNullOrWhiteSpace(this.CredentialName)
? this.Context.Server.Credentials[this.CredentialName]
: null;
}
}
#endregion
private const string ENUMERATOR_FIELD_IDENTITY = "Identity";
private const string ENUMERATOR_FIELD_PROVIDER_NAME = "ProviderName";
#region Constructor
private CDataContainer context = null;
private CDataContainer Context { get { return context; } set { context = value; } }
private CredentialInfo credential;
public CredentialData(CDataContainer context, CredentialInfo credential)
{
this.Context = context;
this.credential = credential;
LoadDataFromXmlContext();
}
#endregion
#region Implementation: LoadDataFromXmlContext(), LoadDataFromServer(), SendDataToServer()
public void Initialize()
{
LoadDataFromXmlContext();
LoadDataFromServer();
// apply CredentialInfo properties
this.CredentialIdentity = this.credential.Identity;
this.CredentialName = this.credential.Name;
this.SecurePassword = CDataContainer.BuildSecureStringFromPassword(string.Empty);
// need to update only during create time
this.IsEncryptionByProvider = false;
if (this.IsEncryptionByProvider)
{
this.ProviderName = string.Empty; // lookup provider here
}
else
{
this.ProviderName = string.Empty;
}
}
/// <summary>
/// LoadDataFromXmlContext
///
/// loads context information from xml - e.g. name of object
/// </summary>
private void LoadDataFromXmlContext()
{
this.CredentialName = this.Context.GetDocumentPropertyString("credential");
this.isPropertiesMode = (this.CredentialName != null) && (this.CredentialName.Length > 0);
}
/// <summary>
/// LoadDataFromServer
///
/// talks with enumerator an retrieves info that is not available in the xml context but on server
/// </summary>
private void LoadDataFromServer()
{
if (this.IsPropertiesMode == true)
{
bool isKatmaiAndNotMatrix = (this.context.Server.Version.Major >= 10);
Urn urn = new Urn("Server/Credential[@Name='" + Urn.EscapeString(this.CredentialName) + "']");
string[] fields;
if (isKatmaiAndNotMatrix)
{
fields = new string[] { ENUMERATOR_FIELD_IDENTITY, ENUMERATOR_FIELD_PROVIDER_NAME };
}
else
{
fields = new string[] { ENUMERATOR_FIELD_IDENTITY };
}
Request r = new Request(urn, fields);
System.Data.DataTable dataTable = Enumerator.GetData(this.Context.ConnectionInfo, r);
if (dataTable.Rows.Count == 0)
{
throw new Exception(SR.CredentialNoLongerExists);
}
System.Data.DataRow dataRow = dataTable.Rows[0];
this.CredentialIdentity = Convert.ToString(dataRow[ENUMERATOR_FIELD_IDENTITY], System.Globalization.CultureInfo.InvariantCulture);
if (isKatmaiAndNotMatrix)
{
this.providerName = Convert.ToString(dataRow[ENUMERATOR_FIELD_PROVIDER_NAME], System.Globalization.CultureInfo.InvariantCulture);
this.isEncryptionByProvider = !string.IsNullOrEmpty(providerName);
}
}
else
{
this.CredentialName = string.Empty;
this.CredentialIdentity = string.Empty;
this.providerName = string.Empty;
this.isEncryptionByProvider = false;
}
this.SecurePassword = new SecureString();
this.PasswordWasChanged = false;
}
/// <summary>
/// SendDataToServer
///
/// here we talk with server via smo and do the actual data changing
/// </summary>
public void SendDataToServer()
{
if (this.IsPropertiesMode == true)
{
SendToServerAlterCredential();
}
else
{
SendToServerCreateCredential();
}
}
/// <summary>
/// create credential - create mode
/// </summary>
private void SendToServerCreateCredential()
{
Microsoft.SqlServer.Management.Smo.Credential smoCredential = new Microsoft.SqlServer.Management.Smo.Credential(
this.Context.Server,
this.CredentialName);
if (this.isEncryptionByProvider)
{
smoCredential.MappedClassType = MappedClassType.CryptographicProvider;
smoCredential.ProviderName = this.providerName;
}
smoCredential.Create(this.CredentialIdentity, this.SecurePassword.ToString());
GC.Collect(); // this.SecurePassword.ToString() just created an immutable string that lives in memory
}
/// <summary>
/// alter credential - properties mode
/// </summary>
private void SendToServerAlterCredential()
{
Microsoft.SqlServer.Management.Smo.Credential smoCredential = this.Context.Server.Credentials[this.CredentialName];
if (smoCredential != null)
{
if (this.PasswordWasChanged == false)
{
if (smoCredential.Identity != this.CredentialIdentity)
{
smoCredential.Alter(this.CredentialIdentity);
}
}
else
{
smoCredential.Alter(this.CredentialIdentity, this.SecurePassword.ToString());
GC.Collect(); // this.SecurePassword.ToString() just created an immutable string that lives in memory
}
}
else
{
throw new Exception(SR.CredentialNoLongerExists);
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
this.SecurePassword.Dispose();
}
#endregion
}
}

View File

@@ -0,0 +1,890 @@
//
// 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
{
/// <summary>
/// DatabaseRoleGeneral - main panel for database role
/// </summary>
internal class DatabaseRoleGeneral
{
#region Members
/// <summary>
/// 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
/// </summary>
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()
/// <summary>
/// LoadData
///
/// loads connection parameters from an xml
/// </summary>
/// <param name="doc"></param>
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);
}
/// <summary>
/// InitProp
///
/// talks with enumerator an retrievives info
/// </summary>
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;
// }
/// <summary>
/// SendDataToServer
///
/// here we talk with server via smo and do the actual data changing
/// </summary>
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
// /// <summary>
// /// Required method for Designer support - do not modify
// /// the contents of this method with the code editor.
// /// </summary>
// 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 ...
/// <summary>
/// loads initial schemas from server together with information about the schema owner
/// </summary>
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);
}
}
/// <summary>
/// initializes the columns and headers of schema grid - but doesnt populate grid with any data
/// </summary>
// 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;
// }
// }
/// <summary>
/// sends to server changes related to schema ownership
/// </summary>
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 ...
/// <summary>
/// loads from server initial membership information
/// </summary>
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);
}
}
}
/// <summary>
/// initialize grid column headers, but not the content
/// </summary>
// 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();
// }
/// <summary>
/// fills the membership grid with data (bitmaps, names, etc)
/// </summary>
// 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;
// }
// }
/// <summary>
/// sends to server user changes related to membership
/// </summary>
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
}
}

View File

@@ -0,0 +1,40 @@
//
// 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 LoginActions : ManagementActionBase
{
private ConfigAction configAction;
private LoginPrototype prototype;
/// <summary>
/// Handle login create and update actions
/// </summary>
public LoginActions(CDataContainer dataContainer, ConfigAction configAction, LoginPrototype prototype)
{
this.DataContainer = dataContainer;
this.configAction = configAction;
this.prototype = prototype;
}
/// <summary>
/// called by the management actions framework to execute the action
/// </summary>
/// <param name="node"></param>
public override void OnRunNow(object sender)
{
if (this.configAction != ConfigAction.Drop)
{
prototype.ApplyGeneralChanges(this.DataContainer.Server);
prototype.ApplyServerRoleChanges(this.DataContainer.Server);
prototype.ApplyDatabaseRoleChanges(this.DataContainer.Server);
}
}
}
}

View File

@@ -0,0 +1,212 @@
//
// 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.Globalization;
using SMO = Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// NetStandard compatible helpers
/// </summary>
#if NETCOREAPP2_0
public class Utils
#else
internal partial class Utils
#endif
{
private Utils() { }
public static bool IsKatmaiOrLater(int version)
{
return (10 <= version);
}
public static bool IsKjOrLater(ServerVersion version)
{
return (version.Major > 10
|| (version.Major == 10 && version.Minor >= 50));
}
public static bool IsSql11OrLater(ServerVersion version)
{
return IsSql11OrLater(version.Major);
}
public static bool IsSql11OrLater(int versionMajor)
{
return (versionMajor >= 11);
}
public static bool IsSql12OrLater(ServerVersion version)
{
return IsSql12OrLater(version.Major);
}
public static bool IsSql12OrLater(int versionMajor)
{
return (versionMajor >= 12);
}
public static bool IsSql13OrLater(ServerVersion version)
{
return IsSql13OrLater(version.Major);
}
public static bool IsSql13OrLater(int versionMajor)
{
return (versionMajor >= 13);
}
public static bool IsSql14OrLater(ServerVersion version)
{
return IsSql14OrLater(version.Major);
}
public static bool IsSql14OrLater(int versionMajor)
{
return (versionMajor >= 14);
}
public static bool IsSql15OrLater(ServerVersion version)
{
return IsSql15OrLater(version.Major);
}
public static bool IsSql15OrLater(int versionMajor)
{
return (versionMajor >= 15);
}
/// <summary>
/// Check if the version is SQL 2019 CU4 or later.
/// </summary>
/// <param name="version"></param>
/// <returns></returns>
/// <remarks>
/// SQL2019 CU3 is going to be 4023; CU4 is going to be 4033
/// SQL2019 CU4 (before the snap to the release branch) is 4028.
/// </remarks>
public static bool IsSql15OCU4OrLater(Version version)
{
return(version >= new Version(15, 0, 4028));
}
/// <summary>
/// Check if the version is SQL 2016 SP1 or later.
/// </summary>
/// <param name="version"></param>
/// <returns>true if the version is SQL 2016 SP1 or later, false otherwise</returns>
public static bool IsSql13SP1OrLater(Version version)
{
return (version >= new Version(13, 0, 3510));
}
public static bool IsXTPSupportedOnServer(SMO.Server server)
{
if(server.DatabaseEngineEdition == DatabaseEngineEdition.SqlOnDemand)
{
return false;
}
bool isXTPSupported = false;
if (server.ConnectionContext.ExecuteScalar("SELECT SERVERPROPERTY('IsXTPSupported')") != DBNull.Value)
{
isXTPSupported = server.IsXTPSupported;
}
return isXTPSupported;
}
public static bool IsPolybasedInstalledOnServer(SMO.Server server)
{
bool isPolybaseInstalled = false;
if (server.IsSupportedProperty("IsPolyBaseInstalled"))
{
isPolybaseInstalled = server.IsPolyBaseInstalled;
}
return isPolybaseInstalled;
}
/// <summary>
/// Returns true if current user has given permission on given server.
/// </summary>
/// <param name="server"></param>
/// <param name="permissionName"></param>
/// <returns></returns>
public static bool HasPermissionOnServer(SMO.Server server, string permissionName)
{
return Convert.ToBoolean(server.ConnectionContext.ExecuteScalar(
string.Format(CultureInfo.InvariantCulture,
"SELECT HAS_PERMS_BY_NAME(null, null, '{0}');",
permissionName)));
}
public static bool FilestreamEnabled(SMO.Server svr)
{
bool result = false;
if (svr != null)
{
if (IsKatmaiOrLater(svr.Information.Version.Major)
&& svr.ServerType != DatabaseEngineType.SqlAzureDatabase) //Azure doesn't support filestream
{
if (svr.Configuration.FilestreamAccessLevel.RunValue != 0)
{
result = true;
}
}
}
return result;
}
public static bool IsYukonOrAbove(SMO.Server server)
{
return server.Version.Major >= 9;
}
public static bool IsBelowYukon(SMO.Server server)
{
return server.Version.Major < 9;
}
/// <summary>
/// Some calendars, such as the UmAlQuraCalendar, support an upper date range that is earlier than MaxValue.
/// In these cases, trying to access MaxValue in variable assignments or formatting and parsing operations can throw
/// an ArgumentOutOfRangeException. Rather than retrieving the value of DateTime.MaxValue, you can retrieve the value
/// of the specified culture's latest valid date value from the
/// System.Globalization.CultureInfo.DateTimeFormat.Calendar.MaxSupportedDateTime property.
/// http://msdn.microsoft.com/en-us/library/system.datetime.maxvalue(v=VS.90).aspx
/// </summary>
/// <returns></returns>
public static DateTime GetMaxCultureDateTime()
{
CultureInfo currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
return currentCulture.DateTimeFormat.Calendar.MaxSupportedDateTime;
}
public static string MakeSqlBracket(string s)
{
return "[" + s.Replace("]", "]]") + "]";
}
/// <summary>
/// Returns whether the server is in AS Azure
/// </summary>
/// <param name="serverName"></param>
/// <returns></returns>
public static bool IsASAzure(string serverName)
{
return !string.IsNullOrEmpty(serverName) && serverName.StartsWith("asazure://", StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,50 @@
//
// 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 Microsoft.SqlServer.Management.Common;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
internal static class PermissionsDataExtensions
{
/// <summary>
/// Whether this SecurableType is a valid Schema-Scoped Securable for the given server version, engine edition and engine type
/// </summary>
/// <param name="type"></param>
/// <param name="serverVersion"></param>
/// <param name="databaseEngineEdition"></param>
/// <param name="databaseEngineType"></param>
/// <returns></returns>
public static bool IsValidSchemaBoundSecurable(this SecurableType type, ServerVersion serverVersion, DatabaseEngineEdition databaseEngineEdition, DatabaseEngineType databaseEngineType)
{
return
type.GetType().GetField(type.ToString())
.GetCustomAttributes(typeof (SchemaScopedSecurableAttribute), true)
.Cast<SchemaScopedSecurableAttribute>()
.Any(attr => attr.IsValid(serverVersion, databaseEngineType, databaseEngineEdition));
}
/// <summary>
/// Gets the Schema-Scoped URN for this SecurableType
/// </summary>
/// <param name="type"></param>
/// <param name="schema"></param>
/// <param name="databaseName"></param>
/// <returns></returns>
public static string GetSchemaScopedUrn(this SecurableType type, string schema, string databaseName)
{
SchemaScopedSecurableAttribute attr =
type.GetType().GetField(type.ToString())
.GetCustomAttributes(typeof (SchemaScopedSecurableAttribute), true)
.Cast<SchemaScopedSecurableAttribute>()
.FirstOrDefault() ?? throw new InvalidOperationException("Type {0} did not define a SchemaScopedSecurableUrn attribute");
return attr.GetUrn(schema, databaseName);
}
}
}

View File

@@ -0,0 +1,88 @@
//
// 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.Reflection;
using System.Text;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// An attribute for sqlmgmt\src\permissionsdata.cs!SecurableType that maps it to the corresponding SMO
/// type. This allows us to use that type to decide whether that securable is valid for a given server
/// version/engine edition/engine type combo and to get the URN suffix value for that type using SMO
/// instead of duplicating it in SqlMgmt.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
internal class SchemaScopedSecurableAttribute : Attribute
{
private readonly Type _smoType;
private readonly string _urnSuffix;
private readonly string _additionalParam;
/// <summary>
/// Basic public constructor
/// </summary>
/// <param name="smoType">The SMO Type this Securable is mapped to</param>
/// <param name="additionalParamName">(Optional) The name of an additional param</param>
/// <param name="additionalParamValue">(Optional) The value of an additional param</param>
public SchemaScopedSecurableAttribute(Type smoType, string additionalParamName = "", object additionalParamValue = null )
{
_smoType = smoType;
//The additional param is optional - just ignore if we don't have a valid name
_additionalParam = string.IsNullOrEmpty(additionalParamName)
? String.Empty
: string.Format("@{0}='{1}'", additionalParamName, Urn.EscapeString(additionalParamValue.ToString()));
PropertyInfo urnSuffixProperty = _smoType.GetProperty("UrnSuffix", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public) ?? throw new InvalidArgumentException(string.Format("Type {0} did not have expected UrnSuffix property defined", smoType.Name));
_urnSuffix = urnSuffixProperty.GetValue(null, null).ToString();
}
/// <summary>
/// The SMO Type that this securable is mapped to
/// </summary>
public Type SmoType
{
get { return _smoType; }
}
/// <summary>
/// Whether this Securable is valid for the given server version/engine type/engine edition combo.
/// </summary>
/// <param name="serverVersion"></param>
/// <param name="databaseEngineType"></param>
/// <param name="databaseEngineEdition"></param>
/// <returns></returns>
public bool IsValid(ServerVersion serverVersion, DatabaseEngineType databaseEngineType, DatabaseEngineEdition databaseEngineEdition)
{
return SmoUtility.IsSupportedObject(_smoType, serverVersion, databaseEngineType, databaseEngineEdition);
}
/// <summary>
/// Builds the URN for this Securable using the specified schema name (with optional database name for db-scoped securables)
/// </summary>
/// <param name="schemaName"></param>
/// <param name="databaseName"></param>
/// <returns></returns>
public string GetUrn(string schemaName, string databaseName = "")
{
StringBuilder urn = new StringBuilder("Server");
if (!string.IsNullOrEmpty(databaseName))
{
urn.AppendFormat("/Database[@Name='{0}']", Urn.EscapeString(databaseName));
}
urn.AppendFormat("/{0}[{1}{2}@Schema='{3}']",
_urnSuffix,
_additionalParam,
string.IsNullOrEmpty(_additionalParam) ? string.Empty : " and ",
Urn.EscapeString(schemaName));
return urn.ToString();
}
}
}

View File

@@ -0,0 +1,327 @@
//
// 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<string, bool> 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<string, bool> 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;
}
/// <summary>
/// Generates script for Create and Properties.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
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";
}
}
}
}

View File

@@ -0,0 +1,82 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
#region Using directives
using System.Collections;
using System.Globalization;
using Microsoft.SqlServer.Management.Smo;
#endregion
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// String comparer that uses the case sensitivity and other settings
/// from a particular SQL collation
/// </summary>
#if DEBUG || EXPOSE_MANAGED_INTERNALS
public
#else
internal
#endif
class SqlCollationSensitiveStringComparer : IComparer
{
private CompareOptions compareOptions;
/// <summary>
/// Constructor
/// </summary>
/// <param name="sqlCollation">The name of the SQL collation, like ALGERIAN_CI_AI</param>
public SqlCollationSensitiveStringComparer(string sqlCollation)
{
if (sqlCollation != null && sqlCollation.Length != 0)
{
this.compareOptions = SqlSupport.GetCompareOptionsFromCollation(sqlCollation);
}
else
{
this.compareOptions = CompareOptions.Ordinal;
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="compareOptions">The CompareOptions for the SQL collation</param>
public SqlCollationSensitiveStringComparer(CompareOptions compareOptions)
{
this.compareOptions = compareOptions;
}
/// <summary>
/// Compare two strings
/// </summary>
/// <param name="x">The first string to compare</param>
/// <param name="y">The second string to compare</param>
/// <returns>Less than zero if x is less than y, 0 if x equals y, greater than zero if x is greater than y</returns>
public int Compare(object x, object y)
{
if (null == x && null == y)
{
return 0;
}
else if (null != x && null == y)
{
return 1;
}
else if (null == x && null != y)
{
return -1;
}
else
{
return CultureInfo.InvariantCulture.CompareInfo.Compare((string) x, (string) y, compareOptions);
}
}
}
}

View File

@@ -0,0 +1,167 @@
//
// 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.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Management;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
internal class UserActions : ManagementActionBase
{
#region Variables
private UserPrototype userPrototype;
private ConfigAction configAction;
#endregion
#region Constructors / Dispose
/// <summary>
/// Handle user create and update actions
/// </summary>
public UserActions(
CDataContainer dataContainer,
ConfigAction configAction,
UserInfo user,
UserPrototypeData? originalData)
{
this.DataContainer = dataContainer;
this.IsDatabaseOperation = true;
this.configAction = configAction;
ExhaustiveUserTypes currentUserType;
if (dataContainer.IsNewObject)
{
currentUserType = UserActions.GetUserTypeForUserInfo(user);
}
else
{
currentUserType = UserActions.GetCurrentUserTypeForExistingUser(dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User);
}
this.userPrototype = UserPrototypeFactory.GetUserPrototype(dataContainer, user, originalData, currentUserType);
}
// /// <summary>
// /// Clean up any resources being used.
// /// </summary>
// protected override void Dispose(bool disposing)
// {
// base.Dispose(disposing);
// }
#endregion
/// <summary>
/// called by the management actions framework to execute the action
/// </summary>
/// <param name="node"></param>
public override void OnRunNow(object sender)
{
if (this.configAction != ConfigAction.Drop)
{
this.userPrototype.ApplyChanges(this.ParentDb);
}
}
internal static ExhaustiveUserTypes GetUserTypeForUserInfo(UserInfo user)
{
ExhaustiveUserTypes userType = ExhaustiveUserTypes.LoginMappedUser;
switch (user.Type)
{
case DatabaseUserType.WithLogin:
userType = ExhaustiveUserTypes.LoginMappedUser;
break;
case DatabaseUserType.WithWindowsGroupLogin:
userType = ExhaustiveUserTypes.WindowsUser;
break;
case DatabaseUserType.Contained:
if (user.AuthenticationType == ServerAuthenticationType.AzureActiveDirectory)
{
userType = ExhaustiveUserTypes.ExternalUser;
}
else
{
userType = ExhaustiveUserTypes.SqlUserWithPassword;
}
break;
case DatabaseUserType.NoConnectAccess:
userType = ExhaustiveUserTypes.SqlUserWithoutLogin;
break;
}
return userType;
}
internal static DatabaseUserType GetDatabaseUserTypeForUserType(ExhaustiveUserTypes userType)
{
DatabaseUserType databaseUserType = DatabaseUserType.WithLogin;
switch (userType)
{
case ExhaustiveUserTypes.LoginMappedUser:
databaseUserType = DatabaseUserType.WithLogin;
break;
case ExhaustiveUserTypes.WindowsUser:
databaseUserType = DatabaseUserType.WithWindowsGroupLogin;
break;
case ExhaustiveUserTypes.SqlUserWithPassword:
databaseUserType = DatabaseUserType.Contained;
break;
case ExhaustiveUserTypes.SqlUserWithoutLogin:
databaseUserType = DatabaseUserType.NoConnectAccess;
break;
case ExhaustiveUserTypes.ExternalUser:
databaseUserType = DatabaseUserType.Contained;
break;
}
return databaseUserType;
}
internal static ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User? user)
{
if (user == null)
{
return ExhaustiveUserTypes.Unknown;
}
switch (user.UserType)
{
case UserType.SqlUser:
if (user.IsSupportedProperty("AuthenticationType"))
{
if (user.AuthenticationType == AuthenticationType.Windows)
{
return ExhaustiveUserTypes.WindowsUser;
}
else if (user.AuthenticationType == AuthenticationType.Database)
{
return ExhaustiveUserTypes.SqlUserWithPassword;
}
}
return ExhaustiveUserTypes.LoginMappedUser;
case UserType.NoLogin:
return ExhaustiveUserTypes.SqlUserWithoutLogin;
case UserType.Certificate:
return ExhaustiveUserTypes.CertificateMappedUser;
case UserType.AsymmetricKey:
return ExhaustiveUserTypes.AsymmetricKeyMappedUser;
case UserType.External:
return ExhaustiveUserTypes.ExternalUser;
default:
return ExhaustiveUserTypes.Unknown;
}
}
internal static bool IsParentDatabaseContained(Urn parentDbUrn, Server server)
{
string parentDbName = parentDbUrn.GetNameForType("Database");
return IsParentDatabaseContained(server.Databases[parentDbName]);
}
internal static bool IsParentDatabaseContained(Database parentDatabase)
{
return parentDatabase.IsSupportedProperty("ContainmentType")
&& parentDatabase.ContainmentType == ContainmentType.Partial;
}
}
}

View File

@@ -0,0 +1,283 @@
//
// 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.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.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
/// <summary>
/// User object type handler
/// </summary>
public class UserHandler : ObjectTypeHandler<UserInfo, UserViewContext>
{
public UserHandler(ConnectionService connectionService) : base(connectionService)
{
}
public override bool CanHandleType(SqlObjectType objectType)
{
return objectType == SqlObjectType.User;
}
public override async Task<InitializeViewResult> InitializeObjectView(Contracts.InitializeViewRequestParams parameters)
{
// check input parameters
if (string.IsNullOrWhiteSpace(parameters.Database))
{
throw new ArgumentNullException("parameters.Database");
}
// open a connection for running the user dialog and associated task
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);
// create a default user data context and database object
CDataContainer dataContainer = CreateUserDataContainer(connInfo, null, ConfigAction.Create, parameters.Database);
string databaseUrn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']", Urn.EscapeString(parameters.Database));
Database parentDb = dataContainer.Server.GetSmoObject(databaseUrn) as Database;
var languageOptions = LanguageUtils.GetDefaultLanguageOptions(dataContainer);
var languageOptionsList = languageOptions.Select(LanguageUtils.FormatLanguageDisplay).ToList();
languageOptionsList.Insert(0, SR.DefaultLanguagePlaceholder);
// if viewing an exisitng user then populate some properties
UserInfo userInfo = null;
string defaultLanguageAlias = null;
ExhaustiveUserTypes userType = ExhaustiveUserTypes.LoginMappedUser;
if (!parameters.IsNewObject)
{
User existingUser = dataContainer.Server.GetSmoObject(parameters.ObjectUrn) as User;
userType = UserActions.GetCurrentUserTypeForExistingUser(existingUser);
DatabaseUserType databaseUserType = UserActions.GetDatabaseUserTypeForUserType(userType);
// if contained user determine if SQL or AAD auth type
ServerAuthenticationType authenticationType =
(databaseUserType == DatabaseUserType.Contained && userType == ExhaustiveUserTypes.ExternalUser)
? ServerAuthenticationType.AzureActiveDirectory : ServerAuthenticationType.Sql;
userInfo = new UserInfo()
{
Type = databaseUserType,
AuthenticationType = authenticationType,
Name = existingUser.Name,
LoginName = existingUser.Login,
DefaultSchema = existingUser.DefaultSchema,
};
// Default language is only applicable for users inside a contained database.
if (LanguageUtils.IsDefaultLanguageSupported(dataContainer.Server)
&& parentDb.ContainmentType != ContainmentType.None)
{
defaultLanguageAlias = LanguageUtils.GetLanguageAliasFromName(
existingUser.Parent.Parent,
existingUser.DefaultLanguage.Name);
}
}
// generate a user prototype
UserPrototype currentUserPrototype = UserPrototypeFactory.GetUserPrototype(dataContainer, userInfo, originalData: null, userType);
// get the default schema if available
string defaultSchema = null;
IUserPrototypeWithDefaultSchema defaultSchemaPrototype = currentUserPrototype as IUserPrototypeWithDefaultSchema;
if (defaultSchemaPrototype != null && defaultSchemaPrototype.IsDefaultSchemaSupported)
{
defaultSchema = defaultSchemaPrototype.DefaultSchema;
}
ServerConnection serverConnection = dataContainer.ServerConnection;
bool isSqlAzure = serverConnection.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase;
bool supportsContainedUser = isSqlAzure || UserActions.IsParentDatabaseContained(parentDb);
// set default alias to <default> if needed
if (string.IsNullOrEmpty(defaultLanguageAlias)
&& supportsContainedUser
&& LanguageUtils.IsDefaultLanguageSupported(dataContainer.Server))
{
defaultLanguageAlias = SR.DefaultLanguagePlaceholder;
}
// set the fake password placeholder when editing an existing user
string password = null;
IUserPrototypeWithPassword userWithPwdPrototype = currentUserPrototype as IUserPrototypeWithPassword;
if (userWithPwdPrototype != null && !parameters.IsNewObject)
{
userWithPwdPrototype.Password = DatabaseUtils.GetReadOnlySecureString(LoginPrototype.fakePassword);
userWithPwdPrototype.PasswordConfirm = DatabaseUtils.GetReadOnlySecureString(LoginPrototype.fakePassword);
password = LoginPrototype.fakePassword;
}
// get the login name if it exists
string loginName = null;
IUserPrototypeWithMappedLogin mappedLoginPrototype = currentUserPrototype as IUserPrototypeWithMappedLogin;
if (mappedLoginPrototype != null)
{
loginName = mappedLoginPrototype.LoginName;
}
// populate user's role assignments
List<string> databaseRoles = new List<string>();
foreach (string role in currentUserPrototype.DatabaseRoleNames)
{
if (currentUserPrototype.IsRoleMember(role))
{
databaseRoles.Add(role);
}
}
// populate user's schema ownerships
List<string> schemaNames = new List<string>();
foreach (string schema in currentUserPrototype.SchemaNames)
{
if (currentUserPrototype.IsSchemaOwner(schema))
{
schemaNames.Add(schema);
}
}
UserViewInfo userViewInfo = new UserViewInfo()
{
ObjectInfo = new UserInfo()
{
Type = userInfo?.Type ?? DatabaseUserType.WithLogin,
AuthenticationType = userInfo?.AuthenticationType ?? ServerAuthenticationType.Sql,
Name = currentUserPrototype.Name,
LoginName = loginName,
Password = password,
DefaultSchema = defaultSchema,
OwnedSchemas = schemaNames.ToArray(),
DatabaseRoles = databaseRoles.ToArray(),
DefaultLanguage = LanguageUtils.FormatLanguageDisplay(
languageOptions.FirstOrDefault(o => o?.Language.Name == defaultLanguageAlias || o?.Language.Alias == defaultLanguageAlias, null)),
},
SupportContainedUser = supportsContainedUser,
SupportWindowsAuthentication = false,
SupportAADAuthentication = currentUserPrototype.AADAuthSupported,
SupportSQLAuthentication = true,
Languages = languageOptionsList.ToArray(),
Schemas = currentUserPrototype.SchemaNames.ToArray(),
Logins = DatabaseUtils.LoadSqlLogins(serverConnection),
DatabaseRoles = currentUserPrototype.DatabaseRoleNames.ToArray()
};
var context = new UserViewContext(parameters, serverConnection, currentUserPrototype.CurrentState);
return new InitializeViewResult { ViewInfo = userViewInfo, Context = context };
}
public override Task Save(UserViewContext context, UserInfo obj)
{
ConfigureUser(
context.Parameters.ContextId,
obj,
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
RunType.RunNow,
context.Parameters.Database,
context.OriginalUserData);
return Task.CompletedTask;
}
public override Task<string> Script(UserViewContext context, UserInfo obj)
{
var script = ConfigureUser(
context.Parameters.ContextId,
obj,
context.Parameters.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
RunType.ScriptToWindow,
context.Parameters.Database,
context.OriginalUserData);
return Task.FromResult(script);
}
internal CDataContainer CreateUserDataContainer(ConnectionInfo connInfo, UserInfo user, ConfigAction configAction, string databaseName)
{
var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer");
var connectionInfoWithConnection = new SqlConnectionInfoWithConnection();
connectionInfoWithConnection.ServerConnection = serverConnection;
string urn = (configAction == ConfigAction.Update && user != null)
? string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']/User[@Name='{1}']",
Urn.EscapeString(databaseName),
Urn.EscapeString(user.Name))
: string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']",
Urn.EscapeString(databaseName));
ActionContext context = new ActionContext(serverConnection, "User", urn);
DataContainerXmlGenerator containerXml = new DataContainerXmlGenerator(context);
if (configAction == ConfigAction.Create)
{
containerXml.AddProperty("itemtype", "User");
}
XmlDocument xmlDoc = containerXml.GenerateXmlDocument();
return CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc);
}
internal string ConfigureUser(string ownerUri, UserInfo user, ConfigAction configAction, RunType runType, string databaseName, UserPrototypeData originalData)
{
ConnectionInfo connInfo;
this.ConnectionService.TryFindConnection(ownerUri, out connInfo);
if (connInfo == null)
{
throw new ArgumentException("Invalid connection URI '{0}'", ownerUri);
}
string sqlScript = string.Empty;
CDataContainer dataContainer = CreateUserDataContainer(connInfo, user, configAction, databaseName);
using (var actions = new UserActions(dataContainer, configAction, user, originalData))
{
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;
}
}
}

View File

@@ -0,0 +1,62 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
[JsonConverter(typeof(StringEnumConverter))]
public enum ServerAuthenticationType
{
[EnumMember(Value = "Windows")]
Windows,
[EnumMember(Value = "Sql")]
Sql,
[EnumMember(Value = "AAD")]
AzureActiveDirectory
}
[JsonConverter(typeof(StringEnumConverter))]
public enum DatabaseUserType
{
// User with a server level login.
[EnumMember(Value = "WithLogin")]
WithLogin,
// User based on a Windows user/group that has no login, but can connect to the Database Engine through membership in a Windows group.
[EnumMember(Value = "WithWindowsGroupLogin")]
WithWindowsGroupLogin,
// Contained user, authentication is done within the database.
[EnumMember(Value = "Contained")]
Contained,
// User that cannot authenticate.
[EnumMember(Value = "NoConnectAccess")]
NoConnectAccess
}
/// <summary>
/// a class for storing various user properties
/// </summary>
public class UserInfo : SqlObject
{
public DatabaseUserType? Type { get; set; }
public string? LoginName { get; set; }
public string? Password { get; set; }
public string? DefaultSchema { get; set; }
public string[]? OwnedSchemas { get; set; }
public string[]? DatabaseRoles { get; set; }
public ServerAuthenticationType AuthenticationType { get; set; }
public string? DefaultLanguage { get; set; }
}
}

View File

@@ -0,0 +1,35 @@
//
// 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;
using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public class UserViewContext : SqlObjectViewContext
{
public UserViewContext(InitializeViewRequestParams parameters, ServerConnection connection, UserPrototypeData originalUserData) : base(parameters)
{
this.OriginalUserData = originalUserData;
this.Connection = connection;
}
public UserPrototypeData OriginalUserData { get; }
public ServerConnection Connection { get; }
public override void Dispose()
{
try
{
this.Connection.Disconnect();
}
catch
{
// ignore
}
}
}
}

View File

@@ -0,0 +1,29 @@
//
// 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
{
/// <summary>
/// The information required to render the user view.
/// </summary>
public class UserViewInfo : SqlObjectViewInfo
{
public bool SupportContainedUser { get; set; }
public bool SupportWindowsAuthentication { get; set; }
public bool SupportAADAuthentication { get; set; }
public bool SupportSQLAuthentication { get; set; }
public string[]? Languages { get; set; }
public string[]? Schemas { get; set; }
public string[]? Logins { get; set; }
public string[]? DatabaseRoles { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
//
// 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 abstract class SqlObject
{
public string? Name { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
[JsonConverter(typeof(StringEnumConverter))]
public enum SqlObjectType
{
[EnumMember(Value = "Column")]
Column,
[EnumMember(Value = "Credential")]
Credential,
[EnumMember(Value = "ServerLevelLogin")]
ServerLevelLogin,
[EnumMember(Value = "Table")]
Table,
[EnumMember(Value = "User")]
User,
[EnumMember(Value = "View")]
View
}
}

View File

@@ -0,0 +1,28 @@
//
// 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.SqlTools.ServiceLayer.ObjectManagement.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public abstract class SqlObjectViewContext : IDisposable
{
public SqlObjectViewContext(InitializeViewRequestParams parameters)
{
this.Parameters = parameters;
}
public InitializeViewRequestParams Parameters { get; }
public abstract void Dispose();
}
public class InitializeViewResult
{
public SqlObjectViewContext Context { get; set; }
public SqlObjectViewInfo ViewInfo { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{
public abstract class SqlObjectViewInfo
{
public SqlObject ObjectInfo { get; set; }
}
}