User creation handler clean-ups (#1867)

* Wire up init user message

* Send schema list

* Load database roles

* Add create user

* Add a delete user handler and format service file
This commit is contained in:
Karl Burtram
2023-02-17 08:38:17 -08:00
committed by GitHub
parent 74dd15c868
commit 675427c690
9 changed files with 487 additions and 373 deletions

View File

@@ -749,7 +749,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
// NOTE: ServerConnection property will constuct the object if needed // NOTE: ServerConnection property will construct the object if needed
m_server ??= new Server(ServerConnection); m_server ??= new Server(ServerConnection);
} }
} }

View File

@@ -3,55 +3,40 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
#nullable disable
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{ {
[JsonConverter(typeof(StringEnumConverter))]
public enum ServerAuthenticationType
{
[EnumMember(Value = "Windows")]
Windows,
[EnumMember(Value = "Sql")]
Sql,
[EnumMember(Value = "AAD")]
AzureActiveDirectory
}
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public enum DatabaseUserType public enum DatabaseUserType
{ {
[EnumMember(Value = "UserWithLogin")] // User with a server level login.
UserWithLogin, [EnumMember(Value = "WithLogin")]
[EnumMember(Value = "UserWithoutLogin")] WithLogin,
UserWithoutLogin // User based on a Windows user/group that has no login, but can connect to the Database Engine through membership in a Windows group.
[EnumMember(Value = "WithWindowsGroupLogin")]
WithWindowsGroupLogin,
// Contained user, authentication is done within the database.
[EnumMember(Value = "Contained")]
Contained,
// User that cannot authenticate.
[EnumMember(Value = "NoConnectAccess")]
NoConnectAccess
} }
public class ExtendedProperty
{
public string Name { get; set; }
public string Value { get; set; }
}
public class SqlObject
{
public string Name { get; set; }
public string Path { get; set; }
}
public class Permission
{
public string Name { get; set; }
public bool Grant { get; set; }
public bool WithGrant { get; set; }
public bool Deny { get; set; }
}
public class SecurablePermissions
{
public SqlObject Securable { get; set; }
public Permission[] Permissions { get; set; }
}
/// <summary> /// <summary>
/// a class for storing various user properties /// a class for storing various user properties
@@ -60,69 +45,44 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{ {
public DatabaseUserType? Type { get; set; } public DatabaseUserType? Type { get; set; }
public string UserName { get; set; } public string? Name { get; set; }
public string LoginName { get; set; } public string? LoginName { get; set; }
public string Password { get; set; } public string? Password { get; set; }
public string DefaultSchema { get; set; } public string? DefaultSchema { get; set; }
public string[] OwnedSchemas { get; set; } public string[]? OwnedSchemas { get; set; }
public bool isEnabled { get; set; } public string[]? DatabaseRoles { get; set; }
public bool isAAD { get; set; } public ServerAuthenticationType AuthenticationType { get; set; }
public ExtendedProperty[]? ExtendedProperties { get; set; } public string? DefaultLanguage { get; set; }
}
public SecurablePermissions[]? SecurablePermissions { get; set; } /// <summary>
/// The information required to render the user view.
/// </summary>
public class UserViewInfo
{
public UserInfo? ObjectInfo { get; set; }
public bool SupportContainedUser { get; set; }
public bool SupportWindowsAuthentication { get; set; }
public bool SupportAADAuthentication { get; set; }
public bool SupportSQLAuthentication { get; set; }
public string[]? Languages { get; set; }
public string[]? Schemas { get; set; }
public string[]? Logins { get; set; }
public string[]? DatabaseRoles { get; set; }
} }
} }
#if false
export interface ServerRole extends SqlObject {
owner: string | undefined;
securablePermissions: SecurablePermissions[];
members: SqlObject[];
memberships: SqlObject[];
isFixedRole: boolean;
}
export interface ServerLogin extends SqlObject {
type: LoginType;
password: string | undefined;
oldPassword: string | undefined;
enforcePasswordPolicy: boolean | undefined;
enforcePasswordExpiration: boolean | undefined;
defaultDatabase: string;
defaultLanguage: string;
serverRoles: string[];
userMapping: ServerLoginDatabaseUserMapping[];
isGroup: boolean;
isEnabled: boolean;
connectPermission: boolean;
isLockedOut: boolean;
}
export interface ServerLoginDatabaseUserMapping {
database: string;
user: string;
defaultSchema: string;
databaseRoles: string[];
}
export interface DatabaseRole extends SqlObject {
owner: string | undefined;
password: string | undefined;
ownedSchemas: string[];
securablePermissions: SecurablePermissions[] | undefined;
extendedProperties: ExtendedProperty[] | undefined;
isFixedRole: boolean;
}
#endif

View File

