Add login management handlers (#1868)

* update contracts

* finish creating/loading login for SQL Server

* support role read for azure and add more handlers

* fix advanced option flags

---------

Co-authored-by: Karl Burtram <karlb@microsoft.com>
This commit is contained in:
Hai Cao
2023-02-17 09:56:03 -08:00
committed by GitHub
parent 86a8861e78
commit 7ffc85d7fc
8 changed files with 489 additions and 89 deletions

View File

@@ -12,14 +12,24 @@ using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{
[JsonConverter(typeof(StringEnumConverter))]
public enum LoginType
public enum LoginAuthenticationType
{
[EnumMember(Value = "Windows")]
Windows,
[EnumMember(Value = "Sql")]
Sql,
[EnumMember(Value = "AAD")]
AzureActiveDirectory
AAD,
[EnumMember(Value = "Others")]
Others
}
public class ServerLoginDatabaseUserMapping
{
public string Database { get; set; }
public string User { get; set; }
public string DefaultSchema { get; set; }
public string[] DatabaseRoles { get; set; }
}
/// <summary>
@@ -27,27 +37,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary>
public class LoginInfo
{
public string LoginName { get; set; }
public string Name { get; set; }
public LoginType LoginType { get; set; }
public string CertificateName { get; set; }
public string AsymmetricKeyName { get; set; }
public LoginAuthenticationType AuthenticationType { get; set; }
public bool WindowsGrantAccess { get; set; }
public bool MustChange { get; set; }
public bool MustChangePassword { get; set; }
public bool IsDisabled { get; set; }
public bool IsEnabled { get; set; }
public bool ConnectPermission { get; set; }
public bool IsLockedOut { get; set; }
public bool EnforcePolicy { get; set; }
public bool EnforcePasswordPolicy { get; set; }
public bool EnforceExpiration { get; set; }
public bool WindowsAuthSupported { get; set; }
public bool EnforcePasswordExpiration { get; set; }
public string Password { get; set; }
@@ -56,5 +61,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
public string DefaultLanguage { get; set; }
public string DefaultDatabase { get; set; }
public string[] ServerRoles {get; set;}
public ServerLoginDatabaseUserMapping[] UserMapping;
}
}

View File

@@ -6,7 +6,6 @@
#nullable disable
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
@@ -16,20 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary>
public class CreateLoginParams : GeneralRequestDetails
{
public string OwnerUri { get; set; }
public string ContextId { get; set; }
public LoginInfo Login { get; set; }
}
/// <summary>
/// Create Login result
/// </summary>
public class CreateLoginResult : ResultStatus
{
public LoginInfo Login { get; set; }
}
/// <summary>
/// Create Login request type
/// </summary>
@@ -39,8 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// Request definition
/// </summary>
public static readonly
RequestType<CreateLoginParams, CreateLoginResult> Type =
RequestType<CreateLoginParams, CreateLoginResult>.Create("security/createlogin");
RequestType<CreateLoginParams, object> Type =
RequestType<CreateLoginParams, object>.Create("objectManagement/createLogin");
}
/// <summary>
@@ -48,9 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary>
public class DeleteLoginParams : GeneralRequestDetails
{
public string OwnerUri { get; set; }
public string ConnectionUri { get; set; }
public string LoginName { get; set; }
public string Name { get; set; }
}
/// <summary>
@@ -62,7 +52,78 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// Request definition
/// </summary>
public static readonly
RequestType<DeleteLoginParams, ResultStatus> Type =
RequestType<DeleteLoginParams, ResultStatus>.Create("security/deletelogin");
RequestType<DeleteLoginParams, object> Type =
RequestType<DeleteLoginParams, object>.Create("objectManagement/deleteLogin");
}
/// <summary>
/// Update Login params
/// </summary>
public class UpdateLoginParams : GeneralRequestDetails
{
public string ContextId { get; set; }
public LoginInfo Login { get; set; }
}
/// <summary>
/// Update Login request type
/// </summary>
public class UpdateLoginRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<UpdateLoginParams, object> Type =
RequestType<UpdateLoginParams, object>.Create("objectManagement/updateLogin");
}
/// <summary>
/// Update Login params
/// </summary>
public class DisposeLoginViewRequestParams : GeneralRequestDetails
{
public string ContextId { get; set; }
}
/// <summary>
/// Update Login request type
/// </summary>
public class DisposeLoginViewRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<DisposeLoginViewRequestParams, object> Type =
RequestType<DisposeLoginViewRequestParams, object>.Create("objectManagement/disposeLoginView");
}
/// <summary>
/// Initialize Login View Request params
/// </summary>
public class InitializeLoginViewRequestParams : GeneralRequestDetails
{
public string ConnectionUri { get; set; }
public string ContextId { get; set; }
public bool IsNewObject { get; set; }
public string Name { get; set; }
}
/// <summary>
/// Initialize Login View request type
/// </summary>
public class InitializeLoginViewRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<InitializeLoginViewRequestParams, LoginViewInfo> Type =
RequestType<InitializeLoginViewRequestParams, LoginViewInfo>.Create("objectManagement/initializeLoginView");
}
}

