mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-23 01:25:42 -05:00
Support creating & editing additional user types (#1962)
* WIP * Fix contained user password handling * More user related bug fixes
This commit is contained in:
@@ -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<LanguageDisplay> 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<LanguageDisplay> res = new List<LanguageDisplay>();
|
||||
foreach (LanguageDisplay ld in sortedLanguages.Values)
|
||||
{
|
||||
res.Add(ld);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region "Credential Handlers"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user