// // System.Web.Security.SqlMembershipProvider // // Authors: // Ben Maurer (bmaurer@users.sourceforge.net) // Lluis Sanchez Gual (lluis@novell.com) // Chris Toshok (toshok@ximian.com) // // (C) 2003 Ben Maurer // Copyright (c) 2005,2006 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Configuration.Provider; using System.Data; using System.Data.Common; using System.Text; using System.Web.Configuration; using System.Security.Cryptography; namespace System.Web.Security { public class SqlMembershipProvider : MembershipProvider { bool enablePasswordReset; bool enablePasswordRetrieval; int maxInvalidPasswordAttempts; MembershipPasswordFormat passwordFormat; bool requiresQuestionAndAnswer; bool requiresUniqueEmail; int minRequiredNonAlphanumericCharacters; int minRequiredPasswordLength; int passwordAttemptWindow; string passwordStrengthRegularExpression; TimeSpan userIsOnlineTimeWindow; ConnectionStringSettings connectionString; DbProviderFactory factory; string applicationName; bool schemaIsOk = false; DbConnection CreateConnection () { if (!schemaIsOk && !(schemaIsOk = AspNetDBSchemaChecker.CheckMembershipSchemaVersion (factory, connectionString.ConnectionString, "membership", "1"))) throw new ProviderException ("Incorrect ASP.NET DB Schema Version."); DbConnection connection; if (connectionString == null) throw new ProviderException ("Connection string for the SQL Membership Provider has not been provided."); try { connection = factory.CreateConnection (); connection.ConnectionString = connectionString.ConnectionString; connection.Open (); } catch (Exception ex) { throw new ProviderException ("Unable to open SQL connection for the SQL Membership Provider.", ex); } return connection; } DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue) { return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue); } DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue) { DbParameter dbp = command.CreateParameter (); dbp.ParameterName = parameterName; dbp.Value = parameterValue; dbp.Direction = direction; command.Parameters.Add (dbp); return dbp; } DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, DbType type, object parameterValue) { DbParameter dbp = command.CreateParameter (); dbp.ParameterName = parameterName; dbp.Value = parameterValue; dbp.Direction = direction; dbp.DbType = type; command.Parameters.Add (dbp); return dbp; } static int GetReturnValue (DbParameter returnValue) { object value = returnValue.Value; return value is int ? (int) value : -1; } void CheckParam (string pName, string p, int length) { if (p == null) throw new ArgumentNullException (pName); if (p.Length == 0 || p.Length > length || p.IndexOf (',') != -1) throw new ArgumentException (String.Format ("invalid format for {0}", pName)); } public override bool ChangePassword (string username, string oldPwd, string newPwd) { if (username != null) username = username.Trim (); if (oldPwd != null) oldPwd = oldPwd.Trim (); if (newPwd != null) newPwd = newPwd.Trim (); CheckParam ("username", username, 256); CheckParam ("oldPwd", oldPwd, 128); CheckParam ("newPwd", newPwd, 128); if (!CheckPassword (newPwd)) throw new ArgumentException (string.Format ( "New Password invalid. New Password length minimum: {0}. Non-alphanumeric characters required: {1}.", MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters)); using (DbConnection connection = CreateConnection ()) { PasswordInfo pi = ValidateUsingPassword (username, oldPwd); if (pi != null) { EmitValidatingPassword (username, newPwd, false); string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt); DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_SetPassword"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@NewPassword", db_password); AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat); AddParameter (command, "@PasswordSalt", pi.PasswordSalt); AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); if (GetReturnValue (returnValue) != 0) return false; return true; } return false; } } public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer) { if (username != null) username = username.Trim (); if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim (); if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim (); CheckParam ("username", username, 256); if (RequiresQuestionAndAnswer) CheckParam ("newPwdQuestion", newPwdQuestion, 128); if (RequiresQuestionAndAnswer) CheckParam ("newPwdAnswer", newPwdAnswer, 128); using (DbConnection connection = CreateConnection ()) { PasswordInfo pi = ValidateUsingPassword (username, password); if (pi != null) { string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt); DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandType = CommandType.StoredProcedure; command.CommandText = @"aspnet_Membership_ChangePasswordQuestionAndAnswer"; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@NewPasswordQuestion", newPwdQuestion); AddParameter (command, "@NewPasswordAnswer", db_passwordAnswer); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); if (GetReturnValue (returnValue) != 0) return false; return true; } return false; } } public override MembershipUser CreateUser (string username, string password, string email, string pwdQuestion, string pwdAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { if (username != null) username = username.Trim (); if (password != null) password = password.Trim (); if (email != null) email = email.Trim (); if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim (); if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim (); /* some initial validation */ if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (',') != -1) { status = MembershipCreateStatus.InvalidUserName; return null; } if (password == null || password.Length == 0 || password.Length > 128) { status = MembershipCreateStatus.InvalidPassword; return null; } if (!CheckPassword (password)) { status = MembershipCreateStatus.InvalidPassword; return null; } EmitValidatingPassword (username, password, true); if (RequiresUniqueEmail && (email == null || email.Length == 0)) { status = MembershipCreateStatus.InvalidEmail; return null; } if (RequiresQuestionAndAnswer && (pwdQuestion == null || pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) { status = MembershipCreateStatus.InvalidQuestion; return null; } if (RequiresQuestionAndAnswer && (pwdAnswer == null || pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) { status = MembershipCreateStatus.InvalidAnswer; return null; } if (providerUserKey != null && !(providerUserKey is Guid)) { status = MembershipCreateStatus.InvalidProviderUserKey; return null; } if (providerUserKey == null) providerUserKey = Guid.NewGuid(); /* encode our password/answer using the * "passwordFormat" configuration option */ string passwordSalt = ""; RandomNumberGenerator rng = RandomNumberGenerator.Create (); byte [] salt = new byte [MembershipHelper.SALT_BYTES]; rng.GetBytes (salt); passwordSalt = Convert.ToBase64String (salt); password = EncodePassword (password, PasswordFormat, passwordSalt); if (RequiresQuestionAndAnswer) pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt); /* make sure the hashed/encrypted password and * answer are still under 128 characters. */ if (password.Length > 128) { status = MembershipCreateStatus.InvalidPassword; return null; } if (RequiresQuestionAndAnswer) { if (pwdAnswer.Length > 128) { status = MembershipCreateStatus.InvalidAnswer; return null; } } status = MembershipCreateStatus.Success; using (DbConnection connection = CreateConnection ()) { try { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_CreateUser"; command.CommandType = CommandType.StoredProcedure; DateTime Now = DateTime.UtcNow; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@Password", password); AddParameter (command, "@PasswordSalt", passwordSalt); AddParameter (command, "@Email", email); AddParameter (command, "@PasswordQuestion", pwdQuestion); AddParameter (command, "@PasswordAnswer", pwdAnswer); AddParameter (command, "@IsApproved", isApproved); AddParameter (command, "@CurrentTimeUtc", Now); AddParameter (command, "@CreateDate", Now); AddParameter (command, "@UniqueEmail", RequiresUniqueEmail); AddParameter (command, "@PasswordFormat", (int) PasswordFormat); AddParameter (command, "@UserId", ParameterDirection.InputOutput, providerUserKey); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); int st = GetReturnValue (returnValue); if (st == 0) return GetUser (username, false); else if (st == 6) status = MembershipCreateStatus.DuplicateUserName; else if (st == 7) status = MembershipCreateStatus.DuplicateEmail; else if (st == 10) status = MembershipCreateStatus.DuplicateProviderUserKey; else status = MembershipCreateStatus.ProviderError; return null; } catch (Exception) { status = MembershipCreateStatus.ProviderError; return null; } } } bool CheckPassword (string password) { if (password.Length < MinRequiredPasswordLength) return false; if (MinRequiredNonAlphanumericCharacters > 0) { int nonAlphanumeric = 0; for (int i = 0; i < password.Length; i++) { if (!Char.IsLetterOrDigit (password [i])) nonAlphanumeric++; } return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters; } return true; } public override bool DeleteUser (string username, bool deleteAllRelatedData) { CheckParam ("username", username, 256); DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers; if (deleteAllRelatedData) deleteBitmask |= DeleteUserTableMask.Profiles | DeleteUserTableMask.UsersInRoles | DeleteUserTableMask.WebPartStateUser; using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Users_DeleteUser"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@TablesToDeleteFrom", (int) deleteBitmask); AddParameter (command, "@NumTablesDeletedFrom", ParameterDirection.Output, 0); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); if (((int) command.Parameters ["@NumTablesDeletedFrom"].Value) == 0) return false; if (GetReturnValue (returnValue) == 0) return true; return false; } } public virtual string GeneratePassword () { return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters); } public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { CheckParam ("emailToMatch", emailToMatch, 256); if (pageIndex < 0) throw new ArgumentException ("pageIndex must be >= 0"); if (pageSize < 0) throw new ArgumentException ("pageSize must be >= 0"); if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue) throw new ArgumentException ("pageIndex and pageSize are too large"); using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_FindUsersByEmail"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@PageIndex", pageIndex); AddParameter (command, "@PageSize", pageSize); AddParameter (command, "@EmailToMatch", emailToMatch); AddParameter (command, "@ApplicationName", ApplicationName); // return value AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null); MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords); return c; } } public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords) { CheckParam ("nameToMatch", nameToMatch, 256); if (pageIndex < 0) throw new ArgumentException ("pageIndex must be >= 0"); if (pageSize < 0) throw new ArgumentException ("pageSize must be >= 0"); if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue) throw new ArgumentException ("pageIndex and pageSize are too large"); using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_FindUsersByName"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@PageIndex", pageIndex); AddParameter (command, "@PageSize", pageSize); AddParameter (command, "@UserNameToMatch", nameToMatch); AddParameter (command, "@ApplicationName", ApplicationName); // return value AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null); MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords); return c; } } public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords) { if (pageIndex < 0) throw new ArgumentException ("pageIndex must be >= 0"); if (pageSize < 0) throw new ArgumentException ("pageSize must be >= 0"); if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue) throw new ArgumentException ("pageIndex and pageSize are too large"); using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_GetAllUsers"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@PageIndex", pageIndex); AddParameter (command, "@PageSize", pageSize); // return value AddParameter (command, "@ReturnValue", ParameterDirection.ReturnValue, null); MembershipUserCollection c = BuildMembershipUserCollection (command, pageIndex, pageSize, out totalRecords); return c; } } MembershipUserCollection BuildMembershipUserCollection (DbCommand command, int pageIndex, int pageSize, out int totalRecords) { DbDataReader reader = null; try { MembershipUserCollection users = new MembershipUserCollection (); reader = command.ExecuteReader (); while (reader.Read ()) users.Add (GetUserFromReader (reader, null, null)); totalRecords = Convert.ToInt32 (command.Parameters ["@ReturnValue"].Value); return users; } catch (Exception) { totalRecords = 0; return null; /* should we let the exception through? */ } finally { if (reader != null) reader.Close (); } } public override int GetNumberOfUsersOnline () { using (DbConnection connection = CreateConnection ()) { DateTime now = DateTime.UtcNow; DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_GetNumberOfUsersOnline"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@CurrentTimeUtc", now.ToString ()); AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@MinutesSinceLastInActive", userIsOnlineTimeWindow.Minutes); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteScalar (); return GetReturnValue (returnValue); } } public override string GetPassword (string username, string answer) { if (!EnablePasswordRetrieval) throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords"); CheckParam ("username", username, 256); if (RequiresQuestionAndAnswer) CheckParam ("answer", answer, 128); PasswordInfo pi = GetPasswordInfo (username); if (pi == null) throw new ProviderException ("An error occurred while retrieving the password from the database"); string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt); string password = null; using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_GetPassword"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts); AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow); AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow); AddParameter (command, "@PasswordAnswer", user_answer); DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); DbDataReader reader = command.ExecuteReader (); int returnValue = GetReturnValue (retValue); if (returnValue == 3) throw new MembershipPasswordException ("Password Answer is invalid"); if (returnValue == 99) throw new MembershipPasswordException ("The user account is currently locked out"); if (reader.Read ()) { password = reader.GetString (0); reader.Close (); } if (pi.PasswordFormat == MembershipPasswordFormat.Clear) return password; else if (pi.PasswordFormat == MembershipPasswordFormat.Encrypted) return DecodePassword (password, pi.PasswordFormat); return password; } } MembershipUser GetUserFromReader (DbDataReader reader, string username, object userId) { int i = 0; if (username == null) i = 1; if (userId != null) username = reader.GetString (8); return new MembershipUser (this.Name, /* XXX is this right? */ (username == null ? reader.GetString (0) : username), /* name */ (userId == null ? reader.GetGuid (8 + i) : userId), /* providerUserKey */ reader.IsDBNull (0 + i) ? null : reader.GetString (0 + i), /* email */ reader.IsDBNull (1 + i) ? null : reader.GetString (1 + i), /* passwordQuestion */ reader.IsDBNull (2 + i) ? null : reader.GetString (2 + i), /* comment */ reader.GetBoolean (3 + i), /* isApproved */ reader.GetBoolean (9 + i), /* isLockedOut */ reader.GetDateTime (4 + i).ToLocalTime (), /* creationDate */ reader.GetDateTime (5 + i).ToLocalTime (), /* lastLoginDate */ reader.GetDateTime (6 + i).ToLocalTime (), /* lastActivityDate */ reader.GetDateTime (7 + i).ToLocalTime (), /* lastPasswordChangedDate */ reader.GetDateTime (10 + i).ToLocalTime () /* lastLockoutDate */); } MembershipUser BuildMembershipUser (DbCommand query, string username, object userId) { try { using (DbConnection connection = CreateConnection ()) { query.Connection = connection; using (DbDataReader reader = query.ExecuteReader ()) { if (!reader.Read ()) return null; return GetUserFromReader (reader, username, userId); } } } catch (Exception) { return null; /* should we let the exception through? */ } finally { query.Connection = null; } } public override MembershipUser GetUser (string username, bool userIsOnline) { if (username == null) throw new ArgumentNullException ("username"); if (username.Length == 0) return null; CheckParam ("username", username, 256); DbCommand command = factory.CreateCommand (); command.CommandText = @"aspnet_Membership_GetUserByName"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@UserName", username); AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@CurrentTimeUtc", DateTime.Now); AddParameter (command, "@UpdateLastActivity", userIsOnline); MembershipUser u = BuildMembershipUser (command, username, null); return u; } public override MembershipUser GetUser (object providerUserKey, bool userIsOnline) { DbCommand command = factory.CreateCommand (); command.CommandText = @"aspnet_Membership_GetUserByUserId"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@UserId", providerUserKey); AddParameter (command, "@CurrentTimeUtc", DateTime.Now); AddParameter (command, "@UpdateLastActivity", userIsOnline); MembershipUser u = BuildMembershipUser (command, string.Empty, providerUserKey); return u; } public override string GetUserNameByEmail (string email) { CheckParam ("email", email, 256); using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_GetUserByEmail"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@Email", email); DbDataReader reader = command.ExecuteReader (); string rv = null; if (reader.Read ()) rv = reader.GetString (0); reader.Close (); return rv; } } bool GetBoolConfigValue (NameValueCollection config, string name, bool def) { bool rv = def; string val = config [name]; if (val != null) { try { rv = Boolean.Parse (val); } catch (Exception e) { throw new ProviderException (String.Format ("{0} must be true or false", name), e); } } return rv; } int GetIntConfigValue (NameValueCollection config, string name, int def) { int rv = def; string val = config [name]; if (val != null) { try { rv = Int32.Parse (val); } catch (Exception e) { throw new ProviderException (String.Format ("{0} must be an integer", name), e); } } return rv; } int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def) { int rv = def; string val = config [name]; if (val != null) { try { rv = (int) Enum.Parse (enumType, val); } catch (Exception e) { throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e); } } return rv; } string GetStringConfigValue (NameValueCollection config, string name, string def) { string rv = def; string val = config [name]; if (val != null) rv = val; return rv; } void EmitValidatingPassword (string username, string password, bool isNewUser) { ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser); OnValidatingPassword (args); /* if we're canceled.. */ if (args.Cancel) { if (args.FailureInformation == null) throw new ProviderException ("Password validation canceled"); else throw args.FailureInformation; } } public override void Initialize (string name, NameValueCollection config) { if (config == null) throw new ArgumentNullException ("config"); base.Initialize (name, config); applicationName = GetStringConfigValue (config, "applicationName", "/"); enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true); enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false); requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true); requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false); passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat), (int) MembershipPasswordFormat.Hashed); maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5); minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7); minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonalphanumericCharacters", 1); passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10); passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", ""); MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership"); userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow; /* we can't support password retrieval with hashed passwords */ if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval) throw new ProviderException ("password retrieval cannot be used with hashed passwords"); string connectionStringName = config ["connectionStringName"]; if (applicationName.Length > 256) throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less."); if (connectionStringName == null || connectionStringName.Length == 0) throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length."); connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName]; factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ? System.Data.SqlClient.SqlClientFactory.Instance : ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName); } public override string ResetPassword (string username, string answer) { if (!EnablePasswordReset) throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords"); CheckParam ("username", username, 256); if (RequiresQuestionAndAnswer) CheckParam ("answer", answer, 128); using (DbConnection connection = CreateConnection ()) { PasswordInfo pi = GetPasswordInfo (username); if (pi == null) throw new ProviderException (username + "is not found in the membership database"); string newPassword = GeneratePassword (); EmitValidatingPassword (username, newPassword, false); string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt); string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt); DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_ResetPassword"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@NewPassword", db_password); AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts); AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow); AddParameter (command, "@PasswordSalt", pi.PasswordSalt); AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow); AddParameter (command, "@PasswordFormat", (int) pi.PasswordFormat); AddParameter (command, "@PasswordAnswer", db_answer); DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); int returnValue = GetReturnValue (retValue); if (returnValue == 0) return newPassword; else if (returnValue == 3) throw new MembershipPasswordException ("Password Answer is invalid"); else if (returnValue == 99) throw new MembershipPasswordException ("The user account is currently locked out"); else throw new ProviderException ("Failed to reset password"); } } public override void UpdateUser (MembershipUser user) { if (user == null) throw new ArgumentNullException ("user"); if (user.UserName == null) throw new ArgumentNullException ("user.UserName"); if (RequiresUniqueEmail && user.Email == null) throw new ArgumentNullException ("user.Email"); CheckParam ("user.UserName", user.UserName, 256); if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0)) throw new ArgumentException ("invalid format for user.Email"); using (DbConnection connection = CreateConnection ()) { int returnValue = 0; DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_UpdateUser"; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", user.UserName); AddParameter (command, "@Email", user.Email == null ? (object) DBNull.Value : (object) user.Email); AddParameter (command, "@Comment", user.Comment == null ? (object) DBNull.Value : (object) user.Comment); AddParameter (command, "@IsApproved", user.IsApproved); AddParameter (command, "@LastLoginDate", DateTime.UtcNow); AddParameter (command, "@LastActivityDate", DateTime.UtcNow); AddParameter (command, "@UniqueEmail", RequiresUniqueEmail); AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow); DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); returnValue = GetReturnValue (retValue); if (returnValue == 1) throw new ProviderException ("The UserName property of user was not found in the database."); if (returnValue == 7) throw new ProviderException ("The Email property of user was equal to an existing e-mail address in the database and RequiresUniqueEmail is set to true."); if (returnValue != 0) throw new ProviderException ("Failed to update user"); } } public override bool ValidateUser (string username, string password) { if (username.Length == 0) return false; CheckParam ("username", username, 256); EmitValidatingPassword (username, password, false); PasswordInfo pi = ValidateUsingPassword (username, password); if (pi != null) { pi.LastLoginDate = DateTime.UtcNow; UpdateUserInfo (username, pi, true, true); return true; } return false; } public override bool UnlockUser (string username) { CheckParam ("username", username, 256); using (DbConnection connection = CreateConnection ()) { try { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_UnlockUser"; ; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); DbParameter returnValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); if (GetReturnValue (returnValue) != 0) return false; } catch (Exception e) { throw new ProviderException ("Failed to unlock user", e); } } return true; } void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity) { CheckParam ("username", username, 256); using (DbConnection connection = CreateConnection ()) { try { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandText = @"aspnet_Membership_UpdateUserInfo"; ; command.CommandType = CommandType.StoredProcedure; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@IsPasswordCorrect", isPasswordCorrect); AddParameter (command, "@UpdateLastLoginActivityDate", updateLoginActivity); AddParameter (command, "@MaxInvalidPasswordAttempts", MaxInvalidPasswordAttempts); AddParameter (command, "@PasswordAttemptWindow", PasswordAttemptWindow); AddParameter (command, "@CurrentTimeUtc", DateTime.UtcNow); AddParameter (command, "@LastLoginDate", pi.LastLoginDate); AddParameter (command, "@LastActivityDate", pi.LastActivityDate); DbParameter retValue = AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); command.ExecuteNonQuery (); int returnValue = GetReturnValue (retValue); if (returnValue != 0) return; } catch (Exception e) { throw new ProviderException ("Failed to update Membership table", e); } } } PasswordInfo ValidateUsingPassword (string username, string password) { MembershipUser user = GetUser (username, true); if (user == null) return null; if (!user.IsApproved || user.IsLockedOut) return null; PasswordInfo pi = GetPasswordInfo (username); if (pi == null) return null; /* do the actual validation */ string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt); if (user_password != pi.Password) { UpdateUserInfo (username, pi, false, false); return null; } return pi; } PasswordInfo GetPasswordInfo (string username) { using (DbConnection connection = CreateConnection ()) { DbCommand command = factory.CreateCommand (); command.Connection = connection; command.CommandType = CommandType.StoredProcedure; command.CommandText = @"aspnet_Membership_GetPasswordWithFormat"; AddParameter (command, "@ApplicationName", ApplicationName); AddParameter (command, "@UserName", username); AddParameter (command, "@UpdateLastLoginActivityDate", false); AddParameter (command, "@CurrentTimeUtc", DateTime.Now); // return value AddParameter (command, "@ReturnVal", ParameterDirection.ReturnValue, DbType.Int32, null); DbDataReader reader = command.ExecuteReader (); if (!reader.Read ()) return null; PasswordInfo pi = new PasswordInfo ( reader.GetString (0), (MembershipPasswordFormat) reader.GetInt32 (1), reader.GetString (2), reader.GetInt32 (3), reader.GetInt32 (4), reader.GetBoolean (5), reader.GetDateTime (6), reader.GetDateTime (7)); return pi; } } string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt) { byte [] password_bytes; byte [] salt_bytes; switch (passwordFormat) { case MembershipPasswordFormat.Clear: return password; case MembershipPasswordFormat.Hashed: password_bytes = Encoding.Unicode.GetBytes (password); salt_bytes = Convert.FromBase64String (salt); byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length]; Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length); Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length); MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership"); string alg_type = section.HashAlgorithmType; if (alg_type.Length == 0) { alg_type = MachineKeySection.Config.Validation.ToString (); // support new (4.0) custom algorithms if (alg_type.StartsWith ("alg:")) alg_type = alg_type.Substring (4); } using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) { // for compatibility (with 2.0) we'll allow MD5 and SHA1 not to map to HMACMD5 and HMACSHA1 // but that won't work with new (4.0) algorithms, like HMACSHA256|384|512 or custom, won't work without using the key KeyedHashAlgorithm kha = (hash as KeyedHashAlgorithm); if (kha != null) kha.Key = MachineKeySection.Config.GetValidationKey (); hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length); return Convert.ToBase64String (hash.Hash); } case MembershipPasswordFormat.Encrypted: password_bytes = Encoding.Unicode.GetBytes (password); salt_bytes = Convert.FromBase64String (salt); byte [] buf = new byte [password_bytes.Length + salt_bytes.Length]; Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length); Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length); return Convert.ToBase64String (EncryptPassword (buf)); default: /* not reached.. */ return null; } } string DecodePassword (string password, MembershipPasswordFormat passwordFormat) { switch (passwordFormat) { case MembershipPasswordFormat.Clear: return password; case MembershipPasswordFormat.Hashed: throw new ProviderException ("Hashed passwords cannot be decoded."); case MembershipPasswordFormat.Encrypted: return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password))); default: /* not reached.. */ return null; } } public override string ApplicationName { get { return applicationName; } set { applicationName = value; } } public override bool EnablePasswordReset { get { return enablePasswordReset; } } public override bool EnablePasswordRetrieval { get { return enablePasswordRetrieval; } } public override MembershipPasswordFormat PasswordFormat { get { return passwordFormat; } } public override bool RequiresQuestionAndAnswer { get { return requiresQuestionAndAnswer; } } public override bool RequiresUniqueEmail { get { return requiresUniqueEmail; } } public override int MaxInvalidPasswordAttempts { get { return maxInvalidPasswordAttempts; } } public override int MinRequiredNonAlphanumericCharacters { get { return minRequiredNonAlphanumericCharacters; } } public override int MinRequiredPasswordLength { get { return minRequiredPasswordLength; } } public override int PasswordAttemptWindow { get { return passwordAttemptWindow; } } public override string PasswordStrengthRegularExpression { get { return passwordStrengthRegularExpression; } } [Flags] enum DeleteUserTableMask { MembershipUsers = 1, UsersInRoles = 2, Profiles = 4, WebPartStateUser = 8 } sealed class PasswordInfo { string _password; MembershipPasswordFormat _passwordFormat; string _passwordSalt; int _failedPasswordAttemptCount; int _failedPasswordAnswerAttemptCount; bool _isApproved; DateTime _lastLoginDate; DateTime _lastActivityDate; internal PasswordInfo ( string password, MembershipPasswordFormat passwordFormat, string passwordSalt, int failedPasswordAttemptCount, int failedPasswordAnswerAttemptCount, bool isApproved, DateTime lastLoginDate, DateTime lastActivityDate) { _password = password; _passwordFormat = passwordFormat; _passwordSalt = passwordSalt; _failedPasswordAttemptCount = failedPasswordAttemptCount; _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount; _isApproved = isApproved; _lastLoginDate = lastLoginDate; _lastActivityDate = lastActivityDate; } public string Password { get { return _password; } set { _password = value; } } public MembershipPasswordFormat PasswordFormat { get { return _passwordFormat; } set { _passwordFormat = value; } } public string PasswordSalt { get { return _passwordSalt; } set { _passwordSalt = value; } } public int FailedPasswordAttemptCount { get { return _failedPasswordAttemptCount; } set { _failedPasswordAttemptCount = value; } } public int FailedPasswordAnswerAttemptCount { get { return _failedPasswordAnswerAttemptCount; } set { _failedPasswordAnswerAttemptCount = value; } } public bool IsApproved { get { return _isApproved; } set { _isApproved = value; } } public DateTime LastLoginDate { get { return _lastLoginDate; } set { _lastLoginDate = value; } } public DateTime LastActivityDate { get { return _lastActivityDate; } set { _lastActivityDate = value; } } } } }