From e314f839d8f44a7456d9cbd2bd90abb0715039f9 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Wed, 19 Apr 2023 15:43:01 -0700 Subject: [PATCH] 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 --- .../HostLoader.cs | 4 - .../Contracts/DisposeViewRequest.cs | 23 + .../ObjectManagement/Contracts/DropRequest.cs | 8 +- .../Contracts/InitializeViewRequest.cs | 48 ++ .../Contracts/RenameRequest.cs | 9 +- .../ObjectManagement/Contracts/SaveRequest.cs | 31 + .../Contracts/ScriptRequest.cs | 29 + .../ObjectManagementService.cs | 146 ++--- .../ObjectManagement/ObjectTypeHandler.cs | 115 ++++ .../ObjectTypes/CommonObjectTypeHandler.cs | 54 ++ .../Credential/CredentialHandler.cs | 88 +++ .../ObjectTypes/Credential}/CredentialInfo.cs | 9 +- .../Credential/CredentialViewContext.cs | 16 + .../Credential/CredentialViewInfo.cs | 14 + .../ObjectTypes/Login/LoginHandler.cs} | 414 +++++-------- .../ObjectTypes/Login}/LoginInfo.cs | 8 +- .../ObjectTypes/Login/LoginViewContext.cs | 18 + .../ObjectTypes/Login}/LoginViewInfo.cs | 7 +- .../ObjectTypes}/Security/AppRoleGeneral.cs | 2 +- .../Security/CredentialActions.cs | 19 +- .../ObjectTypes}/Security/CredentialData.cs | 65 +- .../Security/DatabaseRoleGeneral.cs | 3 +- .../ObjectTypes/Security/LoginActions.cs | 40 ++ .../ObjectTypes}/Security/LoginData.cs | 3 +- .../ObjectTypes}/Security/NetStandardUtils.cs | 2 +- .../ObjectTypes}/Security/PermissionsData.cs | 2 +- .../Security/PermissionsDataExtensions.cs | 2 +- .../SchemaScopedSecurableAttribute.cs | 2 +- .../ServerRoleManageTaskFormComponent.cs | 2 +- .../SqlCollationSensitiveStringComparer.cs | 2 +- .../Security/SqlObjectSearchData.cs | 2 +- .../ObjectTypes/Security/UserActions.cs | 167 +++++ .../ObjectTypes}/Security/UserData.cs | 3 +- .../ObjectTypes/User/UserHandler.cs | 283 +++++++++ .../ObjectTypes/User}/UserInfo.cs | 30 +- .../ObjectTypes/User/UserViewContext.cs | 35 ++ .../ObjectTypes/User/UserViewInfo.cs | 29 + .../ObjectManagement/SqlObject.cs | 12 + .../ObjectManagement/SqlObjectType.cs | 28 + .../ObjectManagement/SqlObjectViewContext.cs | 28 + .../ObjectManagement/SqlObjectViewInfo.cs | 13 + .../Security/Contracts/CredentialRequest.cs | 117 ---- .../Security/Contracts/LoginRequest.cs | 128 ---- .../Security/Contracts/UserRequest.cs | 135 ---- .../Security/SecurityService.cs | 223 ------- .../Security/UserActions.cs | 582 ------------------ .../Agent/AgentProxyTests.cs | 20 +- .../Agent/AgentTestUtils.cs | 4 +- .../ObjectManagement/CredentialTests.cs | 46 ++ .../ObjectManagement/LoginTests.cs | 46 ++ .../ObjectManagementServiceTests.cs | 26 +- .../ObjectManagementTestUtils.cs | 206 +++++++ .../ObjectManagement/UserTests.cs | 97 +++ .../Security/CredentialTests.cs | 86 --- .../Security/LoginTests.cs | 65 -- .../Security/SecurityTestUtils.cs | 300 --------- .../Security/UserTests.cs | 140 ----- 57 files changed, 1802 insertions(+), 2234 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DisposeViewRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/InitializeViewRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/SaveRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/ScriptRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypeHandler.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/CommonObjectTypeHandler.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialHandler.cs rename src/Microsoft.SqlTools.ServiceLayer/{Security/Contracts => ObjectManagement/ObjectTypes/Credential}/CredentialInfo.cs (71%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewInfo.cs rename src/Microsoft.SqlTools.ServiceLayer/{Security/LoginActions.cs => ObjectManagement/ObjectTypes/Login/LoginHandler.cs} (63%) rename src/Microsoft.SqlTools.ServiceLayer/{Security/Contracts => ObjectManagement/ObjectTypes/Login}/LoginInfo.cs (90%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewContext.cs rename src/Microsoft.SqlTools.ServiceLayer/{Security/Contracts => ObjectManagement/ObjectTypes/Login}/LoginViewInfo.cs (80%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/AppRoleGeneral.cs (99%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/CredentialActions.cs (86%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/CredentialData.cs (86%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/DatabaseRoleGeneral.cs (99%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginActions.cs rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/LoginData.cs (99%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/NetStandardUtils.cs (99%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/PermissionsData.cs (99%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/PermissionsDataExtensions.cs (97%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/SchemaScopedSecurableAttribute.cs (98%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/ServerRoleManageTaskFormComponent.cs (99%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/SqlCollationSensitiveStringComparer.cs (97%) rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/SqlObjectSearchData.cs (99%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserActions.cs rename src/Microsoft.SqlTools.ServiceLayer/{ => ObjectManagement/ObjectTypes}/Security/UserData.cs (99%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserHandler.cs rename src/Microsoft.SqlTools.ServiceLayer/{Security/Contracts => ObjectManagement/ObjectTypes/User}/UserInfo.cs (69%) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewInfo.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObject.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewContext.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewInfo.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialRequest.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserRequest.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs delete mode 100644 src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/CredentialTests.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/LoginTests.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs create mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/UserTests.cs delete mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/CredentialTests.cs delete mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs delete mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs delete mode 100644 test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs index 540f2877..a9daffed 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -34,7 +34,6 @@ using Microsoft.SqlTools.ServiceLayer.Profiler; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.SchemaCompare; using Microsoft.SqlTools.ServiceLayer.Scripting; -using Microsoft.SqlTools.ServiceLayer.Security; using Microsoft.SqlTools.ServiceLayer.ServerConfigurations; using Microsoft.SqlTools.ServiceLayer.SqlAssessment; using Microsoft.SqlTools.ServiceLayer.SqlContext; @@ -130,9 +129,6 @@ namespace Microsoft.SqlTools.ServiceLayer ProfilerService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(ProfilerService.Instance); - SecurityService.Instance.InitializeService(serviceHost); - serviceProvider.RegisterSingleService(SecurityService.Instance); - DacFxService.Instance.InitializeService(serviceHost, commandOptions); serviceProvider.RegisterSingleService(DacFxService.Instance); diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DisposeViewRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DisposeViewRequest.cs new file mode 100644 index 00000000..fc144e3b --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DisposeViewRequest.cs @@ -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 Type = RequestType.Create("objectManagement/disposeView"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DropRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DropRequest.cs index 83bb70eb..5f46cd18 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DropRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/DropRequest.cs @@ -11,6 +11,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts { public class DropRequestParams : GeneralRequestDetails { + /// + /// The object type. + /// + public SqlObjectType ObjectType { get; set; } /// /// SFC (SMO) URN identifying the object /// @@ -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 Type = RequestType.Create("objectManagement/drop"); + public static readonly RequestType Type = RequestType.Create("objectManagement/drop"); } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/InitializeViewRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/InitializeViewRequest.cs new file mode 100644 index 00000000..18635b6f --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/InitializeViewRequest.cs @@ -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 + { + /// + /// The connection uri. + /// + public string ConnectionUri { get; set; } + /// + /// The target database name. + /// + public string Database { get; set; } + /// + /// The object type. + /// + public SqlObjectType ObjectType { get; set; } + /// + /// Whether the view is for a new object. + /// + public bool IsNewObject { get; set; } + /// + /// The object view context id. + /// + public string ContextId { get; set; } + /// + /// Urn of the parent object. + /// + public string ParentUrn { get; set; } + /// + /// Urn of the object. Only set when the view is for an existing object. + /// + public string ObjectUrn { get; set; } + } + + public class InitializeViewRequest + { + public static readonly RequestType Type = RequestType.Create("objectManagement/initializeView"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/RenameRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/RenameRequest.cs index 93e459cd..f10e5718 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/RenameRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/RenameRequest.cs @@ -11,6 +11,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts { public class RenameRequestParams : GeneralRequestDetails { + /// + /// The object type. + /// + public SqlObjectType ObjectType { get; set; } /// /// SFC (SMO) URN identifying the object /// @@ -24,8 +28,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts /// public string ConnectionUri { get; set; } } + + public class RenameRequestResponse { } + public class RenameRequest { - public static readonly RequestType Type = RequestType.Create("objectManagement/rename"); + public static readonly RequestType Type = RequestType.Create("objectManagement/rename"); } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/SaveRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/SaveRequest.cs new file mode 100644 index 00000000..5d6de775 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/SaveRequest.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#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 + { + /// + /// The context id. + /// + public string ContextId { get; set; } + /// + /// The object information. + /// + public JToken Object { get; set; } + } + + public class SaveObjectRequestResponse { } + + public class SaveObjectRequest + { + public static readonly RequestType Type = RequestType.Create("objectManagement/save"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/ScriptRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/ScriptRequest.cs new file mode 100644 index 00000000..6f145356 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/Contracts/ScriptRequest.cs @@ -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 + { + /// + /// The context id. + /// + public string ContextId { get; set; } + /// + /// The object information. + /// + public JToken Object { get; set; } + } + + public class ScriptObjectRequest + { + public static readonly RequestType Type = RequestType.Create("objectManagement/script"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs index 32d79bb0..3aca8955 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectManagementService.cs @@ -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 /// public class ObjectManagementService { - private const string ObjectManagementServiceApplicationName = "azdata-object-management"; + public const string ApplicationName = "azdata-object-management"; private static Lazy objectManagementServiceInstance = new Lazy(() => new ObjectManagementService()); public static ObjectManagementService Instance => objectManagementServiceInstance.Value; public static ConnectionService connectionService; private IProtocolEndpoint serviceHost; - public ObjectManagementService() { } + private List objectTypeHandlers = new List(); + private ConcurrentDictionary contextMap = new ConcurrentDictionary(); + + 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)); + } /// /// 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); } - /// - /// Method to handle the renaming operation - /// - /// parameters which are needed to execute renaming operation - /// Request Context - /// - internal async Task HandleRenameRequest(RenameRequestParams requestParams, RequestContext requestContext) + internal async Task HandleRenameRequest(RenameRequestParams requestParams, RequestContext 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()); } - /// - /// Method to handle the delete object request - /// - /// parameters which are needed to execute deletion operation - /// Request Context - /// - internal async Task HandleDropRequest(DropRequestParams requestParams, RequestContext requestContext) + internal async Task HandleDropRequest(DropRequestParams requestParams, RequestContext 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 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 action) + internal async Task HandleSaveObjectRequest(SaveObjectRequestParams requestParams, RequestContext 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 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 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"); } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypeHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypeHandler.cs new file mode 100644 index 00000000..acc94412 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypeHandler.cs @@ -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 InitializeObjectView(Contracts.InitializeViewRequestParams requestParams); + Task Save(SqlObjectViewContext context, SqlObject obj); + Task 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 : 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 InitializeObjectView(Contracts.InitializeViewRequestParams requestParams); + public abstract Task Save(ContextType context, ObjectType obj); + public abstract Task Script(ContextType context, ObjectType obj); + + public Task Save(SqlObjectViewContext context, SqlObject obj) + { + return this.Save((ContextType)context, (ObjectType)obj); + } + + public Task 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); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/CommonObjectTypeHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/CommonObjectTypeHandler.cs new file mode 100644 index 00000000..f86fdca9 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/CommonObjectTypeHandler.cs @@ -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() { } + } + + /// + /// A handler for the object types that only has rename/drop support + /// + public class CommonObjectTypeHandler : ObjectTypeHandler + { + // 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 InitializeObjectView(Contracts.InitializeViewRequestParams requestParams) + { + throw new NotSupportedException(NotSupportedException); + } + + public override Task Script(CommonObjectTypeViewContext context, CommonObjectType obj) + { + throw new NotSupportedException(NotSupportedException); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialHandler.cs new file mode 100644 index 00000000..b16fb8bf --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialHandler.cs @@ -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 +{ + /// + /// Credential object type handler + /// + public class CredentialHandler : ObjectTypeHandler + { + public CredentialHandler(ConnectionService connectionService) : base(connectionService) + { + } + + public override bool CanHandleType(SqlObjectType objectType) + { + return objectType == SqlObjectType.Credential; + } + + public override Task 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 Script(CredentialViewContext context, CredentialInfo obj) + { + throw new NotImplementedException(); + } + + private Task> ConfigureCredential(string ownerUri, CredentialInfo credential, ConfigAction configAction, RunType runType) + { + return Task>.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(true, string.Empty); + } + catch (Exception ex) + { + return new Tuple(false, ex.ToString()); + } + }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialInfo.cs similarity index 71% rename from src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialInfo.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialInfo.cs index 2a1908b2..2c822d8c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialInfo.cs @@ -7,18 +7,17 @@ using System; -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// a class for storing various credential properties /// - public class CredentialInfo + public class CredentialInfo : SqlObject { public int Id { get; set; } public string Identity { get; set; } - public string Name { get; set; } public DateTime DateLastModified { get; set; } public DateTime CreateDate { get; set; } - public string ProviderName { get; set; } + public string ProviderName { get; set; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewContext.cs new file mode 100644 index 00000000..052adeac --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewContext.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +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() { } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewInfo.cs new file mode 100644 index 00000000..9338bd17 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Credential/CredentialViewInfo.cs @@ -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 +{ + /// + /// The information required to render the credential view. + /// + public class CredentialViewInfo : SqlObjectViewInfo + { + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginHandler.cs similarity index 63% rename from src/Microsoft.SqlTools.ServiceLayer/Security/LoginActions.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginHandler.cs index c4899aeb..c6de1ff2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginActions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginHandler.cs @@ -3,133 +3,179 @@ // 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.Data; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; -using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { - internal class LoginServiceHandlerImpl + /// + /// Login object type handler + /// + public class LoginHandler : ObjectTypeHandler { - private class ViewState + public LoginHandler(ConnectionService connectionService) : base(connectionService) { } + + public override bool CanHandleType(SqlObjectType objectType) { - public bool IsNewObject { get; set; } - - public string ConnectionUri { get; set; } - - public ViewState(bool isNewObject, string connectionUri) - { - this.IsNewObject = isNewObject; - - this.ConnectionUri = connectionUri; - } + return objectType == SqlObjectType.ServerLevelLogin; } - private Dictionary contextIdToViewState = new Dictionary(); - - private ConnectionService? connectionService; - - /// - /// Internal for testing purposes only - /// - internal ConnectionService ConnectionServiceInstance + public override Task InitializeObjectView(InitializeViewRequestParams parameters) { - get - { - connectionService ??= ConnectionService.Instance; - return connectionService; - } - - set - { - connectionService = value; - } - } - - /// - /// Handle request to create a login - /// - internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext requestContext) - { - DoHandleCreateLoginRequest(parameters.ContextId, parameters.Login, RunType.RunNow); - - await requestContext.SendResult(new object()); - } - - private string DoHandleCreateLoginRequest( - string contextId, LoginInfo login, RunType runType) - { - ViewState? viewState; - this.contextIdToViewState.TryGetValue(contextId, out viewState); - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(viewState?.ConnectionUri, out connInfo); - - if (connInfo == null) + this.ConnectionService.TryFindConnection(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); + LoginViewInfo loginViewInfo = new LoginViewInfo(); - if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) + // TODO cache databases and languages + string[] databases = new string[dataContainer.Server.Databases.Count]; + for (int i = 0; i < dataContainer.Server.Databases.Count; i++) { - // 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 - } + databases[i] = dataContainer.Server.Databases[i].Name; + } - // check that password and confirm password controls' text matches - if (0 != string.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal)) + 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 loginServerRoles = new List(); + foreach (string role in prototype.ServerRoles.ServerRoleNames) + { + if (prototype.ServerRoles.IsMember(role)) { - // raise error here + loginServerRoles.Add(role); } } - // TODO move this to LoginData - // TODO support role assignment for Azure - foreach (string role in login.ServerRoles ?? Enumerable.Empty()) + LoginInfo loginInfo = new LoginInfo() { - prototype.ServerRoles.SetMember(role, true); + 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 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 ConfigureLogin( - dataContainer, - ConfigAction.Create, - runType, - prototype); + return sqlScript; } - internal async Task HandleUpdateLoginRequest(UpdateLoginParams parameters, RequestContext requestContext) + private string DoHandleUpdateLoginRequest(LoginViewContext context, LoginInfo login, RunType runType) { - DoHandleUpdateLoginRequest(parameters.ContextId, parameters.Login, RunType.RunNow); - - await requestContext.SendResult(new object()); - } - - private string DoHandleUpdateLoginRequest( - string contextId, LoginInfo login, RunType runType) - { - ViewState? viewState; - this.contextIdToViewState.TryGetValue(contextId, out viewState); - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(viewState?.ConnectionUri, out connInfo); - if (connInfo == null) + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + if (connInfo == null) { throw new ArgumentException("Invalid ConnectionUri"); } @@ -191,182 +237,46 @@ namespace Microsoft.SqlTools.ServiceLayer.Security prototype); } - /// - /// Handle request to script a user - /// - internal async Task HandleScriptLoginRequest(ScriptLoginParams parameters, RequestContext requestContext) + private string DoHandleCreateLoginRequest(LoginViewContext context, LoginInfo login, RunType runType) { - if (parameters.ContextId == null) - { - throw new ArgumentException("Invalid context ID"); - } - - ViewState viewState; - this.contextIdToViewState.TryGetValue(parameters.ContextId, out viewState); - - if (viewState == null) - { - throw new ArgumentException("Invalid context ID view state"); - } - - string sqlScript = (viewState.IsNewObject) - ? DoHandleCreateLoginRequest(parameters.ContextId, parameters.Login, RunType.ScriptToWindow) - : DoHandleUpdateLoginRequest(parameters.ContextId, parameters.Login, RunType.ScriptToWindow); - - await requestContext.SendResult(sqlScript); - } - - internal async Task HandleInitializeLoginViewRequest(InitializeLoginViewRequestParams parameters, RequestContext requestContext) - { - this.contextIdToViewState.Add( - parameters.ContextId, - new ViewState(parameters.IsNewObject, parameters.ConnectionUri)); - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo); - if (connInfo == null) + this.ConnectionService.TryFindConnection(context.Parameters.ConnectionUri, out connInfo); + + if (connInfo == null) { throw new ArgumentException("Invalid ConnectionUri"); } + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); - LoginViewInfo loginViewInfo = new LoginViewInfo(); + LoginPrototype prototype = new LoginPrototype(dataContainer.Server, login); - // TODO cache databases and languages - string[] databases = new string[dataContainer.Server.Databases.Count]; - for (int i = 0; i < dataContainer.Server.Databases.Count; i++) + if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) { - 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.Logins[parameters.Name]); - - List loginServerRoles = new List(); - foreach (string role in prototype.ServerRoles.ServerRoleNames) - { - if (prototype.ServerRoles.IsMember(role)) + // 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) { - loginServerRoles.Add(role); + // 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 } } - LoginInfo loginInfo = new LoginInfo() + // TODO move this to LoginData + // TODO support role assignment for Azure + foreach (string role in login.ServerRoles ?? Enumerable.Empty()) { - 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] - }; - - await requestContext.SendResult(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 - }); - } - - 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; - } - } - - internal async Task HandleDisposeLoginViewRequest(DisposeLoginViewRequestParams parameters, RequestContext requestContext) - { - await requestContext.SendResult(new object()); - } - - internal 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; - } + prototype.ServerRoles.SetMember(role, true); } - return sqlScript; - } - } - - internal class LoginActions : ManagementActionBase - { - private ConfigAction configAction; - - private LoginPrototype prototype; - - /// - /// Handle login create and update actions - /// - public LoginActions(CDataContainer dataContainer, ConfigAction configAction, LoginPrototype prototype) - { - this.DataContainer = dataContainer; - this.configAction = configAction; - this.prototype = prototype; + return ConfigureLogin(dataContainer, ConfigAction.Create, runType, prototype); } - /// - /// called by the management actions framework to execute the action - /// - /// - 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); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginInfo.cs similarity index 90% rename from src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginInfo.cs index 8a95996e..82c80856 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { [JsonConverter(typeof(StringEnumConverter))] public enum LoginAuthenticationType @@ -35,10 +35,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// /// a class for storing various login properties /// - public class LoginInfo + public class LoginInfo : SqlObject { - public string Name { get; set; } - public LoginAuthenticationType AuthenticationType { get; set; } public bool WindowsGrantAccess { get; set; } @@ -62,7 +60,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts public string DefaultDatabase { get; set; } - public string[] ServerRoles {get; set;} + public string[] ServerRoles { get; set; } public ServerLoginDatabaseUserMapping[] UserMapping; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewContext.cs new file mode 100644 index 00000000..badaeef0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewContext.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement +{ + public class LoginViewContext : SqlObjectViewContext + { + public LoginViewContext(Contracts.InitializeViewRequestParams parameters) : base(parameters) + { + } + + public override void Dispose() + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewInfo.cs similarity index 80% rename from src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewInfo.cs index 28333888..edd90d67 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Login/LoginViewInfo.cs @@ -2,13 +2,12 @@ // 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.Security.Contracts +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { - public class LoginViewInfo + public class LoginViewInfo : SqlObjectViewInfo { - - public LoginInfo ObjectInfo { get; set; } public bool SupportWindowsAuthentication { get; set; } public bool SupportAADAuthentication { get; set; } public bool SupportSQLAuthentication { get; set; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/AppRoleGeneral.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/AppRoleGeneral.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs index e0afe74b..f770fd26 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/AppRoleGeneral.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/AppRoleGeneral.cs @@ -15,7 +15,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Management; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// AppRoleGeneral - main app role page diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/CredentialActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialActions.cs similarity index 86% rename from src/Microsoft.SqlTools.ServiceLayer/Security/CredentialActions.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialActions.cs index bc874cb4..fe28dd8a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/CredentialActions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialActions.cs @@ -6,24 +6,23 @@ #nullable disable using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { internal class CredentialActions : ManagementActionBase { -#region Constants + #region Constants private const int MAX_SQL_SYS_NAME_LENGTH = 128; // max sql sys name length -#endregion + #endregion -#region Variables + #region Variables private CredentialData credentialData = null; private CredentialInfo credential; private ConfigAction configAction; -#endregion + #endregion -#region Constructors / Dispose + #region Constructors / Dispose /// /// required when loading from Object Explorer context /// @@ -44,9 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security /// /// Clean up any resources being used. /// - protected override void Dispose( bool disposing ) + protected override void Dispose(bool disposing) { - base.Dispose( disposing ); + base.Dispose(disposing); if (disposing == true) { if (this.credentialData != null) @@ -55,7 +54,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } } } -#endregion + #endregion /// /// called on background thread by the framework to execute the action diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/CredentialData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialData.cs similarity index 86% rename from src/Microsoft.SqlTools.ServiceLayer/Security/CredentialData.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialData.cs index 01a52cad..65686f7d 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/CredentialData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/CredentialData.cs @@ -10,36 +10,35 @@ using System.Security; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -namespace Microsoft.SqlTools.ServiceLayer.Security +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; } + public string CredentialName + { + get { return credentialName; } + set { credentialName = value; } } private string credentialIdentity = string.Empty; - public string CredentialIdentity - { - get { return credentialIdentity; } - set { credentialIdentity = value; } + public string CredentialIdentity + { + get { return credentialIdentity; } + set { credentialIdentity = value; } } private SecureString securePassword; - public SecureString SecurePassword - { - get { return securePassword; } - set - { - securePassword = value; - PasswordWasChanged = true; - } + public SecureString SecurePassword + { + get { return securePassword; } + set + { + securePassword = value; + PasswordWasChanged = true; + } } private bool isPropertiesMode = false; @@ -52,24 +51,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } private bool passwordWasChanged = false; - public bool PasswordWasChanged - { - get { return passwordWasChanged; } - set { passwordWasChanged = value; } + public bool PasswordWasChanged + { + get { return passwordWasChanged; } + set { passwordWasChanged = value; } } private bool isEncryptionByProvider = false; - public bool IsEncryptionByProvider - { - get { return isEncryptionByProvider; } - set { isEncryptionByProvider = value; } + public bool IsEncryptionByProvider + { + get { return isEncryptionByProvider; } + set { isEncryptionByProvider = value; } } private string providerName = string.Empty; - public string ProviderName - { - get { return providerName; } - set { providerName = value; } + public string ProviderName + { + get { return providerName; } + set { providerName = value; } } public Microsoft.SqlServer.Management.Smo.Credential Credential @@ -110,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security 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) @@ -146,7 +145,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security bool isKatmaiAndNotMatrix = (this.context.Server.Version.Major >= 10); Urn urn = new Urn("Server/Credential[@Name='" + Urn.EscapeString(this.CredentialName) + "']"); - string [] fields; + string[] fields; if (isKatmaiAndNotMatrix) { fields = new string[] { ENUMERATOR_FIELD_IDENTITY, ENUMERATOR_FIELD_PROVIDER_NAME }; @@ -206,7 +205,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security /// private void SendToServerCreateCredential() { - Microsoft.SqlServer.Management.Smo.Credential smoCredential = new Microsoft.SqlServer.Management.Smo.Credential ( + Microsoft.SqlServer.Management.Smo.Credential smoCredential = new Microsoft.SqlServer.Management.Smo.Credential( this.Context.Server, this.CredentialName); if (this.isEncryptionByProvider) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/DatabaseRoleGeneral.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/DatabaseRoleGeneral.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs index f4c91de3..bd362cda 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/DatabaseRoleGeneral.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/DatabaseRoleGeneral.cs @@ -15,8 +15,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Management; -namespace Microsoft.SqlTools.ServiceLayer.Security - +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// DatabaseRoleGeneral - main panel for database role diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginActions.cs new file mode 100644 index 00000000..0fb39022 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginActions.cs @@ -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; + + /// + /// Handle login create and update actions + /// + public LoginActions(CDataContainer dataContainer, ConfigAction configAction, LoginPrototype prototype) + { + this.DataContainer = dataContainer; + this.configAction = configAction; + this.prototype = prototype; + } + + /// + /// called by the management actions framework to execute the action + /// + /// + public override void OnRunNow(object sender) + { + if (this.configAction != ConfigAction.Drop) + { + prototype.ApplyGeneralChanges(this.DataContainer.Server); + prototype.ApplyServerRoleChanges(this.DataContainer.Server); + prototype.ApplyDatabaseRoleChanges(this.DataContainer.Server); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginData.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginData.cs index d664a075..7a99d672 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/LoginData.cs @@ -16,9 +16,8 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// Encapsulates database roles, access and default schema diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/NetStandardUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/NetStandardUtils.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/NetStandardUtils.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/NetStandardUtils.cs index e444c880..b912a078 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/NetStandardUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/NetStandardUtils.cs @@ -10,7 +10,7 @@ using System.Globalization; using SMO = Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Common; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// NetStandard compatible helpers diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsData.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsData.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsData.cs index 00abfea2..7ca70bbd 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsData.cs @@ -17,7 +17,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo.Broker; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// Enumeration of sql object types that can have permissions diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsDataExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsDataExtensions.cs similarity index 97% rename from src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsDataExtensions.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsDataExtensions.cs index b1e0cea5..8d48976a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/PermissionsDataExtensions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/PermissionsDataExtensions.cs @@ -9,7 +9,7 @@ using System; using System.Linq; using Microsoft.SqlServer.Management.Common; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { internal static class PermissionsDataExtensions { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SchemaScopedSecurableAttribute.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SchemaScopedSecurableAttribute.cs similarity index 98% rename from src/Microsoft.SqlTools.ServiceLayer/Security/SchemaScopedSecurableAttribute.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SchemaScopedSecurableAttribute.cs index 156e96f5..e1d24e1f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SchemaScopedSecurableAttribute.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SchemaScopedSecurableAttribute.cs @@ -12,7 +12,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// An attribute for sqlmgmt\src\permissionsdata.cs!SecurableType that maps it to the corresponding SMO diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/ServerRoleManageTaskFormComponent.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/ServerRoleManageTaskFormComponent.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs index 143990d1..4b1da0be 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/ServerRoleManageTaskFormComponent.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/ServerRoleManageTaskFormComponent.cs @@ -14,7 +14,7 @@ using System.Collections.Specialized; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlTools.ServiceLayer.Management; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { public class ServerRoleManageTaskFormComponent { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlCollationSensitiveStringComparer.cs similarity index 97% rename from src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlCollationSensitiveStringComparer.cs index b5bfbd32..0342ff4a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlCollationSensitiveStringComparer.cs @@ -13,7 +13,7 @@ using Microsoft.SqlServer.Management.Smo; #endregion -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// String comparer that uses the case sensitivity and other settings diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SqlObjectSearchData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlObjectSearchData.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/SqlObjectSearchData.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlObjectSearchData.cs index 68079454..ed9a0dca 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SqlObjectSearchData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/SqlObjectSearchData.cs @@ -15,7 +15,7 @@ using System.Globalization; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Sdk.Sfc; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// An enumeration of the SQL object types the search dialog knows how to look for diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserActions.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserActions.cs new file mode 100644 index 00000000..8240fcf0 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserActions.cs @@ -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 + /// + /// Handle user create and update actions + /// + 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); + } + + // /// + // /// Clean up any resources being used. + // /// + // protected override void Dispose(bool disposing) + // { + // base.Dispose(disposing); + // } + + #endregion + + /// + /// called by the management actions framework to execute the action + /// + /// + 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; + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserData.cs similarity index 99% rename from src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserData.cs index b9c3a2d0..ab121e55 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/Security/UserData.cs @@ -11,10 +11,9 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; -namespace Microsoft.SqlTools.ServiceLayer.Security +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { /// /// Defines the common behavior of all types of database user objects. diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserHandler.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserHandler.cs new file mode 100644 index 00000000..9a1d2321 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserHandler.cs @@ -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 +{ + /// + /// User object type handler + /// + public class UserHandler : ObjectTypeHandler + { + public UserHandler(ConnectionService connectionService) : base(connectionService) + { + } + + public override bool CanHandleType(SqlObjectType objectType) + { + return objectType == SqlObjectType.User; + } + + public override async Task 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 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 databaseRoles = new List(); + foreach (string role in currentUserPrototype.DatabaseRoleNames) + { + if (currentUserPrototype.IsRoleMember(role)) + { + databaseRoles.Add(role); + } + } + + // populate user's schema ownerships + List schemaNames = new List(); + 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 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; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserInfo.cs similarity index 69% rename from src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs rename to src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserInfo.cs index f8f0a885..adf74304 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserInfo.cs @@ -7,7 +7,7 @@ using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts +namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement { [JsonConverter(typeof(StringEnumConverter))] public enum ServerAuthenticationType @@ -41,12 +41,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// /// a class for storing various user properties /// - public class UserInfo + public class UserInfo : SqlObject { public DatabaseUserType? Type { get; set; } - public string? Name { get; set; } - public string? LoginName { get; set; } public string? Password { get; set; } @@ -61,28 +59,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts public string? DefaultLanguage { get; set; } } - - /// - /// The information required to render the user view. - /// - public class UserViewInfo - { - public UserInfo? ObjectInfo { get; set; } - - 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; } - } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewContext.cs new file mode 100644 index 00000000..67a7aab2 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewContext.cs @@ -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 + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewInfo.cs new file mode 100644 index 00000000..a42ebdd4 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/ObjectTypes/User/UserViewInfo.cs @@ -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 +{ + /// + /// The information required to render the user view. + /// + 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; } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObject.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObject.cs new file mode 100644 index 00000000..0b32461f --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObject.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs new file mode 100644 index 00000000..360d3379 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectType.cs @@ -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 + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewContext.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewContext.cs new file mode 100644 index 00000000..0edf72cd --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewContext.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewInfo.cs new file mode 100644 index 00000000..b957351a --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectManagement/SqlObjectViewInfo.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialRequest.cs deleted file mode 100644 index c37cda98..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/CredentialRequest.cs +++ /dev/null @@ -1,117 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; -using Microsoft.SqlTools.Utility; - -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts -{ - /// - /// Get Credential parameters - /// - public class GetCredentialsParams: GeneralRequestDetails - { - public string OwnerUri { get; set; } - } - - public class GetCredentialsResult: ResultStatus - { - public CredentialInfo[] Credentials { get; set; } - } - - /// - /// SQL Agent Credentials request type - /// - public class GetCredentialsRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("security/credentials"); - } - - /// - /// Create Credential parameters - /// - public class CreateCredentialParams : GeneralRequestDetails - { - public string OwnerUri { get; set; } - - public CredentialInfo Credential { get; set; } - } - - /// - /// Create Credential result - /// - public class CredentialResult : ResultStatus - { - public CredentialInfo Credential { get; set; } - - } - - /// - /// Create Credential request type - /// - public class CreateCredentialRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("security/createcredential"); - } - - /// - /// Update Credential params - /// - public class UpdateCredentialParams : GeneralRequestDetails - { - public string OwnerUri { get; set; } - - public CredentialInfo Credential { get; set; } - } - - /// - /// Update Credential request type - /// - public class UpdateCredentialRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("security/updatecredential"); - } - - /// - /// Delete Credential params - /// - public class DeleteCredentialParams : GeneralRequestDetails - { - public string OwnerUri { get; set; } - - public CredentialInfo Credential { get; set; } - } - - /// - /// Delete Credential request type - /// - public class DeleteCredentialRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("security/deletecredential"); - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs deleted file mode 100644 index 6192481e..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs +++ /dev/null @@ -1,128 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using Microsoft.SqlTools.Hosting.Protocol.Contracts; - -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts -{ - /// - /// Create Login parameters - /// - public class CreateLoginParams - { - public string ContextId { get; set; } - - public LoginInfo Login { get; set; } - } - - /// - /// Create Login request type - /// - public class CreateLoginRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/createLogin"); - } - - /// - /// Update Login params - /// - public class UpdateLoginParams - { - public string ContextId { get; set; } - - public LoginInfo Login { get; set; } - } - - /// - /// Update Login request type - /// - public class UpdateLoginRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/updateLogin"); - } - - - /// - /// Update Login params - /// - public class DisposeLoginViewRequestParams - { - public string ContextId { get; set; } - } - - /// - /// Update Login request type - /// - public class DisposeLoginViewRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/disposeLoginView"); - } - - /// - /// Initialize Login View Request params - /// - - public class InitializeLoginViewRequestParams - { - public string ConnectionUri { get; set; } - public string ContextId { get; set; } - public bool IsNewObject { get; set; } - - public string Name { get; set; } - } - - /// - /// Initialize Login View request type - /// - public class InitializeLoginViewRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/initializeLoginView"); - } - - /// - /// Script Login params - /// - public class ScriptLoginParams - { - public string? ContextId { get; set; } - - public LoginInfo? Login { get; set; } - } - - /// - /// Script Login request type - /// - public class ScriptLoginRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/scriptLogin"); - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserRequest.cs deleted file mode 100644 index 82c2a60c..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserRequest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// -// 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.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; - -namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts -{ - /// - /// Initialize User View parameters - /// - public class InitializeUserViewParams - { - public string? ContextId { get; set; } - - public string? ConnectionUri { get; set; } - - public bool IsNewObject { get; set; } - - public string? Database { get; set; } - - public string? Name { get; set; } - } - - /// - /// Initialize User View request type - /// - public class InitializeUserViewRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/initializeUserView"); - } - - /// - /// Create User parameters - /// - public class CreateUserParams - { - public string? ContextId { get; set; } - public UserInfo? User { get; set; } - } - - /// - /// Create User result - /// - public class CreateUserResult : ResultStatus - { - public UserInfo? User { get; set; } - } - - /// - /// Create User request type - /// - public class CreateUserRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/createUser"); - } - - /// - /// Update User parameters - /// - public class UpdateUserParams - { - public string? ContextId { get; set; } - public UserInfo? User { get; set; } - } - - /// - /// Update User request type - /// - public class UpdateUserRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/updateUser"); - } - - /// - /// Update User params - /// - public class DisposeUserViewRequestParams - { - public string? ContextId { get; set; } - } - - /// - /// Update User request type - /// - public class DisposeUserViewRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/disposeUserView"); - } - - /// - /// Script User params - /// - public class ScriptUserParams - { - public string? ContextId { get; set; } - - public UserInfo? User { get; set; } - } - - /// - /// Script User request type - /// - public class ScriptUserRequest - { - /// - /// Request definition - /// - public static readonly - RequestType Type = - RequestType.Create("objectManagement/scriptUser"); - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs deleted file mode 100644 index 8523d907..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs +++ /dev/null @@ -1,223 +0,0 @@ -// -// 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.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.Hosting; -using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; - -namespace Microsoft.SqlTools.ServiceLayer.Security -{ - /// - /// Main class for Security Service functionality - /// - public sealed class SecurityService : IDisposable - { - private bool disposed; - - private ConnectionService? connectionService; - - private UserServiceHandlerImpl userServiceHandler; - - private LoginServiceHandlerImpl loginServiceHandler; - - private static readonly Lazy instance = new Lazy(() => new SecurityService()); - - - /// - /// Construct a new SecurityService instance with default parameters - /// - public SecurityService() - { - userServiceHandler = new UserServiceHandlerImpl(); - loginServiceHandler = new LoginServiceHandlerImpl(); - } - - /// - /// Gets the singleton instance object - /// - public static SecurityService Instance - { - get { return instance.Value; } - } - - /// - /// Internal for testing purposes only - /// - internal ConnectionService ConnectionServiceInstance - { - get - { - connectionService ??= ConnectionService.Instance; - return connectionService; - } - - set - { - connectionService = value; - } - } - - /// - /// Service host object for sending/receiving requests/events. - /// Internal for testing purposes. - /// - internal IProtocolEndpoint? ServiceHost - { - get; - set; - } - - /// - /// Initializes the Security Service instance - /// - public void InitializeService(ServiceHost serviceHost) - { - this.ServiceHost = serviceHost; - - // Credential request handlers - this.ServiceHost.SetRequestHandler(CreateCredentialRequest.Type, HandleCreateCredentialRequest, true); - this.ServiceHost.SetRequestHandler(UpdateCredentialRequest.Type, HandleUpdateCredentialRequest, true); - this.ServiceHost.SetRequestHandler(GetCredentialsRequest.Type, HandleGetCredentialsRequest, true); - - // Login request handlers - this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, this.loginServiceHandler.HandleCreateLoginRequest, true); - this.ServiceHost.SetRequestHandler(UpdateLoginRequest.Type, this.loginServiceHandler.HandleUpdateLoginRequest, true); - this.ServiceHost.SetRequestHandler(InitializeLoginViewRequest.Type, this.loginServiceHandler.HandleInitializeLoginViewRequest, true); - this.ServiceHost.SetRequestHandler(ScriptLoginRequest.Type, this.loginServiceHandler.HandleScriptLoginRequest, true); - this.ServiceHost.SetRequestHandler(DisposeLoginViewRequest.Type, this.loginServiceHandler.HandleDisposeLoginViewRequest, true); - - // User request handlers - this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, this.userServiceHandler.HandleInitializeUserViewRequest, true); - this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, this.userServiceHandler.HandleCreateUserRequest, true); - this.ServiceHost.SetRequestHandler(UpdateUserRequest.Type, this.userServiceHandler.HandleUpdateUserRequest, true); - this.ServiceHost.SetRequestHandler(ScriptUserRequest.Type, this.userServiceHandler.HandleScriptUserRequest, true); - this.ServiceHost.SetRequestHandler(DisposeUserViewRequest.Type, this.userServiceHandler.HandleDisposeUserViewRequest, true); - } - - #region "Credential Handlers" - - /// - /// Handle request to create a credential - /// - internal async Task HandleCreateCredentialRequest(CreateCredentialParams parameters, RequestContext requestContext) - { - var result = await ConfigureCredential(parameters.OwnerUri, - parameters.Credential, - ConfigAction.Create, - RunType.RunNow); - - await requestContext.SendResult(new CredentialResult() - { - Credential = parameters.Credential, - Success = result.Item1, - ErrorMessage = result.Item2 - }); - } - - /// - /// Handle request to update a credential - /// - internal async Task HandleUpdateCredentialRequest(UpdateCredentialParams parameters, RequestContext requestContext) - { - var result = await ConfigureCredential(parameters.OwnerUri, - parameters.Credential, - ConfigAction.Update, - RunType.RunNow); - - await requestContext.SendResult(new CredentialResult() - { - Credential = parameters.Credential, - Success = result.Item1, - ErrorMessage = result.Item2 - }); - } - - /// - /// Handle request to get all credentials - /// - internal async Task HandleGetCredentialsRequest(GetCredentialsParams parameters, RequestContext requestContext) - { - var result = new GetCredentialsResult(); - try - { - ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); - CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); - - var credentials = dataContainer.Server?.Credentials; - int credentialsCount = credentials != null ? credentials.Count : 0; - CredentialInfo[] credentialsInfos = new CredentialInfo[credentialsCount]; - if (credentials != null) - { - for (int i = 0; i < credentialsCount; ++i) - { - credentialsInfos[i] = new CredentialInfo(); - credentialsInfos[i].Name = credentials[i].Name; - credentialsInfos[i].Identity = credentials[i].Identity; - credentialsInfos[i].Id = credentials[i].ID; - credentialsInfos[i].DateLastModified = credentials[i].DateLastModified; - credentialsInfos[i].CreateDate = credentials[i].CreateDate; - credentialsInfos[i].ProviderName = credentials[i].ProviderName; - } - } - result.Credentials = credentialsInfos; - result.Success = true; - } - catch (Exception ex) - { - result.Success = false; - result.ErrorMessage = ex.ToString(); - } - - await requestContext.SendResult(result); - } - - /// - /// Disposes the service - /// - public void Dispose() - { - if (!disposed) - { - disposed = true; - } - } - - internal Task> ConfigureCredential( - string ownerUri, - CredentialInfo credential, - ConfigAction configAction, - RunType runType) - { - return Task>.Run(() => - { - try - { - ConnectionInfo connInfo; - ConnectionServiceInstance.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(true, string.Empty); - } - catch (Exception ex) - { - return new Tuple(false, ex.ToString()); - } - }); - } - - #endregion // "Credential Handlers" - } -} diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs deleted file mode 100644 index 10a85137..00000000 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs +++ /dev/null @@ -1,582 +0,0 @@ -// -// 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.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.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Connection; -using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlTools.ServiceLayer.Management; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; - -namespace Microsoft.SqlTools.ServiceLayer.Security -{ - internal class UserServiceHandlerImpl - { - private class ViewState - { - public bool IsNewObject { get; set; } - - public string Database { get; set; } - - public UserPrototypeData OriginalUserData { get; set; } - - public ViewState(bool isNewObject, string database, UserPrototypeData originalUserData) - { - this.IsNewObject = isNewObject; - this.Database = database; - this.OriginalUserData = originalUserData; - } - } - - private ConnectionService? connectionService; - - private Dictionary contextIdToViewState = new Dictionary(); - - /// - /// Internal for testing purposes only - /// - internal ConnectionService ConnectionServiceInstance - { - get - { - connectionService ??= ConnectionService.Instance; - return connectionService; - } - - set - { - connectionService = value; - } - } - - /// - /// Handle request to initialize user view - /// - internal async Task HandleInitializeUserViewRequest(InitializeUserViewParams parameters, RequestContext requestContext) - { - // check input parameters - if (string.IsNullOrWhiteSpace(parameters.Database)) - { - throw new ArgumentNullException("parameters.Database"); - } - - if (string.IsNullOrWhiteSpace(parameters.ContextId)) - { - throw new ArgumentNullException("parameters.ContextId"); - } - - // open a connection for running the user dialog and associated task - ConnectionInfo originalConnInfo; - ConnectionServiceInstance.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.ConnectionServiceInstance.Connect(connectParams); - } - finally - { - originalConnInfo.ConnectionDetails.DatabaseName = originalDatabaseName; - } - ConnectionInfo connInfo; - this.ConnectionServiceInstance.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.Databases[parentDb.Name].Users[parameters.Name]; - 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 = parameters.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 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 databaseRoles = new List(); - foreach (string role in currentUserPrototype.DatabaseRoleNames) - { - if (currentUserPrototype.IsRoleMember(role)) - { - databaseRoles.Add(role); - } - } - - // populate user's schema ownerships - List schemaNames = new List(); - 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() - }; - - this.contextIdToViewState.Add( - parameters.ContextId, - new ViewState(parameters.IsNewObject, parameters.Database, currentUserPrototype.CurrentState)); - - await requestContext.SendResult(userViewInfo); - } - - /// - /// Handle request to create a user - /// - internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext requestContext) - { - if (parameters.ContextId == null) - { - throw new ArgumentException("Invalid context ID"); - } - - ViewState viewState; - this.contextIdToViewState.TryGetValue(parameters.ContextId, out viewState); - - if (viewState == null) - { - throw new ArgumentException("Invalid context ID view state"); - } - - ConfigureUser( - parameters.ContextId, - parameters.User, - ConfigAction.Create, - RunType.RunNow, - viewState.Database, - viewState.OriginalUserData); - - await requestContext.SendResult(new CreateUserResult() - { - User = parameters.User, - Success = true, - ErrorMessage = string.Empty - }); - } - - /// - /// Handle request to update a user - /// - internal async Task HandleUpdateUserRequest(UpdateUserParams parameters, RequestContext requestContext) - { - if (parameters.ContextId == null) - { - throw new ArgumentException("Invalid context ID"); - } - - ViewState viewState; - this.contextIdToViewState.TryGetValue(parameters.ContextId, out viewState); - - if (viewState == null) - { - throw new ArgumentException("Invalid context ID view state"); - } - - ConfigureUser( - parameters.ContextId, - parameters.User, - ConfigAction.Update, - RunType.RunNow, - viewState.Database, - viewState.OriginalUserData); - - await requestContext.SendResult(new ResultStatus() - { - Success = true, - ErrorMessage = string.Empty - }); - } - - /// - /// Handle request to script a user - /// - internal async Task HandleScriptUserRequest(ScriptUserParams parameters, RequestContext requestContext) - { - if (parameters.ContextId == null) - { - throw new ArgumentException("Invalid context ID"); - } - - ViewState viewState; - this.contextIdToViewState.TryGetValue(parameters.ContextId, out viewState); - - if (viewState == null) - { - throw new ArgumentException("Invalid context ID view state"); - } - - // todo: check if it's an existing user - - string sqlScript = ConfigureUser( - parameters.ContextId, - parameters.User, - viewState.IsNewObject ? ConfigAction.Create : ConfigAction.Update, - RunType.ScriptToWindow, - viewState.Database, - viewState.OriginalUserData); - - await requestContext.SendResult(sqlScript); - } - - internal async Task HandleDisposeUserViewRequest(DisposeUserViewRequestParams parameters, RequestContext requestContext) - { - this.ConnectionServiceInstance.Disconnect(new DisconnectParams() - { - OwnerUri = parameters.ContextId, - Type = null - }); - - if (parameters.ContextId != null) - { - this.contextIdToViewState.Remove(parameters.ContextId); - } - - await requestContext.SendResult(new ResultStatus() - { - Success = true, - ErrorMessage = string.Empty - }); - } - - 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.ConnectionServiceInstance.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; - } - } - - internal class UserActions : ManagementActionBase - { - #region Variables - private UserPrototype userPrototype; - private ConfigAction configAction; - #endregion - - #region Constructors / Dispose - /// - /// Handle user create and update actions - /// - 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); - } - - // /// - // /// Clean up any resources being used. - // /// - // protected override void Dispose(bool disposing) - // { - // base.Dispose(disposing); - // } - - #endregion - - /// - /// called by the management actions framework to execute the action - /// - /// - 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; - } - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentProxyTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentProxyTests.cs index e4853614..6915e994 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentProxyTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentProxyTests.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Agent; using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; -using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Moq; @@ -50,17 +50,17 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent { // setup var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var credential = await SecurityTestUtils.SetupCredential(connectionResult); + var credential = await ObjectManagementTestUtils.SetupCredential(connectionResult.ConnectionInfo.OwnerUri); var service = new AgentService(); var proxy = AgentTestUtils.GetTestProxyInfo(); - await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); + await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); // test await AgentTestUtils.CreateAgentProxy(service, connectionResult, proxy); // cleanup await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); - await SecurityTestUtils.CleanupCredential(connectionResult, credential); + await ObjectManagementTestUtils.CleanupCredential(connectionResult.ConnectionInfo.OwnerUri, credential); } } @@ -74,20 +74,20 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent { // setup var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var credential = await SecurityTestUtils.SetupCredential(connectionResult); + var credential = await ObjectManagementTestUtils.SetupCredential(connectionResult.ConnectionInfo.OwnerUri); var service = new AgentService(); var proxy = AgentTestUtils.GetTestProxyInfo(); - await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); + await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); await AgentTestUtils.CreateAgentProxy(service, connectionResult, proxy); // test string originalProxyName = proxy.AccountName; proxy.AccountName = proxy.AccountName + " Updated"; - await AgentTestUtils.UpdateAgentProxy(service, connectionResult, originalProxyName, proxy); + await AgentTestUtils.UpdateAgentProxy(service, connectionResult, originalProxyName, proxy); // cleanup await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); - await SecurityTestUtils.CleanupCredential(connectionResult, credential); + await ObjectManagementTestUtils.CleanupCredential(connectionResult.ConnectionInfo.OwnerUri, credential); } } @@ -101,13 +101,13 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent { // setup var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var credential = await SecurityTestUtils.SetupCredential(connectionResult); + var credential = await ObjectManagementTestUtils.SetupCredential(connectionResult.ConnectionInfo.OwnerUri); var service = new AgentService(); var proxy = AgentTestUtils.GetTestProxyInfo(); // test await AgentTestUtils.DeleteAgentProxy(service, connectionResult, proxy); - await SecurityTestUtils.CleanupCredential(connectionResult, credential); + await ObjectManagementTestUtils.CleanupCredential(connectionResult.ConnectionInfo.OwnerUri, credential); } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs index 3e1319e0..6aaca059 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Agent; using Microsoft.SqlTools.ServiceLayer.Agent.Contracts; -using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.Utility; using Moq; @@ -81,7 +81,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent return new AgentProxyInfo() { AccountName = "Test Proxy", - CredentialName = SecurityTestUtils.TestCredentialName, + CredentialName = ObjectManagementTestUtils.TestCredentialName, Description = "Test proxy description", IsEnabled = true }; diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/CredentialTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/CredentialTests.cs new file mode 100644 index 00000000..611f9788 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/CredentialTests.cs @@ -0,0 +1,46 @@ +// +// 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.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using NUnit.Framework; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement +{ + /// + /// Tests for the Credential management component + /// + public class CredentialTests + { + /// + /// TestHandleCreateCredentialRequest + /// + [Test] + public async Task TestCredentialOperations() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + // setup, drop credential if exists. + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var credential = ObjectManagementTestUtils.GetTestCredentialInfo(); + var objUrn = ObjectManagementTestUtils.GetCredentialURN(credential.Name); + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn); + + // create and update + var parametersForCreation = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", true, SqlObjectType.Credential, "", ""); + await ObjectManagementTestUtils.SaveObject(parametersForCreation, credential); + + var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", false, SqlObjectType.Credential, "", objUrn); + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, credential); + + // cleanup + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn); + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/LoginTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/LoginTests.cs new file mode 100644 index 00000000..4a55dfac --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/LoginTests.cs @@ -0,0 +1,46 @@ +// +// 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.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement; +using Microsoft.SqlTools.ServiceLayer.Test.Common; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement +{ + /// + /// Tests for the Login management component + /// + public class LoginTests + { + /// + /// Test the basic Create Login method handler + /// + // [Test] + public async Task TestHandleCreateLoginRequest() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + // setup, drop credential if exists. + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var testLogin = ObjectManagementTestUtils.GetTestLoginInfo(); + var objUrn = ObjectManagementTestUtils.GetLoginURN(testLogin.Name); + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn); + + // create and update + var parametersForCreation = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", true, SqlObjectType.ServerLevelLogin, "", ""); + await ObjectManagementTestUtils.SaveObject(parametersForCreation, testLogin); + + var parametersForUpdate = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionResult.ConnectionInfo.OwnerUri, "master", false, SqlObjectType.ServerLevelLogin, "", objUrn); + await ObjectManagementTestUtils.SaveObject(parametersForUpdate, testLogin); + + // cleanup + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, objUrn); + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementServiceTests.cs index 17648703..24d45d2a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementServiceTests.cs @@ -11,7 +11,6 @@ using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; -using Microsoft.SqlTools.ServiceLayer.ObjectManagement; using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; @@ -27,22 +26,15 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { private const string TableQuery = @"CREATE TABLE testTable1_RenamingTable (c1 int)"; private const string OwnerUri = "testDB"; - private ObjectManagementService objectManagementService; private SqlTestDb testDb; - private Mock> requestContextMock; + private Mock> requestContextMock; [SetUp] public async Task TestInitialize() { this.testDb = await SqlTestDb.CreateNewAsync(serverType: TestServerType.OnPrem, query: TableQuery, dbNamePrefix: "RenameTest"); - - requestContextMock = new Mock>(); - ConnectionService connectionService = LiveConnectionHelper.GetLiveTestConnectionService(); - + requestContextMock = new Mock>(); TestConnectionResult connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(testDb.DatabaseName, OwnerUri, ConnectionType.Default); - - ObjectManagementService.ConnectionServiceInstance = connectionService; - this.objectManagementService = new ObjectManagementService(); } [TearDown] @@ -55,10 +47,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement public async Task TestRenameTable() { //arrange & act - await objectManagementService.HandleRenameRequest(this.InitRequestParams("RenamingTable", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']", testDb.DatabaseName)), requestContextMock.Object); + await ObjectManagementTestUtils.Service.HandleRenameRequest(this.InitRequestParams("RenamingTable", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']", testDb.DatabaseName)), requestContextMock.Object); //assert - requestContextMock.Verify(x => x.SendResult(It.Is(r => r == true))); + requestContextMock.Verify(x => x.SendResult(It.IsAny())); Query queryRenameObject = ExecuteQuery("SELECT * FROM " + testDb.DatabaseName + ".sys.tables WHERE name='RenamingTable'"); Assert.That(queryRenameObject.HasExecuted, Is.True, "The query to check for the renamed table was not executed"); @@ -75,10 +67,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement public async Task TestRenameColumn() { //arrange & act - await objectManagementService.HandleRenameRequest(this.InitRequestParams("RenameColumn", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']/Column[@Name='C1']", testDb.DatabaseName)), requestContextMock.Object); + await ObjectManagementTestUtils.Service.HandleRenameRequest(this.InitRequestParams("RenameColumn", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']/Column[@Name='C1']", testDb.DatabaseName)), requestContextMock.Object); //assert - requestContextMock.Verify(x => x.SendResult(It.Is(r => r == true))); + requestContextMock.Verify(x => x.SendResult(It.IsAny())); Query queryRenameObject = ExecuteQuery("SELECT * FROM " + testDb.DatabaseName + ".sys.columns WHERE name='RenameColumn'"); Assert.That(queryRenameObject.HasExecuted, Is.True, "The query to check for the renamed column was not executed"); @@ -96,7 +88,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { Assert.That(async () => { - await objectManagementService.HandleRenameRequest(this.InitRequestParams("RenameColumn", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']/Column[@Name='C1_NOT']", testDb.DatabaseName)), requestContextMock.Object); + await ObjectManagementTestUtils.Service.HandleRenameRequest(this.InitRequestParams("RenameColumn", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_RenamingTable' and @Schema='dbo']/Column[@Name='C1_NOT']", testDb.DatabaseName)), requestContextMock.Object); }, Throws.Exception.TypeOf(), "Did find the column, which should not have existed"); } @@ -105,7 +97,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement { Assert.That(async () => { - await objectManagementService.HandleRenameRequest(this.InitRequestParams("RenamingTable", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_Not' and @Schema='dbo']", testDb.DatabaseName)), requestContextMock.Object); + await ObjectManagementTestUtils.Service.HandleRenameRequest(this.InitRequestParams("RenamingTable", String.Format("Server/Database[@Name='{0}']/Table[@Name='testTable1_Not' and @Schema='dbo']", testDb.DatabaseName)), requestContextMock.Object); }, Throws.Exception.TypeOf(), "Did find the table, which should not have existed"); } @@ -120,7 +112,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement }; Assert.That(async () => { - await objectManagementService.HandleRenameRequest(testRenameRequestParams, requestContextMock.Object); + await ObjectManagementTestUtils.Service.HandleRenameRequest(testRenameRequestParams, requestContextMock.Object); }, Throws.Exception.TypeOf(), "Did find the connection, which should not have existed"); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs new file mode 100644 index 00000000..0b04c79c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/ObjectManagementTestUtils.cs @@ -0,0 +1,206 @@ +// +// 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.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; +using Moq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement +{ + public static class ObjectManagementTestUtils + { + private static ObjectManagementService _objectManagementService; + + static ObjectManagementTestUtils() + { + ObjectManagementService.ConnectionServiceInstance = LiveConnectionHelper.GetLiveTestConnectionService(); + _objectManagementService = new ObjectManagementService(); + } + + internal static ObjectManagementService Service + { + get + { + return _objectManagementService; + } + } + + public static string TestCredentialName = "Current User"; + + internal static string GetCurrentUserIdentity() + { + return string.Format(@"{0}\{1}", Environment.UserDomainName, Environment.UserName); + } + + internal static string GetLoginURN(string name) + { + return string.Format("Server/Login[@Name='{0}']", name); + } + + internal static string GetUserURN(string database, string name) + { + return string.Format("Server/Database[@Name='{0}']/User[@Name='{1}']", database, name); + } + + internal static string GetCredentialURN(string name) + { + return string.Format("Server/Credential[@Name = '{0}']", name); + } + + internal static LoginInfo GetTestLoginInfo() + { + return new LoginInfo() + { + Name = "TestLoginName_" + new Random().NextInt64(10000000, 90000000).ToString(), + AuthenticationType = LoginAuthenticationType.Sql, + WindowsGrantAccess = true, + MustChangePassword = false, + IsEnabled = false, + IsLockedOut = false, + EnforcePasswordPolicy = false, + EnforcePasswordExpiration = false, + Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", + OldPassword = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", + DefaultLanguage = "English - us_english", + DefaultDatabase = "master" + }; + } + + internal static UserInfo GetTestUserInfo(DatabaseUserType userType, string userName = null, string loginName = null) + { + return new UserInfo() + { + Type = userType, + AuthenticationType = ServerAuthenticationType.Sql, + Name = userName ?? "TestUserName_" + new Random().NextInt64(10000000, 90000000).ToString(), + LoginName = loginName, + Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", + DefaultSchema = "dbo", + OwnedSchemas = new string[] { "" } + }; + } + + internal static CredentialInfo GetTestCredentialInfo() + { + return new CredentialInfo() + { + Identity = GetCurrentUserIdentity(), + Name = TestCredentialName + }; + } + + internal static InitializeViewRequestParams GetInitializeViewRequestParams(string connectionUri, string database, bool isNewObject, SqlObjectType objectType, string parentUrn, string objectUrn) + { + return new InitializeViewRequestParams() + { + ConnectionUri = connectionUri, + Database = database, + IsNewObject = isNewObject, + ObjectType = objectType, + ContextId = Guid.NewGuid().ToString(), + ParentUrn = parentUrn, + ObjectUrn = objectUrn + }; + } + + internal static async Task SaveObject(InitializeViewRequestParams parameters, SqlObject obj) + { + // Initialize the view + var initViewRequestContext = new Mock>(); + initViewRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(null)); + await Service.HandleInitializeViewRequest(parameters, initViewRequestContext.Object); + + // Save the object + var saveObjectRequestContext = new Mock>(); + saveObjectRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(new SaveObjectRequestResponse())); + await Service.HandleSaveObjectRequest(new SaveObjectRequestParams { ContextId = parameters.ContextId, Object = JToken.FromObject(obj) }, saveObjectRequestContext.Object); + + // Dispose the view + var disposeViewRequestContext = new Mock>(); + disposeViewRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(new DisposeViewRequestResponse())); + await Service.HandleDisposeViewRequest(new DisposeViewRequestParams { ContextId = parameters.ContextId }, disposeViewRequestContext.Object); + } + + internal static async Task ScriptObject(InitializeViewRequestParams parameters, SqlObject obj) + { + // Initialize the view + var initViewRequestContext = new Mock>(); + initViewRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(null)); + await Service.HandleInitializeViewRequest(parameters, initViewRequestContext.Object); + + // Script the object + var scriptObjectRequestContext = new Mock>(); + scriptObjectRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult("")); + await Service.HandleScriptObjectRequest(new ScriptObjectRequestParams { ContextId = parameters.ContextId, Object = JToken.FromObject(obj) }, scriptObjectRequestContext.Object); + + // Dispose the view + var disposeViewRequestContext = new Mock>(); + disposeViewRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(new DisposeViewRequestResponse())); + await Service.HandleDisposeViewRequest(new DisposeViewRequestParams { ContextId = parameters.ContextId }, disposeViewRequestContext.Object); + } + + internal static async Task DropObject(string connectionUri, string objectUrn) + { + var dropParams = new DropRequestParams + { + ConnectionUri = connectionUri, + ObjectUrn = objectUrn + }; + + var dropRequestContext = new Mock>(); + dropRequestContext.Setup(x => x.SendResult(It.IsAny())) + .Returns(Task.FromResult(new DropRequestResponse())); + + await Service.HandleDropRequest(dropParams, dropRequestContext.Object); + } + + internal static async Task CreateTestLogin(string connectionUri) + { + var testLogin = GetTestLoginInfo(); + var parametersForCreation = GetInitializeViewRequestParams(connectionUri, "master", true, SqlObjectType.ServerLevelLogin, "", ""); + await SaveObject(parametersForCreation, testLogin); + return testLogin; + } + + internal static async Task CreateTestUser(string connectionUri, DatabaseUserType userType, + string userName = null, + string loginName = null, + string databaseName = "master", + bool scriptUser = false) + { + var testUser = GetTestUserInfo(userType, userName, loginName); + var parametersForCreation = GetInitializeViewRequestParams(connectionUri, databaseName, true, SqlObjectType.User, "", ""); + await SaveObject(parametersForCreation, testUser); + return testUser; + } + + internal static async Task SetupCredential(string connectionUri) + { + var credential = GetTestCredentialInfo(); + var parametersForCreation = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionUri, "master", true, SqlObjectType.Credential, "", ""); + await DropObject(connectionUri, GetCredentialURN(credential.Name)); + await ObjectManagementTestUtils.SaveObject(parametersForCreation, credential); + return credential; + } + + internal static async Task CleanupCredential(string connectionUri, CredentialInfo credential) + { + await DropObject(connectionUri, GetCredentialURN(credential.Name)); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/UserTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/UserTests.cs new file mode 100644 index 00000000..85251b2c --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/ObjectManagement/UserTests.cs @@ -0,0 +1,97 @@ +// +// 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.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.ObjectManagement; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using NUnit.Framework; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement +{ + /// + /// Tests for the User management component + /// + public class UserTests + { + /// + /// Test the basic Create User method handler + /// + [Test] + public async Task TestHandleSaveUserWithLoginRequest() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var connectionUri = connectionResult.ConnectionInfo.OwnerUri; + var login = await ObjectManagementTestUtils.CreateTestLogin(connectionUri); + var user = await ObjectManagementTestUtils.CreateTestUser(connectionUri, DatabaseUserType.WithLogin, null, login.Name); + var userUrn = ObjectManagementTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name); + var parameters = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionUri, "master", false, SqlObjectType.User, "", userUrn); + await ObjectManagementTestUtils.SaveObject(parameters, user); + + await ObjectManagementTestUtils.DropObject(connectionUri, userUrn); + await ObjectManagementTestUtils.DropObject(connectionUri, ObjectManagementTestUtils.GetLoginURN(login.Name)); + } + } + + /// + /// Test the basic Create User method handler + /// + // [Test] - Windows-only + public async Task TestHandleCreateUserWithWindowsGroup() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var connectionUri = connectionResult.ConnectionInfo.OwnerUri; + var user = await ObjectManagementTestUtils.CreateTestUser(connectionUri, DatabaseUserType.WithWindowsGroupLogin, $"{Environment.MachineName}\\Administrator"); + await ObjectManagementTestUtils.DropObject(connectionUri, ObjectManagementTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); + } + } + + /// + /// Test the basic Create User method handler + /// + // [Test] - needs contained DB + public async Task TestHandleCreateUserWithContainedSqlPassword() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + string databaseName = "CRM"; + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(databaseName, queryTempFile.FilePath); + var connectionUri = connectionResult.ConnectionInfo.OwnerUri; + var user = await ObjectManagementTestUtils.CreateTestUser(connectionUri, DatabaseUserType.Contained, + userName: null, + loginName: null, + databaseName: connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName); + + await ObjectManagementTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, ObjectManagementTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); + } + } + + /// + /// Test the basic Create User method handler + /// + [Test] + public async Task TestScriptUserWithLogin() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); + var connectionUri = connectionResult.ConnectionInfo.OwnerUri; + var login = await ObjectManagementTestUtils.CreateTestLogin(connectionUri); + var user = await ObjectManagementTestUtils.CreateTestUser(connectionUri, DatabaseUserType.WithLogin, null, login.Name); + var userUrn = ObjectManagementTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name); + var parameters = ObjectManagementTestUtils.GetInitializeViewRequestParams(connectionUri, "master", false, SqlObjectType.User, "", userUrn); + await ObjectManagementTestUtils.ScriptObject(parameters, user); + + await ObjectManagementTestUtils.DropObject(connectionUri, userUrn); + await ObjectManagementTestUtils.DropObject(connectionUri, ObjectManagementTestUtils.GetLoginURN(login.Name)); + } + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/CredentialTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/CredentialTests.cs deleted file mode 100644 index 87e1ac2d..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/CredentialTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using System.Threading.Tasks; -using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; -using Microsoft.SqlTools.ServiceLayer.Security; -using Microsoft.SqlTools.ServiceLayer.Test.Common; -using NUnit.Framework; - -namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security -{ - /// - /// Tests for the Credential management component - /// - public class CredentialTests - { - /// - /// TestHandleCreateCredentialRequest - /// - [Test] - public async Task TestHandleCreateCredentialRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var service = new SecurityService(); - var credential = SecurityTestUtils.GetTestCredentialInfo(); - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - - // test - await SecurityTestUtils.CreateCredential(service, connectionResult, credential); - - // cleanup - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - } - } - - /// - /// TestHandleUpdateCredentialRequest - /// - [Test] - public async Task TestHandleUpdateCredentialRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var service = new SecurityService(); - var credential = SecurityTestUtils.GetTestCredentialInfo(); - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - await SecurityTestUtils.CreateCredential(service, connectionResult, credential); - - // test - await SecurityTestUtils.UpdateCredential(service, connectionResult, credential); - - // cleanup - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - } - } - - /// - /// TestHandleDeleteCredentialRequest - /// - [Test] - public async Task TestHandleDeleteCredentialRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var service = new SecurityService(); - var credential = SecurityTestUtils.GetTestCredentialInfo(); - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - await SecurityTestUtils.CreateCredential(service, connectionResult, credential); - - // test - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - } - } - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs deleted file mode 100644 index a9e5d2d7..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using System.Threading.Tasks; -using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; -using Microsoft.SqlTools.ServiceLayer.Security; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -using Microsoft.SqlTools.ServiceLayer.Test.Common; -// using Microsoft.SqlTools.ServiceLayer.Utility; -using Moq; - -namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security -{ - /// - /// Tests for the Login management component - /// - public class LoginTests - { - /// - /// Test the basic Create Login method handler - /// - // [Test] - public async Task TestHandleCreateLoginRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - var contextId = System.Guid.NewGuid().ToString(); - - var initializeLoginViewRequestParams = new InitializeLoginViewRequestParams - { - ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, - ContextId = contextId, - IsNewObject = true - }; - - var loginParams = new CreateLoginParams - { - ContextId = contextId, - Login = SecurityTestUtils.GetTestLoginInfo() - }; - - var createLoginContext = new Mock>(); - createLoginContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - var initializeLoginViewContext = new Mock>(); - initializeLoginViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new LoginViewInfo())); - - // call the create login method - LoginServiceHandlerImpl service = new LoginServiceHandlerImpl(); - await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object); - await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(loginParams.Login.Name)); - } - } - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs deleted file mode 100644 index 8066599a..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs +++ /dev/null @@ -1,300 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -// - -#nullable disable - -using System; -using System.Threading.Tasks; -using Microsoft.SqlTools.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.ObjectManagement; -using Microsoft.SqlTools.ServiceLayer.ObjectManagement.Contracts; -using Microsoft.SqlTools.ServiceLayer.Security; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; -using Moq; -using static Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility.LiveConnectionHelper; - -namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security -{ - public static class SecurityTestUtils - { - public static string TestCredentialName = "Current User"; - - internal static string GetCurrentUserIdentity() - { - return string.Format(@"{0}\{1}", Environment.UserDomainName, Environment.UserName); - } - - internal static string GetLoginURN(string name) - { - return string.Format("Server/Login[@Name='{0}']", name); - } - - internal static string GetUserURN(string database, string name) - { - return string.Format("Server/Database[@Name='{0}']/User[@Name='{1}']", database, name); - } - - internal static string GetCredentialURN(string name) - { - return string.Format("Server/Credential[@Name = '{0}']", name); - } - - internal static LoginInfo GetTestLoginInfo() - { - return new LoginInfo() - { - Name = "TestLoginName_" + new Random().NextInt64(10000000, 90000000).ToString(), - AuthenticationType = LoginAuthenticationType.Sql, - WindowsGrantAccess = true, - MustChangePassword = false, - IsEnabled = false, - IsLockedOut = false, - EnforcePasswordPolicy = false, - EnforcePasswordExpiration = false, - Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", - OldPassword = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", - DefaultLanguage = "English - us_english", - DefaultDatabase = "master" - }; - } - - internal static UserInfo GetTestUserInfo(DatabaseUserType userType, string userName = null, string loginName = null) - { - return new UserInfo() - { - Type = userType, - AuthenticationType = ServerAuthenticationType.Sql, - Name = userName ?? "TestUserName_" + new Random().NextInt64(10000000, 90000000).ToString(), - LoginName = loginName, - Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", - DefaultSchema = "dbo", - OwnedSchemas = new string[] { "" } - }; - } - - internal static CredentialInfo GetTestCredentialInfo() - { - return new CredentialInfo() - { - Identity = GetCurrentUserIdentity(), - Name = TestCredentialName - }; - } - - internal static async Task CreateCredential( - SecurityService service, - TestConnectionResult connectionResult, - CredentialInfo credential) - { - var context = new Mock>(); - await service.HandleCreateCredentialRequest(new CreateCredentialParams - { - OwnerUri = connectionResult.ConnectionInfo.OwnerUri, - Credential = credential - }, context.Object); - context.VerifyAll(); - } - - internal static async Task UpdateCredential( - SecurityService service, - TestConnectionResult connectionResult, - CredentialInfo credential) - { - var context = new Mock>(); - await service.HandleUpdateCredentialRequest(new UpdateCredentialParams - { - OwnerUri = connectionResult.ConnectionInfo.OwnerUri, - Credential = credential - }, context.Object); - context.VerifyAll(); - } - - public static async Task SetupCredential(TestConnectionResult connectionResult) - { - var service = new SecurityService(); - var credential = SecurityTestUtils.GetTestCredentialInfo(); - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - await SecurityTestUtils.CreateCredential(service, connectionResult, credential); - return credential; - } - - public static async Task CleanupCredential( - TestConnectionResult connectionResult, - CredentialInfo credential) - { - var service = new SecurityService(); - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetCredentialURN(credential.Name)); - } - - internal static async Task CreateLogin(LoginServiceHandlerImpl service, TestConnectionResult connectionResult) - { - string contextId = System.Guid.NewGuid().ToString(); - var initializeLoginViewRequestParams = new InitializeLoginViewRequestParams - { - ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, - ContextId = contextId, - IsNewObject = true - }; - - var loginParams = new CreateLoginParams - { - ContextId = contextId, - Login = SecurityTestUtils.GetTestLoginInfo() - }; - - var createLoginContext = new Mock>(); - createLoginContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - var initializeLoginViewContext = new Mock>(); - initializeLoginViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new LoginViewInfo())); - - // call the create login method - await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object); - await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object); - - return loginParams.Login; - } - - internal static async Task CreateUser( - UserServiceHandlerImpl service, - TestConnectionResult connectionResult, - DatabaseUserType userType, - string userName = null, - string loginName = null, - string databaseName = "master", - bool scriptUser = false) - { - string contextId = System.Guid.NewGuid().ToString(); - var initializeViewRequestParams = new InitializeUserViewParams - { - ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, - ContextId = contextId, - IsNewObject = true, - Database = databaseName - }; - - var initializeUserViewContext = new Mock>(); - initializeUserViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new UserViewInfo())); - - await service.HandleInitializeUserViewRequest(initializeViewRequestParams, initializeUserViewContext.Object); - - if (scriptUser) - { - var scriptParams = new ScriptUserParams - { - ContextId = contextId, - User = SecurityTestUtils.GetTestUserInfo(userType, userName, loginName) - }; - - var scriptUserContext = new Mock>(); - scriptUserContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - - await service.HandleScriptUserRequest(scriptParams, scriptUserContext.Object); - - // verify the result - scriptUserContext.Verify(x => x.SendResult(It.Is - (p => p.Contains("CREATE USER")))); - } - - var userParams = new CreateUserParams - { - ContextId = contextId, - User = SecurityTestUtils.GetTestUserInfo(userType, userName, loginName) - }; - - var createUserContext = new Mock>(); - createUserContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - - // call the create login method - await service.HandleCreateUserRequest(userParams, createUserContext.Object); - - // verify the result - createUserContext.Verify(x => x.SendResult(It.Is - (p => p.Success && p.User.Name != string.Empty))); - - var disposeViewRequestParams = new DisposeUserViewRequestParams - { - ContextId = contextId - }; - - var disposeUserViewContext = new Mock>(); - disposeUserViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - - await service.HandleDisposeUserViewRequest(disposeViewRequestParams, disposeUserViewContext.Object); - - return userParams.User; - } - - internal static async Task UpdateUser( - UserServiceHandlerImpl service, - TestConnectionResult connectionResult, - UserInfo user) - { - string contextId = System.Guid.NewGuid().ToString(); - var initializeViewRequestParams = new InitializeUserViewParams - { - ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, - ContextId = contextId, - IsNewObject = false, - Database = "master", - Name = user.Name - }; - - var initializeUserViewContext = new Mock>(); - initializeUserViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new UserViewInfo())); - - await service.HandleInitializeUserViewRequest(initializeViewRequestParams, initializeUserViewContext.Object); - - // update the user - user.DatabaseRoles = new string[] { "db_datareader" }; - var updateParams = new UpdateUserParams - { - ContextId = contextId, - User = user - }; - var updateUserContext = new Mock>(); - // call the create login method - await service.HandleUpdateUserRequest(updateParams, updateUserContext.Object); - // verify the result - updateUserContext.Verify(x => x.SendResult(It.Is(p => p.Success))); - - var disposeViewRequestParams = new DisposeUserViewRequestParams - { - ContextId = contextId - }; - - var disposeUserViewContext = new Mock>(); - disposeUserViewContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(new object())); - - await service.HandleDisposeUserViewRequest(disposeViewRequestParams, disposeUserViewContext.Object); - - return updateParams.User; - } - - internal static async Task DropObject(string connectionUri, string objectUrn) - { - ObjectManagementService objectManagementService = new ObjectManagementService(); - var dropParams = new DropRequestParams - { - ConnectionUri = connectionUri, - ObjectUrn = objectUrn - }; - - var dropRequestContext = new Mock>(); - dropRequestContext.Setup(x => x.SendResult(It.IsAny())) - .Returns(Task.FromResult(true)); - - await objectManagementService.HandleDropRequest(dropParams, dropRequestContext.Object); - } - } -} diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs deleted file mode 100644 index ed2c92ab..00000000 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -// -// 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.IntegrationTests.Utility; -using Microsoft.SqlTools.ServiceLayer.Security; -using Microsoft.SqlTools.ServiceLayer.Security.Contracts; -using Microsoft.SqlTools.ServiceLayer.Test.Common; -using NUnit.Framework; - -namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security -{ - /// - /// Tests for the User management component - /// - public class UserTests - { - /// - /// Test the basic Create User method handler - /// - [Test] - public async Task TestHandleCreateUserWithLoginRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - UserServiceHandlerImpl userService = new UserServiceHandlerImpl(); - LoginServiceHandlerImpl loginService = new LoginServiceHandlerImpl(); - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - - var login = await SecurityTestUtils.CreateLogin(loginService, connectionResult); - - var user = await SecurityTestUtils.CreateUser(userService, connectionResult, DatabaseUserType.WithLogin, null, login.Name); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(login.Name)); - } - } - - /// - /// Test the basic Create User method handler - /// - // [Test] - Windows-only - public async Task TestHandleCreateUserWithWindowsGroup() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - UserServiceHandlerImpl userService = new UserServiceHandlerImpl(); - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - - var user = await SecurityTestUtils.CreateUser( - userService, - connectionResult, - DatabaseUserType.WithWindowsGroupLogin, - $"{Environment.MachineName}\\Administrator"); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); - } - } - - /// - /// Test the basic Create User method handler - /// - // [Test] - needs contained DB - public async Task TestHandleCreateUserWithContainedSqlPassword() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - UserServiceHandlerImpl userService = new UserServiceHandlerImpl(); - string databaseName = "CRM"; - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync(databaseName, queryTempFile.FilePath); - - var user = await SecurityTestUtils.CreateUser( - userService, - connectionResult, - DatabaseUserType.Contained, - userName: null, - loginName: null, - databaseName: connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); - } - } - - /// - /// Test the basic Update User method handler - /// - [Test] - public async Task TestHandleUpdateUserWithLoginRequest() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - UserServiceHandlerImpl userService = new UserServiceHandlerImpl(); - LoginServiceHandlerImpl loginService = new LoginServiceHandlerImpl(); - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - - var login = await SecurityTestUtils.CreateLogin(loginService, connectionResult); - - var user = await SecurityTestUtils.CreateUser(userService, connectionResult, DatabaseUserType.WithLogin, null, login.Name); - - await SecurityTestUtils.UpdateUser(userService, connectionResult, user); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(login.Name)); - } - } - - /// - /// Test the basic Create User method handler - /// - [Test] - public async Task TestScriptUserWithLogin() - { - using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) - { - // setup - UserServiceHandlerImpl userService = new UserServiceHandlerImpl(); - LoginServiceHandlerImpl loginService = new LoginServiceHandlerImpl(); - var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); - - var login = await SecurityTestUtils.CreateLogin(loginService, connectionResult); - - var user = await SecurityTestUtils.CreateUser(userService, connectionResult, - DatabaseUserType.WithLogin, null, login.Name, scriptUser: true); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name)); - - await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(login.Name)); - } - } - } -}