Merge remote-tracking branch 'origin/6.2.0-membershipprovider' into 7.0.0

Conflicts:
	src/Umbraco.Web.UI/umbraco/controls/passwordChanger.ascx
	src/Umbraco.Web/Security/WebSecurity.cs
	src/umbraco.providers/UsersMembershipProvider.cs
	src/umbraco.providers/members/MembersMembershipProvider.cs
This commit is contained in:
Shannon
2013-10-23 10:51:06 +11:00
6 changed files with 223 additions and 73 deletions

View File

@@ -15,6 +15,40 @@ namespace Umbraco.Core.Security
/// </summary>
public abstract class MembershipProviderBase : MembershipProvider
{
/// <summary>
/// Providers can override this setting, default is 7
/// </summary>
protected virtual int DefaultMinPasswordLength
{
get { return 7; }
}
/// <summary>
/// Providers can override this setting, default is 1
/// </summary>
protected virtual int DefaultMinNonAlphanumericChars
{
get { return 1; }
}
/// <summary>
/// Providers can override this setting, default is false to use better security
/// </summary>
protected virtual bool DefaultUseLegacyEncoding
{
get { return false; }
}
/// <summary>
/// Providers can override this setting, by default this is false which means that the provider will
/// authenticate the username + password when ChangePassword is called. This property exists purely for
/// backwards compatibility.
/// </summary>
internal virtual bool AllowManuallyChangingPassword
{
get { return false; }
}
private string _applicationName;
private bool _enablePasswordReset;
private bool _enablePasswordRetrieval;
@@ -177,8 +211,8 @@ namespace Umbraco.Core.Security
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", false);
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
_passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", 7, true, 0x80);
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80);
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80);
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80);
_passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
_applicationName = config["applicationName"];
@@ -186,7 +220,7 @@ namespace Umbraco.Core.Security
_applicationName = GetDefaultAppName();
//by default we will continue using the legacy encoding.
_useLegacyEncoding = config.GetValue("useLegacyEncoding", true);
_useLegacyEncoding = config.GetValue("useLegacyEncoding", DefaultUseLegacyEncoding);
// make sure password format is clear by default.
string str = config["passwordFormat"] ?? "Clear";
@@ -238,6 +272,26 @@ namespace Umbraco.Core.Security
Strength
}
/// <summary>
/// Checks to ensure the AllowManuallyChangingPassword rule is adhered to
/// </summary>
/// <param name="username"></param>
/// <param name="oldPassword"></param>
/// <param name="newPassword"></param>
/// <returns></returns>
public sealed override bool ChangePassword(string username, string oldPassword, string newPassword)
{
if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false)
{
//If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password
throw new NotSupportedException("This provider does not support manually changing the password");
}
return PerformChangePassword(username, oldPassword, newPassword);
}
protected abstract bool PerformChangePassword(string username, string oldPassword, string newPassword);
protected internal static Attempt<PasswordValidityError> IsPasswordValid(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength)
{
if (minRequiredNonAlphanumericChars > 0)

View File

@@ -77,7 +77,7 @@
</asp:PlaceHolder>
<div id="passwordInputArea">
<asp:PlaceHolder runat="server" ID="CurrentPasswordPlaceHolder" Visible="<%#Provider.EnablePasswordRetrieval == false %>">
<asp:PlaceHolder runat="server" ID="CurrentPasswordPlaceHolder" Visible="<%#ShowOldPassword %>">
<div class="umb-el-wrap ">
<label class="control-label" for="<%=umbPasswordChanger_passwordCurrent.ClientID %>"><%=umbraco.ui.GetText("user", "passwordCurrent")%></label>
<div class="controls controls-row">

View File

@@ -232,78 +232,95 @@ namespace Umbraco.Web.Security
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) });
}
}
if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false)
//we're not resetting it so we need to try to change it.
if (passwordModel.NewPassword.IsNullOrWhiteSpace())
{
//we're not resetting it so we need to try to change it.
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) });
}
if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false)
//This is an edge case and is only necessary for backwards compatibility:
var umbracoBaseProvider = membershipProvider as MembershipProviderBase;
if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword)
{
//this provider allows manually changing the password without the old password, so we can just do it
try
{
//if password retrieval is not enabled but there is no old password we cannot continue
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) });
var result = umbracoBaseProvider.ChangePassword(username, "", passwordModel.NewPassword);
return result == false
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) })
: Attempt.Succeed(new PasswordChangedModel());
}
if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false)
catch (Exception ex)
{
//if an old password is suplied try to change it
try
{
var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword);
if (result == false)
{
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) });
}
}
catch (Exception ex)
{
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
}
}
else if (membershipProvider.EnablePasswordRetrieval == false)
{
//we cannot continue if we cannot get the current password
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) });
}
else if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace())
{
//if the question answer is required but there isn't one, we cannot continue
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) });
}
else
{
//lets try to get the old one so we can change it
try
{
var oldPassword = membershipProvider.GetPassword(
username,
membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null);
try
{
var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword);
if (result == false)
{
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) });
}
}
catch (Exception ex1)
{
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex1);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) });
}
}
catch (Exception ex2)
{
LogHelper.WarnWithException<WebSecurity>("Could not retrieve member password", ex2);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
}
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
}
}
//woot!
return Attempt.Succeed(new PasswordChangedModel());
//The provider does not support manually chaning the password but no old password supplied - need to return an error
if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false)
{
//if password retrieval is not enabled but there is no old password we cannot continue
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) });
}
if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false)
{
//if an old password is suplied try to change it
try
{
var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword);
return result == false
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) })
: Attempt.Succeed(new PasswordChangedModel());
}
catch (Exception ex)
{
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) });
}
}
if (membershipProvider.EnablePasswordRetrieval == false)
{
//we cannot continue if we cannot get the current password
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "value" }) });
}
if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace())
{
//if the question answer is required but there isn't one, we cannot continue
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) });
}
//lets try to get the old one so we can change it
try
{
var oldPassword = membershipProvider.GetPassword(
username,
membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null);
try
{
var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword);
return result == false
? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) })
: Attempt.Succeed(new PasswordChangedModel());
}
catch (Exception ex1)
{
LogHelper.WarnWithException<WebSecurity>("Could not change member password", ex1);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) });
}
}
catch (Exception ex2)
{
LogHelper.WarnWithException<WebSecurity>("Could not retrieve member password", ex2);
return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) });
}
}
/// <summary>