View File

@@ -0,0 +1,22 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{
public class LoginViewInfo
{
public LoginInfo ObjectInfo { get; set; }
public bool SupportWindowsAuthentication { get; set; }
public bool SupportAADAuthentication { get; set; }
public bool SupportSQLAuthentication { get; set; }
public bool CanEditLockedOutState { get; set; }
public string[] Databases;
public string[] Languages;
public string[] ServerRoles;
public bool SupportAdvancedPasswordOptions;
public bool SupportAdvancedOptions;
}
}

View File

@@ -8,6 +8,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
@@ -560,6 +561,50 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private bool initialized;
// query to list all Sql and AAD logins with their role membership, ref: https://learn.microsoft.com/en-us/azure/azure-sql/database/security-server-roles?view=azuresql
// this is a temporary workaround for SMO not supporting server role population for Azure SQL
private static string AZURE_SERVER_ROLE_MEMBERSHIP_QUERY =
@"SELECT
member.principal_id AS MemberPrincipalID
, member.name AS MemberPrincipalName
, roles.principal_id AS RolePrincipalID
, roles.name AS RolePrincipalName
FROM sys.server_role_members AS server_role_members
INNER JOIN sys.server_principals AS roles
ON server_role_members.role_principal_id = roles.principal_id
INNER JOIN sys.server_principals AS member
ON server_role_members.member_principal_id = member.principal_id
LEFT OUTER JOIN sys.sql_logins AS sql_logins
ON server_role_members.member_principal_id = sql_logins.principal_id
WHERE member.principal_id NOT IN (-- prevent SQL Logins from interfering with resultset
SELECT principal_id FROM sys.sql_logins AS sql_logins
WHERE member.principal_id = sql_logins.principal_id)
UNION
SELECT
sql_logins.principal_id AS MemberPrincipalID
, sql_logins.name AS MemberPrincipalName
, roles.principal_id AS RolePrincipalID
, roles.name AS RolePrincipalName
FROM sys.server_role_members AS server_role_members
INNER JOIN sys.server_principals AS roles
ON server_role_members.role_principal_id = roles.principal_id
INNER JOIN sys.sql_logins AS sql_logins
ON server_role_members.member_principal_id = sql_logins.principal_id
";
private static string[] AZURE_SERVER_ROLES =
{
"##MS_DatabaseConnector##",
"##MS_DatabaseManager##",
"##MS_DefinitionReader##",
"##MS_LoginManager##",
"##MS_SecurityDefinitionReader##",
"##MS_ServerStateReader##",
"##MS_ServerStateManager##"
};
/// <summary>
/// Simple description, isMember pair - used as the value in the serverRoles map
/// </summary>
@@ -688,7 +733,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
if (0 != String.Compare(serverRoleName, "public", StringComparison.Ordinal))
{
((ServerRoleInfo) serverRoles[serverRoleName]).isMember = isMember;
var roleInfo = ((ServerRoleInfo) serverRoles[serverRoleName]);
roleInfo.isMember = isMember;
}
}
@@ -732,6 +778,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.initialized = true;
serverRoles.Clear();
if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase)
{
PopulateServerRolesForAzure();
return;
}
try
{
foreach (ServerRole role in server.Roles)
@@ -758,6 +810,52 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
private void PopulateServerRolesForAzure()
{
Dictionary<string, HashSet<string>> roleToMembership = new Dictionary<string, HashSet<string>>();
DataSet dataset = server.ExecutionManager.ConnectionContext.ExecuteWithResults(AZURE_SERVER_ROLE_MEMBERSHIP_QUERY);
if (dataset != null)
{
for (int i = 0; i < dataset.Tables[0].Rows.Count; i++)
{
string login = dataset.Tables[0].Rows[i][1].ToString();
string role = dataset.Tables[0].Rows[i][3].ToString();
if (!roleToMembership.ContainsKey(role))
{
roleToMembership.Add(role, new HashSet<string>());
}
HashSet<string> members;
roleToMembership.TryGetValue(role, out members);
if (members != null)
{
members.Add(login);
}
}
}
foreach (string role in AZURE_SERVER_ROLES)
{
bool isRoleMember = false;
if (this.loginExists)
{
HashSet<string> members;
roleToMembership.TryGetValue(role, out members);
if (members != null && members.Contains(this.loginName))
{
isRoleMember = true;
}
}
string roleDescription = String.Empty; // role.Description;
this.serverRoles.Add(role, new ServerRoleInfo(roleDescription, isRoleMember));
}
}
}
/// <summary>
@@ -816,6 +914,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private Microsoft.SqlServer.Management.Smo.Server server;
private static string defaultLanguageDisplay;
private bool windowsAuthSupported = true;
private bool aADAuthSupported = false;
private StringCollection credentials = null;
#endregion
@@ -953,7 +1052,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
get
{
if (this.server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance)
if (this.server.ServerType == DatabaseEngineType.SqlAzureDatabase)
{
this.windowsAuthSupported = false;
}
@@ -962,6 +1061,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
public bool AADAuthSupported
{
get
{
if (this.server.ServerType == DatabaseEngineType.SqlAzureDatabase)
{
this.aADAuthSupported = true;
}
return this.aADAuthSupported;
}
}
public static string DefaultLanguageDisplay
{
get
@@ -1344,7 +1456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
bool useSqlAuthentication = (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin);
this.windowsGrantAccess = !login.DenyWindowsLogin;
this.windowsGrantAccess = this.server.ServerType != DatabaseEngineType.SqlAzureDatabase ? !login.DenyWindowsLogin : false;
this.sqlPassword = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
this.sqlPasswordConfirm = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
@@ -1365,7 +1477,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
if (isYukon)
{
if (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
if (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin && this.server.ServerType != DatabaseEngineType.SqlAzureDatabase)
{
// these properties make sense only for Yukon+ with SQL Authentication
this.mustChange = login.MustChangePassword;
@@ -1400,7 +1512,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{
this.credentials.Add(login.Credential);
}
else if (server.Information.Version.Major >= 10)
else if (server.Information.Version.Major >= 10 && server.ServerType != DatabaseEngineType.SqlAzureDatabase)
{
this.credentials.Clear();
foreach (string str in login.EnumCredentials())
@@ -1521,6 +1633,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
public bool AADAuthSupported
{
get
{
return this.currentState.AADAuthSupported;
}
}
/// <summary>
/// Whether the Windows account is granted server access (e.g. true == grant access, false == deny access)
/// </summary>
@@ -1963,12 +2083,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.originalState = (LoginPrototypeData) this.currentState.Clone();
this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation);
this.LoginName = login.LoginName;
this.LoginName = login.Name;
this.SqlPassword = login.Password;
this.OldPassword = login.OldPassword;
this.LoginType = SqlServer.Management.Smo.LoginType.SqlLogin;
this.LoginType = GetLoginType(login);
this.DefaultLanguage = login.DefaultLanguage;
this.DefaultDatabase = login.DefaultDatabase;
this.EnforcePolicy = login.EnforcePasswordPolicy;
this.EnforceExpiration = login.EnforcePasswordPolicy ? login.EnforcePasswordExpiration : false;
this.IsLockedOut = login.IsLockedOut;
this.IsDisabled = !login.IsEnabled;
this.MustChange = login.EnforcePasswordPolicy ? login.MustChangePassword : false;
this.WindowsGrantAccess = login.ConnectPermission;
}
private LoginType GetLoginType(LoginInfo loginInfo)
{
switch (loginInfo.AuthenticationType)
{
case LoginAuthenticationType.AAD:
return SqlServer.Management.Smo.LoginType.ExternalUser;
case LoginAuthenticationType.Windows:
return SqlServer.Management.Smo.LoginType.WindowsUser; // TODO handle windows group
default:
return SqlServer.Management.Smo.LoginType.SqlLogin;
}
}
/// <summary>
@@ -2030,7 +2169,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
changesMade = true;
}
if (9 <= server.Information.Version.Major)
if (9 <= server.Information.Version.Major && server.ServerType != DatabaseEngineType.SqlAzureDatabase)
{
if (!this.Exists || (this.currentState.CertificateName != this.originalState.CertificateName))
{

View File

@@ -93,7 +93,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// Login request handlers
this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true);
this.ServiceHost.SetRequestHandler(UpdateLoginRequest.Type, HandleUpdateLoginRequest, true);
this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true);
this.ServiceHost.SetRequestHandler(InitializeLoginViewRequest.Type, HandleInitializeLoginViewRequest, true);
this.ServiceHost.SetRequestHandler(DisposeLoginViewRequest.Type, HandleDisposeLoginViewRequest, true);
// User request handlers
this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, HandleInitializeUserViewRequest, true);
@@ -107,14 +110,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// <summary>
/// Handle request to create a login
/// </summary>
internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext<CreateLoginResult> requestContext)
internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext<object> requestContext)
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo);
// if (connInfo == null)
// {
// // raise an error
// }
string ownerUri;
contextIdToConnectionUriMap.TryGetValue(parameters.ContextId, out ownerUri);
ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo);
if (connInfo == null)
{
// raise error here
}
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
LoginPrototype prototype = new LoginPrototype(dataContainer.Server, parameters.Login);
@@ -127,7 +133,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// return the error associated with null password (coming from policy) - see bug 124377
if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false)
{
// raise error here
// raise error here
}
// check that password and confirm password controls' text matches
@@ -139,29 +145,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
prototype.ApplyGeneralChanges(dataContainer.Server);
await requestContext.SendResult(new CreateLoginResult()
// TODO move this to LoginData
// TODO support role assignment for Azure
LoginPrototype newPrototype = new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Login.Name]);
var _ =newPrototype.ServerRoles.ServerRoleNames;
foreach (string role in parameters.Login.ServerRoles)
{
Login = parameters.Login,
Success = true,
ErrorMessage = string.Empty
});
newPrototype.ServerRoles.SetMember(role, true);
}
newPrototype.ApplyServerRoleChanges(dataContainer.Server);
await requestContext.SendResult(new object());
}
/// <summary>
/// Handle request to delete a credential
/// </summary>
internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext<ResultStatus> requestContext)
internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext<object> requestContext)
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo);
ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo);
// if (connInfo == null)
// {
// // raise an error
// }
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
Login login = dataContainer.Server?.Logins[parameters.LoginName];
Login login = dataContainer.Server?.Logins[parameters.Name];
dataContainer.SqlDialogSubject = login;
DoDropObject(dataContainer);
@@ -172,6 +184,152 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
});
}
internal async Task HandleUpdateLoginRequest(UpdateLoginParams parameters, RequestContext<object> requestContext)
{
ConnectionInfo connInfo;
string ownerUri;
contextIdToConnectionUriMap.TryGetValue(parameters.ContextId, out ownerUri);
ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo);
if (connInfo == null)
{
// raise error here
}
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
LoginPrototype prototype = new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Login.Name]);
var login = parameters.Login;
prototype.SqlPassword = login.Password;
prototype.DefaultLanguage = login.DefaultLanguage;
prototype.DefaultDatabase = login.DefaultDatabase;
prototype.EnforcePolicy = login.EnforcePasswordPolicy;
prototype.EnforceExpiration = login.EnforcePasswordPolicy ? login.EnforcePasswordExpiration : false;
prototype.IsLockedOut = login.IsLockedOut;
prototype.IsDisabled = !login.IsEnabled;
prototype.MustChange = login.EnforcePasswordPolicy ? login.MustChangePassword : false;
prototype.WindowsGrantAccess = login.ConnectPermission;
if (prototype.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
{
// check that there is a password
// this check is made if policy enforcement is off
// with policy turned on we do not display this message, instead we let server
// return the error associated with null password (coming from policy) - see bug 124377
if (prototype.SqlPassword.Length == 0 && prototype.EnforcePolicy == false)
{
// raise error here
}
// check that password and confirm password controls' text matches
if (0 != String.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal))
{
// raise error here
}
}
var _ = prototype.ServerRoles.ServerRoleNames;
foreach (string role in login.ServerRoles)
{
prototype.ServerRoles.SetMember(role, true);
}
prototype.ApplyGeneralChanges(dataContainer.Server);
prototype.ApplyServerRoleChanges(dataContainer.Server);
prototype.ApplyDatabaseRoleChanges(dataContainer.Server);
await requestContext.SendResult(new object());
}
internal async Task HandleInitializeLoginViewRequest(InitializeLoginViewRequestParams parameters, RequestContext<LoginViewInfo> requestContext)
{
contextIdToConnectionUriMap.Add(parameters.ContextId, parameters.ConnectionUri);
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo);
if (connInfo == null)
{
// raise an error
}
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
LoginViewInfo loginViewInfo = new LoginViewInfo();
string[] databases = new string[dataContainer.Server.Databases.Count];
for (int i = 0; i < dataContainer.Server.Databases.Count; i++)
{
databases[i] = dataContainer.Server.Databases[i].Name;
}
string[] languages = new string[dataContainer.Server.Languages.Count];
for (int i = 0; i < dataContainer.Server.Languages.Count; i++)
{
languages[i] = dataContainer.Server.Languages[i].Name;
}
LoginPrototype prototype = parameters.IsNewObject
? new LoginPrototype(dataContainer.Server)
: new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Name]);
List<string> loginServerRoles = new List<string>();
foreach(string role in prototype.ServerRoles.ServerRoleNames)
{
if (prototype.ServerRoles.IsMember(role))
{
loginServerRoles.Add(role);
}
}
LoginInfo loginInfo = new LoginInfo()
{
Name = prototype.LoginName,
Password = prototype.SqlPassword,
OldPassword = prototype.OldPassword,
AuthenticationType = LoginTypeToAuthenticationType(prototype.LoginType),
EnforcePasswordExpiration = prototype.EnforceExpiration,
EnforcePasswordPolicy = prototype.EnforcePolicy,
MustChangePassword = prototype.MustChange,
DefaultDatabase = prototype.DefaultDatabase,
DefaultLanguage = prototype.DefaultDatabase,
ServerRoles = loginServerRoles.ToArray(),
ConnectPermission = prototype.WindowsGrantAccess,
IsEnabled = !prototype.IsDisabled,
IsLockedOut = prototype.IsLockedOut,
UserMapping = new ServerLoginDatabaseUserMapping[0]
};
await requestContext.SendResult(new LoginViewInfo()
{
ObjectInfo = loginInfo,
SupportWindowsAuthentication = prototype.WindowsAuthSupported,
SupportAADAuthentication = prototype.AADAuthSupported,
SupportSQLAuthentication = true, // SQL Auth support for login, not necessarily mean SQL Auth support for CONNECT etc.
CanEditLockedOutState = true,
Databases = databases,
Languages = languages,
ServerRoles = prototype.ServerRoles.ServerRoleNames,
SupportAdvancedPasswordOptions = dataContainer.Server.DatabaseEngineType == DatabaseEngineType.Standalone || dataContainer.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse,
SupportAdvancedOptions = dataContainer.Server.DatabaseEngineType == DatabaseEngineType.Standalone || dataContainer.Server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance
});
}
private LoginAuthenticationType LoginTypeToAuthenticationType(LoginType loginType)
{
switch (loginType)
{
case LoginType.WindowsUser:
case LoginType.WindowsGroup:
return LoginAuthenticationType.Windows;
case LoginType.SqlLogin:
return LoginAuthenticationType.Sql;
case LoginType.ExternalUser:
case LoginType.ExternalGroup:
return LoginAuthenticationType.AAD;
default:
return LoginAuthenticationType.Others;
}
}
internal async Task HandleDisposeLoginViewRequest(DisposeLoginViewRequestParams parameters, RequestContext<object> requestContext)
{
contextIdToConnectionUriMap.Remove(parameters.ContextId);
await requestContext.SendResult(new object());
}
#endregion
#region "User Handlers"

