// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Xml; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Dmf; using Microsoft.SqlServer.Management.Sdk.Sfc; using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.Management; using Microsoft.SqlTools.ServiceLayer.Security.Contracts; using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.Security { /// /// Main class for Security Service functionality /// public sealed class SecurityService : IDisposable { private bool disposed; private ConnectionService? connectionService; private static readonly Lazy instance = new Lazy(() => new SecurityService()); private Dictionary contextIdToConnectionUriMap = new Dictionary(); /// /// Construct a new SecurityService instance with default parameters /// public SecurityService() { } /// /// Gets the singleton instance object /// public static SecurityService Instance { get { return instance.Value; } } /// /// Internal for testing purposes only /// internal ConnectionService ConnectionServiceInstance { get { connectionService ??= ConnectionService.Instance; return connectionService; } set { connectionService = value; } } /// /// Service host object for sending/receiving requests/events. /// Internal for testing purposes. /// internal IProtocolEndpoint? ServiceHost { get; set; } /// /// Initializes the Security Service instance /// public void InitializeService(ServiceHost serviceHost) { this.ServiceHost = serviceHost; // Credential request handlers this.ServiceHost.SetRequestHandler(CreateCredentialRequest.Type, HandleCreateCredentialRequest, true); this.ServiceHost.SetRequestHandler(UpdateCredentialRequest.Type, HandleUpdateCredentialRequest, true); this.ServiceHost.SetRequestHandler(DeleteCredentialRequest.Type, HandleDeleteCredentialRequest, true); this.ServiceHost.SetRequestHandler(GetCredentialsRequest.Type, HandleGetCredentialsRequest, true); // 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); this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, HandleCreateUserRequest, true); this.ServiceHost.SetRequestHandler(DeleteUserRequest.Type, HandleDeleteUserRequest, true); } #region "Login Handlers" /// /// Handle request to create a login /// internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext 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, parameters.Login); 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 } } prototype.ApplyGeneralChanges(dataContainer.Server); // 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) { newPrototype.ServerRoles.SetMember(role, true); } newPrototype.ApplyServerRoleChanges(dataContainer.Server); await requestContext.SendResult(new object()); } /// /// Handle request to delete a credential /// internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext requestContext) { ConnectionInfo 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.Name]; dataContainer.SqlDialogSubject = login; DoDropObject(dataContainer); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = string.Empty }); } internal async Task HandleUpdateLoginRequest(UpdateLoginParams parameters, RequestContext 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; if (0 != String.Compare(login.DefaultLanguage, SR.DefaultLanguagePlaceholder, StringComparison.Ordinal)) { prototype.DefaultLanguage = login.DefaultLanguage.Split(" - ")[1]; } 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 prototype.ServerRoles.ServerRoleNames) { prototype.ServerRoles.SetMember(role, false); } 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 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(); // TODO cache databases and languages 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; } var languageOptions = GetDefaultLanguageOptions(dataContainer); var languageOptionsList = languageOptions.Select(FormatLanguageDisplay).ToList(); if (parameters.IsNewObject) { languageOptionsList.Insert(0, SR.DefaultLanguagePlaceholder); } string[] languages = languageOptionsList.ToArray(); LoginPrototype prototype = parameters.IsNewObject ? new LoginPrototype(dataContainer.Server) : new LoginPrototype(dataContainer.Server, dataContainer.Server.Logins[parameters.Name]); List loginServerRoles = new List(); 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 = FormatLanguageDisplay(languageOptions.FirstOrDefault(o => o?.Language.Name == prototype.DefaultLanguage || o?.Language.Alias == prototype.DefaultLanguage, null)), 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 requestContext) { await requestContext.SendResult(new object()); } private string FormatLanguageDisplay(LanguageDisplay? l) { if (l == null) return null; return string.Format("{0} - {1}", l.Language.Alias, l.Language.Name); } #endregion #region "User Handlers" internal Task> ConfigureUser( string? ownerUri, UserInfo? user, ConfigAction configAction, RunType runType) { return Task>.Run(() => { try { ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); if (connInfo == null) { throw new ArgumentException("Invalid connection URI '{0}'", ownerUri); } var serverConnection = ConnectionService.OpenServerConnection(connInfo, "DataContainer"); var connectionInfoWithConnection = new SqlConnectionInfoWithConnection(); connectionInfoWithConnection.ServerConnection = serverConnection; string urn = string.Format(System.Globalization.CultureInfo.InvariantCulture, "Server/Database[@Name='{0}']", Urn.EscapeString(serverConnection.DatabaseName)); ActionContext context = new ActionContext(serverConnection, "new_user", urn); DataContainerXmlGenerator containerXml = new DataContainerXmlGenerator(context); containerXml.AddProperty("itemtype", "User"); XmlDocument xmlDoc = containerXml.GenerateXmlDocument(); bool objectExists = configAction != ConfigAction.Create; CDataContainer dataContainer = CDataContainer.CreateDataContainer(connectionInfoWithConnection, xmlDoc); using (var actions = new UserActions(dataContainer, user, configAction)) { var executionHandler = new ExecutonHandler(actions); executionHandler.RunNow(runType, this); } return new Tuple(true, string.Empty); } catch (Exception ex) { return new Tuple(false, ex.ToString()); } }); } private Dictionary LoadSchemas(string databaseName, string dbroleName, ServerConnection serverConnection) { bool isPropertiesMode = false; Dictionary schemaOwnership = new Dictionary(); 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(); 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 items = new List(); 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(); } /// /// Handle request to initialize user view /// internal async Task HandleInitializeUserViewRequest(InitializeUserViewParams parameters, RequestContext 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); } /// /// Handle request to create a user /// internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext requestContext) { 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, ConfigAction.Create, RunType.RunNow); await requestContext.SendResult(new CreateUserResult() { User = parameters.User, Success = result.Item1, ErrorMessage = result.Item2 }); } /// /// Handle request to delete a user /// internal async Task HandleDeleteUserRequest(DeleteUserParams parameters, RequestContext 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 IList GetDefaultLanguageOptions(CDataContainer dataContainer) { // this.defaultLanguageComboBox.Items.Clear(); // this.defaultLanguageComboBox.Items.Add(defaultLanguagePlaceholder); // sort the languages alphabetically by alias SortedList sortedLanguages = new SortedList(Comparer.Default); LanguageUtils.SetLanguageDefaultInitFieldsForDefaultLanguages(dataContainer.Server); if (dataContainer.Server != null && dataContainer.Server.Languages != null) { foreach (Language language in dataContainer.Server.Languages) { LanguageDisplay listValue = new LanguageDisplay(language); sortedLanguages.Add(language.Alias, listValue); } } IList res = new List(); foreach (LanguageDisplay ld in sortedLanguages.Values) { res.Add(ld); } return res; } // code needs to be ported into the useraction class // public void UserMemberships_OnRunNow(object sender, CDataContainer dataContainer) // { // UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype; // //In case the UserGeneral/OwnedSchemas pages are loaded, // //those will takes care of applying membership changes also. // //Hence, we only need to apply changes in this method when those are not loaded. // if (!currentPrototype.IsRoleMembershipChangesApplied) // { // //base.OnRunNow(sender); // User user = currentPrototype.ApplyChanges(); // //this.ExecutionMode = ExecutionMode.Success; // dataContainer.ObjectName = currentPrototype.Name; // dataContainer.SqlDialogSubject = user; // } // //setting back to original after changes are applied // currentPrototype.IsRoleMembershipChangesApplied = false; // } // /// // /// implementation of OnPanelRunNow // /// // /// // public void UserOwnedSchemas_OnRunNow(object sender, CDataContainer dataContainer) // { // UserPrototype currentPrototype = UserPrototypeFactory.GetInstance(dataContainer).CurrentPrototype; // //In case the UserGeneral/Membership pages are loaded, // //those will takes care of applying schema ownership changes also. // //Hence, we only need to apply changes in this method when those are not loaded. // if (!currentPrototype.IsSchemaOwnershipChangesApplied) // { // //base.OnRunNow(sender); // User user = currentPrototype.ApplyChanges(); // //this.ExecutionMode = ExecutionMode.Success; // dataContainer.ObjectName = currentPrototype.Name; // dataContainer.SqlDialogSubject = user; // } // //setting back to original after changes are applied // currentPrototype.IsSchemaOwnershipChangesApplied = false; // } // how to populate defaults from prototype, will delete once refactored // private void InitializeValuesInUiControls() // { // IUserPrototypeWithDefaultLanguage defaultLanguagePrototype = this.currentUserPrototype // as IUserPrototypeWithDefaultLanguage; // if (defaultLanguagePrototype != null // && defaultLanguagePrototype.IsDefaultLanguageSupported) // { // string defaultLanguageAlias = defaultLanguagePrototype.DefaultLanguageAlias; // //If engine returns default language as empty or null, that means the default language of // //database will be used. // //Default language is not applicable for users inside an uncontained authentication. // if (string.IsNullOrEmpty(defaultLanguageAlias) // && (this.DataContainer.Server.GetSmoObject(this.parentDbUrn) as Database).ContainmentType != ContainmentType.None) // { // defaultLanguageAlias = this.defaultLanguagePlaceholder; // } // this.defaultLanguageComboBox.Text = defaultLanguageAlias; // } // IUserPrototypeWithDefaultSchema defaultSchemaPrototype = this.currentUserPrototype // as IUserPrototypeWithDefaultSchema; // if (defaultSchemaPrototype != null // && defaultSchemaPrototype.IsDefaultSchemaSupported) // { // this.defaultSchemaTextBox.Text = defaultSchemaPrototype.DefaultSchema; // } // IUserPrototypeWithPassword userWithPwdPrototype = this.currentUserPrototype // as IUserPrototypeWithPassword; // if (userWithPwdPrototype != null // && !this.DataContainer.IsNewObject) // { // this.passwordTextBox.Text = FAKE_PASSWORD; // this.confirmPwdTextBox.Text = FAKE_PASSWORD; // } // } #endregion #region "Credential Handlers" /// /// Handle request to create a credential /// internal async Task HandleCreateCredentialRequest(CreateCredentialParams parameters, RequestContext requestContext) { var result = await ConfigureCredential(parameters.OwnerUri, parameters.Credential, ConfigAction.Create, RunType.RunNow); await requestContext.SendResult(new CredentialResult() { Credential = parameters.Credential, Success = result.Item1, ErrorMessage = result.Item2 }); } /// /// Handle request to update a credential /// internal async Task HandleUpdateCredentialRequest(UpdateCredentialParams parameters, RequestContext requestContext) { var result = await ConfigureCredential(parameters.OwnerUri, parameters.Credential, ConfigAction.Update, RunType.RunNow); await requestContext.SendResult(new CredentialResult() { Credential = parameters.Credential, Success = result.Item1, ErrorMessage = result.Item2 }); } /// /// Handle request to delete a credential /// internal async Task HandleDeleteCredentialRequest(DeleteCredentialParams parameters, RequestContext requestContext) { var result = await ConfigureCredential(parameters.OwnerUri, parameters.Credential, ConfigAction.Drop, RunType.RunNow); await requestContext.SendResult(new ResultStatus() { Success = result.Item1, ErrorMessage = result.Item2 }); } /// /// Handle request to get all credentials /// internal async Task HandleGetCredentialsRequest(GetCredentialsParams parameters, RequestContext requestContext) { var result = new GetCredentialsResult(); try { ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); var credentials = dataContainer.Server?.Credentials; int credentialsCount = credentials != null ? credentials.Count : 0; CredentialInfo[] credentialsInfos = new CredentialInfo[credentialsCount]; if (credentials != null) { for (int i = 0; i < credentialsCount; ++i) { credentialsInfos[i] = new CredentialInfo(); credentialsInfos[i].Name = credentials[i].Name; credentialsInfos[i].Identity = credentials[i].Identity; credentialsInfos[i].Id = credentials[i].ID; credentialsInfos[i].DateLastModified = credentials[i].DateLastModified; credentialsInfos[i].CreateDate = credentials[i].CreateDate; credentialsInfos[i].ProviderName = credentials[i].ProviderName; } } result.Credentials = credentialsInfos; result.Success = true; } catch (Exception ex) { result.Success = false; result.ErrorMessage = ex.ToString(); } await requestContext.SendResult(result); } /// /// Disposes the service /// public void Dispose() { if (!disposed) { disposed = true; } } #endregion #region "Helpers" internal Task> ConfigureCredential( string ownerUri, CredentialInfo credential, ConfigAction configAction, RunType runType) { return Task>.Run(() => { try { ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); using (CredentialActions actions = new CredentialActions(dataContainer, credential, configAction)) { var executionHandler = new ExecutonHandler(actions); executionHandler.RunNow(runType, this); } return new Tuple(true, string.Empty); } catch (Exception ex) { return new Tuple(false, ex.ToString()); } }); } /// /// this is the main method that is called by DropAllObjects for every object /// in the grid /// /// 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 subjectExecutionMode = executionMode; //For Azure the ExecutionManager is different depending on which ExecutionManager //used - one at the Server level and one at the Database level. So to ensure we //don't use the wrong execution mode we need to set the mode for both (for on-prem //this will essentially be a no-op) SqlSmoObject sqlDialogSubject = null; try { sqlDialogSubject = dataContainer.SqlDialogSubject; } catch (System.Exception) { //We may not have a valid dialog subject here (such as if the object hasn't been created yet) //so in that case we'll just ignore it as that's a normal scenario. } if (sqlDialogSubject != null) { subjectExecutionMode = sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes; } Urn objUrn = sqlDialogSubject?.Urn; System.Diagnostics.Debug.Assert(objUrn != null); SfcObjectQuery objectQuery = new SfcObjectQuery(dataContainer.Server); IDroppable droppableObj = null; string[] 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"); droppableObj = obj as IDroppable; } // For Azure databases, the SfcObjectQuery executions above may have overwritten our desired execution mode, so restore it dataContainer.Server.ConnectionContext.SqlExecutionModes = executionMode; if (sqlDialogSubject != null) { sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes = subjectExecutionMode; } if (droppableObj == null) { string objectName = objUrn.GetAttribute("Name"); objectName ??= string.Empty; throw new Microsoft.SqlServer.Management.Smo.MissingObjectException("DropObjectsSR.ObjectDoesNotExist(objUrn.Type, objectName)"); } //special case database drop - see if we need to delete backup and restore history SpecialPreDropActionsForObject(dataContainer, droppableObj, deleteBackupRestoreOrDisableAuditSpecOrDisableAudit: false, dropOpenConnections: false); droppableObj.Drop(); //special case Resource Governor reconfigure - for pool, external pool, group Drop(), we need to issue SpecialPostDropActionsForObject(dataContainer, droppableObj); } private void SpecialPreDropActionsForObject(CDataContainer dataContainer, IDroppable droppableObj, 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; if (deleteBackupRestoreOrDisableAuditSpecOrDisableAudit) { if (db != null) { dataContainer.Server.DeleteBackupHistory(db.Name); } else { // else droppable object should be a server or database audit specification ServerAuditSpecification sas = droppableObj as ServerAuditSpecification; if (sas != null) { sas.Disable(); } else { DatabaseAuditSpecification das = droppableObj as DatabaseAuditSpecification; if (das != null) { das.Disable(); } else { Audit aud = droppableObj as Audit; if (aud != null) { aud.Disable(); } } } } } // special case database drop - drop existing connections to the database other than this one if (dropOpenConnections) { if (db?.ActiveConnections > 0) { // force the database to be single user db.DatabaseOptions.UserAccess = DatabaseUserAccess.Single; db.Alter(TerminationClause.RollbackTransactionsImmediately); } } } 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) { Policy policyToDrop = (Policy)droppableObj; if (!string.IsNullOrEmpty(policyToDrop.ObjectSet)) { ObjectSet objectSet = policyToDrop.Parent.ObjectSets[policyToDrop.ObjectSet]; objectSet.Drop(); } } ResourcePool rp = droppableObj as ResourcePool; ExternalResourcePool erp = droppableObj as ExternalResourcePool; WorkloadGroup wg = droppableObj as WorkloadGroup; if (null != rp || null != erp || null != wg) { // Alter() Resource Governor to reconfigure dataContainer.Server.ResourceGovernor.Alter(); } } #endregion // "Helpers" // some potentially useful code for working with server & db roles to be refactored later #region "Roles" private class SchemaOwnership { public bool initiallyOwned; public bool currentlyOwned; public SchemaOwnership(bool initiallyOwned) { this.initiallyOwned = initiallyOwned; this.currentlyOwned = initiallyOwned; } } private class RoleMembership { public bool initiallyAMember; public bool currentlyAMember; public RoleMembership(bool initiallyAMember) { this.initiallyAMember = initiallyAMember; this.currentlyAMember = initiallyAMember; } public RoleMembership(bool initiallyAMember, bool currentlyAMember) { this.initiallyAMember = initiallyAMember; this.currentlyAMember = currentlyAMember; } } // private void DbRole_LoadMembership(string databaseName, string dbroleName, ServerConnection serverConnection) // { // var roleMembers = new HybridDictionary(); // bool isPropertiesMode = false; // if (isPropertiesMode) // { // Enumerator enumerator = new Enumerator(); // Urn urn = String.Format(System.Globalization.CultureInfo.InvariantCulture, // "Server/Database[@Name='{0}']/Role[@Name='{1}']/Member", // Urn.EscapeString(databaseName), // Urn.EscapeString(dbroleName)); // string[] fields = new string[] { "Name" }; // OrderBy[] orderBy = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc)}; // Request request = new Request(urn, fields, orderBy); // DataTable dt = enumerator.Process(serverConnection, request); // foreach (DataRow dr in dt.Rows) // { // string memberName = dr["Name"].ToString(); // if (memberName != null) // { // roleMembers[memberName] = new RoleMembership(true); // } // } // } // } // /// // /// sends to server user changes related to membership // /// // private void DbRole_SendToServerMembershipChanges(Database db, DatabaseRole dbrole) // { // var roleMembers = new HybridDictionary(); // IDictionaryEnumerator enumerator = roleMembers.GetEnumerator(); // enumerator.Reset(); // while (enumerator.MoveNext()) // { // DictionaryEntry entry = enumerator.Entry; // string memberName = entry.Key.ToString(); // 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); // } // } // } // } // private void InitProp(ServerConnection serverConnection, string serverName, string databaseName, // string dbroleName, string dbroleUrn, bool isPropertiesMode) // { // System.Diagnostics.Debug.Assert(serverName!=null); // System.Diagnostics.Debug.Assert((databaseName!=null) && (databaseName.Trim().Length!=0)); // // LoadSchemas(); // // LoadMembership(); // if (isPropertiesMode == true) // { // // 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); // Enumerator en = new Enumerator(); // Request req = new Request(); // req.Fields = new String [] { "Owner" }; // if ((dbroleUrn!=null) && (dbroleUrn.Trim().Length != 0)) // { // req.Urn = dbroleUrn; // } // else // { // req.Urn = "Server/Database[@Name='" + Urn.EscapeString(databaseName) + "']/Role[@Name='" + Urn.EscapeString(dbroleName) + "]"; // } // DataTable dt = en.Process(serverConnection, req); // System.Diagnostics.Debug.Assert(dt!=null); // System.Diagnostics.Debug.Assert(dt.Rows.Count==1); // if (dt.Rows.Count==0) // { // throw new Exception("DatabaseRoleSR.ErrorDbRoleNotFound"); // } // // DataRow dr = dt.Rows[0]; // // this.initialOwner = Convert.ToString(dr[DatabaseRoleGeneral.ownerField],System.Globalization.CultureInfo.InvariantCulture); // // this.textBoxOwner.Text = this.initialOwner; // } // } // private void DbRole_SendDataToServer(CDataContainer dataContainer, string databaseName, // string dbroleName, string ownerName, string initialOwner, string roleName, bool isPropertiesMode) // { // 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]; // 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"); // role = database.Roles[dbroleName]; // System.Diagnostics.Debug.Assert(role != null, "role is null"); // if (0 != String.Compare(ownerName, initialOwner, StringComparison.Ordinal)) // { // 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); // } // /// // /// sends to server changes related to schema ownership // /// // private void DbRole_SendToServerSchemaOwnershipChanges(CDataContainer dataContainer, Database db, DatabaseRole dbrole) // { // if (dataContainer.Server == null) // { // return; // } // HybridDictionary schemaOwnership = new HybridDictionary(); // 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 != null && (ownership.currentlyOwned && !ownership.initiallyOwned)) // { // Schema schema = db.Schemas[schemaName]; // schema.Owner = dbrole.Name; // schema.Alter(); // } // } // } // } #endregion } }