@@ -8,15 +8,43 @@ 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
{ {
/// <summary>
/// Initialize User View parameters
/// </summary>
public class InitializeUserViewParams
{
public string? ContextId { get; set; }
public string? ConnectionUri { get; set; }
public bool isNewObject { get; set; }
public string? Database { get; set; }
public string? Name { get; set; }
}
/// <summary>
/// Initialize User View request type
/// </summary>
public class InitializeUserViewRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<InitializeUserViewParams, UserViewInfo> Type =
RequestType<InitializeUserViewParams, UserViewInfo>.Create("objectManagement/initializeUserView");
}
/// <summary> /// <summary>
/// Create User parameters /// Create User parameters
/// </summary> /// </summary>
public class CreateUserParams : GeneralRequestDetails public class CreateUserParams : GeneralRequestDetails
{ {
public string OwnerUri { get; set; } public string? ContextId { get; set; }
public UserInfo? User { get; set; }
public UserInfo User { get; set; }
} }
/// <summary> /// <summary>
@@ -24,10 +52,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public class CreateUserResult : ResultStatus public class CreateUserResult : ResultStatus
{ {
public UserInfo User { get; set; } public UserInfo? User { get; set; }
} }
/// <summary> /// <summary>
/// Create User request type /// Create User request type
/// </summary> /// </summary>
@@ -38,17 +65,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public static readonly public static readonly
RequestType<CreateUserParams, CreateUserResult> Type = RequestType<CreateUserParams, CreateUserResult> Type =
RequestType<CreateUserParams, CreateUserResult>.Create("objectmanagement/createuser"); RequestType<CreateUserParams, CreateUserResult>.Create("objectManagement/createUser");
} }
/// <summary> /// <summary>
/// Delete User params /// Delete User params
/// </summary> /// </summary>
public class DeleteUserParams : GeneralRequestDetails public class DeleteUserParams
{ {
public string OwnerUri { get; set; } public string? ConnectionUri { get; set; }
public string UserName { get; set; } public string? Database { get; set; }
public string? Name { get; set; }
} }
/// <summary> /// <summary>
@@ -61,6 +90,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
/// </summary> /// </summary>
public static readonly public static readonly
RequestType<DeleteUserParams, ResultStatus> Type = RequestType<DeleteUserParams, ResultStatus> Type =
RequestType<DeleteUserParams, ResultStatus>.Create("objectmanagement/deleteuser"); RequestType<DeleteUserParams, ResultStatus>.Create("objectManagement/deleteUser");
} }
} }

View File