View File

@@ -11,7 +11,7 @@ using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
using Microsoft.SqlTools.ServiceLayer.Security;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Microsoft.SqlTools.ServiceLayer.Utility;
// using Microsoft.SqlTools.ServiceLayer.Utility;
using Moq;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
@@ -31,40 +31,46 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{
// setup
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
var contextId = System.Guid.NewGuid().ToString();
var initializeLoginViewRequestParams = new InitializeLoginViewRequestParams
{
ConnectionUri = connectionResult.ConnectionInfo.OwnerUri,
ContextId = contextId,
IsNewObject = true
};
var loginParams = new CreateLoginParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
ContextId = contextId,
Login = SecurityTestUtils.GetTestLoginInfo()
};
var createContext = new Mock<RequestContext<CreateLoginResult>>();
createContext.Setup(x => x.SendResult(It.IsAny<CreateLoginResult>()))
var createLoginContext = new Mock<RequestContext<object>>();
createLoginContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new object()));
var initializeLoginViewContext = new Mock<RequestContext<LoginViewInfo>>();
initializeLoginViewContext.Setup(x => x.SendResult(It.IsAny<LoginViewInfo>()))
.Returns(Task.FromResult(new LoginViewInfo()));
// call the create login method
SecurityService service = new SecurityService();
await service.HandleCreateLoginRequest(loginParams, createContext.Object);
// verify the result
createContext.Verify(x => x.SendResult(It.Is<CreateLoginResult>
(p => p.Success && p.Login.LoginName != string.Empty)));
await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object);
await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object);
// cleanup created login
var deleteParams = new DeleteLoginParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
LoginName = loginParams.Login.LoginName
ConnectionUri = connectionResult.ConnectionInfo.OwnerUri,
Name = loginParams.Login.Name
};
var deleteContext = new Mock<RequestContext<ResultStatus>>();
deleteContext.Setup(x => x.SendResult(It.IsAny<ResultStatus>()))
var deleteContext = new Mock<RequestContext<object>>();
deleteContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new object()));
// call the create login method
await service.HandleDeleteLoginRequest(deleteParams, deleteContext.Object);
// verify the result
deleteContext.Verify(x => x.SendResult(It.Is<ResultStatus>(p => p.Success)));
}
}
}

