From c3444e5cf56f658c84353ebe2a76582a552f022e Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Thu, 23 Mar 2023 18:01:55 -0700 Subject: [PATCH] Support creating & editing additional user types (#1962) * WIP * Fix contained user password handling * More user related bug fixes --- .../Security/SecurityService.cs | 31 +--- .../Security/UserActions.cs | 152 +++++++++++------- .../Security/UserData.cs | 17 +- .../Utility/DatabaseUtils.cs | 40 +++++ .../Utility/LanguageUtils.cs | 68 +++++--- .../Security/SecurityTestUtils.cs | 12 +- .../Security/UserTests.cs | 26 +++ 7 files changed, 224 insertions(+), 122 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs index c7109d83..60e2fe01 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs @@ -4,7 +4,6 @@ // using System; -using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; @@ -245,8 +244,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security databases[i] = dataContainer.Server.Databases[i].Name; } - var languageOptions = GetDefaultLanguageOptions(dataContainer); - var languageOptionsList = languageOptions.Select(FormatLanguageDisplay).ToList(); + var languageOptions = LanguageUtils.GetDefaultLanguageOptions(dataContainer); + var languageOptionsList = languageOptions.Select(SecurityService.FormatLanguageDisplay).ToList(); if (parameters.IsNewObject) { languageOptionsList.Insert(0, SR.DefaultLanguagePlaceholder); @@ -320,36 +319,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security await requestContext.SendResult(new object()); } - private string FormatLanguageDisplay(LanguageDisplay? l) + internal static string FormatLanguageDisplay(LanguageDisplay? l) { if (l == null) return null; return string.Format("{0} - {1}", l.Language.Alias, l.Language.Name); } - private IList GetDefaultLanguageOptions(CDataContainer dataContainer) - { - // sort the languages alphabetically by alias - SortedList sortedLanguages = new SortedList(Comparer.Default); - - LanguageUtils.SetLanguageDefaultInitFieldsForDefaultLanguages(dataContainer.Server); - if (dataContainer.Server != null && dataContainer.Server.Languages != null) - { - foreach (Language language in dataContainer.Server.Languages) - { - LanguageDisplay listValue = new LanguageDisplay(language); - sortedLanguages.Add(language.Alias, listValue); - } - } - - IList res = new List(); - foreach (LanguageDisplay ld in sortedLanguages.Values) - { - res.Add(ld); - } - - return res; - } - #endregion #region "Credential Handlers" diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs index cf949631..46e0202c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/UserActions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using System.Xml; using Microsoft.SqlServer.Management.Common; @@ -101,42 +102,47 @@ namespace Microsoft.SqlTools.ServiceLayer.Security 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; - + Database? parentDb = dataContainer.Server.GetSmoObject(databaseUrn) as Database; + + var languageOptions = LanguageUtils.GetDefaultLanguageOptions(dataContainer); + var languageOptionsList = languageOptions.Select(SecurityService.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]; + User existingUser = dataContainer.Server.Databases[parentDb.Name].Users[parameters.Name]; userType = UserActions.GetCurrentUserTypeForExistingUser(existingUser); + DatabaseUserType databaseUserType = UserActions.GetDatabaseUserTypeForUserType(userType); userInfo = new UserInfo() { + Type = databaseUserType, Name = parameters.Name, LoginName = existingUser.Login, - DefaultSchema = existingUser.DefaultSchema + DefaultSchema = existingUser.DefaultSchema, }; + + // update the authentication type for contained users + if (databaseUserType == DatabaseUserType.Contained) + { + userInfo.AuthenticationType = ServerAuthenticationType.Sql; + } + + // Default language is only applicable for users inside a contained database. + if (parentDb.ContainmentType != ContainmentType.None + && LanguageUtils.IsDefaultLanguageSupported(dataContainer.Server)) + { + 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 language if available - IUserPrototypeWithDefaultLanguage defaultLanguagePrototype = currentUserPrototype as IUserPrototypeWithDefaultLanguage; - string? defaultLanguageAlias = null; - if (defaultLanguagePrototype != null && defaultLanguagePrototype.IsDefaultLanguageSupported) - { - string dbUrn = "Server/Database[@Name='" + Urn.EscapeString(parameters.Database) + "']"; - defaultLanguageAlias = defaultLanguagePrototype.DefaultLanguageAlias; - //If engine returns default language as empty or null, that means the default language of - //database will be used. - //Default language is not applicable for users inside an uncontained authentication. - if (string.IsNullOrEmpty(defaultLanguageAlias) - && (dataContainer.Server.GetSmoObject(dbUrn) as Database).ContainmentType != ContainmentType.None) - { - defaultLanguageAlias = SR.DefaultLanguagePlaceholder; - } - } + UserPrototype currentUserPrototype = UserPrototypeFactory.GetUserPrototype(dataContainer, userInfo, originalData: null, userType); // get the default schema if available string? defaultSchema = null; @@ -145,13 +151,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Security { defaultSchema = defaultSchemaPrototype.DefaultSchema; } - - // IUserPrototypeWithPassword userWithPwdPrototype = currentUserPrototype as IUserPrototypeWithPassword; - // if (userWithPwdPrototype != null && !this.DataContainer.IsNewObject) - // { - // this.passwordTextBox.Text = FAKE_PASSWORD; - // this.confirmPwdTextBox.Text = FAKE_PASSWORD; - // } + + // set default alias to if needed + if (string.IsNullOrEmpty(defaultLanguageAlias) + && parentDb.ContainmentType != ContainmentType.None + && 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; @@ -180,26 +197,28 @@ namespace Microsoft.SqlTools.ServiceLayer.Security schemaNames.Add(schema); } } - + ServerConnection serverConnection = dataContainer.ServerConnection; UserViewInfo userViewInfo = new UserViewInfo() { ObjectInfo = new UserInfo() { - Type = DatabaseUserType.WithLogin, + Type = userInfo?.Type ?? DatabaseUserType.WithLogin, + AuthenticationType = userInfo?.AuthenticationType ?? ServerAuthenticationType.Sql, Name = currentUserPrototype.Name, LoginName = loginName, - Password = string.Empty, + Password = password, DefaultSchema = defaultSchema, OwnedSchemas = schemaNames.ToArray(), DatabaseRoles = databaseRoles.ToArray(), - DefaultLanguage = defaultLanguageAlias + DefaultLanguage = SecurityService.FormatLanguageDisplay( + languageOptions.FirstOrDefault(o => o?.Language.Name == defaultLanguageAlias || o?.Language.Alias == defaultLanguageAlias, null)), }, SupportContainedUser = UserActions.IsParentDatabaseContained(parentDb), // support for these will be added later SupportWindowsAuthentication = false, SupportAADAuthentication = false, SupportSQLAuthentication = true, - Languages = new string[] { }, + Languages = languageOptionsList.ToArray(), Schemas = currentUserPrototype.SchemaNames.ToArray(), Logins = DatabaseUtils.LoadSqlLogins(serverConnection), DatabaseRoles = currentUserPrototype.DatabaseRoleNames.ToArray() @@ -363,28 +382,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Security internal class UserActions : ManagementActionBase { #region Variables - //private UserPrototypeData userData; private UserPrototype userPrototype; - private UserInfo? user; private ConfigAction configAction; #endregion #region Constructors / Dispose /// - /// required when loading from Object Explorer context - /// - /// + /// Handle user create and update actions + /// public UserActions( - CDataContainer context, + CDataContainer dataContainer, ConfigAction configAction, UserInfo? user, UserPrototypeData? originalData) { - this.DataContainer = context; - this.user = user; + this.DataContainer = dataContainer; this.configAction = configAction; - this.userPrototype = InitUserPrototype(context, user, originalData); + 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); } // /// @@ -409,24 +435,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security } } - private UserPrototype InitUserPrototype(CDataContainer dataContainer, UserInfo user, UserPrototypeData? originalData) - { - ExhaustiveUserTypes currentUserType; - if (dataContainer.IsNewObject) - { - currentUserType = GetUserTypeForUserInfo(user); - } - else - { - currentUserType = UserActions.GetCurrentUserTypeForExistingUser( - dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User); - } - - UserPrototype currentUserPrototype = UserPrototypeFactory.GetUserPrototype(dataContainer, user, originalData, currentUserType); - return currentUserPrototype; - } - - private ExhaustiveUserTypes GetUserTypeForUserInfo(UserInfo user) + internal static ExhaustiveUserTypes GetUserTypeForUserInfo(UserInfo user) { ExhaustiveUserTypes userType = ExhaustiveUserTypes.LoginMappedUser; switch (user.Type) @@ -447,6 +456,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Security 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; + } + return databaseUserType; + } + internal static ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User? user) { if (user == null) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs index 65fd50ea..1e475b54 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs @@ -7,12 +7,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Security; +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; -using Microsoft.SqlServer.Management.Common; namespace Microsoft.SqlTools.ServiceLayer.Security { @@ -122,9 +122,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security this.mappedLoginName = userInfo.LoginName; this.defaultSchemaName = userInfo.DefaultSchema; if (!string.IsNullOrEmpty(userInfo.Password)) - { + { this.password = DatabaseUtils.GetReadOnlySecureString(userInfo.Password); } + if (!string.IsNullOrEmpty(userInfo.DefaultLanguage) + && string.Compare(userInfo.DefaultLanguage, SR.DefaultLanguagePlaceholder, StringComparison.Ordinal) != 0) + { + this.defaultLanguageAlias = LanguageUtils.GetLanguageAliasFromDisplayText(userInfo.DefaultLanguage); + } } this.LoadRoleMembership(context, userInfo); @@ -460,7 +465,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security public void SetRoleMembership(string roleName, bool isMember) { this.currentState.isMember[roleName] = isMember; - } + } #endregion @@ -800,9 +805,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security { get { - //Default Language was not supported before Denali. - return SqlMgmtUtils.IsSql11OrLater(this.context.Server.ConnectionContext.ServerVersion) - && this.context.Server.ServerType != DatabaseEngineType.SqlAzureDatabase; + return LanguageUtils.IsDefaultLanguageSupported(this.context.Server); } } @@ -970,7 +973,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security { user.Alter(); - if (this.currentState.password != this.originalState.password) + if (!DatabaseUtils.IsSecureStringsEqual(this.currentState.password, this.originalState.password)) { if (this.currentState.isOldPasswordRequired) { diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs index ca86e6bd..88bec13e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/DatabaseUtils.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Data; using System.IO; +using System.Runtime.InteropServices; using System.Security; namespace Microsoft.SqlTools.ServiceLayer.Utility @@ -86,6 +87,45 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return ss; } + public static bool IsSecureStringsEqual(SecureString ss1, SecureString ss2) + { + IntPtr bstr1 = IntPtr.Zero; + IntPtr bstr2 = IntPtr.Zero; + try + { + bstr1 = Marshal.SecureStringToBSTR(ss1); + bstr2 = Marshal.SecureStringToBSTR(ss2); + int length1 = Marshal.ReadInt32(bstr1, -4); + int length2 = Marshal.ReadInt32(bstr2, -4); + if (length1 != length2) + { + return false; + } + + for (int x = 0; x < length1; ++x) + { + byte b1 = Marshal.ReadByte(bstr1, x); + byte b2 = Marshal.ReadByte(bstr2, x); + if (b1 != b2) + { + return false; + } + } + return true; + } + finally + { + if (bstr2 != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(bstr2); + } + if (bstr1 != IntPtr.Zero) + { + Marshal.ZeroFreeBSTR(bstr1); + } + } + } + /// /// this is the main method that is called by DropAllObjects for every object /// in the grid diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs index 44f970cf..ec9c6033 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/LanguageUtils.cs @@ -4,13 +4,16 @@ // using System; +using System.Collections; +using System.Collections.Generic; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlTools.ServiceLayer.Management; namespace Microsoft.SqlTools.ServiceLayer.Utility { /// - /// Summary description for CUtils. + /// Utility functions for working with server languages /// internal class LanguageUtils { @@ -20,8 +23,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// /// Returns string.Empty in case it doesn't find a matching languageName on the server - public static string GetLanguageAliasFromName(Server connectedServer, - string languageName) + public static string GetLanguageAliasFromName(Server connectedServer, string languageName) { string languageAlias = string.Empty; @@ -45,8 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// /// Returns string.Empty in case it doesn't find a matching languageAlias on the server - public static string GetLanguageNameFromAlias(Server connectedServer, - string languageAlias) + public static string GetLanguageNameFromAlias(Server connectedServer, string languageAlias) { string languageName = string.Empty; @@ -70,8 +71,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// /// Throws exception in case it doesn't find a matching languageId on the server - public static int GetLcidFromLangId(Server connectedServer, - int langId) + public static int GetLcidFromLangId(Server connectedServer, int langId) { int lcid = -1; //Unacceptable Lcid. @@ -100,8 +100,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// /// Throws exception in case it doesn't find a matching lcid on the server - public static int GetLangIdFromLcid(Server connectedServer, - int lcid) + public static int GetLangIdFromLcid(Server connectedServer, int lcid) { int langId = -1; //Unacceptable LangId. @@ -129,8 +128,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// /// /// - public static LanguageChoice GetLanguageChoiceAlias(Server connectedServer, - int lcid) + public static LanguageChoice GetLanguageChoiceAlias(Server connectedServer, int lcid) { SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer); @@ -155,17 +153,46 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" }; connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded); } + + public static IList GetDefaultLanguageOptions(CDataContainer dataContainer) + { + // sort the languages alphabetically by alias + SortedList sortedLanguages = new SortedList(Comparer.Default); + + LanguageUtils.SetLanguageDefaultInitFieldsForDefaultLanguages(dataContainer.Server); + if (dataContainer.Server != null && dataContainer.Server.Languages != null) + { + foreach (Language language in dataContainer.Server.Languages) + { + LanguageDisplay listValue = new LanguageDisplay(language); + sortedLanguages.Add(language.Alias, listValue); + } + } + + IList res = new List(); + foreach (LanguageDisplay ld in sortedLanguages.Values) + { + res.Add(ld); + } + + return res; + } + + public static string GetLanguageAliasFromDisplayText(string? displayText) + { + string[] parts = displayText?.Split(" - "); + return (parts != null && parts.Length > 1) ? parts[0] : displayText; + } + + public static bool IsDefaultLanguageSupported(Server server) + { + //Default Language was not supported before Denali. + return SqlMgmtUtils.IsSql11OrLater(server.ConnectionContext.ServerVersion) + && server.ServerType != DatabaseEngineType.SqlAzureDatabase; + } + } - #region interface - ILanguageLcidWithConnectionInfo - used by property editors to talk with data object - interface ILanguageLcidWithConnectionInfo - { - int Lcid { get; } - ServerConnection Connection { get; } - } - #endregion - - #region class - LanguageChoice internal class LanguageChoice { public string alias; @@ -181,5 +208,4 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility return alias; } } - #endregion } diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs index 23bd36f7..c322ea10 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs @@ -54,8 +54,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security IsLockedOut = false, EnforcePasswordPolicy = false, EnforcePasswordExpiration = false, - Password = "placeholder", - OldPassword = "placeholder", + Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", + OldPassword = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", DefaultLanguage = "English - us_english", DefaultDatabase = "master" }; @@ -66,9 +66,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security return new UserInfo() { Type = userType, + AuthenticationType = ServerAuthenticationType.Sql, Name = userName ?? "TestUserName_" + new Random().NextInt64(10000000, 90000000).ToString(), LoginName = loginName, - Password = "placeholder", + Password = "placeholder" + new Random().NextInt64(10000000, 90000000).ToString() + "!*PLACEHOLDER", DefaultSchema = "dbo", OwnedSchemas = new string[] { "" } }; @@ -163,7 +164,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security TestConnectionResult connectionResult, DatabaseUserType userType, string userName = null, - string loginName = null) + string loginName = null, + string databaseName = "master") { string contextId = System.Guid.NewGuid().ToString(); var initializeViewRequestParams = new InitializeUserViewParams @@ -171,7 +173,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security ConnectionUri = connectionResult.ConnectionInfo.OwnerUri, ContextId = contextId, IsNewObject = true, - Database = "master" + Database = databaseName }; var initializeUserViewContext = new Mock>(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs index e3997b23..780bc69e 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/UserTests.cs @@ -64,6 +64,32 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security } } + /// + /// Test the basic Create User method handler + /// + // [Test] - needs contained DB + public async Task TestHandleCreateUserWithContainedSqlPassword() + { + using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile()) + { + // setup + SecurityService service = new SecurityService(); + 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 ///