// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.Collections; using System.Collections.Specialized; using System.Data; 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()); /// /// 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(DeleteLoginRequest.Type, HandleDeleteLoginRequest, true); // User request handlers this.ServiceHost.SetRequestHandler(CreateUserRequest.Type, HandleCreateUserRequest, true); } #region "Login Handlers" /// /// Handle request to create a login /// internal async Task HandleCreateLoginRequest(CreateLoginParams parameters, RequestContext requestContext) { ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); // if (connInfo == null) // { // // raise an error // } 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); await requestContext.SendResult(new CreateLoginResult() { Login = parameters.Login, Success = true, ErrorMessage = string.Empty }); } /// /// Handle request to delete a credential /// internal async Task HandleDeleteLoginRequest(DeleteLoginParams parameters, RequestContext requestContext) { ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); // if (connInfo == null) // { // // raise an error // } CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); Login login = dataContainer.Server.Logins[parameters.LoginName]; dataContainer.SqlDialogSubject = login; DoDropObject(dataContainer); await requestContext.SendResult(new ResultStatus() { Success = true, ErrorMessage = string.Empty }); } #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()); } }); } /// /// Handle request to create a user /// internal async Task HandleCreateUserRequest(CreateUserParams parameters, RequestContext requestContext) { var result = await ConfigureUser(parameters.OwnerUri, parameters.User, ConfigAction.Create, RunType.RunNow); await requestContext.SendResult(new CreateUserResult() { User = parameters.User, Success = result.Item1, ErrorMessage = result.Item2 }); } private void 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); foreach (Language language in dataContainer.Server.Languages) { LanguageDisplay listValue = new LanguageDisplay(language); sortedLanguages.Add(language.Alias, listValue); } // add the language display objects to the combo box foreach (LanguageDisplay languageDisplay in sortedLanguages.Values) { //this.defaultLanguageComboBox.Items.Add(languageDisplay); } } // 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() // { // 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 // 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; // } // } // 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" /// /// 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.Count; CredentialInfo[] credentialsInfos = new CredentialInfo[credentialsCount]; 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) { 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) { 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 (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(); 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.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); } private void DbRole_LoadSchemas(string databaseName, string dbroleName, ServerConnection serverConnection) { bool isPropertiesMode = false; HybridDictionary schemaOwnership; schemaOwnership = new HybridDictionary(); 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)); schemaOwnership[schemaName] = new SchemaOwnership(roleOwnsSchema); } } /// /// sends to server changes related to schema ownership /// 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 } }