/// <summary>
/// Checks password complexity requirements for the actual membership provider
/// </summary>
/// <param name="password">password to check</param>
/// <returns>true if the password meets the req. complexity</returns>
static public bool CheckPasswordComplexity(string password)
{
return CheckPasswordComplexity(Membership.Provider, password);
}
/// <summary>
/// Checks password complexity requirements for the given membership provider
/// </summary>
/// <param name="membershipProvider">membership provider</param>
/// <param name="password">password to check</param>
/// <returns>true if the password meets the req. complexity</returns>
static public bool CheckPasswordComplexity(MembershipProvider membershipProvider, string password)
{
if (string.IsNullOrEmpty(password)) return false;
if (password.Length < membershipProvider.MinRequiredPasswordLength) return false;
int nonAlnumCount = 0;
for (int i = 0; i < password.Length; i++)
{
if (!char.IsLetterOrDigit(password, i)) nonAlnumCount++;
}
if (nonAlnumCount < membershipProvider.MinRequiredNonAlphanumericCharacters) return false;
if (!string.IsNullOrEmpty(membershipProvider.PasswordStrengthRegularExpression) &&
!Regex.IsMatch(password, membershipProvider.PasswordStrengthRegularExpression))
{
return false;
}
return true;
}
OK, so I finally figured a workaround. It is not the cleanest, but the only one I could come up with or found anywhere that was close to what should have been inclued by default.
Note: I did not have the option to persist this in a database or a cookie.
I created a ChangePasswordNew
method that has the same behavior as the MembershipProvider
ChangePassword
method, but returns string instead of bool. So my new method would look like this:
public string ChangePasswordNew(string username, string oldPassword, string newPassword)
{
//if password rules met, change password but do not return bool as MembershipProvider method does,
//return Success or the exact error for failure instead
if(passwordChangeRequirementsMet == true)
{
//change the password
return "success";
}
else
return "exact reason why password cannot be changed";
}
Now, subscribe to the onPasswordChanging
event of the ChangePassword
Control:
protected void PasswordIsChanging(object sender, LoginCancelEventArgs e)
{
try
{
string response = Provider.ChangePasswordNew(username, currentPass, newPass);
if(response != "success")
{
changePwdCtrl.FailureText = response;
}
else
{
//cancel call to the default membership provider method that will attempt to
//change the password again. Instead, replicate the 'steps' of AttemptChangePassword(),
//an internal method of the ChangePassword control.
//Performing all the steps instead of calling the method because just calling method
//does not work for some reason
e.Cancel = true;
FormsAuthentication.SetAuthCookie(username, false);
OnPasswordChanged(sender, e);
MethodInfo successMethodInfo = changePwdCtrl.GetType().GetMethod("PerformSuccessAction", BindingFlags.NonPublic | BindingFlags.Instance);
successMethodInfo.Invoke(changePwdCtrl, new object[] { "", "", changePwdCtrl.NewPassword });
}
}
catch(Exception ex)
{
LogException(ex);
throw;
}
}
Note: In this case, if there is an error on password change, i.e. response is not "success", the Memebership Provider ChangePassword
method will still be called and will return an error again. But I am already setting the FailureText
property to the descriptive error returned from response in the first call, which was my objective. I did not mind two method calls in my case. Your case may be different.
Hope this helps someone!
Best Answer
Well, this has been answered before. The trick is to add a second custom provider and use that in the pages that are accessible for the administrator.
http://peterkellner.net/2007/02/15/resetpasswordaspnet/