diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs index 12549bb1..8a95996e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs @@ -12,14 +12,24 @@ using Newtonsoft.Json.Converters; namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts { [JsonConverter(typeof(StringEnumConverter))] - public enum LoginType + public enum LoginAuthenticationType { [EnumMember(Value = "Windows")] Windows, [EnumMember(Value = "Sql")] Sql, [EnumMember(Value = "AAD")] - AzureActiveDirectory + AAD, + [EnumMember(Value = "Others")] + Others + } + + public class ServerLoginDatabaseUserMapping + { + public string Database { get; set; } + public string User { get; set; } + public string DefaultSchema { get; set; } + public string[] DatabaseRoles { get; set; } } /// @@ -27,27 +37,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// public class LoginInfo { - public string LoginName { get; set; } + public string Name { get; set; } - public LoginType LoginType { get; set; } - - public string CertificateName { get; set; } - - public string AsymmetricKeyName { get; set; } + public LoginAuthenticationType AuthenticationType { get; set; } public bool WindowsGrantAccess { get; set; } - public bool MustChange { get; set; } + public bool MustChangePassword { get; set; } - public bool IsDisabled { get; set; } + public bool IsEnabled { get; set; } + public bool ConnectPermission { get; set; } public bool IsLockedOut { get; set; } - public bool EnforcePolicy { get; set; } + public bool EnforcePasswordPolicy { get; set; } - public bool EnforceExpiration { get; set; } - - public bool WindowsAuthSupported { get; set; } + public bool EnforcePasswordExpiration { get; set; } public string Password { get; set; } @@ -56,5 +61,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts public string DefaultLanguage { get; set; } public string DefaultDatabase { get; set; } + + public string[] ServerRoles {get; set;} + + public ServerLoginDatabaseUserMapping[] UserMapping; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs index 1029f2ec..5f5d168c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs @@ -6,7 +6,6 @@ #nullable disable using Microsoft.SqlTools.Hosting.Protocol.Contracts; -using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts @@ -16,20 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// public class CreateLoginParams : GeneralRequestDetails { - public string OwnerUri { get; set; } + public string ContextId { get; set; } public LoginInfo Login { get; set; } } - /// - /// Create Login result - /// - public class CreateLoginResult : ResultStatus - { - public LoginInfo Login { get; set; } - } - - /// /// Create Login request type /// @@ -39,8 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// Request definition /// public static readonly - RequestType Type = - RequestType.Create("security/createlogin"); + RequestType Type = + RequestType.Create("objectManagement/createLogin"); } /// @@ -48,9 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// public class DeleteLoginParams : GeneralRequestDetails { - public string OwnerUri { get; set; } + public string ConnectionUri { get; set; } - public string LoginName { get; set; } + public string Name { get; set; } } /// @@ -62,7 +52,78 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts /// Request definition /// public static readonly - RequestType Type = - RequestType.Create("security/deletelogin"); + RequestType Type = + RequestType.Create("objectManagement/deleteLogin"); + } + + /// + /// Update Login params + /// + public class UpdateLoginParams : GeneralRequestDetails + { + 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 : GeneralRequestDetails + { + 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 : GeneralRequestDetails + { + 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"); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs new file mode 100644 index 00000000..28333888 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginViewInfo.cs @@ -0,0 +1,22 @@ +// +// 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.Security.Contracts +{ + public class LoginViewInfo + { + + public LoginInfo ObjectInfo { get; set; } + public bool SupportWindowsAuthentication { get; set; } + public bool SupportAADAuthentication { get; set; } + public bool SupportSQLAuthentication { get; set; } + public bool CanEditLockedOutState { get; set; } + public string[] Databases; + public string[] Languages; + public string[] ServerRoles; + public bool SupportAdvancedPasswordOptions; + public bool SupportAdvancedOptions; + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs index 81d177c9..7334ddd5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/LoginData.cs @@ -8,6 +8,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Data; @@ -560,6 +561,50 @@ namespace Microsoft.SqlTools.ServiceLayer.Security private bool initialized; + // query to list all Sql and AAD logins with their role membership, ref: https://learn.microsoft.com/en-us/azure/azure-sql/database/security-server-roles?view=azuresql + // this is a temporary workaround for SMO not supporting server role population for Azure SQL + private static string AZURE_SERVER_ROLE_MEMBERSHIP_QUERY = +@"SELECT + member.principal_id AS MemberPrincipalID + , member.name AS MemberPrincipalName + , roles.principal_id AS RolePrincipalID + , roles.name AS RolePrincipalName +FROM sys.server_role_members AS server_role_members +INNER JOIN sys.server_principals AS roles + ON server_role_members.role_principal_id = roles.principal_id +INNER JOIN sys.server_principals AS member + ON server_role_members.member_principal_id = member.principal_id +LEFT OUTER JOIN sys.sql_logins AS sql_logins + ON server_role_members.member_principal_id = sql_logins.principal_id +WHERE member.principal_id NOT IN (-- prevent SQL Logins from interfering with resultset + SELECT principal_id FROM sys.sql_logins AS sql_logins + WHERE member.principal_id = sql_logins.principal_id) + +UNION + +SELECT + sql_logins.principal_id AS MemberPrincipalID + , sql_logins.name AS MemberPrincipalName + , roles.principal_id AS RolePrincipalID + , roles.name AS RolePrincipalName +FROM sys.server_role_members AS server_role_members +INNER JOIN sys.server_principals AS roles + ON server_role_members.role_principal_id = roles.principal_id +INNER JOIN sys.sql_logins AS sql_logins + ON server_role_members.member_principal_id = sql_logins.principal_id +"; + + private static string[] AZURE_SERVER_ROLES = + { + "##MS_DatabaseConnector##", + "##MS_DatabaseManager##", + "##MS_DefinitionReader##", + "##MS_LoginManager##", + "##MS_SecurityDefinitionReader##", + "##MS_ServerStateReader##", + "##MS_ServerStateManager##" + }; + /// /// Simple description, isMember pair - used as the value in the serverRoles map /// @@ -688,7 +733,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security if (0 != String.Compare(serverRoleName, "public", StringComparison.Ordinal)) { - ((ServerRoleInfo) serverRoles[serverRoleName]).isMember = isMember; + var roleInfo = ((ServerRoleInfo) serverRoles[serverRoleName]); + roleInfo.isMember = isMember; } } @@ -732,6 +778,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security this.initialized = true; serverRoles.Clear(); + if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) + { + PopulateServerRolesForAzure(); + return; + } + try { foreach (ServerRole role in server.Roles) @@ -758,6 +810,52 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } } + private void PopulateServerRolesForAzure() + { + Dictionary> roleToMembership = new Dictionary>(); + DataSet dataset = server.ExecutionManager.ConnectionContext.ExecuteWithResults(AZURE_SERVER_ROLE_MEMBERSHIP_QUERY); + + if (dataset != null) + { + + for (int i = 0; i < dataset.Tables[0].Rows.Count; i++) + { + string login = dataset.Tables[0].Rows[i][1].ToString(); + string role = dataset.Tables[0].Rows[i][3].ToString(); + + if (!roleToMembership.ContainsKey(role)) + { + roleToMembership.Add(role, new HashSet()); + } + HashSet members; + roleToMembership.TryGetValue(role, out members); + + if (members != null) + { + members.Add(login); + } + } + } + + foreach (string role in AZURE_SERVER_ROLES) + { + bool isRoleMember = false; + + if (this.loginExists) + { + HashSet members; + roleToMembership.TryGetValue(role, out members); + + if (members != null && members.Contains(this.loginName)) + { + isRoleMember = true; + } + } + + string roleDescription = String.Empty; // role.Description; + this.serverRoles.Add(role, new ServerRoleInfo(roleDescription, isRoleMember)); + } + } } /// @@ -816,6 +914,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security private Microsoft.SqlServer.Management.Smo.Server server; private static string defaultLanguageDisplay; private bool windowsAuthSupported = true; + private bool aADAuthSupported = false; private StringCollection credentials = null; #endregion @@ -953,7 +1052,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security { get { - if (this.server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance) + if (this.server.ServerType == DatabaseEngineType.SqlAzureDatabase) { this.windowsAuthSupported = false; } @@ -962,6 +1061,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } } + public bool AADAuthSupported + { + get + { + if (this.server.ServerType == DatabaseEngineType.SqlAzureDatabase) + { + this.aADAuthSupported = true; + } + + return this.aADAuthSupported; + } + } + public static string DefaultLanguageDisplay { get @@ -1344,7 +1456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security bool useSqlAuthentication = (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin); - this.windowsGrantAccess = !login.DenyWindowsLogin; + this.windowsGrantAccess = this.server.ServerType != DatabaseEngineType.SqlAzureDatabase ? !login.DenyWindowsLogin : false; this.sqlPassword = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty; this.sqlPasswordConfirm = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty; @@ -1365,7 +1477,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security if (isYukon) { - if (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) + if (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin && this.server.ServerType != DatabaseEngineType.SqlAzureDatabase) { // these properties make sense only for Yukon+ with SQL Authentication this.mustChange = login.MustChangePassword; @@ -1400,7 +1512,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security { this.credentials.Add(login.Credential); } - else if (server.Information.Version.Major >= 10) + else if (server.Information.Version.Major >= 10 && server.ServerType != DatabaseEngineType.SqlAzureDatabase) { this.credentials.Clear(); foreach (string str in login.EnumCredentials()) @@ -1521,6 +1633,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } } + public bool AADAuthSupported + { + get + { + return this.currentState.AADAuthSupported; + } + } + /// /// Whether the Windows account is granted server access (e.g. true == grant access, false == deny access) /// @@ -1963,12 +2083,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Security this.originalState = (LoginPrototypeData) this.currentState.Clone(); this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation); - this.LoginName = login.LoginName; + this.LoginName = login.Name; this.SqlPassword = login.Password; this.OldPassword = login.OldPassword; - this.LoginType = SqlServer.Management.Smo.LoginType.SqlLogin; + this.LoginType = GetLoginType(login); this.DefaultLanguage = login.DefaultLanguage; this.DefaultDatabase = login.DefaultDatabase; + this.EnforcePolicy = login.EnforcePasswordPolicy; + this.EnforceExpiration = login.EnforcePasswordPolicy ? login.EnforcePasswordExpiration : false; + this.IsLockedOut = login.IsLockedOut; + this.IsDisabled = !login.IsEnabled; + this.MustChange = login.EnforcePasswordPolicy ? login.MustChangePassword : false; + this.WindowsGrantAccess = login.ConnectPermission; + } + + private LoginType GetLoginType(LoginInfo loginInfo) + { + switch (loginInfo.AuthenticationType) + { + case LoginAuthenticationType.AAD: + return SqlServer.Management.Smo.LoginType.ExternalUser; + case LoginAuthenticationType.Windows: + return SqlServer.Management.Smo.LoginType.WindowsUser; // TODO handle windows group + default: + return SqlServer.Management.Smo.LoginType.SqlLogin; + } } /// @@ -2030,7 +2169,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security changesMade = true; } - if (9 <= server.Information.Version.Major) + if (9 <= server.Information.Version.Major && server.ServerType != DatabaseEngineType.SqlAzureDatabase) { if (!this.Exists || (this.currentState.CertificateName != this.originalState.CertificateName)) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs index a0cf554f..1b26a266 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs @@ -93,7 +93,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security // Login request handlers this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true); + this.ServiceHost.SetRequestHandler(UpdateLoginRequest.Type, HandleUpdateLoginRequest, true); this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true); + this.ServiceHost.SetRequestHandler(InitializeLoginViewRequest.Type, HandleInitializeLoginViewRequest, true); + this.ServiceHost.SetRequestHandler(DisposeLoginViewRequest.Type, HandleDisposeLoginViewRequest, true); // User request handlers this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, HandleInitializeUserViewRequest, true); @@ -107,14 +110,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Security /// /// Handle request to create a login /// - internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext requestContext) + internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext requestContext) { ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); - // if (connInfo == null) - // { - // // raise an error - // } + string ownerUri; + contextIdToConnectionUriMap.TryGetValue(parameters.ContextId, out ownerUri); + ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); + + if (connInfo == null) + { + // raise error here + } CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); LoginPrototype prototype = new LoginPrototype(dataContainer.Server, parameters.Login); @@ -127,7 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security // return the error associated with null password (coming from policy) - see bug 124377 if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false) { - // raise error here + // raise error here } // check that password and confirm password controls' text matches @@ -139,29 +145,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Security prototype.ApplyGeneralChanges(dataContainer.Server); - await requestContext.SendResult(new CreateLoginResult() + // TODO move this to LoginData + // TODO support role assignment for Azure + LoginPrototype newPrototype = new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Login.Name]); + var _ =newPrototype.ServerRoles.ServerRoleNames; + + foreach (string role in parameters.Login.ServerRoles) { - Login = parameters.Login, - Success = true, - ErrorMessage = string.Empty - }); + newPrototype.ServerRoles.SetMember(role, true); + } + + newPrototype.ApplyServerRoleChanges(dataContainer.Server); + await requestContext.SendResult(new object()); } /// /// Handle request to delete a credential /// - internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext requestContext) + internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext requestContext) { ConnectionInfo connInfo; - ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); + ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo); // if (connInfo == null) // { // // raise an error // } CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); - Login login = dataContainer.Server?.Logins[parameters.LoginName]; - + Login login = dataContainer.Server?.Logins[parameters.Name]; + dataContainer.SqlDialogSubject = login; DoDropObject(dataContainer); @@ -172,6 +184,152 @@ namespace Microsoft.SqlTools.ServiceLayer.Security }); } + internal async Task HandleUpdateLoginRequest(UpdateLoginParams parameters, RequestContext requestContext) + { + ConnectionInfo connInfo; + string ownerUri; + contextIdToConnectionUriMap.TryGetValue(parameters.ContextId, out ownerUri); + ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); + if (connInfo == null) + { + // raise error here + } + + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + LoginPrototype prototype = new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Login.Name]); + + var login = parameters.Login; + prototype.SqlPassword = login.Password; + prototype.DefaultLanguage = login.DefaultLanguage; + prototype.DefaultDatabase = login.DefaultDatabase; + prototype.EnforcePolicy = login.EnforcePasswordPolicy; + prototype.EnforceExpiration = login.EnforcePasswordPolicy ? login.EnforcePasswordExpiration : false; + prototype.IsLockedOut = login.IsLockedOut; + prototype.IsDisabled = !login.IsEnabled; + prototype.MustChange = login.EnforcePasswordPolicy ? login.MustChangePassword : false; + prototype.WindowsGrantAccess = login.ConnectPermission; + + if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) + { + // check that there is a password + // this check is made if policy enforcement is off + // with policy turned on we do not display this message, instead we let server + // return the error associated with null password (coming from policy) - see bug 124377 + if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false) + { + // raise error here + } + + // check that password and confirm password controls' text matches + if (0 != String.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal)) + { + // raise error here + } + } + + var _ = prototype.ServerRoles.ServerRoleNames; + foreach (string role in login.ServerRoles) + { + prototype.ServerRoles.SetMember(role, true); + } + + prototype.ApplyGeneralChanges(dataContainer.Server); + prototype.ApplyServerRoleChanges(dataContainer.Server); + prototype.ApplyDatabaseRoleChanges(dataContainer.Server); + await requestContext.SendResult(new object()); + } + + internal async Task HandleInitializeLoginViewRequest(InitializeLoginViewRequestParams parameters, RequestContext requestContext) + { + contextIdToConnectionUriMap.Add(parameters.ContextId, parameters.ConnectionUri); + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo); + if (connInfo == null) + { + // raise an error + } + CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); + LoginViewInfo loginViewInfo = new LoginViewInfo(); + + string[] databases = new string[dataContainer.Server.Databases.Count]; + for (int i = 0; i < dataContainer.Server.Databases.Count; i++) + { + databases[i] = dataContainer.Server.Databases[i].Name; + } + string[] languages = new string[dataContainer.Server.Languages.Count]; + for (int i = 0; i < dataContainer.Server.Languages.Count; i++) + { + languages[i] = dataContainer.Server.Languages[i].Name; + } + + 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)) + { + loginServerRoles.Add(role); + } + } + + LoginInfo loginInfo = new LoginInfo() + { + Name = prototype.LoginName, + Password = prototype.SqlPassword, + OldPassword = prototype.OldPassword, + AuthenticationType = LoginTypeToAuthenticationType(prototype.LoginType), + EnforcePasswordExpiration = prototype.EnforceExpiration, + EnforcePasswordPolicy = prototype.EnforcePolicy, + MustChangePassword = prototype.MustChange, + DefaultDatabase = prototype.DefaultDatabase, + DefaultLanguage = prototype.DefaultDatabase, + 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 = true, + 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) + { + contextIdToConnectionUriMap.Remove(parameters.ContextId); + await requestContext.SendResult(new object()); + } #endregion #region "User Handlers" diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs index 2670bcaf..1bea88b3 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs @@ -11,7 +11,7 @@ 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 Microsoft.SqlTools.ServiceLayer.Utility; using Moq; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security @@ -31,40 +31,46 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security { // 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 { - OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + ContextId = contextId, Login = SecurityTestUtils.GetTestLoginInfo() }; - var createContext = new Mock>(); - createContext.Setup(x => x.SendResult(It.IsAny())) + 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 SecurityService service = new SecurityService(); - await service.HandleCreateLoginRequest(loginParams, createContext.Object); - - // verify the result - createContext.Verify(x => x.SendResult(It.Is - (p => p.Success && p.Login.LoginName != string.Empty))); + await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object); + await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object); // cleanup created login var deleteParams = new DeleteLoginParams { - OwnerUri = connectionResult.ConnectionInfo.OwnerUri, - LoginName = loginParams.Login.LoginName + ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, + Name = loginParams.Login.Name }; - var deleteContext = new Mock>(); - deleteContext.Setup(x => x.SendResult(It.IsAny())) + var deleteContext = new Mock>(); + deleteContext.Setup(x => x.SendResult(It.IsAny())) .Returns(Task.FromResult(new object())); // call the create login method await service.HandleDeleteLoginRequest(deleteParams, deleteContext.Object); - - // verify the result - deleteContext.Verify(x => x.SendResult(It.Is(p => p.Success))); } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs index 53cbf065..e9fca086 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs @@ -29,17 +29,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security { return new LoginInfo() { - LoginName = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(), - LoginType= LoginType.Sql, - CertificateName = "Test Cert", - AsymmetricKeyName = "Asymmetric Test Cert", + Name = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(), + AuthenticationType= LoginAuthenticationType.Sql, WindowsGrantAccess = true, - MustChange = false, - IsDisabled = false, + MustChangePassword = false, + IsEnabled = false, IsLockedOut = false, - EnforcePolicy = false, - EnforceExpiration = false, - WindowsAuthSupported = false, + EnforcePasswordPolicy = false, + EnforcePasswordExpiration = false, Password = "placeholder", OldPassword = "placeholder", DefaultLanguage = "us_english", diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs index 33888698..817bdf90 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs @@ -28,29 +28,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security { // 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 { - OwnerUri = connectionResult.ConnectionInfo.OwnerUri, + ContextId = contextId, Login = SecurityTestUtils.GetTestLoginInfo() }; - var createLoginContext = new Mock>(); - createLoginContext.Setup(x => x.SendResult(It.IsAny())) + 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 SecurityService service = new SecurityService(); + await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object); await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object); - // verify the result - createLoginContext.Verify(x => x.SendResult(It.Is - (p => p.Success && p.Login.LoginName != string.Empty))); - - var userParams = new CreateUserParams { ContextId = connectionResult.ConnectionInfo.OwnerUri, - User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName) + User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.Name) }; var createUserContext = new Mock>();