Enable support for Windows users (#1957)

* WIP

* Update for Windows user
This commit is contained in:
Karl Burtram
2023-03-21 19:00:11 -07:00
committed by GitHub
parent d46bb2ce53
commit a104251885
4 changed files with 110 additions and 141 deletions

View File

@@ -60,6 +60,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// </summary>
internal async Task HandleInitializeUserViewRequest(InitializeUserViewParams parameters, RequestContext<UserViewInfo> requestContext)
{
// check input parameters
if (string.IsNullOrWhiteSpace(parameters.Database))
{
throw new ArgumentNullException("parameters.Database");
@@ -70,13 +71,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
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
{
@@ -93,26 +94,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
originalConnInfo.ConnectionDetails.DatabaseName = originalDatabaseName;
}
ConnectionInfo connInfo;
this.ConnectionServiceInstance.TryFindConnection(parameters.ContextId, out connInfo);
CDataContainer dataContainer = CreateUserDataContainer(connInfo, null, ConfigAction.Create, parameters.Database);
// 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;
// if viewing an exisitng user then populate some properties
UserInfo? userInfo = null;
ExhaustiveUserTypes userType = ExhaustiveUserTypes.LoginMappedUser;
if (!parameters.IsNewObject)
{
User? existingUser = null;
string databaseUrn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']",
Urn.EscapeString(parameters.Database));
Database? parentDb = dataContainer.Server.GetSmoObject(databaseUrn) as Database;
existingUser = dataContainer.Server.Databases[parentDb.Name].Users[parameters.Name];
if (string.IsNullOrWhiteSpace(existingUser.Login))
{
throw new ApplicationException("Only 'User with Login' user type supported");
}
User? existingUser = dataContainer.Server.Databases[parentDb.Name].Users[parameters.Name];
userType = UserActions.GetCurrentUserTypeForExistingUser(existingUser);
userInfo = new UserInfo()
{
Name = parameters.Name,
@@ -121,9 +118,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
};
}
UserPrototypeFactory userPrototypeFactory = UserPrototypeFactory.GetInstance(dataContainer, userInfo, originalData: null);
UserPrototype currentUserPrototype = userPrototypeFactory.GetUserPrototype(ExhaustiveUserTypes.LoginMappedUser);
// 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)
@@ -140,12 +138,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
// get the default schema if available
string? defaultSchema = null;
IUserPrototypeWithDefaultSchema defaultSchemaPrototype = currentUserPrototype as IUserPrototypeWithDefaultSchema;
if (defaultSchemaPrototype != null && defaultSchemaPrototype.IsDefaultSchemaSupported)
{
defaultSchema = defaultSchemaPrototype.DefaultSchema;
}
// IUserPrototypeWithPassword userWithPwdPrototype = currentUserPrototype as IUserPrototypeWithPassword;
// if (userWithPwdPrototype != null && !this.DataContainer.IsNewObject)
// {
@@ -153,6 +153,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// this.confirmPwdTextBox.Text = FAKE_PASSWORD;
// }
// get the login name if it exists
string? loginName = null;
IUserPrototypeWithMappedLogin mappedLoginPrototype = currentUserPrototype as IUserPrototypeWithMappedLogin;
if (mappedLoginPrototype != null)
@@ -160,6 +161,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
loginName = mappedLoginPrototype.LoginName;
}
// populate user's role assignments
List<string> databaseRoles = new List<string>();
foreach (string role in currentUserPrototype.DatabaseRoleNames)
{
@@ -169,6 +171,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
// populate user's schema ownerships
List<string> schemaNames = new List<string>();
foreach (string schema in currentUserPrototype.SchemaNames)
{
@@ -178,12 +181,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
// default to dbo schema, if there isn't already a default
if (string.IsNullOrWhiteSpace(defaultSchema) && currentUserPrototype.SchemaNames.Contains("dbo"))
{
defaultSchema = "dbo";
}
ServerConnection serverConnection = dataContainer.ServerConnection;
UserViewInfo userViewInfo = new UserViewInfo()
{
@@ -198,7 +195,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
DatabaseRoles = databaseRoles.ToArray(),
DefaultLanguage = defaultLanguageAlias
},
SupportContainedUser = false, // support for these will be added later
SupportContainedUser = UserActions.IsParentDatabaseContained(parentDb), // support for these will be added later
SupportWindowsAuthentication = false,
SupportAADAuthentication = false,
SupportSQLAuthentication = true,
@@ -349,7 +346,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
CDataContainer dataContainer = CreateUserDataContainer(connInfo, user, configAction, databaseName);
using (var actions = new UserActions(dataContainer, user, configAction, originalData))
using (var actions = new UserActions(dataContainer, configAction, user, originalData))
{
var executionHandler = new ExecutonHandler(actions);
executionHandler.RunNow(runType, this);
@@ -379,8 +376,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// <param name="context"></param>
public UserActions(
CDataContainer context,
UserInfo? user,
ConfigAction configAction,
UserInfo? user,
UserPrototypeData? originalData)
{
this.DataContainer = context;
@@ -401,19 +398,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
#endregion
/// <summary>
/// called on background thread by the framework to execute the action
/// called by the management actions framework to execute the action
/// </summary>
/// <param name="node"></param>
public override void OnRunNow(object sender)
{
if (this.configAction == ConfigAction.Drop)
{
// if (this.credentialData.Credential != null)
// {
// this.credentialData.Credential.DropIfExists();
// }
}
else
if (this.configAction != ConfigAction.Drop)
{
this.userPrototype.ApplyChanges();
}
@@ -422,30 +412,42 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private UserPrototype InitUserPrototype(CDataContainer dataContainer, UserInfo user, UserPrototypeData? originalData)
{
ExhaustiveUserTypes currentUserType;
UserPrototypeFactory userPrototypeFactory = UserPrototypeFactory.GetInstance(dataContainer, user, originalData);
if (dataContainer.IsNewObject)
{
if (dataContainer.Server != null && IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer.Server))
{
currentUserType = ExhaustiveUserTypes.SqlUserWithPassword;
}
else
{
currentUserType = ExhaustiveUserTypes.LoginMappedUser;
}
currentUserType = GetUserTypeForUserInfo(user);
}
else
{
currentUserType = this.GetCurrentUserTypeForExistingUser(
currentUserType = UserActions.GetCurrentUserTypeForExistingUser(
dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User);
}
UserPrototype currentUserPrototype = userPrototypeFactory.GetUserPrototype(currentUserType);
return currentUserPrototype;
UserPrototype currentUserPrototype = UserPrototypeFactory.GetUserPrototype(dataContainer, user, originalData, currentUserType);
return currentUserPrototype;
}
private ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User? user)
private 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:
userType = ExhaustiveUserTypes.SqlUserWithPassword;
break;
case DatabaseUserType.NoConnectAccess:
userType = ExhaustiveUserTypes.SqlUserWithoutLogin;
break;
}
return userType;
}
internal static ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User? user)
{
if (user == null)
{
@@ -478,18 +480,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
private static bool IsParentDatabaseContained(Urn parentDbUrn, Server server)
internal static bool IsParentDatabaseContained(Urn parentDbUrn, Server server)
{
string parentDbName = parentDbUrn.GetNameForType("Database");
Database parentDatabase = server.Databases[parentDbName];
return IsParentDatabaseContained(server.Databases[parentDbName]);
}
if (parentDatabase.IsSupportedProperty("ContainmentType")
&& parentDatabase.ContainmentType == ContainmentType.Partial)
{
return true;
}
return false;
internal static bool IsParentDatabaseContained(Database parentDatabase)
{
return parentDatabase.IsSupportedProperty("ContainmentType")
&& parentDatabase.ContainmentType == ContainmentType.Partial;
}
}
}

View File

@@ -763,12 +763,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
base.SaveProperties(user);
if (!this.Exists || (user.Login != this.currentState.mappedLoginName))
bool isValidLoginName = !string.IsNullOrWhiteSpace(this.currentState.mappedLoginName);
bool isCreatingOrUpdatingLogin = !this.Exists || user.Login != this.currentState.mappedLoginName;
if (isValidLoginName && isCreatingOrUpdatingLogin)
{
user.Login = this.currentState.mappedLoginName;
}
}
}
}
internal class UserPrototypeForWindowsUser : UserPrototypeForSqlUserWithLogin,
IUserPrototypeWithDefaultLanguage
@@ -985,107 +987,47 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// <summary>
/// Used to create or return required UserPrototype objects for a user type.
/// This factory class also helps us in maintaining a single set of current data
/// and original data mapped to all UserPrototypes.
///
/// Also this UserPrototypeFactory is a Singleton object for one datacontainer object.
/// Making it Singleton helps us in using same factory object inside other pages too.
/// </summary>
internal class UserPrototypeFactory
internal static class UserPrototypeFactory
{
private static UserPrototypeFactory? singletonInstance;
private UserPrototypeData currentData;
private UserPrototypeData originalData;
private CDataContainer context;
private UserPrototype? asymmetricKeyMappedUser;
private UserPrototype? certificateMappedUser;
private UserPrototype? loginMappedUser;
private UserPrototype? noLoginUser;
private UserPrototype? sqlUserWithPassword;
private UserPrototype? windowsUser;
private UserPrototype? currentPrototype;
public UserPrototype CurrentPrototype
{
get
{
currentPrototype ??= new UserPrototype(this.context,
this.currentData,
this.originalData);
return currentPrototype;
}
}
private UserPrototypeFactory(CDataContainer context, UserInfo user, UserPrototypeData? originalData)
{
this.context = context;
this.currentData = new UserPrototypeData(this.context, user);
this.originalData = originalData ?? this.currentData.Clone();
}
public static UserPrototypeFactory GetInstance(CDataContainer context, UserInfo? user, UserPrototypeData? originalData)
{
if (singletonInstance != null
&& singletonInstance.context != context)
{
singletonInstance = null;
}
singletonInstance ??= new UserPrototypeFactory(context, user, originalData);
return singletonInstance;
}
public UserPrototype GetUserPrototype(ExhaustiveUserTypes userType)
public static UserPrototype GetUserPrototype(
CDataContainer context, UserInfo? user,
UserPrototypeData? originalData, ExhaustiveUserTypes userType)
{
UserPrototype currentPrototype = null;
UserPrototypeData currentData = new UserPrototypeData(context, user);
switch (userType)
{
case ExhaustiveUserTypes.AsymmetricKeyMappedUser:
currentData.userType = UserType.AsymmetricKey;
this.asymmetricKeyMappedUser ??= new UserPrototype(this.context, this.currentData, this.originalData);
this.currentPrototype = asymmetricKeyMappedUser;
currentPrototype ??= new UserPrototype(context, currentData, originalData);
break;
case ExhaustiveUserTypes.CertificateMappedUser:
currentData.userType = UserType.Certificate;
this.certificateMappedUser ??= new UserPrototype(this.context, this.currentData, this.originalData);
this.currentPrototype = certificateMappedUser;
currentPrototype ??= new UserPrototype(context, currentData, originalData);
break;
case ExhaustiveUserTypes.LoginMappedUser:
currentData.userType = UserType.SqlUser;
this.loginMappedUser ??= new UserPrototypeForSqlUserWithLogin(this.context, this.currentData, this.originalData);
this.currentPrototype = loginMappedUser;
currentPrototype ??= new UserPrototypeForSqlUserWithLogin(context, currentData, originalData);
break;
case ExhaustiveUserTypes.SqlUserWithoutLogin:
currentData.userType = UserType.NoLogin;
this.noLoginUser ??= new UserPrototypeWithDefaultSchema(this.context, this.currentData, this.originalData);
this.currentPrototype = noLoginUser;
currentPrototype ??= new UserPrototypeWithDefaultSchema(context, currentData, originalData);
break;
case ExhaustiveUserTypes.SqlUserWithPassword:
currentData.userType = UserType.SqlUser;
this.sqlUserWithPassword ??= new UserPrototypeForSqlUserWithPassword(this.context, this.currentData, this.originalData);
this.currentPrototype = sqlUserWithPassword;
currentPrototype ??= new UserPrototypeForSqlUserWithPassword(context, currentData, originalData);
break;
case ExhaustiveUserTypes.WindowsUser:
currentData.userType = UserType.SqlUser;
this.windowsUser ??= new UserPrototypeForWindowsUser(this.context, this.currentData, this.originalData);
this.currentPrototype = windowsUser;
currentPrototype ??= new UserPrototypeForWindowsUser(context, currentData, originalData);
break;
default:
System.Diagnostics.Debug.Assert(false, "Unknown UserType provided.");
this.currentPrototype = null;
currentPrototype = null;
break;
}
return this.currentPrototype;
return currentPrototype;
}
}

