ASP.NET 2.0 #4: Security I: AD/AM Membership Provider

Here is my repaired version of Jit Ghosh's AD/AM Membership Provider: 

 

using System;
using System.Configuration;
using System.Web.Configuration;
using System.Configuration.Provider;
using System.Web.Security;
using System.DirectoryServices;
using System.Security.Cryptography;
using System.Text;
using System.IO;

namespace CustomProviders
{
/// <summary>
/// Implements a custom provier for the ASP.NET 2.0 Membership Service.
/// This provider uses Microsoft Active Directory Application Mode as the storage system for user information
/// </summary>
public class ADAMMembershipProvider: MembershipProvider
{

  private string _Name = null;

  private string _ADAMServerUri = "localhost";
private string _ADAMUserName = null;
private string _ADAMPassword = null;
private string _ADAMTopContainer = "CN=ASP.NET Security Provider";
private string _ADAMUserContainer = "CN=Users";
private bool _EnablePasswordRetrieval = false;
private bool _EnablePasswordReset = true;
private string _ApplicationName = null;
private bool _RequiresQuestionAndAnswer = false;
private bool _RequiresUniqueEMail = false;
private byte[] _DecryptionKey = null;
private byte[] _ValidationKey = null;

  private MembershipPasswordFormat _PasswordFormat = MembershipPasswordFormat.Hashed;

  private const int PAGEINDEX_NOPAGING = 1;
private const int PAGESIZE_NOPAGING = int.MaxValue;

  //Filter formats for various LDAP queries

  private const string _ADAMCommonName = "CN={0}";
private const string _ADAMGetAllUsersFilterAppSpecific = "(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserApplicationName={0}))";
private const string _ADAMUserSearchByUserNameFilterAppSpecific = "(&(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserApplicationName={0}))(aspnetmembershipuserUserName={1}))";
private const string _ADAMUserSearchByEmailFilterAppSpecific = "(&(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserApplicationName={0}))(aspnetmembershipuserEMail={1}))";
private const string _ADAMUserSearchByNameAndEmailFilterAppSpecific = "(&(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserApplicationName={0}))(&(aspnetmembershipuserUserName={1})(aspnetmembershipuserEMail={2})))";
private const string _ADAMCountUsersOnlineAppSpecific = "(&(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserApplicationName={0}))(aspnetmembershipuserLastActivityTimeStamp>={1:yyMMddHHmmss}Z))";
private const string _ADAMGetAllUsersFilter = "(objectClass=aspnetmembershipuser)";
private const string _ADAMUserSearchByUserNameFilter = "(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserUserName={0}))";
private const string _ADAMUserSearchByEmailFilter = "(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserEMail={0}))";
private const string _ADAMUserSearchByNameAndEmailFilter = "(&(objectClass=aspnetmembershipuser)(&(aspnetmembershipuserUserName={0})(aspnetmembershipuserEMail={1})))";
private const string _ADAMCountUsersOnline = "(&(objectClass=aspnetmembershipuser)(aspnetmembershipuserLastActivityTimeStamp>={0:yyMMddHHmmss}Z))";
private string _ADAMLDAPPathToUserContainer = null;

  //Various ADAM Schema class and attribute name literals
private const string _ADAMUserObjectClass = "aspnetmembershipuser";
private const string _ADAMPropApplicationName = "aspnetmembershipuserApplicationName";
private const string _ADAMPropUserName = "aspnetmembershipuserUserName";
private const string _ADAMPropUserId = "aspnetmembershipuserUserId";
private const string _ADAMPropEMail = "aspnetmembershipuserEmail";
private const string _ADAMPropComment = "aspnetmembershipuserComment";
private const string _ADAMPropIsAnonymous = "aspnetmembershipuserIsAnonymous";
private const string _ADAMPropIsApproved = "aspnetmembershipuserIsApproved";
private const string _ADAMPropUserCreationTimestamp = "aspnetmembershipuserUserCreationTimeStamp";
private const string _ADAMPropLastActivityTimestamp = "aspnetmembershipuserLastActivityTimeStamp";
private const string _ADAMPropLastLoginTimestamp = "aspnetmembershipuserLastLoginTimeStamp";
private const string _ADAMPropLastPasswordChangeTimestamp = "aspnetmembershipuserLastPasswordChangeTimestamp";
private const string _ADAMPropPassword = "aspnetmembershipuserPassword";
private const string _ADAMPropPasswordQuestion = "aspnetmembershipuserPasswordQuestion";
private const string _ADAMPropPasswordAnswer = "aspnetmembershipuserPasswordAnswer";
private const string _ADAMPropPasswordFormat = "aspnetmembershipuserPasswordFormat";
private const string _ADAMPropPasswordSaltOrIV = "aspnetmembershipuserPasswordSaltOrIV";

