mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>>();
|
||||
|
||||
Reference in New Issue
Block a user