diff --git a/src/Microsoft.SqlTools.ServiceLayer/Management/Common/Utils.cs b/src/Microsoft.SqlTools.ServiceLayer/Management/Common/Utils.cs
index ab2f5c5c..1962bfc5 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Management/Common/Utils.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Management/Common/Utils.cs
@@ -287,6 +287,76 @@ namespace Microsoft.SqlTools.ServiceLayer.Management
{
return !string.IsNullOrEmpty(serverName) && serverName.StartsWith("asazure://", StringComparison.OrdinalIgnoreCase);
}
+
+ public static bool IsSql11OrLater(ServerVersion version)
+ {
+ return IsSql11OrLater(version.Major);
+ }
+
+ public static bool IsSql11OrLater(int versionMajor)
+ {
+ return (versionMajor >= 11);
+ }
+
+ public static bool IsSql12OrLater(ServerVersion version)
+ {
+ return IsSql12OrLater(version.Major);
+ }
+
+ public static bool IsSql12OrLater(int versionMajor)
+ {
+ return (versionMajor >= 12);
+ }
+
+ public static bool IsSql13OrLater(ServerVersion version)
+ {
+ return IsSql13OrLater(version.Major);
+ }
+
+ public static bool IsSql13OrLater(int versionMajor)
+ {
+ return (versionMajor >= 13);
+ }
+
+ public static bool IsSql14OrLater(ServerVersion version)
+ {
+ return IsSql14OrLater(version.Major);
+ }
+
+ public static bool IsSql14OrLater(int versionMajor)
+ {
+ return (versionMajor >= 14);
+ }
+
+ public static bool IsSql15OrLater(ServerVersion version)
+ {
+ return IsSql15OrLater(version.Major);
+ }
+
+ public static bool IsSql15OrLater(int versionMajor)
+ {
+ return (versionMajor >= 15);
+ }
+
+ public static bool IsSql16OrLater(ServerVersion version)
+ {
+ return IsSql16OrLater(version.Major);
+ }
+
+ public static bool IsSql16OrLater(int versionMajor)
+ {
+ return (versionMajor >= 16);
+ }
+
+ public static bool IsYukonOrAbove(SqlServer.Management.Smo.Server server)
+ {
+ return server.Version.Major >= 9;
+ }
+
+ public static bool IsBelowYukon(SqlServer.Management.Smo.Server server)
+ {
+ return server.Version.Major < 9;
+ }
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs
new file mode 100644
index 00000000..35544599
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginInfo.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum LoginType
+ {
+ [EnumMember(Value = "Windows")]
+ Windows,
+ [EnumMember(Value = "Sql")]
+ Sql,
+ [EnumMember(Value = "AAD")]
+ AzureActiveDirectory
+ }
+
+ ///
+ /// a class for storing various login properties
+ ///
+ public class LoginInfo
+ {
+ public string LoginName { get; set; }
+
+ public LoginType LoginType { get; set; }
+
+ public string CertificateName { get; set; }
+
+ public string AsymmetricKeyName { get; set; }
+
+ public bool WindowsGrantAccess { get; set; }
+
+ public bool MustChange { get; set; }
+
+ public bool IsDisabled { get; set; }
+
+ public bool IsLockedOut { get; set; }
+
+ public bool EnforcePolicy { get; set; }
+
+ public bool EnforceExpiration { get; set; }
+
+ public bool WindowsAuthSupported { get; set; }
+
+ public string Password { get; set; }
+
+ public string OldPassword { get; set; }
+
+ public string DefaultLanguage { get; set; }
+
+ public string DefaultDatabase { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs
new file mode 100644
index 00000000..6a2eb8db
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/LoginRequest.cs
@@ -0,0 +1,66 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using Microsoft.SqlTools.Hosting.Protocol.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using Microsoft.SqlTools.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
+{
+ ///
+ /// Create Login parameters
+ ///
+ public class CreateLoginParams : GeneralRequestDetails
+ {
+ public string OwnerUri { get; set; }
+
+ public LoginInfo Login { get; set; }
+ }
+
+ ///
+ /// Create Login result
+ ///
+ public class CreateLoginResult : ResultStatus
+ {
+ public LoginInfo Login { get; set; }
+ }
+
+
+ ///
+ /// Create Login request type
+ ///
+ public class CreateLoginRequest
+ {
+ ///
+ /// Request definition
+ ///
+ public static readonly
+ RequestType Type =
+ RequestType.Create("security/createlogin");
+ }
+
+ ///
+ /// Delete Login params
+ ///
+ public class DeleteLoginParams : GeneralRequestDetails
+ {
+ public string OwnerUri { get; set; }
+
+ public string LoginName { get; set; }
+ }
+
+ ///
+ /// Delete Login request type
+ ///
+ public class DeleteLoginRequest
+ {
+ ///
+ /// Request definition
+ ///
+ public static readonly
+ RequestType Type =
+ RequestType.Create("security/deletelogin");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs
new file mode 100644
index 00000000..24a95813
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/Contracts/UserInfo.cs
@@ -0,0 +1,124 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Runtime.Serialization;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+
+namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
+{
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum DatabaseUserType
+ {
+ [EnumMember(Value = "UserWithLogin")]
+ UserWithLogin,
+ [EnumMember(Value = "UserWithoutLogin")]
+ UserWithoutLogin
+ }
+
+ 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; }
+ }
+
+ ///
+ /// a class for storing various user properties
+ ///
+ public class UserInfo
+ {
+ DatabaseUserType? Type { get; set; }
+
+ public string LoginName { get; set; }
+
+ public string Password { get; set; }
+
+ public string DefaultSchema { get; set; }
+
+ public string[] OwnedSchemas { get; set; }
+
+ public bool isEnabled { get; set; }
+
+ public bool isAAD { get; set; }
+
+ public ExtendedProperty[] ExtendedProperties { get; set; }
+
+ public SecurablePermissions[] SecurablePermissions { 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
\ No newline at end of file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/CreateLoginData.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/CreateLoginData.cs
new file mode 100644
index 00000000..e6a39582
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/CreateLoginData.cs
@@ -0,0 +1,2473 @@
+//
+// 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.Specialized;
+using System.Data;
+
+using Microsoft.SqlServer.Management.Common;
+using Microsoft.SqlServer.Management.Sdk.Sfc;
+using Microsoft.SqlServer.Management.Smo;
+using Microsoft.SqlTools.ServiceLayer.Management;
+using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
+
+namespace Microsoft.SqlTools.ServiceLayer.Security
+{
+ ///
+ /// Encapsulates database roles, access and default schema
+ ///
+ internal class DatabaseRoles : ICloneable
+ {
+ private Microsoft.SqlServer.Management.Smo.Server server;
+
+ private string databaseName;
+ private string loginName;
+ private bool permit;
+ private bool isAccessible;
+ private string defaultSchema;
+ private string userName;
+ private HybridDictionary databaseRoles;
+ private StringCollection schemaNames = null;
+ private bool loginExists;
+ private bool roleMembershipChanged;
+ private bool guestStatus;
+
+ private bool initializedDatabaseAccess;
+ private bool initializedRoleMembership;
+
+ ///
+ /// The name of the database
+ ///
+ public string DatabaseName
+ {
+ get
+ {
+ return databaseName;
+ }
+ }
+
+ ///
+ /// The default schema (user name) for the login in the database
+ ///
+ public string DefaultSchema
+ {
+ get
+ {
+ if (!this.initializedDatabaseAccess)
+ {
+ this.InitializeDatabaseAccess();
+ }
+
+ return defaultSchema;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initializedDatabaseAccess, "unexpected property set for unitialized DatabaseRoles");
+ defaultSchema = value;
+ }
+ }
+
+ ///
+ /// Whether the login has access to the database
+ ///
+ public bool PermitDatabaseAccess
+ {
+ get
+ {
+ if (!this.initializedDatabaseAccess)
+ {
+ this.InitializeDatabaseAccess();
+ }
+
+ return permit;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initializedDatabaseAccess, "unexpected property set for unitialized DatabaseRoles");
+ permit = value;
+ }
+ }
+
+ ///
+ /// Whether the database is accessible
+ ///
+ public bool DatabaseIsAccessible
+ {
+ get
+ {
+ if (!this.initializedDatabaseAccess)
+ {
+ this.InitializeDatabaseAccess();
+ }
+
+ return this.isAccessible;
+ }
+ }
+
+ ///
+ /// Guest account enables or disabled in the database
+ ///
+ public bool GuestStatus
+ {
+ get
+ {
+ if (!this.initializedRoleMembership)
+ {
+ this.InitializeRoleMembership();
+ }
+ return this.guestStatus;
+
+ }
+ }
+
+ ///
+ /// The names of all the database roles defined for the database
+ ///
+ public string[] DatabaseRoleNames
+ {
+ get
+ {
+ if (!this.initializedRoleMembership)
+ {
+ this.InitializeRoleMembership();
+ }
+
+ SortedList sortedRoles = new SortedList(databaseRoles, Comparer.Default);
+ string[] result = new string[sortedRoles.Count];
+
+ sortedRoles.Keys.CopyTo(result, 0);
+
+ return result;
+ }
+ }
+
+ ///
+ /// The names of the schemas in the database sorted alphabetically
+ ///
+ public StringCollection SchemaNames
+ {
+ get
+ {
+ if (this.schemaNames == null)
+ {
+ this.schemaNames = new StringCollection();
+ Enumerator enumerator = new Enumerator();
+ Urn urn = new Urn(String.Format(System.Globalization.CultureInfo.InvariantCulture, "Server/Database[@Name='{0}']/Schema", Urn.EscapeString(databaseName)));
+ string[] fields = new string[] { "Name"};
+ OrderBy[] orderBy = new OrderBy[] { new OrderBy("Name", OrderBy.Direction.Asc)};
+ Request request = new Request(urn, fields, orderBy);
+ DataTable enumeratorResults = enumerator.Process(this.server.ConnectionContext, request);
+
+ System.Diagnostics.Debug.Assert(enumeratorResults.Rows.Count != 0, "couldn't enumerate schemas in the database");
+
+ for (int i = 0; i < enumeratorResults.Rows.Count; ++i)
+ {
+ this.schemaNames.Add(enumeratorResults.Rows[i]["Name"].ToString());
+ }
+ }
+
+ return this.schemaNames;
+ }
+ }
+
+ ///
+ /// Has the user changed membership in any role?
+ ///
+ public bool RoleMembershipChanged
+ {
+ get
+ {
+ return this.roleMembershipChanged;
+ }
+ }
+ ///
+ /// Whether the login already exists
+ ///
+ private bool LoginExists
+ {
+ get
+ {
+ return this.loginExists;
+ }
+ }
+
+
+ ///
+ /// constructor
+ ///
+ private DatabaseRoles()
+ {
+ this.DefaultInitialize();
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The server we are working with
+ /// The name of the database for which we are encapsulating data
+ public DatabaseRoles(Microsoft.SqlServer.Management.Smo.Server server, string databaseName)
+ {
+ this.DefaultInitialize();
+
+ this.server = server;
+ this.databaseName = databaseName;
+
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The server we are working with
+ /// The name of the database for which we are encapsulating data
+ /// The name of the login we are modifying
+ public DatabaseRoles(Microsoft.SqlServer.Management.Smo.Server server, string databaseName, string loginName)
+ {
+ this.DefaultInitialize();
+
+ this.server = server;
+ this.loginName = loginName;
+ this.databaseName = databaseName;
+ this.loginExists = true;
+
+ }
+
+
+ ///
+ /// Gets whether the associated login is a member of a particular role
+ ///
+ /// The name of the database role
+ /// True if the login is a member of the role, false otherwise
+ public bool IsMember(string databaseRoleName)
+ {
+ if (!this.initializedRoleMembership)
+ {
+ this.InitializeRoleMembership();
+ }
+
+ System.Diagnostics.Debug.Assert(databaseRoles.Contains(databaseRoleName), "databaseRoleName is not the name of a role in the database");
+
+ bool isPublic = (0 == String.Compare(databaseRoleName, "public", StringComparison.Ordinal));
+ bool result = isPublic || (bool) databaseRoles[databaseRoleName];
+
+ return result;
+ }
+
+ ///
+ /// Sets whether the associated login is a member of a particular role
+ ///
+ /// The name of the database role
+ /// Whether the login is a member of the role
+ public void SetMember(string databaseRoleName, bool isMember)
+ {
+ System.Diagnostics.Debug.Assert(databaseRoles.Contains(databaseRoleName), "databaseRoleName is not the name of a role in the database");
+
+ if (0 != String.Compare(databaseRoleName, "public", StringComparison.Ordinal))
+ {
+ databaseRoles[databaseRoleName] = isMember;
+ this.roleMembershipChanged = true;
+ }
+ }
+
+ ///
+ /// Create a clone of this DatabaseRoles object
+ ///
+ /// The clone DatabaseRoles
+ public object Clone()
+ {
+ DatabaseRoles result = new DatabaseRoles();
+
+ result.server = this.server;
+ result.loginName = this.loginName;
+ result.databaseName = this.databaseName;
+ result.loginExists = this.loginExists;
+ result.permit = this.permit;
+ result.isAccessible = this.isAccessible;
+ result.defaultSchema = this.defaultSchema;
+ result.userName = this.userName;
+ result.guestStatus = this.guestStatus;
+
+ result.initializedDatabaseAccess = this.initializedDatabaseAccess;
+ result.initializedRoleMembership = this.initializedRoleMembership;
+
+ foreach (string key in this.databaseRoles.Keys)
+ {
+ result.databaseRoles[key] = this.databaseRoles[key];
+ }
+
+ return result;
+ }
+
+ ///
+ /// Determine whether the login has access to the database
+ ///
+ /// the name of the schema associated with the login in the database
+ /// whether the user has database access
+ /// True if there is a user associated with the login in the database, false otherwise
+ private bool GetDatabaseUserInfo(out string userName, out bool hasDBAccess, out string defaultSchema)
+ {
+ bool result = false;
+
+ userName = String.Empty;
+ hasDBAccess = false;
+ defaultSchema = String.Empty;
+
+ if (this.isAccessible)
+ {
+ try
+ {
+ Request request = new Request();
+
+ request.Urn = String.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "Server/Database[@Name='{0}']/User[@Login='{1}']",
+ Urn.EscapeString(databaseName),
+ Urn.EscapeString(this.loginName));
+
+ if (server.Information.Version.Major >= 9)
+ {
+ request.Fields = new string[3] { "Name", "HasDBAccess", "DefaultSchema"};
+ }
+ else
+ {
+ request.Fields = new string[2] { "Name", "HasDBAccess"};
+ }
+
+ DataTable users = new Enumerator().Process(server.ConnectionContext, request);
+
+ if (0 != users.Rows.Count)
+ {
+ System.Diagnostics.Debug.Assert(1 == users.Rows.Count, "unexpected number of users for the the login");
+
+ result = true;
+
+ userName = Convert.ToString (users.Rows[0][0], System.Globalization.CultureInfo.InvariantCulture);
+ hasDBAccess = Convert.ToBoolean(users.Rows[0][1], System.Globalization.CultureInfo.InvariantCulture);
+ if (server.Information.Version.Major >= 9)
+ {
+ defaultSchema = Convert.ToString(users.Rows[0][2], System.Globalization.CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ defaultSchema = String.Empty;
+ }
+ }
+ }
+ catch (EnumeratorException ex)
+ {
+ System.Diagnostics.Trace.TraceError(ex.Message);
+
+ // if we got an exception determining whether the user has access,
+ // then the database is effectively inaccessible
+ this.isAccessible = false;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Determine whether a particular user is a member of a role
+ ///
+ /// The name of the role
+ /// The name of the user
+ /// True if the user is a member of the role, false otherwise
+ private bool DatabaseRoleContainsUser(string roleName, string userName)
+ {
+ bool result = false;
+
+ if (this.isAccessible && (0 != userName.Length))
+ {
+ Request request = new Request();
+
+ request.Urn = String.Format(System.Globalization.CultureInfo.InvariantCulture,
+ "Server/Database[@Name='{0}']/Role[@Name='{1}']/Member[@Name='{2}']",
+ Urn.EscapeString(databaseName),
+ Urn.EscapeString(roleName),
+ Urn.EscapeString(userName));
+
+ request.Fields = new string[1] { "Name"};
+
+ DataTable members = new Enumerator().Process(server.ConnectionContext, request);
+
+ if (0 != members.Rows.Count)
+ {
+ System.Diagnostics.Debug.Assert(1 == members.Rows.Count, "unexpected number of members for the user name");
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Initialize member variables to default values
+ ///
+ private void DefaultInitialize()
+ {
+ this.server = null;
+ this.loginName = String.Empty;
+ this.databaseName = String.Empty;
+ this.loginExists = false;
+ this.permit = false;
+ this.isAccessible = false;
+ this.defaultSchema = String.Empty;
+ this.userName = String.Empty;
+ this.databaseRoles = new HybridDictionary();
+ this.guestStatus = false;
+
+ this.initializedDatabaseAccess = false;
+ this.initializedRoleMembership = false;
+ this.roleMembershipChanged = false;
+ }
+
+ ///
+ /// Determines status of the guest account in the database
+ ///
+ ///
+ private void GetGuestStatus(Database database)
+ {
+ System.Diagnostics.Debug.Assert(database != null, "we need a valid database to determine guest access!");
+ if (database != null)
+ {
+ User guest = database.Users["guest"];
+
+ // if guest doesn't exist, then guest doesn't have access.
+ this.guestStatus = (guest != null) ? guest.HasDBAccess : false;
+ }
+ }
+
+ ///
+ /// Initialize database access information
+ ///
+ private void InitializeDatabaseAccess()
+ {
+ this.initializedDatabaseAccess = true;
+
+ if ((server != null) && (databaseName.Length != 0))
+ {
+ // determine whether the database is accessible to the user
+ try
+ {
+ DataTable dt = new Enumerator().Process(this.server.ConnectionContext,
+ new Request(new Urn(string.Format("Server/Database[@Name='{0}']", Urn.EscapeString(databaseName))),
+ new string[] { "Status" }));
+ if (dt != null &&
+ dt.Rows.Count > 0 &&
+ dt.Rows[0]["Status"] != DBNull.Value)
+ {
+ this.isAccessible = (Convert.ToInt32(dt.Rows[0]["Status"]) & (int)DatabaseStatus.Normal) != 0;
+ }
+ else
+ {
+ this.isAccessible = false;
+ }
+ }
+ catch (Exception)
+ {
+ // if we got an exception checking accessibility, the database
+ // is inaccessible to the user at the very least
+ this.isAccessible = false;
+ }
+
+ if (this.isAccessible && (0 != this.loginName.Length))
+ {
+ this.GetDatabaseUserInfo(out this.userName, out this.permit, out this.defaultSchema);
+ }
+ }
+ }
+
+ ///
+ /// Initialize role membership information for the database
+ ///
+ private void InitializeRoleMembership()
+ {
+ this.initializedRoleMembership = true;
+
+ if (this.DatabaseIsAccessible)
+ {
+ // Get user information
+ Database database = server.Databases[this.databaseName];
+
+ if (database != null)
+ {
+ GetGuestStatus(database);
+ }
+
+ string userName = String.Empty;
+ bool hasAccess = false;
+ string defaultSchema = String.Empty;
+ bool userExists = this.GetDatabaseUserInfo(out userName, out hasAccess, out defaultSchema);
+
+
+ // get database role names
+ Request request = new Request();
+
+ request.Urn = String.Format(System.Globalization.CultureInfo.InvariantCulture,"Server/Database[@Name='{0}']/Role", Urn.EscapeString(databaseName));
+ request.Fields = new string[1] { "Name"};
+
+ DataTable roles = new Enumerator().Process(server.ConnectionContext, request);
+ int roleCount = roles.Rows.Count;
+
+ // determine which roles the user is a member of
+ for (int roleIndex = 0; roleIndex < roleCount; ++roleIndex)
+ {
+ string roleName = roles.Rows[roleIndex][0].ToString();
+ bool isRoleMember = (userExists) ? this.DatabaseRoleContainsUser(roleName, userName) : false;
+
+ this.databaseRoles.Add(roleName, isRoleMember);
+ }
+ }
+ }
+
+ ///
+ /// gets/sets the name of the user for our login in this database
+ ///
+ ///
+ public string UserName
+ {
+ get
+ {
+ if (!this.initializedDatabaseAccess)
+ {
+ this.InitializeDatabaseAccess();
+ }
+
+ return userName;
+ }
+ set
+ {
+ userName = value;
+ }
+ }
+ }
+
+ ///
+ /// Encapsulates server roles of which a particular login is a member
+ ///
+ internal class ServerRoles : ICloneable
+ {
+ private Microsoft.SqlServer.Management.Smo.Server server;
+ private string loginName;
+ private bool loginExists;
+ private HybridDictionary serverRoles;
+
+ private bool initialized;
+
+ ///
+ /// Simple description, isMember pair - used as the value in the serverRoles map
+ ///
+ private class ServerRoleInfo : ICloneable
+ {
+ public string roleDescription;
+ public bool isMember;
+
+ public ServerRoleInfo(string roleDescription)
+ {
+ this.roleDescription = roleDescription;
+ this.isMember = false;
+ }
+
+ public ServerRoleInfo(string roleDescription, bool isMember)
+ {
+ this.roleDescription = roleDescription;
+ this.isMember = isMember;
+ }
+
+ public object Clone()
+ {
+ ServerRoleInfo result = new ServerRoleInfo(this.roleDescription, this.isMember);
+ return result;
+ }
+ }
+
+ ///
+ /// constructor
+ ///
+ private ServerRoles()
+ {
+ this.server = null;
+ this.loginName = String.Empty;
+ this.loginExists = false;
+ this.serverRoles = new HybridDictionary();
+ this.initialized = false;
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server with which we are working
+ public ServerRoles(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ this.server = server;
+ this.loginName = String.Empty;
+ this.loginExists = false;
+ this.serverRoles = new HybridDictionary();
+ this.initialized = false;
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server with which we are working
+ /// The name of the login we are modifying
+ public ServerRoles(Microsoft.SqlServer.Management.Smo.Server server, string loginName)
+ {
+ System.Diagnostics.Debug.Assert(server.Logins[loginName] != null, "loginName does not refer to an actual login on the server");
+
+ this.server = server;
+ this.loginName = loginName;
+ this.loginExists = true;
+ this.serverRoles = new HybridDictionary();
+ this.initialized = false;
+ }
+
+
+ ///
+ /// Get the names of the roles defined on the server
+ ///
+ /// Array of role names
+ public string[] ServerRoleNames
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ PopulateServerRoles();
+ }
+
+ SortedList sortedRoles = new SortedList(serverRoles, Comparer.Default);
+ string[] result = new string[sortedRoles.Count];
+
+ sortedRoles.Keys.CopyTo(result, 0);
+
+ return result;
+ }
+ }
+
+ ///
+ /// Get whether the associated login is a member of a particular role
+ ///
+ /// The name of the role for which we are checking membership
+ /// True if the login is a member of the role, false otherwise
+ public bool IsMember(string serverRoleName)
+ {
+ if (!this.initialized)
+ {
+ this.PopulateServerRoles();
+ }
+
+ bool result = false;
+
+ if (serverRoleName == "public")
+ {
+ result = true;
+ }
+ else if (serverRoles.Contains(serverRoleName))
+ {
+ result = ((ServerRoleInfo) serverRoles[serverRoleName]).isMember;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Set whether the associated login is a member of a particular role
+ ///
+ /// The name of the role whose membership we wish to modify
+ /// True if the login should be a member of the role, false otherwise
+ public void SetMember(string serverRoleName, bool isMember)
+ {
+ System.Diagnostics.Debug.Assert(serverRoles.Contains(serverRoleName), "serverRoleName is not the name of a role in the server");
+
+ if (0 != String.Compare(serverRoleName, "public", StringComparison.Ordinal))
+ {
+ ((ServerRoleInfo) serverRoles[serverRoleName]).isMember = isMember;
+ }
+ }
+
+ ///
+ /// Get the role description for a particular role
+ ///
+ /// The name of the role for which we are getting a description
+ /// The role description
+ public string GetDescription(string serverRoleName)
+ {
+ System.Diagnostics.Debug.Assert(serverRoles.Contains(serverRoleName), "serverRoleName is not the name of a role in the server");
+ return((ServerRoleInfo) serverRoles[serverRoleName]).roleDescription;
+ }
+ ///
+ /// Create a clone of this ServerRoles object
+ ///
+ /// The clone ServerRoles object
+ public object Clone()
+ {
+ ServerRoles result = new ServerRoles();
+
+ result.server = this.server;
+ result.loginName = this.loginName;
+ result.loginExists = this.loginExists;
+ result.initialized = this.initialized;
+
+ foreach (string key in this.serverRoles.Keys)
+ {
+ ServerRoleInfo roleInfo = (ServerRoleInfo) this.serverRoles[key];
+ result.serverRoles[key] = roleInfo.Clone();
+ }
+
+ return result;
+ }
+
+ ///
+ /// Populate the server roles map
+ ///
+ private void PopulateServerRoles()
+ {
+ this.initialized = true;
+ serverRoles.Clear();
+
+ try
+ {
+ foreach (ServerRole role in server.Roles)
+ {
+ bool isRoleMember = false;
+
+ if (this.loginExists)
+ {
+ StringCollection roleMembers = new StringCollection();
+ roleMembers = role.EnumMemberNames();
+ isRoleMember = roleMembers.Contains(this.loginName);
+ }
+
+ string roleDescription = String.Empty; // role.Description;
+ this.serverRoles.Add(role.Name, new ServerRoleInfo(roleDescription, isRoleMember));
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Trace.TraceError(ex.Message);
+
+ // swallow the exception - this method gets called before the dialog is fully created, so there
+ // is no way to display the error message
+ }
+ }
+
+ }
+
+ ///
+ /// Adapter between the CreateLogin dbCommanderPages and the
+ ///
+ internal class LoginPrototype
+ {
+ private SqlCollationSensitiveStringComparer comparer = null;
+
+ ///
+ /// string of asterisks to display in lieu of the actual password
+ ///
+ public static string fakePassword = "***************";
+
+ ///
+ /// Private class encapsulating the data that is changed by the UI.
+ ///
+ ///
+ /// Isolating this data allows for an easy implementation of Reset() and
+ /// simplifies difference detection when committing changes to the server.
+ ///
+ private class LoginPrototypeData : ICloneable
+ {
+ #region data members
+ private string loginName = string.Empty;
+ private SqlServer.Management.Smo.LoginType loginType = SqlServer.Management.Smo.LoginType.WindowsUser;
+
+ // General data
+ private string defaultDatabase = "master";
+ private string defaultLanguage = String.Empty;
+ private ServerRoles serverRoles = null;
+ private HybridDictionary databaseRolesCollection = null;
+
+ // Windows Authentication data
+ private bool windowsGrantAccess = true;
+
+ // SQL Authentication data
+ private string sqlPassword = string.Empty;
+ private string sqlPasswordConfirm = string.Empty;
+ private string oldPassword = string.Empty;
+ private bool showOldPassword = false;
+
+ // yukon only
+ private bool mustChange = true;
+ private bool isDisabled = false;
+ private bool isLockedOut = false;
+ private bool enforcePolicy = true;
+ private bool enforceExpiration = true;
+
+ // Certificate and Asymmetric Key based
+ private string certificateName = String.Empty;
+ private string asymmetricKeyName = String.Empty;
+
+ private bool initialized = false;
+ private Login login = null;
+ private Microsoft.SqlServer.Management.Smo.Server server;
+ private static string defaultLanguageDisplay;
+ private bool windowsAuthSupported = true;
+
+ private StringCollection credentials = null;
+ #endregion
+
+ #region Properties
+
+ // General properties
+
+ public SqlServer.Management.Smo.LoginType LoginType
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.loginType;
+ }
+
+ set
+ {
+ this.loginType = value;
+ }
+ }
+
+ public string LoginName
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.loginName;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.loginName = value;
+ }
+ }
+
+ public string DefaultDatabase
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.defaultDatabase;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.defaultDatabase = value;
+ }
+ }
+
+ public string DefaultLanguage
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.defaultLanguage;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.defaultLanguage = value;
+ }
+ }
+
+ public ServerRoles ServerRoles
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.serverRoles;
+ }
+ }
+
+ public HybridDictionary DatabaseRolesCollection
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.databaseRolesCollection;
+ }
+ }
+
+ public bool Exists
+ {
+ get
+ {
+ return(this.login != null);
+ }
+ }
+
+ public Microsoft.SqlServer.Management.Smo.Server Server
+ {
+ get
+ {
+ return this.server;
+ }
+ }
+
+ public Login Login
+ {
+ get
+ {
+ return this.login;
+ }
+ }
+
+ public bool WindowsAuthSupported
+ {
+ get
+ {
+ if (this.server.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance)
+ {
+ this.windowsAuthSupported = false;
+ }
+
+ return this.windowsAuthSupported;
+ }
+ }
+
+ public static string DefaultLanguageDisplay
+ {
+ get
+ {
+ return defaultLanguageDisplay;
+ }
+ }
+
+ // Windows Authentication properties
+
+ public bool WindowsGrantAccess
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.windowsGrantAccess;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.windowsGrantAccess = value;
+ }
+ }
+
+ // SQL Authentication properties
+
+ public string SqlPassword
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.sqlPassword;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.sqlPassword = value;
+ }
+ }
+
+ public string SqlPasswordConfirm
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.sqlPasswordConfirm;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.sqlPasswordConfirm = value;
+ }
+ }
+
+ public string OldPassword
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.oldPassword;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ this.oldPassword = value;
+ }
+ }
+
+ public bool ShowOldPassword
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+ return this.showOldPassword;
+ }
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialzation");
+ this.showOldPassword = value;
+ }
+ }
+
+
+ public bool MustChange
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.mustChange;
+ }
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ System.Diagnostics.Debug.Assert(Server.Information.Version.Major>=9);
+ this.mustChange = value;
+ }
+ }
+
+ public bool IsDisabled
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.isDisabled;
+ }
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ System.Diagnostics.Debug.Assert(Server.Information.Version.Major>=9);
+ this.isDisabled = value;
+ }
+ }
+
+ public bool IsLockedOut
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.isLockedOut;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ System.Diagnostics.Debug.Assert(Server.Information.Version.Major >= 9);
+ this.isLockedOut = value;
+ }
+ }
+
+ public bool EnforcePolicy
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.enforcePolicy;
+ }
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ System.Diagnostics.Debug.Assert(Server.Information.Version.Major>=9);
+ this.enforcePolicy = value;
+ }
+ }
+
+ public bool EnforceExpiration
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+
+ return this.enforceExpiration;
+ }
+ set
+ {
+ System.Diagnostics.Debug.Assert(this.initialized, "unexpected property set before initialization");
+ System.Diagnostics.Debug.Assert(Server.Information.Version.Major>=9);
+ this.enforceExpiration = value;
+ }
+ }
+
+ // Certificate and Asymmtric Key properties
+ public string CertificateName
+ {
+ get
+ {
+ return this.certificateName;
+ }
+ set
+ {
+ this.certificateName = value;
+ }
+ }
+
+ public string AsymmetricKeyName
+ {
+ get
+ {
+ return this.asymmetricKeyName;
+ }
+ set
+ {
+ this.asymmetricKeyName = value;
+ }
+ }
+
+ public StringCollection Credentials
+ {
+ get
+ {
+ if (!this.initialized)
+ {
+ LoadData();
+ }
+ return this.credentials;
+ }
+ set
+ {
+ if (Server.Information.Version.Major < 10)
+ System.Diagnostics.Debug.Assert(value.Count <= 1, "Max one credential can be mapped to a login in server < Katmai");
+ this.credentials.Clear();
+ foreach (string str in value)
+ {
+ this.credentials.Add(str);
+ }
+ }
+ }
+
+ #endregion
+
+ static LoginPrototypeData()
+ {
+ // ResourceManager resourceManager = new ResourceManager(
+ // "Microsoft.SqlServer.Management.SqlManagerUI.CreateLoginStrings",
+ // typeof(LoginPrototype).Assembly);
+
+ // defaultLanguageDisplay = resourceManager.GetString("prototype.defaultLanguage");
+ }
+
+ ///
+ /// private default contructor - used by Clone()
+ ///
+ private LoginPrototypeData()
+ {
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server on which we are creating a new login
+ public LoginPrototypeData(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ this.server = server;
+ if (server.HostPlatform != HostPlatformNames.Windows)
+ {
+ LoginType = SqlServer.Management.Smo.LoginType.SqlLogin;
+ }
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server on which we are modifying a login
+ /// The login we are modifying
+ public LoginPrototypeData(Microsoft.SqlServer.Management.Smo.Server server, Login login)
+ {
+ this.server = server;
+ this.login = login;
+ }
+
+ ///
+ /// Create a clone of this LoginPrototypeData object
+ ///
+ /// The clone LoginPrototypeData object
+ public object Clone()
+ {
+ LoginPrototypeData result = new LoginPrototypeData();
+
+ result.loginName = this.loginName;
+ result.loginType = this.loginType;
+
+ result.defaultDatabase = this.defaultDatabase;
+ result.defaultLanguage = this.defaultLanguage;
+ result.serverRoles = (this.serverRoles != null) ? (ServerRoles) this.serverRoles.Clone() : null;
+
+ if (this.credentials != null)
+ {
+ result.credentials = new StringCollection();
+ foreach (string credential in this.credentials)
+ {
+ result.credentials.Add(credential);
+ }
+ }
+
+ if (this.databaseRolesCollection != null)
+ {
+ result.databaseRolesCollection = new HybridDictionary();
+
+ foreach (string databaseName in this.databaseRolesCollection.Keys)
+ {
+ DatabaseRoles roles = (DatabaseRoles) this.databaseRolesCollection[databaseName];
+
+ result.databaseRolesCollection[databaseName] = roles.Clone();
+ }
+ }
+ else
+ {
+ result.databaseRolesCollection = null;
+ }
+
+ result.windowsGrantAccess = this.windowsGrantAccess;
+
+ result.sqlPassword = this.sqlPassword;
+ result.sqlPasswordConfirm = this.sqlPasswordConfirm;
+ result.oldPassword = this.oldPassword;
+ result.showOldPassword = this.showOldPassword;
+
+ result.mustChange = this.mustChange;
+ result.isDisabled = this.isDisabled;
+ result.enforcePolicy = this.enforcePolicy;
+ result.enforceExpiration = this.enforceExpiration;
+
+ result.certificateName = this.certificateName;
+ result.asymmetricKeyName = this.asymmetricKeyName;
+
+ result.initialized = this.initialized;
+ result.server = this.server;
+ result.login = this.login;
+
+ return result;
+ }
+
+
+ private void LoadData()
+ {
+ this.initialized = true;
+
+ if (this.Exists)
+ {
+ LoadExisting();
+ }
+ else
+ {
+ LoadNew();
+ }
+ }
+
+ private void LoadExisting()
+ {
+ System.Diagnostics.Debug.Assert(server != null, "server is null");
+ System.Diagnostics.Debug.Assert(login != null, "login is null");
+
+ this.loginName = login.Name;
+ this.loginType = login.LoginType;
+
+ bool useWindowsAuthentication =
+ (login.LoginType == SqlServer.Management.Smo.LoginType.WindowsUser) ||
+ (login.LoginType == SqlServer.Management.Smo.LoginType.WindowsGroup);
+
+ bool useSqlAuthentication = (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin);
+
+ this.windowsGrantAccess = !login.DenyWindowsLogin;
+
+ this.sqlPassword = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
+ this.sqlPasswordConfirm = useSqlAuthentication ? LoginPrototype.fakePassword : string.Empty;
+
+ this.defaultDatabase = login.DefaultDatabase;
+ this.credentials = new StringCollection();
+
+ if ((login.Language != null) && (login.Language.Length != 0))
+ {
+ this.defaultLanguage = login.Language;
+ }
+ else
+ {
+ this.defaultLanguage = LoginPrototypeData.DefaultLanguageDisplay;
+ }
+
+ bool isYukon = (9 <= server.Information.Version.Major);
+
+ if (isYukon)
+ {
+ if (login.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
+ {
+ // these properties make sense only for Yukon+ with SQL Authentication
+ this.mustChange = login.MustChangePassword;
+ this.enforcePolicy = login.PasswordPolicyEnforced;
+ this.enforceExpiration = login.PasswordExpirationEnabled;
+ this.isLockedOut = login.IsLocked;
+ }
+ else
+ {
+ this.mustChange = false;
+ this.enforcePolicy = false;
+ this.enforceExpiration = false;
+ this.isDisabled = false;
+ this.isLockedOut = false;
+ }
+
+ this.isDisabled = login.IsDisabled;
+
+ if (login.LoginType == SqlServer.Management.Smo.LoginType.Certificate)
+ {
+ this.certificateName = login.Certificate;
+ }
+ else if (login.LoginType == SqlServer.Management.Smo.LoginType.AsymmetricKey)
+ {
+ this.asymmetricKeyName = login.AsymmetricKey;
+ }
+ }
+
+ this.serverRoles = new ServerRoles(server, login.Name);
+ this.databaseRolesCollection = new HybridDictionary();
+ if (server.Information.Version.Major == 9 && !string.IsNullOrEmpty(login.Credential))
+ {
+ this.credentials.Add(login.Credential);
+ }
+ else if (server.Information.Version.Major >= 10)
+ {
+ this.credentials.Clear();
+ foreach (string str in login.EnumCredentials())
+ {
+ this.credentials.Add(str);
+ }
+ }
+ }
+
+ private void LoadNew()
+ {
+ if (!WindowsAuthSupported)
+ {
+ this.loginType = SqlServer.Management.Smo.LoginType.SqlLogin;
+ }
+
+ this.defaultLanguage = LoginPrototypeData.DefaultLanguageDisplay;
+
+ this.serverRoles = new ServerRoles(server);
+ this.databaseRolesCollection = new HybridDictionary();
+ this.credentials = new StringCollection();
+ }
+
+ }
+
+ private bool exists;
+ private string machineName;
+ private LoginPrototypeData currentState;
+ private LoginPrototypeData originalState;
+
+ private bool mapToCredential;
+ public bool MapToCredential
+ {
+ set
+ {
+ this.mapToCredential = value;
+ }
+ }
+
+ ///
+ /// Whether the login already exists on the server
+ ///
+ public bool Exists
+ {
+ get
+ {
+ return this.exists;
+ }
+ }
+
+ ///
+ /// Whether to use NT Authentication. (e.g. true == NT Authentication, false == SQL Authentication)
+ ///
+ public SqlServer.Management.Smo.LoginType LoginType
+ {
+ get
+ {
+ return this.currentState.LoginType;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(!this.Exists, "we shouldn't be changing the login type for existing logins");
+ this.currentState.LoginType = value;
+ }
+ }
+
+ ///
+ /// The Windows account name for the login (e.g. MYDOMAIN\mysuser)
+ ///
+ public string LoginName
+ {
+ get
+ {
+ if (!this.Exists && this.UseWindowsAuthentication && this.currentState.LoginName.Length != 0)
+ {
+ return CUtils.CanonicalizeWindowsLoginName(this.currentState.LoginName);
+ }
+
+ return this.currentState.LoginName;
+ }
+
+ set
+ {
+ System.Diagnostics.Debug.Assert(!this.Exists, "shouldn't be renaming existing logins");
+
+ this.UpdateDefaultSchemaAndUserNames(value);
+ this.currentState.LoginName = value;
+ }
+ }
+
+ ///
+ /// If the login is a windows login, returns the windows login name with domain name capitalized;
+ /// otherwise returns the login name as-is
+ ///
+ public string CanonicalizedLoginName
+ {
+ get
+ {
+ string result = String.Empty;
+
+ if (this.LoginName.Length != 0)
+ {
+ result = this.UseWindowsAuthentication ?
+ CUtils.CanonicalizeWindowsLoginName(this.LoginName) :
+ this.LoginName;
+ }
+
+ return result;
+ }
+ }
+
+ public bool WindowsAuthSupported
+ {
+ get
+ {
+ return this.currentState.WindowsAuthSupported;
+ }
+ }
+
+ ///
+ /// Whether the Windows account is granted server access (e.g. true == grant access, false == deny access)
+ ///
+ public bool WindowsGrantAccess
+ {
+ get
+ {
+ return this.currentState.WindowsGrantAccess;
+ }
+
+ set
+ {
+ this.currentState.WindowsGrantAccess = value;
+ }
+ }
+
+ ///
+ /// The password associated with the SQL login
+ ///
+ public string SqlPassword
+ {
+ get
+ {
+ return this.currentState.SqlPassword.ToString();
+ }
+
+ set
+ {
+ this.currentState.SqlPassword = value;
+ }
+ }
+
+ ///
+ /// The password confirmation, which should be the same as the password
+ ///
+ public string SqlPasswordConfirm
+ {
+ get
+ {
+ return this.currentState.SqlPasswordConfirm.ToString();
+ }
+
+ set
+ {
+ this.currentState.SqlPasswordConfirm = value;
+ }
+ }
+
+ ///
+ /// The login's current password
+ ///
+ public string OldPassword
+ {
+ get
+ {
+ return this.currentState.OldPassword.ToString();
+ }
+
+ set
+ {
+ this.currentState.OldPassword = value;
+ }
+ }
+
+ ///
+ /// If the old password is to be considered or not
+ ///
+ public bool ShowOldPassword
+ {
+ get
+ {
+ return this.currentState.ShowOldPassword;
+ }
+ set
+ {
+ this.currentState.ShowOldPassword = value;
+ }
+ }
+
+
+ ///
+ /// The default database for the login
+ ///
+ public string DefaultDatabase
+ {
+ get
+ {
+ return this.currentState.DefaultDatabase;
+ }
+
+ set
+ {
+ this.currentState.DefaultDatabase = value;
+ }
+ }
+
+ ///
+ /// The default language for the login
+ ///
+ public string DefaultLanguage
+ {
+ get
+ {
+ return this.currentState.DefaultLanguage;
+ }
+
+ set
+ {
+ this.currentState.DefaultLanguage = value;
+ }
+ }
+
+ ///
+ /// Yukon+ only
+ ///
+ /// Password must change at next login
+ ///
+ public bool MustChange
+ {
+ get
+ {
+ return this.currentState.MustChange;
+ }
+ set
+ {
+ this.currentState.MustChange = value;
+ }
+ }
+
+ ///
+ /// Yukon+ only
+ ///
+ /// Is login disabled?
+ ///
+ public bool IsDisabled
+ {
+ get
+ {
+ return this.currentState.IsDisabled;
+ }
+ set
+ {
+ this.currentState.IsDisabled = value;
+ }
+ }
+
+ ///
+ /// Yukon+ only
+ ///
+ /// Is login locked out?
+ ///
+ public bool IsLockedOut
+ {
+ get
+ {
+ return this.currentState.IsLockedOut;
+ }
+ set
+ {
+ this.currentState.IsLockedOut = value;
+ }
+ }
+
+ ///
+ /// Yukon+ only
+ ///
+ /// enforce system policy on sql login's password
+ ///
+ public bool EnforcePolicy
+ {
+ get
+ {
+ return this.currentState.EnforcePolicy;
+ }
+ set
+ {
+ this.currentState.EnforcePolicy = value;
+ }
+ }
+
+ ///
+ /// Yukon+ only
+ ///
+ /// expiration of sql login password
+ ///
+ public bool EnforceExpiration
+ {
+ get
+ {
+ return this.currentState.EnforceExpiration;
+ }
+ set
+ {
+ this.currentState.EnforceExpiration = value;
+ }
+ }
+
+ ///
+ /// If this is a certificate based login, returns the name of the certificate;
+ /// otherwise, returns an empty string
+ ///
+ public string CertificateName
+ {
+ get
+ {
+ return this.currentState.CertificateName;
+ }
+ set
+ {
+ this.currentState.CertificateName = value;
+ }
+ }
+
+ ///
+ /// If this is an asymmetric key based login, returns the name of the key;
+ /// otherwise, returns an empty string
+ ///
+ public string AsymmetricKeyName
+ {
+ get
+ {
+ return this.currentState.AsymmetricKeyName;
+ }
+ set
+ {
+ this.currentState.AsymmetricKeyName = value;
+ }
+ }
+
+ ///
+ /// The server roles collection for the login
+ ///
+ public ServerRoles ServerRoles
+ {
+ get
+ {
+ return this.currentState.ServerRoles;
+ }
+ }
+
+ public StringCollection Credentials
+ {
+ get
+ {
+ return this.currentState.Credentials;
+ }
+ set
+ {
+ this.currentState.Credentials.Clear();
+ foreach (string str in value)
+ {
+ this.currentState.Credentials.Add(str);
+ }
+ }
+ }
+
+ ///
+ /// Get the database roles collection for the user in a particular database
+ ///
+ /// The name of the database
+ /// The database roles collection
+ public DatabaseRoles GetDatabaseRoles(string databaseName)
+ {
+ DatabaseRoles result = null;
+
+ if (!this.currentState.DatabaseRolesCollection.Contains(databaseName))
+ {
+ if (this.Exists)
+ {
+ result = new DatabaseRoles(this.currentState.Server, databaseName, this.LoginName);
+ }
+ else
+ {
+ result = new DatabaseRoles(this.currentState.Server, databaseName);
+ }
+ this.currentState.DatabaseRolesCollection.Add(databaseName, result);
+ this.originalState.DatabaseRolesCollection.Add(databaseName, result.Clone());
+ }
+ else
+ {
+ result = (DatabaseRoles) this.currentState.DatabaseRolesCollection[databaseName];
+ }
+
+ return result;
+ }
+
+ ///
+ /// Get the names of the schemas in the indicated database in alphabetical order
+ ///
+ /// The name of the database
+ /// The names of the database's schemas
+ public StringCollection GetDatabaseSchemaNames(string databaseName)
+ {
+ return this.GetDatabaseRoles(databaseName).SchemaNames;
+ }
+
+
+ ///
+ /// A list of names of the databases on the server
+ ///
+ public string[] DatabaseNames
+ {
+ get
+ {
+ Request request = new Request();
+
+ request.Urn = "Server/Database";
+ request.Fields = new string[1] {"Name"};
+ request.OrderByList = new OrderBy[1] { new OrderBy("Name", OrderBy.Direction.Asc)};
+
+ DataTable databases = new Enumerator().Process(this.currentState.Server.ConnectionContext, request);
+ int databaseCount = databases.Rows.Count;
+ string[] result = new string[databaseCount];
+
+ for (int databaseIndex = 0; databaseIndex < databaseCount; ++databaseIndex)
+ {
+ result[databaseIndex] = databases.Rows[databaseIndex][0].ToString();
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// A list of names of the certificates in master database.
+ ///
+ public DataTable CertificateNames
+ {
+ get
+ {
+ Request request = new Request();
+
+ request.Urn = "Server/Database[@Name='master']/Certificate";
+ request.Fields = new string[1] { "Name" };
+ request.OrderByList = new OrderBy[1] { new OrderBy("Name", OrderBy.Direction.Asc) };
+
+ DataTable certificates = new Enumerator().Process(this.currentState.Server.ConnectionContext, request);
+
+ DataView dv = certificates.DefaultView;
+ dv.RowFilter = "Name NOT LIKE '##MS%'";
+
+ return dv.ToTable();
+ }
+ }
+
+ ///
+ /// A list of names of the certificates in master database.
+ ///
+ public DataTable AsymmetricKeyNames
+ {
+ get
+ {
+ Request request = new Request();
+
+ request.Urn = "Server/Database[@Name='master']/AsymmetricKey";
+ request.Fields = new string[1] { "Name" };
+ request.OrderByList = new OrderBy[1] { new OrderBy("Name", OrderBy.Direction.Asc) };
+
+ DataTable asymmetricKeys = new Enumerator().Process(this.currentState.Server.ConnectionContext, request);
+
+ return asymmetricKeys;
+ }
+ }
+
+
+ public Hashtable credProviderMap;
+ public StringCollection CredentialNames
+ {
+ get
+ {
+ if (this.currentState.Server.Information.Version.Major < 9)
+ return null;
+
+ bool isKatmai = (this.currentState.Server.Version.Major >= 10);
+ Request request = new Request();
+ request.Urn = "Server/Credential";
+ if (isKatmai)
+ {
+ request.Fields = new string[2] { "Name", "ProviderName" };
+ }
+ else
+ {
+ request.Fields = new string[1] { "Name" };
+ }
+ DataTable dt = new Enumerator().Process(this.currentState.Server.ConnectionContext, request);
+ StringCollection result = new StringCollection();
+ credProviderMap = new Hashtable();
+ foreach (DataRow dr in dt.Rows)
+ {
+ result.Add(dr["Name"].ToString());
+ if (isKatmai)
+ {
+ credProviderMap.Add(dr["Name"].ToString(), dr["ProviderName"].ToString());
+ }
+ else
+ {
+ credProviderMap.Add(dr["Name"].ToString(), string.Empty);
+ }
+ }
+ return result;
+ }
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server on which we are creating a login
+ public LoginPrototype(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ this.exists = false;
+ this.machineName = server.ConnectionContext.TrueName.ToUpperInvariant();
+ this.currentState = new LoginPrototypeData(server);
+ this.originalState = (LoginPrototypeData) this.currentState.Clone();
+ this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation);
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server on which we are modifying a login
+ /// The login we are modifying
+ public LoginPrototype(Microsoft.SqlServer.Management.Smo.Server server, Login login)
+ {
+ this.exists = true;
+ this.machineName = server.ConnectionContext.TrueName.ToUpperInvariant();
+ this.currentState = new LoginPrototypeData(server, login);
+ this.originalState = (LoginPrototypeData) this.currentState.Clone();
+ this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation);
+ }
+
+ ///
+ /// constructor
+ ///
+ /// The server on which we are creating a login
+ public LoginPrototype(Microsoft.SqlServer.Management.Smo.Server server, LoginInfo login)
+ {
+ this.exists = false;
+ this.machineName = server.ConnectionContext.TrueName.ToUpperInvariant();
+ this.currentState = new LoginPrototypeData(server);
+ this.originalState = (LoginPrototypeData) this.currentState.Clone();
+ this.comparer = new SqlCollationSensitiveStringComparer(server.Information.Collation);
+
+ this.LoginName = login.LoginName;
+ this.SqlPassword = login.Password;
+ this.OldPassword = login.OldPassword;
+ this.LoginType = SqlServer.Management.Smo.LoginType.SqlLogin;
+ this.DefaultLanguage = login.DefaultLanguage;
+ this.DefaultDatabase = login.DefaultDatabase;
+ }
+
+ ///
+ /// Reset the prototype state to its initial state
+ ///
+ public void Reset(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ if (this.Exists)
+ {
+ this.Reload(server);
+ }
+ else
+ {
+ this.currentState = (LoginPrototypeData) this.originalState.Clone();
+ }
+ }
+
+ public void Reload(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ System.Diagnostics.Debug.Assert(this.Exists, "trying to load the state for a login that doesn't exist");
+
+ Login login = server.Logins[this.LoginName];
+ System.Diagnostics.Debug.Assert(login != null, "login does not exist on the server");
+
+ this.originalState = new LoginPrototypeData(server, login);
+ this.currentState = (LoginPrototypeData) this.originalState.Clone();
+
+ }
+
+ ///
+ /// Create the login or modify the login's access type, default database, default language,
+ /// and password
+ /// map the login to credentials
+ ///
+ public void ApplyGeneralChanges(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ bool changesMade = false;
+ Login login = null;
+
+ // if the login exists, get the login, otherwise make a new login
+ if (this.Exists)
+ {
+ login = server.Logins[this.LoginName];
+ if (login == null)
+ {
+ throw new ApplicationException("CreateLoginSR.LoginMissing(this.LoginName)");
+ }
+ }
+ else
+ {
+ login = new Login(server, this.CanonicalizedLoginName);
+ login.LoginType = this.LoginType;
+ }
+
+ // set whether to grant SQL CONNECT to the login and whether to disable the login
+ if (this.currentState.WindowsGrantAccess != this.originalState.WindowsGrantAccess)
+ {
+ login.DenyWindowsLogin = !this.WindowsGrantAccess;
+ changesMade = true;
+ }
+
+ if (9 <= server.Information.Version.Major)
+ {
+ if (!this.Exists || (this.currentState.CertificateName != this.originalState.CertificateName))
+ {
+ login.Certificate = this.CertificateName;
+ changesMade = true;
+ }
+
+ if (!this.Exists || (this.currentState.AsymmetricKeyName != this.originalState.AsymmetricKeyName))
+ {
+ login.AsymmetricKey = this.AsymmetricKeyName;
+ changesMade = true;
+ }
+ }
+
+ // set the default database and language
+ if (!this.Exists || (this.currentState.DefaultDatabase != this.originalState.DefaultDatabase))
+ {
+ login.DefaultDatabase = this.DefaultDatabase;
+ changesMade = true;
+ }
+
+ if (!this.Exists || (this.currentState.DefaultLanguage != this.originalState.DefaultLanguage))
+ {
+ if (this.DefaultLanguage == LoginPrototypeData.DefaultLanguageDisplay)
+ {
+ login.Language = String.Empty;
+ }
+ else
+ {
+ login.Language = this.DefaultLanguage;
+ }
+
+ changesMade = true;
+ }
+
+ // enforcing password policy, enforcing password expiration, and mapping a credential are supported only on Yukon+
+ if ((this.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) && (server.Information.Version.Major >= 9))
+ {
+ if (!this.Exists || (this.currentState.EnforcePolicy != this.originalState.EnforcePolicy))
+ {
+ login.PasswordPolicyEnforced = this.currentState.EnforcePolicy;
+ changesMade = true;
+ }
+
+ if (!this.Exists || (this.currentState.EnforceExpiration != this.originalState.EnforceExpiration))
+ {
+ login.PasswordExpirationEnabled = this.currentState.EnforceExpiration;
+ changesMade = true;
+ }
+ }
+
+ if (server.Information.Version.Major == 9)
+ {
+ System.Diagnostics.Debug.Assert(this.currentState.Credentials.Count <= 1, "Yukon can have max one credential mapped to a login");
+ if (this.currentState.Credentials.Count == 1 //One credential is currently present in the grid
+ && mapToCredential) //Credential in the grid should be honored.
+ {
+ if (!this.originalState.Credentials.Contains(this.currentState.Credentials[0])) //Ensuring that credential is either added or changed.
+ {
+ // new credential is added
+ login.Credential = this.currentState.Credentials[0];
+ changesMade = true;
+ }
+ }
+ else if (this.originalState.Credentials.Count == 1 //Originally atleast one credential was present.
+ && (!mapToCredential //Credential in the grid should be ignored.
+ || this.currentState.Credentials.Count == 0)) //Grid is empty now.
+ {
+ // credential is dropped
+ login.Credential = string.Empty;
+ changesMade = true;
+ }
+ }
+
+ // create or alter the login
+ if (this.Exists)
+ {
+ if (changesMade)
+ {
+ login.Alter();
+ }
+ }
+ else
+ {
+ // if login is a SQL Login and the login is being created, specify the password
+ if (this.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin)
+ {
+ login.Create(this.SqlPassword, this.MustChange ? LoginCreateOptions.MustChange : LoginCreateOptions.None);
+ }
+ else
+ {
+ login.Create();
+ }
+ }
+
+ // If the login already exists and uses SQL authentication and either
+ // the password or one of the password options has changed, change
+ // the password to match. Note that this should be delayed until after
+ // the "enforce policy" and "enforce expiration" options have been set
+ // in the call to Login.Alter() above.
+ if ((this.LoginType == SqlServer.Management.Smo.LoginType.SqlLogin) && this.Exists)
+ {
+ if (this.currentState.SqlPassword != this.originalState.SqlPassword) //Password is changed
+ {
+ if (server.Information.Version.Major >= 9
+ && (this.currentState.IsLockedOut != this.originalState.IsLockedOut
+ || this.currentState.MustChange != this.originalState.MustChange))
+ {
+ if (this.currentState.IsLockedOut != this.originalState.IsLockedOut //IsLockedOut value has been changed
+ && !this.currentState.IsLockedOut) //IsLockedOut is false, which means we have to unlock the login now
+ {
+ login.ChangePassword(this.SqlPassword, !this.currentState.IsLockedOut, this.MustChange);
+ }
+ else //no change in isLockedOut means we don't need to include UNBLOCK Option in the Alter Login query
+ {
+ login.ChangePassword(this.SqlPassword, false, this.MustChange);
+ }
+ }
+ else //For server versions < 9, UNLOCK(IsLockedOut) and MUST_CHANGE(MustChange) are not provided
+ { //For server version >= 9, if we don't change UNLOCK(IsLockedOut) and MUST_CHANGE(MustChange), the T-Sql remains the same as the previous versions
+ if (!this.currentState.ShowOldPassword)
+ {
+ login.ChangePassword(this.SqlPassword);
+ }
+ else
+ {
+ login.ChangePassword(this.OldPassword, this.SqlPassword);
+ }
+ }
+ }
+ else //Password should be reset while unlocking
+ {
+ if (server.Information.Version.Major >= 9
+ && (this.currentState.IsLockedOut != this.originalState.IsLockedOut)
+ )
+ {
+ throw new ArgumentException("CreateLoginSR.ResetPasswordWhileUnlocking");
+ }
+ }
+ }
+
+ // enable or disable the login as needed
+ if (this.currentState.IsDisabled != this.originalState.IsDisabled)
+ {
+ if (this.currentState.IsDisabled)
+ {
+ login.Disable();
+ }
+ else
+ {
+ login.Enable();
+ }
+ }
+ if (server.Information.Version.Major >= 10)
+ {
+ foreach (string dropCredential in this.originalState.Credentials)
+ {
+ if (!mapToCredential || !this.currentState.Credentials.Contains(dropCredential))
+ {
+ login.DropCredential(dropCredential);
+ }
+ }
+ foreach (string newCredential in this.currentState.Credentials)
+ {
+ if (mapToCredential && !this.originalState.Credentials.Contains(newCredential))
+ {
+ login.AddCredential(newCredential);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Set the login's server role membership
+ ///
+ public void ApplyServerRoleChanges(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ // get the login
+ Login login = server.Logins[this.LoginName];
+
+ // foreach server role
+ foreach (ServerRole role in server.Roles)
+ {
+ // all users are always members of the public role, so skip processing for public
+ if (0 != String.Compare(role.Name, "public", StringComparison.Ordinal))
+ {
+ bool wasOriginallyARoleMember = this.originalState.ServerRoles.IsMember(role.Name);
+ bool isCurrentlyARoleMember = this.currentState.ServerRoles.IsMember(role.Name);
+
+
+ // if the login is currently a member of the role, but wasn't originally a member, add the login to the role
+ if (isCurrentlyARoleMember && !wasOriginallyARoleMember)
+ {
+ role.AddMember(this.LoginName);
+ }
+ // if the login is not currently a member of the role, but originally was a member, remove the login from the role
+ else if (!isCurrentlyARoleMember && wasOriginallyARoleMember)
+ {
+ role.DropMember(this.LoginName);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Set the login's database role membership
+ ///
+ public void ApplyDatabaseRoleChanges(Microsoft.SqlServer.Management.Smo.Server server)
+ {
+ System.Diagnostics.Debug.Assert(server != null, "the server is null");
+
+ // get the login
+ Login login = server.Logins[this.LoginName];
+
+ // foreach database
+ foreach (Database database in server.Databases)
+ {
+ // if anything has been changed, then the currentState will include the database
+ if (this.currentState.DatabaseRolesCollection.Contains(database.Name))
+ {
+ DatabaseRoles currentDatabaseRoles = (DatabaseRoles) this.currentState.DatabaseRolesCollection[database.Name];
+ DatabaseRoles originalDatabaseRoles = (DatabaseRoles) this.originalState.DatabaseRolesCollection[database.Name];
+
+ // if the login is currently permitted in the database
+ if (currentDatabaseRoles.PermitDatabaseAccess)
+ {
+ // get the existing user for the login in the database
+ bool userRecreated = false;
+ string existingUserName = originalDatabaseRoles.UserName;
+ User user =
+ ((existingUserName != null) && (existingUserName.Length != 0)) ?
+ database.Users[existingUserName] :
+ null;
+
+ // If the user doesn't exist, create the user in the database.
+ if (user == null)
+ {
+ // Note that if the user name already exists and is mapped to
+ // another login, SMO will emit the appropriate error message.
+
+ user = new User(database, currentDatabaseRoles.UserName);
+ user.Login = this.LoginName;
+ user.Create();
+ }
+ // if a user does exist in the database for the login and
+ // the user name has been changed
+ else if (user.Name != currentDatabaseRoles.UserName)
+ {
+ // rename the user to the new name
+ user.Rename(currentDatabaseRoles.UserName);
+ }
+
+ System.Diagnostics.Debug.Assert(user != null, "user has not been created");
+
+ if (currentDatabaseRoles.DefaultSchema != originalDatabaseRoles.DefaultSchema)
+ {
+ user.DefaultSchema = currentDatabaseRoles.DefaultSchema;
+ user.Alter();
+
+ // if the schema doesn't exist, create it
+ if ((user.DefaultSchema != null) && (user.DefaultSchema.Length != 0) &&
+ !database.Schemas.Contains(user.DefaultSchema))
+ {
+ Schema schema = new Schema(database, user.DefaultSchema);
+ schema.Owner = user.Name;
+
+ schema.Create();
+ }
+ }
+
+ // if any of the roles have been changed
+ if (currentDatabaseRoles.RoleMembershipChanged || userRecreated || !this.Exists)
+ {
+ // commit the role changes to the server
+ foreach (DatabaseRole role in database.Roles)
+ {
+ // all users are always members of the public role, so skip processing for public
+ if (0 != String.Compare(role.Name, "public", StringComparison.Ordinal))
+ {
+ // If the login is new, it is not a member of any role
+ //
+ bool wasOriginallyARoleMember = (this.Exists && originalDatabaseRoles.IsMember(role.Name));
+ bool isCurrentlyARoleMember = currentDatabaseRoles.IsMember(role.Name);
+ bool defaultSchemaChanged = (originalDatabaseRoles.DefaultSchema != currentDatabaseRoles.DefaultSchema);
+
+
+
+ // if the login is currently in the role, but wasn't originally, add the login to the role
+ if (isCurrentlyARoleMember && (!wasOriginallyARoleMember || userRecreated))
+ {
+ role.AddMember(currentDatabaseRoles.UserName);
+ }
+ // if the login is currently not in the role, but originally was, remove the login from the role
+ if (!isCurrentlyARoleMember && (wasOriginallyARoleMember && !userRecreated))
+ {
+ role.DropMember(originalDatabaseRoles.UserName);
+ }
+ }
+ }
+ }
+ }
+ // else if the login was originally permitted in the database
+ else if (originalDatabaseRoles.PermitDatabaseAccess)
+ {
+ // remove the associated user
+ string existingUserName = login.GetDatabaseUser(database.Name);
+ if ((existingUserName != null) && (existingUserName.Length != 0))
+ {
+ System.Diagnostics.Debug.Assert(database.Users[existingUserName] != null, "user not found in collection");
+ database.Users[existingUserName].Drop();
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Remove the login from all database roles in the named database
+ ///
+ /// The name of the database from which we are removing login
+ internal void DisjoinAllDatabaseRoles(string databaseName)
+ {
+ DatabaseRoles roles = this.GetDatabaseRoles(databaseName);
+
+ foreach (string roleName in roles.DatabaseRoleNames)
+ {
+ roles.SetMember(roleName, false);
+ }
+ }
+
+ ///
+ /// Update any default schemas or user names in the databaseRolesCollection that match
+ /// the old login name to match a new login name.
+ ///
+ /// The new login name
+ private void UpdateDefaultSchemaAndUserNames(string newLoginName)
+ {
+ foreach (DatabaseRoles databaseRoles in this.currentState.DatabaseRolesCollection.Values)
+ {
+ if (databaseRoles.PermitDatabaseAccess)
+ {
+ // if the default schema name is the same as the login name,
+ // then set the default schema name to the new login name
+ if (this.NameMatchesCurrentLoginName(databaseRoles.DefaultSchema))
+ {
+ databaseRoles.DefaultSchema = newLoginName;
+ }
+
+ // if the user name is the same as the login name,
+ // then set the user name to the new login name
+ if (this.NameMatchesCurrentLoginName(databaseRoles.UserName))
+ {
+ databaseRoles.UserName = newLoginName;
+ }
+
+ }
+ }
+ }
+
+ ///
+ /// Does the input name match the current login name
+ ///
+ /// The name to compare
+ /// True if they match, otherwise false
+ private bool NameMatchesCurrentLoginName(string otherName)
+ {
+ // if the login is not a windows login, do a collation-sensitive comparison.
+ //
+ // if the login is a windows login, do a case-insensitive comparison
+ // because domain\user is equivalent to the canonicalized form of DOMAIN\user.
+
+ bool matchesSqlLogin =
+ !this.UseWindowsAuthentication &&
+ (0 == this.comparer.Compare(this.CanonicalizedLoginName, otherName));
+
+ bool matchesWindowsLogin =
+ this.UseWindowsAuthentication &&
+ (0 == string.Compare(this.CanonicalizedLoginName, otherName, StringComparison.OrdinalIgnoreCase));
+
+ return (matchesSqlLogin || matchesWindowsLogin);
+ }
+
+ ///
+ /// tells us if user changed the password (password initial state different then current state)
+ /// this is required by ui since it needs to enable disable some checkboxes based on this info
+ ///
+ internal bool PasswordWasChanged
+ {
+ get
+ {
+ if ( (!this.Exists) || (this.currentState.SqlPassword != this.originalState.SqlPassword))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public bool UseWindowsAuthentication
+ {
+ get
+ {
+ return ((this.LoginType == SqlServer.Management.Smo.LoginType.WindowsUser) || (this.LoginType == SqlServer.Management.Smo.LoginType.WindowsGroup));
+ }
+ }
+
+ public bool UseAadAuthentication
+ {
+ get
+ {
+ return ((this.LoginType == SqlServer.Management.Smo.LoginType.ExternalUser) || (this.LoginType == SqlServer.Management.Smo.LoginType.ExternalGroup));
+ }
+ }
+ }
+
+ ///
+ /// Case-sensitive, culture-invariant string comparer
+ ///
+ internal class CaseSensitiveCultureInvariantComparer : IComparer
+ {
+ public CaseSensitiveCultureInvariantComparer() {}
+
+ ///
+ /// Compare strings a and b
+ ///
+ /// First string
+ /// Second string
+ /// less than zero if a is less than b, 0 if a and b are equal, and greater than zero if a is greater than b
+ public int Compare(object a, object b)
+ {
+ if ((a == null) || (b == null) || !(a is String) || !(b is String))
+ {
+ throw new ArgumentException();
+ }
+
+ string string_a = (string) a;
+ string string_b = (string) b;
+
+ return String.Compare(string_a, string_b, StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs
index e83edc4b..80e4c562 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/SecurityService.cs
@@ -4,7 +4,15 @@
//
using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Data;
+using System.Security;
using System.Threading.Tasks;
+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;
@@ -79,8 +87,356 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
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);
}
+
+#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"
+
+ private UserPrototypeNew InitUserNew(CDataContainer dataContainer)
+ {
+ // this.DataContainer = context;
+ // this.parentDbUrn = new Urn(this.DataContainer.ParentUrn);
+ // this.objectUrn = new Urn(this.DataContainer.ObjectUrn);
+ ExhaustiveUserTypes currentUserType;
+ UserPrototypeFactory userPrototypeFactory = UserPrototypeFactory.GetInstance(dataContainer);
+
+ if (dataContainer.IsNewObject)
+ {
+ if (IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer))
+ {
+ currentUserType = ExhaustiveUserTypes.SqlUserWithPassword;
+ }
+ else
+ {
+ currentUserType = ExhaustiveUserTypes.LoginMappedUser;
+ }
+ }
+ else
+ {
+ currentUserType = this.GetCurrentUserTypeForExistingUser(
+ dataContainer.Server.GetSmoObject(dataContainer.ObjectUrn) as User);
+ }
+
+ UserPrototypeNew currentUserPrototype = userPrototypeFactory.GetUserPrototype(currentUserType);
+ return currentUserPrototype;
+ }
+
+ private ExhaustiveUserTypes GetCurrentUserTypeForExistingUser(User user)
+ {
+ switch (user.UserType)
+ {
+ case UserType.SqlUser:
+ if (user.IsSupportedProperty("AuthenticationType"))
+ {
+ if (user.AuthenticationType == AuthenticationType.Windows)
+ {
+ return ExhaustiveUserTypes.WindowsUser;
+ }
+ else if (user.AuthenticationType == AuthenticationType.Database)
+ {
+ return ExhaustiveUserTypes.SqlUserWithPassword;
+ }
+ }
+
+ return ExhaustiveUserTypes.LoginMappedUser;
+
+ case UserType.NoLogin:
+ return ExhaustiveUserTypes.SqlUserWithoutLogin;
+
+ case UserType.Certificate:
+ return ExhaustiveUserTypes.CertificateMappedUser;
+
+ case UserType.AsymmetricKey:
+ return ExhaustiveUserTypes.AsymmetricKeyMappedUser;
+
+ default:
+ return ExhaustiveUserTypes.Unknown;
+ }
+ }
+
+ private bool IsParentDatabaseContained(Urn parentDbUrn, CDataContainer dataContainer)
+ {
+ string parentDbName = parentDbUrn.GetNameForType("Database");
+ Database parentDatabase = dataContainer.Server.Databases[parentDbName];
+
+ if (parentDatabase.IsSupportedProperty("ContainmentType")
+ && parentDatabase.ContainmentType == ContainmentType.Partial)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ private void GetUserTypeOptions(CDataContainer dataContainer)
+ {
+ if (SqlMgmtUtils.IsSql11OrLater(dataContainer.Server.ServerVersion)
+ && IsParentDatabaseContained(dataContainer.ParentUrn, dataContainer))
+ {
+ // this.userTypeComboBox.Items.AddRange(
+ // new string[]{
+ // UserSR.SqlUserWithPasswordUserTypeText
+ // }
+ //);
+ }
+ if (SqlMgmtUtils.IsYukonOrAbove(dataContainer.Server))
+ {
+ // this.userTypeComboBox.Items.AddRange(
+ // new string[]{
+ // UserSR.AsymmetricKeyUserTypeText,
+ // UserSR.CertificateUserTypeText,
+ // UserSR.WithoutLoginSqlUserTypeText,
+ // UserSR.WindowsUserTypeText
+ // }
+ // );
+ }
+ // this.userTypeComboBox.Items.AddRange(
+ // new string[]{
+ // UserSR.LoginMappedSqlUserTypeText
+ // }
+ // );
+ }
+
+ 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);
+ }
+ }
+
+ private SecureString GetReadOnlySecureString(string secret)
+ {
+ SecureString ss = new SecureString();
+ foreach (char c in secret.ToCharArray())
+ {
+ ss.AppendChar(c);
+ }
+ ss.MakeReadOnly();
+
+ return ss;
+ }
+
+ public void UserMemberships_OnRunNow(object sender, CDataContainer dataContainer)
+ {
+ UserPrototypeNew 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)
+ {
+ UserPrototypeNew 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
///
@@ -183,6 +539,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
}
}
+#endregion
+
#region "Helpers"
internal Task> ConfigureCredential(
@@ -214,6 +572,376 @@ namespace Microsoft.SqlTools.ServiceLayer.Security
});
}
+ ///
+ /// 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 = null;
+
+ 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 = null;
+ 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
+
}
+
+
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs
new file mode 100644
index 00000000..33f72bbb
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/SqlCollationSensitiveStringComparer.cs
@@ -0,0 +1,80 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+#region Using directives
+
+using System.Collections;
+using System.Globalization;
+
+using Microsoft.SqlServer.Management.Smo;
+
+#endregion
+
+namespace Microsoft.SqlTools.ServiceLayer.Security
+{
+ ///
+ /// String comparer that uses the case sensitivity and other settings
+ /// from a particular SQL collation
+ ///
+#if DEBUG || EXPOSE_MANAGED_INTERNALS
+ public
+#else
+ internal
+#endif
+ class SqlCollationSensitiveStringComparer : IComparer
+ {
+ private CompareOptions compareOptions;
+
+ ///
+ /// Constructor
+ ///
+ /// The name of the SQL collation, like ALGERIAN_CI_AI
+ public SqlCollationSensitiveStringComparer(string sqlCollation)
+ {
+ if (sqlCollation != null && sqlCollation.Length != 0)
+ {
+ this.compareOptions = SqlSupport.GetCompareOptionsFromCollation(sqlCollation);
+ }
+ else
+ {
+ this.compareOptions = CompareOptions.Ordinal;
+ }
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// The CompareOptions for the SQL collation
+ public SqlCollationSensitiveStringComparer(CompareOptions compareOptions)
+ {
+ this.compareOptions = compareOptions;
+ }
+
+ ///
+ /// Compare two strings
+ ///
+ /// The first string to compare
+ /// The second string to compare
+ /// Less than zero if x is less than y, 0 if x equals y, greater than zero if x is greater than y
+ public int Compare(object x, object y)
+ {
+ if (null == x && null == y)
+ {
+ return 0;
+ }
+ else if (null != x && null == y)
+ {
+ return 1;
+ }
+ else if (null == x && null != y)
+ {
+ return -1;
+ }
+ else
+ {
+ return CultureInfo.InvariantCulture.CompareInfo.Compare((string) x, (string) y, compareOptions);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs b/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs
new file mode 100644
index 00000000..caca6623
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/Security/UserData.cs
@@ -0,0 +1,1128 @@
+//
+// 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.Generic;
+using System.Security;
+using Microsoft.SqlServer.Management.Smo;
+using Microsoft.SqlServer.Management.Sdk.Sfc;
+using Microsoft.SqlTools.ServiceLayer.Management;
+
+namespace Microsoft.SqlTools.ServiceLayer.Security
+{
+ ///
+ /// Defines the common behavior of all types of database user objects.
+ ///
+ internal interface IUserPrototype
+ {
+ string Name { get; set; }
+ UserType UserType { get; set; }
+ string AsymmetricKeyName { get; set; }
+ string CertificateName { get; set; }
+ bool IsSystemObject { get; }
+ bool Exists { get; }
+ List SchemaNames { get; }
+ bool IsSchemaOwner(string schemaName);
+ void SetSchemaOwner(string schemaName, bool isOwner);
+ List DatabaseRoleNames { get; }
+ bool IsRoleMember(string roleName);
+ void SetRoleMembership(string roleName, bool isMember);
+ }
+
+ ///
+ /// Defines the behavior of those database users which have default schema.
+ ///
+ internal interface IUserPrototypeWithDefaultSchema
+ {
+ bool IsDefaultSchemaSupported { get; }
+ string DefaultSchema { get; set; }
+ }
+
+ ///
+ /// Defines the behavior of those database users which are mapped to a login.
+ ///
+ internal interface IUserPrototypeWithMappedLogin
+ {
+ string LoginName { get; set; }
+ }
+
+ ///
+ /// Defines the behavior of those database users which have password.
+ ///
+ internal interface IUserPrototypeWithPassword
+ {
+ SecureString Password { set; }
+ SecureString PasswordConfirm { set; }
+ SecureString OldPassword { set; }
+ bool IsOldPasswordRequired { get; set; }
+ }
+
+ ///
+ /// Defines the behavior of those database users which have default language.
+ ///
+ internal interface IUserPrototypeWithDefaultLanguage
+ {
+ bool IsDefaultLanguageSupported { get; }
+ string DefaultLanguageAlias { get; set; }
+ }
+
+ ///
+ /// Object have exhaustive list of data elements which are required for creating
+ /// any type of database user.
+ ///
+ public class UserPrototypeDataNew
+ {
+ public string name = string.Empty;
+ public UserType userType = UserType.SqlUser;
+ public bool isSystemObject = false;
+ public Dictionary isSchemaOwned = null;
+ public Dictionary isMember = null;
+
+ public AuthenticationType authenticationType = AuthenticationType.Instance;
+ public string mappedLoginName = string.Empty;
+ public string certificateName = string.Empty;
+ public string asymmetricKeyName = string.Empty;
+ public string defaultSchemaName = string.Empty;
+ public string defaultLanguageAlias = string.Empty;
+ public SecureString password = new SecureString();
+ public SecureString passwordConfirm = new SecureString();
+ public SecureString oldPassword = new SecureString();
+ public bool isOldPasswordRequired = false;
+
+ ///
+ /// Used for creating clone of a UserPrototypeData.
+ ///
+ private UserPrototypeDataNew()
+ {
+ this.isSchemaOwned = new Dictionary();
+ this.isMember = new Dictionary();
+ }
+
+ public UserPrototypeDataNew(CDataContainer context)
+ {
+ this.isSchemaOwned = new Dictionary();
+ this.isMember = new Dictionary();
+
+ if (!context.IsNewObject)
+ {
+ this.LoadUserData(context);
+ }
+
+ this.LoadRoleMembership(context);
+
+ this.LoadSchemaData(context);
+ }
+
+ public UserPrototypeDataNew Clone()
+ {
+ UserPrototypeDataNew result = new UserPrototypeDataNew();
+
+ result.asymmetricKeyName = this.asymmetricKeyName;
+ result.authenticationType = this.authenticationType;
+ result.certificateName = this.certificateName;
+ result.defaultLanguageAlias = this.defaultLanguageAlias;
+ result.defaultSchemaName = this.defaultSchemaName;
+ result.isSystemObject = this.isSystemObject;
+ result.mappedLoginName = this.mappedLoginName;
+ result.name = this.name;
+ result.oldPassword = this.oldPassword;
+ result.password = this.password;
+ result.passwordConfirm = this.passwordConfirm;
+ result.isOldPasswordRequired = this.isOldPasswordRequired;
+ result.userType = this.userType;
+
+ foreach (string key in this.isMember.Keys)
+ {
+ result.isMember[key] = this.isMember[key];
+ }
+
+ foreach (string key in this.isSchemaOwned.Keys)
+ {
+ result.isSchemaOwned[key] = this.isSchemaOwned[key];
+ }
+
+ return result;
+ }
+
+ public bool HasSameValueAs(UserPrototypeDataNew other)
+ {
+ bool result =
+ (this.asymmetricKeyName == other.asymmetricKeyName) &&
+ (this.authenticationType == other.authenticationType) &&
+ (this.certificateName == other.certificateName) &&
+ (this.defaultLanguageAlias == other.defaultLanguageAlias) &&
+ (this.defaultSchemaName == other.defaultSchemaName) &&
+ (this.isSystemObject == other.isSystemObject) &&
+ (this.mappedLoginName == other.mappedLoginName) &&
+ (this.name == other.name) &&
+ (this.oldPassword == other.oldPassword) &&
+ (this.password == other.password) &&
+ (this.passwordConfirm == other.passwordConfirm) &&
+ (this.isOldPasswordRequired == other.isOldPasswordRequired) &&
+ (this.userType == other.userType);
+
+ result = result && this.isMember.Keys.Count == other.isMember.Keys.Count;
+ if (result)
+ {
+ foreach (string key in this.isMember.Keys)
+ {
+ if (this.isMember[key] != other.isMember[key])
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ result = result && this.isSchemaOwned.Keys.Count == other.isSchemaOwned.Keys.Count;
+ if (result)
+ {
+ foreach (string key in this.isSchemaOwned.Keys)
+ {
+ if (this.isSchemaOwned[key] != other.isSchemaOwned[key])
+ {
+ result = false;
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Initializes this object with values from the existing database user.
+ ///
+ ///
+ private void LoadUserData(CDataContainer context)
+ {
+ User existingUser = context.Server.GetSmoObject(new Urn(context.ObjectUrn)) as User;
+
+ this.name = existingUser.Name;
+ this.mappedLoginName = existingUser.Login;
+ this.isSystemObject = existingUser.IsSystemObject;
+
+ if (SqlMgmtUtils.IsYukonOrAbove(context.Server))
+ {
+ this.asymmetricKeyName = existingUser.AsymmetricKey;
+ this.certificateName = existingUser.Certificate;
+ this.defaultSchemaName = existingUser.DefaultSchema;
+ this.userType = existingUser.UserType;
+ }
+
+ if (SqlMgmtUtils.IsSql11OrLater(context.Server.ServerVersion))
+ {
+ this.authenticationType = existingUser.AuthenticationType;
+ this.defaultLanguageAlias = LanguageUtils.GetLanguageAliasFromName(existingUser.Parent.Parent,
+ existingUser.DefaultLanguage.Name);
+ }
+ }
+
+ ///
+ /// Loads role membership of a database user.
+ ///
+ ///
+ private void LoadRoleMembership(CDataContainer context)
+ {
+ Urn objUrn = new Urn(context.ObjectUrn);
+ Urn databaseUrn = objUrn.Parent;
+
+ Database parentDb = context.Server.GetSmoObject(databaseUrn) as Database;
+ User existingUser = context.Server.Databases[parentDb.Name].Users[objUrn.GetNameForType("User")];
+
+ foreach (DatabaseRole dbRole in parentDb.Roles)
+ {
+ var comparer = parentDb.GetStringComparer();
+ if (comparer.Compare(dbRole.Name, "public") != 0)
+ {
+ if (context.IsNewObject)
+ {
+ this.isMember[dbRole.Name] = false;
+ }
+ else
+ {
+ this.isMember[dbRole.Name] = existingUser.IsMember(dbRole.Name);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Loads schema ownership related data.
+ ///
+ ///
+ private void LoadSchemaData(CDataContainer context)
+ {
+ Urn objUrn = new Urn(context.ObjectUrn);
+ Urn databaseUrn = objUrn.Parent;
+
+ Database parentDb = context.Server.GetSmoObject(databaseUrn) as Database;
+ User existingUser = context.Server.Databases[parentDb.Name].Users[objUrn.GetNameForType("User")];
+
+ if (!SqlMgmtUtils.IsYukonOrAbove(context.Server)
+ || parentDb.CompatibilityLevel <= CompatibilityLevel.Version80)
+ {
+ return;
+ }
+
+ foreach (Schema sch in parentDb.Schemas)
+ {
+ if (context.IsNewObject)
+ {
+ this.isSchemaOwned[sch.Name] = false;
+ }
+ else
+ {
+ var comparer = parentDb.GetStringComparer();
+ this.isSchemaOwned[sch.Name] = comparer.Compare(sch.Owner, existingUser.Name) == 0;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Prototype object for creating or altering users
+ ///
+ internal class UserPrototypeNew : IUserPrototype
+ {
+ protected UserPrototypeDataNew originalState = null;
+ protected UserPrototypeDataNew currentState = null;
+
+ private List schemaNames = null;
+ private List roleNames = null;
+ private bool exists = false;
+ private Database parent = null;
+
+ public bool IsRoleMembershipChangesApplied { get; set; } //default is false
+ public bool IsSchemaOwnershipChangesApplied { get; set; } //default is false
+
+ #region IUserPrototype Members
+
+ public string Name
+ {
+ get
+ {
+ return this.currentState.name;
+ }
+ set
+ {
+ this.currentState.name = value;
+ }
+ }
+
+ public string CertificateName
+ {
+ get
+ {
+ return this.currentState.certificateName;
+ }
+ set
+ {
+ this.currentState.certificateName = value;
+ }
+ }
+
+ public string AsymmetricKeyName
+ {
+ get
+ {
+ return this.currentState.asymmetricKeyName;
+ }
+ set
+ {
+ this.currentState.asymmetricKeyName = value;
+ }
+ }
+
+ public UserType UserType
+ {
+ get
+ {
+ return this.currentState.userType;
+ }
+ set
+ {
+ this.currentState.userType = value;
+ }
+ }
+
+ public bool IsSystemObject
+ {
+ get
+ {
+ return this.currentState.isSystemObject;
+ }
+ }
+
+ public bool Exists
+ {
+ get
+ {
+ return this.exists;
+ }
+ }
+
+ public List SchemaNames
+ {
+ get
+ {
+ return this.schemaNames;
+ }
+ }
+
+ public List DatabaseRoleNames
+ {
+ get
+ {
+ return this.roleNames;
+ }
+ }
+
+ public bool IsSchemaOwner(string schemaName)
+ {
+ bool isSchemaOwner = false;
+ this.currentState.isSchemaOwned.TryGetValue(schemaName, out isSchemaOwner);
+
+ return isSchemaOwner;
+ }
+
+ public void SetSchemaOwner(string schemaName, bool isOwner)
+ {
+ this.currentState.isSchemaOwned[schemaName] = isOwner;
+ }
+
+ public bool IsRoleMember(string roleName)
+ {
+ bool isRoleMember = false;
+ this.currentState.isMember.TryGetValue(roleName, out isRoleMember);
+
+ return isRoleMember;
+ }
+
+ public void SetRoleMembership(string roleName, bool isMember)
+ {
+ this.currentState.isMember[roleName] = isMember;
+ }
+
+ #endregion
+
+ ///
+ /// Constructor
+ ///
+ /// The context for the dialog
+ public UserPrototypeNew(CDataContainer context,
+ UserPrototypeDataNew current,
+ UserPrototypeDataNew original)
+ {
+ this.currentState = current;
+ this.originalState = original;
+
+ this.exists = !context.IsNewObject;
+ this.parent = context.Server.GetSmoObject(new Urn(context.ParentUrn)) as Database;
+
+ this.PopulateRoles();
+ this.PopulateSchemas();
+ }
+
+ private void PopulateRoles()
+ {
+ this.roleNames = new List();
+
+ foreach (DatabaseRole dbRole in this.parent.Roles)
+ {
+ var comparer = this.parent.GetStringComparer();
+ if (comparer.Compare(dbRole.Name, "public") != 0)
+ {
+ this.roleNames.Add(dbRole.Name);
+ }
+ }
+ }
+
+ private void PopulateSchemas()
+ {
+ this.schemaNames = new List();
+
+ if (!SqlMgmtUtils.IsYukonOrAbove(this.parent.Parent)
+ || this.parent.CompatibilityLevel <= CompatibilityLevel.Version80)
+ {
+ return;
+ }
+
+ foreach (Schema sch in this.parent.Schemas)
+ {
+ this.schemaNames.Add(sch.Name);
+ }
+ }
+
+ public bool IsYukonOrLater
+ {
+ get
+ {
+ return SqlMgmtUtils.IsYukonOrAbove(this.parent.Parent);
+ }
+ }
+
+ public User ApplyChanges()
+ {
+ User user = null;
+
+ user = this.GetUser();
+
+ if (this.ChangesExist())
+ {
+ this.SaveProperties(user);
+ this.CreateOrAlterUser(user);
+
+ //Extended Properties page also executes Alter() method on the same user object
+ //in order to add extended properties. If at that time any property is dirty,
+ //it will again generate the script corresponding to that.
+ user.Refresh();
+
+ this.ApplySchemaOwnershipChanges(user);
+ this.IsSchemaOwnershipChangesApplied = true;
+
+ this.ApplyRoleMembershipChanges(user);
+ this.IsRoleMembershipChangesApplied = true;
+ }
+
+ return user;
+ }
+
+ protected virtual void CreateOrAlterUser(User user)
+ {
+ if (!this.Exists)
+ {
+ user.Create();
+ }
+ else
+ {
+ user.Alter();
+ }
+ }
+
+ private void ApplySchemaOwnershipChanges(User user)
+ {
+ IEnumerator> enumerator = this.currentState.isSchemaOwned.GetEnumerator();
+ enumerator.Reset();
+
+ String nullString = null;
+
+ while (enumerator.MoveNext())
+ {
+ string schemaName = enumerator.Current.Key.ToString();
+ bool userIsOwner = (bool)enumerator.Current.Value;
+
+ if (((bool)this.originalState.isSchemaOwned[schemaName]) != userIsOwner)
+ {
+ System.Diagnostics.Debug.Assert(!this.Exists || userIsOwner, "shouldn't have to unset ownership for new users");
+
+ Schema schema = this.parent.Schemas[schemaName];
+ schema.Owner = userIsOwner ? user.Name : nullString;
+ schema.Alter();
+ }
+ }
+ }
+
+ private void ApplyRoleMembershipChanges(User user)
+ {
+ IEnumerator> enumerator = this.currentState.isMember.GetEnumerator();
+ enumerator.Reset();
+
+ while (enumerator.MoveNext())
+ {
+ string roleName = enumerator.Current.Key;
+ bool userIsMember = (bool)enumerator.Current.Value;
+
+ if (((bool)this.originalState.isMember[roleName]) != userIsMember)
+ {
+ System.Diagnostics.Debug.Assert(this.Exists || userIsMember, "shouldn't have to unset membership for new users");
+
+ DatabaseRole role = this.parent.Roles[roleName];
+
+ if (userIsMember)
+ {
+ role.AddMember(user.Name);
+ }
+ else
+ {
+ role.DropMember(user.Name);
+ }
+ }
+ }
+ }
+
+ protected virtual void SaveProperties(User user)
+ {
+ if (!this.Exists || (user.UserType != this.currentState.userType))
+ {
+ user.UserType = this.currentState.userType;
+ }
+
+ if ((this.currentState.userType == UserType.Certificate)
+ &&(!this.Exists || (user.Certificate != this.currentState.certificateName))
+ )
+ {
+ user.Certificate = this.currentState.certificateName;
+ }
+
+ if ((this.currentState.userType == UserType.AsymmetricKey)
+ && (!this.Exists || (user.AsymmetricKey != this.currentState.asymmetricKeyName))
+ )
+ {
+ user.AsymmetricKey = this.currentState.asymmetricKeyName;
+ }
+ }
+
+ public User GetUser()
+ {
+ User result = null;
+
+ // if we think we exist, get the SMO user object
+ if (this.Exists)
+ {
+ result = this.parent.Users[this.originalState.name];
+ result.Refresh();
+
+ System.Diagnostics.Debug.Assert(0 == String.Compare(this.originalState.name, this.currentState.name, StringComparison.Ordinal), "name of existing user has changed");
+ if (result == null)
+ {
+ throw new Exception();
+ }
+ }
+ else
+ {
+ result = new User(this.parent, this.Name);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Will calling ApplyChanges do anything?
+ ///
+ /// True if there are changes to apply, false otherwise
+ public bool ChangesExist()
+ {
+ bool result =
+ !this.Exists ||
+ !this.originalState.HasSameValueAs(this.currentState);
+
+ return result;
+ }
+ }
+
+ internal class UserPrototypeWithDefaultSchema : UserPrototypeNew,
+ IUserPrototypeWithDefaultSchema
+ {
+ private CDataContainer context;
+
+ #region IUserPrototypeWithDefaultSchema Members
+
+ public virtual bool IsDefaultSchemaSupported
+ {
+ get
+ {
+ //Default Schema was not supported in Shiloh.
+ return this.context.Server.ConnectionContext.ServerVersion.Major > 8 ;
+ }
+ }
+
+ public string DefaultSchema
+ {
+ get
+ {
+ return this.currentState.defaultSchemaName;
+ }
+ set
+ {
+ this.currentState.defaultSchemaName = value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor
+ ///
+ /// The context for the dialog
+ public UserPrototypeWithDefaultSchema(CDataContainer context,
+ UserPrototypeDataNew current,
+ UserPrototypeDataNew original)
+ : base(context, current, original)
+ {
+ this.context = context;
+ }
+
+ protected override void SaveProperties(User user)
+ {
+ base.SaveProperties(user);
+
+ if (this.IsDefaultSchemaSupported
+ && (!this.Exists || (user.DefaultSchema != this.currentState.defaultSchemaName)))
+ {
+ user.DefaultSchema = this.currentState.defaultSchemaName;
+ }
+ }
+ }
+
+ internal class UserPrototypeForSqlUserWithLogin : UserPrototypeWithDefaultSchema,
+ IUserPrototypeWithMappedLogin
+ {
+
+ #region IUserPrototypeWithMappedLogin Members
+
+ public string LoginName
+ {
+ get
+ {
+ return this.currentState.mappedLoginName;
+ }
+ set
+ {
+ this.currentState.mappedLoginName = value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor
+ ///
+ /// The context for the dialog
+ public UserPrototypeForSqlUserWithLogin(CDataContainer context,
+ UserPrototypeDataNew current,
+ UserPrototypeDataNew original)
+ : base(context, current, original)
+ {
+ }
+
+ protected override void SaveProperties(User user)
+ {
+ base.SaveProperties(user);
+
+ if (!this.Exists || (user.Login != this.currentState.mappedLoginName))
+ {
+ user.Login = this.currentState.mappedLoginName;
+ }
+ }
+ }
+
+ internal class UserPrototypeForWindowsUser : UserPrototypeForSqlUserWithLogin,
+ IUserPrototypeWithDefaultLanguage
+ {
+ private CDataContainer context;
+
+ public override bool IsDefaultSchemaSupported
+ {
+ get
+ {
+ //Default Schema was not supported before Denali for windows group.
+ User user = null;
+
+ user = this.GetUser();
+ if (this.Exists && user.LoginType == LoginType.WindowsGroup)
+ {
+ return SqlMgmtUtils.IsSql11OrLater(this.context.Server.ConnectionContext.ServerVersion);
+ }
+ else
+ {
+ return base.IsDefaultSchemaSupported;
+ }
+ }
+ }
+
+ #region IUserPrototypeWithDefaultLanguage Members
+
+ public bool IsDefaultLanguageSupported
+ {
+ get
+ {
+ //Default Language was not supported before Denali.
+ return SqlMgmtUtils.IsSql11OrLater(this.context.Server.ConnectionContext.ServerVersion);
+ }
+ }
+
+ public string DefaultLanguageAlias
+ {
+ get
+ {
+ return this.currentState.defaultLanguageAlias;
+ }
+ set
+ {
+ this.currentState.defaultLanguageAlias = value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor
+ ///
+ /// The context for the dialog
+ public UserPrototypeForWindowsUser(CDataContainer context,
+ UserPrototypeDataNew current,
+ UserPrototypeDataNew original)
+ : base(context, current, original)
+ {
+ this.context = context;
+ }
+
+ protected override void SaveProperties(User user)
+ {
+ base.SaveProperties(user);
+
+ if (this.IsDefaultLanguageSupported)
+ {
+ //If this.currentState.defaultLanguageAlias is , we will get defaultLanguageName as string.Empty
+ string defaultLanguageName = LanguageUtils.GetLanguageNameFromAlias(user.Parent.Parent,
+ this.currentState.defaultLanguageAlias);
+
+ if (!this.Exists || (user.DefaultLanguage.Name != defaultLanguageName)) //comparing name of the language.
+ {
+ //Default language is invalid inside an uncontained database.
+ if (user.Parent.ContainmentType != ContainmentType.None)
+ {
+ //Setting what user has set, i.e. the Alias of the language.
+ user.DefaultLanguage.Name = this.currentState.defaultLanguageAlias;
+ }
+ }
+ }
+ }
+ }
+
+ internal class UserPrototypeForSqlUserWithPassword : UserPrototypeWithDefaultSchema,
+ IUserPrototypeWithDefaultLanguage,
+ IUserPrototypeWithPassword
+ {
+ private CDataContainer context;
+
+ #region IUserPrototypeWithDefaultLanguage Members
+
+ public bool IsDefaultLanguageSupported
+ {
+ get
+ {
+ //Default Language was not supported before Denali.
+ return SqlMgmtUtils.IsSql11OrLater(this.context.Server.ConnectionContext.ServerVersion);
+ }
+ }
+
+ public string DefaultLanguageAlias
+ {
+ get
+ {
+ return this.currentState.defaultLanguageAlias;
+ }
+ set
+ {
+ this.currentState.defaultLanguageAlias = value;
+ }
+ }
+
+ #endregion
+
+ #region IUserPrototypeWithPassword Members
+
+ public SecureString Password
+ {
+ set
+ {
+ this.currentState.password = value;
+ }
+ }
+
+ public SecureString PasswordConfirm
+ {
+ set
+ {
+ this.currentState.passwordConfirm = value;
+ }
+ }
+
+ public SecureString OldPassword
+ {
+ set
+ {
+ this.currentState.oldPassword = value;
+ }
+ }
+
+ public bool IsOldPasswordRequired
+ {
+ get
+ {
+ return this.currentState.isOldPasswordRequired;
+ }
+ set
+ {
+ this.currentState.isOldPasswordRequired = value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor
+ ///
+ /// The context for the dialog
+ public UserPrototypeForSqlUserWithPassword(CDataContainer context,
+ UserPrototypeDataNew current,
+ UserPrototypeDataNew original)
+ : base(context, current, original)
+ {
+ this.context = context;
+ }
+
+ protected override void SaveProperties(User user)
+ {
+ base.SaveProperties(user);
+
+ if (this.IsDefaultLanguageSupported)
+ {
+ //If this.currentState.defaultLanguageAlias is , we will get defaultLanguageName as string.Empty
+ string defaultLanguageName = LanguageUtils.GetLanguageNameFromAlias(user.Parent.Parent,
+ this.currentState.defaultLanguageAlias);
+
+ if (!this.Exists || (user.DefaultLanguage.Name != defaultLanguageName)) //comparing name of the language.
+ {
+ //Default language is invalid inside an uncontained database.
+ if (user.Parent.ContainmentType != ContainmentType.None)
+ {
+ //Setting what user has set, i.e. the Alias of the language.
+ user.DefaultLanguage.Name = this.currentState.defaultLanguageAlias;
+ }
+ }
+ }
+ }
+
+ protected override void CreateOrAlterUser(User user)
+ {
+ if (!this.Exists) //New User
+ {
+ user.Create(this.currentState.password);
+ }
+ else //Existing User
+ {
+ user.Alter();
+
+ if (this.currentState.password != this.originalState.password)
+ {
+ if (this.currentState.isOldPasswordRequired)
+ {
+ user.ChangePassword(this.currentState.oldPassword, this.currentState.password);
+ }
+ else
+ {
+ user.ChangePassword(this.currentState.password);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Used to create or return required UserPrototype objects for a user type.
+ /// This factory class also helps us in maintaining a single set of current data
+ /// and original data mapped to all UserPrototypes.
+ ///
+ /// Also this UserPrototypeFactory is a Singleton object for one datacontainer object.
+ /// Making it Singleton helps us in using same factory object inside other pages too.
+ ///
+ internal class UserPrototypeFactory
+ {
+ private static UserPrototypeFactory singletonInstance;
+
+ private UserPrototypeDataNew currentData;
+ private UserPrototypeDataNew originalData;
+ private CDataContainer context;
+
+ private UserPrototypeNew asymmetricKeyMappedUser;
+ private UserPrototypeNew certificateMappedUser;
+ private UserPrototypeNew loginMappedUser;
+ private UserPrototypeNew noLoginUser;
+ private UserPrototypeNew sqlUserWithPassword;
+ private UserPrototypeNew windowsUser;
+
+ private UserPrototypeNew currentPrototype;
+
+ public UserPrototypeNew CurrentPrototype
+ {
+ get
+ {
+ currentPrototype ??= new UserPrototypeNew(this.context,
+ this.currentData,
+ this.originalData);
+ return currentPrototype;
+ }
+ }
+
+ private UserPrototypeFactory(CDataContainer context)
+ {
+ this.context = context;
+
+ this.originalData = new UserPrototypeDataNew(this.context);
+ this.currentData = this.originalData.Clone();
+ }
+
+ public static UserPrototypeFactory GetInstance(CDataContainer context)
+ {
+ if (singletonInstance != null
+ && singletonInstance.context != context)
+ {
+ singletonInstance = null;
+ }
+
+ singletonInstance ??= new UserPrototypeFactory(context);
+
+ return singletonInstance;
+ }
+
+ public UserPrototypeNew GetUserPrototype(ExhaustiveUserTypes userType)
+ {
+ switch (userType)
+ {
+ case ExhaustiveUserTypes.AsymmetricKeyMappedUser:
+ currentData.userType = UserType.AsymmetricKey;
+ this.asymmetricKeyMappedUser ??= new UserPrototypeNew(this.context, this.currentData, this.originalData);
+ this.currentPrototype = asymmetricKeyMappedUser;
+ break;
+
+ case ExhaustiveUserTypes.CertificateMappedUser:
+ currentData.userType = UserType.Certificate;
+ this.certificateMappedUser ??= new UserPrototypeNew(this.context, this.currentData, this.originalData);
+ this.currentPrototype = certificateMappedUser;
+ break;
+
+ case ExhaustiveUserTypes.LoginMappedUser:
+ currentData.userType = UserType.SqlUser;
+ this.loginMappedUser ??= new UserPrototypeForSqlUserWithLogin(this.context, this.currentData, this.originalData);
+ this.currentPrototype = loginMappedUser;
+ break;
+
+ case ExhaustiveUserTypes.SqlUserWithoutLogin:
+ currentData.userType = UserType.NoLogin;
+ this.noLoginUser ??= new UserPrototypeWithDefaultSchema(this.context, this.currentData, this.originalData);
+ this.currentPrototype = noLoginUser;
+ break;
+
+ case ExhaustiveUserTypes.SqlUserWithPassword:
+ currentData.userType = UserType.SqlUser;
+ this.sqlUserWithPassword ??= new UserPrototypeForSqlUserWithPassword(this.context, this.currentData, this.originalData);
+ this.currentPrototype = sqlUserWithPassword;
+ break;
+
+ case ExhaustiveUserTypes.WindowsUser:
+ currentData.userType = UserType.SqlUser;
+ this.windowsUser ??= new UserPrototypeForWindowsUser(this.context, this.currentData, this.originalData);
+ this.currentPrototype = windowsUser;
+ break;
+
+ default:
+ System.Diagnostics.Debug.Assert(false, "Unknown UserType provided.");
+ this.currentPrototype = null;
+ break;
+ }
+ return this.currentPrototype;
+ }
+ }
+
+ ///
+ /// Lists all types of possible database users.
+ ///
+ internal enum ExhaustiveUserTypes
+ {
+ Unknown,
+ SqlUserWithoutLogin,
+ SqlUserWithPassword,
+ WindowsUser,
+ LoginMappedUser,
+ CertificateMappedUser,
+ AsymmetricKeyMappedUser
+ };
+
+ internal class LanguageUtils
+ {
+ ///
+ /// Gets alias for a language name.
+ ///
+ ///
+ ///
+ /// Returns string.Empty in case it doesn't find a matching languageName on the server
+ public static string GetLanguageAliasFromName(Server connectedServer,
+ string languageName)
+ {
+ string languageAlias = string.Empty;
+
+ SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
+
+ foreach (Language lang in connectedServer.Languages)
+ {
+ if (lang.Name == languageName)
+ {
+ languageAlias = lang.Alias;
+ break;
+ }
+ }
+
+ return languageAlias;
+ }
+
+ ///
+ /// Gets name for a language alias.
+ ///
+ ///
+ ///
+ /// Returns string.Empty in case it doesn't find a matching languageAlias on the server
+ public static string GetLanguageNameFromAlias(Server connectedServer,
+ string languageAlias)
+ {
+ string languageName = string.Empty;
+
+ SetLanguageDefaultInitFieldsForDefaultLanguages(connectedServer);
+
+ foreach (Language lang in connectedServer.Languages)
+ {
+ if (lang.Alias == languageAlias)
+ {
+ languageName = lang.Name;
+ break;
+ }
+ }
+
+ return languageName;
+ }
+
+ ///
+ /// Sets exhaustive fields required for displaying and working with default languages in server,
+ /// database and user dialogs as default init fields so that queries are not sent again and again.
+ ///
+ /// server on which languages will be enumerated
+ public static void SetLanguageDefaultInitFieldsForDefaultLanguages(Server connectedServer)
+ {
+ string[] fieldsNeeded = new string[] { "Alias", "Name", "LocaleID", "LangID" };
+ connectedServer.SetDefaultInitFields(typeof(Language), fieldsNeeded);
+ }
+ }
+
+ internal class ObjectNoLongerExistsException : Exception
+ {
+ private static string ExceptionMessage
+ {
+ get
+ {
+ return "Object no longer exists";
+ }
+ }
+
+ public ObjectNoLongerExistsException()
+ : base(ExceptionMessage)
+ {
+ //
+ // TODO: Add constructor logic here
+ //
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs
new file mode 100644
index 00000000..1171561e
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/LoginTests.cs
@@ -0,0 +1,69 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System.Threading.Tasks;
+using Microsoft.SqlTools.Hosting.Protocol;
+using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
+using Microsoft.SqlTools.ServiceLayer.Security;
+using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Test.Common;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using Moq;
+
+namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
+{
+ ///
+ /// Tests for the Login management component
+ ///
+ public class LoginTests
+ {
+ ///
+ /// Test the basic Create Login method handler
+ ///
+ // [Test]
+ public async Task TestHandleCreateLoginRequest()
+ {
+ using (SelfCleaningTempFile queryTempFile = new SelfCleaningTempFile())
+ {
+ // setup
+ var connectionResult = await LiveConnectionHelper.InitLiveConnectionInfoAsync("master", queryTempFile.FilePath);
+ var loginParams = new CreateLoginParams
+ {
+ OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
+ Login = SecurityTestUtils.GetTestLoginInfo()
+ };
+
+ var createContext = new Mock>();
+ createContext.Setup(x => x.SendResult(It.IsAny()))
+ .Returns(Task.FromResult(new object()));
+
+ // call the create login method
+ SecurityService service = new SecurityService();
+ await service.HandleCreateLoginRequest(loginParams, createContext.Object);
+
+ // verify the result
+ createContext.Verify(x => x.SendResult(It.Is
+ (p => p.Success && p.Login.LoginName != string.Empty)));
+
+ // cleanup created login
+ var deleteParams = new DeleteLoginParams
+ {
+ OwnerUri = connectionResult.ConnectionInfo.OwnerUri,
+ LoginName = loginParams.Login.LoginName
+ };
+
+ var deleteContext = new Mock>();
+ deleteContext.Setup(x => x.SendResult(It.IsAny()))
+ .Returns(Task.FromResult(new object()));
+
+ // call the create login method
+ await service.HandleDeleteLoginRequest(deleteParams, deleteContext.Object);
+
+ // verify the result
+ deleteContext.Verify(x => x.SendResult(It.Is(p => p.Success)));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs
index 1d1329f4..bfeb906d 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Security/SecurityTestUtils.cs
@@ -23,6 +23,28 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Security
return string.Format(@"{0}\{1}", Environment.UserDomainName, Environment.UserName);
}
+ internal static LoginInfo GetTestLoginInfo()
+ {
+ return new LoginInfo()
+ {
+ LoginName = "TestLoginName_" + new Random().NextInt64(10000000,90000000).ToString(),
+ LoginType= LoginType.Sql,
+ CertificateName = "Test Cert",
+ AsymmetricKeyName = "Asymmetric Test Cert",
+ WindowsGrantAccess = true,
+ MustChange = false,
+ IsDisabled = false,
+ IsLockedOut = false,
+ EnforcePolicy = false,
+ EnforceExpiration = false,
+ WindowsAuthSupported = false,
+ Password = "!#!@#@#@dflksdjfksdlfjlksdFEEfjklsed9393",
+ OldPassword = "{{OLD_TEST_PASSWORD_PLACEHOLDER}}",
+ DefaultLanguage = "us_english",
+ DefaultDatabase = "master"
+ };
+ }
+
internal static CredentialInfo GetTestCredentialInfo()
{
return new CredentialInfo()
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
index 99a5d237..fa6d2654 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Microsoft.SqlTools.ServiceLayer.UnitTests.csproj
@@ -5,7 +5,7 @@
$(DefineConstants);NETCOREAPP1_0;TRACE
false
-
+