Support scripting for Users (#2002)

* WIP 1

* WIP2

* Fix merge break

* Support alter existing object
This commit is contained in:
Karl Burtram
2023-04-13 11:52:37 -07:00
committed by GitHub
parent a37093a773
commit 948ae3903e
5 changed files with 119 additions and 11 deletions

View File

@@ -109,4 +109,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
RequestType<DisposeUserViewRequestParams, ResultStatus> Type =
RequestType<DisposeUserViewRequestParams, ResultStatus>.Create("objectManagement/disposeUserView");
}
/// <summary>
/// Script User params
/// </summary>
public class ScriptUserParams
{
public string? ContextId { get; set; }
public UserInfo? User { get; set; }
}
/// <summary>
/// Script User request type
/// </summary>
public class ScriptUserRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<ScriptUserParams, string> Type =
RequestType<ScriptUserParams, string>.Create("objectManagement/scriptUser");
}
}

View File

@@ -95,6 +95,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, this.userServiceHandler.HandleInitializeUserViewRequest, true);
this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, this.userServiceHandler.HandleCreateUserRequest, true);
this.ServiceHost.SetRequestHandler(UpdateUserRequest.Type, this.userServiceHandler.HandleUpdateUserRequest, true);
this.ServiceHost.SetRequestHandler(ScriptUserRequest.Type, this.userServiceHandler.HandleScriptUserRequest, true);
this.ServiceHost.SetRequestHandler(DisposeUserViewRequest.Type, this.userServiceHandler.HandleDisposeUserViewRequest, true);
}

View File

@@ -24,12 +24,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
private class UserViewState
{
public bool IsNewObject { get; set; }
public string Database { get; set; }
public UserPrototypeData OriginalUserData { get; set; }
public UserViewState(string database, UserPrototypeData originalUserData)
public UserViewState(bool isNewObject, string database, UserPrototypeData originalUserData)
{
this.IsNewObject = isNewObject;
this.Database = database;
this.OriginalUserData = originalUserData;
}
@@ -229,7 +232,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.contextIdToViewState.Add(
parameters.ContextId,
new UserViewState(parameters.Database, currentUserPrototype.CurrentState));
new UserViewState(parameters.IsNewObject, parameters.Database, currentUserPrototype.CurrentState));
await requestContext.SendResult(userViewInfo);
}
@@ -252,7 +255,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
throw new ArgumentException("Invalid context ID view state");
}
Tuple<bool, string> result = ConfigureUser(
ConfigureUser(
parameters.ContextId,
parameters.User,
ConfigAction.Create,
@@ -263,8 +266,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
await requestContext.SendResult(new CreateUserResult()
{
User = parameters.User,
Success = result.Item1,
ErrorMessage = result.Item2
Success = true,
ErrorMessage = string.Empty
});
}
@@ -286,7 +289,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
throw new ArgumentException("Invalid context ID view state");
}
Tuple<bool, string> result = ConfigureUser(
ConfigureUser(
parameters.ContextId,
parameters.User,
ConfigAction.Update,
@@ -296,11 +299,42 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
await requestContext.SendResult(new ResultStatus()
{
Success = result.Item1,
ErrorMessage = result.Item2
Success = true,
ErrorMessage = string.Empty
});
}
/// <summary>
/// Handle request to update a user
/// </summary>
internal async Task HandleScriptUserRequest(ScriptUserParams parameters, RequestContext<string> requestContext)
{
if (parameters.ContextId == null)
{
throw new ArgumentException("Invalid context ID");
}
UserViewState viewState;
this.contextIdToViewState.TryGetValue(parameters.ContextId, out viewState);
if (viewState == null)
{
throw new ArgumentException("Invalid context ID view state");
}
// todo: check if it's an existing user
string sqlScript = ConfigureUser(
parameters.ContextId,
parameters.User,
viewState.IsNewObject ? ConfigAction.Create : ConfigAction.Update,
RunType.ScriptToWindow,
viewState.Database,
viewState.OriginalUserData);
await requestContext.SendResult(sqlScript);
}
internal async Task HandleDisposeUserViewRequest(DisposeUserViewRequestParams parameters, RequestContext<ResultStatus> requestContext)
{
this.ConnectionServiceInstance.Disconnect(new DisconnectParams()
@@ -352,7 +386,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
return CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc);
}
internal Tuple<bool, string> ConfigureUser(
internal string ConfigureUser(
string? ownerUri,
UserInfo? user,
ConfigAction configAction,
@@ -367,6 +401,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
throw new ArgumentException("Invalid connection URI '{0}'", ownerUri);
}
string sqlScript = string.Empty;
CDataContainer dataContainer = CreateUserDataContainer(connInfo, user, configAction, databaseName);
using (var actions = new UserActions(dataContainer, configAction, user, originalData))
{
@@ -376,9 +411,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
throw executionHandler.ExecutionFailureException;
}
if (runType == RunType.ScriptToWindow)
{
sqlScript = executionHandler.ScriptTextFromLastRun;
}
}
return new Tuple<bool, string>(true, string.Empty);
return sqlScript;
}
}

View File

@@ -165,7 +165,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
DatabaseUserType userType,
string userName = null,
string loginName = null,
string databaseName = "master")
string databaseName = "master",
bool scriptUser = false)
{
string contextId = System.Guid.NewGuid().ToString();
var initializeViewRequestParams = new InitializeUserViewParams
@@ -181,6 +182,25 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
.Returns(Task.FromResult(new UserViewInfo()));
await service.HandleInitializeUserViewRequest(initializeViewRequestParams, initializeUserViewContext.Object);
if (scriptUser)
{
var scriptParams = new ScriptUserParams
{
ContextId = contextId,
User = SecurityTestUtils.GetTestUserInfo(userType, userName, loginName)
};
var scriptUserContext = new Mock<RequestContext<string>>();
scriptUserContext.Setup(x => x.SendResult(It.IsAny<string>()))
.Returns(Task.FromResult(new object()));
await service.HandleScriptUserRequest(scriptParams, scriptUserContext.Object);
// verify the result
scriptUserContext.Verify(x => x.SendResult(It.Is<string>
(p => p.Contains("CREATE USER"))));
}
var userParams = new CreateUserParams
{

View File

@@ -112,5 +112,29 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(login.Name));
}
}
/// <summary>
/// Test the basic Create User method handler
/// </summary>
[Test]
public async Task TestScriptUserWithLogin()
{
using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
{
// setup
UserServiceHandlerImpl userService = new UserServiceHandlerImpl();
LoginServiceHandlerImpl loginService = new LoginServiceHandlerImpl();
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
var login = await SecurityTestUtils.CreateLogin(loginService, connectionResult);
var user = await SecurityTestUtils.CreateUser(userService, connectionResult,
DatabaseUserType.WithLogin, null, login.Name, scriptUser: true);
await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetUserURN(connectionResult.ConnectionInfo.ConnectionDetails.DatabaseName, user.Name));
await SecurityTestUtils.DropObject(connectionResult.ConnectionInfo.OwnerUri, SecurityTestUtils.GetLoginURN(login.Name));
}
}
}
}