using System; using System.Collections.Generic; using System.Configuration; using System.Configuration.Provider; using System.Data; using System.Data.SqlClient; using System.Web.Configuration; using System.Web.Security; using System.Xml.Linq; using Neo.Afx.ComponentModel; using Neo.Afx.Diagnostics; namespace Neo.Afx.Security { /// /// Manages storage of membership information for an RIA domain service application in a SQL Server database /// public class DomainMembershipProvider : MembershipProvider { const string EventSource = "DomainMembershipProvider"; const string ExceptionMessage = "An exception occurred. Please check the log."; #region Overridden Properties #region ApplicationName /// /// The name of the application using the custom membership provider. /// /// /// /// The name of the application using the custom membership provider. /// public override string ApplicationName { get; set; } #endregion #region EnablePasswordReset bool _enablePasswordReset; /// /// Indicates whether the membership provider is configured to allow users to reset their passwords. /// /// /// true if the membership provider supports password reset; otherwise, false. The default is true. /// public override bool EnablePasswordReset { get { return _enablePasswordReset; } } #endregion #region EnablePasswordRetrieval bool _enablePasswordRetrieval; /// /// Indicates whether the membership provider is configured to allow users to retrieve their passwords. /// /// /// true if the membership provider is configured to support password retrieval; otherwise, false. The default is true. /// public override bool EnablePasswordRetrieval { get { return _enablePasswordRetrieval; } } #endregion #region MaxInvalidPasswordAttempts int _maxInvalidPasswordAttempts; /// /// Gets the number of invalid password or password-answer attempts allowed before the membership user is locked out. /// /// /// /// The number of invalid password or password-answer attempts allowed before the membership user is locked out. /// public override int MaxInvalidPasswordAttempts { get { return _maxInvalidPasswordAttempts; } } #endregion #region MinRequiredNonAlphanumericCharacters int _minRequiredNonAlphanumericCharacters; /// /// Gets the minimum number of special characters that must be present in a valid password. /// /// /// /// The minimum number of special characters that must be present in a valid password. /// public override int MinRequiredNonAlphanumericCharacters { get { return _minRequiredNonAlphanumericCharacters; } } #endregion #region MinRequiredPasswordLength int _minRequiredPasswordLength; /// /// Gets the minimum length required for a password. /// /// /// /// The minimum length required for a password. /// public override int MinRequiredPasswordLength { get { return _minRequiredPasswordLength; } } #endregion #region PasswordAttemptWindow int _passwordAttemptWindow; /// /// Gets the number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. /// /// /// /// The number of minutes in which a maximum number of invalid password or password-answer attempts are allowed before the membership user is locked out. /// public override int PasswordAttemptWindow { get { return _passwordAttemptWindow; } } #endregion #region PasswordFormat MembershipPasswordFormat _passwordFormat; /// /// Gets a value indicating the format for storing passwords in the membership data store. /// /// /// /// One of the values indicating the format for storing passwords in the data store. /// public override MembershipPasswordFormat PasswordFormat { get { return _passwordFormat; } } #endregion #region PasswordStrengthRegularExpression string _passwordStrengthRegularExpression; /// /// Gets the regular expression used to evaluate a password. /// /// /// /// A regular expression used to evaluate a password. /// public override string PasswordStrengthRegularExpression { get { return _passwordStrengthRegularExpression; } } #endregion #region RequiresQuestionAndAnswer bool _requiresQuestionAndAnswer; /// /// Gets a value indicating whether the membership provider is configured to require the user to answer a password question for password reset and retrieval. /// /// /// true if a password answer is required for password reset and retrieval; otherwise, false. The default is true. /// public override bool RequiresQuestionAndAnswer { get { return _requiresQuestionAndAnswer; } } #endregion #region RequiresUniqueEmail bool _requiresUniqueEmail; /// /// Gets a value indicating whether the membership provider is configured to require a unique e-mail address for each user name. /// /// /// true if the membership provider requires a unique e-mail address; otherwise, false. The default is true. /// public override bool RequiresUniqueEmail { get { return _requiresUniqueEmail; } } #endregion #region WriteExceptionsToEventLog /// /// Gets or sets a value indicating whether to write exceptions to the event log. /// public bool WriteExceptionsToEventLog { get; set; } #endregion #endregion string _connectionString; string _rsaKey; #region Initialize /// /// Initializes the provider. /// /// The friendly name of the provider. /// A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider. /// /// The name of the provider is null. /// /// /// The name of the provider has a length of zero. /// /// /// An attempt is made to call on a provider after the provider has already been initialized. /// public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { #region Initialize values from web.config if(config == null) { throw new ArgumentNullException("config"); } if(String.IsNullOrEmpty(name)) { name = "DomainMembershipProvider"; } if(String.IsNullOrEmpty(config["description"])) { config.Remove("description"); config.Add("description", "Domain Membership provider"); } #endregion // Initialize the abstract base class. base.Initialize(name, config); ApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath); _maxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5")); _passwordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10")); _minRequiredNonAlphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonAlphanumericCharacters"], "1")); _minRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7")); _passwordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], "")); _enablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true")); _enablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true")); _requiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "true")); _requiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true")); WriteExceptionsToEventLog = Convert.ToBoolean(GetConfigValue(config["writeExceptionsToEventLog"], "true")); #region Get password format string tempFormat = config["passwordFormat"] ?? "Hashed"; switch(tempFormat) { case "Hashed": _passwordFormat = MembershipPasswordFormat.Hashed; break; case "Encrypted": _passwordFormat = MembershipPasswordFormat.Encrypted; break; case "Clear": _passwordFormat = MembershipPasswordFormat.Clear; break; default: throw new ProviderException("Password format not supported."); } #endregion #region Initialize connection string ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]]; if(connectionStringSettings == null || connectionStringSettings.ConnectionString.Trim() == "") { throw new ProviderException("Connection string cannot be blank."); } _connectionString = connectionStringSettings.ConnectionString; #endregion var rsaSettings = RsaSettings.GetSection(); if(rsaSettings != null) { _rsaKey = rsaSettings.Key; } #region Get encryption and decryption key information from the configuration var cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath); var machineKey = (MachineKeySection)cfg.GetSection("system.web/machineKey"); if(machineKey.ValidationKey.Contains("AutoGenerate")) { if(PasswordFormat != MembershipPasswordFormat.Clear) { throw new ProviderException("Hashed or Encrypted passwords are not supported with auto-generated keys."); } } #endregion } #endregion #region GetConfigValue static string GetConfigValue(string configValue, string defaultValue) { return String.IsNullOrEmpty(configValue) ? defaultValue : configValue; } #endregion ///////////////////////////////////////////////////////////////////// // Overridden methods ///////////////////////////////////////////////////////////////////// #region ChangePassword /// /// Changes the password. /// /// Name of the user. /// The old password - obfuscated. /// The new password - encrypted. /// true if successful; false otherwise. public override bool ChangePassword(string userName, string oldPassword, string newPassword) { if(!ValidateUser(userName, oldPassword)) { return false; } var thePassword = (_rsaKey != null) ? RsaProvider.Decrypt(_rsaKey, newPassword) : newPassword; var args = new ValidatePasswordEventArgs(userName, thePassword, false); OnValidatingPassword(args); if(args.Cancel) { if(args.FailureInformation != null) { throw args.FailureInformation; } throw new MembershipPasswordException("Change password canceled due to new password validation failure."); } bool success; try { DbSavePassword(userName, thePassword, false); success = true; } catch(Exception e) { success = false; if(WriteExceptionsToEventLog) { LogUtility.WriteApplicationEventLog(e); throw new ProviderException(ExceptionMessage); } } return success; } #endregion #region ChangePasswordQuestionAndAnswer /// /// Processes a request to update the password question and answer for a membership user. /// /// The user to change the password question and answer for. /// The password for the specified user. /// The new password question for the specified user. /// The new password answer for the specified user. /// /// true if the password question and answer are updated successfully; otherwise, false. /// public override bool ChangePasswordQuestionAndAnswer(string userName, string password, string newPasswordQuestion, string newPasswordAnswer) { if(!ValidateUser(userName, password)) { return false; } var theAnswer = (_rsaKey != null) ? RsaProvider.Decrypt(_rsaKey, newPasswordAnswer) : newPasswordAnswer; bool success; try { DbSavePasswordQuestionAndAnswer(userName, newPasswordQuestion, theAnswer); success = true; } catch(Exception e) { success = false; if(WriteExceptionsToEventLog) { LogUtility.WriteApplicationEventLog(e); throw new ProviderException(ExceptionMessage); } } return success; } #endregion #region ResetPassword /// /// Resets a user's password to a new, automatically generated password. /// /// The user to reset the password for. /// The password answer for the specified user. /// The new password for the specified user. public override string ResetPassword(string userName, string answer) { bool isValid; try { isValid = ValidateAnswer(userName, answer); } catch(Exception e) { if(WriteExceptionsToEventLog) { LogUtility.WriteApplicationEventLog(e); throw new ProviderException(ExceptionMessage); } throw; } if(!isValid) { throw new ProviderException("Invalid user and/or answer."); } return ResetPassword(userName); } /// /// Resets a user's password to a new, automatically generated password. /// /// The user to reset the password for. /// The new password for the specified user. public string ResetPassword(string userName) { var newPassword = Membership.GeneratePassword(11, 0); DbSavePassword(userName, newPassword, true); return newPassword; } #endregion #region ValidateUser /// /// Validates the user. /// /// The name of the user to be validated. /// The password to be validated. /// true if authentication succeeded; false otherwise. public override bool ValidateUser(string userName, string password) { bool isValid; try { isValid = ValidateCredentials(userName, password); } catch(Exception e) { if(WriteExceptionsToEventLog) { LogUtility.WriteApplicationEventLog(e); throw new ProviderException(ExceptionMessage); } throw; } return isValid; } #endregion #region Not Implemented /// /// Adds a new membership user to the data source. /// /// The user name for the new user. /// The password for the new user. /// The e-mail address for the new user. /// The password question for the new user. /// The password answer for the new user /// Whether or not the new user is approved to be validated. /// The unique identifier from the membership data source for the user. /// A enumeration value indicating whether the user was created successfully. /// This method is not implemented. /// /// A object populated with the information for the newly created user. /// public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { throw new NotImplementedException(); } /// /// Removes a user from the membership data source. /// /// The name of the user to delete. /// true to delete data related to the user from the database; false to leave data related to the user in the database. /// This method is not implemented. /// /// true if the user was successfully deleted; otherwise, false. /// public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); } /// /// Gets a collection of membership users where the e-mail address contains the specified e-mail address to match. /// /// The e-mail address to search for. /// The index of the page of results to return. is zero-based. /// The size of the page of results to return. /// The total number of matched users. /// This method is not implemented. /// /// A collection that contains a page of objects beginning at the page specified by . /// public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } /// /// Gets a collection of membership users where the user name contains the specified user name to match. /// /// The user name to search for. /// The index of the page of results to return. is zero-based. /// The size of the page of results to return. /// The total number of matched users. /// This method is not implemented. /// /// A collection that contains a page of objects beginning at the page specified by . /// public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } /// /// Gets a collection of all the users in the data source in pages of data. /// /// The index of the page of results to return. is zero-based. /// The size of the page of results to return. /// The total number of matched users. /// This method is not implemented. /// /// A collection that contains a page of objects beginning at the page specified by . /// public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } /// /// Gets the number of users currently accessing the application. /// /// This method is not implemented. /// /// The number of users currently accessing the application. /// public override int GetNumberOfUsersOnline() { throw new NotImplementedException(); } /// /// Gets the password for the specified user name from the data source. /// /// The user to retrieve the password for. /// The password answer for the user. /// This method is not implemented. /// /// The password for the specified user name. /// public override string GetPassword(string username, string answer) { throw new NotImplementedException(); } /// /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. /// /// The name of the user to get information for. /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. /// This method is not implemented. /// /// A object populated with the specified user's information from the data source. /// public override MembershipUser GetUser(string username, bool userIsOnline) { throw new NotImplementedException(); } /// /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user. /// /// The unique identifier for the membership user to get information for. /// true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user. /// This method is not implemented. /// /// A object populated with the specified user's information from the data source. /// public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { throw new NotImplementedException(); } /// /// Gets the user name associated with the specified e-mail address. /// /// The e-mail address to search for. /// This method is not implemented. /// /// The user name associated with the specified e-mail address. If no match is found, return null. /// public override string GetUserNameByEmail(string email) { throw new NotImplementedException(); } /// /// Clears a lock so that the membership user can be validated. /// /// The membership user whose lock status you want to clear. /// This method is not implemented. /// /// true if the membership user was successfully unlocked; otherwise, false. /// public override bool UnlockUser(string userName) { throw new NotImplementedException(); } /// /// Updates information about a user in the data source. /// /// A object that represents the user to update and the updated information for the user. /// This method is not implemented. public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } #endregion ///////////////////////////////////////////////////////////////////// // Custom methods ///////////////////////////////////////////////////////////////////// #region GetToken /// /// Initializes the login/password change process by retrieving the user's password salt and a random challenge salt. /// /// The user name to be queried. /// The user's authentication token if the user was found; null otherwise. public string GetToken(string userName) { return GetChallengeXmlToken("GetToken", userName, DbGetPasswordSalt, DbSavePasswordChallenge); } #endregion #region GetPasswordQuestion /// /// Initializes the password reset process by retrieving the user's password answer salt and a random challenge salt. /// /// The user name to be queried. /// The user's password answer token if the user was found; null otherwise. public string GetPasswordQuestion(string userName) { return DbGetPasswordQuestion(userName); } #endregion #region GetResetToken /// /// Initializes the password reset process by retrieving the user's password answer salt and a random challenge salt. /// /// The user name to be queried. /// The user's authentication token if the user was found; null otherwise. public string GetResetToken(string userName) { return GetChallengeXmlToken("GetPasswordQuestion", userName, DbGetPasswordAnswerSalt, DbSavePasswordAnswerChallenge, DbGetPasswordQuestion); } #endregion #region GetPasswordQuestions /// /// Gets a list of security questions. /// /// A list of security questions. public IEnumerable GetPasswordQuestions() { return DbGetPasswordQuestions(); } #endregion #region GetUserFingerprintTemplates /// /// Gets a list of security questions. /// /// A datatable containing userid, name and fingerprinttemplate. public DataTable GetUserFingerprintTemplates() { return DbGetUserFingerprintTemplates(); } #endregion ///////////////////////////////////////////////////////////////////// // Database access methods ///////////////////////////////////////////////////////////////////// #region GetUserID /// /// Gets the user ID. /// /// The name of the user to retrieved. /// The authentication mode. /// The ID of the user; -1 if not found. public long GetUserID(string userName, AuthenticationMode authenticationMode) { long? result; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserID"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", authenticationMode.ToString()); cmd.Connection.Open(); result = cmd.ExecuteScalar().ToInt64(); } } return result.HasValue ? result.Value : -1; } #endregion #region GetUserName /// /// Gets the user name. /// /// The NT account of the user to retrieved. /// The name of the user; null if not found. public string GetUserName(string ntAccount) { string result; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserName"; cmd.Parameters.AddWithValue("@NTAccount", ntAccount); cmd.Connection.Open(); result = cmd.ExecuteScalar().ToStringSafe(); } } return string.IsNullOrEmpty(result) ? null : result; } #endregion #region DbGetPasswordSalt string DbGetPasswordSalt(string userName) { string result; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserPasswordSalt"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Connection.Open(); result = cmd.ExecuteScalar().ToStringSafe(); } } return result; } #endregion #region DbGetPasswordHash void DbGetPasswordHash(string userName, out string passwordHash, out string passwordSalt, out string challenge) { passwordHash = string.Empty; passwordSalt = string.Empty; challenge = string.Empty; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserPasswordHash"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Connection.Open(); using(var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { if(reader.HasRows && reader.Read()) { passwordHash = reader["PasswordHash"].ToStringSafe(); passwordSalt = reader["PasswordSalt"].ToStringSafe(); challenge = reader["PasswordChallenge"].ToStringSafe(); } } } } } #endregion #region DbSavePasswordChallenge void DbSavePasswordChallenge(string userName, string challenge) { using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_SaveUserPasswordChallenge"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Parameters.AddWithValue("@PasswordChallenge", challenge); cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } } #endregion #region DbGetPasswordQuestion string DbGetPasswordQuestion(string userName) { string result; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserPasswordQuestion"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Connection.Open(); result = cmd.ExecuteScalar().ToStringSafe(); } } return result; } #endregion #region DbGetPasswordQuestions IEnumerable DbGetPasswordQuestions() { var questions = new List(); using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetPasswordQuestions"; cmd.Connection.Open(); using(var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while(reader.Read()) { questions.Add(reader[0].ToStringSafe()); } } } } return questions; } #endregion #region DbGetPasswordAnswerSalt string DbGetPasswordAnswerSalt(string userName) { string result; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserPasswordAnswerSalt"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Connection.Open(); result = cmd.ExecuteScalar().ToStringSafe(); } } return result; } #endregion #region DbGetPasswordAnswerHash void DbGetPasswordAnswerHash(string userName, out string passwordAnswerHash, out string passwordAnswerSalt, out string challenge) { passwordAnswerHash = string.Empty; passwordAnswerSalt = string.Empty; challenge = string.Empty; using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserPasswordAnswerHash"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Connection.Open(); using(var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { if(reader.HasRows && reader.Read()) { passwordAnswerHash = reader["PasswordAnswerHash"].ToStringSafe(); passwordAnswerSalt = reader["PasswordAnswerSalt"].ToStringSafe(); challenge = reader["PasswordAnswerChallenge"].ToStringSafe(); } } } } } #endregion #region DbSavePasswordAnswerChallenge void DbSavePasswordAnswerChallenge(string userName, string challenge) { using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_SaveUserPasswordAnswerChallenge"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Parameters.AddWithValue("@PasswordAnswerChallenge", challenge); cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } } #endregion #region DbSavePassword void DbSavePassword(string userName, string newPassword, bool isPasswordReset) { using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_SaveUserPassword"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Parameters.AddWithValue("@NewPassword", newPassword); cmd.Parameters.AddWithValue("@IsPasswordReset", isPasswordReset); cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } } #endregion #region DbSavePasswordQuestionAndAnswer void DbSavePasswordQuestionAndAnswer(string userName, string question, string answer) { using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_SaveUserPasswordQuestionAnswer"; cmd.Parameters.AddWithValue("@UserName", userName); cmd.Parameters.AddWithValue("@AuthMode", DomainAuthenticationProvider.AuthenticationMode.ToString()); cmd.Parameters.AddWithValue("@PasswordQuestion", question); cmd.Parameters.AddWithValue("@PasswordAnswer", answer); cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } } #endregion #region DbGetUserFingerprintTemplates DataTable DbGetUserFingerprintTemplates() { var fingerprints = new DataTable(); using(var conn = new SqlConnection(_connectionString)) { using(var cmd = conn.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "Security.Pr_GetUserFingerprintTemplates"; cmd.Connection.Open(); using(var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { fingerprints.Load(reader); } } } return fingerprints; } #endregion /////////////////////////////////////////////////////////////////// // Helper methods /////////////////////////////////////////////////////////////////// #region ValidateCredentials /// /// Validates the user's credentials against the database. /// /// The user name to be validated. /// The credential to be validated. /// true if the credentials are valid; false otherwise. bool ValidateCredentials(string userName, string credential) { string challenge; string password; string salt; DbGetPasswordHash(userName, out password, out salt, out challenge); var dbCredential = SecurePassword.Obfuscate(password, salt, challenge); return credential == dbCredential; } #endregion #region ValidateAnswer /// /// Validates the user's credentials against the database. /// /// The user name to be validated. /// The answer to be validated. /// true if the answer is valid; false otherwise. bool ValidateAnswer(string userName, string answer) { string challenge; string answerHash; string salt; DbGetPasswordAnswerHash(userName, out answerHash, out salt, out challenge); var dbAnswer = SecurePassword.Obfuscate(answerHash, salt, challenge); return answer == dbAnswer; } #endregion #region GetChallengeXmlToken string GetChallengeXmlToken(string tokenType, string userName, Func getSalt, Action saveChallenge, Func getContent = null) { if(!String.IsNullOrEmpty(userName)) { try { // returns null if userName does not exist string content = null; if(getContent != null) { content = getContent(userName); } var salt = getSalt(userName); if(!String.IsNullOrEmpty(salt)) { var challenge = SecurePassword.CreateSalt(); saveChallenge(userName, challenge); var token = new XElement("Token", new XAttribute("Salt", salt), new XAttribute("Challenge", challenge) ); if(content != null) { token.Value = content; } return token.ToString(); } } catch(Exception e) { if(WriteExceptionsToEventLog) { LogUtility.WriteApplicationEventLog(e); throw new ProviderException(ExceptionMessage); } throw; } } return null; } #endregion } }