Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / fx / src / xsp / System / Web / Security / ADMembershipProvider.cs / 1 / ADMembershipProvider.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Web.Security { using System.Net; using System.Web; using System.Text; using System.Text.RegularExpressions; using System.Security; using System.Collections; using System.Globalization; using System.Configuration; using System.DirectoryServices; using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.Protocols; using System.Web.Hosting; using System.Security.Cryptography; using System.Web.Configuration; using System.Security.Permissions; using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Security.Principal; using System.Web.DataAccess; using System.Web.Util; using System.Reflection; using System.Configuration.Provider; using System.Web.Management; public enum ActiveDirectoryConnectionProtection { None = 0, Ssl = 1, SignAndSeal = 2 } internal enum DirectoryType { AD = 0, ADAM = 1, Unknown = 2 } internal enum CredentialsType { Windows = 0, NonWindows = 1 } [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)] [DirectoryServicesPermission(SecurityAction.LinkDemand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public class ActiveDirectoryMembershipProvider : MembershipProvider { // // keeps track of whether the provider has already been initialized // private bool initialized = false; // // configuration parameters common to all membership providers // private string adConnectionString; private bool enablePasswordRetrieval = false; private bool enablePasswordReset; private bool enableSearchMethods; private bool requiresQuestionAndAnswer; private string appName; private bool requiresUniqueEmail; private int maxInvalidPasswordAttempts; private int passwordAttemptWindow; private int passwordAnswerAttemptLockoutDuration; private int minRequiredPasswordLength; private int minRequiredNonalphanumericCharacters; private string passwordStrengthRegularExpression; // // configuration parameters specific to the AD membership provider // and related to the directory connection are stored within the DirectoryInformation class // DirectoryInformation directoryInfo = null; // // custom schema mappings (and their default values) // private string attributeMapUsername = "userPrincipalName"; private string attributeMapEmail = "mail"; private string attributeMapPasswordQuestion = null; private string attributeMapPasswordAnswer = null; private string attributeMapFailedPasswordAnswerCount = null; private string attributeMapFailedPasswordAnswerTime= null; private string attributeMapFailedPasswordAnswerLockoutTime = null; // // maximum lengths for the different string properties // private int maxUsernameLength = 256; private int maxUsernameLengthForCreation = 64; private int maxPasswordLength = 128; private int maxCommentLength = 1024; private int maxEmailLength = 256; private int maxPasswordQuestionLength = 256; private int maxPasswordAnswerLength = 128; // // user account flags // private const int UF_ACCOUNT_DISABLED =0x2; private const int UF_LOCKOUT=0x10; private readonly DateTime DefaultLastLockoutDate = new DateTime(1754, 1, 1, 0, 0, 0, DateTimeKind.Utc); private const int AD_SALT_SIZE_IN_BYTES = 16; // // table containing the valid syntaxes for various attribute mappings // Hashtable syntaxes = new Hashtable(); Hashtable attributesInUse = new Hashtable(StringComparer.OrdinalIgnoreCase); Hashtable userObjectAttributes = null; // // auth type to be used for validation // AuthType authTypeForValidation; LdapConnection connection; bool usernameIsSAMAccountName = false; bool usernameIsUPN = true; // // password size for autogenerating password // private const int PASSWORD_SIZE = 14; public override string ApplicationName { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return appName; } set { throw new NotSupportedException(SR.GetString(SR.ADMembership_Setting_ApplicationName_not_supported)); } } public ActiveDirectoryConnectionProtection CurrentConnectionProtection { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return directoryInfo.ConnectionProtection; } } public override MembershipPasswordFormat PasswordFormat { get { // // AD membership provider does not support password retrieval // (regardless of the settings). As a result the provider operates as // if the password was effectively hashed. // return MembershipPasswordFormat.Hashed; } } public override bool EnablePasswordRetrieval { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enablePasswordRetrieval; } } public override bool EnablePasswordReset { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enablePasswordReset; } } public bool EnableSearchMethods { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enableSearchMethods; } } public override bool RequiresQuestionAndAnswer { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return requiresQuestionAndAnswer; } } public override bool RequiresUniqueEmail { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return requiresUniqueEmail; } } public override int MaxInvalidPasswordAttempts { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return maxInvalidPasswordAttempts; } } public override int PasswordAttemptWindow { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordAttemptWindow; } } public int PasswordAnswerAttemptLockoutDuration { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordAnswerAttemptLockoutDuration; } } public override int MinRequiredPasswordLength { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return minRequiredPasswordLength; } } public override int MinRequiredNonAlphanumericCharacters { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return minRequiredNonalphanumericCharacters; } } public override string PasswordStrengthRegularExpression { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordStrengthRegularExpression; } } // // NOTE: In every method of the provider we need to demand DirectoryServicesPermission (irrespective of // whether the underlying calls to S.DS/S.DS.Protocols result in full demand or link demand for that permission. // Moreover, once we demand the permission, we should also assert it so that S.DS/S.DS.Protocols does not make the // same demand (if we do not assert then in the case of S.DS/S.DS.Protocols making a full demand we would have two stack walks) // #pragma warning disable 0688 [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override void Initialize(string name, NameValueCollection config) { if (System.Web.Hosting.HostingEnvironment.IsHosted) HttpRuntime.CheckAspNetHostingPermission (AspNetHostingPermissionLevel.Low, SR.Feature_not_supported_at_this_level); if (initialized) return; if (config == null) throw new ArgumentNullException("config"); if (String.IsNullOrEmpty(name)) name = "AspNetActiveDirectoryMembershipProvider"; if (string.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", SR.GetString(SR.ADMembership_Description)); } base.Initialize(name, config); appName = config["applicationName"]; if (string.IsNullOrEmpty(appName)) appName = SecUtility.GetDefaultAppName(); if( appName.Length > 256 ) throw new ProviderException(SR.GetString(SR.Provider_application_name_too_long)); string temp = config["connectionStringName"]; if (String.IsNullOrEmpty(temp)) throw new ProviderException(SR.GetString(SR.Connection_name_not_specified)); adConnectionString = GetConnectionString(temp, true); if (String.IsNullOrEmpty(adConnectionString)) throw new ProviderException(SR.GetString(SR.Connection_string_not_found, temp)); // // Get the provider specific configuration settings // // connectionProtection string connProtection = config["connectionProtection"]; if (connProtection == null) connProtection = "Secure"; else { if ((String.Compare(connProtection, "Secure", StringComparison.Ordinal) != 0) && (String.Compare(connProtection, "None", StringComparison.Ordinal) != 0)) throw new ProviderException(SR.GetString(SR.ADMembership_InvalidConnectionProtection, connProtection)); } // // credentials // username and password if specified must not be empty, moreover if one is specified the other must // be specified as well // string username = config["connectionUsername"]; if (username != null && username.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Connection_username_must_not_be_empty)); string password = config["connectionPassword"]; if (password != null && password.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Connection_password_must_not_be_empty)); if ((username != null && password == null) || (password != null && username == null)) throw new ProviderException(SR.GetString(SR.ADMembership_Username_and_password_reqd)); NetworkCredential credential = new NetworkCredential(username, password); int clientSearchTimeout = SecUtility.GetIntValue(config, "clientSearchTimeout", -1, false, 0); int serverSearchTimeout = SecUtility.GetIntValue(config, "serverSearchTimeout", -1, false, 0); enableSearchMethods = SecUtility.GetBooleanValue(config, "enableSearchMethods", false); requiresUniqueEmail = SecUtility.GetBooleanValue(config, "requiresUniqueEmail", false); enablePasswordReset = SecUtility.GetBooleanValue(config, "enablePasswordReset", false); requiresQuestionAndAnswer = SecUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", false); minRequiredPasswordLength = SecUtility.GetIntValue( config, "minRequiredPasswordLength", 7, false, 128 ); minRequiredNonalphanumericCharacters = SecUtility.GetIntValue( config, "minRequiredNonalphanumericCharacters", 1, true, 128 ); passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; if( passwordStrengthRegularExpression != null ) { passwordStrengthRegularExpression = passwordStrengthRegularExpression.Trim(); if( passwordStrengthRegularExpression.Length != 0 ) { try { Regex regex = new Regex( passwordStrengthRegularExpression ); } catch( ArgumentException e ) { throw new ProviderException( e.Message, e ); } } } else { passwordStrengthRegularExpression = string.Empty; } if (minRequiredNonalphanumericCharacters > minRequiredPasswordLength) throw new HttpException(SR.GetString(SR.MinRequiredNonalphanumericCharacters_can_not_be_more_than_MinRequiredPasswordLength)); using (new ApplicationImpersonationContext()) { // // This will make some checks regarding whether the connectionProtection is valid (choose the right // connectionprotection if necessary, make sure credentials are valid, container exists and the directory is // either AD or ADAM) // directoryInfo = new DirectoryInformation(adConnectionString, credential, connProtection, clientSearchTimeout, serverSearchTimeout, enablePasswordReset); // // initialize the syntaxes table // syntaxes.Add("attributeMapUsername", "DirectoryString"); syntaxes.Add("attributeMapEmail", "DirectoryString"); syntaxes.Add("attributeMapPasswordQuestion", "DirectoryString"); syntaxes.Add("attributeMapPasswordAnswer", "DirectoryString"); syntaxes.Add("attributeMapFailedPasswordAnswerCount", "Integer"); syntaxes.Add("attributeMapFailedPasswordAnswerTime", "Integer8"); syntaxes.Add("attributeMapFailedPasswordAnswerLockoutTime", "Integer8"); // // initialize the in use attributes list // attributesInUse.Add("objectclass", null); attributesInUse.Add("objectsid", null); attributesInUse.Add("comment", null); attributesInUse.Add("whencreated", null); attributesInUse.Add("pwdlastset", null); attributesInUse.Add("msds-user-account-control-computed", null); attributesInUse.Add("lockouttime", null); if (directoryInfo.DirectoryType == DirectoryType.AD) attributesInUse.Add("useraccountcontrol", null); else attributesInUse.Add("msds-useraccountdisabled", null); // // initialize the user attributes list // userObjectAttributes = GetUserObjectAttributes(); // // get the username/email schema mappings // int maxLength; string attrMapping = GetAttributeMapping(config, "attributeMapUsername", out maxLength); if (attrMapping != null) { attributeMapUsername = attrMapping; if (maxLength != -1) { if (maxLength < maxUsernameLength) maxUsernameLength = maxLength; if (maxLength < maxUsernameLengthForCreation) maxUsernameLengthForCreation = maxLength; } } attributesInUse.Add(attributeMapUsername, null); if (StringUtil.EqualsIgnoreCase(attributeMapUsername, "sAMAccountName")) { usernameIsSAMAccountName = true; usernameIsUPN = false; } attrMapping = GetAttributeMapping(config, "attributeMapEmail", out maxLength); if (attrMapping != null) { attributeMapEmail = attrMapping; if (maxLength != -1 && maxLength < maxEmailLength) maxEmailLength = maxLength; } attributesInUse.Add(attributeMapEmail, null); // // get max length of "comment" attribute // maxLength = GetRangeUpperForSchemaAttribute("comment"); if (maxLength != -1 && maxLength < maxCommentLength) maxCommentLength = maxLength; // // enablePasswordReset and requiresQuestionAndAnswer should match // if (enablePasswordReset) { // // AD membership provider does not support password reset without question and answer // if (!requiresQuestionAndAnswer) throw new ProviderException(SR.GetString(SR.ADMembership_PasswordReset_without_question_not_supported)); // // Other password reset related attributes // maxInvalidPasswordAttempts = SecUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); passwordAttemptWindow = SecUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); passwordAnswerAttemptLockoutDuration = SecUtility.GetIntValue(config, "passwordAnswerAttemptLockoutDuration", 30, false, 0); // // some more schema mappings that must be specified for Password Reset // attributeMapFailedPasswordAnswerCount = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerCount", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerCount != null) attributesInUse.Add(attributeMapFailedPasswordAnswerCount, null); attributeMapFailedPasswordAnswerTime = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerTime", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerTime != null) attributesInUse.Add(attributeMapFailedPasswordAnswerTime, null); attributeMapFailedPasswordAnswerLockoutTime = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerLockoutTime", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerLockoutTime != null) attributesInUse.Add(attributeMapFailedPasswordAnswerLockoutTime, null); if (attributeMapFailedPasswordAnswerCount == null || attributeMapFailedPasswordAnswerTime == null || attributeMapFailedPasswordAnswerLockoutTime == null) throw new ProviderException(SR.GetString(SR.ADMembership_BadPasswordAnswerMappings_not_specified)); } // // Password Q&A mappings // attributeMapPasswordQuestion = GetAttributeMapping(config, "attributeMapPasswordQuestion", out maxLength); if (attributeMapPasswordQuestion != null) { if (maxLength != -1 && maxLength < maxPasswordQuestionLength) maxPasswordQuestionLength = maxLength; attributesInUse.Add(attributeMapPasswordQuestion, null); } attributeMapPasswordAnswer = GetAttributeMapping(config, "attributeMapPasswordAnswer", out maxLength); if (attributeMapPasswordAnswer != null) { if (maxLength != -1 && maxLength < maxPasswordAnswerLength) maxPasswordAnswerLength = maxLength; attributesInUse.Add(attributeMapPasswordAnswer, null); } if (requiresQuestionAndAnswer) { // // We also need to check that the password question and answer attributes are mapped // if (attributeMapPasswordQuestion == null || attributeMapPasswordAnswer == null) throw new ProviderException(SR.GetString(SR.ADMembership_PasswordQuestionAnswerMapping_not_specified)); } // // the auth type to be used for validation is determined as follows: // if directory is ADAM: authType = AuthType.Basic // if directory is AD: authType is based on connectionProtection (None, SSL: AuthType.Basic; SignAndSeal: AuthType.Negotiate) // if (directoryInfo.DirectoryType == DirectoryType.ADAM) authTypeForValidation = AuthType.Basic; else authTypeForValidation = directoryInfo.GetLdapAuthenticationTypes(directoryInfo.ConnectionProtection, CredentialsType.NonWindows); if (directoryInfo.DirectoryType == DirectoryType.AD) { // // if password reset is enabled we should perform all operations on a single server // if (enablePasswordReset) directoryInfo.SelectServer(); // // if the username is mapped to upn we need to do forest wide search to check the uniqueness of the upn. // and if the username is mapped to samAccountName then we need to append the domain name in the username for reliable validation // directoryInfo.InitializeDomainAndForestName(); } } // // Create a new common ldap connection for validation // connection = directoryInfo.CreateNewLdapConnection(authTypeForValidation); config.Remove("name"); config.Remove("applicationName"); config.Remove("connectionStringName"); config.Remove("requiresUniqueEmail"); config.Remove("enablePasswordReset"); config.Remove("requiresQuestionAndAnswer"); config.Remove("attributeMapPasswordQuestion"); config.Remove("attributeMapPasswordAnswer"); config.Remove("attributeMapUsername"); config.Remove("attributeMapEmail"); config.Remove("connectionProtection"); config.Remove("connectionUsername"); config.Remove("connectionPassword"); config.Remove("clientSearchTimeout"); config.Remove("serverSearchTimeout"); config.Remove("enableSearchMethods"); config.Remove("maxInvalidPasswordAttempts"); config.Remove("passwordAttemptWindow"); config.Remove("passwordAnswerAttemptLockoutDuration"); config.Remove("attributeMapFailedPasswordAnswerCount"); config.Remove("attributeMapFailedPasswordAnswerTime"); config.Remove("attributeMapFailedPasswordAnswerLockoutTime"); config.Remove("minRequiredPasswordLength"); config.Remove("minRequiredNonalphanumericCharacters"); config.Remove("passwordStrengthRegularExpression"); if (config.Count > 0) { string attribUnrecognized = config.GetKey(0); if (!String.IsNullOrEmpty(attribUnrecognized)) throw new ProviderException(SR.GetString(SR.Provider_unrecognized_attribute, attribUnrecognized)); } initialized = true; } #pragma warning restore 0688 [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { status = (MembershipCreateStatus) 0; MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (providerUserKey != null) throw new NotSupportedException(SR.GetString(SR.ADMembership_Setting_UserId_not_supported)); if ((passwordQuestion != null) && (attributeMapPasswordQuestion == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordQ_not_supported)); if ((passwordAnswer != null) && (attributeMapPasswordAnswer == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordA_not_supported)); if(!SecUtility.ValidateParameter(ref username, true, true, true, maxUsernameLengthForCreation)) { status = MembershipCreateStatus.InvalidUserName; return null; } // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) { status = MembershipCreateStatus.InvalidUserName; return null; } if(!ValidatePassword(password, maxPasswordLength)) { status = MembershipCreateStatus.InvalidPassword; return null; } if(!SecUtility.ValidateParameter(ref email, RequiresUniqueEmail, true, false, maxEmailLength)) { status = MembershipCreateStatus.InvalidEmail; return null; } if(!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, maxPasswordQuestionLength)) { status = MembershipCreateStatus.InvalidQuestion; return null; } // validate the parameter before encoding the password answer if(!SecUtility.ValidateParameter(ref passwordAnswer, RequiresQuestionAndAnswer, true, false, maxPasswordAnswerLength)) { status = MembershipCreateStatus.InvalidAnswer; return null; } string encodedPasswordAnswer; if (!string.IsNullOrEmpty(passwordAnswer)) { encodedPasswordAnswer = Encrypt(passwordAnswer); // check length of encoded password answer if (maxPasswordAnswerLength > 0 && encodedPasswordAnswer.Length > maxPasswordAnswerLength) { status = MembershipCreateStatus.InvalidAnswer; return null; } } else encodedPasswordAnswer = passwordAnswer; if( password.Length < MinRequiredPasswordLength ) { status = MembershipCreateStatus.InvalidPassword; return null; } int count = 0; for( int i = 0; i < password.Length; i++ ) { if( !char.IsLetterOrDigit( password, i ) ) { count++; } } if( count < MinRequiredNonAlphanumericCharacters ) { status = MembershipCreateStatus.InvalidPassword; return null; } if( PasswordStrengthRegularExpression.Length > 0 ) { if( !Regex.IsMatch( password, PasswordStrengthRegularExpression ) ) { status = MembershipCreateStatus.InvalidPassword; return null; } } ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true); OnValidatingPassword(e); if(e.Cancel) { status = MembershipCreateStatus.InvalidPassword; return null; } try { // // Get the directory entry for the container and create a user object under it // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.CreationContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = null; DirectoryEntry userEntry = null; try { containerEntry = connection.DirectoryEntry; // to avoid unnecessary searches (for better performance) containerEntry.AuthenticationType |= AuthenticationTypes.FastBind; // // we set the username as the cn // userEntry = containerEntry.Children.Add(GetEscapedRdn("CN=" + username), "user"); // // if we are talking to Active Directory // set the sAMAccountName (if username is not mapped to this attribute, we need to autogenerate it) // (NOTE: We do not need to do this if the domain controller functionality is Windows 2003 (dcLevel = 2)) // if (directoryInfo.DirectoryType == DirectoryType.AD) { string sAMAccountName= null; bool setSAMAccountName = false; if (usernameIsSAMAccountName) { sAMAccountName = username; setSAMAccountName = true; } else { int dcLevel = GetDomainControllerLevel(containerEntry.Options.GetCurrentServerName()); if (dcLevel != 2) { sAMAccountName = GenerateAccountName(); setSAMAccountName = true; } } if (setSAMAccountName) userEntry.Properties["sAMAccountName"].Value = sAMAccountName; } // // if username is mapped to userPrincipalName and we are talking to AD, we need to do // a GC search to find if the same upn already exists or not // On ADAM, uniqueness of userPrincipalName is enforced on the server itself across all partitions // if (usernameIsUPN) { if (directoryInfo.DirectoryType == DirectoryType.AD && !IsUpnUnique(username)) { status = MembershipCreateStatus.DuplicateUserName; return null; } userEntry.Properties["userPrincipalName"].Value = username; } // // set other attributes // if (email != null) { if (RequiresUniqueEmail && !IsEmailUnique(containerEntry, username, email, false /* existing */)) { status = MembershipCreateStatus.DuplicateEmail; return null; } userEntry.Properties[attributeMapEmail].Value = email; } if (passwordQuestion != null) userEntry.Properties[attributeMapPasswordQuestion].Value = passwordQuestion; if (passwordAnswer != null) userEntry.Properties[attributeMapPasswordAnswer].Value = encodedPasswordAnswer; // // commit the user object // try { userEntry.CommitChanges(); } catch (COMException e1) { if ((e1.ErrorCode == unchecked((int) 0x80071392)) || (e1.ErrorCode == unchecked((int) 0x8007200d))) { status = MembershipCreateStatus.DuplicateUserName; return null; } else if ((e1.ErrorCode == unchecked((int) 0x8007001f)) && (e1 is DirectoryServicesCOMException)) { // // this error corresponds to LDAP_OTHER // if username was mapped to sAMAccountName and the name is too long // then the extended error should be 1315 (ERROR_INVALID_ACCOUNT_NAME) // DirectoryServicesCOMException dsce = e1 as DirectoryServicesCOMException; if (dsce.ExtendedError == 1315) { status = MembershipCreateStatus.InvalidUserName; return null; } else throw; } else throw; } // // set the password // try { SetPasswordPortIfApplicable(userEntry); // // Set the password // userEntry.Invoke("SetPassword", new object[]{ password }); // // if the user is approved then we need to enable the account (disabled dy default) // if (isApproved) { if (directoryInfo.DirectoryType == DirectoryType.AD) { const int UF_ACCOUNT_DISABLED =0x2; const int UF_PASSWD_NOTREQD = 0x20; int val = (int)PropertyManager.GetPropertyValue(userEntry, "userAccountControl"); val &= ~(UF_ACCOUNT_DISABLED | UF_PASSWD_NOTREQD); userEntry.Properties["userAccountControl"].Value = val; } else { // ADAM case userEntry.Properties["msDS-UserAccountDisabled"].Value = false; } userEntry.CommitChanges(); } else { // // For ADAM the user may be created as enabled in some cases // so we need to explicitly disable it // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { userEntry.Properties["msDS-UserAccountDisabled"].Value = true; userEntry.CommitChanges(); } } // // For ADAM users, we need to add the user to the Readers group in that // partition // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { DirectoryEntry readersEntry = new DirectoryEntry(directoryInfo.GetADsPath("CN=Readers,CN=Roles," + directoryInfo.ADAMPartitionDN), directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); readersEntry.Properties["member"].Add(PropertyManager.GetPropertyValue(userEntry, "distinguishedName")); readersEntry.CommitChanges(); } } // // At this point we have already created the user object in AD/ADAM but we // have failed in either SetPassword or while enabling/disabling the user, so we try to delete the user object // catch (COMException) { containerEntry.Children.Remove(userEntry); throw; } catch (ProviderException) { containerEntry.Children.Remove(userEntry); throw; } catch (TargetInvocationException tie) { containerEntry.Children.Remove(userEntry); if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // status as InvalidPassword // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) { status = MembershipCreateStatus.InvalidPassword; return null; } // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // else if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } // // Create a user object // DirectoryEntry dummyEntry = null; bool dummyFlag = false; string dummyString; user = FindUser(userEntry, "(objectClass=*)", System.DirectoryServices.SearchScope.Base, false /*retrieveSAMAccountName */, out dummyEntry, out dummyFlag, out dummyString); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); // // if there are no mappings for password question and answer, we should throw a NotSupportedException // if ((newPasswordQuestion != null) && (attributeMapPasswordQuestion == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordQ_not_supported)); if ((newPasswordAnswer != null) && (attributeMapPasswordAnswer == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordA_not_supported)); CheckUserName( ref username, maxUsernameLength, "username" ); CheckPassword(password, maxPasswordLength, "password"); SecUtility.CheckParameter( ref newPasswordQuestion, RequiresQuestionAndAnswer, true, false, maxPasswordQuestionLength, "newPasswordQuestion" ); // validate the parameter before encoding the password answer CheckPasswordAnswer(ref newPasswordAnswer, RequiresQuestionAndAnswer, maxPasswordAnswerLength, "newPasswordAnswer"); string encodedPasswordAnswer; if (!string.IsNullOrEmpty(newPasswordAnswer)) { encodedPasswordAnswer = Encrypt(newPasswordAnswer); // check length of encoded password answer if (maxPasswordAnswerLength > 0 && encodedPasswordAnswer.Length > maxPasswordAnswerLength) throw new ArgumentException(SR.GetString(SR.ADMembership_Parameter_too_long, "newPasswordAnswer"), "newPasswordAnswer"); } else encodedPasswordAnswer = newPasswordAnswer; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, then simple bind will fail as it needs domain information. // To workaround this whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for authenticating the user. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } // // validate the user's credentials // if (!ValidateCredentials(usernameForAuthentication, password)) return false; if (EnablePasswordReset && resetBadPasswordAnswerAttributes) { // // user supplied correct password, so we need to reset the password answer tracking info // (NOTE: The reason we do not call the Reset method here is so that we can make all the modifications in one transaction) // userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; } if (newPasswordQuestion == null) { // set it to null only if it already exists if ((attributeMapPasswordQuestion != null) && (userEntry.Properties.Contains(attributeMapPasswordQuestion))) userEntry.Properties[attributeMapPasswordQuestion].Clear(); } else userEntry.Properties[attributeMapPasswordQuestion].Value = newPasswordQuestion; if (newPasswordAnswer == null) { // set it to null only if it already exists if ((attributeMapPasswordAnswer != null) && (userEntry.Properties.Contains(attributeMapPasswordAnswer))) userEntry.Properties[attributeMapPasswordAnswer].Clear(); } else userEntry.Properties[attributeMapPasswordAnswer].Value = encodedPasswordAnswer; userEntry.CommitChanges(); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password question and answer changed successfully // return true; } public override string GetPassword(string username, string passwordAnswer) { // // ADMembership Provider does not support password retrieval // throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordRetrieval_not_supported_AD)); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool ChangePassword(string username, string oldPassword, string newPassword) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username" ); CheckPassword(oldPassword, maxPasswordLength, "oldPassword"); CheckPassword(newPassword, maxPasswordLength, "newPassword"); if( newPassword.Length < MinRequiredPasswordLength ) { throw new ArgumentException(SR.GetString( SR.Password_too_short, "newPassword", MinRequiredPasswordLength.ToString(CultureInfo.InvariantCulture))); } int count = 0; for( int i = 0; i < newPassword.Length; i++ ) { if( !char.IsLetterOrDigit( newPassword, i ) ) { count++; } } if( count < MinRequiredNonAlphanumericCharacters ) { throw new ArgumentException(SR.GetString( SR.Password_need_more_non_alpha_numeric_chars, "newPassword", MinRequiredNonAlphanumericCharacters.ToString(CultureInfo.InvariantCulture))); } if( PasswordStrengthRegularExpression.Length > 0 ) { if( !Regex.IsMatch( newPassword, PasswordStrengthRegularExpression ) ) { throw new ArgumentException(SR.GetString(SR.Password_does_not_match_regular_expression, "newPassword")); } } ValidatePasswordEventArgs e = new ValidatePasswordEventArgs( username, newPassword, false ); OnValidatingPassword(e); if(e.Cancel) { if(e.FailureInformation != null) throw e.FailureInformation; else throw new ArgumentException(SR.GetString( SR.Membership_Custom_Password_Validation_Failure), "newPassword"); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, the S.DS(adsi) will pass NULL // domain name to the underlying wldap32 layer. This results in authentication failure even for valid credentials. To workaround this // whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for changing the password. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry (Also get sAMAccountName if needed) // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } // // associate the user's context with the directory entry // userEntry.Username = (usernameIsSAMAccountName) ? directoryInfo.DomainName + "\\" + usernameForAuthentication : usernameForAuthentication; userEntry.Password = oldPassword; userEntry.AuthenticationType = directoryInfo.GetAuthenticationTypes(directoryInfo.ConnectionProtection, (directoryInfo.DirectoryType == DirectoryType.AD) ? CredentialsType.Windows : CredentialsType.NonWindows); try { SetPasswordPortIfApplicable(userEntry); // // Change the password // userEntry.Invoke("ChangePassword", new object[]{ oldPassword, newPassword }); } catch (COMException e2) { if (e2.ErrorCode == unchecked((int) 0x8007052e)) return false; else throw; } catch (TargetInvocationException tie) { if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // MembershipPasswordException // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) throw new MembershipPasswordException(SR.GetString(SR.Membership_InvalidPassword), ce); // // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // else if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } if (EnablePasswordReset && resetBadPasswordAnswerAttributes) { // // associate the process context with the directory entry // userEntry.Username = directoryInfo.GetUsername(); userEntry.Password = directoryInfo.GetPassword(); userEntry.AuthenticationType = directoryInfo.AuthenticationTypes; // // user supplied correct password, so we need to reset the password answer tracking info // ResetBadPasswordAnswerAttributes(userEntry); } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password changed successfully // return true; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override string ResetPassword(string username, string passwordAnswer) { string newPassword = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnablePasswordReset) throw new NotSupportedException(SR.GetString(SR.Not_configured_to_support_password_resets)); CheckUserName(ref username, maxUsernameLength, "username"); CheckPasswordAnswer(ref passwordAnswer, RequiresQuestionAndAnswer, maxPasswordAnswerLength, "passwordAnswer"); try { // // validate the password answer // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; try { // // get the user's directory entry // MembershipUser user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); // // user does not exist, throw exception // if (user == null) throw new ProviderException(SR.GetString(SR.Membership_UserNotFound)); // // if user is locked, throw an exception // if (user.IsLockedOut) throw new MembershipPasswordException(SR.GetString(SR.Membership_AccountLockOut)); string storedPasswordAnswer = Decrypt((string) PropertyManager.GetPropertyValue(userEntry, attributeMapPasswordAnswer)); if (!StringUtil.EqualsIgnoreCase(passwordAnswer, storedPasswordAnswer)) { UpdateBadPasswordAnswerAttributes(userEntry); throw new MembershipPasswordException(SR.GetString(SR.Membership_WrongAnswer)); } else { if (resetBadPasswordAnswerAttributes) ResetBadPasswordAnswerAttributes(userEntry); } SetPasswordPortIfApplicable(userEntry); // // Reset the password (generating a random new password) // newPassword = GeneratePassword(); ValidatePasswordEventArgs e = new ValidatePasswordEventArgs( username, newPassword, false ); OnValidatingPassword(e); if(e.Cancel) { if(e.FailureInformation != null) throw e.FailureInformation; else throw new ProviderException(SR.GetString( SR.Membership_Custom_Password_Validation_Failure)); } userEntry.Invoke("SetPassword", new object[]{ newPassword }); } catch (TargetInvocationException tie) { if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // ProviderException // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) throw new ProviderException(SR.GetString(SR.ADMembership_Generated_password_not_complex), ce); // // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password was reset successfully, return the generated password // return newPassword; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool UnlockUser(string username) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName( ref username, maxUsernameLength, "username" ); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; try { // // get the user's directory entry // userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); // // user does not exist, return false // if (userEntry == null) return false; userEntry.Properties["lockoutTime"].Value = 0; if (EnablePasswordReset) { userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; } userEntry.CommitChanges(); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // user unlocked successfully, return true // return true; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override void UpdateUser(MembershipUser user) { bool emailModified = true; bool commentModified = true; bool isApprovedModified = true; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if( user == null ) { throw new ArgumentNullException("user" ); } ActiveDirectoryMembershipUser adUser = user as ActiveDirectoryMembershipUser; if (adUser != null) { // // check which fields have really been modified // emailModified = adUser.emailModified; commentModified = adUser.commentModified; isApprovedModified = adUser.isApprovedModified; } string temp = user.UserName; CheckUserName( ref temp, maxUsernameLength, "UserName" ); string email = user.Email; if (emailModified) SecUtility.CheckParameter( ref email, RequiresUniqueEmail, true, false, maxEmailLength, "Email"); if (commentModified && user.Comment != null) { if (user.Comment.Length == 0) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, "Comment"), "Comment"); if (maxCommentLength > 0 && user.Comment.Length > maxCommentLength) throw new ArgumentException(SR.GetString(SR.Parameter_too_long, "Comment", maxCommentLength.ToString(CultureInfo.InvariantCulture)), "Comment"); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; try { // // get the user's directory entry // userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(user.UserName) + ")"); if (userEntry == null) throw new ProviderException(SR.GetString(SR.Membership_UserNotFound)); if (!((emailModified) || (commentModified) || (isApprovedModified))) // nothing has been modified return; // // update the email // if enableUniqueEmail is specified, we need to ensure that the email is unique // if (emailModified) { if (email == null) { // set the email to null only if email already exists if (userEntry.Properties.Contains(attributeMapEmail)) userEntry.Properties[attributeMapEmail].Clear(); } else { if (RequiresUniqueEmail && !IsEmailUnique(null, user.UserName, email, true /* existing */)) throw new ProviderException(SR.GetString(SR.Membership_DuplicateEmail)); userEntry.Properties[attributeMapEmail].Value = email; } } // // update the comment // if (commentModified) { if (user.Comment == null) { // set the comment to null only if comment already exists if (userEntry.Properties.Contains("comment")) userEntry.Properties["comment"].Clear(); } else { // // we use the original value ("user.Comment") to preserve all white space // (including leading and trailing white space) userEntry.Properties["comment"].Value = user.Comment; } } // // update the IsApproved field // if (isApprovedModified) { if (directoryInfo.DirectoryType == DirectoryType.AD) { // userAccountControl attribute const int UF_ACCOUNT_DISABLED =0x2; int val = (int)PropertyManager.GetPropertyValue(userEntry, "userAccountControl"); if (user.IsApproved) val &= ~UF_ACCOUNT_DISABLED; else val |= UF_ACCOUNT_DISABLED; userEntry.Properties["userAccountControl"].Value = val; } else { // different attribute for ADAM userEntry.Properties["msDS-UserAccountDisabled"].Value = !(user.IsApproved); } } userEntry.CommitChanges(); if (adUser != null) { adUser.emailModified = false; adUser.commentModified = false; adUser.isApprovedModified = false; } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return; } [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.LinkDemand, Unrestricted=true)] public override bool ValidateUser(string username, string password) { if( ValidateUserCore(username, password)) { PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_SUCCESS); WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationSuccess, username); return true; } else { PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_FAIL); WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationFailure, username); return false; } } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] private bool ValidateUserCore(string username, string password) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if(!SecUtility.ValidateParameter(ref username, true, true, true, maxUsernameLength)) { return false; } // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) { return false; } if( !ValidatePassword(password, maxPasswordLength)) { return false; } bool result = false; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, then simple bind will fail as it needs domain information. // To workaround this whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for authenticating the user. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } result = ValidateCredentials(usernameForAuthentication, password); if (EnablePasswordReset && result && resetBadPasswordAnswerAttributes) { // // user supplied correct password, so we need to reset the password answer tracking info // ResetBadPasswordAnswerAttributes(userEntry); } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return result; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if( providerUserKey == null ) { throw new ArgumentNullException( "providerUserKey" ); } if ( !( providerUserKey is SecurityIdentifier) ) { throw new ArgumentException( SR.GetString( SR.ADMembership_InvalidProviderUserKey ), "providerUserKey" ); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { // // Search for the user and return a MembershipUser object // SecurityIdentifier sid = providerUserKey as SecurityIdentifier; StringBuilder sidHexValueStr = new StringBuilder(); int binaryLength = sid.BinaryLength; byte[] sidBinaryForm = new byte[binaryLength]; sid.GetBinaryForm(sidBinaryForm, 0); for (int i = 0; i < binaryLength; i++) { sidHexValueStr.Append("\\"); sidHexValueStr.Append(sidBinaryForm[i].ToString("x2", NumberFormatInfo.InvariantInfo)); } DirectoryEntry dummyEntry; bool resetBadPasswordAnswerAttributes = false; user = FindUser(containerEntry, "(" + attributeMapUsername + "=*)(objectSid=" + sidHexValueStr.ToString() + ")", out dummyEntry /* ignored */, out resetBadPasswordAnswerAttributes /* ignored */); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser GetUser(string username, bool userIsOnline) { MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username" ); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { // // Search for the user and return a MembershipUser object // DirectoryEntry dummyEntry; bool resetBadPasswordAnswerAttributes = false; user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out dummyEntry /*ignored */, out resetBadPasswordAnswerAttributes /* ignored */); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override string GetUserNameByEmail(string email) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); SecUtility.CheckParameter(ref email, false, true, false, maxEmailLength, "email"); string username = null; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; SearchResultCollection resCol = null; try { DirectorySearcher searcher = new DirectorySearcher(containerEntry); if (email != null) searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) +"))"; else searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(!(" + attributeMapEmail + "=" +"*)))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; searcher.PropertiesToLoad.Add(attributeMapUsername); if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); resCol = searcher.FindAll(); bool userFound = false; foreach (SearchResult res in resCol) { if (!userFound) { username = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapUsername); userFound = true; if (!RequiresUniqueEmail) break; } else { if (RequiresUniqueEmail) { // there is a duplicate entry, so we need to throw an ProviderException throw new ProviderException(SR.GetString(SR.Membership_more_than_one_user_with_email)); } else // we should never get here break; } } } finally { if (resCol != null) resCol.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return username; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool DeleteUser(string username, bool deleteAllRelatedData) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username"); try { // // Get the Directory Entry for the container // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.CreationContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; // to avoid unnecessary searches (for better performance) containerEntry.AuthenticationType |= AuthenticationTypes.FastBind; DirectoryEntry userEntry = null; try { // // Get the directory entry for the user // string dummyString; userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", System.DirectoryServices.SearchScope.OneLevel, false /* retrieveSAMAccountName */, out dummyString); if (userEntry == null) return false; // // Remove the entry from the container // containerEntry.Children.Remove(userEntry); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80072030)) { // // incase some one else deleted the object just before this // return false; } else throw; } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return true; } public virtual string GeneratePassword() { // // return Membership.GeneratePassword( MinRequiredPasswordLength < PASSWORD_SIZE ? PASSWORD_SIZE : MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { return FindUsersByName("*", pageIndex, pageSize, out totalRecords); } public override int GetNumberOfUsersOnline() { // // ADMembershipProvider does not support the notion of online users // throw new NotSupportedException(SR.GetString(SR.ADMembership_OnlineUsers_not_supported)); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnableSearchMethods) throw new NotSupportedException(SR.GetString(SR.ADMembership_Provider_SearchMethods_not_supported)); SecUtility.CheckParameter( ref usernameToMatch, true, true, true, maxUsernameLength, "usernameToMatch" ); if ( pageIndex < 0 ) throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex"); if ( pageSize < 1 ) throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize"); long upperBound = (long)pageIndex * pageSize + pageSize - 1; if ( upperBound > Int32.MaxValue ) throw new ArgumentException(SR.GetString(SR.PageIndex_PageSize_bad), "pageIndex and pageSize"); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { totalRecords = 0; return FindUsers(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(usernameToMatch, false) + ")", attributeMapUsername, pageIndex, pageSize, out totalRecords); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnableSearchMethods) throw new NotSupportedException(SR.GetString(SR.ADMembership_Provider_SearchMethods_not_supported)); SecUtility.CheckParameter(ref emailToMatch, false, true, false, maxEmailLength, "emailToMatch"); if ( pageIndex < 0 ) throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex"); if ( pageSize < 1 ) throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize"); long upperBound = (long)pageIndex * pageSize + pageSize - 1; if ( upperBound > Int32.MaxValue ) throw new ArgumentException(SR.GetString(SR.PageIndex_PageSize_bad), "pageIndex and pageSize"); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { totalRecords = 0; string filter = null; if (emailToMatch != null) filter = "(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(emailToMatch, false) +")"; else filter = "(" + attributeMapUsername + "=*)(!(" + attributeMapEmail + "=" +"*))"; return FindUsers(containerEntry, filter, attributeMapEmail, pageIndex, pageSize, out totalRecords); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } } private bool ValidateCredentials(string username, string password) { bool result = false; NetworkCredential credentialForValidation = (usernameIsSAMAccountName) ? new NetworkCredential(username, password, directoryInfo.DomainName) : DirectoryInformation.GetCredentialsWithDomain(new NetworkCredential(username, password)); // // NOTE: we do not need to revert context here since this method is always // called with explicit credentials // // // if this is concurrent bind (use the common connection) // if (directoryInfo.ConcurrentBindSupported) { try { connection.Bind(credentialForValidation); result = true; } catch (LdapException e) { if (e.ErrorCode == 0x31) { // // authentication failure, invalid user // result = false; } else { // // some other failure // throw; } } } else { // // create a new ldap connection // LdapConnection newConnection = directoryInfo.CreateNewLdapConnection(authTypeForValidation); try { newConnection.Bind(credentialForValidation); result = true; } catch (LdapException e2) { if (e2.ErrorCode == 0x31) { // // authentication failure, invalid user // result = false; } else { // // some other failure // throw; } } finally { newConnection.Dispose(); } } return result; } private DirectoryEntry FindUserEntryAndSAMAccountName(DirectoryEntry containerEntry, string filter, out string sAMAccountName) { return FindUserEntry(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, true /*retrieveSAMAccountName */, out sAMAccountName); } private DirectoryEntry FindUserEntry(DirectoryEntry containerEntry, string filter) { string dummyString; return FindUserEntry(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, false /*retrieveSAMAccountName */, out dummyString); } private DirectoryEntry FindUserEntry(DirectoryEntry containerEntry, string filter, System.DirectoryServices.SearchScope searchScope, bool retrieveSAMAccountName, out string sAMAccountName) { Debug.Assert(containerEntry != null); DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = searchScope; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); if (retrieveSAMAccountName) searcher.PropertiesToLoad.Add("sAMAccountName"); SearchResult res = searcher.FindOne(); sAMAccountName = null; if (res != null) { if (retrieveSAMAccountName) sAMAccountName = (string) PropertyManager.GetSearchResultPropertyValue(res, "sAMAccountName"); return res.GetDirectoryEntry(); } else return null; } private MembershipUser FindUserAndSAMAccountName(DirectoryEntry containerEntry, string filter, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes, out string sAMAccountName) { return FindUser(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, true /* retrieveSAMAccountName */, out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); } private MembershipUser FindUser(DirectoryEntry containerEntry, string filter, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes) { string dummyString; return FindUser(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, false /* retrieveSAMAccountName */, out userEntry, out resetBadPasswordAnswerAttributes, out dummyString); } private MembershipUser FindUser(DirectoryEntry containerEntry, string filter, System.DirectoryServices.SearchScope searchScope, bool retrieveSAMAccountName, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes, out string sAMAccountName) { Debug.Assert(containerEntry != null); MembershipUser user = null; DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = searchScope; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); // // load all the attributes needed to create a MembershipUser object // searcher.PropertiesToLoad.Add(attributeMapUsername); searcher.PropertiesToLoad.Add("objectSid"); searcher.PropertiesToLoad.Add(attributeMapEmail); searcher.PropertiesToLoad.Add("comment"); searcher.PropertiesToLoad.Add("whenCreated"); searcher.PropertiesToLoad.Add("pwdLastSet"); searcher.PropertiesToLoad.Add("msDS-User-Account-Control-Computed"); searcher.PropertiesToLoad.Add("lockoutTime"); if (retrieveSAMAccountName) searcher.PropertiesToLoad.Add("sAMAccountName"); if (attributeMapPasswordQuestion != null) searcher.PropertiesToLoad.Add(attributeMapPasswordQuestion); if (directoryInfo.DirectoryType == DirectoryType.AD) searcher.PropertiesToLoad.Add("userAccountControl"); else searcher.PropertiesToLoad.Add("msDS-UserAccountDisabled"); if (EnablePasswordReset) { searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerCount); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerTime); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerLockoutTime); } SearchResult res = searcher.FindOne(); resetBadPasswordAnswerAttributes = false; sAMAccountName = null; if (res != null) { user = GetMembershipUserFromSearchResult(res); userEntry = res.GetDirectoryEntry(); if (retrieveSAMAccountName) sAMAccountName = (string) PropertyManager.GetSearchResultPropertyValue(res, "sAMAccountName"); if ((EnablePasswordReset) && res.Properties.Contains(attributeMapFailedPasswordAnswerCount)) resetBadPasswordAnswerAttributes = ((int) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerCount) > 0); } else { userEntry = null; } return user; } private MembershipUserCollection FindUsers(DirectoryEntry containerEntry, string filter, string sortKey, int pageIndex, int pageSize, out int totalRecords) { Debug.Assert(containerEntry != null); MembershipUserCollection col = new MembershipUserCollection(); int lastOffset = (pageIndex + 1) * pageSize; int startOffset = lastOffset -pageSize + 1; DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); // // load all the attributes needed to create a MembershipUser object // searcher.PropertiesToLoad.Add(attributeMapUsername); searcher.PropertiesToLoad.Add("objectSid"); searcher.PropertiesToLoad.Add(attributeMapEmail); searcher.PropertiesToLoad.Add("comment"); searcher.PropertiesToLoad.Add("whenCreated"); searcher.PropertiesToLoad.Add("pwdLastSet"); searcher.PropertiesToLoad.Add("msDS-User-Account-Control-Computed"); searcher.PropertiesToLoad.Add("lockoutTime"); if (attributeMapPasswordQuestion != null) searcher.PropertiesToLoad.Add(attributeMapPasswordQuestion); if (directoryInfo.DirectoryType == DirectoryType.AD) searcher.PropertiesToLoad.Add("userAccountControl"); else searcher.PropertiesToLoad.Add("msDS-UserAccountDisabled"); if (EnablePasswordReset) { searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerCount); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerTime); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerLockoutTime); } // // turn on paging // searcher.PageSize = 512; // // need to sort the users based on the attribute that is mapped to the username // searcher.Sort = new SortOption(sortKey, SortDirection.Ascending); SearchResultCollection resCol = searcher.FindAll(); try { int count = 0; totalRecords = 0; foreach(SearchResult res in resCol) { count++; // // add only the requested window of the result set // if (count >= startOffset && count <= lastOffset) { col.Add(GetMembershipUserFromSearchResult(res)); } } totalRecords = count; } finally { resCol.Dispose(); } return col; } private void CheckPasswordAnswer(ref string passwordAnswer, bool checkForNull, int maxSize,string paramName) { if (passwordAnswer == null) { if (checkForNull) throw new ArgumentNullException(paramName); return; } passwordAnswer = passwordAnswer.Trim(); if (passwordAnswer.Length < 1) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, paramName), paramName); if (maxSize > 0 && passwordAnswer.Length > maxSize) throw new ArgumentException(SR.GetString(SR.ADMembership_Parameter_too_long, paramName), paramName); } private bool ValidatePassword(string password, int maxSize) { if (password == null) return false; if (password.Trim().Length < 1) return false; if (maxSize > 0 && password.Length > maxSize) return false; return true; } private void CheckPassword(string password, int maxSize, string paramName) { if (password == null) throw new ArgumentNullException(paramName); if (password.Trim().Length < 1) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, paramName), paramName); if (maxSize > 0 && password.Length > maxSize) throw new ArgumentException(SR.GetString(SR.Parameter_too_long, paramName, maxSize.ToString(CultureInfo.InvariantCulture)), paramName); } private void CheckUserName(ref string username, int maxSize, string paramName) { SecUtility.CheckParameter( ref username, true, true, true, maxSize, paramName ); // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) throw new ArgumentException(SR.GetString(SR.ADMembership_UPN_contains_backslash, paramName), paramName); } private int GetDomainControllerLevel(string serverName) { int dcLevel = 0; DirectoryEntry rootdse = new DirectoryEntry("LDAP://" + serverName + "/RootDSE", directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); string dcLevelString = (string) rootdse.Properties["domainControllerFunctionality"].Value; if (dcLevelString != null) dcLevel = Int32.Parse(dcLevelString, NumberFormatInfo.InvariantInfo); return dcLevel; } private void UpdateBadPasswordAnswerAttributes(DirectoryEntry userEntry) { // // get the password answer tracking related attributes to determine if we are still in an // active window for bad password answer attempts // int badPasswordAttemptCount = 0; bool inActiveWindow = false; DateTime currentTime = DateTime.UtcNow; if (userEntry.Properties.Contains(attributeMapFailedPasswordAnswerTime)) { DateTime lastBadPasswordAnswerTime = GetDateTimeFromLargeInteger((NativeComInterfaces.IAdsLargeInteger) PropertyManager.GetPropertyValue(userEntry, attributeMapFailedPasswordAnswerTime)); TimeSpan diffTime = currentTime.Subtract(lastBadPasswordAnswerTime); inActiveWindow = (diffTime <= new TimeSpan(0, PasswordAttemptWindow, 0)); } // get the current bad password count int currentBadPasswordAttemptCount = 0; if (userEntry.Properties.Contains(attributeMapFailedPasswordAnswerCount)) currentBadPasswordAttemptCount = (int) PropertyManager.GetPropertyValue(userEntry, attributeMapFailedPasswordAnswerCount); if (inActiveWindow && (currentBadPasswordAttemptCount > 0)) { // within an active window for bad password answer attempts (increment count, if greater than 0) badPasswordAttemptCount = currentBadPasswordAttemptCount + 1; } else { // start a new active window (set count = 1) badPasswordAttemptCount = 1; } // set the bad password attempt count and time userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = badPasswordAttemptCount; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = GetLargeIntegerFromDateTime(currentTime); if (badPasswordAttemptCount >= maxInvalidPasswordAttempts) { // // user needs to be locked out due to too many bad password answer attempts // userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = GetLargeIntegerFromDateTime(currentTime); } userEntry.CommitChanges(); } private void ResetBadPasswordAnswerAttributes(DirectoryEntry userEntry) { // // clear the password answer tracking related attributes (reset the window) // userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; userEntry.CommitChanges(); } private MembershipUser GetMembershipUserFromSearchResult(SearchResult res) { // username string username = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapUsername); // providerUserKey is the SID of the user byte[] sidBinaryForm = (byte[]) PropertyManager.GetSearchResultPropertyValue(res, "objectSid"); object providerUserKey = new SecurityIdentifier(sidBinaryForm, 0); // email (optional) string email = (res.Properties.Contains(attributeMapEmail)) ? (string) res.Properties[attributeMapEmail][0] : null; // passwordQuestion string passwordQuestion = null; if ((attributeMapPasswordQuestion != null) && (res.Properties.Contains(attributeMapPasswordQuestion))) passwordQuestion = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapPasswordQuestion); //comment (optional) string comment = (res.Properties.Contains("comment")) ? (string) res.Properties["comment"][0] : null; //isApproved and isLockedOut bool isApproved; bool isLockedOut = false; if (directoryInfo.DirectoryType == DirectoryType.AD) { int val = (int) PropertyManager.GetSearchResultPropertyValue(res, "userAccountControl"); if ((val & UF_ACCOUNT_DISABLED) == 0) isApproved = true; else isApproved = false; // // the "msDS-User-Account-Control-Computed" is the correct attribute to determine if the // user is locked out or not. This attribute does not exist in W2K schema, so if we do not see this attribute in the result set // we will use the "lockoutTime". Note, if the user is not locked out and the schema is W2K3, this attribute will exist in the result // and have value 0 (since it's constructed), therefore absence of the attribute signifies that schema is W2K. // if (res.Properties.Contains("msDS-User-Account-Control-Computed")) { int val2 = (int) PropertyManager.GetSearchResultPropertyValue(res, "msDS-User-Account-Control-Computed"); if ((val2 & UF_LOCKOUT) != 0) isLockedOut = true; } else if (res.Properties.Contains("lockoutTime")) { // NOTE: all date-time computation is done in UTC time though the values returned are in local time DateTime lockoutTime = DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime")); DateTime currentTime = DateTime.UtcNow; TimeSpan diffTime = currentTime.Subtract(lockoutTime); isLockedOut = (diffTime <= directoryInfo.ADLockoutDuration); } } else { isApproved = true; // if the msDS-UserAccountDisabled attribute if not present then the user is enabled if (res.Properties.Contains("msDS-UserAccountDisabled")) isApproved = !((bool) PropertyManager.GetSearchResultPropertyValue(res, "msDS-UserAccountDisabled")); // // ADAM schema contains the "msDS-User-Account-Control-Computed" attribute, therefore it is used to determine the // lockout status of the user // int val2 = (int) PropertyManager.GetSearchResultPropertyValue(res, "msDS-User-Account-Control-Computed"); if ((val2 & UF_LOCKOUT) != 0) isLockedOut = true; } // lastLockoutDate (DateTime.FromFileTime cnoverts to Local time) DateTime lastLockoutDate = DefaultLastLockoutDate; if (isLockedOut) lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime")); // // if password reset is enabled, we need to check if user is locked out due to bad password answer (and set/change the last lockout date) // if ((EnablePasswordReset) && (res.Properties.Contains(attributeMapFailedPasswordAnswerLockoutTime))) { // NOTE: all date-time computation is done in UTC time though the values returned are in local time DateTime badPasswordAnswerLockoutTime = DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); DateTime currentTime = DateTime.UtcNow; TimeSpan diffTime = currentTime.Subtract(badPasswordAnswerLockoutTime); bool isLockedOutByBadPasswordAnswer = (diffTime <= new TimeSpan(0, PasswordAnswerAttemptLockoutDuration, 0)); if (isLockedOutByBadPasswordAnswer) { if (isLockedOut) { // // The account is locked both due to bad password and bad password answer, so we have two lockout dates // Taking the later one. // if (DateTime.Compare(badPasswordAnswerLockoutTime, DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime"))) > 0) lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); } else { // // Account is locked out only due to bad password answer // isLockedOut = true; lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); } } } //createTimeStamp DateTime whenCreated = ((DateTime) PropertyManager.GetSearchResultPropertyValue(res, "whenCreated")).ToLocalTime(); //lastLogon (not supported) DateTime lastLogon = DateTime.MinValue; //lastActivity (not supported) DateTime lastActivity = DateTime.MinValue; //lastpwdchange (DateTime.FromFileTime cnoverts to Local time) DateTime lastPasswordChange = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, "pwdLastSet")); return new ActiveDirectoryMembershipUser(Name, username, sidBinaryForm, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, whenCreated, lastLogon, lastActivity, lastPasswordChange, lastLockoutDate, true /* valuesAreUpdated */); } private string GetEscapedRdn(string rdn) { NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); return pathCracker.GetEscapedElement(0, rdn); } // // Generates an escaped name that may be used in an LDAP query. The characters // ( ) * \ must be escaped when used in an LDAP query per RFC 2254. // internal string GetEscapedFilterValue(string filterValue) { return GetEscapedFilterValue(filterValue, true /* escapeWildChar */); } internal string GetEscapedFilterValue(string filterValue, bool escapeWildChar) { int index = -1; char[] specialCharacters = new char[] { '(', ')', '*', '\\' }; char[] specialCharactersWithoutWildChar = new char[] { '(', ')', '\\' }; index = escapeWildChar ? filterValue.IndexOfAny(specialCharacters) : filterValue.IndexOfAny(specialCharactersWithoutWildChar); if (index != -1) { // // if it contains any of the special characters then we // need to escape those // StringBuilder str = new StringBuilder(2 * filterValue.Length); str.Append(filterValue.Substring(0, index)); for (int i = index; i < filterValue.Length; i++) { switch (filterValue[i]) { case ('(') : { str.Append("\\28"); break; } case (')') : { str.Append("\\29"); break; } case ('*') : { if (escapeWildChar) str.Append("\\2A"); else str.Append("*"); break; } case ('\\') : { // this may be the escaped version of '*', i.e. "\2A" or "\2a" if ((escapeWildChar) || (!(((filterValue.Length - i) >= 3) && (filterValue[i + 1] == '2') && ((filterValue[i + 2] == 'A') || (filterValue[i + 2] == 'a'))))) str.Append("\\5C"); else str.Append("\\"); break; } default : { str.Append(filterValue[i]); break; } } } return str.ToString(); } else { // // just return the original string // return filterValue; } } // // private string GenerateAccountName() { char[] accountNameEncodingTable = new char[] {'0','1','2','3','4','5','6','7', '8','9','A','B','C','D','E','F', 'G','H','I','J','K','L','M','N', 'O','P','Q','R','S','T','U','V' }; // // account name will be 20 characters long; // char[] accountName = new char[20]; // // Generate a 64 bit random quantity // byte[] random = new byte[12]; //RNGCryptoServiceProvider is an implementation of a random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(random); // The array is now filled with cryptographically strong random bytes. // create a 32 bit random numbers from this uint random32a = 0; uint random32b = 0; uint random32c = 0; for (int i = 0; i < 4; i++) { random32a = random32a | unchecked((uint)(random[i] << (8 * i))); } for (int i = 0; i < 4; i++) { random32b = random32b | unchecked((uint)(random[4 + i] << (8 * i))); } for (int i = 0; i < 4; i++) { random32c = random32c | unchecked((uint)(random[8 + i] << (8 * i))); } // // The first character in the account name is a $ sign // accountName[0] = '$'; // // The next 6 chars are the least 30 bits of random32a (base 32 encoded) // for (int i=1;i<=6;i++) { // // Lookup the char corresponding to the last 5 bits of // random32a // accountName[i] = accountNameEncodingTable[(random32a & 0x1F)]; // // Shift random32a right by 5 places // random32a = random32a >> 5; } // // The next char is a "-" to make the name more readable // accountName[7] = '-'; // // The next 12 chars are formed by base 32 encoding the last 30 // bits of random32b and random32c. // for (int i=8;i<=13;i++) { // // Lookup the char corresponding to the last 5 bits // accountName[i] = accountNameEncodingTable[(random32b & 0x1F)]; // // Shift right by 5 places // random32b = random32b >> 5; } for (int i=13;i<=19;i++) { // // Lookup the char corresponding to the last 5 bits // accountName[i] = accountNameEncodingTable[(random32c & 0x1F)]; // // Shift right by 5 places // random32c = random32c >> 5; } return new String(accountName); } private void SetPasswordPortIfApplicable(DirectoryEntry userEntry) { // // For ADAM, if the port is specified and we are using Ssl for connection protection, // we should set the password port. // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { try { if ((directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.Ssl) && (directoryInfo.PortSpecified)) { userEntry.Options.PasswordPort = directoryInfo.Port; userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingSsl; } else if ((directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal) || (directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.None)) { userEntry.Options.PasswordPort = directoryInfo.Port; userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingClear; } } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005008)) { // // If ADSI returns E_ADS_BAD_PARAMETER, it means we are running // on a platform where ADSI does not support setting of the password port // Since ADSI will set the password port to 636 and password method to Ssl, we can // ignore this error only if that is what we are trying to set // if (!((directoryInfo.Port == DirectoryInformation.SSL_PORT) && (directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.Ssl))) throw new ProviderException(SR.GetString(SR.ADMembership_unable_to_set_password_port)); } else throw; } } } private bool IsUpnUnique(string username) { // // NOTE: we do not need to revert context here since this method is always // called after reverting any impersonated context // DirectoryEntry rootEntry = new DirectoryEntry("GC://" + directoryInfo.ForestName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); DirectorySearcher searcher = new DirectorySearcher(rootEntry); searcher.Filter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=" + GetEscapedFilterValue(username) + "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); bool result; try { result = (searcher.FindOne() == null); } finally { rootEntry.Dispose(); } return result; } private bool IsEmailUnique(DirectoryEntry containerEntry, string username, string email, bool existing) { bool disposeContainerEntry = false; if (containerEntry == null) { // // NOTE: we do not need to revert context here since this method is always // called after reverting any impersonated context // containerEntry = new DirectoryEntry(directoryInfo.GetADsPath(directoryInfo.ContainerDN), directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); disposeContainerEntry = true; } DirectorySearcher searcher = new DirectorySearcher(containerEntry); if (existing) searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) + ")(!(" + GetEscapedRdn("cn=" + GetEscapedFilterValue(username)) + ")))"; else searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) + "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); bool result; try { result = (searcher.FindOne() == null); } finally { if (disposeContainerEntry) { containerEntry.Dispose(); containerEntry = null; } } return result; } private string GetConnectionString(string connectionStringName, bool appLevel) { if (String.IsNullOrEmpty(connectionStringName)) return null; RuntimeConfig config = (appLevel) ? RuntimeConfig.GetAppConfig() : RuntimeConfig.GetConfig(); ConnectionStringSettings connObj = config.ConnectionStrings.ConnectionStrings[connectionStringName]; if (connObj == null) { // // No connection string by the specified name // throw new ProviderException(SR.GetString(SR.Connection_string_not_found, connectionStringName)); } return connObj.ConnectionString; } private string GetAttributeMapping(NameValueCollection config, string valueName, out int maxLength) { string sValue = config[valueName]; maxLength = -1; if (sValue == null) return null; sValue = sValue.Trim(); if (sValue.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Schema_mappings_must_not_be_empty, valueName)); return GetValidatedSchemaMapping(valueName, sValue, out maxLength); } private string GetValidatedSchemaMapping(string valueName, string attributeName, out int maxLength) { if (String.Compare(valueName, "attributeMapUsername", StringComparison.Ordinal) == 0) { if (directoryInfo.DirectoryType == DirectoryType.AD) { // // username can only be mapped to "sAMAccountName", "userPrincipalName" // if ((!StringUtil.EqualsIgnoreCase(attributeName, "sAMAccountName")) && (!StringUtil.EqualsIgnoreCase(attributeName, "userPrincipalName"))) throw new ProviderException(SR.GetString(SR.ADMembership_Username_mapping_invalid)); } else { // // for ADAM, username can only be mapped to "userPrincipalName" // if (!StringUtil.EqualsIgnoreCase(attributeName, "userPrincipalName")) throw new ProviderException(SR.GetString(SR.ADMembership_Username_mapping_invalid_ADAM)); } } else { // // ensure that we are not already using this attribute // if (attributesInUse.Contains(attributeName)) throw new ProviderException(SR.GetString(SR.ADMembership_mapping_not_unique, valueName, attributeName)); // // ensure that the attribute exists on the user object // if (!userObjectAttributes.Contains(attributeName)) throw new ProviderException(SR.GetString(SR.ADMembership_MappedAttribute_does_not_exist_on_user, attributeName, valueName)); } try { // // verify that this is an existing property and it's syntax is correct // DirectoryEntry propertyEntry = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/" + attributeName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); // // to get the syntax we need to invoke the "syntax" property // string syntax = (string) propertyEntry.InvokeGet("Syntax"); // // check that the syntax is as per the syntaxes table // if (!StringUtil.EqualsIgnoreCase(syntax, (string) syntaxes[valueName])) throw new ProviderException(SR.GetString(SR.ADMembership_Wrong_syntax, valueName, (string) syntaxes[valueName])); // // if the type is "DirectoryString", then set the maxLength value if any // maxLength = -1; if (StringUtil.EqualsIgnoreCase(syntax, "DirectoryString")) { try { maxLength = (int) propertyEntry.InvokeGet("MaxRange"); } catch (TargetInvocationException e) { // // if the inner exception is a comexception with error code 0x8007500d, then the max range is not set // so we ignore that exception // if (!((e.InnerException is COMException) && (((COMException)e.InnerException).ErrorCode == unchecked((int) 0x8000500d)))) throw; } } // // unless this is the username (which we already know is mapped // to a single valued attribute), the attribute should be single valued // if (String.Compare(valueName, "attributeMapUsername", StringComparison.Ordinal) != 0) { bool isMultiValued = (bool) propertyEntry.InvokeGet("MultiValued"); if (isMultiValued) throw new ProviderException(SR.GetString(SR.ADMembership_attribute_not_single_valued, valueName)); } } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_MappedAttribute_does_not_exist, attributeName, valueName), e); else throw; } // // add the attribute name (lower cased) to the in use attributes list // return attributeName; } private int GetRangeUpperForSchemaAttribute(string attributeName) { int rangeUpper = -1; DirectoryEntry propertyEntry = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/" + attributeName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); try { rangeUpper = (int) propertyEntry.InvokeGet("MaxRange"); } catch (TargetInvocationException e) { // // if the inner exception is a comexception with error code 0x8007500d, then the max range is not set // so we ignore that exception // if (!((e.InnerException is COMException) && (((COMException)e.InnerException).ErrorCode == unchecked((int) 0x8000500d)))) throw; } return rangeUpper; } private Hashtable GetUserObjectAttributes() { DirectoryEntry de = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/user", directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); object value = null; bool listEmpty = false; Hashtable attributes = new Hashtable(StringComparer.OrdinalIgnoreCase); try { value = de.InvokeGet("MandatoryProperties"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { listEmpty = true; } else throw; } if (!listEmpty) { if (value is ICollection) { foreach (string attribute in (ICollection) value) { if (!attributes.Contains(attribute)) attributes.Add(attribute, null); } } else { // single value if (!attributes.Contains(value)) attributes.Add(value, null); } } listEmpty = false; try { value = de.InvokeGet("OptionalProperties"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { listEmpty = true; } else throw; } if (!listEmpty) { if (value is ICollection) { foreach (string attribute in (ICollection) value) { if (!attributes.Contains(attribute)) attributes.Add(attribute, null); } } else { // single value if (!attributes.Contains(value)) attributes.Add(value, null); } } return attributes; } private DateTime GetDateTimeFromLargeInteger(NativeComInterfaces.IAdsLargeInteger largeIntValue) { // // Convert large integer to int64 value // Int64 int64Value = largeIntValue.HighPart * 0x100000000 + (uint) largeIntValue.LowPart; // // Return the DateTime in utc // return DateTime.FromFileTimeUtc(int64Value); } private NativeComInterfaces.IAdsLargeInteger GetLargeIntegerFromDateTime(DateTime dateTimeValue) { // // Convert DateTime value to utc file time // Int64 int64Value = dateTimeValue.ToFileTimeUtc(); // // convert to large integer // NativeComInterfaces.IAdsLargeInteger largeIntValue = (NativeComInterfaces.IAdsLargeInteger) new NativeComInterfaces.LargeInteger(); largeIntValue.HighPart = (int) (int64Value >> 32); largeIntValue.LowPart = (int) (int64Value & 0xFFFFFFFF); return largeIntValue; } private string Encrypt(string clearTextString) { // we should never be getting null input here Debug.Assert(clearTextString != null); byte[] bIn = Encoding.Unicode.GetBytes(clearTextString); byte[] bSalt = new byte[AD_SALT_SIZE_IN_BYTES]; (new RNGCryptoServiceProvider()).GetBytes(bSalt); byte[] bAll = new byte[bSalt.Length + bIn.Length]; Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length); Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); return Convert.ToBase64String(EncryptPassword(bAll)); } private string Decrypt(string encryptedString) { // we should never be getting null input here Debug.Assert(encryptedString != null); byte[] bEncryptedData = Convert.FromBase64String(encryptedString); byte[] bAll = DecryptPassword(bEncryptedData); return Encoding.Unicode.GetString(bAll, AD_SALT_SIZE_IN_BYTES, bAll.Length - AD_SALT_SIZE_IN_BYTES); } } internal sealed class DirectoryInformation { private string serverName = null; private string containerDN = null; private string creationContainerDN = null; private string adspath = null; private int port = 389; private bool portSpecified = false; private DirectoryType directoryType = DirectoryType.Unknown; private ActiveDirectoryConnectionProtection connectionProtection = ActiveDirectoryConnectionProtection.None; private bool concurrentBindSupported = false; private int clientSearchTimeout = -1; private int serverSearchTimeout = -1; private DirectoryEntry rootdse = null; private NetworkCredential credentials = null; private AuthenticationTypes authenticationType = AuthenticationTypes.None; private AuthType ldapAuthType = AuthType.Basic; private string adamPartitionDN = null; private TimeSpan adLockoutDuration; private string forestName = null; private string domainName = null; private bool isServer = false; private const string LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID ="1.2.840.113556.1.4.1851"; private const string LDAP_CAP_ACTIVE_DIRECTORY_OID ="1.2.840.113556.1.4.800"; private const string LDAP_SERVER_FAST_BIND_OID = "1.2.840.113556.1.4.1781"; internal const int SSL_PORT = 636; private const int GC_PORT = 3268; private const int GC_SSL_PORT = 3269; private const string GUID_USERS_CONTAINER_W = "a9d1ca15768811d1aded00c04fd8d5cd"; // // authentication types for S.DS and S.DS.Protocols (rows are indexed by connection protection // columns are indexed by type of credentials (see CredentialType enum) // AuthenticationTypes[,] authTypes = new AuthenticationTypes[,] {{AuthenticationTypes.None, AuthenticationTypes.None}, {AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer , AuthenticationTypes.SecureSocketsLayer }, {AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing, AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing}}; AuthType[,] ldapAuthTypes = new AuthType[,] {{AuthType.Negotiate, AuthType.Basic}, {AuthType.Negotiate, AuthType.Basic}, {AuthType.Negotiate, AuthType.Negotiate}}; internal DirectoryInformation(string adspath, NetworkCredential credentials, string connProtection, int clientSearchTimeout, int serverSearchTimeout, bool enablePasswordReset) { // // all parameters have already been validated at this point // this.adspath = adspath; this.credentials = credentials; this.clientSearchTimeout = clientSearchTimeout; this.serverSearchTimeout = serverSearchTimeout; Debug.Assert(adspath != null); Debug.Assert(adspath.Length > 0); // // Provider must be LDAP // if (!(adspath.StartsWith("LDAP", StringComparison.Ordinal))) throw new ProviderException(SR.GetString(SR.ADMembership_OnlyLdap_supported)); // // Parse out the server/domain information // NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); try { pathCracker.Set(adspath, NativeComInterfaces.ADS_SETTYPE_FULL); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_invalid_path)); else throw; } // Get the server and container names try { serverName = pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_SERVER); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_ServerlessADsPath_not_supported)); else throw; } Debug.Assert(serverName != null); creationContainerDN = containerDN = pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_X500_DN); // // Parse out the port number if specified // int index = serverName.IndexOf(':'); if (index != -1) { string tempStr = serverName; serverName = tempStr.Substring(0, index); Debug.Assert(tempStr.Length > index); port = Int32.Parse(tempStr.Substring(index + 1), NumberFormatInfo.InvariantInfo); portSpecified = true; } if (String.Compare(connProtection, "Secure", StringComparison.Ordinal) == 0) { // // The logic is as follows: // 1. Try Ssl first and check if concurrent binds are possible for validating users // 2. If Ssl is not supported, try signing and sealing // 3. If both the above are not supported, then we will fail // bool trySignAndSeal = false; bool trySslWithSecureAuth = false; // first try with simple bind if (!IsDefaultCredential()) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.NonWindows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.NonWindows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); // this will force a bind rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.Ssl; if (!portSpecified) { port = SSL_PORT; portSpecified = true; } } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x8007052e)) { // // this could be an ADAM target with windows user (in that case simple bind will not work) // trySslWithSecureAuth = true; } else if (ce.ErrorCode == unchecked((int) 0x8007203a)) { // server is not operational error, do nothing, we need to fall back to SignAndSeal trySignAndSeal = true; } else throw; } } else { // default credentials, so we have to do secure bind trySslWithSecureAuth = true; } if (trySslWithSecureAuth) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.Windows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.Windows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); // this will force a bind rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.Ssl; if (!portSpecified) { port = SSL_PORT; portSpecified = true; } } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x8007203a)) { // server is not operational error, do nothing, we need to fall back to SignAndSeal trySignAndSeal = true; } else throw; } } if (trySignAndSeal) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.SignAndSeal, CredentialsType.Windows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.SignAndSeal, CredentialsType.Windows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.SignAndSeal; } catch (COMException e) { throw new ProviderException(SR.GetString(SR.ADMembership_Secure_connection_not_established, e.Message), e); } } } else { // // No connection protection // // // we will do a simple bind but we must ensure that the credentials are explicitly specified // since in the case of default credentials we cannot honor it (default credentials become anonymous in the case of // simple bind) // if (IsDefaultCredential()) throw new NotSupportedException(SR.GetString(SR.ADMembership_Default_Creds_not_supported)); // simple bind authenticationType = GetAuthenticationTypes(connectionProtection, CredentialsType.NonWindows); ldapAuthType = GetLdapAuthenticationTypes(connectionProtection, CredentialsType.NonWindows); rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); } // // Determine whether this is AD or ADAM by binding to the rootdse and // checking the supported capabilities // if (rootdse == null) rootdse = new DirectoryEntry(GetADsPath("RootDSE"), GetUsername(), GetPassword(), authenticationType); directoryType = GetDirectoryType(); // // if the directory type is ADAM and the conntectionProtection was selected // as sign and seal, then we should throw an ProviderException. This is becuase validate user will always fail for ADAM // because ADAM does not support secure authentication for ADAM users. // if ((directoryType == DirectoryType.ADAM) && (this.connectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal)) throw new ProviderException(SR.GetString(SR.ADMembership_Ssl_connection_not_established)); // // for AD, we need to block the GC ports // if ((directoryType == DirectoryType.AD) && ((port == GC_PORT) || (port == GC_SSL_PORT))) throw new ProviderException(SR.GetString(SR.ADMembership_GCPortsNotSupported)); // // if container dn is null, we need to get the default naming context // (containerDN cannot be null for ADAM) // if (String.IsNullOrEmpty(containerDN)) { if (directoryType == DirectoryType.AD) { containerDN = (string)rootdse.Properties["defaultNamingContext"].Value; if (containerDN == null) throw new ProviderException(SR.GetString(SR.ADMembership_DefContainer_not_specified)); // // we will create users in the default users container, check that it exists // string wkUsersContainerPath = GetADsPath(""); DirectoryEntry containerEntry = new DirectoryEntry(wkUsersContainerPath, GetUsername(), GetPassword(), authenticationType); try { creationContainerDN = (string) PropertyManager.GetPropertyValue(containerEntry, "distinguishedName"); } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x80072030)) throw new ProviderException(SR.GetString(SR.ADMembership_DefContainer_does_not_exist)); else throw; } } else { // container must be specified for ADAM throw new ProviderException(SR.GetString(SR.ADMembership_Container_must_be_specified)); } } else { // // Normalize the container name (incase it was specified as GUID or WKGUID) // DirectoryEntry containerEntry = new DirectoryEntry(GetADsPath(containerDN), GetUsername(), GetPassword(), authenticationType); try { creationContainerDN = containerDN = (string) PropertyManager.GetPropertyValue(containerEntry, "distinguishedName"); } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x80072030)) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else throw; } } // // Check if the specified path(container) exists on the specified server/domain // (NOTE: We need to do this using S.DS.Protocols rather than S.DS because we need to // bypass the referral chasing which is automatic in S.DS) // LdapConnection tempConnection = new LdapConnection(new LdapDirectoryIdentifier(serverName + ":" + port), GetCredentialsWithDomain(credentials), ldapAuthType); tempConnection.SessionOptions.ProtocolVersion = 3; try { tempConnection.SessionOptions.ReferralChasing = System.DirectoryServices.Protocols.ReferralChasingOptions.None; SetSessionOptionsForSecureConnection(tempConnection, false /*useConcurrentBind */); tempConnection.Bind(); SearchRequest request = new SearchRequest(); request.DistinguishedName = containerDN; request.Filter = "(objectClass=*)"; request.Scope = System.DirectoryServices.Protocols.SearchScope.Base; request.Attributes.Add("distinguishedName"); request.Attributes.Add("objectClass"); if (ServerSearchTimeout != -1) request.TimeLimit = new TimeSpan(0, ServerSearchTimeout, 0); SearchResponse response; try { response = (SearchResponse) tempConnection.SendRequest(request); if (response.ResultCode == ResultCode.Referral || response.ResultCode == ResultCode.NoSuchObject) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else if (response.ResultCode != ResultCode.Success) throw new ProviderException(response.ErrorMessage); } catch (DirectoryOperationException oe) { SearchResponse errorResponse = (SearchResponse) oe.Response; if (errorResponse.ResultCode == ResultCode.NoSuchObject) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else throw; } // // check that the container is of an object type that can be a superior of a user object // DirectoryAttribute objectClass = response.Entries[0].Attributes["objectClass"]; if (!ContainerIsSuperiorOfUser(objectClass)) throw new ProviderException(SR.GetString(SR.ADMembership_Container_not_superior)); // // Determine whether concurrent bind is supported // if ((connectionProtection == ActiveDirectoryConnectionProtection.None) || (connectionProtection == ActiveDirectoryConnectionProtection.Ssl)) { this.concurrentBindSupported = IsConcurrentBindSupported(tempConnection); } } finally { tempConnection.Dispose(); } // // if this is ADAM, get the partition DN // if (directoryType == DirectoryType.ADAM) { adamPartitionDN = GetADAMPartitionFromContainer(); } else { if (enablePasswordReset) { // for AD, get the lockout duration for user account auto unlock DirectoryEntry de = new DirectoryEntry(GetADsPath((string) PropertyManager.GetPropertyValue(rootdse, "defaultNamingContext")), GetUsername(), GetPassword(), AuthenticationTypes); NativeComInterfaces.IAdsLargeInteger largeIntValue = (NativeComInterfaces.IAdsLargeInteger) PropertyManager.GetPropertyValue(de, "lockoutDuration"); Int64 int64Value = largeIntValue.HighPart * 0x100000000 + (uint) largeIntValue.LowPart; // int64Value is the negative of the number of 100 nanoseconds interval that makes up the lockout duration adLockoutDuration = new TimeSpan(-int64Value); } } } internal bool ConcurrentBindSupported { get { return concurrentBindSupported; } } internal string ContainerDN { get { return containerDN; } } internal string CreationContainerDN { get { return creationContainerDN; } } internal int Port { get { return port; } } internal bool PortSpecified { get { return portSpecified; } } #if UNUSED_CODE internal NetworkCredential Credential { get { return credentials; } } #endif internal DirectoryType DirectoryType { get { return directoryType; } } internal ActiveDirectoryConnectionProtection ConnectionProtection { get { return connectionProtection; } } internal AuthenticationTypes AuthenticationTypes { get { return authenticationType; } } internal int ClientSearchTimeout { get { return clientSearchTimeout; } } internal int ServerSearchTimeout { get { return serverSearchTimeout; } } internal string ADAMPartitionDN { get { return adamPartitionDN; } } internal TimeSpan ADLockoutDuration { get { return adLockoutDuration; } } internal string ForestName { get { return forestName; } } internal string DomainName { get { return domainName; } } internal void InitializeDomainAndForestName() { if (!isServer) { DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, serverName, GetUsername(), GetPassword()); try { Domain domain = Domain.GetDomain(context); domainName = GetNetbiosDomainNameIfAvailable(domain.Name); forestName = domain.Forest.Name; } catch (ActiveDirectoryObjectNotFoundException) { // the serverName may be the name of the server rather than domain isServer = true; } } if (isServer) { DirectoryContext context = new DirectoryContext(DirectoryContextType.DirectoryServer, serverName, GetUsername(), GetPassword()); try { Domain domain = Domain.GetDomain(context); domainName = GetNetbiosDomainNameIfAvailable(domain.Name); forestName = domain.Forest.Name; } catch (ActiveDirectoryObjectNotFoundException) { // we were unable to contact the domain or server throw new ProviderException(SR.GetString(SR.ADMembership_unable_to_contact_domain)); } } } internal void SelectServer() { // // if the name specified in the target is a domain name, then we should // perform all operations on the PDC. If the name is not a domain name // then it would be the name of a server. In that case we perform all // operations on that server // serverName = GetPdcIfDomain(serverName); isServer = true; } // // Creates a new ldap connection with the specified auth types // (the session options are set based on the connection protection that was // determined during the initialize method) // internal LdapConnection CreateNewLdapConnection(AuthType authType) { LdapConnection newConnection = null; newConnection = new LdapConnection(new LdapDirectoryIdentifier(serverName + ":" + port)); newConnection.AuthType = authType; newConnection.SessionOptions.ProtocolVersion = 3; SetSessionOptionsForSecureConnection(newConnection, true /* useConcurrentBind */); return newConnection; } // // this method returns the ADsPath for the given DN // internal string GetADsPath(string dn) { string path = null; // // provider and server information // Debug.Assert(serverName != null); path = "LDAP://" + serverName; // // port info if specified // if (portSpecified) path = path + ":" + port; // // DN of the object // Debug.Assert(dn != null); NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); pathCracker.Set(dn, NativeComInterfaces.ADS_SETTYPE_DN); pathCracker.EscapedMode = NativeComInterfaces.ADS_ESCAPEDMODE_ON; path = path + "/" + pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_X500_DN); return path; } internal void SetSessionOptionsForSecureConnection(LdapConnection connection, bool useConcurrentBind) { if (connectionProtection == ActiveDirectoryConnectionProtection.Ssl) { connection.SessionOptions.SecureSocketLayer = true; } else if (connectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal) { connection.SessionOptions.Signing = true; connection.SessionOptions.Sealing = true; } if (useConcurrentBind && this.concurrentBindSupported) { try { connection.SessionOptions.FastConcurrentBind(); } catch (PlatformNotSupportedException) { // // concurrent bind is not supported by the client, (continue without it and don't try to set it next time) // this.concurrentBindSupported = false; } } } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal string GetUsername() { if (credentials == null) return null; if (credentials.UserName == null) return null; if (credentials.UserName.Length == 0 && (credentials.Password == null || credentials.Password.Length == 0)) return null; return this.credentials.UserName; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal string GetPassword() { if (credentials == null) return null; if (credentials.Password == null) return null; if (credentials.Password.Length == 0 && (credentials.UserName == null || credentials.UserName.Length == 0)) return null; return this.credentials.Password; } internal AuthenticationTypes GetAuthenticationTypes(ActiveDirectoryConnectionProtection connectionProtection, CredentialsType type) { return authTypes[(int) connectionProtection, (int) type]; } internal AuthType GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection connectionProtection, CredentialsType type) { return ldapAuthTypes[(int) connectionProtection, (int) type]; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal bool IsDefaultCredential() { if ((credentials.UserName == null || credentials.UserName.Length == 0) && (credentials.Password == null || credentials.Password.Length == 0)) return true; return false; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal static NetworkCredential GetCredentialsWithDomain(NetworkCredential credentials) { NetworkCredential credentialsWithDomain; if (credentials == null) credentialsWithDomain = new NetworkCredential(null, null); else { string tempUsername = credentials.UserName; string username = null; string password = null; string domainName = null; if (!String.IsNullOrEmpty(tempUsername)) { int index = tempUsername.IndexOf('\\'); if (index != -1) { domainName = tempUsername.Substring(0, index); username = tempUsername.Substring(index + 1); } else username = tempUsername; password = credentials.Password; } credentialsWithDomain = new NetworkCredential(username, password, domainName); } return credentialsWithDomain; } private bool IsConcurrentBindSupported(LdapConnection ldapConnection) { bool result = false; Debug.Assert(ldapConnection != null); // // supportedExtension is a constructed attribute so we need to search and load that attribute explicitly // SearchRequest request = new SearchRequest(); request.Scope = System.DirectoryServices.Protocols.SearchScope.Base; request.Attributes.Add("supportedExtension"); if (ServerSearchTimeout != -1) request.TimeLimit = new TimeSpan(0, ServerSearchTimeout, 0); SearchResponse response = (SearchResponse) ldapConnection.SendRequest(request); if (response.ResultCode != ResultCode.Success) throw new ProviderException(response.ErrorMessage); foreach (string supportedExtension in response.Entries[0].Attributes["supportedExtension"].GetValues(typeof(string))) { if (StringUtil.EqualsIgnoreCase(supportedExtension, LDAP_SERVER_FAST_BIND_OID)) { result = true; break; } } return result; } // // This function goes through each of the naming contexts on the server // and determines which one is the longest postfix of the container DN. // That will give the DN of partition that the container lives in. // // private string GetADAMPartitionFromContainer() { string partitionName = null; int startsAt = Int32.MaxValue; foreach(string namingContext in rootdse.Properties["namingContexts"]) { bool endsWith = containerDN.EndsWith(namingContext, StringComparison.Ordinal); int lastIndexOf = containerDN.LastIndexOf(namingContext, StringComparison.Ordinal); if (endsWith && (lastIndexOf != -1) && (lastIndexOf < startsAt)) { partitionName = namingContext; startsAt = lastIndexOf; } } if (partitionName == null) throw new ProviderException(SR.GetString(SR.ADMembership_No_ADAM_Partition)); return partitionName; } // // This function goes through each of the object class values for the container to determine // whether the object class is one of the possible superiors of the user object // private bool ContainerIsSuperiorOfUser(DirectoryAttribute objectClass) { ArrayList possibleSuperiorsList = new ArrayList(); // // first get a list of all the classes from which the user class is derived // DirectoryEntry de = new DirectoryEntry(GetADsPath("schema") + "/user", GetUsername(), GetPassword(), AuthenticationTypes); ArrayList classesList = new ArrayList(); bool derivedFromlistEmpty = false; object value = null; try { value = de.InvokeGet("DerivedFrom"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { derivedFromlistEmpty = true; } else throw; } if (!derivedFromlistEmpty) { if (value is ICollection) { classesList.AddRange((ICollection) value); } else { // single value classesList.Add((string) value); } } // // we will use this list to create a filter of all the classSchema objects that we need to determine the recursive list // of "possibleSecuperiors". We need to add the user class also. // classesList.Add("user"); // // Now search under the schema naming context for all these classes and get the "possSuperiors" and "systemPossSuperiors" attributes // DirectoryEntry schemaNC = new DirectoryEntry(GetADsPath((string) rootdse.Properties["schemaNamingContext"].Value), GetUsername(), GetPassword(), AuthenticationTypes); DirectorySearcher searcher = new DirectorySearcher(schemaNC); searcher.Filter = "(&(objectClass=classSchema)(|"; foreach(string supClass in classesList) searcher.Filter += "(ldapDisplayName=" + supClass + ")"; searcher.Filter += "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.OneLevel; searcher.PropertiesToLoad.Add("possSuperiors"); searcher.PropertiesToLoad.Add("systemPossSuperiors"); SearchResultCollection resCol = searcher.FindAll(); try { foreach (SearchResult res in resCol) { possibleSuperiorsList.AddRange(res.Properties["possSuperiors"]); possibleSuperiorsList.AddRange(res.Properties["systemPossSuperiors"]); } } finally { resCol.Dispose(); } // // Now we have the list of all the possible superiors, check if the objectClass that was specified as a parameter // to this function is one of these values, if so, return true else false // foreach (string objectClassValue in objectClass.GetValues(typeof(string))) { if (possibleSuperiorsList.Contains(objectClassValue)) return true; } return false; } // // This method determines whether the server we are talking to // is an AD domain controller or an ADAM instance // private DirectoryType GetDirectoryType() { DirectoryType directoryType = DirectoryType.Unknown; foreach (string supportedCapability in rootdse.Properties["supportedCapabilities"]) { if (StringUtil.EqualsIgnoreCase(supportedCapability, LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID)) { directoryType = DirectoryType.ADAM; break; } else if (StringUtil.EqualsIgnoreCase(supportedCapability, LDAP_CAP_ACTIVE_DIRECTORY_OID)) { directoryType = DirectoryType.AD; break; } } if (directoryType == DirectoryType.Unknown) throw new ProviderException(SR.GetString(SR.ADMembership_Valid_Targets)); return directoryType; } // // This method returns the dns name of the primary domain controller if the specified name is a domain, // else is just returns the name as is // internal string GetPdcIfDomain(string name) { IntPtr pDomainControllerInfo = IntPtr.Zero; /* DS_DIRECTORY_SERVICE_REQUIRED 0x00000010 DS_RETURN_DNS_NAME 0x40000000 DS_PDC_REQUIRED 0x00000080 */ uint flags = 0x00000010 | 0x40000000 | 0x00000080; string pdc = null; int ERROR_NO_SUCH_DOMAIN = 1355; int result = NativeMethods.DsGetDcName(null, name, IntPtr.Zero, null, flags, out pDomainControllerInfo); try { if (result == 0) { // success case DomainControllerInfo domainControllerInfo = new DomainControllerInfo(); Marshal.PtrToStructure(pDomainControllerInfo, domainControllerInfo); Debug.Assert(domainControllerInfo != null); Debug.Assert(domainControllerInfo.DomainControllerName != null); Debug.Assert(domainControllerInfo.DomainControllerName.Length > 2); // domain controller name is in the format "\\server", so we need to strip the back slashes pdc = domainControllerInfo.DomainControllerName.Substring(2); } else if (result == ERROR_NO_SUCH_DOMAIN) pdc = name; else throw new ProviderException(GetErrorMessage(result)); } finally { // free the buffer if (pDomainControllerInfo != IntPtr.Zero) { NativeMethods.NetApiBufferFree(pDomainControllerInfo); } } return pdc; } internal string GetNetbiosDomainNameIfAvailable(string dnsDomainName) { string result = null; // // Get the netbios name from the "nETBIOSName" attribute on the crossRef object for this domain // DirectoryEntry partitionsEntry = new DirectoryEntry(GetADsPath("CN=Partitions," + (string) PropertyManager.GetPropertyValue(rootdse, "configurationNamingContext")), GetUsername(), GetPassword()); DirectorySearcher searcher = new DirectorySearcher(partitionsEntry); searcher.SearchScope = System.DirectoryServices.SearchScope.OneLevel; StringBuilder str = new StringBuilder(15); str.Append("(&(objectCategory=crossRef)(dnsRoot="); str.Append(dnsDomainName); str.Append(")(systemFlags:1.2.840.113556.1.4.804:=1)(systemFlags:1.2.840.113556.1.4.804:=2))"); searcher.Filter = str.ToString(); searcher.PropertiesToLoad.Add("nETBIOSName"); SearchResult res = searcher.FindOne(); if ((res == null) || (!(res.Properties.Contains("nETBIOSName")))) // return the dns name result = dnsDomainName; else // return the netbios name result = (string) PropertyManager.GetSearchResultPropertyValue(res, "nETBIOSName"); return result; } private static string GetErrorMessage(int errorCode) { uint temp = (uint) errorCode; temp = ( (((temp) & 0x0000FFFF) | (7 << 16) | 0x80000000)); string errorMsg = String.Empty; StringBuilder sb = new StringBuilder(256); int result = NativeMethods.FormatMessageW(NativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS | NativeMethods.FORMAT_MESSAGE_FROM_SYSTEM | NativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY, 0, (int)temp, 0, sb, sb.Capacity + 1, 0); if (result != 0) { errorMsg = sb.ToString(0, result); } else { errorMsg = SR.GetString(SR.ADMembership_Unknown_Error, string.Format(CultureInfo.InvariantCulture, "{0}", errorCode)); } return errorMsg; } } internal static class PropertyManager { public static object GetPropertyValue(DirectoryEntry directoryEntry, string propertyName) { Debug.Assert(directoryEntry != null, "PropertyManager::GetPropertyValue - directoryEntry is null"); Debug.Assert(propertyName != null, "PropertyManager::GetPropertyValue - propertyName is null"); if (directoryEntry.Properties[propertyName].Count == 0) { if (directoryEntry.Properties["distinguishedName"].Count != 0) throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found_on_object, propertyName, (string) directoryEntry.Properties["distinguishedName"].Value )); else throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found, propertyName)); } return directoryEntry.Properties[propertyName].Value; } public static object GetSearchResultPropertyValue(SearchResult res, string propertyName) { Debug.Assert(res != null, "PropertyManager::GetSearchResultPropertyValue - res is null"); Debug.Assert(propertyName != null, "PropertyManager::GetSearchResultPropertyValue - propertyName is null"); ResultPropertyValueCollection propertyValues = null; propertyValues = res.Properties[propertyName]; if ((propertyValues == null) || (propertyValues.Count < 1)) throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found, propertyName)); return propertyValues[0]; } } /*typedef struct _DOMAIN_CONTROLLER_INFO { LPTSTR DomainControllerName; LPTSTR DomainControllerAddress; ULONG DomainControllerAddressType; GUID DomainGuid; LPTSTR DomainName; LPTSTR DnsForestName; ULONG Flags; LPTSTR DcSiteName; LPTSTR ClientSiteName; } DOMAIN_CONTROLLER_INFO, *PDOMAIN_CONTROLLER_INFO; */ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] internal sealed class DomainControllerInfo { #pragma warning disable 0649 public string DomainControllerName; public string DomainControllerAddress; public int DomainControllerAddressType; public Guid DomainGuid; public string DomainName; public string DnsForestName; public int Flags; public string DcSiteName; public string ClientSiteName; #pragma warning restore 0649 public DomainControllerInfo() {} } [SuppressUnmanagedCodeSecurityAttribute()] internal static class NativeMethods { internal const int ERROR_NO_SUCH_DOMAIN = 1355; internal const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; internal const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; internal const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; /*DWORD DsGetDcName( LPCTSTR ComputerName, LPCTSTR DomainName, GUID* DomainGuid, LPCTSTR SiteName, ULONG Flags, PDOMAIN_CONTROLLER_INFO* DomainControllerInfo );*/ [DllImport("Netapi32.dll", CallingConvention=CallingConvention.StdCall, EntryPoint="DsGetDcNameW", CharSet=CharSet.Unicode)] internal static extern int DsGetDcName( [In] string computerName, [In] string domainName, [In] IntPtr domainGuid, [In] string siteName, [In] uint flags, [Out] out IntPtr domainControllerInfo); /*NET_API_STATUS NetApiBufferFree( LPVOID Buffer );*/ [DllImport("Netapi32.dll")] internal static extern int NetApiBufferFree( [In] IntPtr buffer); [DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode)] public static extern int FormatMessageW( [In] int dwFlags, [In] int lpSource, [In] int dwMessageId, [In] int dwLanguageId, [Out] StringBuilder lpBuffer, [In] int nSize, [In] int arguments); } [ ComVisible(false), SuppressUnmanagedCodeSecurityAttribute() ] internal static class NativeComInterfaces { /*typedef enum { ADS_SETTYPE_FULL=1, ADS_SETTYPE_PROVIDER=2, ADS_SETTYPE_SERVER=3, ADS_SETTYPE_DN=4 } ADS_SETTYPE_ENUM; typedef enum { ADS_FORMAT_WINDOWS=1, ADS_FORMAT_WINDOWS_NO_SERVER=2, ADS_FORMAT_WINDOWS_DN=3, ADS_FORMAT_WINDOWS_PARENT=4, ADS_FORMAT_X500=5, ADS_FORMAT_X500_NO_SERVER=6, ADS_FORMAT_X500_DN=7, ADS_FORMAT_X500_PARENT=8, ADS_FORMAT_SERVER=9, ADS_FORMAT_PROVIDER=10, ADS_FORMAT_LEAF=11 } ADS_FORMAT_ENUM; typedef enum { ADS_ESCAPEDMODE_DEFAULT=1, ADS_ESCAPEDMODE_ON=2, ADS_ESCAPEDMODE_OFF=3, ADS_ESCAPEDMODE_OFF_EX=4 } ADS_ESCAPE_MODE_ENUM;*/ internal const int ADS_SETTYPE_FULL = 1; internal const int ADS_SETTYPE_DN = 4; internal const int ADS_FORMAT_PROVIDER = 10; internal const int ADS_FORMAT_SERVER = 9; internal const int ADS_FORMAT_X500_DN = 7; internal const int ADS_ESCAPEDMODE_ON = 2; internal const int ADS_ESCAPEDMODE_OFF = 3; // // Pathname as a co-class that implements the IAdsPathname interface // [ComImport, Guid("080d0d78-f421-11d0-a36e-00c04fb950dc")] internal class Pathname { } [ComImport, Guid("D592AED4-F420-11D0-A36E-00C04FB950DC"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)] internal interface IAdsPathname { // HRESULT Set([in] BSTR bstrADsPath, [in] long lnSetType); [SuppressUnmanagedCodeSecurityAttribute()] int Set([In, MarshalAs(UnmanagedType.BStr)] string bstrADsPath, [In, MarshalAs(UnmanagedType.U4)] int lnSetType); // HRESULT SetDisplayType([in] long lnDisplayType); int SetDisplayType([In, MarshalAs(UnmanagedType.U4)] int lnDisplayType); // HRESULT Retrieve([in] long lnFormatType, [out, retval] BSTR* pbstrADsPath); [return: MarshalAs(UnmanagedType.BStr)][SuppressUnmanagedCodeSecurityAttribute()] string Retrieve([In, MarshalAs(UnmanagedType.U4)] int lnFormatType); // HRESULT GetNumElements([out, retval] long* plnNumPathElements); [return: MarshalAs(UnmanagedType.U4)] int GetNumElements(); // HRESULT GetElement([in] long lnElementIndex, [out, retval] BSTR* pbstrElement); [return: MarshalAs(UnmanagedType.BStr)] string GetElement([In, MarshalAs(UnmanagedType.U4)] int lnElementIndex); // HRESULT AddLeafElement([in] BSTR bstrLeafElement); void AddLeafElement([In, MarshalAs(UnmanagedType.BStr)] string bstrLeafElement); // HRESULT RemoveLeafElement(); void RemoveLeafElement(); // HRESULT CopyPath([out, retval] IDispatch** ppAdsPath); [return: MarshalAs(UnmanagedType.Interface)] object CopyPath(); // HRESULT GetEscapedElement([in] long lnReserved, [in] BSTR bstrInStr, [out, retval] BSTR* pbstrOutStr ); [return: MarshalAs(UnmanagedType.BStr)][SuppressUnmanagedCodeSecurityAttribute()] string GetEscapedElement([In, MarshalAs(UnmanagedType.U4)] int lnReserved, [In, MarshalAs(UnmanagedType.BStr)] string bstrInStr); int EscapedMode { get; [SuppressUnmanagedCodeSecurityAttribute()] set; } } // // LargeInteger as a co-class that implements the IAdsLargeInteger interface // [ComImport, Guid("927971f5-0939-11d1-8be1-00c04fd8d503")] internal class LargeInteger { } [ComImport, Guid("9068270b-0939-11d1-8be1-00c04fd8d503"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)] internal interface IAdsLargeInteger { long HighPart { [SuppressUnmanagedCodeSecurityAttribute()] get; [SuppressUnmanagedCodeSecurityAttribute()] set; } long LowPart { [SuppressUnmanagedCodeSecurityAttribute()] get; [SuppressUnmanagedCodeSecurityAttribute()] set; } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Web.Security { using System.Net; using System.Web; using System.Text; using System.Text.RegularExpressions; using System.Security; using System.Collections; using System.Globalization; using System.Configuration; using System.DirectoryServices; using System.DirectoryServices.ActiveDirectory; using System.DirectoryServices.Protocols; using System.Web.Hosting; using System.Security.Cryptography; using System.Web.Configuration; using System.Security.Permissions; using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Security.Principal; using System.Web.DataAccess; using System.Web.Util; using System.Reflection; using System.Configuration.Provider; using System.Web.Management; public enum ActiveDirectoryConnectionProtection { None = 0, Ssl = 1, SignAndSeal = 2 } internal enum DirectoryType { AD = 0, ADAM = 1, Unknown = 2 } internal enum CredentialsType { Windows = 0, NonWindows = 1 } [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)] [DirectoryServicesPermission(SecurityAction.LinkDemand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public class ActiveDirectoryMembershipProvider : MembershipProvider { // // keeps track of whether the provider has already been initialized // private bool initialized = false; // // configuration parameters common to all membership providers // private string adConnectionString; private bool enablePasswordRetrieval = false; private bool enablePasswordReset; private bool enableSearchMethods; private bool requiresQuestionAndAnswer; private string appName; private bool requiresUniqueEmail; private int maxInvalidPasswordAttempts; private int passwordAttemptWindow; private int passwordAnswerAttemptLockoutDuration; private int minRequiredPasswordLength; private int minRequiredNonalphanumericCharacters; private string passwordStrengthRegularExpression; // // configuration parameters specific to the AD membership provider // and related to the directory connection are stored within the DirectoryInformation class // DirectoryInformation directoryInfo = null; // // custom schema mappings (and their default values) // private string attributeMapUsername = "userPrincipalName"; private string attributeMapEmail = "mail"; private string attributeMapPasswordQuestion = null; private string attributeMapPasswordAnswer = null; private string attributeMapFailedPasswordAnswerCount = null; private string attributeMapFailedPasswordAnswerTime= null; private string attributeMapFailedPasswordAnswerLockoutTime = null; // // maximum lengths for the different string properties // private int maxUsernameLength = 256; private int maxUsernameLengthForCreation = 64; private int maxPasswordLength = 128; private int maxCommentLength = 1024; private int maxEmailLength = 256; private int maxPasswordQuestionLength = 256; private int maxPasswordAnswerLength = 128; // // user account flags // private const int UF_ACCOUNT_DISABLED =0x2; private const int UF_LOCKOUT=0x10; private readonly DateTime DefaultLastLockoutDate = new DateTime(1754, 1, 1, 0, 0, 0, DateTimeKind.Utc); private const int AD_SALT_SIZE_IN_BYTES = 16; // // table containing the valid syntaxes for various attribute mappings // Hashtable syntaxes = new Hashtable(); Hashtable attributesInUse = new Hashtable(StringComparer.OrdinalIgnoreCase); Hashtable userObjectAttributes = null; // // auth type to be used for validation // AuthType authTypeForValidation; LdapConnection connection; bool usernameIsSAMAccountName = false; bool usernameIsUPN = true; // // password size for autogenerating password // private const int PASSWORD_SIZE = 14; public override string ApplicationName { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return appName; } set { throw new NotSupportedException(SR.GetString(SR.ADMembership_Setting_ApplicationName_not_supported)); } } public ActiveDirectoryConnectionProtection CurrentConnectionProtection { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return directoryInfo.ConnectionProtection; } } public override MembershipPasswordFormat PasswordFormat { get { // // AD membership provider does not support password retrieval // (regardless of the settings). As a result the provider operates as // if the password was effectively hashed. // return MembershipPasswordFormat.Hashed; } } public override bool EnablePasswordRetrieval { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enablePasswordRetrieval; } } public override bool EnablePasswordReset { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enablePasswordReset; } } public bool EnableSearchMethods { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return enableSearchMethods; } } public override bool RequiresQuestionAndAnswer { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return requiresQuestionAndAnswer; } } public override bool RequiresUniqueEmail { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return requiresUniqueEmail; } } public override int MaxInvalidPasswordAttempts { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return maxInvalidPasswordAttempts; } } public override int PasswordAttemptWindow { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordAttemptWindow; } } public int PasswordAnswerAttemptLockoutDuration { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordAnswerAttemptLockoutDuration; } } public override int MinRequiredPasswordLength { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return minRequiredPasswordLength; } } public override int MinRequiredNonAlphanumericCharacters { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return minRequiredNonalphanumericCharacters; } } public override string PasswordStrengthRegularExpression { get { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); return passwordStrengthRegularExpression; } } // // NOTE: In every method of the provider we need to demand DirectoryServicesPermission (irrespective of // whether the underlying calls to S.DS/S.DS.Protocols result in full demand or link demand for that permission. // Moreover, once we demand the permission, we should also assert it so that S.DS/S.DS.Protocols does not make the // same demand (if we do not assert then in the case of S.DS/S.DS.Protocols making a full demand we would have two stack walks) // #pragma warning disable 0688 [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override void Initialize(string name, NameValueCollection config) { if (System.Web.Hosting.HostingEnvironment.IsHosted) HttpRuntime.CheckAspNetHostingPermission (AspNetHostingPermissionLevel.Low, SR.Feature_not_supported_at_this_level); if (initialized) return; if (config == null) throw new ArgumentNullException("config"); if (String.IsNullOrEmpty(name)) name = "AspNetActiveDirectoryMembershipProvider"; if (string.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", SR.GetString(SR.ADMembership_Description)); } base.Initialize(name, config); appName = config["applicationName"]; if (string.IsNullOrEmpty(appName)) appName = SecUtility.GetDefaultAppName(); if( appName.Length > 256 ) throw new ProviderException(SR.GetString(SR.Provider_application_name_too_long)); string temp = config["connectionStringName"]; if (String.IsNullOrEmpty(temp)) throw new ProviderException(SR.GetString(SR.Connection_name_not_specified)); adConnectionString = GetConnectionString(temp, true); if (String.IsNullOrEmpty(adConnectionString)) throw new ProviderException(SR.GetString(SR.Connection_string_not_found, temp)); // // Get the provider specific configuration settings // // connectionProtection string connProtection = config["connectionProtection"]; if (connProtection == null) connProtection = "Secure"; else { if ((String.Compare(connProtection, "Secure", StringComparison.Ordinal) != 0) && (String.Compare(connProtection, "None", StringComparison.Ordinal) != 0)) throw new ProviderException(SR.GetString(SR.ADMembership_InvalidConnectionProtection, connProtection)); } // // credentials // username and password if specified must not be empty, moreover if one is specified the other must // be specified as well // string username = config["connectionUsername"]; if (username != null && username.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Connection_username_must_not_be_empty)); string password = config["connectionPassword"]; if (password != null && password.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Connection_password_must_not_be_empty)); if ((username != null && password == null) || (password != null && username == null)) throw new ProviderException(SR.GetString(SR.ADMembership_Username_and_password_reqd)); NetworkCredential credential = new NetworkCredential(username, password); int clientSearchTimeout = SecUtility.GetIntValue(config, "clientSearchTimeout", -1, false, 0); int serverSearchTimeout = SecUtility.GetIntValue(config, "serverSearchTimeout", -1, false, 0); enableSearchMethods = SecUtility.GetBooleanValue(config, "enableSearchMethods", false); requiresUniqueEmail = SecUtility.GetBooleanValue(config, "requiresUniqueEmail", false); enablePasswordReset = SecUtility.GetBooleanValue(config, "enablePasswordReset", false); requiresQuestionAndAnswer = SecUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", false); minRequiredPasswordLength = SecUtility.GetIntValue( config, "minRequiredPasswordLength", 7, false, 128 ); minRequiredNonalphanumericCharacters = SecUtility.GetIntValue( config, "minRequiredNonalphanumericCharacters", 1, true, 128 ); passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"]; if( passwordStrengthRegularExpression != null ) { passwordStrengthRegularExpression = passwordStrengthRegularExpression.Trim(); if( passwordStrengthRegularExpression.Length != 0 ) { try { Regex regex = new Regex( passwordStrengthRegularExpression ); } catch( ArgumentException e ) { throw new ProviderException( e.Message, e ); } } } else { passwordStrengthRegularExpression = string.Empty; } if (minRequiredNonalphanumericCharacters > minRequiredPasswordLength) throw new HttpException(SR.GetString(SR.MinRequiredNonalphanumericCharacters_can_not_be_more_than_MinRequiredPasswordLength)); using (new ApplicationImpersonationContext()) { // // This will make some checks regarding whether the connectionProtection is valid (choose the right // connectionprotection if necessary, make sure credentials are valid, container exists and the directory is // either AD or ADAM) // directoryInfo = new DirectoryInformation(adConnectionString, credential, connProtection, clientSearchTimeout, serverSearchTimeout, enablePasswordReset); // // initialize the syntaxes table // syntaxes.Add("attributeMapUsername", "DirectoryString"); syntaxes.Add("attributeMapEmail", "DirectoryString"); syntaxes.Add("attributeMapPasswordQuestion", "DirectoryString"); syntaxes.Add("attributeMapPasswordAnswer", "DirectoryString"); syntaxes.Add("attributeMapFailedPasswordAnswerCount", "Integer"); syntaxes.Add("attributeMapFailedPasswordAnswerTime", "Integer8"); syntaxes.Add("attributeMapFailedPasswordAnswerLockoutTime", "Integer8"); // // initialize the in use attributes list // attributesInUse.Add("objectclass", null); attributesInUse.Add("objectsid", null); attributesInUse.Add("comment", null); attributesInUse.Add("whencreated", null); attributesInUse.Add("pwdlastset", null); attributesInUse.Add("msds-user-account-control-computed", null); attributesInUse.Add("lockouttime", null); if (directoryInfo.DirectoryType == DirectoryType.AD) attributesInUse.Add("useraccountcontrol", null); else attributesInUse.Add("msds-useraccountdisabled", null); // // initialize the user attributes list // userObjectAttributes = GetUserObjectAttributes(); // // get the username/email schema mappings // int maxLength; string attrMapping = GetAttributeMapping(config, "attributeMapUsername", out maxLength); if (attrMapping != null) { attributeMapUsername = attrMapping; if (maxLength != -1) { if (maxLength < maxUsernameLength) maxUsernameLength = maxLength; if (maxLength < maxUsernameLengthForCreation) maxUsernameLengthForCreation = maxLength; } } attributesInUse.Add(attributeMapUsername, null); if (StringUtil.EqualsIgnoreCase(attributeMapUsername, "sAMAccountName")) { usernameIsSAMAccountName = true; usernameIsUPN = false; } attrMapping = GetAttributeMapping(config, "attributeMapEmail", out maxLength); if (attrMapping != null) { attributeMapEmail = attrMapping; if (maxLength != -1 && maxLength < maxEmailLength) maxEmailLength = maxLength; } attributesInUse.Add(attributeMapEmail, null); // // get max length of "comment" attribute // maxLength = GetRangeUpperForSchemaAttribute("comment"); if (maxLength != -1 && maxLength < maxCommentLength) maxCommentLength = maxLength; // // enablePasswordReset and requiresQuestionAndAnswer should match // if (enablePasswordReset) { // // AD membership provider does not support password reset without question and answer // if (!requiresQuestionAndAnswer) throw new ProviderException(SR.GetString(SR.ADMembership_PasswordReset_without_question_not_supported)); // // Other password reset related attributes // maxInvalidPasswordAttempts = SecUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0); passwordAttemptWindow = SecUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0); passwordAnswerAttemptLockoutDuration = SecUtility.GetIntValue(config, "passwordAnswerAttemptLockoutDuration", 30, false, 0); // // some more schema mappings that must be specified for Password Reset // attributeMapFailedPasswordAnswerCount = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerCount", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerCount != null) attributesInUse.Add(attributeMapFailedPasswordAnswerCount, null); attributeMapFailedPasswordAnswerTime = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerTime", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerTime != null) attributesInUse.Add(attributeMapFailedPasswordAnswerTime, null); attributeMapFailedPasswordAnswerLockoutTime = GetAttributeMapping(config, "attributeMapFailedPasswordAnswerLockoutTime", out maxLength /* ignored */); if (attributeMapFailedPasswordAnswerLockoutTime != null) attributesInUse.Add(attributeMapFailedPasswordAnswerLockoutTime, null); if (attributeMapFailedPasswordAnswerCount == null || attributeMapFailedPasswordAnswerTime == null || attributeMapFailedPasswordAnswerLockoutTime == null) throw new ProviderException(SR.GetString(SR.ADMembership_BadPasswordAnswerMappings_not_specified)); } // // Password Q&A mappings // attributeMapPasswordQuestion = GetAttributeMapping(config, "attributeMapPasswordQuestion", out maxLength); if (attributeMapPasswordQuestion != null) { if (maxLength != -1 && maxLength < maxPasswordQuestionLength) maxPasswordQuestionLength = maxLength; attributesInUse.Add(attributeMapPasswordQuestion, null); } attributeMapPasswordAnswer = GetAttributeMapping(config, "attributeMapPasswordAnswer", out maxLength); if (attributeMapPasswordAnswer != null) { if (maxLength != -1 && maxLength < maxPasswordAnswerLength) maxPasswordAnswerLength = maxLength; attributesInUse.Add(attributeMapPasswordAnswer, null); } if (requiresQuestionAndAnswer) { // // We also need to check that the password question and answer attributes are mapped // if (attributeMapPasswordQuestion == null || attributeMapPasswordAnswer == null) throw new ProviderException(SR.GetString(SR.ADMembership_PasswordQuestionAnswerMapping_not_specified)); } // // the auth type to be used for validation is determined as follows: // if directory is ADAM: authType = AuthType.Basic // if directory is AD: authType is based on connectionProtection (None, SSL: AuthType.Basic; SignAndSeal: AuthType.Negotiate) // if (directoryInfo.DirectoryType == DirectoryType.ADAM) authTypeForValidation = AuthType.Basic; else authTypeForValidation = directoryInfo.GetLdapAuthenticationTypes(directoryInfo.ConnectionProtection, CredentialsType.NonWindows); if (directoryInfo.DirectoryType == DirectoryType.AD) { // // if password reset is enabled we should perform all operations on a single server // if (enablePasswordReset) directoryInfo.SelectServer(); // // if the username is mapped to upn we need to do forest wide search to check the uniqueness of the upn. // and if the username is mapped to samAccountName then we need to append the domain name in the username for reliable validation // directoryInfo.InitializeDomainAndForestName(); } } // // Create a new common ldap connection for validation // connection = directoryInfo.CreateNewLdapConnection(authTypeForValidation); config.Remove("name"); config.Remove("applicationName"); config.Remove("connectionStringName"); config.Remove("requiresUniqueEmail"); config.Remove("enablePasswordReset"); config.Remove("requiresQuestionAndAnswer"); config.Remove("attributeMapPasswordQuestion"); config.Remove("attributeMapPasswordAnswer"); config.Remove("attributeMapUsername"); config.Remove("attributeMapEmail"); config.Remove("connectionProtection"); config.Remove("connectionUsername"); config.Remove("connectionPassword"); config.Remove("clientSearchTimeout"); config.Remove("serverSearchTimeout"); config.Remove("enableSearchMethods"); config.Remove("maxInvalidPasswordAttempts"); config.Remove("passwordAttemptWindow"); config.Remove("passwordAnswerAttemptLockoutDuration"); config.Remove("attributeMapFailedPasswordAnswerCount"); config.Remove("attributeMapFailedPasswordAnswerTime"); config.Remove("attributeMapFailedPasswordAnswerLockoutTime"); config.Remove("minRequiredPasswordLength"); config.Remove("minRequiredNonalphanumericCharacters"); config.Remove("passwordStrengthRegularExpression"); if (config.Count > 0) { string attribUnrecognized = config.GetKey(0); if (!String.IsNullOrEmpty(attribUnrecognized)) throw new ProviderException(SR.GetString(SR.Provider_unrecognized_attribute, attribUnrecognized)); } initialized = true; } #pragma warning restore 0688 [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { status = (MembershipCreateStatus) 0; MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (providerUserKey != null) throw new NotSupportedException(SR.GetString(SR.ADMembership_Setting_UserId_not_supported)); if ((passwordQuestion != null) && (attributeMapPasswordQuestion == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordQ_not_supported)); if ((passwordAnswer != null) && (attributeMapPasswordAnswer == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordA_not_supported)); if(!SecUtility.ValidateParameter(ref username, true, true, true, maxUsernameLengthForCreation)) { status = MembershipCreateStatus.InvalidUserName; return null; } // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) { status = MembershipCreateStatus.InvalidUserName; return null; } if(!ValidatePassword(password, maxPasswordLength)) { status = MembershipCreateStatus.InvalidPassword; return null; } if(!SecUtility.ValidateParameter(ref email, RequiresUniqueEmail, true, false, maxEmailLength)) { status = MembershipCreateStatus.InvalidEmail; return null; } if(!SecUtility.ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, true, false, maxPasswordQuestionLength)) { status = MembershipCreateStatus.InvalidQuestion; return null; } // validate the parameter before encoding the password answer if(!SecUtility.ValidateParameter(ref passwordAnswer, RequiresQuestionAndAnswer, true, false, maxPasswordAnswerLength)) { status = MembershipCreateStatus.InvalidAnswer; return null; } string encodedPasswordAnswer; if (!string.IsNullOrEmpty(passwordAnswer)) { encodedPasswordAnswer = Encrypt(passwordAnswer); // check length of encoded password answer if (maxPasswordAnswerLength > 0 && encodedPasswordAnswer.Length > maxPasswordAnswerLength) { status = MembershipCreateStatus.InvalidAnswer; return null; } } else encodedPasswordAnswer = passwordAnswer; if( password.Length < MinRequiredPasswordLength ) { status = MembershipCreateStatus.InvalidPassword; return null; } int count = 0; for( int i = 0; i < password.Length; i++ ) { if( !char.IsLetterOrDigit( password, i ) ) { count++; } } if( count < MinRequiredNonAlphanumericCharacters ) { status = MembershipCreateStatus.InvalidPassword; return null; } if( PasswordStrengthRegularExpression.Length > 0 ) { if( !Regex.IsMatch( password, PasswordStrengthRegularExpression ) ) { status = MembershipCreateStatus.InvalidPassword; return null; } } ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true); OnValidatingPassword(e); if(e.Cancel) { status = MembershipCreateStatus.InvalidPassword; return null; } try { // // Get the directory entry for the container and create a user object under it // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.CreationContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = null; DirectoryEntry userEntry = null; try { containerEntry = connection.DirectoryEntry; // to avoid unnecessary searches (for better performance) containerEntry.AuthenticationType |= AuthenticationTypes.FastBind; // // we set the username as the cn // userEntry = containerEntry.Children.Add(GetEscapedRdn("CN=" + username), "user"); // // if we are talking to Active Directory // set the sAMAccountName (if username is not mapped to this attribute, we need to autogenerate it) // (NOTE: We do not need to do this if the domain controller functionality is Windows 2003 (dcLevel = 2)) // if (directoryInfo.DirectoryType == DirectoryType.AD) { string sAMAccountName= null; bool setSAMAccountName = false; if (usernameIsSAMAccountName) { sAMAccountName = username; setSAMAccountName = true; } else { int dcLevel = GetDomainControllerLevel(containerEntry.Options.GetCurrentServerName()); if (dcLevel != 2) { sAMAccountName = GenerateAccountName(); setSAMAccountName = true; } } if (setSAMAccountName) userEntry.Properties["sAMAccountName"].Value = sAMAccountName; } // // if username is mapped to userPrincipalName and we are talking to AD, we need to do // a GC search to find if the same upn already exists or not // On ADAM, uniqueness of userPrincipalName is enforced on the server itself across all partitions // if (usernameIsUPN) { if (directoryInfo.DirectoryType == DirectoryType.AD && !IsUpnUnique(username)) { status = MembershipCreateStatus.DuplicateUserName; return null; } userEntry.Properties["userPrincipalName"].Value = username; } // // set other attributes // if (email != null) { if (RequiresUniqueEmail && !IsEmailUnique(containerEntry, username, email, false /* existing */)) { status = MembershipCreateStatus.DuplicateEmail; return null; } userEntry.Properties[attributeMapEmail].Value = email; } if (passwordQuestion != null) userEntry.Properties[attributeMapPasswordQuestion].Value = passwordQuestion; if (passwordAnswer != null) userEntry.Properties[attributeMapPasswordAnswer].Value = encodedPasswordAnswer; // // commit the user object // try { userEntry.CommitChanges(); } catch (COMException e1) { if ((e1.ErrorCode == unchecked((int) 0x80071392)) || (e1.ErrorCode == unchecked((int) 0x8007200d))) { status = MembershipCreateStatus.DuplicateUserName; return null; } else if ((e1.ErrorCode == unchecked((int) 0x8007001f)) && (e1 is DirectoryServicesCOMException)) { // // this error corresponds to LDAP_OTHER // if username was mapped to sAMAccountName and the name is too long // then the extended error should be 1315 (ERROR_INVALID_ACCOUNT_NAME) // DirectoryServicesCOMException dsce = e1 as DirectoryServicesCOMException; if (dsce.ExtendedError == 1315) { status = MembershipCreateStatus.InvalidUserName; return null; } else throw; } else throw; } // // set the password // try { SetPasswordPortIfApplicable(userEntry); // // Set the password // userEntry.Invoke("SetPassword", new object[]{ password }); // // if the user is approved then we need to enable the account (disabled dy default) // if (isApproved) { if (directoryInfo.DirectoryType == DirectoryType.AD) { const int UF_ACCOUNT_DISABLED =0x2; const int UF_PASSWD_NOTREQD = 0x20; int val = (int)PropertyManager.GetPropertyValue(userEntry, "userAccountControl"); val &= ~(UF_ACCOUNT_DISABLED | UF_PASSWD_NOTREQD); userEntry.Properties["userAccountControl"].Value = val; } else { // ADAM case userEntry.Properties["msDS-UserAccountDisabled"].Value = false; } userEntry.CommitChanges(); } else { // // For ADAM the user may be created as enabled in some cases // so we need to explicitly disable it // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { userEntry.Properties["msDS-UserAccountDisabled"].Value = true; userEntry.CommitChanges(); } } // // For ADAM users, we need to add the user to the Readers group in that // partition // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { DirectoryEntry readersEntry = new DirectoryEntry(directoryInfo.GetADsPath("CN=Readers,CN=Roles," + directoryInfo.ADAMPartitionDN), directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); readersEntry.Properties["member"].Add(PropertyManager.GetPropertyValue(userEntry, "distinguishedName")); readersEntry.CommitChanges(); } } // // At this point we have already created the user object in AD/ADAM but we // have failed in either SetPassword or while enabling/disabling the user, so we try to delete the user object // catch (COMException) { containerEntry.Children.Remove(userEntry); throw; } catch (ProviderException) { containerEntry.Children.Remove(userEntry); throw; } catch (TargetInvocationException tie) { containerEntry.Children.Remove(userEntry); if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // status as InvalidPassword // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) { status = MembershipCreateStatus.InvalidPassword; return null; } // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // else if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } // // Create a user object // DirectoryEntry dummyEntry = null; bool dummyFlag = false; string dummyString; user = FindUser(userEntry, "(objectClass=*)", System.DirectoryServices.SearchScope.Base, false /*retrieveSAMAccountName */, out dummyEntry, out dummyFlag, out dummyString); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); // // if there are no mappings for password question and answer, we should throw a NotSupportedException // if ((newPasswordQuestion != null) && (attributeMapPasswordQuestion == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordQ_not_supported)); if ((newPasswordAnswer != null) && (attributeMapPasswordAnswer == null)) throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordA_not_supported)); CheckUserName( ref username, maxUsernameLength, "username" ); CheckPassword(password, maxPasswordLength, "password"); SecUtility.CheckParameter( ref newPasswordQuestion, RequiresQuestionAndAnswer, true, false, maxPasswordQuestionLength, "newPasswordQuestion" ); // validate the parameter before encoding the password answer CheckPasswordAnswer(ref newPasswordAnswer, RequiresQuestionAndAnswer, maxPasswordAnswerLength, "newPasswordAnswer"); string encodedPasswordAnswer; if (!string.IsNullOrEmpty(newPasswordAnswer)) { encodedPasswordAnswer = Encrypt(newPasswordAnswer); // check length of encoded password answer if (maxPasswordAnswerLength > 0 && encodedPasswordAnswer.Length > maxPasswordAnswerLength) throw new ArgumentException(SR.GetString(SR.ADMembership_Parameter_too_long, "newPasswordAnswer"), "newPasswordAnswer"); } else encodedPasswordAnswer = newPasswordAnswer; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, then simple bind will fail as it needs domain information. // To workaround this whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for authenticating the user. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } // // validate the user's credentials // if (!ValidateCredentials(usernameForAuthentication, password)) return false; if (EnablePasswordReset && resetBadPasswordAnswerAttributes) { // // user supplied correct password, so we need to reset the password answer tracking info // (NOTE: The reason we do not call the Reset method here is so that we can make all the modifications in one transaction) // userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; } if (newPasswordQuestion == null) { // set it to null only if it already exists if ((attributeMapPasswordQuestion != null) && (userEntry.Properties.Contains(attributeMapPasswordQuestion))) userEntry.Properties[attributeMapPasswordQuestion].Clear(); } else userEntry.Properties[attributeMapPasswordQuestion].Value = newPasswordQuestion; if (newPasswordAnswer == null) { // set it to null only if it already exists if ((attributeMapPasswordAnswer != null) && (userEntry.Properties.Contains(attributeMapPasswordAnswer))) userEntry.Properties[attributeMapPasswordAnswer].Clear(); } else userEntry.Properties[attributeMapPasswordAnswer].Value = encodedPasswordAnswer; userEntry.CommitChanges(); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password question and answer changed successfully // return true; } public override string GetPassword(string username, string passwordAnswer) { // // ADMembership Provider does not support password retrieval // throw new NotSupportedException(SR.GetString(SR.ADMembership_PasswordRetrieval_not_supported_AD)); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool ChangePassword(string username, string oldPassword, string newPassword) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username" ); CheckPassword(oldPassword, maxPasswordLength, "oldPassword"); CheckPassword(newPassword, maxPasswordLength, "newPassword"); if( newPassword.Length < MinRequiredPasswordLength ) { throw new ArgumentException(SR.GetString( SR.Password_too_short, "newPassword", MinRequiredPasswordLength.ToString(CultureInfo.InvariantCulture))); } int count = 0; for( int i = 0; i < newPassword.Length; i++ ) { if( !char.IsLetterOrDigit( newPassword, i ) ) { count++; } } if( count < MinRequiredNonAlphanumericCharacters ) { throw new ArgumentException(SR.GetString( SR.Password_need_more_non_alpha_numeric_chars, "newPassword", MinRequiredNonAlphanumericCharacters.ToString(CultureInfo.InvariantCulture))); } if( PasswordStrengthRegularExpression.Length > 0 ) { if( !Regex.IsMatch( newPassword, PasswordStrengthRegularExpression ) ) { throw new ArgumentException(SR.GetString(SR.Password_does_not_match_regular_expression, "newPassword")); } } ValidatePasswordEventArgs e = new ValidatePasswordEventArgs( username, newPassword, false ); OnValidatingPassword(e); if(e.Cancel) { if(e.FailureInformation != null) throw e.FailureInformation; else throw new ArgumentException(SR.GetString( SR.Membership_Custom_Password_Validation_Failure), "newPassword"); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, the S.DS(adsi) will pass NULL // domain name to the underlying wldap32 layer. This results in authentication failure even for valid credentials. To workaround this // whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for changing the password. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry (Also get sAMAccountName if needed) // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } // // associate the user's context with the directory entry // userEntry.Username = (usernameIsSAMAccountName) ? directoryInfo.DomainName + "\\" + usernameForAuthentication : usernameForAuthentication; userEntry.Password = oldPassword; userEntry.AuthenticationType = directoryInfo.GetAuthenticationTypes(directoryInfo.ConnectionProtection, (directoryInfo.DirectoryType == DirectoryType.AD) ? CredentialsType.Windows : CredentialsType.NonWindows); try { SetPasswordPortIfApplicable(userEntry); // // Change the password // userEntry.Invoke("ChangePassword", new object[]{ oldPassword, newPassword }); } catch (COMException e2) { if (e2.ErrorCode == unchecked((int) 0x8007052e)) return false; else throw; } catch (TargetInvocationException tie) { if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // MembershipPasswordException // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) throw new MembershipPasswordException(SR.GetString(SR.Membership_InvalidPassword), ce); // // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // else if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } if (EnablePasswordReset && resetBadPasswordAnswerAttributes) { // // associate the process context with the directory entry // userEntry.Username = directoryInfo.GetUsername(); userEntry.Password = directoryInfo.GetPassword(); userEntry.AuthenticationType = directoryInfo.AuthenticationTypes; // // user supplied correct password, so we need to reset the password answer tracking info // ResetBadPasswordAnswerAttributes(userEntry); } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password changed successfully // return true; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override string ResetPassword(string username, string passwordAnswer) { string newPassword = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnablePasswordReset) throw new NotSupportedException(SR.GetString(SR.Not_configured_to_support_password_resets)); CheckUserName(ref username, maxUsernameLength, "username"); CheckPasswordAnswer(ref passwordAnswer, RequiresQuestionAndAnswer, maxPasswordAnswerLength, "passwordAnswer"); try { // // validate the password answer // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; try { // // get the user's directory entry // MembershipUser user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); // // user does not exist, throw exception // if (user == null) throw new ProviderException(SR.GetString(SR.Membership_UserNotFound)); // // if user is locked, throw an exception // if (user.IsLockedOut) throw new MembershipPasswordException(SR.GetString(SR.Membership_AccountLockOut)); string storedPasswordAnswer = Decrypt((string) PropertyManager.GetPropertyValue(userEntry, attributeMapPasswordAnswer)); if (!StringUtil.EqualsIgnoreCase(passwordAnswer, storedPasswordAnswer)) { UpdateBadPasswordAnswerAttributes(userEntry); throw new MembershipPasswordException(SR.GetString(SR.Membership_WrongAnswer)); } else { if (resetBadPasswordAnswerAttributes) ResetBadPasswordAnswerAttributes(userEntry); } SetPasswordPortIfApplicable(userEntry); // // Reset the password (generating a random new password) // newPassword = GeneratePassword(); ValidatePasswordEventArgs e = new ValidatePasswordEventArgs( username, newPassword, false ); OnValidatingPassword(e); if(e.Cancel) { if(e.FailureInformation != null) throw e.FailureInformation; else throw new ProviderException(SR.GetString( SR.Membership_Custom_Password_Validation_Failure)); } userEntry.Invoke("SetPassword", new object[]{ newPassword }); } catch (TargetInvocationException tie) { if (tie.InnerException is COMException) { COMException ce = (COMException) tie.InnerException; int errorCode = ce.ErrorCode; // // if the exception is due to password not meeting complexity requirements, then return // ProviderException // if ((errorCode == unchecked((int) 0x800708c5)) || (errorCode == unchecked((int) 0x8007202f)) || (errorCode == unchecked((int) 0x8007052d)) || (errorCode == unchecked((int) 0x8007052f))) throw new ProviderException(SR.GetString(SR.ADMembership_Generated_password_not_complex), ce); // // if the target is ADAM and the exception is due to property not found, this indicates that a secure // connection could not be setup for changing the password and ADSI is falling back to kerberos which does not work for ADAM // so we will provide a clearer exception // if ((errorCode == unchecked((int) 0x8000500d) && (directoryInfo.DirectoryType == DirectoryType.ADAM))) throw new ProviderException(SR.GetString(SR.ADMembership_No_secure_conn_for_password)); else throw; } else throw; } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // Password was reset successfully, return the generated password // return newPassword; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool UnlockUser(string username) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName( ref username, maxUsernameLength, "username" ); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; try { // // get the user's directory entry // userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); // // user does not exist, return false // if (userEntry == null) return false; userEntry.Properties["lockoutTime"].Value = 0; if (EnablePasswordReset) { userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; } userEntry.CommitChanges(); } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } // // user unlocked successfully, return true // return true; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override void UpdateUser(MembershipUser user) { bool emailModified = true; bool commentModified = true; bool isApprovedModified = true; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if( user == null ) { throw new ArgumentNullException("user" ); } ActiveDirectoryMembershipUser adUser = user as ActiveDirectoryMembershipUser; if (adUser != null) { // // check which fields have really been modified // emailModified = adUser.emailModified; commentModified = adUser.commentModified; isApprovedModified = adUser.isApprovedModified; } string temp = user.UserName; CheckUserName( ref temp, maxUsernameLength, "UserName" ); string email = user.Email; if (emailModified) SecUtility.CheckParameter( ref email, RequiresUniqueEmail, true, false, maxEmailLength, "Email"); if (commentModified && user.Comment != null) { if (user.Comment.Length == 0) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, "Comment"), "Comment"); if (maxCommentLength > 0 && user.Comment.Length > maxCommentLength) throw new ArgumentException(SR.GetString(SR.Parameter_too_long, "Comment", maxCommentLength.ToString(CultureInfo.InvariantCulture)), "Comment"); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; try { // // get the user's directory entry // userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(user.UserName) + ")"); if (userEntry == null) throw new ProviderException(SR.GetString(SR.Membership_UserNotFound)); if (!((emailModified) || (commentModified) || (isApprovedModified))) // nothing has been modified return; // // update the email // if enableUniqueEmail is specified, we need to ensure that the email is unique // if (emailModified) { if (email == null) { // set the email to null only if email already exists if (userEntry.Properties.Contains(attributeMapEmail)) userEntry.Properties[attributeMapEmail].Clear(); } else { if (RequiresUniqueEmail && !IsEmailUnique(null, user.UserName, email, true /* existing */)) throw new ProviderException(SR.GetString(SR.Membership_DuplicateEmail)); userEntry.Properties[attributeMapEmail].Value = email; } } // // update the comment // if (commentModified) { if (user.Comment == null) { // set the comment to null only if comment already exists if (userEntry.Properties.Contains("comment")) userEntry.Properties["comment"].Clear(); } else { // // we use the original value ("user.Comment") to preserve all white space // (including leading and trailing white space) userEntry.Properties["comment"].Value = user.Comment; } } // // update the IsApproved field // if (isApprovedModified) { if (directoryInfo.DirectoryType == DirectoryType.AD) { // userAccountControl attribute const int UF_ACCOUNT_DISABLED =0x2; int val = (int)PropertyManager.GetPropertyValue(userEntry, "userAccountControl"); if (user.IsApproved) val &= ~UF_ACCOUNT_DISABLED; else val |= UF_ACCOUNT_DISABLED; userEntry.Properties["userAccountControl"].Value = val; } else { // different attribute for ADAM userEntry.Properties["msDS-UserAccountDisabled"].Value = !(user.IsApproved); } } userEntry.CommitChanges(); if (adUser != null) { adUser.emailModified = false; adUser.commentModified = false; adUser.isApprovedModified = false; } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return; } [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.LinkDemand, Unrestricted=true)] public override bool ValidateUser(string username, string password) { if( ValidateUserCore(username, password)) { PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_SUCCESS); WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationSuccess, username); return true; } else { PerfCounters.IncrementCounter(AppPerfCounter.MEMBER_FAIL); WebBaseEvent.RaiseSystemEvent(null, WebEventCodes.AuditMembershipAuthenticationFailure, username); return false; } } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] private bool ValidateUserCore(string username, string password) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if(!SecUtility.ValidateParameter(ref username, true, true, true, maxUsernameLength)) { return false; } // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) { return false; } if( !ValidatePassword(password, maxPasswordLength)) { return false; } bool result = false; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; DirectoryEntry userEntry = null; bool resetBadPasswordAnswerAttributes = false; string usernameForAuthentication = null; try { if (EnablePasswordReset) { // // get the user's directory entry // NOTE: If the username is mapped to userPrincipalName and the username does not contain '@' in it, then simple bind will fail as it needs domain information. // To workaround this whenever we are talking to AD, username is mapped to userPrincipalName and does not contain '@', we will get the sAMAccountName // while getting the user object and use that for authenticating the user. // MembershipUser user = null; if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; user = FindUserAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out userEntry, out resetBadPasswordAnswerAttributes); usernameForAuthentication = username; } // // user does not exist, return false // if (user == null) return false; // // here we want to check if the user is already unlocked due to bad password answer (or bad password) // if (user.IsLockedOut) return false; } else { // // get the user's directory entry // if ((directoryInfo.DirectoryType == DirectoryType.AD) && (usernameIsUPN) && (username.IndexOf('@') == -1)) { string sAMAccountName = null; userEntry = FindUserEntryAndSAMAccountName(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out sAMAccountName); usernameForAuthentication = directoryInfo.DomainName + "\\" + sAMAccountName; } else { userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")"); usernameForAuthentication = username; } // // user does not exist, return false // if (userEntry == null) return false; } result = ValidateCredentials(usernameForAuthentication, password); if (EnablePasswordReset && result && resetBadPasswordAnswerAttributes) { // // user supplied correct password, so we need to reset the password answer tracking info // ResetBadPasswordAnswerAttributes(userEntry); } } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return result; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if( providerUserKey == null ) { throw new ArgumentNullException( "providerUserKey" ); } if ( !( providerUserKey is SecurityIdentifier) ) { throw new ArgumentException( SR.GetString( SR.ADMembership_InvalidProviderUserKey ), "providerUserKey" ); } try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { // // Search for the user and return a MembershipUser object // SecurityIdentifier sid = providerUserKey as SecurityIdentifier; StringBuilder sidHexValueStr = new StringBuilder(); int binaryLength = sid.BinaryLength; byte[] sidBinaryForm = new byte[binaryLength]; sid.GetBinaryForm(sidBinaryForm, 0); for (int i = 0; i < binaryLength; i++) { sidHexValueStr.Append("\\"); sidHexValueStr.Append(sidBinaryForm[i].ToString("x2", NumberFormatInfo.InvariantInfo)); } DirectoryEntry dummyEntry; bool resetBadPasswordAnswerAttributes = false; user = FindUser(containerEntry, "(" + attributeMapUsername + "=*)(objectSid=" + sidHexValueStr.ToString() + ")", out dummyEntry /* ignored */, out resetBadPasswordAnswerAttributes /* ignored */); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUser GetUser(string username, bool userIsOnline) { MembershipUser user = null; if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username" ); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { // // Search for the user and return a MembershipUser object // DirectoryEntry dummyEntry; bool resetBadPasswordAnswerAttributes = false; user = FindUser(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", out dummyEntry /*ignored */, out resetBadPasswordAnswerAttributes /* ignored */); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return user; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override string GetUserNameByEmail(string email) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); SecUtility.CheckParameter(ref email, false, true, false, maxEmailLength, "email"); string username = null; try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; SearchResultCollection resCol = null; try { DirectorySearcher searcher = new DirectorySearcher(containerEntry); if (email != null) searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) +"))"; else searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(!(" + attributeMapEmail + "=" +"*)))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; searcher.PropertiesToLoad.Add(attributeMapUsername); if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); resCol = searcher.FindAll(); bool userFound = false; foreach (SearchResult res in resCol) { if (!userFound) { username = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapUsername); userFound = true; if (!RequiresUniqueEmail) break; } else { if (RequiresUniqueEmail) { // there is a duplicate entry, so we need to throw an ProviderException throw new ProviderException(SR.GetString(SR.Membership_more_than_one_user_with_email)); } else // we should never get here break; } } } finally { if (resCol != null) resCol.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return username; } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override bool DeleteUser(string username, bool deleteAllRelatedData) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); CheckUserName(ref username, maxUsernameLength, "username"); try { // // Get the Directory Entry for the container // DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.CreationContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; // to avoid unnecessary searches (for better performance) containerEntry.AuthenticationType |= AuthenticationTypes.FastBind; DirectoryEntry userEntry = null; try { // // Get the directory entry for the user // string dummyString; userEntry = FindUserEntry(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(username) + ")", System.DirectoryServices.SearchScope.OneLevel, false /* retrieveSAMAccountName */, out dummyString); if (userEntry == null) return false; // // Remove the entry from the container // containerEntry.Children.Remove(userEntry); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80072030)) { // // incase some one else deleted the object just before this // return false; } else throw; } finally { if (userEntry != null) userEntry.Dispose(); connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } return true; } public virtual string GeneratePassword() { // // return Membership.GeneratePassword( MinRequiredPasswordLength < PASSWORD_SIZE ? PASSWORD_SIZE : MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { return FindUsersByName("*", pageIndex, pageSize, out totalRecords); } public override int GetNumberOfUsersOnline() { // // ADMembershipProvider does not support the notion of online users // throw new NotSupportedException(SR.GetString(SR.ADMembership_OnlineUsers_not_supported)); } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnableSearchMethods) throw new NotSupportedException(SR.GetString(SR.ADMembership_Provider_SearchMethods_not_supported)); SecUtility.CheckParameter( ref usernameToMatch, true, true, true, maxUsernameLength, "usernameToMatch" ); if ( pageIndex < 0 ) throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex"); if ( pageSize < 1 ) throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize"); long upperBound = (long)pageIndex * pageSize + pageSize - 1; if ( upperBound > Int32.MaxValue ) throw new ArgumentException(SR.GetString(SR.PageIndex_PageSize_bad), "pageIndex and pageSize"); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { totalRecords = 0; return FindUsers(containerEntry, "(" + attributeMapUsername + "=" + GetEscapedFilterValue(usernameToMatch, false) + ")", attributeMapUsername, pageIndex, pageSize, out totalRecords); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } } [DirectoryServicesPermission(SecurityAction.Assert, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.Demand, Unrestricted=true)] [DirectoryServicesPermission(SecurityAction.InheritanceDemand, Unrestricted=true)] public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { if (!initialized) throw new InvalidOperationException(SR.GetString(SR.ADMembership_Provider_not_initialized)); if (!EnableSearchMethods) throw new NotSupportedException(SR.GetString(SR.ADMembership_Provider_SearchMethods_not_supported)); SecUtility.CheckParameter(ref emailToMatch, false, true, false, maxEmailLength, "emailToMatch"); if ( pageIndex < 0 ) throw new ArgumentException(SR.GetString(SR.PageIndex_bad), "pageIndex"); if ( pageSize < 1 ) throw new ArgumentException(SR.GetString(SR.PageSize_bad), "pageSize"); long upperBound = (long)pageIndex * pageSize + pageSize - 1; if ( upperBound > Int32.MaxValue ) throw new ArgumentException(SR.GetString(SR.PageIndex_PageSize_bad), "pageIndex and pageSize"); try { DirectoryEntryHolder connection = ActiveDirectoryConnectionHelper.GetDirectoryEntry(directoryInfo, directoryInfo.ContainerDN, true /* revertImpersonation */); DirectoryEntry containerEntry = connection.DirectoryEntry; try { totalRecords = 0; string filter = null; if (emailToMatch != null) filter = "(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(emailToMatch, false) +")"; else filter = "(" + attributeMapUsername + "=*)(!(" + attributeMapEmail + "=" +"*))"; return FindUsers(containerEntry, filter, attributeMapEmail, pageIndex, pageSize, out totalRecords); } finally { connection.Close(); } } catch { // // this outer try-catch is to mitigate the exception filter attack (since we maybe suspending impersonation) // throw; } } private bool ValidateCredentials(string username, string password) { bool result = false; NetworkCredential credentialForValidation = (usernameIsSAMAccountName) ? new NetworkCredential(username, password, directoryInfo.DomainName) : DirectoryInformation.GetCredentialsWithDomain(new NetworkCredential(username, password)); // // NOTE: we do not need to revert context here since this method is always // called with explicit credentials // // // if this is concurrent bind (use the common connection) // if (directoryInfo.ConcurrentBindSupported) { try { connection.Bind(credentialForValidation); result = true; } catch (LdapException e) { if (e.ErrorCode == 0x31) { // // authentication failure, invalid user // result = false; } else { // // some other failure // throw; } } } else { // // create a new ldap connection // LdapConnection newConnection = directoryInfo.CreateNewLdapConnection(authTypeForValidation); try { newConnection.Bind(credentialForValidation); result = true; } catch (LdapException e2) { if (e2.ErrorCode == 0x31) { // // authentication failure, invalid user // result = false; } else { // // some other failure // throw; } } finally { newConnection.Dispose(); } } return result; } private DirectoryEntry FindUserEntryAndSAMAccountName(DirectoryEntry containerEntry, string filter, out string sAMAccountName) { return FindUserEntry(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, true /*retrieveSAMAccountName */, out sAMAccountName); } private DirectoryEntry FindUserEntry(DirectoryEntry containerEntry, string filter) { string dummyString; return FindUserEntry(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, false /*retrieveSAMAccountName */, out dummyString); } private DirectoryEntry FindUserEntry(DirectoryEntry containerEntry, string filter, System.DirectoryServices.SearchScope searchScope, bool retrieveSAMAccountName, out string sAMAccountName) { Debug.Assert(containerEntry != null); DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = searchScope; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); if (retrieveSAMAccountName) searcher.PropertiesToLoad.Add("sAMAccountName"); SearchResult res = searcher.FindOne(); sAMAccountName = null; if (res != null) { if (retrieveSAMAccountName) sAMAccountName = (string) PropertyManager.GetSearchResultPropertyValue(res, "sAMAccountName"); return res.GetDirectoryEntry(); } else return null; } private MembershipUser FindUserAndSAMAccountName(DirectoryEntry containerEntry, string filter, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes, out string sAMAccountName) { return FindUser(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, true /* retrieveSAMAccountName */, out userEntry, out resetBadPasswordAnswerAttributes, out sAMAccountName); } private MembershipUser FindUser(DirectoryEntry containerEntry, string filter, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes) { string dummyString; return FindUser(containerEntry, filter, System.DirectoryServices.SearchScope.Subtree, false /* retrieveSAMAccountName */, out userEntry, out resetBadPasswordAnswerAttributes, out dummyString); } private MembershipUser FindUser(DirectoryEntry containerEntry, string filter, System.DirectoryServices.SearchScope searchScope, bool retrieveSAMAccountName, out DirectoryEntry userEntry, out bool resetBadPasswordAnswerAttributes, out string sAMAccountName) { Debug.Assert(containerEntry != null); MembershipUser user = null; DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = searchScope; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); // // load all the attributes needed to create a MembershipUser object // searcher.PropertiesToLoad.Add(attributeMapUsername); searcher.PropertiesToLoad.Add("objectSid"); searcher.PropertiesToLoad.Add(attributeMapEmail); searcher.PropertiesToLoad.Add("comment"); searcher.PropertiesToLoad.Add("whenCreated"); searcher.PropertiesToLoad.Add("pwdLastSet"); searcher.PropertiesToLoad.Add("msDS-User-Account-Control-Computed"); searcher.PropertiesToLoad.Add("lockoutTime"); if (retrieveSAMAccountName) searcher.PropertiesToLoad.Add("sAMAccountName"); if (attributeMapPasswordQuestion != null) searcher.PropertiesToLoad.Add(attributeMapPasswordQuestion); if (directoryInfo.DirectoryType == DirectoryType.AD) searcher.PropertiesToLoad.Add("userAccountControl"); else searcher.PropertiesToLoad.Add("msDS-UserAccountDisabled"); if (EnablePasswordReset) { searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerCount); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerTime); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerLockoutTime); } SearchResult res = searcher.FindOne(); resetBadPasswordAnswerAttributes = false; sAMAccountName = null; if (res != null) { user = GetMembershipUserFromSearchResult(res); userEntry = res.GetDirectoryEntry(); if (retrieveSAMAccountName) sAMAccountName = (string) PropertyManager.GetSearchResultPropertyValue(res, "sAMAccountName"); if ((EnablePasswordReset) && res.Properties.Contains(attributeMapFailedPasswordAnswerCount)) resetBadPasswordAnswerAttributes = ((int) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerCount) > 0); } else { userEntry = null; } return user; } private MembershipUserCollection FindUsers(DirectoryEntry containerEntry, string filter, string sortKey, int pageIndex, int pageSize, out int totalRecords) { Debug.Assert(containerEntry != null); MembershipUserCollection col = new MembershipUserCollection(); int lastOffset = (pageIndex + 1) * pageSize; int startOffset = lastOffset -pageSize + 1; DirectorySearcher searcher = new DirectorySearcher(containerEntry); searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; searcher.Filter = "(&(objectCategory=person)(objectClass=user)" + filter + ")"; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); // // load all the attributes needed to create a MembershipUser object // searcher.PropertiesToLoad.Add(attributeMapUsername); searcher.PropertiesToLoad.Add("objectSid"); searcher.PropertiesToLoad.Add(attributeMapEmail); searcher.PropertiesToLoad.Add("comment"); searcher.PropertiesToLoad.Add("whenCreated"); searcher.PropertiesToLoad.Add("pwdLastSet"); searcher.PropertiesToLoad.Add("msDS-User-Account-Control-Computed"); searcher.PropertiesToLoad.Add("lockoutTime"); if (attributeMapPasswordQuestion != null) searcher.PropertiesToLoad.Add(attributeMapPasswordQuestion); if (directoryInfo.DirectoryType == DirectoryType.AD) searcher.PropertiesToLoad.Add("userAccountControl"); else searcher.PropertiesToLoad.Add("msDS-UserAccountDisabled"); if (EnablePasswordReset) { searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerCount); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerTime); searcher.PropertiesToLoad.Add(attributeMapFailedPasswordAnswerLockoutTime); } // // turn on paging // searcher.PageSize = 512; // // need to sort the users based on the attribute that is mapped to the username // searcher.Sort = new SortOption(sortKey, SortDirection.Ascending); SearchResultCollection resCol = searcher.FindAll(); try { int count = 0; totalRecords = 0; foreach(SearchResult res in resCol) { count++; // // add only the requested window of the result set // if (count >= startOffset && count <= lastOffset) { col.Add(GetMembershipUserFromSearchResult(res)); } } totalRecords = count; } finally { resCol.Dispose(); } return col; } private void CheckPasswordAnswer(ref string passwordAnswer, bool checkForNull, int maxSize,string paramName) { if (passwordAnswer == null) { if (checkForNull) throw new ArgumentNullException(paramName); return; } passwordAnswer = passwordAnswer.Trim(); if (passwordAnswer.Length < 1) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, paramName), paramName); if (maxSize > 0 && passwordAnswer.Length > maxSize) throw new ArgumentException(SR.GetString(SR.ADMembership_Parameter_too_long, paramName), paramName); } private bool ValidatePassword(string password, int maxSize) { if (password == null) return false; if (password.Trim().Length < 1) return false; if (maxSize > 0 && password.Length > maxSize) return false; return true; } private void CheckPassword(string password, int maxSize, string paramName) { if (password == null) throw new ArgumentNullException(paramName); if (password.Trim().Length < 1) throw new ArgumentException(SR.GetString(SR.Parameter_can_not_be_empty, paramName), paramName); if (maxSize > 0 && password.Length > maxSize) throw new ArgumentException(SR.GetString(SR.Parameter_too_long, paramName, maxSize.ToString(CultureInfo.InvariantCulture)), paramName); } private void CheckUserName(ref string username, int maxSize, string paramName) { SecUtility.CheckParameter( ref username, true, true, true, maxSize, paramName ); // // if username is mapped to UPN, it should not contain '\' // if (usernameIsUPN && (username.IndexOf('\\') != -1)) throw new ArgumentException(SR.GetString(SR.ADMembership_UPN_contains_backslash, paramName), paramName); } private int GetDomainControllerLevel(string serverName) { int dcLevel = 0; DirectoryEntry rootdse = new DirectoryEntry("LDAP://" + serverName + "/RootDSE", directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); string dcLevelString = (string) rootdse.Properties["domainControllerFunctionality"].Value; if (dcLevelString != null) dcLevel = Int32.Parse(dcLevelString, NumberFormatInfo.InvariantInfo); return dcLevel; } private void UpdateBadPasswordAnswerAttributes(DirectoryEntry userEntry) { // // get the password answer tracking related attributes to determine if we are still in an // active window for bad password answer attempts // int badPasswordAttemptCount = 0; bool inActiveWindow = false; DateTime currentTime = DateTime.UtcNow; if (userEntry.Properties.Contains(attributeMapFailedPasswordAnswerTime)) { DateTime lastBadPasswordAnswerTime = GetDateTimeFromLargeInteger((NativeComInterfaces.IAdsLargeInteger) PropertyManager.GetPropertyValue(userEntry, attributeMapFailedPasswordAnswerTime)); TimeSpan diffTime = currentTime.Subtract(lastBadPasswordAnswerTime); inActiveWindow = (diffTime <= new TimeSpan(0, PasswordAttemptWindow, 0)); } // get the current bad password count int currentBadPasswordAttemptCount = 0; if (userEntry.Properties.Contains(attributeMapFailedPasswordAnswerCount)) currentBadPasswordAttemptCount = (int) PropertyManager.GetPropertyValue(userEntry, attributeMapFailedPasswordAnswerCount); if (inActiveWindow && (currentBadPasswordAttemptCount > 0)) { // within an active window for bad password answer attempts (increment count, if greater than 0) badPasswordAttemptCount = currentBadPasswordAttemptCount + 1; } else { // start a new active window (set count = 1) badPasswordAttemptCount = 1; } // set the bad password attempt count and time userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = badPasswordAttemptCount; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = GetLargeIntegerFromDateTime(currentTime); if (badPasswordAttemptCount >= maxInvalidPasswordAttempts) { // // user needs to be locked out due to too many bad password answer attempts // userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = GetLargeIntegerFromDateTime(currentTime); } userEntry.CommitChanges(); } private void ResetBadPasswordAnswerAttributes(DirectoryEntry userEntry) { // // clear the password answer tracking related attributes (reset the window) // userEntry.Properties[attributeMapFailedPasswordAnswerCount].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerTime].Value = 0; userEntry.Properties[attributeMapFailedPasswordAnswerLockoutTime].Value = 0; userEntry.CommitChanges(); } private MembershipUser GetMembershipUserFromSearchResult(SearchResult res) { // username string username = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapUsername); // providerUserKey is the SID of the user byte[] sidBinaryForm = (byte[]) PropertyManager.GetSearchResultPropertyValue(res, "objectSid"); object providerUserKey = new SecurityIdentifier(sidBinaryForm, 0); // email (optional) string email = (res.Properties.Contains(attributeMapEmail)) ? (string) res.Properties[attributeMapEmail][0] : null; // passwordQuestion string passwordQuestion = null; if ((attributeMapPasswordQuestion != null) && (res.Properties.Contains(attributeMapPasswordQuestion))) passwordQuestion = (string) PropertyManager.GetSearchResultPropertyValue(res, attributeMapPasswordQuestion); //comment (optional) string comment = (res.Properties.Contains("comment")) ? (string) res.Properties["comment"][0] : null; //isApproved and isLockedOut bool isApproved; bool isLockedOut = false; if (directoryInfo.DirectoryType == DirectoryType.AD) { int val = (int) PropertyManager.GetSearchResultPropertyValue(res, "userAccountControl"); if ((val & UF_ACCOUNT_DISABLED) == 0) isApproved = true; else isApproved = false; // // the "msDS-User-Account-Control-Computed" is the correct attribute to determine if the // user is locked out or not. This attribute does not exist in W2K schema, so if we do not see this attribute in the result set // we will use the "lockoutTime". Note, if the user is not locked out and the schema is W2K3, this attribute will exist in the result // and have value 0 (since it's constructed), therefore absence of the attribute signifies that schema is W2K. // if (res.Properties.Contains("msDS-User-Account-Control-Computed")) { int val2 = (int) PropertyManager.GetSearchResultPropertyValue(res, "msDS-User-Account-Control-Computed"); if ((val2 & UF_LOCKOUT) != 0) isLockedOut = true; } else if (res.Properties.Contains("lockoutTime")) { // NOTE: all date-time computation is done in UTC time though the values returned are in local time DateTime lockoutTime = DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime")); DateTime currentTime = DateTime.UtcNow; TimeSpan diffTime = currentTime.Subtract(lockoutTime); isLockedOut = (diffTime <= directoryInfo.ADLockoutDuration); } } else { isApproved = true; // if the msDS-UserAccountDisabled attribute if not present then the user is enabled if (res.Properties.Contains("msDS-UserAccountDisabled")) isApproved = !((bool) PropertyManager.GetSearchResultPropertyValue(res, "msDS-UserAccountDisabled")); // // ADAM schema contains the "msDS-User-Account-Control-Computed" attribute, therefore it is used to determine the // lockout status of the user // int val2 = (int) PropertyManager.GetSearchResultPropertyValue(res, "msDS-User-Account-Control-Computed"); if ((val2 & UF_LOCKOUT) != 0) isLockedOut = true; } // lastLockoutDate (DateTime.FromFileTime cnoverts to Local time) DateTime lastLockoutDate = DefaultLastLockoutDate; if (isLockedOut) lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime")); // // if password reset is enabled, we need to check if user is locked out due to bad password answer (and set/change the last lockout date) // if ((EnablePasswordReset) && (res.Properties.Contains(attributeMapFailedPasswordAnswerLockoutTime))) { // NOTE: all date-time computation is done in UTC time though the values returned are in local time DateTime badPasswordAnswerLockoutTime = DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); DateTime currentTime = DateTime.UtcNow; TimeSpan diffTime = currentTime.Subtract(badPasswordAnswerLockoutTime); bool isLockedOutByBadPasswordAnswer = (diffTime <= new TimeSpan(0, PasswordAnswerAttemptLockoutDuration, 0)); if (isLockedOutByBadPasswordAnswer) { if (isLockedOut) { // // The account is locked both due to bad password and bad password answer, so we have two lockout dates // Taking the later one. // if (DateTime.Compare(badPasswordAnswerLockoutTime, DateTime.FromFileTimeUtc((Int64) PropertyManager.GetSearchResultPropertyValue(res, "lockoutTime"))) > 0) lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); } else { // // Account is locked out only due to bad password answer // isLockedOut = true; lastLockoutDate = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, attributeMapFailedPasswordAnswerLockoutTime)); } } } //createTimeStamp DateTime whenCreated = ((DateTime) PropertyManager.GetSearchResultPropertyValue(res, "whenCreated")).ToLocalTime(); //lastLogon (not supported) DateTime lastLogon = DateTime.MinValue; //lastActivity (not supported) DateTime lastActivity = DateTime.MinValue; //lastpwdchange (DateTime.FromFileTime cnoverts to Local time) DateTime lastPasswordChange = DateTime.FromFileTime((Int64) PropertyManager.GetSearchResultPropertyValue(res, "pwdLastSet")); return new ActiveDirectoryMembershipUser(Name, username, sidBinaryForm, providerUserKey, email, passwordQuestion, comment, isApproved, isLockedOut, whenCreated, lastLogon, lastActivity, lastPasswordChange, lastLockoutDate, true /* valuesAreUpdated */); } private string GetEscapedRdn(string rdn) { NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); return pathCracker.GetEscapedElement(0, rdn); } // // Generates an escaped name that may be used in an LDAP query. The characters // ( ) * \ must be escaped when used in an LDAP query per RFC 2254. // internal string GetEscapedFilterValue(string filterValue) { return GetEscapedFilterValue(filterValue, true /* escapeWildChar */); } internal string GetEscapedFilterValue(string filterValue, bool escapeWildChar) { int index = -1; char[] specialCharacters = new char[] { '(', ')', '*', '\\' }; char[] specialCharactersWithoutWildChar = new char[] { '(', ')', '\\' }; index = escapeWildChar ? filterValue.IndexOfAny(specialCharacters) : filterValue.IndexOfAny(specialCharactersWithoutWildChar); if (index != -1) { // // if it contains any of the special characters then we // need to escape those // StringBuilder str = new StringBuilder(2 * filterValue.Length); str.Append(filterValue.Substring(0, index)); for (int i = index; i < filterValue.Length; i++) { switch (filterValue[i]) { case ('(') : { str.Append("\\28"); break; } case (')') : { str.Append("\\29"); break; } case ('*') : { if (escapeWildChar) str.Append("\\2A"); else str.Append("*"); break; } case ('\\') : { // this may be the escaped version of '*', i.e. "\2A" or "\2a" if ((escapeWildChar) || (!(((filterValue.Length - i) >= 3) && (filterValue[i + 1] == '2') && ((filterValue[i + 2] == 'A') || (filterValue[i + 2] == 'a'))))) str.Append("\\5C"); else str.Append("\\"); break; } default : { str.Append(filterValue[i]); break; } } } return str.ToString(); } else { // // just return the original string // return filterValue; } } // // private string GenerateAccountName() { char[] accountNameEncodingTable = new char[] {'0','1','2','3','4','5','6','7', '8','9','A','B','C','D','E','F', 'G','H','I','J','K','L','M','N', 'O','P','Q','R','S','T','U','V' }; // // account name will be 20 characters long; // char[] accountName = new char[20]; // // Generate a 64 bit random quantity // byte[] random = new byte[12]; //RNGCryptoServiceProvider is an implementation of a random number generator. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(random); // The array is now filled with cryptographically strong random bytes. // create a 32 bit random numbers from this uint random32a = 0; uint random32b = 0; uint random32c = 0; for (int i = 0; i < 4; i++) { random32a = random32a | unchecked((uint)(random[i] << (8 * i))); } for (int i = 0; i < 4; i++) { random32b = random32b | unchecked((uint)(random[4 + i] << (8 * i))); } for (int i = 0; i < 4; i++) { random32c = random32c | unchecked((uint)(random[8 + i] << (8 * i))); } // // The first character in the account name is a $ sign // accountName[0] = '$'; // // The next 6 chars are the least 30 bits of random32a (base 32 encoded) // for (int i=1;i<=6;i++) { // // Lookup the char corresponding to the last 5 bits of // random32a // accountName[i] = accountNameEncodingTable[(random32a & 0x1F)]; // // Shift random32a right by 5 places // random32a = random32a >> 5; } // // The next char is a "-" to make the name more readable // accountName[7] = '-'; // // The next 12 chars are formed by base 32 encoding the last 30 // bits of random32b and random32c. // for (int i=8;i<=13;i++) { // // Lookup the char corresponding to the last 5 bits // accountName[i] = accountNameEncodingTable[(random32b & 0x1F)]; // // Shift right by 5 places // random32b = random32b >> 5; } for (int i=13;i<=19;i++) { // // Lookup the char corresponding to the last 5 bits // accountName[i] = accountNameEncodingTable[(random32c & 0x1F)]; // // Shift right by 5 places // random32c = random32c >> 5; } return new String(accountName); } private void SetPasswordPortIfApplicable(DirectoryEntry userEntry) { // // For ADAM, if the port is specified and we are using Ssl for connection protection, // we should set the password port. // if (directoryInfo.DirectoryType == DirectoryType.ADAM) { try { if ((directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.Ssl) && (directoryInfo.PortSpecified)) { userEntry.Options.PasswordPort = directoryInfo.Port; userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingSsl; } else if ((directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal) || (directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.None)) { userEntry.Options.PasswordPort = directoryInfo.Port; userEntry.Options.PasswordEncoding = PasswordEncodingMethod.PasswordEncodingClear; } } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005008)) { // // If ADSI returns E_ADS_BAD_PARAMETER, it means we are running // on a platform where ADSI does not support setting of the password port // Since ADSI will set the password port to 636 and password method to Ssl, we can // ignore this error only if that is what we are trying to set // if (!((directoryInfo.Port == DirectoryInformation.SSL_PORT) && (directoryInfo.ConnectionProtection == ActiveDirectoryConnectionProtection.Ssl))) throw new ProviderException(SR.GetString(SR.ADMembership_unable_to_set_password_port)); } else throw; } } } private bool IsUpnUnique(string username) { // // NOTE: we do not need to revert context here since this method is always // called after reverting any impersonated context // DirectoryEntry rootEntry = new DirectoryEntry("GC://" + directoryInfo.ForestName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); DirectorySearcher searcher = new DirectorySearcher(rootEntry); searcher.Filter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=" + GetEscapedFilterValue(username) + "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); bool result; try { result = (searcher.FindOne() == null); } finally { rootEntry.Dispose(); } return result; } private bool IsEmailUnique(DirectoryEntry containerEntry, string username, string email, bool existing) { bool disposeContainerEntry = false; if (containerEntry == null) { // // NOTE: we do not need to revert context here since this method is always // called after reverting any impersonated context // containerEntry = new DirectoryEntry(directoryInfo.GetADsPath(directoryInfo.ContainerDN), directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); disposeContainerEntry = true; } DirectorySearcher searcher = new DirectorySearcher(containerEntry); if (existing) searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) + ")(!(" + GetEscapedRdn("cn=" + GetEscapedFilterValue(username)) + ")))"; else searcher.Filter = "(&(objectCategory=person)(objectClass=user)(" + attributeMapUsername + "=*)(" + attributeMapEmail + "=" + GetEscapedFilterValue(email) + "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree; if (directoryInfo.ClientSearchTimeout != -1) searcher.ClientTimeout = new TimeSpan(0, directoryInfo.ClientSearchTimeout, 0); if (directoryInfo.ServerSearchTimeout != -1) searcher.ServerPageTimeLimit = new TimeSpan(0, directoryInfo.ServerSearchTimeout, 0); bool result; try { result = (searcher.FindOne() == null); } finally { if (disposeContainerEntry) { containerEntry.Dispose(); containerEntry = null; } } return result; } private string GetConnectionString(string connectionStringName, bool appLevel) { if (String.IsNullOrEmpty(connectionStringName)) return null; RuntimeConfig config = (appLevel) ? RuntimeConfig.GetAppConfig() : RuntimeConfig.GetConfig(); ConnectionStringSettings connObj = config.ConnectionStrings.ConnectionStrings[connectionStringName]; if (connObj == null) { // // No connection string by the specified name // throw new ProviderException(SR.GetString(SR.Connection_string_not_found, connectionStringName)); } return connObj.ConnectionString; } private string GetAttributeMapping(NameValueCollection config, string valueName, out int maxLength) { string sValue = config[valueName]; maxLength = -1; if (sValue == null) return null; sValue = sValue.Trim(); if (sValue.Length == 0) throw new ProviderException(SR.GetString(SR.ADMembership_Schema_mappings_must_not_be_empty, valueName)); return GetValidatedSchemaMapping(valueName, sValue, out maxLength); } private string GetValidatedSchemaMapping(string valueName, string attributeName, out int maxLength) { if (String.Compare(valueName, "attributeMapUsername", StringComparison.Ordinal) == 0) { if (directoryInfo.DirectoryType == DirectoryType.AD) { // // username can only be mapped to "sAMAccountName", "userPrincipalName" // if ((!StringUtil.EqualsIgnoreCase(attributeName, "sAMAccountName")) && (!StringUtil.EqualsIgnoreCase(attributeName, "userPrincipalName"))) throw new ProviderException(SR.GetString(SR.ADMembership_Username_mapping_invalid)); } else { // // for ADAM, username can only be mapped to "userPrincipalName" // if (!StringUtil.EqualsIgnoreCase(attributeName, "userPrincipalName")) throw new ProviderException(SR.GetString(SR.ADMembership_Username_mapping_invalid_ADAM)); } } else { // // ensure that we are not already using this attribute // if (attributesInUse.Contains(attributeName)) throw new ProviderException(SR.GetString(SR.ADMembership_mapping_not_unique, valueName, attributeName)); // // ensure that the attribute exists on the user object // if (!userObjectAttributes.Contains(attributeName)) throw new ProviderException(SR.GetString(SR.ADMembership_MappedAttribute_does_not_exist_on_user, attributeName, valueName)); } try { // // verify that this is an existing property and it's syntax is correct // DirectoryEntry propertyEntry = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/" + attributeName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); // // to get the syntax we need to invoke the "syntax" property // string syntax = (string) propertyEntry.InvokeGet("Syntax"); // // check that the syntax is as per the syntaxes table // if (!StringUtil.EqualsIgnoreCase(syntax, (string) syntaxes[valueName])) throw new ProviderException(SR.GetString(SR.ADMembership_Wrong_syntax, valueName, (string) syntaxes[valueName])); // // if the type is "DirectoryString", then set the maxLength value if any // maxLength = -1; if (StringUtil.EqualsIgnoreCase(syntax, "DirectoryString")) { try { maxLength = (int) propertyEntry.InvokeGet("MaxRange"); } catch (TargetInvocationException e) { // // if the inner exception is a comexception with error code 0x8007500d, then the max range is not set // so we ignore that exception // if (!((e.InnerException is COMException) && (((COMException)e.InnerException).ErrorCode == unchecked((int) 0x8000500d)))) throw; } } // // unless this is the username (which we already know is mapped // to a single valued attribute), the attribute should be single valued // if (String.Compare(valueName, "attributeMapUsername", StringComparison.Ordinal) != 0) { bool isMultiValued = (bool) propertyEntry.InvokeGet("MultiValued"); if (isMultiValued) throw new ProviderException(SR.GetString(SR.ADMembership_attribute_not_single_valued, valueName)); } } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_MappedAttribute_does_not_exist, attributeName, valueName), e); else throw; } // // add the attribute name (lower cased) to the in use attributes list // return attributeName; } private int GetRangeUpperForSchemaAttribute(string attributeName) { int rangeUpper = -1; DirectoryEntry propertyEntry = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/" + attributeName, directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); try { rangeUpper = (int) propertyEntry.InvokeGet("MaxRange"); } catch (TargetInvocationException e) { // // if the inner exception is a comexception with error code 0x8007500d, then the max range is not set // so we ignore that exception // if (!((e.InnerException is COMException) && (((COMException)e.InnerException).ErrorCode == unchecked((int) 0x8000500d)))) throw; } return rangeUpper; } private Hashtable GetUserObjectAttributes() { DirectoryEntry de = new DirectoryEntry(directoryInfo.GetADsPath("schema") + "/user", directoryInfo.GetUsername(), directoryInfo.GetPassword(), directoryInfo.AuthenticationTypes); object value = null; bool listEmpty = false; Hashtable attributes = new Hashtable(StringComparer.OrdinalIgnoreCase); try { value = de.InvokeGet("MandatoryProperties"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { listEmpty = true; } else throw; } if (!listEmpty) { if (value is ICollection) { foreach (string attribute in (ICollection) value) { if (!attributes.Contains(attribute)) attributes.Add(attribute, null); } } else { // single value if (!attributes.Contains(value)) attributes.Add(value, null); } } listEmpty = false; try { value = de.InvokeGet("OptionalProperties"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { listEmpty = true; } else throw; } if (!listEmpty) { if (value is ICollection) { foreach (string attribute in (ICollection) value) { if (!attributes.Contains(attribute)) attributes.Add(attribute, null); } } else { // single value if (!attributes.Contains(value)) attributes.Add(value, null); } } return attributes; } private DateTime GetDateTimeFromLargeInteger(NativeComInterfaces.IAdsLargeInteger largeIntValue) { // // Convert large integer to int64 value // Int64 int64Value = largeIntValue.HighPart * 0x100000000 + (uint) largeIntValue.LowPart; // // Return the DateTime in utc // return DateTime.FromFileTimeUtc(int64Value); } private NativeComInterfaces.IAdsLargeInteger GetLargeIntegerFromDateTime(DateTime dateTimeValue) { // // Convert DateTime value to utc file time // Int64 int64Value = dateTimeValue.ToFileTimeUtc(); // // convert to large integer // NativeComInterfaces.IAdsLargeInteger largeIntValue = (NativeComInterfaces.IAdsLargeInteger) new NativeComInterfaces.LargeInteger(); largeIntValue.HighPart = (int) (int64Value >> 32); largeIntValue.LowPart = (int) (int64Value & 0xFFFFFFFF); return largeIntValue; } private string Encrypt(string clearTextString) { // we should never be getting null input here Debug.Assert(clearTextString != null); byte[] bIn = Encoding.Unicode.GetBytes(clearTextString); byte[] bSalt = new byte[AD_SALT_SIZE_IN_BYTES]; (new RNGCryptoServiceProvider()).GetBytes(bSalt); byte[] bAll = new byte[bSalt.Length + bIn.Length]; Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length); Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length); return Convert.ToBase64String(EncryptPassword(bAll)); } private string Decrypt(string encryptedString) { // we should never be getting null input here Debug.Assert(encryptedString != null); byte[] bEncryptedData = Convert.FromBase64String(encryptedString); byte[] bAll = DecryptPassword(bEncryptedData); return Encoding.Unicode.GetString(bAll, AD_SALT_SIZE_IN_BYTES, bAll.Length - AD_SALT_SIZE_IN_BYTES); } } internal sealed class DirectoryInformation { private string serverName = null; private string containerDN = null; private string creationContainerDN = null; private string adspath = null; private int port = 389; private bool portSpecified = false; private DirectoryType directoryType = DirectoryType.Unknown; private ActiveDirectoryConnectionProtection connectionProtection = ActiveDirectoryConnectionProtection.None; private bool concurrentBindSupported = false; private int clientSearchTimeout = -1; private int serverSearchTimeout = -1; private DirectoryEntry rootdse = null; private NetworkCredential credentials = null; private AuthenticationTypes authenticationType = AuthenticationTypes.None; private AuthType ldapAuthType = AuthType.Basic; private string adamPartitionDN = null; private TimeSpan adLockoutDuration; private string forestName = null; private string domainName = null; private bool isServer = false; private const string LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID ="1.2.840.113556.1.4.1851"; private const string LDAP_CAP_ACTIVE_DIRECTORY_OID ="1.2.840.113556.1.4.800"; private const string LDAP_SERVER_FAST_BIND_OID = "1.2.840.113556.1.4.1781"; internal const int SSL_PORT = 636; private const int GC_PORT = 3268; private const int GC_SSL_PORT = 3269; private const string GUID_USERS_CONTAINER_W = "a9d1ca15768811d1aded00c04fd8d5cd"; // // authentication types for S.DS and S.DS.Protocols (rows are indexed by connection protection // columns are indexed by type of credentials (see CredentialType enum) // AuthenticationTypes[,] authTypes = new AuthenticationTypes[,] {{AuthenticationTypes.None, AuthenticationTypes.None}, {AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer , AuthenticationTypes.SecureSocketsLayer }, {AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing, AuthenticationTypes.Secure | AuthenticationTypes.Signing | AuthenticationTypes.Sealing}}; AuthType[,] ldapAuthTypes = new AuthType[,] {{AuthType.Negotiate, AuthType.Basic}, {AuthType.Negotiate, AuthType.Basic}, {AuthType.Negotiate, AuthType.Negotiate}}; internal DirectoryInformation(string adspath, NetworkCredential credentials, string connProtection, int clientSearchTimeout, int serverSearchTimeout, bool enablePasswordReset) { // // all parameters have already been validated at this point // this.adspath = adspath; this.credentials = credentials; this.clientSearchTimeout = clientSearchTimeout; this.serverSearchTimeout = serverSearchTimeout; Debug.Assert(adspath != null); Debug.Assert(adspath.Length > 0); // // Provider must be LDAP // if (!(adspath.StartsWith("LDAP", StringComparison.Ordinal))) throw new ProviderException(SR.GetString(SR.ADMembership_OnlyLdap_supported)); // // Parse out the server/domain information // NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); try { pathCracker.Set(adspath, NativeComInterfaces.ADS_SETTYPE_FULL); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_invalid_path)); else throw; } // Get the server and container names try { serverName = pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_SERVER); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x80005000)) throw new ProviderException(SR.GetString(SR.ADMembership_ServerlessADsPath_not_supported)); else throw; } Debug.Assert(serverName != null); creationContainerDN = containerDN = pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_X500_DN); // // Parse out the port number if specified // int index = serverName.IndexOf(':'); if (index != -1) { string tempStr = serverName; serverName = tempStr.Substring(0, index); Debug.Assert(tempStr.Length > index); port = Int32.Parse(tempStr.Substring(index + 1), NumberFormatInfo.InvariantInfo); portSpecified = true; } if (String.Compare(connProtection, "Secure", StringComparison.Ordinal) == 0) { // // The logic is as follows: // 1. Try Ssl first and check if concurrent binds are possible for validating users // 2. If Ssl is not supported, try signing and sealing // 3. If both the above are not supported, then we will fail // bool trySignAndSeal = false; bool trySslWithSecureAuth = false; // first try with simple bind if (!IsDefaultCredential()) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.NonWindows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.NonWindows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); // this will force a bind rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.Ssl; if (!portSpecified) { port = SSL_PORT; portSpecified = true; } } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x8007052e)) { // // this could be an ADAM target with windows user (in that case simple bind will not work) // trySslWithSecureAuth = true; } else if (ce.ErrorCode == unchecked((int) 0x8007203a)) { // server is not operational error, do nothing, we need to fall back to SignAndSeal trySignAndSeal = true; } else throw; } } else { // default credentials, so we have to do secure bind trySslWithSecureAuth = true; } if (trySslWithSecureAuth) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.Windows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.Ssl, CredentialsType.Windows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); // this will force a bind rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.Ssl; if (!portSpecified) { port = SSL_PORT; portSpecified = true; } } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x8007203a)) { // server is not operational error, do nothing, we need to fall back to SignAndSeal trySignAndSeal = true; } else throw; } } if (trySignAndSeal) { authenticationType = GetAuthenticationTypes(ActiveDirectoryConnectionProtection.SignAndSeal, CredentialsType.Windows); ldapAuthType = GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection.SignAndSeal, CredentialsType.Windows); try { rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); rootdse.RefreshCache(); this.connectionProtection = ActiveDirectoryConnectionProtection.SignAndSeal; } catch (COMException e) { throw new ProviderException(SR.GetString(SR.ADMembership_Secure_connection_not_established, e.Message), e); } } } else { // // No connection protection // // // we will do a simple bind but we must ensure that the credentials are explicitly specified // since in the case of default credentials we cannot honor it (default credentials become anonymous in the case of // simple bind) // if (IsDefaultCredential()) throw new NotSupportedException(SR.GetString(SR.ADMembership_Default_Creds_not_supported)); // simple bind authenticationType = GetAuthenticationTypes(connectionProtection, CredentialsType.NonWindows); ldapAuthType = GetLdapAuthenticationTypes(connectionProtection, CredentialsType.NonWindows); rootdse = new DirectoryEntry(GetADsPath("rootdse"), GetUsername(), GetPassword(), authenticationType); } // // Determine whether this is AD or ADAM by binding to the rootdse and // checking the supported capabilities // if (rootdse == null) rootdse = new DirectoryEntry(GetADsPath("RootDSE"), GetUsername(), GetPassword(), authenticationType); directoryType = GetDirectoryType(); // // if the directory type is ADAM and the conntectionProtection was selected // as sign and seal, then we should throw an ProviderException. This is becuase validate user will always fail for ADAM // because ADAM does not support secure authentication for ADAM users. // if ((directoryType == DirectoryType.ADAM) && (this.connectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal)) throw new ProviderException(SR.GetString(SR.ADMembership_Ssl_connection_not_established)); // // for AD, we need to block the GC ports // if ((directoryType == DirectoryType.AD) && ((port == GC_PORT) || (port == GC_SSL_PORT))) throw new ProviderException(SR.GetString(SR.ADMembership_GCPortsNotSupported)); // // if container dn is null, we need to get the default naming context // (containerDN cannot be null for ADAM) // if (String.IsNullOrEmpty(containerDN)) { if (directoryType == DirectoryType.AD) { containerDN = (string)rootdse.Properties["defaultNamingContext"].Value; if (containerDN == null) throw new ProviderException(SR.GetString(SR.ADMembership_DefContainer_not_specified)); // // we will create users in the default users container, check that it exists // string wkUsersContainerPath = GetADsPath(""); DirectoryEntry containerEntry = new DirectoryEntry(wkUsersContainerPath, GetUsername(), GetPassword(), authenticationType); try { creationContainerDN = (string) PropertyManager.GetPropertyValue(containerEntry, "distinguishedName"); } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x80072030)) throw new ProviderException(SR.GetString(SR.ADMembership_DefContainer_does_not_exist)); else throw; } } else { // container must be specified for ADAM throw new ProviderException(SR.GetString(SR.ADMembership_Container_must_be_specified)); } } else { // // Normalize the container name (incase it was specified as GUID or WKGUID) // DirectoryEntry containerEntry = new DirectoryEntry(GetADsPath(containerDN), GetUsername(), GetPassword(), authenticationType); try { creationContainerDN = containerDN = (string) PropertyManager.GetPropertyValue(containerEntry, "distinguishedName"); } catch (COMException ce) { if (ce.ErrorCode == unchecked((int) 0x80072030)) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else throw; } } // // Check if the specified path(container) exists on the specified server/domain // (NOTE: We need to do this using S.DS.Protocols rather than S.DS because we need to // bypass the referral chasing which is automatic in S.DS) // LdapConnection tempConnection = new LdapConnection(new LdapDirectoryIdentifier(serverName + ":" + port), GetCredentialsWithDomain(credentials), ldapAuthType); tempConnection.SessionOptions.ProtocolVersion = 3; try { tempConnection.SessionOptions.ReferralChasing = System.DirectoryServices.Protocols.ReferralChasingOptions.None; SetSessionOptionsForSecureConnection(tempConnection, false /*useConcurrentBind */); tempConnection.Bind(); SearchRequest request = new SearchRequest(); request.DistinguishedName = containerDN; request.Filter = "(objectClass=*)"; request.Scope = System.DirectoryServices.Protocols.SearchScope.Base; request.Attributes.Add("distinguishedName"); request.Attributes.Add("objectClass"); if (ServerSearchTimeout != -1) request.TimeLimit = new TimeSpan(0, ServerSearchTimeout, 0); SearchResponse response; try { response = (SearchResponse) tempConnection.SendRequest(request); if (response.ResultCode == ResultCode.Referral || response.ResultCode == ResultCode.NoSuchObject) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else if (response.ResultCode != ResultCode.Success) throw new ProviderException(response.ErrorMessage); } catch (DirectoryOperationException oe) { SearchResponse errorResponse = (SearchResponse) oe.Response; if (errorResponse.ResultCode == ResultCode.NoSuchObject) throw new ProviderException(SR.GetString(SR.ADMembership_Container_does_not_exist)); else throw; } // // check that the container is of an object type that can be a superior of a user object // DirectoryAttribute objectClass = response.Entries[0].Attributes["objectClass"]; if (!ContainerIsSuperiorOfUser(objectClass)) throw new ProviderException(SR.GetString(SR.ADMembership_Container_not_superior)); // // Determine whether concurrent bind is supported // if ((connectionProtection == ActiveDirectoryConnectionProtection.None) || (connectionProtection == ActiveDirectoryConnectionProtection.Ssl)) { this.concurrentBindSupported = IsConcurrentBindSupported(tempConnection); } } finally { tempConnection.Dispose(); } // // if this is ADAM, get the partition DN // if (directoryType == DirectoryType.ADAM) { adamPartitionDN = GetADAMPartitionFromContainer(); } else { if (enablePasswordReset) { // for AD, get the lockout duration for user account auto unlock DirectoryEntry de = new DirectoryEntry(GetADsPath((string) PropertyManager.GetPropertyValue(rootdse, "defaultNamingContext")), GetUsername(), GetPassword(), AuthenticationTypes); NativeComInterfaces.IAdsLargeInteger largeIntValue = (NativeComInterfaces.IAdsLargeInteger) PropertyManager.GetPropertyValue(de, "lockoutDuration"); Int64 int64Value = largeIntValue.HighPart * 0x100000000 + (uint) largeIntValue.LowPart; // int64Value is the negative of the number of 100 nanoseconds interval that makes up the lockout duration adLockoutDuration = new TimeSpan(-int64Value); } } } internal bool ConcurrentBindSupported { get { return concurrentBindSupported; } } internal string ContainerDN { get { return containerDN; } } internal string CreationContainerDN { get { return creationContainerDN; } } internal int Port { get { return port; } } internal bool PortSpecified { get { return portSpecified; } } #if UNUSED_CODE internal NetworkCredential Credential { get { return credentials; } } #endif internal DirectoryType DirectoryType { get { return directoryType; } } internal ActiveDirectoryConnectionProtection ConnectionProtection { get { return connectionProtection; } } internal AuthenticationTypes AuthenticationTypes { get { return authenticationType; } } internal int ClientSearchTimeout { get { return clientSearchTimeout; } } internal int ServerSearchTimeout { get { return serverSearchTimeout; } } internal string ADAMPartitionDN { get { return adamPartitionDN; } } internal TimeSpan ADLockoutDuration { get { return adLockoutDuration; } } internal string ForestName { get { return forestName; } } internal string DomainName { get { return domainName; } } internal void InitializeDomainAndForestName() { if (!isServer) { DirectoryContext context = new DirectoryContext(DirectoryContextType.Domain, serverName, GetUsername(), GetPassword()); try { Domain domain = Domain.GetDomain(context); domainName = GetNetbiosDomainNameIfAvailable(domain.Name); forestName = domain.Forest.Name; } catch (ActiveDirectoryObjectNotFoundException) { // the serverName may be the name of the server rather than domain isServer = true; } } if (isServer) { DirectoryContext context = new DirectoryContext(DirectoryContextType.DirectoryServer, serverName, GetUsername(), GetPassword()); try { Domain domain = Domain.GetDomain(context); domainName = GetNetbiosDomainNameIfAvailable(domain.Name); forestName = domain.Forest.Name; } catch (ActiveDirectoryObjectNotFoundException) { // we were unable to contact the domain or server throw new ProviderException(SR.GetString(SR.ADMembership_unable_to_contact_domain)); } } } internal void SelectServer() { // // if the name specified in the target is a domain name, then we should // perform all operations on the PDC. If the name is not a domain name // then it would be the name of a server. In that case we perform all // operations on that server // serverName = GetPdcIfDomain(serverName); isServer = true; } // // Creates a new ldap connection with the specified auth types // (the session options are set based on the connection protection that was // determined during the initialize method) // internal LdapConnection CreateNewLdapConnection(AuthType authType) { LdapConnection newConnection = null; newConnection = new LdapConnection(new LdapDirectoryIdentifier(serverName + ":" + port)); newConnection.AuthType = authType; newConnection.SessionOptions.ProtocolVersion = 3; SetSessionOptionsForSecureConnection(newConnection, true /* useConcurrentBind */); return newConnection; } // // this method returns the ADsPath for the given DN // internal string GetADsPath(string dn) { string path = null; // // provider and server information // Debug.Assert(serverName != null); path = "LDAP://" + serverName; // // port info if specified // if (portSpecified) path = path + ":" + port; // // DN of the object // Debug.Assert(dn != null); NativeComInterfaces.IAdsPathname pathCracker = (NativeComInterfaces.IAdsPathname) new NativeComInterfaces.Pathname(); pathCracker.Set(dn, NativeComInterfaces.ADS_SETTYPE_DN); pathCracker.EscapedMode = NativeComInterfaces.ADS_ESCAPEDMODE_ON; path = path + "/" + pathCracker.Retrieve(NativeComInterfaces.ADS_FORMAT_X500_DN); return path; } internal void SetSessionOptionsForSecureConnection(LdapConnection connection, bool useConcurrentBind) { if (connectionProtection == ActiveDirectoryConnectionProtection.Ssl) { connection.SessionOptions.SecureSocketLayer = true; } else if (connectionProtection == ActiveDirectoryConnectionProtection.SignAndSeal) { connection.SessionOptions.Signing = true; connection.SessionOptions.Sealing = true; } if (useConcurrentBind && this.concurrentBindSupported) { try { connection.SessionOptions.FastConcurrentBind(); } catch (PlatformNotSupportedException) { // // concurrent bind is not supported by the client, (continue without it and don't try to set it next time) // this.concurrentBindSupported = false; } } } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal string GetUsername() { if (credentials == null) return null; if (credentials.UserName == null) return null; if (credentials.UserName.Length == 0 && (credentials.Password == null || credentials.Password.Length == 0)) return null; return this.credentials.UserName; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal string GetPassword() { if (credentials == null) return null; if (credentials.Password == null) return null; if (credentials.Password.Length == 0 && (credentials.UserName == null || credentials.UserName.Length == 0)) return null; return this.credentials.Password; } internal AuthenticationTypes GetAuthenticationTypes(ActiveDirectoryConnectionProtection connectionProtection, CredentialsType type) { return authTypes[(int) connectionProtection, (int) type]; } internal AuthType GetLdapAuthenticationTypes(ActiveDirectoryConnectionProtection connectionProtection, CredentialsType type) { return ldapAuthTypes[(int) connectionProtection, (int) type]; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal bool IsDefaultCredential() { if ((credentials.UserName == null || credentials.UserName.Length == 0) && (credentials.Password == null || credentials.Password.Length == 0)) return true; return false; } [EnvironmentPermission(SecurityAction.Assert, Read="USERNAME")] [SecurityPermission(SecurityAction.Assert, Flags=SecurityPermissionFlag.UnmanagedCode)] internal static NetworkCredential GetCredentialsWithDomain(NetworkCredential credentials) { NetworkCredential credentialsWithDomain; if (credentials == null) credentialsWithDomain = new NetworkCredential(null, null); else { string tempUsername = credentials.UserName; string username = null; string password = null; string domainName = null; if (!String.IsNullOrEmpty(tempUsername)) { int index = tempUsername.IndexOf('\\'); if (index != -1) { domainName = tempUsername.Substring(0, index); username = tempUsername.Substring(index + 1); } else username = tempUsername; password = credentials.Password; } credentialsWithDomain = new NetworkCredential(username, password, domainName); } return credentialsWithDomain; } private bool IsConcurrentBindSupported(LdapConnection ldapConnection) { bool result = false; Debug.Assert(ldapConnection != null); // // supportedExtension is a constructed attribute so we need to search and load that attribute explicitly // SearchRequest request = new SearchRequest(); request.Scope = System.DirectoryServices.Protocols.SearchScope.Base; request.Attributes.Add("supportedExtension"); if (ServerSearchTimeout != -1) request.TimeLimit = new TimeSpan(0, ServerSearchTimeout, 0); SearchResponse response = (SearchResponse) ldapConnection.SendRequest(request); if (response.ResultCode != ResultCode.Success) throw new ProviderException(response.ErrorMessage); foreach (string supportedExtension in response.Entries[0].Attributes["supportedExtension"].GetValues(typeof(string))) { if (StringUtil.EqualsIgnoreCase(supportedExtension, LDAP_SERVER_FAST_BIND_OID)) { result = true; break; } } return result; } // // This function goes through each of the naming contexts on the server // and determines which one is the longest postfix of the container DN. // That will give the DN of partition that the container lives in. // // private string GetADAMPartitionFromContainer() { string partitionName = null; int startsAt = Int32.MaxValue; foreach(string namingContext in rootdse.Properties["namingContexts"]) { bool endsWith = containerDN.EndsWith(namingContext, StringComparison.Ordinal); int lastIndexOf = containerDN.LastIndexOf(namingContext, StringComparison.Ordinal); if (endsWith && (lastIndexOf != -1) && (lastIndexOf < startsAt)) { partitionName = namingContext; startsAt = lastIndexOf; } } if (partitionName == null) throw new ProviderException(SR.GetString(SR.ADMembership_No_ADAM_Partition)); return partitionName; } // // This function goes through each of the object class values for the container to determine // whether the object class is one of the possible superiors of the user object // private bool ContainerIsSuperiorOfUser(DirectoryAttribute objectClass) { ArrayList possibleSuperiorsList = new ArrayList(); // // first get a list of all the classes from which the user class is derived // DirectoryEntry de = new DirectoryEntry(GetADsPath("schema") + "/user", GetUsername(), GetPassword(), AuthenticationTypes); ArrayList classesList = new ArrayList(); bool derivedFromlistEmpty = false; object value = null; try { value = de.InvokeGet("DerivedFrom"); } catch (COMException e) { if (e.ErrorCode == unchecked((int) 0x8000500D)) { derivedFromlistEmpty = true; } else throw; } if (!derivedFromlistEmpty) { if (value is ICollection) { classesList.AddRange((ICollection) value); } else { // single value classesList.Add((string) value); } } // // we will use this list to create a filter of all the classSchema objects that we need to determine the recursive list // of "possibleSecuperiors". We need to add the user class also. // classesList.Add("user"); // // Now search under the schema naming context for all these classes and get the "possSuperiors" and "systemPossSuperiors" attributes // DirectoryEntry schemaNC = new DirectoryEntry(GetADsPath((string) rootdse.Properties["schemaNamingContext"].Value), GetUsername(), GetPassword(), AuthenticationTypes); DirectorySearcher searcher = new DirectorySearcher(schemaNC); searcher.Filter = "(&(objectClass=classSchema)(|"; foreach(string supClass in classesList) searcher.Filter += "(ldapDisplayName=" + supClass + ")"; searcher.Filter += "))"; searcher.SearchScope = System.DirectoryServices.SearchScope.OneLevel; searcher.PropertiesToLoad.Add("possSuperiors"); searcher.PropertiesToLoad.Add("systemPossSuperiors"); SearchResultCollection resCol = searcher.FindAll(); try { foreach (SearchResult res in resCol) { possibleSuperiorsList.AddRange(res.Properties["possSuperiors"]); possibleSuperiorsList.AddRange(res.Properties["systemPossSuperiors"]); } } finally { resCol.Dispose(); } // // Now we have the list of all the possible superiors, check if the objectClass that was specified as a parameter // to this function is one of these values, if so, return true else false // foreach (string objectClassValue in objectClass.GetValues(typeof(string))) { if (possibleSuperiorsList.Contains(objectClassValue)) return true; } return false; } // // This method determines whether the server we are talking to // is an AD domain controller or an ADAM instance // private DirectoryType GetDirectoryType() { DirectoryType directoryType = DirectoryType.Unknown; foreach (string supportedCapability in rootdse.Properties["supportedCapabilities"]) { if (StringUtil.EqualsIgnoreCase(supportedCapability, LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID)) { directoryType = DirectoryType.ADAM; break; } else if (StringUtil.EqualsIgnoreCase(supportedCapability, LDAP_CAP_ACTIVE_DIRECTORY_OID)) { directoryType = DirectoryType.AD; break; } } if (directoryType == DirectoryType.Unknown) throw new ProviderException(SR.GetString(SR.ADMembership_Valid_Targets)); return directoryType; } // // This method returns the dns name of the primary domain controller if the specified name is a domain, // else is just returns the name as is // internal string GetPdcIfDomain(string name) { IntPtr pDomainControllerInfo = IntPtr.Zero; /* DS_DIRECTORY_SERVICE_REQUIRED 0x00000010 DS_RETURN_DNS_NAME 0x40000000 DS_PDC_REQUIRED 0x00000080 */ uint flags = 0x00000010 | 0x40000000 | 0x00000080; string pdc = null; int ERROR_NO_SUCH_DOMAIN = 1355; int result = NativeMethods.DsGetDcName(null, name, IntPtr.Zero, null, flags, out pDomainControllerInfo); try { if (result == 0) { // success case DomainControllerInfo domainControllerInfo = new DomainControllerInfo(); Marshal.PtrToStructure(pDomainControllerInfo, domainControllerInfo); Debug.Assert(domainControllerInfo != null); Debug.Assert(domainControllerInfo.DomainControllerName != null); Debug.Assert(domainControllerInfo.DomainControllerName.Length > 2); // domain controller name is in the format "\\server", so we need to strip the back slashes pdc = domainControllerInfo.DomainControllerName.Substring(2); } else if (result == ERROR_NO_SUCH_DOMAIN) pdc = name; else throw new ProviderException(GetErrorMessage(result)); } finally { // free the buffer if (pDomainControllerInfo != IntPtr.Zero) { NativeMethods.NetApiBufferFree(pDomainControllerInfo); } } return pdc; } internal string GetNetbiosDomainNameIfAvailable(string dnsDomainName) { string result = null; // // Get the netbios name from the "nETBIOSName" attribute on the crossRef object for this domain // DirectoryEntry partitionsEntry = new DirectoryEntry(GetADsPath("CN=Partitions," + (string) PropertyManager.GetPropertyValue(rootdse, "configurationNamingContext")), GetUsername(), GetPassword()); DirectorySearcher searcher = new DirectorySearcher(partitionsEntry); searcher.SearchScope = System.DirectoryServices.SearchScope.OneLevel; StringBuilder str = new StringBuilder(15); str.Append("(&(objectCategory=crossRef)(dnsRoot="); str.Append(dnsDomainName); str.Append(")(systemFlags:1.2.840.113556.1.4.804:=1)(systemFlags:1.2.840.113556.1.4.804:=2))"); searcher.Filter = str.ToString(); searcher.PropertiesToLoad.Add("nETBIOSName"); SearchResult res = searcher.FindOne(); if ((res == null) || (!(res.Properties.Contains("nETBIOSName")))) // return the dns name result = dnsDomainName; else // return the netbios name result = (string) PropertyManager.GetSearchResultPropertyValue(res, "nETBIOSName"); return result; } private static string GetErrorMessage(int errorCode) { uint temp = (uint) errorCode; temp = ( (((temp) & 0x0000FFFF) | (7 << 16) | 0x80000000)); string errorMsg = String.Empty; StringBuilder sb = new StringBuilder(256); int result = NativeMethods.FormatMessageW(NativeMethods.FORMAT_MESSAGE_IGNORE_INSERTS | NativeMethods.FORMAT_MESSAGE_FROM_SYSTEM | NativeMethods.FORMAT_MESSAGE_ARGUMENT_ARRAY, 0, (int)temp, 0, sb, sb.Capacity + 1, 0); if (result != 0) { errorMsg = sb.ToString(0, result); } else { errorMsg = SR.GetString(SR.ADMembership_Unknown_Error, string.Format(CultureInfo.InvariantCulture, "{0}", errorCode)); } return errorMsg; } } internal static class PropertyManager { public static object GetPropertyValue(DirectoryEntry directoryEntry, string propertyName) { Debug.Assert(directoryEntry != null, "PropertyManager::GetPropertyValue - directoryEntry is null"); Debug.Assert(propertyName != null, "PropertyManager::GetPropertyValue - propertyName is null"); if (directoryEntry.Properties[propertyName].Count == 0) { if (directoryEntry.Properties["distinguishedName"].Count != 0) throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found_on_object, propertyName, (string) directoryEntry.Properties["distinguishedName"].Value )); else throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found, propertyName)); } return directoryEntry.Properties[propertyName].Value; } public static object GetSearchResultPropertyValue(SearchResult res, string propertyName) { Debug.Assert(res != null, "PropertyManager::GetSearchResultPropertyValue - res is null"); Debug.Assert(propertyName != null, "PropertyManager::GetSearchResultPropertyValue - propertyName is null"); ResultPropertyValueCollection propertyValues = null; propertyValues = res.Properties[propertyName]; if ((propertyValues == null) || (propertyValues.Count < 1)) throw new ProviderException(SR.GetString(SR.ADMembership_Property_not_found, propertyName)); return propertyValues[0]; } } /*typedef struct _DOMAIN_CONTROLLER_INFO { LPTSTR DomainControllerName; LPTSTR DomainControllerAddress; ULONG DomainControllerAddressType; GUID DomainGuid; LPTSTR DomainName; LPTSTR DnsForestName; ULONG Flags; LPTSTR DcSiteName; LPTSTR ClientSiteName; } DOMAIN_CONTROLLER_INFO, *PDOMAIN_CONTROLLER_INFO; */ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] internal sealed class DomainControllerInfo { #pragma warning disable 0649 public string DomainControllerName; public string DomainControllerAddress; public int DomainControllerAddressType; public Guid DomainGuid; public string DomainName; public string DnsForestName; public int Flags; public string DcSiteName; public string ClientSiteName; #pragma warning restore 0649 public DomainControllerInfo() {} } [SuppressUnmanagedCodeSecurityAttribute()] internal static class NativeMethods { internal const int ERROR_NO_SUCH_DOMAIN = 1355; internal const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; internal const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; internal const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; /*DWORD DsGetDcName( LPCTSTR ComputerName, LPCTSTR DomainName, GUID* DomainGuid, LPCTSTR SiteName, ULONG Flags, PDOMAIN_CONTROLLER_INFO* DomainControllerInfo );*/ [DllImport("Netapi32.dll", CallingConvention=CallingConvention.StdCall, EntryPoint="DsGetDcNameW", CharSet=CharSet.Unicode)] internal static extern int DsGetDcName( [In] string computerName, [In] string domainName, [In] IntPtr domainGuid, [In] string siteName, [In] uint flags, [Out] out IntPtr domainControllerInfo); /*NET_API_STATUS NetApiBufferFree( LPVOID Buffer );*/ [DllImport("Netapi32.dll")] internal static extern int NetApiBufferFree( [In] IntPtr buffer); [DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode)] public static extern int FormatMessageW( [In] int dwFlags, [In] int lpSource, [In] int dwMessageId, [In] int dwLanguageId, [Out] StringBuilder lpBuffer, [In] int nSize, [In] int arguments); } [ ComVisible(false), SuppressUnmanagedCodeSecurityAttribute() ] internal static class NativeComInterfaces { /*typedef enum { ADS_SETTYPE_FULL=1, ADS_SETTYPE_PROVIDER=2, ADS_SETTYPE_SERVER=3, ADS_SETTYPE_DN=4 } ADS_SETTYPE_ENUM; typedef enum { ADS_FORMAT_WINDOWS=1, ADS_FORMAT_WINDOWS_NO_SERVER=2, ADS_FORMAT_WINDOWS_DN=3, ADS_FORMAT_WINDOWS_PARENT=4, ADS_FORMAT_X500=5, ADS_FORMAT_X500_NO_SERVER=6, ADS_FORMAT_X500_DN=7, ADS_FORMAT_X500_PARENT=8, ADS_FORMAT_SERVER=9, ADS_FORMAT_PROVIDER=10, ADS_FORMAT_LEAF=11 } ADS_FORMAT_ENUM; typedef enum { ADS_ESCAPEDMODE_DEFAULT=1, ADS_ESCAPEDMODE_ON=2, ADS_ESCAPEDMODE_OFF=3, ADS_ESCAPEDMODE_OFF_EX=4 } ADS_ESCAPE_MODE_ENUM;*/ internal const int ADS_SETTYPE_FULL = 1; internal const int ADS_SETTYPE_DN = 4; internal const int ADS_FORMAT_PROVIDER = 10; internal const int ADS_FORMAT_SERVER = 9; internal const int ADS_FORMAT_X500_DN = 7; internal const int ADS_ESCAPEDMODE_ON = 2; internal const int ADS_ESCAPEDMODE_OFF = 3; // // Pathname as a co-class that implements the IAdsPathname interface // [ComImport, Guid("080d0d78-f421-11d0-a36e-00c04fb950dc")] internal class Pathname { } [ComImport, Guid("D592AED4-F420-11D0-A36E-00C04FB950DC"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)] internal interface IAdsPathname { // HRESULT Set([in] BSTR bstrADsPath, [in] long lnSetType); [SuppressUnmanagedCodeSecurityAttribute()] int Set([In, MarshalAs(UnmanagedType.BStr)] string bstrADsPath, [In, MarshalAs(UnmanagedType.U4)] int lnSetType); // HRESULT SetDisplayType([in] long lnDisplayType); int SetDisplayType([In, MarshalAs(UnmanagedType.U4)] int lnDisplayType); // HRESULT Retrieve([in] long lnFormatType, [out, retval] BSTR* pbstrADsPath); [return: MarshalAs(UnmanagedType.BStr)][SuppressUnmanagedCodeSecurityAttribute()] string Retrieve([In, MarshalAs(UnmanagedType.U4)] int lnFormatType); // HRESULT GetNumElements([out, retval] long* plnNumPathElements); [return: MarshalAs(UnmanagedType.U4)] int GetNumElements(); // HRESULT GetElement([in] long lnElementIndex, [out, retval] BSTR* pbstrElement); [return: MarshalAs(UnmanagedType.BStr)] string GetElement([In, MarshalAs(UnmanagedType.U4)] int lnElementIndex); // HRESULT AddLeafElement([in] BSTR bstrLeafElement); void AddLeafElement([In, MarshalAs(UnmanagedType.BStr)] string bstrLeafElement); // HRESULT RemoveLeafElement(); void RemoveLeafElement(); // HRESULT CopyPath([out, retval] IDispatch** ppAdsPath); [return: MarshalAs(UnmanagedType.Interface)] object CopyPath(); // HRESULT GetEscapedElement([in] long lnReserved, [in] BSTR bstrInStr, [out, retval] BSTR* pbstrOutStr ); [return: MarshalAs(UnmanagedType.BStr)][SuppressUnmanagedCodeSecurityAttribute()] string GetEscapedElement([In, MarshalAs(UnmanagedType.U4)] int lnReserved, [In, MarshalAs(UnmanagedType.BStr)] string bstrInStr); int EscapedMode { get; [SuppressUnmanagedCodeSecurityAttribute()] set; } } // // LargeInteger as a co-class that implements the IAdsLargeInteger interface // [ComImport, Guid("927971f5-0939-11d1-8be1-00c04fd8d503")] internal class LargeInteger { } [ComImport, Guid("9068270b-0939-11d1-8be1-00c04fd8d503"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)] internal interface IAdsLargeInteger { long HighPart { [SuppressUnmanagedCodeSecurityAttribute()] get; [SuppressUnmanagedCodeSecurityAttribute()] set; } long LowPart { [SuppressUnmanagedCodeSecurityAttribute()] get; [SuppressUnmanagedCodeSecurityAttribute()] set; } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- DictionaryKeyPropertyAttribute.cs
- CorrelationTokenInvalidatedHandler.cs
- DefaultObjectMappingItemCollection.cs
- FixedPageStructure.cs
- TemplateBaseAction.cs
- Win32Native.cs
- ObfuscateAssemblyAttribute.cs
- KeyValuePairs.cs
- SqlCommand.cs
- Compress.cs
- SerializerWriterEventHandlers.cs
- Popup.cs
- FrameworkElement.cs
- ListViewDeletedEventArgs.cs
- TraceListener.cs
- SettingsPropertyIsReadOnlyException.cs
- PrintEvent.cs
- UnwrappedTypesXmlSerializerManager.cs
- TextPenaltyModule.cs
- ManifestResourceInfo.cs
- UnmanagedMemoryStream.cs
- Pair.cs
- MetricEntry.cs
- SqlTriggerContext.cs
- SecurityManager.cs
- ParallelDesigner.cs
- XmlUTF8TextReader.cs
- ToolStripLocationCancelEventArgs.cs
- FixedStringLookup.cs
- LinkLabel.cs
- Wildcard.cs
- DocumentApplicationJournalEntry.cs
- FixUp.cs
- ModelPerspective.cs
- HostingEnvironmentSection.cs
- WebPartHelpVerb.cs
- AmbientValueAttribute.cs
- RequiredFieldValidator.cs
- MetadataArtifactLoaderCompositeFile.cs
- OpenFileDialog.cs
- SerializationEventsCache.cs
- CalendarButton.cs
- DomainConstraint.cs
- SignedPkcs7.cs
- FixedSOMLineRanges.cs
- OutputBuffer.cs
- Emitter.cs
- DnsPermission.cs
- CommandField.cs
- MetadataUtilsSmi.cs
- LayoutInformation.cs
- BaseResourcesBuildProvider.cs
- Missing.cs
- ImageCodecInfoPrivate.cs
- ResourceExpression.cs
- DiscreteKeyFrames.cs
- SmiTypedGetterSetter.cs
- QueryResponse.cs
- DataGridViewBindingCompleteEventArgs.cs
- Token.cs
- DataContractJsonSerializer.cs
- FontNamesConverter.cs
- PreservationFileWriter.cs
- LightweightEntityWrapper.cs
- ShapingWorkspace.cs
- WindowsNonControl.cs
- DataTableNewRowEvent.cs
- PropertyEmitter.cs
- AssemblyAttributesGoHere.cs
- SizeAnimationClockResource.cs
- PseudoWebRequest.cs
- Trace.cs
- PermissionSetTriple.cs
- DoubleAnimation.cs
- CustomTrackingRecord.cs
- CollectionBase.cs
- ColumnHeader.cs
- KeyInterop.cs
- BitmapEffectGroup.cs
- Pkcs9Attribute.cs
- Crc32.cs
- CoreSwitches.cs
- UInt16Storage.cs
- WMICapabilities.cs
- AssemblyAttributes.cs
- ZipIOExtraFieldPaddingElement.cs
- basevalidator.cs
- Shared.cs
- MultiView.cs
- BlockUIContainer.cs
- HttpWebResponse.cs
- AppDomainProtocolHandler.cs
- XmlSortKeyAccumulator.cs
- GrammarBuilderBase.cs
- ClientConfigurationHost.cs
- Descriptor.cs
- Timer.cs
- DataControlField.cs
- GridSplitter.cs
- LocationChangedEventArgs.cs