|
//------------------------------------------------------------------------------
// <copyright file="ADMembershipProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
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
}
[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;
private MembershipPasswordCompatibilityMode _LegacyPasswordCompatibilityMode = MembershipPasswordCompatibilityMode.Framework20;
private int? passwordStrengthRegexTimeout;
//
// 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)
//
[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);
TimeUnit timeoutUnit = SecUtility.GetTimeoutUnit(config, "timeoutUnit", TimeUnit.Minutes);
passwordStrengthRegexTimeout = SecUtility.GetNullableIntValue(config, "passwordStrengthRegexTimeout");
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, timeoutUnit);
//
// 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);
temp = config["passwordCompatMode"];
if (!string.IsNullOrEmpty(temp))
_LegacyPasswordCompatibilityMode = (MembershipPasswordCompatibilityMode) Enum.Parse(typeof(MembershipPasswordCompatibilityMode), temp);
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("timeoutUnit");
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");
config.Remove("passwordCompatMode");
config.Remove("passwordStrengthRegexTimeout");
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;
}
[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( !RegexUtil.IsMatch( password, PasswordStrengthRegularExpression, RegexOptions.None, passwordStrengthRegexTimeout ) )
{
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( !RegexUtil.IsMatch( newPassword, PasswordStrengthRegularExpression, RegexOptions.None, passwordStrengthRegexTimeout ) )
{
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, RequiresUniqueEmail, 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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
//
// 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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
//
// 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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
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 = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ClientSearchTimeout, directoryInfo.TimeoutUnit);
if (directoryInfo.ServerSearchTimeout != -1)
searcher.ServerPageTimeLimit = DateTimeUtil.GetTimeoutFromTimeUnit(directoryInfo.ServerSearchTimeout, directoryInfo.TimeoutUnit);
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, _LegacyPasswordCompatibilityMode));
}
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 TimeUnit timeUnit = TimeUnit.Unknown;
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,
TimeUnit timeUnit)
{
//
// all parameters have already been validated at this point
//
this.adspath = adspath;
this.credentials = credentials;
this.clientSearchTimeout = clientSearchTimeout;
this.serverSearchTimeout = serverSearchTimeout;
this.timeUnit = timeUnit;
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("<WKGUID=" + GUID_USERS_CONTAINER_W + "," + containerDN + ">");
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; }
}
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 TimeUnit TimeoutUnit
{
get { return timeUnit; }
}
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;
}
catch (DirectoryOperationException)
{
// Dev10 Bug# 623663:
// concurrent bind is not supported when a client certificate is specified, (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, "");
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;
}
}
}
}
|