  public ADAMMembershipProvider()
{

  }

  public override string ResetPassword(string name, string answer)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   string RetVal = null;

   try
{
//Cannot reset password if not enabled via configuration settings
if (_EnablePasswordReset == false)
throw new NotSupportedException("Current configuration settings do not allow resetting passwords");
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", name));

    if (deMembershipUser != null)//user record found!!
{
deMembershipUser.RefreshCache();
//if configuration settings require a security question/answer protocol for passwords
//then the supplied answer parameter needs to match the stored password answer for the user
if (_RequiresQuestionAndAnswer && (deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value == null || (string)deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value != answer))
throw new ApplicationException("Password answer does not match");

     //generate a new random password. I have chosen to use a 6 character password.
//It is advisable to make this a configuration setting as well
RetVal = Membership.GeneratePassword(6);
//convert password for storage per password format, and set the user record with the new password
deMembershipUser.Properties[_ADAMPropPassword].Value = this.ConvertPasswordForStorage(RetVal);
//record timestamp
deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value = DateTime.UtcNow;
//save changes
deMembershipUser.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error resetting password", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
//return new password
return RetVal; ;
}

  public override bool ChangePassword(string name, string oldPwd, string newPwd)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   try
{

    //check for existence of user record
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", name));

    if (deMembershipUser != null)//user record found!!
{
deMembershipUser.RefreshCache();
//check if the old password matches
if (!ComparePassword(oldPwd, (byte[])deMembershipUser.Properties[_ADAMPropPassword].Value))
throw new ApplicationException("Existing password does not match");
////convert password for storage per password format, and set the user record with the new password
deMembershipUser.Properties[_ADAMPropPassword].Value = ConvertPasswordForStorage(newPwd);
//record timestamp
deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value = DateTime.UtcNow;
//save changes
deMembershipUser.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error changing password", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return true;
}

  public override void UpdateUser(MembershipUser user)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   try
{
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, user.UserName) : string.Format(_ADAMUserSearchByUserNameFilter, user.UserName), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", user.UserName));

    int Count = 0;

    if (deMembershipUser != null)//user record found!!
{
deMembershipUser.RefreshCache();
//if requiresUniqueEmail is true, the update cannot specify an email that violates uniqueness
//if the email in the update is different than the original email in the user record, check to see if there
//is another user in the system with the same email
if (_RequiresUniqueEMail && deMembershipUser.Properties[_ADAMPropEMail].Value != user.Email
&& ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByEmailFilterAppSpecific, _ApplicationName, user.Email) : string.Format(_ADAMUserSearchByEmailFilter, user.Email), deUserContainer, out deMembershipUser) == true)
throw new ApplicationException(string.Format("A user with email {0} already exists", user.Email));

     //set the user record with the updates
deMembershipUser.Properties[_ADAMPropUserName].Value = user.UserName;
deMembershipUser.Properties[_ADAMPropEMail].Value = user.Email;
//deMembershipUser.Properties[_ADAMPropComment].Value = user.Comment;
//deMembershipUser.Properties[_ADAMPropIsApproved].Value = user.IsApproved.ToString();
deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value = user.PasswordQuestion;

     

     //save changes
deMembershipUser.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error updating User", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}

 

  }

  public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out MembershipCreateStatus status)
{
MembershipUser NewUser = null;
DirectoryEntry deUserContainer = null;

   DirectoryEntry deMembershipUser = null;

   status = MembershipCreateStatus.UserRejected;

   try
{
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, username) : string.Format(_ADAMUserSearchByUserNameFilter, username), deUserContainer, out deMembershipUser) == true)
{
//if found, return error
status = MembershipCreateStatus.DuplicateUserName;
return null;
}
//check for duplicate email constraint
if (_RequiresUniqueEMail && ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByEmailFilterAppSpecific, _ApplicationName, email) : string.Format(_ADAMUserSearchByEmailFilter, email), deUserContainer, out deMembershipUser) == true)
{
//if violated return error
status = MembershipCreateStatus.DuplicateEmail;
return null;
}

    //create new user id