View File

@@ -29,17 +29,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{
return new LoginInfo()
{
LoginName = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(),
LoginType= LoginType.Sql,
CertificateName = "Test Cert",
AsymmetricKeyName = "Asymmetric Test Cert",
Name = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(),
AuthenticationType= LoginAuthenticationType.Sql,
WindowsGrantAccess = true,
MustChange = false,
IsDisabled = false,
MustChangePassword = false,
IsEnabled = false,
IsLockedOut = false,
EnforcePolicy = false,
EnforceExpiration = false,
WindowsAuthSupported = false,
EnforcePasswordPolicy = false,
EnforcePasswordExpiration = false,
Password = "placeholder",
OldPassword = "placeholder",
DefaultLanguage = "us_english",

View File

@@ -28,29 +28,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{
// setup
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
var contextId = System.Guid.NewGuid().ToString();
var initializeLoginViewRequestParams = new InitializeLoginViewRequestParams
{
ConnectionUri = connectionResult.ConnectionInfo.OwnerUri,
ContextId = contextId,
IsNewObject = true
};
var loginParams = new CreateLoginParams
{
OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
ContextId = contextId,
Login = SecurityTestUtils.GetTestLoginInfo()
};
var createLoginContext = new Mock<RequestContext<CreateLoginResult>>();
createLoginContext.Setup(x => x.SendResult(It.IsAny<CreateLoginResult>()))
var createLoginContext = new Mock<RequestContext<object>>();
createLoginContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new object()));
var initializeLoginViewContext = new Mock<RequestContext<LoginViewInfo>>();
initializeLoginViewContext.Setup(x => x.SendResult(It.IsAny<LoginViewInfo>()))
.Returns(Task.FromResult(new LoginViewInfo()));
// call the create login method
SecurityService service = new SecurityService();
await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object);
await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object);
// verify the result
createLoginContext.Verify(x => x.SendResult(It.Is<CreateLoginResult>
(p => p.Success && p.Login.LoginName != string.Empty)));
var userParams = new CreateUserParams
{
ContextId = connectionResult.ConnectionInfo.OwnerUri,
User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName)
User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.Name)
};
var createUserContext = new Mock<RequestContext<CreateUserResult>>();