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 namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{ {
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public enum LoginType public enum LoginAuthenticationType
{ {
[EnumMember(Value = "Windows")] [EnumMember(Value = "Windows")]
Windows, Windows,
[EnumMember(Value = "Sql")] [EnumMember(Value = "Sql")]
Sql, Sql,
[EnumMember(Value = "AAD")] [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> /// <summary>
@@ -27,27 +37,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public class LoginInfo public class LoginInfo
{ {
public string LoginName { get; set; } public string Name { get; set; }
public LoginType LoginType { get; set; } public LoginAuthenticationType AuthenticationType { get; set; }
public string CertificateName { get; set; }
public string AsymmetricKeyName { get; set; }
public bool WindowsGrantAccess { 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 IsLockedOut { get; set; }
public bool EnforcePolicy { get; set; } public bool EnforcePasswordPolicy { get; set; }
public bool EnforceExpiration { get; set; } public bool EnforcePasswordExpiration { get; set; }
public bool WindowsAuthSupported { get; set; }
public string Password { get; set; } public string Password { get; set; }
@@ -56,5 +61,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
public string DefaultLanguage { get; set; } public string DefaultLanguage { get; set; }
public string DefaultDatabase { get; set; } public string DefaultDatabase { get; set; }
public string[] ServerRoles {get; set;}
public ServerLoginDatabaseUserMapping[] UserMapping;
} }
} }

View File

@@ -6,7 +6,6 @@
#nullable disable #nullable disable
using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
@@ -16,20 +15,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public class CreateLoginParams : GeneralRequestDetails public class CreateLoginParams : GeneralRequestDetails
{ {
public string OwnerUri { get; set; } public string ContextId { get; set; }
public LoginInfo Login { get; set; } public LoginInfo Login { get; set; }
} }
/// <summary>
/// Create Login result
/// </summary>
public class CreateLoginResult : ResultStatus
{
public LoginInfo Login { get; set; }
}
/// <summary> /// <summary>
/// Create Login request type /// Create Login request type
/// </summary> /// </summary>
@@ -39,8 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// Request definition /// Request definition
/// </summary> /// </summary>
public static readonly public static readonly
RequestType<CreateLoginParams, CreateLoginResult> Type = RequestType<CreateLoginParams, object> Type =
RequestType<CreateLoginParams, CreateLoginResult>.Create("security/createlogin"); RequestType<CreateLoginParams, object>.Create("objectManagement/createLogin");
} }
/// <summary> /// <summary>
@@ -48,9 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public class DeleteLoginParams : GeneralRequestDetails 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> /// <summary>
@@ -62,7 +52,78 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// Request definition /// Request definition
/// </summary> /// </summary>
public static readonly public static readonly
RequestType<DeleteLoginParams, ResultStatus> Type = RequestType<DeleteLoginParams, object> Type =
RequestType<DeleteLoginParams, ResultStatus>.Create("security/deletelogin"); 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;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Data; using System.Data;
@@ -560,6 +561,50 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private bool initialized; 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> /// <summary>
/// Simple description, isMember pair - used as the value in the serverRoles map /// Simple description, isMember pair - used as the value in the serverRoles map
/// </summary> /// </summary>
@@ -688,7 +733,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
if (0 != String.Compare(serverRoleName, "public", StringComparison.Ordinal)) 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; this.initialized = true;
serverRoles.Clear(); serverRoles.Clear();
if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase)
{
PopulateServerRolesForAzure();
return;
}
try try
{ {
foreach (ServerRole role in server.Roles) 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> /// <summary>
@@ -816,6 +914,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private Microsoft.SqlServer.Management.Smo.Server server; private Microsoft.SqlServer.Management.Smo.Server server;
private static string defaultLanguageDisplay; private static string defaultLanguageDisplay;
private bool windowsAuthSupported = true; private bool windowsAuthSupported = true;
private bool aADAuthSupported = false;
private StringCollection credentials = null; private StringCollection credentials = null;
#endregion #endregion
@@ -953,7 +1052,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{ {
get get
{ {
if (this.server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance) if (this.server.ServerType == DatabaseEngineType.SqlAzureDatabase)
{ {
this.windowsAuthSupported = false; 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 public static string DefaultLanguageDisplay
{ {
get get
@@ -1344,7 +1456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
bool useSqlAuthentication = (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin); 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.sqlPassword = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
this.sqlPasswordConfirm = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty; this.sqlPasswordConfirm = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
@@ -1365,7 +1477,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
if (isYukon) 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 // these properties make sense only for Yukon+ with SQL Authentication
this.mustChange = login.MustChangePassword; this.mustChange = login.MustChangePassword;
@@ -1400,7 +1512,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{ {
this.credentials.Add(login.Credential); 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(); this.credentials.Clear();
foreach (string str in login.EnumCredentials()) foreach (string str in login.EnumCredentials())
@@ -1521,6 +1633,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
} }
public bool AADAuthSupported
{
get
{
return this.currentState.AADAuthSupported;
}
}
/// <summary> /// <summary>
/// Whether the Windows account is granted server access (e.g. true == grant access, false == deny access) /// Whether the Windows account is granted server access (e.g. true == grant access, false == deny access)
/// </summary> /// </summary>
@@ -1963,12 +2083,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.originalState = (LoginPrototypeData) this.currentState.Clone(); this.originalState = (LoginPrototypeData) this.currentState.Clone();
this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation); this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation);
this.LoginName = login.LoginName; this.LoginName = login.Name;
this.SqlPassword = login.Password; this.SqlPassword = login.Password;
this.OldPassword = login.OldPassword; this.OldPassword = login.OldPassword;
this.LoginType = SqlServer.Management.Smo.LoginType.SqlLogin; this.LoginType = GetLoginType(login);
this.DefaultLanguage = login.DefaultLanguage; this.DefaultLanguage = login.DefaultLanguage;
this.DefaultDatabase = login.DefaultDatabase; 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> /// <summary>
@@ -2030,7 +2169,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
changesMade = true; 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)) if (!this.Exists || (this.currentState.CertificateName != this.originalState.CertificateName))
{ {

View File

@@ -93,7 +93,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// Login request handlers // Login request handlers
this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true); this.ServiceHost.SetRequestHandler(CreateLoginRequest.Type, HandleCreateLoginRequest, true);
this.ServiceHost.SetRequestHandler(UpdateLoginRequest.Type, HandleUpdateLoginRequest, true);
this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, 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 // User request handlers
this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, HandleInitializeUserViewRequest, true); this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, HandleInitializeUserViewRequest, true);
@@ -107,14 +110,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// <summary> /// <summary>
/// Handle request to create a login /// Handle request to create a login
/// </summary> /// </summary>
internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext<CreateLoginResult> requestContext) internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext<object> requestContext)
{ {
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); string ownerUri;
// if (connInfo == null) contextIdToConnectionUriMap.TryGetValue(parameters.ContextId, out ownerUri);
// { ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo);
// // raise an error
// } if (connInfo == null)
{
// raise error here
}
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
LoginPrototype prototype = new LoginPrototype(dataContainer.Server, parameters.Login); LoginPrototype prototype = new LoginPrototype(dataContainer.Server, parameters.Login);
@@ -139,28 +145,34 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
prototype.ApplyGeneralChanges(dataContainer.Server); 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, newPrototype.ServerRoles.SetMember(role, true);
Success = true, }
ErrorMessage = string.Empty
}); newPrototype.ApplyServerRoleChanges(dataContainer.Server);
await requestContext.SendResult(new object());
} }
/// <summary> /// <summary>
/// Handle request to delete a credential /// Handle request to delete a credential
/// </summary> /// </summary>
internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext<ResultStatus> requestContext) internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext<object> requestContext)
{ {
ConnectionInfo connInfo; ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo);
// if (connInfo == null) // if (connInfo == null)
// { // {
// // raise an error // // raise an error
// } // }
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
Login login = dataContainer.Server?.Logins[parameters.LoginName]; Login login = dataContainer.Server?.Logins[parameters.Name];
dataContainer.SqlDialogSubject = login; dataContainer.SqlDialogSubject = login;
DoDropObject(dataContainer); 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 #endregion
#region "User Handlers" #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;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts; using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Microsoft.SqlTools.ServiceLayer.Utility; // using Microsoft.SqlTools.ServiceLayer.Utility;
using Moq; using Moq;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
@@ -31,40 +31,46 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{ {
// setup // setup
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); 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 var loginParams = new CreateLoginParams
{ {
OwnerUri = connectionResult.ConnectionInfo.OwnerUri, ContextId = contextId,
Login = SecurityTestUtils.GetTestLoginInfo() Login = SecurityTestUtils.GetTestLoginInfo()
}; };
var createContext = new Mock<RequestContext<CreateLoginResult>>(); var createLoginContext = new Mock<RequestContext<object>>();
createContext.Setup(x => x.SendResult(It.IsAny<CreateLoginResult>())) createLoginContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new 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 // call the create login method
SecurityService service = new SecurityService(); SecurityService service = new SecurityService();
await service.HandleCreateLoginRequest(loginParams, createContext.Object); await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object);
await service.HandleCreateLoginRequest(loginParams, createLoginContext.Object);
// verify the result
createContext.Verify(x => x.SendResult(It.Is<CreateLoginResult>
(p => p.Success && p.Login.LoginName != string.Empty)));
// cleanup created login // cleanup created login
var deleteParams = new DeleteLoginParams var deleteParams = new DeleteLoginParams
{ {
OwnerUri = connectionResult.ConnectionInfo.OwnerUri, ConnectionUri = connectionResult.ConnectionInfo.OwnerUri,
LoginName = loginParams.Login.LoginName Name = loginParams.Login.Name
}; };
var deleteContext = new Mock<RequestContext<ResultStatus>>(); var deleteContext = new Mock<RequestContext<object>>();
deleteContext.Setup(x => x.SendResult(It.IsAny<ResultStatus>())) deleteContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new object())); .Returns(Task.FromResult(new object()));
// call the create login method // call the create login method
await service.HandleDeleteLoginRequest(deleteParams, deleteContext.Object); 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() return new LoginInfo()
{ {
LoginName = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(), Name = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(),
LoginType= LoginType.Sql, AuthenticationType= LoginAuthenticationType.Sql,
CertificateName = "Test Cert",
AsymmetricKeyName = "Asymmetric Test Cert",
WindowsGrantAccess = true, WindowsGrantAccess = true,
MustChange = false, MustChangePassword = false,
IsDisabled = false, IsEnabled = false,
IsLockedOut = false, IsLockedOut = false,
EnforcePolicy = false, EnforcePasswordPolicy = false,
EnforceExpiration = false, EnforcePasswordExpiration = false,
WindowsAuthSupported = false,
Password = "placeholder", Password = "placeholder",
OldPassword = "placeholder", OldPassword = "placeholder",
DefaultLanguage = "us_english", DefaultLanguage = "us_english",

View File

@@ -28,29 +28,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{ {
// setup // setup
var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath); 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 var loginParams = new CreateLoginParams
{ {
OwnerUri = connectionResult.ConnectionInfo.OwnerUri, ContextId = contextId,
Login = SecurityTestUtils.GetTestLoginInfo() Login = SecurityTestUtils.GetTestLoginInfo()
}; };
var createLoginContext = new Mock<RequestContext<CreateLoginResult>>(); var createLoginContext = new Mock<RequestContext<object>>();
createLoginContext.Setup(x => x.SendResult(It.IsAny<CreateLoginResult>())) createLoginContext.Setup(x => x.SendResult(It.IsAny<object>()))
.Returns(Task.FromResult(new 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 // call the create login method
SecurityService service = new SecurityService(); SecurityService service = new SecurityService();
await service.HandleInitializeLoginViewRequest(initializeLoginViewRequestParams, initializeLoginViewContext.Object);
await service.HandleCreateLoginRequest(loginParams, createLoginContext.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 var userParams = new CreateUserParams
{ {
ContextId = connectionResult.ConnectionInfo.OwnerUri, ContextId = connectionResult.ConnectionInfo.OwnerUri,
User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName) User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.Name)
}; };
var createUserContext = new Mock<RequestContext<CreateUserResult>>(); var createUserContext = new Mock<RequestContext<CreateUserResult>>();