Guid UserId = Guid.NewGuid();
DateTime TimeNow = DateTime.UtcNow;
//add the object. Set CN to the user id GUID. CN needs to be unique. This avoids conflicts around having same user names
//for multiple applications. Other alternative could be to use a concat to username and appname as CN
deMembershipUser = deUserContainer.Children.Add(string.Format(_ADAMCommonName, UserId.ToString()), _ADAMUserObjectClass);
//set other attributes
deMembershipUser.Properties[_ADAMPropUserId].Value = UserId.ToByteArray();
deMembershipUser.Properties[_ADAMPropUserName].Value = username;
deMembershipUser.Properties[_ADAMPropApplicationName].Value = _ApplicationName;
deMembershipUser.Properties[_ADAMPropPassword].Value = ConvertPasswordForStorage(password);
deMembershipUser.Properties[_ADAMPropEMail].Value = email;
deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value = TimeNow;
deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value = passwordQuestion;
deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value = passwordAnswer;
deMembershipUser.Properties[_ADAMPropIsApproved].Value = true;

//save changes
deMembershipUser.CommitChanges();
//create new instance of MembershipUser and return it
NewUser = new MembershipUser(this, username, UserId, email, null, null, true, (DateTime)deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value, DateTime.Now, DateTime.Now, DateTime.Now);
status = MembershipCreateStatus.Success;

   }