View File

@@ -10,6 +10,7 @@ using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Web.Models;
namespace umbraco.controls
@@ -23,6 +24,23 @@ namespace umbraco.controls
get { return Membership.Providers[MembershipProviderName]; }
}
/// <summary>
/// Determines whether to show the old password field or not
/// </summary>
protected bool ShowOldPassword
{
get
{
var umbProvider = Provider as MembershipProviderBase;
if (umbProvider != null && umbProvider.AllowManuallyChangingPassword)
{
return false;
}
return Provider.EnablePasswordRetrieval == false;
}
}
public bool IsChangingPassword
{
get

View File

@@ -18,6 +18,30 @@ namespace umbraco.providers
/// </summary>
public class UsersMembershipProvider : MembershipProviderBase
{
/// <summary>
/// Override to maintain backwards compatibility with 0 required non-alphanumeric chars
/// </summary>
protected override int DefaultMinNonAlphanumericChars
{
get { return 0; }
}
/// <summary>
/// Override to maintain backwards compatibility with only 4 required length
/// </summary>
protected override int DefaultMinPasswordLength
{
get { return 4; }
}
/// <summary>
/// Override to maintain backwards compatibility
/// </summary>
protected override bool DefaultUseLegacyEncoding
{
get { return true; }
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
if (config == null) throw new ArgumentNullException("config");
@@ -41,8 +65,10 @@ namespace umbraco.providers
/// During installation the application will not be configured, if this is the case and the 'default' password
/// is stored in the database then we will validate the user - this will allow for an admin password reset if required
/// </remarks>
public override bool ChangePassword(string username, string oldPassword, string newPassword)
protected override bool PerformChangePassword(string username, string oldPassword, string newPassword)
{
if (ApplicationContext.Current.IsConfigured == false && oldPassword == "default"
|| ValidateUser(username, oldPassword))
{

View File

@@ -66,6 +66,38 @@ namespace umbraco.providers.members
}
#endregion
/// <summary>
/// Override to maintain backwards compatibility with 0 required non-alphanumeric chars
/// </summary>
protected override int DefaultMinNonAlphanumericChars
{
get { return 0; }
}
/// <summary>
/// Override to maintain backwards compatibility with only 4 required length
/// </summary>
protected override int DefaultMinPasswordLength
{
get { return 4; }
}
/// <summary>
/// Override to maintain backwards compatibility
/// </summary>
protected override bool DefaultUseLegacyEncoding
{
get { return true; }
}
/// <summary>
/// For backwards compatibility, this provider supports this option
/// </summary>
internal override bool AllowManuallyChangingPassword
{
get { return true; }
}
#region Initialization Method
/// <summary>
@@ -148,16 +180,19 @@ namespace umbraco.providers.members
/// Processes a request to update the password for a membership user.
/// </summary>
/// <param name="username">The user to update the password for.</param>
/// <param name="oldPassword">The current password for the specified user.</param>
/// <param name="oldPassword">This property is ignore for this provider</param>
/// <param name="newPassword">The new password for the specified user.</param>
/// <returns>
/// true if the password was updated successfully; otherwise, false.
/// </returns>
public override bool ChangePassword(string username, string oldPassword, string newPassword)
protected override bool PerformChangePassword(string username, string oldPassword, string newPassword)
{
//NOTE: due to backwards compatibilty reasons, this provider doesn't care about the old password and
// allows simply setting the password manually so we don't really care about the old password.
// This is allowed based on the overridden AllowManuallyChangingPassword option.
// in order to support updating passwords from the umbraco core, we can't validate the old password
var m = Member.GetMemberFromLoginNameAndPassword(username, oldPassword);
var m = Member.GetMemberFromLoginName(username);
if (m == null) return false;
var args = new ValidatePasswordEventArgs(username, newPassword, false);