View File

@@ -61,12 +61,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
};
}
internal static UserInfo GetTestUserInfo(string loginName)
internal static UserInfo GetTestUserInfo(DatabaseUserType userType, string userName = null, string loginName = null)
{
return new UserInfo()
{
Type = DatabaseUserType.WithLogin,
Name = "TestUserName_" + new Random().NextInt64(10000000, 90000000).ToString(),
Type = userType,
Name = userName ?? "TestUserName_" + new Random().NextInt64(10000000, 90000000).ToString(),
LoginName = loginName,
Password = "placeholder",
DefaultSchema = "dbo",
@@ -161,7 +161,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
internal static async Task<UserInfo> CreateUser(
UserServiceHandlerImpl service,
TestConnectionResult connectionResult,
LoginInfo login)
DatabaseUserType userType,
string userName = null,
string loginName = null)
{
string contextId = System.Guid.NewGuid().ToString();
var initializeViewRequestParams = new InitializeUserViewParams
@@ -181,7 +183,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
var userParams = new CreateUserParams
{
ContextId = contextId,
User = SecurityTestUtils.GetTestUserInfo(login.Name)
User = SecurityTestUtils.GetTestUserInfo(userType, userName, loginName)
};
var createUserContext = new Mock<RequestContext<CreateUserResult>>();

View File

@@ -3,9 +3,11 @@
// 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;
@@ -31,7 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
var login = await SecurityTestUtils.CreateLogin(service, connectionResult);
var user = await SecurityTestUtils.CreateUser(userService, connectionResult, login);
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));
@@ -39,6 +41,29 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
}
}
/// <summary>
/// Test the basic Create User method handler
/// </summary>
// [Test] - Windows-only
public async Task TestHandleCreateUserWithWindowsGroup()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
{
// setup
SecurityService service = new SecurityService();
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));
}
}
/// <summary>
/// Test the basic Update User method handler
/// </summary>
@@ -54,7 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
var login = await SecurityTestUtils.CreateLogin(service, connectionResult);
var user = await SecurityTestUtils.CreateUser(userService, connectionResult, login);
var user = await SecurityTestUtils.CreateUser(userService, connectionResult, DatabaseUserType.WithLogin, null, login.Name);
await SecurityTestUtils.UpdateUser(userService, connectionResult, user);