@@ -3,12 +3,11 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
#nullable disable
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
@@ -31,10 +30,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
{ {
private bool disposed; private bool disposed;
private ConnectionService connectionService; private ConnectionService? connectionService;
private static readonly Lazy<SecurityService> instance = new Lazy<SecurityService>(() => new SecurityService()); private static readonly Lazy<SecurityService> instance = new Lazy<SecurityService>(() => new SecurityService());
private Dictionary<string, string> contextIdToConnectionUriMap = new Dictionary<string, string>();
/// <summary> /// <summary>
/// Construct a new SecurityService instance with default parameters /// Construct a new SecurityService instance with default parameters
/// </summary> /// </summary>
@@ -71,7 +72,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// Service host object for sending/receiving requests/events. /// Service host object for sending/receiving requests/events.
/// Internal for testing purposes. /// Internal for testing purposes.
/// </summary> /// </summary>
internal IProtocolEndpoint ServiceHost internal IProtocolEndpoint? ServiceHost
{ {
get; get;
set; set;
@@ -95,11 +96,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true); this.ServiceHost.SetRequestHandler(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true);
// User request handlers // User request handlers
this.ServiceHost.SetRequestHandler(InitializeUserViewRequest.Type, HandleInitializeUserViewRequest, true);
this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, HandleCreateUserRequest, true); this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, HandleCreateUserRequest, true);
this.ServiceHost.SetRequestHandler(DeleteUserRequest.Type, HandleDeleteUserRequest, true);
} }
#region "Login Handlers" #region "Login Handlers"
/// <summary> /// <summary>
/// Handle request to create a login /// Handle request to create a login
@@ -128,10 +131,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
// check that password and confirm password controls' text matches // check that password and confirm password controls' text matches
if (0 != String.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal)) if (0 != string.Compare(prototype.SqlPassword, prototype.SqlPasswordConfirm, StringComparison.Ordinal))
{ {
// raise error here // raise error here
} }
} }
prototype.ApplyGeneralChanges(dataContainer.Server); prototype.ApplyGeneralChanges(dataContainer.Server);
@@ -157,11 +160,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// } // }
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.LoginName];
dataContainer.SqlDialogSubject = login; dataContainer.SqlDialogSubject = login;
DoDropObject(dataContainer); DoDropObject(dataContainer);
await requestContext.SendResult(new ResultStatus() await requestContext.SendResult(new ResultStatus()
{ {
Success = true, Success = true,
@@ -169,13 +172,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}); });
} }
#endregion #endregion
#region "User Handlers" #region "User Handlers"
internal Task<Tuple<bool, string>> ConfigureUser( internal Task<Tuple<bool, string>> ConfigureUser(
string ownerUri, string? ownerUri,
UserInfo user, UserInfo? user,
ConfigAction configAction, ConfigAction configAction,
RunType runType) RunType runType)
{ {
@@ -194,8 +197,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
var connectionInfoWithConnection = new SqlConnectionInfoWithConnection(); var connectionInfoWithConnection = new SqlConnectionInfoWithConnection();
connectionInfoWithConnection.ServerConnection = serverConnection; connectionInfoWithConnection.ServerConnection = serverConnection;
string urn = string.Format(System.Globalization.CultureInfo.InvariantCulture, string urn = string.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']", "Server/Database[@Name='{0}']",
Urn.EscapeString(serverConnection.DatabaseName)); Urn.EscapeString(serverConnection.DatabaseName));
ActionContext context = new ActionContext(serverConnection, "new_user", urn); ActionContext context = new ActionContext(serverConnection, "new_user", urn);
@@ -221,12 +224,142 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}); });
} }
private Dictionary<string, SchemaOwnership> LoadSchemas(string databaseName, string dbroleName, ServerConnection serverConnection)
{
bool isPropertiesMode = false;
Dictionary<string, SchemaOwnership> schemaOwnership = new Dictionary<string, SchemaOwnership>();
Enumerator en = new Enumerator();
Request req = new Request();
req.Fields = new String[] { "Name", "Owner" };
req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Schema";
DataTable dt = en.Process(serverConnection, req);
System.Diagnostics.Debug.Assert((dt != null) && (0 < dt.Rows.Count), "enumerator did not return schemas");
System.Diagnostics.Debug.Assert(!isPropertiesMode || (dbroleName.Length != 0), "role name is not known");
foreach (DataRow dr in dt.Rows)
{
string schemaName = Convert.ToString(dr["Name"], System.Globalization.CultureInfo.InvariantCulture);
string schemaOwner = Convert.ToString(dr["Owner"], System.Globalization.CultureInfo.InvariantCulture);
bool roleOwnsSchema = isPropertiesMode && (0 == String.Compare(dbroleName, schemaOwner, StringComparison.Ordinal));
if (schemaName != null)
{
schemaOwnership[schemaName] = new SchemaOwnership(roleOwnsSchema);
}
}
return schemaOwnership;
}
private string[] LoadDatabaseRoles(ServerConnection serverConnection, string databaseName)
{
var server = new Server(serverConnection);
string dbUrn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']";
Database? parent = server.GetSmoObject(new Urn(dbUrn)) as Database;
var roles = new List<string>();
if (parent != null)
{
foreach (DatabaseRole dbRole in parent.Roles)
{
var comparer = parent.GetStringComparer();
if (comparer.Compare(dbRole.Name, "public") != 0)
{
roles.Add(dbRole.Name);
}
}
}
return roles.ToArray();
}
private string[] LoadSqlLogins(ServerConnection serverConnection)
{
return LoadItems(serverConnection, "Server/Login");
}
private string[] LoadItems(ServerConnection serverConnection, string urn)
{
List<string> items = new List<string>();
Request req = new Request();
req.Urn = urn;
req.ResultType = ResultType.IDataReader;
req.Fields = new string[] { "Name" };
Enumerator en = new Enumerator();
using (IDataReader reader = en.Process(serverConnection, req).Data as IDataReader)
{
if (reader != null)
{
string name;
while (reader.Read())
{
// Get the permission name
name = reader.GetString(0);
items.Add(name);
}
}
}
return items.ToArray();
}
/// <summary>
/// Handle request to initialize user view
/// </summary>
internal async Task HandleInitializeUserViewRequest(InitializeUserViewParams parameters, RequestContext<UserViewInfo> requestContext)
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo);
if (connInfo == null)
{
throw new ArgumentException("Invalid connection URI '{0}'", parameters.ConnectionUri);
}
if (parameters.ContextId != null && parameters.ConnectionUri != null)
{
this.contextIdToConnectionUriMap.Add(parameters.ContextId, parameters.ConnectionUri);
}
var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer");
string databaseName = parameters.Database ?? "master";
var schemaMap = LoadSchemas(databaseName, string.Empty, serverConnection);
UserViewInfo userViewInfo = new UserViewInfo()
{
ObjectInfo = new UserInfo()
{
Type = DatabaseUserType.WithLogin,
Name = string.Empty,
LoginName = string.Empty,
Password = string.Empty,
DefaultSchema = string.Empty,
OwnedSchemas = new string[] { },
DatabaseRoles = new string[] { },
},
SupportContainedUser = true,
SupportWindowsAuthentication = true,
SupportAADAuthentication = true,
SupportSQLAuthentication = true,
Languages = new string[] { },
Schemas = schemaMap.Keys.ToArray(),
Logins = LoadSqlLogins(serverConnection),
DatabaseRoles = LoadDatabaseRoles(serverConnection, databaseName)
};
await requestContext.SendResult(userViewInfo);
}
/// <summary> /// <summary>
/// Handle request to create a user /// Handle request to create a user
/// </summary> /// </summary>
internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext<CreateUserResult> requestContext) internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext<CreateUserResult> requestContext)
{ {
var result = await ConfigureUser(parameters.OwnerUri, if (parameters.ContextId == null || !this.contextIdToConnectionUriMap.ContainsKey(parameters.ContextId))
{
throw new ArgumentException("Invalid context ID");
}
string connectionUri = this.contextIdToConnectionUriMap[parameters.ContextId];
var result = await ConfigureUser(connectionUri,
parameters.User, parameters.User,
ConfigAction.Create, ConfigAction.Create,
RunType.RunNow); RunType.RunNow);
@@ -236,10 +369,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
User = parameters.User, User = parameters.User,
Success = result.Item1, Success = result.Item1,
ErrorMessage = result.Item2 ErrorMessage = result.Item2
}); });
}
/// <summary>
/// Handle request to delete a user
/// </summary>
internal async Task HandleDeleteUserRequest(DeleteUserParams parameters, RequestContext<ResultStatus> requestContext)
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(parameters.ConnectionUri, out connInfo);
// if (connInfo == null)
// {
// // raise an error
// }
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
string dbUrn = "Server/Database[@Name='" + Urn.EscapeString(parameters.Database) + "']";
Database? parent = dataContainer.Server.GetSmoObject(new Urn(dbUrn)) as Database;
User user = parent.Users[parameters.Name];
dataContainer.SqlDialogSubject = user;
DoDropObject(dataContainer);
await requestContext.SendResult(new ResultStatus()
{
Success = true,
ErrorMessage = string.Empty
});
} }
private void GetDefaultLanguageOptions(CDataContainer dataContainer) private void GetDefaultLanguageOptions(CDataContainer dataContainer)
{ {
// this.defaultLanguageComboBox.Items.Clear(); // this.defaultLanguageComboBox.Items.Clear();
@@ -249,10 +407,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
SortedList sortedLanguages = new SortedList(Comparer.Default); SortedList sortedLanguages = new SortedList(Comparer.Default);
LanguageUtils.SetLanguageDefaultInitFieldsForDefaultLanguages(dataContainer.Server); LanguageUtils.SetLanguageDefaultInitFieldsForDefaultLanguages(dataContainer.Server);
foreach (Language language in dataContainer.Server.Languages) if (dataContainer.Server != null && dataContainer.Server.Languages != null)
{ {
LanguageDisplay listValue = new LanguageDisplay(language); foreach (Language language in dataContainer.Server.Languages)
sortedLanguages.Add(language.Alias, listValue); {
LanguageDisplay listValue = new LanguageDisplay(language);
sortedLanguages.Add(language.Alias, listValue);
}
} }
// add the language display objects to the combo box // add the language display objects to the combo box
@@ -314,22 +475,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// how to populate defaults from prototype, will delete once refactored // how to populate defaults from prototype, will delete once refactored
// private void InitializeValuesInUiControls() // private void InitializeValuesInUiControls()
// { // {
// this.userNameTextBox.Text = this.currentUserPrototype.Name;
// if(this.currentUserPrototype.UserType == UserType.Certificate)
// {
// this.mappedObjTextbox.Text = this.currentUserPrototype.CertificateName;
// }
// if (this.currentUserPrototype.UserType == UserType.AsymmetricKey)
// {
// this.mappedObjTextbox.Text = this.currentUserPrototype.AsymmetricKeyName;
// }
// IUserPrototypeWithMappedLogin mappedLoginPrototype = this.currentUserPrototype
// as IUserPrototypeWithMappedLogin;
// if (mappedLoginPrototype != null)
// {
// this.mappedObjTextbox.Text = mappedLoginPrototype.LoginName;
// }
// IUserPrototypeWithDefaultLanguage defaultLanguagePrototype = this.currentUserPrototype // IUserPrototypeWithDefaultLanguage defaultLanguagePrototype = this.currentUserPrototype
// as IUserPrototypeWithDefaultLanguage; // as IUserPrototypeWithDefaultLanguage;
@@ -356,7 +501,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// { // {
// this.defaultSchemaTextBox.Text = defaultSchemaPrototype.DefaultSchema; // this.defaultSchemaTextBox.Text = defaultSchemaPrototype.DefaultSchema;
// } // }
// IUserPrototypeWithPassword userWithPwdPrototype = this.currentUserPrototype // IUserPrototypeWithPassword userWithPwdPrototype = this.currentUserPrototype
// as IUserPrototypeWithPassword; // as IUserPrototypeWithPassword;
// if (userWithPwdPrototype != null // if (userWithPwdPrototype != null
@@ -366,31 +510,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// this.confirmPwdTextBox.Text = FAKE_PASSWORD; // this.confirmPwdTextBox.Text = FAKE_PASSWORD;
// } // }
// } // }
// private void UpdateUiControlsOnLoad()
// {
// if (!this.DataContainer.IsNewObject)
// {
// this.userNameTextBox.ReadOnly = true; //Rename is not allowed from the dialog.
// this.userSearchButton.Enabled = false;
// this.mappedObjTextbox.ReadOnly = true; //Changing mapped login, certificate and asymmetric key is not allowed
// this.mappedObjSearchButton.Enabled = false;
// //from SMO also.
// this.userTypeComboBox.Enabled = false;
// this.oldPasswordTextBox.ReadOnly = true;
// }
// else
// {
// //Old password is only useful for changing the password.
// this.specifyOldPwdCheckBox.Enabled = false;
// this.oldPasswordLabel.Enabled = false;
// this.oldPasswordTextBox.Enabled = false;
// }
// }
#endregion
#region "Credential Handlers"
#endregion
#region "Credential Handlers"
/// <summary> /// <summary>
/// Handle request to create a credential /// Handle request to create a credential
@@ -458,18 +584,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo);
CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true);
var credentials = dataContainer.Server.Credentials; var credentials = dataContainer.Server?.Credentials;
int credentialsCount = credentials.Count; int credentialsCount = credentials != null ? credentials.Count : 0;
CredentialInfo[] credentialsInfos = new CredentialInfo[credentialsCount]; CredentialInfo[] credentialsInfos = new CredentialInfo[credentialsCount];
for (int i = 0; i < credentialsCount; ++i) if (credentials != null)
{ {
credentialsInfos[i] = new CredentialInfo(); for (int i = 0; i < credentialsCount; ++i)
credentialsInfos[i].Name = credentials[i].Name; {
credentialsInfos[i].Identity = credentials[i].Identity; credentialsInfos[i] = new CredentialInfo();
credentialsInfos[i].Id = credentials[i].ID; credentialsInfos[i].Name = credentials[i].Name;
credentialsInfos[i].DateLastModified = credentials[i].DateLastModified; credentialsInfos[i].Identity = credentials[i].Identity;
credentialsInfos[i].CreateDate = credentials[i].CreateDate; credentialsInfos[i].Id = credentials[i].ID;
credentialsInfos[i].ProviderName = credentials[i].ProviderName; credentialsInfos[i].DateLastModified = credentials[i].DateLastModified;
credentialsInfos[i].CreateDate = credentials[i].CreateDate;
credentialsInfos[i].ProviderName = credentials[i].ProviderName;
}
} }
result.Credentials = credentialsInfos; result.Credentials = credentialsInfos;
result.Success = true; result.Success = true;
@@ -494,9 +623,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
} }
#endregion #endregion
#region "Helpers" #region "Helpers"
internal Task<Tuple<bool, string>> ConfigureCredential( internal Task<Tuple<bool, string>> ConfigureCredential(
string ownerUri, string ownerUri,
@@ -533,7 +662,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// </summary> /// </summary>
/// <param name="objectRowNumber"></param> /// <param name="objectRowNumber"></param>
private void DoDropObject(CDataContainer dataContainer) private void DoDropObject(CDataContainer dataContainer)
{ {
// if a server isn't connected then there is nothing to do
if (dataContainer.Server == null)
{
return;
}
var executionMode = dataContainer.Server.ConnectionContext.SqlExecutionModes; var executionMode = dataContainer.Server.ConnectionContext.SqlExecutionModes;
var subjectExecutionMode = executionMode; var subjectExecutionMode = executionMode;
@@ -557,15 +692,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes; sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes;
} }
Urn objUrn = sqlDialogSubject.Urn; Urn objUrn = sqlDialogSubject?.Urn;
System.Diagnostics.Debug.Assert(objUrn != null); System.Diagnostics.Debug.Assert(objUrn != null);
SfcObjectQuery objectQuery = new SfcObjectQuery(dataContainer.Server); SfcObjectQuery objectQuery = new SfcObjectQuery(dataContainer.Server);
IDroppable droppableObj = null; IDroppable droppableObj = null;
string[] fields = null; string[] fields = null;
foreach( object obj in objectQuery.ExecuteIterator( new SfcQueryExpression( objUrn.ToString() ), fields, null ) ) foreach (object obj in objectQuery.ExecuteIterator(new SfcQueryExpression(objUrn.ToString()), fields, null))
{ {
System.Diagnostics.Debug.Assert(droppableObj == null, "there is only one object"); System.Diagnostics.Debug.Assert(droppableObj == null, "there is only one object");
droppableObj = obj as IDroppable; droppableObj = obj as IDroppable;
@@ -586,7 +721,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
//special case database drop - see if we need to delete backup and restore history //special case database drop - see if we need to delete backup and restore history
SpecialPreDropActionsForObject(dataContainer, droppableObj, SpecialPreDropActionsForObject(dataContainer, droppableObj,
deleteBackupRestoreOrDisableAuditSpecOrDisableAudit: false, deleteBackupRestoreOrDisableAuditSpecOrDisableAudit: false,
dropOpenConnections: false); dropOpenConnections: false);
@@ -596,10 +731,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
SpecialPostDropActionsForObject(dataContainer, droppableObj); SpecialPostDropActionsForObject(dataContainer, droppableObj);
} }
private void SpecialPreDropActionsForObject(CDataContainer dataContainer, IDroppable droppableObj, private void SpecialPreDropActionsForObject(CDataContainer dataContainer, IDroppable droppableObj,
bool deleteBackupRestoreOrDisableAuditSpecOrDisableAudit, bool dropOpenConnections) bool deleteBackupRestoreOrDisableAuditSpecOrDisableAudit, bool dropOpenConnections)
{ {
// if a server isn't connected then there is nothing to do
if (dataContainer.Server == null)
{
return;
}
Database db = droppableObj as Database; Database db = droppableObj as Database;
if (deleteBackupRestoreOrDisableAuditSpecOrDisableAudit) if (deleteBackupRestoreOrDisableAuditSpecOrDisableAudit)
@@ -638,7 +779,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
// special case database drop - drop existing connections to the database other than this one // special case database drop - drop existing connections to the database other than this one
if (dropOpenConnections) if (dropOpenConnections)
{ {
if (db.ActiveConnections > 0) if (db?.ActiveConnections > 0)
{ {
// force the database to be single user // force the database to be single user
db.DatabaseOptions.UserAccess = DatabaseUserAccess.Single; db.DatabaseOptions.UserAccess = DatabaseUserAccess.Single;
@@ -649,6 +790,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
private void SpecialPostDropActionsForObject(CDataContainer dataContainer, IDroppable droppableObj) private void SpecialPostDropActionsForObject(CDataContainer dataContainer, IDroppable droppableObj)
{ {
// if a server isn't connected then there is nothing to do
if (dataContainer.Server == null)
{
return;
}
if (droppableObj is Policy) if (droppableObj is Policy)
{ {
Policy policyToDrop = (Policy)droppableObj; Policy policyToDrop = (Policy)droppableObj;
@@ -670,10 +817,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
} }
#endregion // "Helpers" #endregion // "Helpers"
// some potentially useful code for working with server & db roles to be refactored later // some potentially useful code for working with server & db roles to be refactored later
#region "Roles" #region "Roles"
private class SchemaOwnership private class SchemaOwnership
{ {
public bool initiallyOwned; public bool initiallyOwned;
@@ -704,199 +851,181 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
} }
private void DbRole_LoadMembership(string databaseName, string dbroleName, ServerConnection serverConnection) // private void DbRole_LoadMembership(string databaseName, string dbroleName, ServerConnection serverConnection)
{ // {
var roleMembers = new HybridDictionary(); // var roleMembers = new HybridDictionary();
bool isPropertiesMode = false; // bool isPropertiesMode = false;
if (isPropertiesMode) // if (isPropertiesMode)
{ // {
Enumerator enumerator = new Enumerator(); // Enumerator enumerator = new Enumerator();
Urn urn = String.Format(System.Globalization.CultureInfo.InvariantCulture, // Urn urn = String.Format(System.Globalization.CultureInfo.InvariantCulture,
"Server/Database[@Name='{0}']/Role[@Name='{1}']/Member", // "Server/Database[@Name='{0}']/Role[@Name='{1}']/Member",
Urn.EscapeString(databaseName), // Urn.EscapeString(databaseName),
Urn.EscapeString(dbroleName)); // Urn.EscapeString(dbroleName));
string[] fields = new string[] { "Name" }; // string[] fields = new string[] { "Name" };
OrderBy[] orderBy = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc)}; // OrderBy[] orderBy = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc)};
Request request = new Request(urn, fields, orderBy); // Request request = new Request(urn, fields, orderBy);
DataTable dt = enumerator.Process(serverConnection, request); // DataTable dt = enumerator.Process(serverConnection, request);
foreach (DataRow dr in dt.Rows) // foreach (DataRow dr in dt.Rows)
{ // {
string memberName = dr["Name"].ToString(); // string memberName = dr["Name"].ToString();
roleMembers[memberName] = new RoleMembership(true); // if (memberName != null)
} // {
} // roleMembers[memberName] = new RoleMembership(true);
} // }
// }
// }
// }
/// <summary> // /// <summary>
/// sends to server user changes related to membership // /// sends to server user changes related to membership
/// </summary> // /// </summary>
private void DbRole_SendToServerMembershipChanges(Database db, DatabaseRole dbrole) // private void DbRole_SendToServerMembershipChanges(Database db, DatabaseRole dbrole)
{ // {
var roleMembers = new HybridDictionary(); // var roleMembers = new HybridDictionary();
IDictionaryEnumerator enumerator = roleMembers.GetEnumerator(); // IDictionaryEnumerator enumerator = roleMembers.GetEnumerator();
enumerator.Reset(); // enumerator.Reset();
while (enumerator.MoveNext()) // while (enumerator.MoveNext())
{ // {
DictionaryEntry entry = enumerator.Entry; // DictionaryEntry entry = enumerator.Entry;
string memberName = entry.Key.ToString(); // string memberName = entry.Key.ToString();
RoleMembership membership = (RoleMembership) entry.Value; // RoleMembership membership = (RoleMembership) entry.Value;
// if (membership != null)
// {
// if (!membership.initiallyAMember && membership.currentlyAMember)
// {
// dbrole.AddMember(memberName);
// }
// else if (membership.initiallyAMember && !membership.currentlyAMember)
// {
// dbrole.DropMember(memberName);
// }
// }
// }
// }
if (!membership.initiallyAMember && membership.currentlyAMember) // private void InitProp(ServerConnection serverConnection, string serverName, string databaseName,
{ // string dbroleName, string dbroleUrn, bool isPropertiesMode)
dbrole.AddMember(memberName); // {
} // System.Diagnostics.Debug.Assert(serverName!=null);
else if (membership.initiallyAMember && !membership.currentlyAMember) // System.Diagnostics.Debug.Assert((databaseName!=null) && (databaseName.Trim().Length!=0));
{
dbrole.DropMember(memberName);
}
}
}
private void InitProp(ServerConnection serverConnection, string serverName, string databaseName, // // LoadSchemas();
string dbroleName, string dbroleUrn, bool isPropertiesMode) // // LoadMembership();
{
System.Diagnostics.Debug.Assert(serverName!=null);
System.Diagnostics.Debug.Assert((databaseName!=null) && (databaseName.Trim().Length!=0));
// LoadSchemas(); // if (isPropertiesMode == true)
// LoadMembership(); // {
// // initialize from enumerator in properties mode
// System.Diagnostics.Debug.Assert(dbroleName!=null);
// System.Diagnostics.Debug.Assert(dbroleName.Trim().Length !=0);
// System.Diagnostics.Debug.Assert(dbroleUrn!=null);
// System.Diagnostics.Debug.Assert(dbroleUrn.Trim().Length != 0);
if (isPropertiesMode == true) // Enumerator en = new Enumerator();
{ // Request req = new Request();
// initialize from enumerator in properties mode // req.Fields = new String [] { "Owner" };
System.Diagnostics.Debug.Assert(dbroleName!=null);
System.Diagnostics.Debug.Assert(dbroleName.Trim().Length !=0);
System.Diagnostics.Debug.Assert(dbroleUrn!=null);
System.Diagnostics.Debug.Assert(dbroleUrn.Trim().Length != 0);
Enumerator en = new Enumerator(); // if ((dbroleUrn!=null) && (dbroleUrn.Trim().Length != 0))
Request req = new Request(); // {
req.Fields = new String [] { "Owner" }; // req.Urn = dbroleUrn;
// }
// else
// {
// req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Role[@Name='" + Urn.EscapeString(dbroleName) + "]";
// }
if ((dbroleUrn!=null) && (dbroleUrn.Trim().Length != 0)) // DataTable dt = en.Process(serverConnection, req);
{ // System.Diagnostics.Debug.Assert(dt!=null);
req.Urn = dbroleUrn; // System.Diagnostics.Debug.Assert(dt.Rows.Count==1);
}
else
{
req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Role[@Name='" + Urn.EscapeString(dbroleName) + "]";
}
DataTable dt = en.Process(serverConnection, req); // if (dt.Rows.Count==0)
System.Diagnostics.Debug.Assert(dt!=null); // {
System.Diagnostics.Debug.Assert(dt.Rows.Count==1); // throw new Exception("DatabaseRoleSR.ErrorDbRoleNotFound");
// }
if (dt.Rows.Count==0) // // DataRow dr = dt.Rows[0];
{ // // this.initialOwner = Convert.ToString(dr[DatabaseRoleGeneral.ownerField],System.Globalization.CultureInfo.InvariantCulture);
throw new Exception("DatabaseRoleSR.ErrorDbRoleNotFound"); // // this.textBoxOwner.Text = this.initialOwner;
} // }
// }
// DataRow dr = dt.Rows[0]; // private void DbRole_SendDataToServer(CDataContainer dataContainer, string databaseName,
// this.initialOwner = Convert.ToString(dr[DatabaseRoleGeneral.ownerField],System.Globalization.CultureInfo.InvariantCulture); // string dbroleName, string ownerName, string initialOwner, string roleName, bool isPropertiesMode)
// this.textBoxOwner.Text = this.initialOwner; // {
} // System.Diagnostics.Debug.Assert(databaseName != null && databaseName.Trim().Length != 0, "database name is empty");
} // System.Diagnostics.Debug.Assert(dataContainer.Server != null, "server is null");
private void DbRole_SendDataToServer(CDataContainer dataContainer, string databaseName, // Database database = dataContainer.Server.Databases[databaseName];
string dbroleName, string ownerName, string initialOwner, string roleName, bool isPropertiesMode) // System.Diagnostics.Debug.Assert(database!= null, "database is null");
{
System.Diagnostics.Debug.Assert(databaseName != null && databaseName.Trim().Length != 0, "database name is empty");
System.Diagnostics.Debug.Assert(dataContainer.Server != null, "server is null");
Database database = dataContainer.Server.Databases[databaseName]; // DatabaseRole role;
System.Diagnostics.Debug.Assert(database!= null, "database is null");
DatabaseRole role; // if (isPropertiesMode == true) // in properties mode -> alter role
// {
// System.Diagnostics.Debug.Assert(dbroleName != null && dbroleName.Trim().Length != 0, "role name is empty");
if (isPropertiesMode == true) // in properties mode -> alter role // role = database.Roles[dbroleName];
{ // System.Diagnostics.Debug.Assert(role != null, "role is null");
System.Diagnostics.Debug.Assert(dbroleName != null && dbroleName.Trim().Length != 0, "role name is empty");
role = database.Roles[dbroleName]; // if (0 != String.Compare(ownerName, initialOwner, StringComparison.Ordinal))
System.Diagnostics.Debug.Assert(role != null, "role is null"); // {
// role.Owner = ownerName;
// role.Alter();
// }
// }
// else // not in properties mode -> create role
// {
// role = new DatabaseRole(database, roleName);
// if (ownerName.Length != 0)
// {
// role.Owner = ownerName;
// }
if (0 != String.Compare(ownerName, initialOwner, StringComparison.Ordinal)) // role.Create();
{ // }
role.Owner = ownerName;
role.Alter();
}
}
else // not in properties mode -> create role
{
role = new DatabaseRole(database, roleName);
if (ownerName.Length != 0)
{
role.Owner = ownerName;
}
role.Create(); // // SendToServerSchemaOwnershipChanges(database, role);
} // // SendToServerMembershipChanges(database, role);
// }
// SendToServerSchemaOwnershipChanges(database, role);
// SendToServerMembershipChanges(database, role);
}
private void DbRole_LoadSchemas(string databaseName, string dbroleName, ServerConnection serverConnection) // /// <summary>
{ // /// sends to server changes related to schema ownership
bool isPropertiesMode = false; // /// </summary>
HybridDictionary schemaOwnership; // private void DbRole_SendToServerSchemaOwnershipChanges(CDataContainer dataContainer, Database db, DatabaseRole dbrole)
schemaOwnership = new HybridDictionary(); // {
// if (dataContainer.Server == null)
// {
// return;
// }
Enumerator en = new Enumerator(); // HybridDictionary schemaOwnership = new HybridDictionary();
Request req = new Request(); // if (9 <= dataContainer.Server.Information.Version.Major)
req.Fields = new String [] { "Name", "Owner" }; // {
req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Schema"; // IDictionaryEnumerator enumerator = schemaOwnership.GetEnumerator();
// enumerator.Reset();
// while (enumerator.MoveNext())
// {
// DictionaryEntry de = enumerator.Entry;
// string schemaName = de.Key.ToString();
// SchemaOwnership ownership = (SchemaOwnership)de.Value;
DataTable dt = en.Process(serverConnection, req); // // If we are creating a new role, then no schema will have been initially owned by this role.
System.Diagnostics.Debug.Assert((dt != null) && (0 < dt.Rows.Count), "enumerator did not return schemas"); // // If we are modifying an existing role, we can only take ownership of roles. (Ownership can't
System.Diagnostics.Debug.Assert(!isPropertiesMode || (dbroleName.Length != 0), "role name is not known"); // // be renounced, it can only be positively assigned to a principal.)
// if (ownership != null && (ownership.currentlyOwned && !ownership.initiallyOwned))
// {
// Schema schema = db.Schemas[schemaName];
// schema.Owner = dbrole.Name;
// schema.Alter();
// }
// }
// }
// }
foreach (DataRow dr in dt.Rows) #endregion
{
string schemaName = Convert.ToString(dr["Name"],System.Globalization.CultureInfo.InvariantCulture);
string schemaOwner = Convert.ToString(dr["Owner"],System.Globalization.CultureInfo.InvariantCulture);
bool roleOwnsSchema =
isPropertiesMode &&
(0 == String.Compare(dbroleName, schemaOwner, StringComparison.Ordinal));
schemaOwnership[schemaName] = new SchemaOwnership(roleOwnsSchema);
}
}
/// <summary>
/// sends to server changes related to schema ownership
/// </summary>
private void DbRole_SendToServerSchemaOwnershipChanges(CDataContainer dataContainer, Database db, DatabaseRole dbrole)
{
HybridDictionary schemaOwnership = null;
if (9 <= dataContainer.Server.Information.Version.Major)
{
IDictionaryEnumerator enumerator = schemaOwnership.GetEnumerator();
enumerator.Reset();
while (enumerator.MoveNext())
{
DictionaryEntry de = enumerator.Entry;
string schemaName = de.Key.ToString();
SchemaOwnership ownership = (SchemaOwnership)de.Value;
// If we are creating a new role, then no schema will have been initially owned by this role.
// If we are modifying an existing role, we can only take ownership of roles. (Ownership can't
// be renounced, it can only be positively assigned to a principal.)
if (ownership.currentlyOwned && !ownership.initiallyOwned)
{
Schema schema = db.Schemas[schemaName];
schema.Owner = dbrole.Name;
schema.Alter();
}
}
}
}
#endregion
} }
} }

View File

@@ -26,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
/// <param name="context"></param> /// <param name="context"></param>
public UserActions( public UserActions(
CDataContainer context, CDataContainer context,
UserInfo user, UserInfo? user,
ConfigAction configAction) ConfigAction configAction)
{ {
this.DataContainer = context; this.DataContainer = context;

View File

@@ -114,7 +114,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
} }
else else
{ {
this.name = userInfo.UserName; this.name = userInfo.Name;
this.mappedLoginName = userInfo.LoginName; this.mappedLoginName = userInfo.LoginName;
this.defaultSchemaName = userInfo.DefaultSchema; this.defaultSchemaName = userInfo.DefaultSchema;
this.password = DatabaseUtils.GetReadOnlySecureString(userInfo.Password); this.password = DatabaseUtils.GetReadOnlySecureString(userInfo.Password);

View File

@@ -150,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
/// database and user dialogs as default init fields so that queries are not sent again and again. /// database and user dialogs as default init fields so that queries are not sent again and again.
/// </summary> /// </summary>
/// <param name="connectedServer">server on which languages will be enumerated</param> /// <param name="connectedServer">server on which languages will be enumerated</param>
public static void SetLanguageDefaultInitFieldsForDefaultLanguages(Server connectedServer) public static void SetLanguageDefaultInitFieldsForDefaultLanguages(Server? connectedServer)
{ {
string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" }; string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" };
connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded); connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded);

View File

@@ -51,16 +51,12 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
{ {
return new UserInfo() return new UserInfo()
{ {
Type = DatabaseUserType.UserWithLogin, Type = DatabaseUserType.WithLogin,
UserName = "TestUserName_" + new Random().NextInt64(10000000,90000000).ToString(), Name = "TestUserName_" + new Random().NextInt64(10000000,90000000).ToString(),
LoginName = loginName, LoginName = loginName,
Password = "placeholder", Password = "placeholder",
DefaultSchema = "dbo", DefaultSchema = "dbo",
OwnedSchemas = new string[] { "dbo" }, OwnedSchemas = new string[] { "dbo" }
isEnabled = false,
isAAD = false,
ExtendedProperties = null,
SecurablePermissions = null
}; };
} }

View File

@@ -49,7 +49,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
var userParams = new CreateUserParams var userParams = new CreateUserParams
{ {
OwnerUri = connectionResult.ConnectionInfo.OwnerUri, ContextId = connectionResult.ConnectionInfo.OwnerUri,
User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName) User = SecurityTestUtils.GetTestUserInfo(loginParams.Login.LoginName)
}; };
@@ -62,7 +62,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
// verify the result // verify the result
createUserContext.Verify(x => x.SendResult(It.Is<CreateUserResult> createUserContext.Verify(x => x.SendResult(It.Is<CreateUserResult>
(p => p.Success && p.User.UserName != string.Empty))); (p => p.Success && p.User.Name != string.Empty)));
} }
} }
} }