Support creating & editing additional user types (#1962)

* WIP

* Fix contained user password handling

* More user related bug fixes
This commit is contained in:
Karl Burtram
2023-03-23 18:01:55 -07:00
committed by GitHub
parent 5fce03f6cf
commit c3444e5cf5
7 changed files with 224 additions and 122 deletions

View File

@@ -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 <default> 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
/// <summary>
/// required when loading from Object Explorer context
/// </summary>
/// <param name="context"></param>
/// Handle user create and update actions
/// </summary>
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);
}
// /// <summary>
@@ -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)