catch (Exception Ex)
{
status = MembershipCreateStatus.ProviderError;
NewUser = null;
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}

   return NewUser;

  }

  public override bool EnablePasswordReset
{
get
{

    return _EnablePasswordReset;
}
}

  public override int GetNumberOfUsersOnline()
{
DirectoryEntry deUserContainer = null;

   int Count = 0;
try
{

    deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
//search for all users where LastActivityTimeStamp >= TimeNow - UserIsOnlineTimeWindow
string Filter = (_ApplicationName != null) ? string.Format(_ADAMCountUsersOnlineAppSpecific, _ApplicationName, DateTime.UtcNow.AddMinutes(Convert.ToDouble(-1 * Membership.UserIsOnlineTimeWindow))) : string.Format(_ADAMCountUsersOnline, DateTime.UtcNow.AddMinutes(Convert.ToDouble(-1 * Membership.UserIsOnlineTimeWindow)));
ADAMFindUsers(Filter, deUserContainer, PAGEINDEX_NOPAGING, PAGESIZE_NOPAGING, out Count);
}
catch (Exception Ex)
{
throw;
}
finally
{
try
{
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
//return count
return Count;
}

  public override bool ChangePasswordQuestionAndAnswer(string name, string password, string newPwdQuestion, string newPwdAnswer)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   try
{
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", name));

    if (deMembershipUser != null)//user record found !!
{
deMembershipUser.RefreshCache();
//check if the old password matches
if (!ComparePassword(password, (byte[])deMembershipUser.Properties[_ADAMPropPassword].Value))
throw new ApplicationException("Existing password does not match");
//set properties and save changes
deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value = newPwdQuestion;
deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value = newPwdAnswer;
deMembershipUser.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error changing password", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return true;
}

  public override MembershipUser GetUser(string name, bool userIsOnline)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   try
{
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", name));

    if (deMembershipUser != null)//user record found!!
{

     //refresh the AD cache to load all attributes
deMembershipUser.RefreshCache();
//if the user is indicated to be online
if (userIsOnline)
{
//update the appropriate timestamp and save changes
deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value = DateTime.UtcNow;
deMembershipUser.CommitChanges();
}
//return a new instance created from the retrieved user record
return this.ConstructMembershipUserFromDirectoryEntry(deMembershipUser);
/*
return new MembershipUser(this,
(string)deMembershipUser.Properties[_ADAMPropUserName].Value,
deMembershipUser.Properties[_ADAMPropUserId].Value == null ? string.Empty : (string)deMembershipUser.Properties[_ADAMPropUserId].Value,
deMembershipUser.Properties[_ADAMPropEMail].Value == null ? String.Empty : (string)deMembershipUser.Properties[_ADAMPropEMail].Value,
deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value == null ? String.Empty : (string)deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value,
deMembershipUser.Properties[_ADAMPropComment].Value == null ? String.Empty : (string)deMembershipUser.Properties[_ADAMPropComment].Value,
deMembershipUser.Properties[_ADAMPropIsApproved].Value == null ? false : (bool)deMembershipUser.Properties[_ADAMPropIsApproved].Value,
deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value == null ? DateTime.Now : (DateTime)deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value,
deMembershipUser.Properties[_ADAMPropLastLoginTimestamp].Value == null ? DateTime.Now : (DateTime)deMembershipUser.Properties[_ADAMPropLastLoginTimestamp].Value,
deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value == null ? DateTime.Now : (DateTime)deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value,
deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value == null ? DateTime.Now : (DateTime)deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value);
*/
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error getting user", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return null;
}

  public override bool EnablePasswordRetrieval
{
get
{
return _EnablePasswordRetrieval;
}
}

  public override string ApplicationName
{
get
{

    return _ApplicationName;
}
set
{
_ApplicationName = value;
}
}

  public override string GetUserNameByEmail(string email)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;
string RetVal = null;

   try
{
//find the user for this application with the specified email address
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByEmailFilterAppSpecific, _ApplicationName, email) : string.Format(_ADAMUserSearchByEmailFilter, email), deUserContainer, out deMembershipUser))
return null;

    if (deMembershipUser != null)//user record found!!
{
//refresh the cache with the attribute values
deMembershipUser.RefreshCache();
//return user name
RetVal = (string)deMembershipUser.Properties[_ADAMPropUserName].Value;
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error retrieving user name", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return RetVal;
}

  public override bool ValidateUser(string name, string password)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;
bool RetVal = false;

   try
{

    //check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
RetVal = false;

    if (deMembershipUser != null)//user record found!!
{
//refresh cache with attribute values
deMembershipUser.RefreshCache();
//compare supplied password with stored password(or hash of supplied password with the stored hash in case the password format is set to hashed)
RetVal = ComparePassword(password, (byte[])deMembershipUser.Properties[_ADAMPropPassword].Value);
DateTime TimeNow = DateTime.UtcNow;
//set appropriate timestamps
deMembershipUser.Properties[_ADAMPropLastLoginTimestamp].Value = TimeNow;
deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value = TimeNow;
deMembershipUser.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error validating user", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
//return comparison result
return RetVal;
}

  public override string GetPassword(string name, string answer)
{

   DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   string RetVal = null;
try
{
//if password retrieval is disabled via configuration settings or the password format is set to hashed
//we cannot retriev the password
if (!_EnablePasswordRetrieval || _PasswordFormat == MembershipPasswordFormat.Hashed)
throw new ApplicationException("Current configuration settings do not allow password retrieval");
//check if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
throw new ApplicationException(string.Format("User {0} does not exist", name));

    if (deMembershipUser != null)//user record found!!
{
//refresh cache with stored attributes
deMembershipUser.RefreshCache();
//if configuration settings demand a security question/answer protocol, the supplied password answer
//needs to match the stored password answer
if (_RequiresQuestionAndAnswer && (deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value == null || (string)deMembershipUser.Properties[_ADAMPropPasswordAnswer].Value != answer))
throw new ApplicationException("Password answer does not match");

//decode password and return it
RetVal = GetReadablePassword((byte[])deMembershipUser.Properties[_ADAMPropPassword].Value);
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error retrieving password", Ex);
}
finally
{
try
{
deMembershipUser.Close();
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return RetVal;

  }

  public override bool DeleteUser(string name, bool deleteAllRelatedData)
{
DirectoryEntry deUserContainer = null;
DirectoryEntry deMembershipUser = null;

   try
{

    //check if user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
if (ADAMFindUser((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, name) : string.Format(_ADAMUserSearchByUserNameFilter, name), deUserContainer, out deMembershipUser) == false)
return false;

    if (deMembershipUser != null)//user record found!!
{

     //remove the record
deUserContainer.Children.Remove(deMembershipUser);
//save changes
deUserContainer.CommitChanges();
}
}
catch (Exception Ex)
{
throw new ApplicationException("Error deleting user", Ex);
}
finally
{
try
{
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}
return true;
}

 

  public override bool RequiresQuestionAndAnswer
{
get
{
return _RequiresQuestionAndAnswer;
}
}

 

  public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
totalRecords = 0;

   DirectoryEntry deUserContainer = null;

   MembershipUserCollection RetVal = null;
try
{

 

    deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
//get all the users or the nth page of users, and return the collection
RetVal = ADAMFindUsers((_ApplicationName != null) ? string.Format(_ADAMUserSearchByEmailFilterAppSpecific, _ApplicationName, emailToMatch) : string.Format(_ADAMUserSearchByEmailFilter, emailToMatch), deUserContainer, pageIndex, pageSize, out totalRecords);

   }
catch (Exception Ex)
{
throw;
}
finally
{
try
{
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}

   return RetVal;
}

  public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
totalRecords = 0;

   DirectoryEntry deUserContainer = null;

   MembershipUserCollection RetVal = null;
try
{

 

    deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
//find the users matching
RetVal = ADAMFindUsers((_ApplicationName != null) ? string.Format(_ADAMUserSearchByUserNameFilterAppSpecific, _ApplicationName, usernameToMatch) : string.Format(_ADAMUserSearchByUserNameFilter, usernameToMatch), deUserContainer, pageIndex, pageSize, out totalRecords);

   }
catch (Exception Ex)
{
throw;
}
finally
{
try
{
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}

   return RetVal;
}

  public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{

   totalRecords = 0;

   DirectoryEntry deUserContainer = null;

   MembershipUserCollection RetVal = null;
try
{

    //let's first see if the user exists
deUserContainer = (_ADAMUserName != null && _ADAMPassword != null) ? new DirectoryEntry(_ADAMLDAPPathToUserContainer, _ADAMUserName, _ADAMPassword) : new DirectoryEntry(_ADAMLDAPPathToUserContainer);
RetVal = ADAMFindUsers((_ApplicationName != null) ? string.Format(_ADAMGetAllUsersFilterAppSpecific, _ApplicationName) : _ADAMGetAllUsersFilter, deUserContainer, pageIndex, pageSize, out totalRecords);

   }
catch (Exception Ex)
{
throw;
}
finally
{
try
{
deUserContainer.Close();
}
catch (Exception Ex)
{
}
}

   return RetVal;
}

 

  public override string Name
{
get
{
return _Name;
}
}

  public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
_Name = name;

   try
{
if (config["server"] != null)
_ADAMServerUri = config["server"];

    if (config["userName"] != null)
_ADAMUserName = config["userName"];

    if (config["password"] != null)
_ADAMPassword = config["password"];

    if (config["topContainer"] != null)
_ADAMTopContainer = config["topContainer"];

    if (config["userContainer"] != null)
_ADAMUserContainer = config["userContainer"];

    if (config["applicationName"] != null)
_ApplicationName = config["applicationName"];

    if (config["enablePasswordRetrieval"] != null)
_EnablePasswordRetrieval = Convert.ToBoolean(config["enablePasswordRetrieval"].ToLower());

    if (config["enablePasswordReset"] != null)
_EnablePasswordReset = Convert.ToBoolean(config["enablePasswordReset"].ToLower());

    if (config["requiresQuestionAndAnswer"] != null)
_RequiresQuestionAndAnswer = Convert.ToBoolean(config["requiresQuestionAndAnswer"].ToLower());

    if (config["requiresUniqueEMail"] != null)
_RequiresUniqueEMail = Convert.ToBoolean(config["requiresUniqueEMail"].ToLower());

    if (config["passwordFormat"] != null)
{
switch (config["passwordFormat"].ToLower())
{
case "clear":
_PasswordFormat = MembershipPasswordFormat.Clear;
break;
case "hashed":
_PasswordFormat = MembershipPasswordFormat.Hashed;
break;
case "encrypted":
_PasswordFormat = MembershipPasswordFormat.Encrypted;
break;
default:
throw new ConfigurationException(string.Format("Unknown password format {0}. Supported password formats are Clear,Hashed or Encrypted", config["passwordFormat"]));
}
}
LoadKey(config);

 

   }
catch (Exception Ex)
{
throw new System.Configuration.ConfigurationException("There was an error reading the membership configuration settings", Ex);
}

   _ADAMLDAPPathToUserContainer = string.Format("LDAP://{0}/{1},{2}", _ADAMServerUri, _ADAMUserContainer, _ADAMTopContainer);

  }

  private void LoadKey(System.Collections.Specialized.NameValueCollection config)
{
if (_PasswordFormat != MembershipPasswordFormat.Clear)
{
//object section = ConfigurationSettings.GetConfig("system.web/machineKey");
//MachineKeySection machineKeySection = (MachineKeySection)section;

//MachineKeySection machineKeySection = (MachineKeySection)ConfigurationSettings.GetConfig("system.web/machineKey");
Configuration cfg = Configuration.GetWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
MachineKeySection machineKeySection = (MachineKeySection)cfg.GetSection("system.web/machineKey");

    switch (_PasswordFormat)
{
case MembershipPasswordFormat.Encrypted: //we need a symmetric key

      if (!machineKeySection.DecryptionKey.ToLower().Contains("autogenerate"))
_DecryptionKey = HexStringToByteArray(machineKeySection.DecryptionKey);
else if (config["decryptionKey"] != null)
_DecryptionKey = HexStringToByteArray(config["decryptionKey"]);
else
throw new ConfigurationException("Symmetric key required to encrypt passwords. Are you missing a <decryptionKey> in your provider entry ?");
break;
case MembershipPasswordFormat.Hashed:
if (!machineKeySection.ValidationKey.ToLower().Contains("autogenerate"))
_ValidationKey = HexStringToByteArray(machineKeySection.ValidationKey);
else if (config["validationKey"] != null)
_ValidationKey = HexStringToByteArray(config["validationKey"]);
else
throw new ConfigurationException("Symmetric key required to hash passwords. Are you missing a <validationKey> in your provider entry ?");
break;
}
}
}

  private MembershipUser ConstructMembershipUserFromDirectoryEntry(DirectoryEntry deMembershipUser)
{
object userName;
object userId;
object eMail;
object passwordQuestion;
string comment;
object isApproved;
object creationDate;
object lastLoginDate;
object lastActivityDate;
object lastPasswordChangedDate;

   userName = deMembershipUser.Properties[_ADAMPropUserName].Value;
userId = deMembershipUser.Properties[_ADAMPropUserId].Value;
eMail = deMembershipUser.Properties[_ADAMPropEMail].Value;
passwordQuestion = deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value;
comment = string.Empty;
isApproved = deMembershipUser.Properties[_ADAMPropIsApproved].Value;
creationDate = deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value;
lastLoginDate = deMembershipUser.Properties[_ADAMPropLastLoginTimestamp].Value;
lastActivityDate = deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value;
lastPasswordChangedDate = deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value;

   userName = userName == null ? null : (string)userName;
userId = userId == null ? string.Empty : new Guid((byte[])userId).ToString(); ;
eMail = eMail == null ? null : (string)eMail;
passwordQuestion = passwordQuestion == null ? null : (string)passwordQuestion;
isApproved = isApproved == null ? false : (bool)isApproved;
creationDate = creationDate == null ? DateTime.Now : (DateTime)creationDate;
lastLoginDate = lastLoginDate == null ? DateTime.Now : (DateTime)lastLoginDate;
lastActivityDate = lastActivityDate == null ? DateTime.Now : (DateTime)lastActivityDate;
lastPasswordChangedDate = lastPasswordChangedDate == null ? DateTime.Now : (DateTime)lastPasswordChangedDate;

   MembershipUser user = new MembershipUser(this, (string)userName, (string)userId, (string)eMail, (string)passwordQuestion, comment, (bool)isApproved, (DateTime)creationDate, (DateTime)lastLoginDate, (DateTime)lastActivityDate, (DateTime)lastPasswordChangedDate);
return user;
}

  private MembershipUserCollection ADAMFindUsers(string Filter, DirectoryEntry SearchRoot, int PageIndex, int PageSize, out int Count)
{

   Count = 0;

   SearchResultCollection dsUserSearchResult = null;
MembershipUserCollection RetVal = new MembershipUserCollection();

   DirectorySearcher dsUser = new DirectorySearcher(SearchRoot, Filter);
dsUser.SearchScope = SearchScope.OneLevel;

   try
{
if ((dsUserSearchResult = dsUser.FindAll()) != null)
{
Count = dsUserSearchResult.Count;
if (PageSize == PAGESIZE_NOPAGING) PageSize = Count;
//int StartIndex = (PageIndex - 1) * PageSize;
int StartIndex = PageIndex * PageSize;
int EndIndex = ((PageIndex + 1) * PageSize) - 1;
if (EndIndex > (Count - 1))
{
EndIndex = Count - 1;
}

     MembershipUser user;
DirectoryEntry deMembershipUser;
for (int Idx = StartIndex; Idx <= EndIndex; Idx++)
{

deMembershipUser = dsUserSearchResult[Idx].GetDirectoryEntry();
deMembershipUser.RefreshCache();

      /*
userName = deMembershipUser.Properties[_ADAMPropUserName].Value;
userId = deMembershipUser.Properties[_ADAMPropUserId].Value;
eMail = deMembershipUser.Properties[_ADAMPropEMail].Value;
passwordQuestion = deMembershipUser.Properties[_ADAMPropPasswordQuestion].Value;
comment = string.Empty;
isApproved = deMembershipUser.Properties[_ADAMPropIsApproved].Value;
creationDate = deMembershipUser.Properties[_ADAMPropUserCreationTimestamp].Value;
lastLoginDate = deMembershipUser.Properties[_ADAMPropLastLoginTimestamp].Value;
lastActivityDate = deMembershipUser.Properties[_ADAMPropLastActivityTimestamp].Value;
lastPasswordChangedDate = deMembershipUser.Properties[_ADAMPropLastPasswordChangeTimestamp].Value;

      userName = userName == null?null:(string)userName;
userId = userId == null?string.Empty:new Guid((byte[])userId).ToString();;
eMail = eMail == null?null:(string)eMail;
passwordQuestion = passwordQuestion == null?null:(string)passwordQuestion;
isApproved = isApproved == null?false:(bool)isApproved;
creationDate = creationDate == null?DateTime.Now:(DateTime)creationDate;
lastLoginDate = lastLoginDate == null?DateTime.Now:(DateTime)lastLoginDate;
lastActivityDate = lastActivityDate == null?DateTime.Now:(DateTime)lastActivityDate;
lastPasswordChangedDate = lastPasswordChangedDate == null?DateTime.Now:(DateTime)lastPasswordChangedDate;

user = new MembershipUser(this,(string)userName,(string)userId,(string)eMail,(string)passwordQuestion,comment,(bool)isApproved,(DateTime)creationDate,(DateTime)lastLoginDate,(DateTime)lastActivityDate,(DateTime)lastPasswordChangedDate);
*/
user = this.ConstructMembershipUserFromDirectoryEntry(deMembershipUser);
RetVal.Add(user);

deMembershipUser.Close();
}

    }

    return RetVal;
}
catch (Exception Ex)
{
throw new ApplicationException("Error retrieving users", Ex);
}

 

  }

  private bool ADAMFindUser(string Filter, DirectoryEntry SearchRoot, out DirectoryEntry deUser)
{

   SearchResult dsUserSearchResult = null;
deUser = null;

   DirectorySearcher dsUser = new DirectorySearcher(SearchRoot, Filter);
dsUser.SearchScope = SearchScope.OneLevel;

   if ((dsUserSearchResult = dsUser.FindOne()) != null)
{
deUser = dsUserSearchResult.GetDirectoryEntry();
return true;
}
else
return false;

  }

  private string GetReadablePassword(byte[] StoredPassword)
{
System.Text.UnicodeEncoding ue = new System.Text.UnicodeEncoding();
string RetVal = null;

   switch (_PasswordFormat)
{
case MembershipPasswordFormat.Clear:
RetVal = ue.GetString(StoredPassword);
break;

    case MembershipPasswordFormat.Hashed:
throw new ApplicationException("Password cannot be recovered from a hashed format");

    case MembershipPasswordFormat.Encrypted:
TripleDESCryptoServiceProvider tripleDes = new TripleDESCryptoServiceProvider();
tripleDes.Key = _DecryptionKey;
tripleDes.IV = new byte[8];

     CryptoStream cryptoStream = new CryptoStream(new MemoryStream(StoredPassword), tripleDes.CreateDecryptor(), CryptoStreamMode.Read);
MemoryStream msPasswordDec = new MemoryStream();
int BytesRead = 0;
byte[] Buffer = new byte[32];
while ((BytesRead = cryptoStream.Read(Buffer, 0, 32)) > 0)
{
msPasswordDec.Write(Buffer, 0, BytesRead);
}
cryptoStream.Close();

     RetVal = ue.GetString(msPasswordDec.ToArray());
msPasswordDec.Close();
break;
}
return RetVal;
}
private byte[] ConvertPasswordForStorage(string Password)
{
System.Text.UnicodeEncoding ue = new System.Text.UnicodeEncoding();
byte[] uePassword = ue.GetBytes(Password);
byte[] RetVal = null;

   switch (_PasswordFormat)
{
case MembershipPasswordFormat.Clear:
RetVal = uePassword;
break;
case MembershipPasswordFormat.Hashed:

     HMACSHA1 SHA1KeyedHasher = new HMACSHA1();
SHA1KeyedHasher.Key = _ValidationKey;
RetVal = SHA1KeyedHasher.ComputeHash(uePassword);
break;
case MembershipPasswordFormat.Encrypted:
TripleDESCryptoServiceProvider tripleDes = new TripleDESCryptoServiceProvider();
tripleDes.Key = _DecryptionKey;
tripleDes.IV = new byte[8];
MemoryStream mStreamEnc = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(mStreamEnc, tripleDes.CreateEncryptor(), CryptoStreamMode.Write);

     cryptoStream.Write(uePassword, 0, uePassword.Length);
cryptoStream.FlushFinalBlock();
RetVal = mStreamEnc.ToArray();
cryptoStream.Close();
break;

   }
return RetVal;
}

  private bool ComparePassword(string Password, byte[] StoredPassword)
{
UnicodeEncoding ue = new UnicodeEncoding();
bool RetVal = false;
switch (_PasswordFormat)
{
case MembershipPasswordFormat.Clear:
RetVal = (ue.GetString(StoredPassword) == Password);
break;
case MembershipPasswordFormat.Hashed:
byte[] uePassword = ue.GetBytes(Password);
HMACSHA1 SHA1KeyedHasher = new HMACSHA1();
SHA1KeyedHasher.Key = _ValidationKey;
byte[] PasswordHash = SHA1KeyedHasher.ComputeHash(uePassword);

     if (PasswordHash.Length != StoredPassword.Length)
RetVal = false;
else
{
int idx = 0;
for (idx = 0; (idx < StoredPassword.Length) && (PasswordHash[idx] == StoredPassword[idx]); idx++) ;
RetVal = (idx == StoredPassword.Length) ? true : false;
}
break;
case MembershipPasswordFormat.Encrypted:
TripleDESCryptoServiceProvider tripleDes = new TripleDESCryptoServiceProvider();
tripleDes.Key = _DecryptionKey;
tripleDes.IV = new byte[8];

     CryptoStream cryptoStream = new CryptoStream(new MemoryStream(StoredPassword), tripleDes.CreateDecryptor(), CryptoStreamMode.Read);
MemoryStream msPasswordDec = new MemoryStream();
int BytesRead = 0;
byte[] Buffer = new byte[32];
while ((BytesRead = cryptoStream.Read(Buffer, 0, 32)) > 0)
{
msPasswordDec.Write(Buffer, 0, BytesRead);
}
cryptoStream.Close();

     RetVal = (ue.GetString(msPasswordDec.ToArray()) == Password);
msPasswordDec.Close();
break;
}

   return RetVal;
}

  private byte[] HexStringToByteArray(string HexString)
{
byte[] Result = new byte[HexString.Length / 2];
for (int idx = 0; idx < Result.Length; idx++)
Result[idx] = Convert.ToByte(HexString.Substring(idx * 2, 2), 16);
return Result;
}

 }
}